@grainulation/silo 1.0.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.
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/bin/silo.js +327 -0
- package/lib/analytics.js +76 -0
- package/lib/import-export.js +174 -0
- package/lib/index.js +28 -0
- package/lib/packs.js +184 -0
- package/lib/search.js +128 -0
- package/lib/serve-mcp.js +337 -0
- package/lib/server.js +425 -0
- package/lib/store.js +145 -0
- package/lib/templates.js +139 -0
- package/package.json +48 -0
- package/packs/api-design.json +189 -0
- package/packs/architecture.json +175 -0
- package/packs/ci-cd.json +175 -0
- package/packs/compliance.json +203 -0
- package/packs/data-engineering.json +175 -0
- package/packs/frontend.json +175 -0
- package/packs/migration.json +147 -0
- package/packs/observability.json +175 -0
- package/packs/security.json +175 -0
- package/packs/team-process.json +175 -0
- package/packs/testing.json +147 -0
- package/public/grainulation-tokens.css +321 -0
- package/public/index.html +803 -0
package/lib/templates.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* templates.js — Sprint template management
|
|
3
|
+
*
|
|
4
|
+
* Templates are pre-built research questions with seeded claims.
|
|
5
|
+
* A template contains: question, audience, constraints, and starter claims.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const { Store } = require('./store.js');
|
|
11
|
+
|
|
12
|
+
class Templates {
|
|
13
|
+
constructor(store) {
|
|
14
|
+
this.store = store || new Store();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Save a sprint configuration as a reusable template.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} name - Template name
|
|
21
|
+
* @param {object} template
|
|
22
|
+
* @param {string} template.question - Research question
|
|
23
|
+
* @param {string} template.audience - Who this is for
|
|
24
|
+
* @param {string[]} template.constraints - Hard constraints
|
|
25
|
+
* @param {object[]} template.seedClaims - Claims to start with
|
|
26
|
+
* @param {string[]} template.tags - Searchable tags
|
|
27
|
+
*/
|
|
28
|
+
save(name, template) {
|
|
29
|
+
this.store.init();
|
|
30
|
+
const id = this._slugify(name);
|
|
31
|
+
const entry = {
|
|
32
|
+
id,
|
|
33
|
+
name,
|
|
34
|
+
...template,
|
|
35
|
+
savedAt: new Date().toISOString(),
|
|
36
|
+
};
|
|
37
|
+
const filePath = path.join(this.store.templatesDir, `${id}.json`);
|
|
38
|
+
const tmp1 = filePath + '.tmp.' + process.pid;
|
|
39
|
+
fs.writeFileSync(tmp1, JSON.stringify(entry, null, 2) + '\n', 'utf-8');
|
|
40
|
+
fs.renameSync(tmp1, filePath);
|
|
41
|
+
|
|
42
|
+
this.store._addToIndex({
|
|
43
|
+
id,
|
|
44
|
+
name,
|
|
45
|
+
type: 'template',
|
|
46
|
+
claimCount: (template.seedClaims || []).length,
|
|
47
|
+
storedAt: entry.savedAt,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return entry;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Get a template by name/id. */
|
|
54
|
+
get(nameOrId) {
|
|
55
|
+
const id = this._slugify(nameOrId);
|
|
56
|
+
const filePath = path.join(this.store.templatesDir, `${id}.json`);
|
|
57
|
+
if (!fs.existsSync(filePath)) return null;
|
|
58
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** List all saved templates. */
|
|
62
|
+
list() {
|
|
63
|
+
this.store.init();
|
|
64
|
+
const dir = this.store.templatesDir;
|
|
65
|
+
if (!fs.existsSync(dir)) return [];
|
|
66
|
+
|
|
67
|
+
return fs
|
|
68
|
+
.readdirSync(dir)
|
|
69
|
+
.filter((f) => f.endsWith('.json'))
|
|
70
|
+
.map((f) => {
|
|
71
|
+
try {
|
|
72
|
+
const data = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf-8'));
|
|
73
|
+
return {
|
|
74
|
+
id: data.id,
|
|
75
|
+
name: data.name,
|
|
76
|
+
question: data.question,
|
|
77
|
+
seedClaims: (data.seedClaims || []).length,
|
|
78
|
+
tags: data.tags || [],
|
|
79
|
+
};
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
.filter(Boolean);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Instantiate a template into a sprint directory.
|
|
89
|
+
* Creates claims.json with seed claims and a sprint config stub.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} nameOrId - Template name/id
|
|
92
|
+
* @param {string} targetDir - Directory to instantiate into
|
|
93
|
+
*/
|
|
94
|
+
instantiate(nameOrId, targetDir) {
|
|
95
|
+
const template = this.get(nameOrId);
|
|
96
|
+
if (!template) {
|
|
97
|
+
throw new Error(`Template "${nameOrId}" not found`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!fs.existsSync(targetDir)) {
|
|
101
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Write seed claims
|
|
105
|
+
const claimsPath = path.join(targetDir, 'claims.json');
|
|
106
|
+
const claims = (template.seedClaims || []).map((c, i) => ({
|
|
107
|
+
...c,
|
|
108
|
+
id: c.id || `d${String(i + 1).padStart(3, '0')}`,
|
|
109
|
+
}));
|
|
110
|
+
const tmpClaims = claimsPath + '.tmp.' + process.pid;
|
|
111
|
+
fs.writeFileSync(tmpClaims, JSON.stringify(claims, null, 2) + '\n', 'utf-8');
|
|
112
|
+
fs.renameSync(tmpClaims, claimsPath);
|
|
113
|
+
|
|
114
|
+
// Write sprint config stub
|
|
115
|
+
const configPath = path.join(targetDir, 'sprint.json');
|
|
116
|
+
const config = {
|
|
117
|
+
question: template.question,
|
|
118
|
+
audience: template.audience,
|
|
119
|
+
constraints: template.constraints || [],
|
|
120
|
+
fromTemplate: template.id,
|
|
121
|
+
createdAt: new Date().toISOString(),
|
|
122
|
+
};
|
|
123
|
+
const tmpConfig = configPath + '.tmp.' + process.pid;
|
|
124
|
+
fs.writeFileSync(tmpConfig, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
125
|
+
fs.renameSync(tmpConfig, configPath);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
claimsFile: claimsPath,
|
|
129
|
+
configFile: configPath,
|
|
130
|
+
seedClaims: claims.length,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
_slugify(str) {
|
|
135
|
+
return str.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = { Templates };
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@grainulation/silo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Reusable knowledge for research sprints -- shared claim libraries, templates, and knowledge packs",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./lib/index.js",
|
|
8
|
+
"./server": {
|
|
9
|
+
"import": "./lib/server.js"
|
|
10
|
+
},
|
|
11
|
+
"./tokens": "./public/grainulation-tokens.css"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"silo": "bin/silo.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node test/basic.test.js",
|
|
18
|
+
"start": "node bin/silo.js serve",
|
|
19
|
+
"serve": "node bin/silo.js serve"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"research",
|
|
23
|
+
"claims",
|
|
24
|
+
"knowledge",
|
|
25
|
+
"templates",
|
|
26
|
+
"sprint"
|
|
27
|
+
],
|
|
28
|
+
"author": "grainulation contributors",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/grainulation/silo.git"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18.0.0"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/grainulation/silo/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://silo.grainulation.com",
|
|
41
|
+
"files": [
|
|
42
|
+
"bin/",
|
|
43
|
+
"lib/",
|
|
44
|
+
"packs/",
|
|
45
|
+
"public/",
|
|
46
|
+
"CHANGELOG.md"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "API Design",
|
|
3
|
+
"description": "REST conventions, versioning strategies, pagination, error formats, backwards compatibility, and GraphQL tradeoff analysis for HTTP APIs.",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"claims": [
|
|
6
|
+
{
|
|
7
|
+
"id": "api-001",
|
|
8
|
+
"type": "constraint",
|
|
9
|
+
"topic": "HTTP method semantics",
|
|
10
|
+
"content": "GET must be safe and idempotent (no side effects). PUT must be idempotent (same result on repeat). POST is neither safe nor idempotent. DELETE should be idempotent (deleting an already-deleted resource returns 204 or 404, not an error). Violating these semantics breaks caches, retries, and client expectations.",
|
|
11
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
12
|
+
"evidence": "documented",
|
|
13
|
+
"status": "active",
|
|
14
|
+
"phase_added": "define",
|
|
15
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
16
|
+
"conflicts_with": [],
|
|
17
|
+
"resolved_by": null,
|
|
18
|
+
"tags": ["api", "rest", "http-methods", "idempotency"]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "api-002",
|
|
22
|
+
"type": "recommendation",
|
|
23
|
+
"topic": "error response format",
|
|
24
|
+
"content": "Use RFC 7807 (Problem Details) for error responses: {type, title, status, detail, instance}. Include a machine-readable error code field for client-side branching. Never expose stack traces, internal paths, or database errors in production responses.",
|
|
25
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
26
|
+
"evidence": "documented",
|
|
27
|
+
"status": "active",
|
|
28
|
+
"phase_added": "define",
|
|
29
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
30
|
+
"conflicts_with": [],
|
|
31
|
+
"resolved_by": null,
|
|
32
|
+
"tags": ["api", "error-handling", "rfc-7807", "rest"]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "api-003",
|
|
36
|
+
"type": "recommendation",
|
|
37
|
+
"topic": "cursor-based pagination",
|
|
38
|
+
"content": "Prefer cursor-based pagination over offset-based for datasets that change frequently. Offset pagination becomes inconsistent when rows are inserted or deleted between pages. Cursor pagination uses an opaque token (base64-encoded last-seen ID) and supports consistent forward iteration. Return {data, next_cursor, has_more} in responses.",
|
|
39
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
40
|
+
"evidence": "production",
|
|
41
|
+
"status": "active",
|
|
42
|
+
"phase_added": "define",
|
|
43
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
44
|
+
"conflicts_with": [],
|
|
45
|
+
"resolved_by": null,
|
|
46
|
+
"tags": ["api", "pagination", "cursor", "rest"]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "api-004",
|
|
50
|
+
"type": "risk",
|
|
51
|
+
"topic": "URL path versioning lock-in",
|
|
52
|
+
"content": "URL path versioning (/v1/users, /v2/users) is simple but creates permanent routing complexity. Every version doubles the endpoint surface. Prefer header-based versioning (Accept: application/vnd.api+json;version=2) or additive-only changes (no versioning needed if you never remove or rename fields).",
|
|
53
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
54
|
+
"evidence": "web",
|
|
55
|
+
"status": "active",
|
|
56
|
+
"phase_added": "define",
|
|
57
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
58
|
+
"conflicts_with": [],
|
|
59
|
+
"resolved_by": null,
|
|
60
|
+
"tags": ["api", "versioning", "rest", "backwards-compatibility"]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "api-005",
|
|
64
|
+
"type": "constraint",
|
|
65
|
+
"topic": "backwards compatibility rules",
|
|
66
|
+
"content": "Breaking changes for public APIs: removing a field, renaming a field, changing a field type, changing a required/optional status, removing an endpoint, changing authentication. Non-breaking: adding optional fields, adding new endpoints, adding new enum values (if clients handle unknown values). Never make breaking changes without a version bump and deprecation period of at least 6 months.",
|
|
67
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
68
|
+
"evidence": "documented",
|
|
69
|
+
"status": "active",
|
|
70
|
+
"phase_added": "define",
|
|
71
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
72
|
+
"conflicts_with": [],
|
|
73
|
+
"resolved_by": null,
|
|
74
|
+
"tags": ["api", "backwards-compatibility", "deprecation", "breaking-changes"]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "api-006",
|
|
78
|
+
"type": "factual",
|
|
79
|
+
"topic": "GraphQL vs REST tradeoffs",
|
|
80
|
+
"content": "GraphQL eliminates over-fetching and under-fetching but introduces: query complexity analysis (must limit depth to 7-10 and breadth), N+1 data loader patterns, cache invalidation complexity (no HTTP caching by default), and a steeper client learning curve. REST is simpler for CRUD-heavy APIs with predictable access patterns.",
|
|
81
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
82
|
+
"evidence": "documented",
|
|
83
|
+
"status": "active",
|
|
84
|
+
"phase_added": "define",
|
|
85
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
86
|
+
"conflicts_with": [],
|
|
87
|
+
"resolved_by": null,
|
|
88
|
+
"tags": ["api", "graphql", "rest", "tradeoffs"]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "api-007",
|
|
92
|
+
"type": "recommendation",
|
|
93
|
+
"topic": "rate limit headers",
|
|
94
|
+
"content": "Include rate limit headers in every response: X-RateLimit-Limit (max requests per window), X-RateLimit-Remaining (remaining in current window), X-RateLimit-Reset (Unix timestamp when window resets). Return 429 Too Many Requests with a Retry-After header when limit is exceeded. This lets well-behaved clients self-throttle.",
|
|
95
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
96
|
+
"evidence": "documented",
|
|
97
|
+
"status": "active",
|
|
98
|
+
"phase_added": "define",
|
|
99
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
100
|
+
"conflicts_with": [],
|
|
101
|
+
"resolved_by": null,
|
|
102
|
+
"tags": ["api", "rate-limiting", "headers", "429"]
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"id": "api-008",
|
|
106
|
+
"type": "constraint",
|
|
107
|
+
"topic": "request payload size limits",
|
|
108
|
+
"content": "Set explicit request body size limits: 1 MB for standard JSON APIs, 10 MB for file uploads via multipart, and configurable limits for specific endpoints. Unbounded request bodies enable denial-of-service attacks. Return 413 Payload Too Large with a clear message stating the limit.",
|
|
109
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
110
|
+
"evidence": "documented",
|
|
111
|
+
"status": "active",
|
|
112
|
+
"phase_added": "define",
|
|
113
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
114
|
+
"conflicts_with": [],
|
|
115
|
+
"resolved_by": null,
|
|
116
|
+
"tags": ["api", "security", "payload-limits", "dos"]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"id": "api-009",
|
|
120
|
+
"type": "recommendation",
|
|
121
|
+
"topic": "idempotency keys for mutations",
|
|
122
|
+
"content": "POST endpoints that create resources should accept an Idempotency-Key header. Store the key with the response for 24 hours. If a duplicate key arrives, return the stored response without re-executing. This prevents duplicate charges, orders, or records from network retries. Stripe, PayPal, and AWS all use this pattern.",
|
|
123
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
124
|
+
"evidence": "production",
|
|
125
|
+
"status": "active",
|
|
126
|
+
"phase_added": "define",
|
|
127
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
128
|
+
"conflicts_with": [],
|
|
129
|
+
"resolved_by": null,
|
|
130
|
+
"tags": ["api", "idempotency", "reliability", "payments"]
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"id": "api-010",
|
|
134
|
+
"type": "risk",
|
|
135
|
+
"topic": "nested resource depth",
|
|
136
|
+
"content": "Deeply nested REST routes (/orgs/:id/teams/:id/members/:id/roles) become rigid and hard to evolve. Limit nesting to 2 levels maximum. Use flat routes with query filters for deeper relationships (/roles?member_id=X&team_id=Y). Deep nesting also makes URL construction fragile for API consumers.",
|
|
137
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
138
|
+
"evidence": "web",
|
|
139
|
+
"status": "active",
|
|
140
|
+
"phase_added": "define",
|
|
141
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
142
|
+
"conflicts_with": [],
|
|
143
|
+
"resolved_by": null,
|
|
144
|
+
"tags": ["api", "rest", "url-design", "nesting"]
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"id": "api-011",
|
|
148
|
+
"type": "factual",
|
|
149
|
+
"topic": "HTTP status code semantics",
|
|
150
|
+
"content": "Use status codes precisely: 200 (success with body), 201 (created, include Location header), 204 (success, no body), 400 (malformed request), 401 (unauthenticated), 403 (authenticated but unauthorized), 404 (not found or hidden), 409 (conflict/duplicate), 422 (valid syntax but semantic error), 429 (rate limited), 500 (server error). Avoid 200-wrapping errors.",
|
|
151
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
152
|
+
"evidence": "documented",
|
|
153
|
+
"status": "active",
|
|
154
|
+
"phase_added": "define",
|
|
155
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
156
|
+
"conflicts_with": [],
|
|
157
|
+
"resolved_by": null,
|
|
158
|
+
"tags": ["api", "http", "status-codes", "rest"]
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"id": "api-012",
|
|
162
|
+
"type": "recommendation",
|
|
163
|
+
"topic": "API documentation contract testing",
|
|
164
|
+
"content": "Generate API documentation from code (OpenAPI/Swagger) rather than maintaining it manually. Use contract testing (Pact, Dredd, or OpenAPI-diff in CI) to verify that the implementation matches the spec. Manual docs drift within weeks; contract tests catch drift on every PR.",
|
|
165
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
166
|
+
"evidence": "tested",
|
|
167
|
+
"status": "active",
|
|
168
|
+
"phase_added": "define",
|
|
169
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
170
|
+
"conflicts_with": [],
|
|
171
|
+
"resolved_by": null,
|
|
172
|
+
"tags": ["api", "documentation", "contract-testing", "openapi"]
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"id": "api-013",
|
|
176
|
+
"type": "estimate",
|
|
177
|
+
"topic": "API response time targets",
|
|
178
|
+
"content": "For user-facing APIs: p50 under 100ms, p95 under 500ms, p99 under 1s. For internal service-to-service APIs: p50 under 20ms, p95 under 100ms. These targets assume a CDN for static assets and regional deployment. Every 100ms of API latency reduces conversion by approximately 1% in e-commerce contexts.",
|
|
179
|
+
"source": { "origin": "industry", "artifact": null, "connector": null },
|
|
180
|
+
"evidence": "web",
|
|
181
|
+
"status": "active",
|
|
182
|
+
"phase_added": "define",
|
|
183
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
184
|
+
"conflicts_with": [],
|
|
185
|
+
"resolved_by": null,
|
|
186
|
+
"tags": ["api", "performance", "latency", "sla"]
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Architecture Decision Templates",
|
|
3
|
+
"description": "Constraint and risk claims for common architecture decisions -- monolith vs microservices, build vs buy, sync vs async, SQL vs NoSQL. Drop into any ADR sprint.",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"claims": [
|
|
6
|
+
{
|
|
7
|
+
"id": "arch-001",
|
|
8
|
+
"type": "constraint",
|
|
9
|
+
"topic": "microservices operational overhead",
|
|
10
|
+
"content": "Microservices add operational overhead proportional to service count: each service needs its own CI/CD pipeline, monitoring, alerting, and on-call rotation. Below ~10 engineers, this cost often exceeds the benefit.",
|
|
11
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
12
|
+
"evidence": "documented",
|
|
13
|
+
"status": "active",
|
|
14
|
+
"phase_added": "define",
|
|
15
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
16
|
+
"conflicts_with": [],
|
|
17
|
+
"resolved_by": null,
|
|
18
|
+
"tags": ["architecture", "microservices", "team-size"]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "arch-002",
|
|
22
|
+
"type": "risk",
|
|
23
|
+
"topic": "distributed transaction complexity",
|
|
24
|
+
"content": "Distributed transactions across microservices require saga patterns or two-phase commit. Both add complexity and failure modes that don't exist in a monolith.",
|
|
25
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
26
|
+
"evidence": "documented",
|
|
27
|
+
"status": "active",
|
|
28
|
+
"phase_added": "define",
|
|
29
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
30
|
+
"conflicts_with": [],
|
|
31
|
+
"resolved_by": null,
|
|
32
|
+
"tags": ["architecture", "microservices", "transactions"]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "arch-003",
|
|
36
|
+
"type": "factual",
|
|
37
|
+
"topic": "event-driven eventual consistency",
|
|
38
|
+
"content": "Event-driven architectures decouple producers from consumers but introduce eventual consistency. Any user-facing flow that requires strong consistency must use synchronous calls or compensating transactions.",
|
|
39
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
40
|
+
"evidence": "documented",
|
|
41
|
+
"status": "active",
|
|
42
|
+
"phase_added": "define",
|
|
43
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
44
|
+
"conflicts_with": [],
|
|
45
|
+
"resolved_by": null,
|
|
46
|
+
"tags": ["architecture", "event-driven", "consistency"]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "arch-004",
|
|
50
|
+
"type": "recommendation",
|
|
51
|
+
"topic": "modular monolith first",
|
|
52
|
+
"content": "Start with a modular monolith (clear module boundaries, separate data access per module) and extract services only when a module has a distinct scaling, deployment, or team-ownership need.",
|
|
53
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
54
|
+
"evidence": "documented",
|
|
55
|
+
"status": "active",
|
|
56
|
+
"phase_added": "define",
|
|
57
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
58
|
+
"conflicts_with": [],
|
|
59
|
+
"resolved_by": null,
|
|
60
|
+
"tags": ["architecture", "monolith", "modularity"]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "arch-005",
|
|
64
|
+
"type": "risk",
|
|
65
|
+
"topic": "build vs buy cost analysis",
|
|
66
|
+
"content": "Build vs buy: custom solutions accumulate maintenance cost of ~15-20% of initial build cost per year. SaaS vendors average 5-10% annual price increases. Break-even is typically 3-5 years.",
|
|
67
|
+
"source": { "origin": "industry", "artifact": null, "connector": null },
|
|
68
|
+
"evidence": "web",
|
|
69
|
+
"status": "active",
|
|
70
|
+
"phase_added": "define",
|
|
71
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
72
|
+
"conflicts_with": [],
|
|
73
|
+
"resolved_by": null,
|
|
74
|
+
"tags": ["architecture", "build-vs-buy", "cost"]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "arch-006",
|
|
78
|
+
"type": "constraint",
|
|
79
|
+
"topic": "SQL vs NoSQL decision criteria",
|
|
80
|
+
"content": "SQL databases provide ACID transactions and are the correct default for structured data with relationships. NoSQL is justified when: (a) schema is truly unpredictable, (b) horizontal write scaling is required, or (c) access patterns are pure key-value.",
|
|
81
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
82
|
+
"evidence": "documented",
|
|
83
|
+
"status": "active",
|
|
84
|
+
"phase_added": "define",
|
|
85
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
86
|
+
"conflicts_with": [],
|
|
87
|
+
"resolved_by": null,
|
|
88
|
+
"tags": ["architecture", "database", "sql", "nosql"]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "arch-007",
|
|
92
|
+
"type": "factual",
|
|
93
|
+
"topic": "CAP theorem",
|
|
94
|
+
"content": "CAP theorem: a distributed system can provide at most two of Consistency, Availability, and Partition tolerance. Since network partitions are inevitable, the real choice is between CP (consistent but may reject requests) and AP (available but may return stale data).",
|
|
95
|
+
"source": { "origin": "academic", "artifact": null, "connector": null },
|
|
96
|
+
"evidence": "documented",
|
|
97
|
+
"status": "active",
|
|
98
|
+
"phase_added": "define",
|
|
99
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
100
|
+
"conflicts_with": [],
|
|
101
|
+
"resolved_by": null,
|
|
102
|
+
"tags": ["architecture", "cap-theorem", "distributed"]
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"id": "arch-008",
|
|
106
|
+
"type": "risk",
|
|
107
|
+
"topic": "shared database coupling",
|
|
108
|
+
"content": "Shared databases between services create hidden coupling. Schema changes in one service break others. Each service should own its data store, exposing data only through APIs.",
|
|
109
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
110
|
+
"evidence": "documented",
|
|
111
|
+
"status": "active",
|
|
112
|
+
"phase_added": "define",
|
|
113
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
114
|
+
"conflicts_with": [],
|
|
115
|
+
"resolved_by": null,
|
|
116
|
+
"tags": ["architecture", "database", "coupling"]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"id": "arch-009",
|
|
120
|
+
"type": "recommendation",
|
|
121
|
+
"topic": "cache invalidation strategy",
|
|
122
|
+
"content": "For cache invalidation, prefer short TTLs with stale-while-revalidate over complex invalidation logic. Cache invalidation bugs are the #1 source of data inconsistency in production systems.",
|
|
123
|
+
"source": { "origin": "industry", "artifact": null, "connector": null },
|
|
124
|
+
"evidence": "web",
|
|
125
|
+
"status": "active",
|
|
126
|
+
"phase_added": "define",
|
|
127
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
128
|
+
"conflicts_with": [],
|
|
129
|
+
"resolved_by": null,
|
|
130
|
+
"tags": ["architecture", "caching", "invalidation"]
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"id": "arch-010",
|
|
134
|
+
"type": "constraint",
|
|
135
|
+
"topic": "per-client rate limiting",
|
|
136
|
+
"content": "API gateway rate limiting must be set per client, not globally. Global limits allow a single misbehaving client to consume the entire quota. Use token bucket or sliding window algorithms.",
|
|
137
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
138
|
+
"evidence": "documented",
|
|
139
|
+
"status": "active",
|
|
140
|
+
"phase_added": "define",
|
|
141
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
142
|
+
"conflicts_with": [],
|
|
143
|
+
"resolved_by": null,
|
|
144
|
+
"tags": ["architecture", "api", "rate-limiting"]
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"id": "arch-011",
|
|
148
|
+
"type": "recommendation",
|
|
149
|
+
"topic": "circuit breaker pattern",
|
|
150
|
+
"content": "Circuit breakers (open after N failures, half-open after timeout, close after success) prevent cascade failures. Every synchronous cross-service call should have a circuit breaker with configurable thresholds.",
|
|
151
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
152
|
+
"evidence": "documented",
|
|
153
|
+
"status": "active",
|
|
154
|
+
"phase_added": "define",
|
|
155
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
156
|
+
"conflicts_with": [],
|
|
157
|
+
"resolved_by": null,
|
|
158
|
+
"tags": ["architecture", "resilience", "circuit-breaker"]
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"id": "arch-012",
|
|
162
|
+
"type": "factual",
|
|
163
|
+
"topic": "p99 latency over average",
|
|
164
|
+
"content": "Latency at the 99th percentile (p99) matters more than average latency for user experience. A system with 50ms average but 2s p99 feels worse than one with 100ms average and 200ms p99.",
|
|
165
|
+
"source": { "origin": "best-practice", "artifact": null, "connector": null },
|
|
166
|
+
"evidence": "documented",
|
|
167
|
+
"status": "active",
|
|
168
|
+
"phase_added": "define",
|
|
169
|
+
"timestamp": "2025-01-01T00:00:00.000Z",
|
|
170
|
+
"conflicts_with": [],
|
|
171
|
+
"resolved_by": null,
|
|
172
|
+
"tags": ["architecture", "latency", "performance"]
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
}
|