@ryuenn3123/agentic-senior-core 3.0.19 → 3.0.20
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/.agent-context/prompts/bootstrap-design.md +84 -103
- package/.agent-context/prompts/init-project.md +32 -100
- package/.agent-context/prompts/refactor.md +22 -44
- package/.agent-context/prompts/review-code.md +28 -52
- package/.agent-context/review-checklists/architecture-review.md +31 -62
- package/.agent-context/review-checklists/pr-checklist.md +74 -108
- package/.agent-context/rules/api-docs.md +18 -206
- package/.agent-context/rules/architecture.md +40 -207
- package/.agent-context/rules/database-design.md +10 -199
- package/.agent-context/rules/docker-runtime.md +5 -5
- package/.agent-context/rules/efficiency-vs-hype.md +11 -149
- package/.agent-context/rules/error-handling.md +9 -231
- package/.agent-context/rules/event-driven.md +17 -221
- package/.agent-context/rules/frontend-architecture.md +66 -119
- package/.agent-context/rules/git-workflow.md +1 -1
- package/.agent-context/rules/microservices.md +28 -161
- package/.agent-context/rules/naming-conv.md +8 -138
- package/.agent-context/rules/performance.md +9 -175
- package/.agent-context/rules/realtime.md +11 -44
- package/.agent-context/rules/security.md +11 -295
- package/.agent-context/rules/testing.md +9 -174
- package/.agent-context/state/benchmark-analysis.json +3 -3
- package/.agent-context/state/memory-continuity-benchmark.json +1 -1
- package/.agent-context/state/onboarding-report.json +71 -11
- package/.agents/workflows/init-project.md +7 -24
- package/.agents/workflows/refactor.md +7 -24
- package/.agents/workflows/review-code.md +7 -24
- package/.cursorrules +22 -21
- package/.gemini/instructions.md +2 -2
- package/.github/copilot-instructions.md +2 -2
- package/.instructions.md +112 -213
- package/.windsurfrules +22 -21
- package/AGENTS.md +4 -4
- package/CONTRIBUTING.md +13 -22
- package/README.md +6 -20
- package/lib/cli/commands/init.mjs +102 -148
- package/lib/cli/commands/launch.mjs +3 -3
- package/lib/cli/commands/optimize.mjs +14 -4
- package/lib/cli/commands/upgrade.mjs +25 -23
- package/lib/cli/compiler.mjs +96 -62
- package/lib/cli/constants.mjs +28 -136
- package/lib/cli/detector/design-evidence.mjs +189 -6
- package/lib/cli/detector.mjs +6 -7
- package/lib/cli/init-detection-flow.mjs +10 -93
- package/lib/cli/init-selection.mjs +2 -68
- package/lib/cli/project-scaffolder/constants.mjs +1 -1
- package/lib/cli/project-scaffolder/design-contract.mjs +162 -108
- package/lib/cli/project-scaffolder/discovery.mjs +36 -82
- package/lib/cli/project-scaffolder/prompt-builders.mjs +41 -55
- package/lib/cli/project-scaffolder/storage.mjs +0 -2
- package/lib/cli/token-optimization.mjs +1 -1
- package/lib/cli/utils.mjs +75 -9
- package/package.json +2 -2
- package/scripts/detection-benchmark.mjs +4 -15
- package/scripts/documentation-boundary-audit.mjs +9 -9
- package/scripts/explain-on-demand-audit.mjs +11 -11
- package/scripts/forbidden-content-check.mjs +9 -9
- package/scripts/frontend-usability-audit.mjs +45 -35
- package/scripts/llm-judge.mjs +1 -1
- package/scripts/mcp-server/constants.mjs +2 -2
- package/scripts/mcp-server/tool-registry.mjs +1 -1
- package/scripts/release-gate/audit-checks.mjs +4 -4
- package/scripts/release-gate/static-checks.mjs +5 -5
- package/scripts/release-gate.mjs +1 -1
- package/scripts/rules-guardian-audit.mjs +14 -13
- package/scripts/single-source-lazy-loading-audit.mjs +3 -3
- package/scripts/sync-thin-adapters.mjs +5 -5
- package/scripts/ui-design-judge/design-execution-summary.mjs +27 -1
- package/scripts/ui-design-judge/prompting.mjs +4 -4
- package/scripts/ui-design-judge/reporting.mjs +2 -1
- package/scripts/ui-design-judge/rubric-calibration.mjs +8 -5
- package/scripts/ui-design-judge/rubric-goldset.json +2 -2
- package/scripts/ui-design-judge.mjs +70 -6
- package/scripts/validate/config.mjs +138 -48
- package/scripts/validate/coverage-checks.mjs +32 -7
- package/scripts/validate.mjs +8 -4
- package/lib/cli/architect.mjs +0 -431
|
@@ -1,154 +1,16 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
> Every dependency is a liability. Every `npm install` is a trust decision.
|
|
4
|
-
> The best dependency is the one you don't need.
|
|
1
|
+
# Dependency and Tooling Boundary
|
|
5
2
|
|
|
6
3
|
## Latest-Compatible-First Rule
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
- If the framework has an official scaffolder or setup command that produces current defaults, prefer that path over manually assembling stale `package.json` entries one by one.
|
|
10
|
-
- Only step down to an older dependency version after documenting the exact compatibility, runtime, platform, or ecosystem reason.
|
|
11
|
-
- Treat release notes, official docs, and framework migration guides as the primary source of truth for dependency setup.
|
|
12
|
-
|
|
13
|
-
## The Dependency Decision Framework
|
|
14
|
-
|
|
15
|
-
Before adding ANY dependency, answer these 5 questions:
|
|
16
|
-
|
|
17
|
-
### 1. Can You Do It Without a Library? (The stdlib-first Rule)
|
|
18
|
-
```
|
|
19
|
-
❌ OVER-ENGINEERING:
|
|
20
|
-
npm install is-odd # 1 line of code: n % 2 !== 0
|
|
21
|
-
npm install left-pad # Already in String.prototype.padStart
|
|
22
|
-
npm install is-number # typeof x === 'number' && !isNaN(x)
|
|
23
|
-
npm install array-flatten # Array.prototype.flat() exists since ES2019
|
|
24
|
-
|
|
25
|
-
✅ USE THE LANGUAGE:
|
|
26
|
-
const isOdd = (n: number) => n % 2 !== 0;
|
|
27
|
-
const padded = str.padStart(10, '0');
|
|
28
|
-
const flat = nested.flat(Infinity);
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
**Dependency Defense:** If the user asks to install a new library, or if you feel the need to use one, evaluate it against the "stdlib-first" rule. If the functionality can be implemented safely in under 20 lines of code, write it yourself. If a dependency is strictly necessary, you MUST justify it by providing its bundle size, maintenance status, why the standard library is insufficient, and why the chosen version is the latest stable compatible option.
|
|
32
|
-
|
|
33
|
-
### 2. Is It Maintained? (The Pulse Check)
|
|
34
|
-
| Signal | 🟢 Healthy | 🔴 Dead |
|
|
35
|
-
|--------|-----------|---------|
|
|
36
|
-
| Last commit | < 6 months ago | > 2 years ago |
|
|
37
|
-
| Open issues | Actively triaged | 500+ unread |
|
|
38
|
-
| Downloads/week | Growing or stable | Declining |
|
|
39
|
-
| Bus factor | > 3 maintainers | 1 maintainer, inactive |
|
|
40
|
-
| Security | No known CVEs | Unpatched vulnerabilities |
|
|
41
|
-
|
|
42
|
-
**Rule:** If a library has a bus factor of 1 and no commit in 12+ months, it is a risk. Find an alternative or vendor-fork it.
|
|
43
|
-
|
|
44
|
-
### 3. What's the Cost? (The Weight Check)
|
|
45
|
-
```
|
|
46
|
-
Before: npm install moment # 289KB minified, entire library for date formatting
|
|
47
|
-
After: npm install date-fns # 12KB for just the functions you use (tree-shakeable)
|
|
48
|
-
Better: Intl.DateTimeFormat # 0KB — built into the runtime
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
**Rule:** Check the bundle impact. Use [bundlephobia.com](https://bundlephobia.com) for JS packages. If a library adds > 50KB to your bundle for a simple feature, find a lighter alternative or implement it yourself.
|
|
52
|
-
|
|
53
|
-
### 4. Is It the Community Standard? (The Ecosystem Rule)
|
|
54
|
-
Prefer packages that the ecosystem has settled on:
|
|
55
|
-
|
|
56
|
-
| Need | ✅ Standard | ❌ Avoid |
|
|
57
|
-
|------|-----------|---------|
|
|
58
|
-
| HTTP client (Node) | `undici` (built-in) / native `fetch` / `ky` | `axios` (declining, CVE-2025-58754, bloated) |
|
|
59
|
-
| Validation | `zod` | `joi` (heavier), `yup` (less type-safe) |
|
|
60
|
-
| ORM (Node) | `prisma`, `drizzle` | `sequelize` (legacy API), `typeorm` (decorator hell) |
|
|
61
|
-
| Date handling | `date-fns`, `dayjs`, `Temporal` (when stable) | `moment` (deprecated, massive) |
|
|
62
|
-
| Testing | `vitest` (new projects), `jest` (existing) | `mocha` + `chai` + `sinon` (3 deps for what 1 does) |
|
|
63
|
-
| Password hashing | `argon2` (OWASP primary), `bcrypt` (legacy) | Custom crypto (NEVER) |
|
|
64
|
-
| Env loading | `dotenv` (if needed) | Custom `.env` parser |
|
|
65
|
-
|
|
66
|
-
**Note:** These are current recommendations as of March 2026. Evaluate against this framework; don't blindly follow.
|
|
67
|
-
|
|
68
|
-
### 5. Can You Remove It Later? (The Exit Strategy)
|
|
69
|
-
```
|
|
70
|
-
❌ HIGH LOCK-IN:
|
|
71
|
-
// Decorators from a specific framework scattered across 200 files
|
|
72
|
-
@Controller() @Injectable() @Guard() // In every file — framework is your entire architecture
|
|
73
|
-
|
|
74
|
-
✅ LOW LOCK-IN:
|
|
75
|
-
// Framework stays at the edges; business logic is framework-free
|
|
76
|
-
// Switching from Express to Fastify only changes the transport layer
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
**Rule:** Wrap external dependencies that touch > 5 files. If you need to replace a library, it should only affect the wrapper, not your entire codebase.
|
|
5
|
+
The LLM may choose modern libraries and tooling when they fit the project. This rule does not prefer "no library" or any fixed dependency set.
|
|
80
6
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
2. **Library does one thing** that's 5 lines of code → REJECT: Write it yourself
|
|
90
|
-
3. **Library is in alpha/beta** with < 1 year of releases → REJECT: Not production-ready
|
|
91
|
-
4. **Library has a cooler API** but the current one works fine → REJECT: "Cool" is not a business requirement
|
|
92
|
-
5. **"Everyone is using it"** → SO WHAT: Popularity is not a quality signal
|
|
93
|
-
|
|
94
|
-
### The Agent Must Justify Dependencies
|
|
95
|
-
When an AI agent suggests adding a new dependency, it MUST provide:
|
|
96
|
-
```
|
|
97
|
-
📦 DEPENDENCY JUSTIFICATION
|
|
98
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
99
|
-
Package: [name@version]
|
|
100
|
-
Purpose: [what it does in 1 sentence]
|
|
101
|
-
Stdlib Alternative: [why it's insufficient — or "none"]
|
|
102
|
-
Bundle Size: [minified + gzipped]
|
|
103
|
-
Maintenance: [last release date, maintainer count]
|
|
104
|
-
Version Source: [official docs, release notes, or framework reference]
|
|
105
|
-
Lock-in Risk: [low/medium/high — how many files would it touch]
|
|
106
|
-
Exit Strategy: [how to remove it if needed]
|
|
107
|
-
Fallback Reason: [only required when not using the latest stable compatible version]
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
|
-
## Dependency Update Strategy
|
|
113
|
-
|
|
114
|
-
1. **Start from latest stable compatible versions** — older versions need a written reason
|
|
115
|
-
2. **Prefer official framework setup flows** when they yield newer, canonical dependency sets
|
|
116
|
-
3. **Pin exact versions** in lockfiles (already default with package-lock.json, bun.lockb)
|
|
117
|
-
4. **Review changelogs** before major updates — never blindly `npm update`
|
|
118
|
-
5. **Update in isolation** — one dependency per PR, with tests passing
|
|
119
|
-
6. **Security patches immediately** — don't wait for the sprint
|
|
120
|
-
7. **Monthly audit** — run `npm audit` / `bun audit` and address findings
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
## The Zero-Dependency Ideal
|
|
125
|
-
|
|
126
|
-
The best packages are those with zero or minimal dependencies themselves:
|
|
127
|
-
- `zod` — 0 dependencies ✅
|
|
128
|
-
- `date-fns` — 0 dependencies ✅
|
|
129
|
-
- `nanoid` — 0 dependencies ✅
|
|
130
|
-
- `bcrypt` — 1 native dependency (justified for crypto) ✅
|
|
131
|
-
|
|
132
|
-
A package that pulls in 50 transitive dependencies for a simple feature is a supply chain attack waiting to happen.
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
## Quick Decision Tree
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
Do I need this functionality?
|
|
140
|
-
→ No → Don't install anything
|
|
141
|
-
→ Yes ↓
|
|
142
|
-
|
|
143
|
-
Can I write it in < 20 lines?
|
|
144
|
-
→ Yes → Write it yourself
|
|
145
|
-
→ No ↓
|
|
146
|
-
|
|
147
|
-
Does the language/runtime provide it?
|
|
148
|
-
→ Yes → Use the built-in
|
|
149
|
-
→ No ↓
|
|
7
|
+
Before adding or recommending a dependency:
|
|
8
|
+
- check current official docs, release notes, and setup guidance when the ecosystem decision matters
|
|
9
|
+
- choose the latest stable compatible dependency version unless a project constraint blocks it
|
|
10
|
+
- use the official scaffolder or setup command when it creates the current supported project shape
|
|
11
|
+
- Only step down to an older dependency version after documenting the exact compatibility, runtime, platform, or ecosystem reason.
|
|
12
|
+
- explain why the dependency improves maintainability, UX, performance, security, or delivery speed
|
|
13
|
+
- avoid packages that are stale, thinly maintained, too heavy for the job, or added only because they are popular
|
|
14
|
+
- keep dependency boundaries replaceable when the library would spread through many files
|
|
150
15
|
|
|
151
|
-
|
|
152
|
-
→ Yes → Install it, add justification comment
|
|
153
|
-
→ No → Build a minimal internal implementation, consider vendoring
|
|
154
|
-
```
|
|
16
|
+
Reject offline dependency decisions, outdated tutorial versions, and trend choices that are not grounded in the current repo and brief.
|
|
@@ -1,234 +1,12 @@
|
|
|
1
|
-
# Error Handling
|
|
1
|
+
# Error Handling Boundary
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> When it explodes at 3 AM, you won't know where to look.
|
|
3
|
+
Use the target language and framework's normal error model. Do not invent a custom exception architecture from this repo.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
Reject these failure patterns:
|
|
6
|
+
- swallowed errors
|
|
7
|
+
- generic errors that erase the domain cause
|
|
8
|
+
- client-facing leaks of stack traces, secrets, SQL, infrastructure details, or provider internals
|
|
9
|
+
- retries without transient-failure evidence, limits, backoff, and a clear final outcome
|
|
10
|
+
- logs that say only "something failed" without action, target, actor, or trace context
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
2. **Always add context.** A stack trace is not enough — include WHAT was happening and WITH WHAT data.
|
|
10
|
-
3. **Fail fast at boundaries.** Validate early, reject bad data before it travels deep into the system.
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Swallowed Error Detection (Instant Rejection)
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
❌ DEATH PENALTY: Empty catch block
|
|
18
|
-
try {
|
|
19
|
-
await processPayment(order);
|
|
20
|
-
} catch (error) {
|
|
21
|
-
// silently swallowed — payment may have failed, user has no idea
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
❌ DEATH PENALTY: Catch and log only (no re-throw or recovery)
|
|
25
|
-
try {
|
|
26
|
-
await processPayment(order);
|
|
27
|
-
} catch (error) {
|
|
28
|
-
console.log('error:', error); // Logged to a void nobody reads
|
|
29
|
-
// Execution continues as if nothing happened
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
✅ CORRECT: Handle, log with context, re-throw or recover
|
|
33
|
-
try {
|
|
34
|
-
await processPayment(order);
|
|
35
|
-
} catch (error) {
|
|
36
|
-
logger.error('Payment processing failed', {
|
|
37
|
-
orderId: order.id,
|
|
38
|
-
userId: order.userId,
|
|
39
|
-
amount: order.total,
|
|
40
|
-
error: error instanceof Error ? error.message : String(error),
|
|
41
|
-
});
|
|
42
|
-
throw new PaymentFailedError(order.id, { cause: error });
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## Typed Error Codes
|
|
49
|
-
|
|
50
|
-
### Rule: Use Explicit Error Types, Not Generic Errors
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
❌ BANNED: Generic errors
|
|
54
|
-
throw new Error('Not found');
|
|
55
|
-
throw new Error('Permission denied');
|
|
56
|
-
throw new Error('Invalid input');
|
|
57
|
-
|
|
58
|
-
✅ REQUIRED: Typed, domain-specific errors
|
|
59
|
-
throw new NotFoundError('User', userId);
|
|
60
|
-
throw new ForbiddenError('Cannot delete other user\'s order');
|
|
61
|
-
throw new ValidationError('Email format is invalid', { field: 'email' });
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Error Class Pattern (Any Language)
|
|
65
|
-
```typescript
|
|
66
|
-
// Base application error
|
|
67
|
-
class AppError extends Error {
|
|
68
|
-
constructor(
|
|
69
|
-
message: string,
|
|
70
|
-
public readonly code: string,
|
|
71
|
-
public readonly statusCode: number,
|
|
72
|
-
public readonly context?: Record<string, unknown>,
|
|
73
|
-
) {
|
|
74
|
-
super(message);
|
|
75
|
-
this.name = this.constructor.name;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Specific errors
|
|
80
|
-
class NotFoundError extends AppError {
|
|
81
|
-
constructor(resource: string, id: string | number) {
|
|
82
|
-
super(`${resource} not found: ${id}`, 'NOT_FOUND', 404, { resource, id });
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
class ValidationError extends AppError {
|
|
87
|
-
constructor(message: string, context?: Record<string, unknown>) {
|
|
88
|
-
super(message, 'VALIDATION_ERROR', 400, context);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
class ForbiddenError extends AppError {
|
|
93
|
-
constructor(reason: string) {
|
|
94
|
-
super(reason, 'FORBIDDEN', 403);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## Structured Logging
|
|
102
|
-
|
|
103
|
-
### Rule: Logs Must Include Context
|
|
104
|
-
|
|
105
|
-
```
|
|
106
|
-
❌ USELESS:
|
|
107
|
-
logger.error('Something went wrong');
|
|
108
|
-
logger.info('Processing...');
|
|
109
|
-
console.log(error);
|
|
110
|
-
|
|
111
|
-
✅ USEFUL:
|
|
112
|
-
logger.error('Order processing failed', {
|
|
113
|
-
traceId: req.traceId,
|
|
114
|
-
userId: currentUser.id,
|
|
115
|
-
orderId: order.id,
|
|
116
|
-
action: 'processOrder',
|
|
117
|
-
error: error.message,
|
|
118
|
-
stack: error.stack,
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
logger.info('Payment completed', {
|
|
122
|
-
traceId: req.traceId,
|
|
123
|
-
orderId: order.id,
|
|
124
|
-
amount: payment.amount,
|
|
125
|
-
provider: payment.provider,
|
|
126
|
-
durationMs: Date.now() - startTime,
|
|
127
|
-
});
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Required Log Fields
|
|
131
|
-
| Field | When | Purpose |
|
|
132
|
-
|-------|------|---------|
|
|
133
|
-
| `traceId` / `requestId` | Always (in request context) | Correlate logs across services |
|
|
134
|
-
| `userId` | When authenticated | Who triggered the action |
|
|
135
|
-
| `action` | Always | What was happening |
|
|
136
|
-
| `error` + `stack` | On errors | What went wrong |
|
|
137
|
-
| `durationMs` | On slow operations | Performance visibility |
|
|
138
|
-
|
|
139
|
-
---
|
|
140
|
-
|
|
141
|
-
## Error Response Format (APIs)
|
|
142
|
-
|
|
143
|
-
### Rule: NEVER Leak Internal Details
|
|
144
|
-
|
|
145
|
-
```
|
|
146
|
-
❌ BANNED response to client:
|
|
147
|
-
{
|
|
148
|
-
"error": "ER_NO_SUCH_TABLE: Table 'mydb.user_sessions' doesn't exist",
|
|
149
|
-
"stack": "Error: at Query.execute (/app/node_modules/mysql..."
|
|
150
|
-
}
|
|
151
|
-
// Congratulations, you just told the attacker your DB name and framework
|
|
152
|
-
|
|
153
|
-
✅ REQUIRED response to client:
|
|
154
|
-
{
|
|
155
|
-
"error": {
|
|
156
|
-
"code": "INTERNAL_ERROR",
|
|
157
|
-
"message": "An unexpected error occurred. Please try again.",
|
|
158
|
-
"traceId": "abc-123-def"
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
// Internal details go to your logs, not to the client
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### Error Response Mapping
|
|
165
|
-
| Internal Error | HTTP Status | Client Message |
|
|
166
|
-
|---------------|-------------|----------------|
|
|
167
|
-
| `ValidationError` | 400 | Show specific field errors |
|
|
168
|
-
| `AuthenticationError` | 401 | "Invalid credentials" (never specify which field) |
|
|
169
|
-
| `ForbiddenError` | 403 | "Insufficient permissions" |
|
|
170
|
-
| `NotFoundError` | 404 | "Resource not found" |
|
|
171
|
-
| `ConflictError` | 409 | "Resource already exists" |
|
|
172
|
-
| `RateLimitError` | 429 | "Too many requests" |
|
|
173
|
-
| Unhandled errors | 500 | "Internal error" + traceId for support |
|
|
174
|
-
|
|
175
|
-
---
|
|
176
|
-
|
|
177
|
-
## Fail-Fast at Boundaries
|
|
178
|
-
|
|
179
|
-
```
|
|
180
|
-
// ✅ Validate at the entry point, fail before going deeper
|
|
181
|
-
async function createOrder(req: Request) {
|
|
182
|
-
// Step 1: Validate input IMMEDIATELY
|
|
183
|
-
const parsed = CreateOrderSchema.safeParse(req.body);
|
|
184
|
-
if (!parsed.success) {
|
|
185
|
-
throw new ValidationError('Invalid order data', parsed.error.flatten());
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Step 2: Now use the validated, typed data
|
|
189
|
-
const order = await orderService.create(parsed.data);
|
|
190
|
-
return order;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ❌ Don't let bad data travel deep before failing
|
|
194
|
-
async function createOrder(req: Request) {
|
|
195
|
-
const data = req.body; // Unvalidated!
|
|
196
|
-
const order = await orderService.create(data);
|
|
197
|
-
// Service calls repository, repository tries to insert...
|
|
198
|
-
// Database throws a cryptic constraint violation 5 layers deep
|
|
199
|
-
}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
---
|
|
203
|
-
|
|
204
|
-
## Retry Strategy
|
|
205
|
-
|
|
206
|
-
When retrying failed operations:
|
|
207
|
-
|
|
208
|
-
1. **Only retry transient errors** (network timeouts, 503s) — NEVER retry validation errors or auth failures
|
|
209
|
-
2. **Use exponential backoff** — 100ms → 200ms → 400ms → 800ms
|
|
210
|
-
3. **Set a maximum retry count** (typically 3)
|
|
211
|
-
4. **Log every retry attempt** with attempt number and error
|
|
212
|
-
5. **Add jitter** to prevent thundering herd
|
|
213
|
-
|
|
214
|
-
```typescript
|
|
215
|
-
async function withRetry<T>(
|
|
216
|
-
operation: () => Promise<T>,
|
|
217
|
-
maxAttempts: number = 3,
|
|
218
|
-
baseDelayMs: number = 100,
|
|
219
|
-
): Promise<T> {
|
|
220
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
221
|
-
try {
|
|
222
|
-
return await operation();
|
|
223
|
-
} catch (error) {
|
|
224
|
-
if (attempt === maxAttempts || !isTransientError(error)) {
|
|
225
|
-
throw error;
|
|
226
|
-
}
|
|
227
|
-
const delay = baseDelayMs * Math.pow(2, attempt - 1) + Math.random() * 100;
|
|
228
|
-
logger.warn('Retrying operation', { attempt, maxAttempts, delayMs: delay });
|
|
229
|
-
await sleep(delay);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
throw new Error('Unreachable');
|
|
233
|
-
}
|
|
234
|
-
```
|
|
12
|
+
At boundaries, validate early, return safe user-facing errors, and keep machine-readable error context for operators and callers.
|
|
@@ -1,226 +1,22 @@
|
|
|
1
|
-
# Event
|
|
1
|
+
# Event Boundary
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> Events are the nervous system of a distributed system.
|
|
3
|
+
Do not add event-driven architecture because it sounds modern. Use it only when the product or repo shows a real async boundary.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
Use events when:
|
|
6
|
+
- multiple independent consumers must react to the same fact
|
|
7
|
+
- synchronous coupling would harm reliability, latency, or ownership
|
|
8
|
+
- audit history, fan-out, or eventual consistency is a real requirement
|
|
9
|
+
- the team can operate retries, monitoring, and failure recovery
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
1. Multiple services need to react to the same state change
|
|
10
|
-
2. You need temporal decoupling (producer doesn't wait for consumer)
|
|
11
|
-
3. Audit trail / event history is a business requirement
|
|
12
|
-
4. Systems need to scale producers and consumers independently
|
|
13
|
-
5. Eventual consistency is acceptable
|
|
11
|
+
Reject events when a direct call, database transaction, or simple module boundary is enough.
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
13
|
+
Hard rules:
|
|
14
|
+
- events describe facts that already happened
|
|
15
|
+
- payloads are versioned, typed, and documented
|
|
16
|
+
- producers do not know consumer internals
|
|
17
|
+
- consumers are idempotent
|
|
18
|
+
- retries are bounded and dead-letter or recovery behavior is defined
|
|
19
|
+
- transactional publishing uses an outbox or equivalent safety pattern when data consistency matters
|
|
20
|
+
- event catalogs or docs identify producer, consumers, ownership, and schema evolution rules
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Core Patterns
|
|
24
|
-
|
|
25
|
-
### 1. Pub/Sub (Publish-Subscribe)
|
|
26
|
-
|
|
27
|
-
Producer emits an event. Zero or more consumers react independently.
|
|
28
|
-
|
|
29
|
-
```
|
|
30
|
-
┌──────────┐
|
|
31
|
-
│ Consumer A│ (Send email)
|
|
32
|
-
└────▲──────┘
|
|
33
|
-
┌──────────┐ │
|
|
34
|
-
│ Producer │──→ EVENT BUS ──→┌──────────┐
|
|
35
|
-
│ (Order │ │ │ Consumer B│ (Update inventory)
|
|
36
|
-
│ Service) │ │ └──────────┘
|
|
37
|
-
└──────────┘ │
|
|
38
|
-
┌───▼──────┐
|
|
39
|
-
│ Consumer C│ (Analytics)
|
|
40
|
-
└──────────┘
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
**Rules:**
|
|
44
|
-
- Producer does NOT know about consumers (fire-and-forget)
|
|
45
|
-
- Each consumer processes independently (failure in one doesn't affect others)
|
|
46
|
-
- Consumers MUST be idempotent (same event processed twice = same result)
|
|
47
|
-
|
|
48
|
-
### 2. CQRS (Command Query Responsibility Segregation)
|
|
49
|
-
|
|
50
|
-
Separate the write model (commands) from the read model (queries).
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
┌─────────────┐ ┌─────────────┐
|
|
54
|
-
│ Write Side │ Events │ Read Side │
|
|
55
|
-
│ (Commands) │ ──────────────→ │ (Queries) │
|
|
56
|
-
│ │ │ │
|
|
57
|
-
│ Rich domain │ │ Denormalized │
|
|
58
|
-
│ model │ │ read models │
|
|
59
|
-
│ Normalized │ │ Optimized │
|
|
60
|
-
│ schema │ │ for queries │
|
|
61
|
-
└─────────────┘ └─────────────┘
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
**When to use CQRS:**
|
|
65
|
-
- Read/write ratio is heavily skewed (100:1 reads to writes)
|
|
66
|
-
- Read and write models have fundamentally different shapes
|
|
67
|
-
- You need different scaling for reads vs writes
|
|
68
|
-
|
|
69
|
-
**When NOT to use CQRS:**
|
|
70
|
-
- Simple CRUD with no complex queries
|
|
71
|
-
- Read and write models are identical (just use a repository)
|
|
72
|
-
- You don't have the team capacity to maintain two models
|
|
73
|
-
|
|
74
|
-
### 3. Event Sourcing
|
|
75
|
-
|
|
76
|
-
Store the full history of state changes as immutable events, not just the current state.
|
|
77
|
-
|
|
78
|
-
```
|
|
79
|
-
Traditional: User { name: "Jane", email: "jane@new.com" }
|
|
80
|
-
→ You only know the current state
|
|
81
|
-
|
|
82
|
-
Event Sourced:
|
|
83
|
-
1. UserCreated { name: "Jane", email: "jane@old.com" }
|
|
84
|
-
2. EmailChanged { email: "jane@mid.com" }
|
|
85
|
-
3. EmailChanged { email: "jane@new.com" }
|
|
86
|
-
→ You know the full history, can rebuild any point in time
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
**When to use Event Sourcing:**
|
|
90
|
-
- Audit trail is a regulatory requirement (finance, healthcare)
|
|
91
|
-
- "Time travel" queries are needed (what was the state on March 1?)
|
|
92
|
-
- Complex domain with many state transitions
|
|
93
|
-
- Combined with CQRS for complex read requirements
|
|
94
|
-
|
|
95
|
-
**When NOT to use Event Sourcing:**
|
|
96
|
-
- Simple CRUD applications (overkill)
|
|
97
|
-
- Team has no experience with event stores
|
|
98
|
-
- No clear business need for historical state
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## Event Design Rules
|
|
103
|
-
|
|
104
|
-
### Naming: Past Tense, Domain Language
|
|
105
|
-
|
|
106
|
-
```
|
|
107
|
-
❌ BANNED:
|
|
108
|
-
"UpdateOrder" → Commands, not events
|
|
109
|
-
"ORDER_UPDATE" → Screaming snake in an event name
|
|
110
|
-
"data" → Meaningless
|
|
111
|
-
|
|
112
|
-
✅ REQUIRED:
|
|
113
|
-
"OrderPlaced" → Past tense, describes what happened
|
|
114
|
-
"PaymentProcessed" → Specific, clear domain action
|
|
115
|
-
"InventoryReserved" → Business language, not technical
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Event Schema: Include Context
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
// ❌ BANNED: Minimal event with no context
|
|
122
|
-
{ type: "OrderPlaced", orderId: "123" }
|
|
123
|
-
|
|
124
|
-
// ✅ REQUIRED: Rich event with all necessary context
|
|
125
|
-
{
|
|
126
|
-
eventId: "evt_abc123", // Unique, for idempotency
|
|
127
|
-
eventType: "OrderPlaced", // What happened
|
|
128
|
-
aggregateId: "order_123", // Which entity
|
|
129
|
-
aggregateType: "Order", // Entity type
|
|
130
|
-
timestamp: "2026-03-11T...", // When it happened
|
|
131
|
-
version: 1, // Schema version
|
|
132
|
-
correlationId: "req_xyz", // Trace across services
|
|
133
|
-
causationId: "cmd_456", // What caused this event
|
|
134
|
-
data: { // The event payload
|
|
135
|
-
orderId: "order_123",
|
|
136
|
-
userId: "user_789",
|
|
137
|
-
items: [...],
|
|
138
|
-
totalAmount: 99.99,
|
|
139
|
-
currency: "USD"
|
|
140
|
-
},
|
|
141
|
-
metadata: { // Operational metadata
|
|
142
|
-
producerService: "order-service",
|
|
143
|
-
producerVersion: "2.1.0"
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Event Versioning
|
|
149
|
-
|
|
150
|
-
Events are immutable — once published, they can't change. Handle evolution with:
|
|
151
|
-
|
|
152
|
-
1. **Weak schema (preferred):** Consumers ignore unknown fields, use defaults for missing fields
|
|
153
|
-
2. **Upcasting:** Transform old events to new schema on read
|
|
154
|
-
3. **New event type:** If the change is breaking, create `OrderPlacedV2`
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
BANNED: Changing the schema of an existing event in a breaking way
|
|
158
|
-
BANNED: Removing fields from events
|
|
159
|
-
ALLOWED: Adding optional fields with defaults
|
|
160
|
-
ALLOWED: Creating a new event type for breaking changes
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
## Infrastructure Choices
|
|
166
|
-
|
|
167
|
-
| Need | Recommended | Avoid |
|
|
168
|
-
|------|-----------|-------|
|
|
169
|
-
| General pub/sub | Apache Kafka, NATS, RabbitMQ | Custom TCP sockets |
|
|
170
|
-
| Cloud-native | AWS EventBridge, GCP Pub/Sub, Azure Service Bus | Polling a database table |
|
|
171
|
-
| Simple/local | Redis Streams, NATS | ZeroMQ for production events |
|
|
172
|
-
| Event store | EventStoreDB, Kafka (with compaction) | Relational DB as event store (unless simple) |
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## Reliability Patterns
|
|
177
|
-
|
|
178
|
-
### Outbox Pattern (Transactional Events)
|
|
179
|
-
|
|
180
|
-
Ensure events are published reliably alongside database writes:
|
|
181
|
-
|
|
182
|
-
```
|
|
183
|
-
1. Write to database AND outbox table in a single transaction
|
|
184
|
-
2. Background process reads outbox and publishes to message broker
|
|
185
|
-
3. Mark outbox entry as published
|
|
186
|
-
|
|
187
|
-
This guarantees: if the DB write succeeds, the event WILL be published.
|
|
188
|
-
No more "DB updated but event lost" bugs.
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
### Dead Letter Queue (DLQ)
|
|
192
|
-
|
|
193
|
-
Messages that fail processing after N retries go to a DLQ:
|
|
194
|
-
- Monitor DLQ size — it should be near zero
|
|
195
|
-
- Alert when DLQ grows
|
|
196
|
-
- Investigate and reprocess failed messages
|
|
197
|
-
- Never ignore a growing DLQ
|
|
198
|
-
|
|
199
|
-
### Idempotency (Non-Negotiable)
|
|
200
|
-
|
|
201
|
-
```
|
|
202
|
-
EVERY consumer MUST handle duplicate events safely.
|
|
203
|
-
|
|
204
|
-
Techniques:
|
|
205
|
-
1. Idempotency key: Store processed eventIds, skip if seen
|
|
206
|
-
2. Natural idempotency: Operations that are naturally safe to repeat
|
|
207
|
-
(e.g., SET status = 'paid' is idempotent; INCREMENT balance is NOT)
|
|
208
|
-
3. Optimistic locking: Use version numbers to detect conflicts
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
---
|
|
212
|
-
|
|
213
|
-
## The Event-Driven Checklist
|
|
214
|
-
|
|
215
|
-
Before implementing event-driven patterns:
|
|
216
|
-
|
|
217
|
-
- [ ] Business justification exists (not just "it's modern")
|
|
218
|
-
- [ ] Team understands eventual consistency trade-offs
|
|
219
|
-
- [ ] Message broker selected and provisioned
|
|
220
|
-
- [ ] Event schema defined with versioning strategy
|
|
221
|
-
- [ ] All consumers are idempotent
|
|
222
|
-
- [ ] Dead letter queue configured with monitoring
|
|
223
|
-
- [ ] Distributed tracing in place (OpenTelemetry)
|
|
224
|
-
- [ ] Outbox pattern used for transactional events (if needed)
|
|
225
|
-
- [ ] Consumer failure handling defined (retry, DLQ, alert)
|
|
226
|
-
- [ ] Event catalog maintained (what events exist, who produces/consumes)
|
|
22
|
+
If event tooling is unresolved, the LLM must recommend a current project-fit broker or managed service from official docs before implementation.
|