micro-contracts 0.9.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.
Files changed (99) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +351 -0
  3. package/dist/cli/templates.d.ts +16 -0
  4. package/dist/cli/templates.d.ts.map +1 -0
  5. package/dist/cli/templates.js +377 -0
  6. package/dist/cli/templates.js.map +1 -0
  7. package/dist/cli.d.ts +9 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +978 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/generator/dependencyGenerator.d.ts +43 -0
  12. package/dist/generator/dependencyGenerator.d.ts.map +1 -0
  13. package/dist/generator/dependencyGenerator.js +159 -0
  14. package/dist/generator/dependencyGenerator.js.map +1 -0
  15. package/dist/generator/domainGenerator.d.ts +16 -0
  16. package/dist/generator/domainGenerator.d.ts.map +1 -0
  17. package/dist/generator/domainGenerator.js +212 -0
  18. package/dist/generator/domainGenerator.js.map +1 -0
  19. package/dist/generator/index.d.ts +37 -0
  20. package/dist/generator/index.d.ts.map +1 -0
  21. package/dist/generator/index.js +747 -0
  22. package/dist/generator/index.js.map +1 -0
  23. package/dist/generator/linter.d.ts +24 -0
  24. package/dist/generator/linter.d.ts.map +1 -0
  25. package/dist/generator/linter.js +202 -0
  26. package/dist/generator/linter.js.map +1 -0
  27. package/dist/generator/overlayProcessor.d.ts +90 -0
  28. package/dist/generator/overlayProcessor.d.ts.map +1 -0
  29. package/dist/generator/overlayProcessor.js +532 -0
  30. package/dist/generator/overlayProcessor.js.map +1 -0
  31. package/dist/generator/schemaGenerator.d.ts +10 -0
  32. package/dist/generator/schemaGenerator.d.ts.map +1 -0
  33. package/dist/generator/schemaGenerator.js +299 -0
  34. package/dist/generator/schemaGenerator.js.map +1 -0
  35. package/dist/generator/templateProcessor.d.ts +178 -0
  36. package/dist/generator/templateProcessor.d.ts.map +1 -0
  37. package/dist/generator/templateProcessor.js +607 -0
  38. package/dist/generator/templateProcessor.js.map +1 -0
  39. package/dist/generator/typeGenerator.d.ts +9 -0
  40. package/dist/generator/typeGenerator.d.ts.map +1 -0
  41. package/dist/generator/typeGenerator.js +395 -0
  42. package/dist/generator/typeGenerator.js.map +1 -0
  43. package/dist/guardrails/allowlist.d.ts +45 -0
  44. package/dist/guardrails/allowlist.d.ts.map +1 -0
  45. package/dist/guardrails/allowlist.js +261 -0
  46. package/dist/guardrails/allowlist.js.map +1 -0
  47. package/dist/guardrails/config.d.ts +40 -0
  48. package/dist/guardrails/config.d.ts.map +1 -0
  49. package/dist/guardrails/config.js +174 -0
  50. package/dist/guardrails/config.js.map +1 -0
  51. package/dist/guardrails/docs.d.ts +24 -0
  52. package/dist/guardrails/docs.d.ts.map +1 -0
  53. package/dist/guardrails/docs.js +138 -0
  54. package/dist/guardrails/docs.js.map +1 -0
  55. package/dist/guardrails/drift.d.ts +23 -0
  56. package/dist/guardrails/drift.d.ts.map +1 -0
  57. package/dist/guardrails/drift.js +127 -0
  58. package/dist/guardrails/drift.js.map +1 -0
  59. package/dist/guardrails/index.d.ts +19 -0
  60. package/dist/guardrails/index.d.ts.map +1 -0
  61. package/dist/guardrails/index.js +25 -0
  62. package/dist/guardrails/index.js.map +1 -0
  63. package/dist/guardrails/lint.d.ts +20 -0
  64. package/dist/guardrails/lint.d.ts.map +1 -0
  65. package/dist/guardrails/lint.js +274 -0
  66. package/dist/guardrails/lint.js.map +1 -0
  67. package/dist/guardrails/manifest.d.ts +43 -0
  68. package/dist/guardrails/manifest.d.ts.map +1 -0
  69. package/dist/guardrails/manifest.js +231 -0
  70. package/dist/guardrails/manifest.js.map +1 -0
  71. package/dist/guardrails/runner.d.ts +31 -0
  72. package/dist/guardrails/runner.d.ts.map +1 -0
  73. package/dist/guardrails/runner.js +268 -0
  74. package/dist/guardrails/runner.js.map +1 -0
  75. package/dist/guardrails/security.d.ts +31 -0
  76. package/dist/guardrails/security.d.ts.map +1 -0
  77. package/dist/guardrails/security.js +181 -0
  78. package/dist/guardrails/security.js.map +1 -0
  79. package/dist/guardrails/typecheck.d.ts +15 -0
  80. package/dist/guardrails/typecheck.d.ts.map +1 -0
  81. package/dist/guardrails/typecheck.js +104 -0
  82. package/dist/guardrails/typecheck.js.map +1 -0
  83. package/dist/guardrails/types.d.ts +196 -0
  84. package/dist/guardrails/types.d.ts.map +1 -0
  85. package/dist/guardrails/types.js +8 -0
  86. package/dist/guardrails/types.js.map +1 -0
  87. package/dist/index.d.ts +7 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +7 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/types.d.ts +489 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/types.js +297 -0
  94. package/dist/types.js.map +1 -0
  95. package/docs/architecture.svg +226 -0
  96. package/docs/development-guardrails.md +541 -0
  97. package/docs/guardrails-concept.svg +252 -0
  98. package/docs/overlays-deep-dive.md +298 -0
  99. package/package.json +66 -0
package/dist/types.js ADDED
@@ -0,0 +1,297 @@
1
+ /**
2
+ * OpenAPI specification types for code generation
3
+ */
4
+ /**
5
+ * Parse dependency reference string
6
+ */
7
+ export function parseDependencyRef(ref) {
8
+ const parts = ref.split('.');
9
+ if (parts.length !== 3)
10
+ return null;
11
+ return {
12
+ module: parts[0],
13
+ domain: parts[1],
14
+ method: parts[2],
15
+ raw: ref,
16
+ };
17
+ }
18
+ // =============================================================================
19
+ // Config Utilities
20
+ // =============================================================================
21
+ /**
22
+ * Check if config is multi-module format
23
+ */
24
+ export function isMultiModuleConfig(config) {
25
+ return typeof config === 'object' && config !== null && 'modules' in config;
26
+ }
27
+ /**
28
+ * Expand placeholders in a string
29
+ */
30
+ export function expandPlaceholders(template, moduleName) {
31
+ const pascalCase = moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
32
+ const upperSnake = moduleName.toUpperCase().replace(/-/g, '_');
33
+ return template
34
+ .replace(/\{module\}/g, moduleName)
35
+ .replace(/\{Module\}/g, pascalCase)
36
+ .replace(/\{MODULE\}/g, upperSnake);
37
+ }
38
+ /**
39
+ * Resolve outputs configuration
40
+ */
41
+ function resolveOutputs(moduleName, moduleConfig, defaults) {
42
+ const expand = (s) => expandPlaceholders(s, moduleName);
43
+ const resolvedOutputs = [];
44
+ // Merge default outputs with module overrides
45
+ const defaultOutputs = defaults.outputs || {};
46
+ const moduleOutputs = moduleConfig.outputs || {};
47
+ const allOutputIds = new Set([
48
+ ...Object.keys(defaultOutputs),
49
+ ...Object.keys(moduleOutputs),
50
+ ]);
51
+ for (const id of allOutputIds) {
52
+ const defaultConfig = defaultOutputs[id];
53
+ const moduleOverride = moduleOutputs[id];
54
+ // Skip if explicitly disabled
55
+ if (moduleOverride?.enabled === false)
56
+ continue;
57
+ // Need at least default config
58
+ if (!defaultConfig && !moduleOverride?.output)
59
+ continue;
60
+ const output = expand(moduleOverride?.output ?? defaultConfig?.output ?? '');
61
+ const template = moduleOverride?.template ?? defaultConfig?.template ?? '';
62
+ if (!output || !template)
63
+ continue;
64
+ resolvedOutputs.push({
65
+ id,
66
+ output,
67
+ template,
68
+ overwrite: moduleOverride?.overwrite ?? defaultConfig?.overwrite ?? true,
69
+ condition: moduleOverride?.condition ?? defaultConfig?.condition ?? 'always',
70
+ enabled: moduleOverride?.enabled ?? defaultConfig?.enabled ?? true,
71
+ config: {
72
+ ...(defaultConfig?.config || {}),
73
+ ...(moduleOverride?.config || {}),
74
+ },
75
+ });
76
+ }
77
+ return resolvedOutputs;
78
+ }
79
+ /**
80
+ * Resolve module config by applying defaults and expanding placeholders
81
+ */
82
+ export function resolveModuleConfig(moduleName, moduleConfig, defaults = {}) {
83
+ const expand = (s) => expandPlaceholders(s, moduleName);
84
+ // Contract output
85
+ const contractOutput = expand(moduleConfig.contract?.output ??
86
+ defaults.contract?.output ??
87
+ `packages/contract/${moduleName}`);
88
+ // Public contract output
89
+ const contractPublicOutput = expand(moduleConfig.contractPublic?.output ??
90
+ defaults.contractPublic?.output ??
91
+ `packages/contract-published/${moduleName}`);
92
+ // Server config (legacy)
93
+ const serverEnabled = moduleConfig.server?.enabled !== false;
94
+ const server = serverEnabled ? {
95
+ output: expand(moduleConfig.server?.output ??
96
+ defaults.server?.output ??
97
+ `server/src/${moduleName}`),
98
+ routes: moduleConfig.server?.routes ?? defaults.server?.routes ?? 'routes.generated.ts',
99
+ domainsPath: expand(moduleConfig.server?.domainsPath ??
100
+ defaults.server?.domainsPath ??
101
+ `fastify.domains.${moduleName}`),
102
+ } : null;
103
+ // Frontend config (legacy)
104
+ const frontendEnabled = moduleConfig.frontend?.enabled !== false;
105
+ const frontendDefaults = defaults.frontend;
106
+ const frontendOverride = moduleConfig.frontend;
107
+ let frontendShared = null;
108
+ if (frontendEnabled) {
109
+ const sharedConfig = frontendOverride?.shared ?? frontendDefaults?.shared;
110
+ if (sharedConfig) {
111
+ frontendShared = {
112
+ output: expand(sharedConfig.output ?? 'frontend/src/shared'),
113
+ client: expand(sharedConfig.client ?? `${moduleName}.api.generated.ts`),
114
+ };
115
+ }
116
+ }
117
+ const frontend = frontendEnabled ? {
118
+ output: expand(frontendOverride?.output ??
119
+ frontendDefaults?.output ??
120
+ `frontend/src/${moduleName}`),
121
+ client: frontendOverride?.client ?? frontendDefaults?.client ?? 'api.generated.ts',
122
+ domain: frontendOverride?.domain ?? frontendDefaults?.domain ?? 'domain.generated.ts',
123
+ shared: frontendShared,
124
+ } : null;
125
+ // Docs config
126
+ const docs = {
127
+ enabled: moduleConfig.docs?.enabled ?? defaults.docs?.enabled ?? true,
128
+ template: moduleConfig.docs?.template ?? defaults.docs?.template ?? 'default',
129
+ };
130
+ // Overlays (shared + module-specific)
131
+ const overlays = [
132
+ ...(defaults.overlays?.shared || []),
133
+ ...(moduleConfig.overlays || []),
134
+ ];
135
+ const overlayCollision = defaults.overlays?.collision || 'error';
136
+ // Templates (module overrides defaults) - legacy
137
+ const serverWithTemplate = server ? {
138
+ ...server,
139
+ template: moduleConfig.templates?.server ?? defaults.templates?.server,
140
+ } : null;
141
+ const frontendWithTemplate = frontend ? {
142
+ ...frontend,
143
+ template: moduleConfig.templates?.frontend ?? defaults.templates?.frontend,
144
+ } : null;
145
+ // New outputs system
146
+ const outputs = resolveOutputs(moduleName, moduleConfig, defaults);
147
+ return {
148
+ name: moduleName,
149
+ openapi: moduleConfig.openapi,
150
+ contractOutput,
151
+ contractPublicOutput,
152
+ server: serverWithTemplate,
153
+ frontend: frontendWithTemplate,
154
+ docs,
155
+ overlays,
156
+ overlayCollision,
157
+ outputs,
158
+ spectral: moduleConfig.spectral,
159
+ dependsOn: moduleConfig.dependsOn,
160
+ };
161
+ }
162
+ // Helper to check if object is a reference
163
+ export function isReference(obj) {
164
+ return typeof obj === 'object' && obj !== null && '$ref' in obj;
165
+ }
166
+ // Extract schema name from $ref
167
+ export function getRefName(ref) {
168
+ // "#/components/schemas/EntryListResponse" -> "EntryListResponse"
169
+ const parts = ref.split('/');
170
+ return parts[parts.length - 1];
171
+ }
172
+ /**
173
+ * Extract dependencies from OpenAPI spec
174
+ */
175
+ export function extractDependencies(spec) {
176
+ const moduleLevelDeps = [];
177
+ const operationLevelDeps = new Map();
178
+ const allDepsSet = new Set();
179
+ // Module-level dependencies
180
+ const moduleDepRefs = spec.info['x-micro-contracts-depend-on'] || [];
181
+ for (const ref of moduleDepRefs) {
182
+ const parsed = parseDependencyRef(ref);
183
+ if (parsed) {
184
+ moduleLevelDeps.push(parsed);
185
+ allDepsSet.add(ref);
186
+ }
187
+ }
188
+ // Operation-level dependencies
189
+ for (const [, pathItem] of Object.entries(spec.paths)) {
190
+ for (const method of ['get', 'post', 'put', 'patch', 'delete']) {
191
+ const operation = pathItem[method];
192
+ if (!operation)
193
+ continue;
194
+ const opId = operation.operationId || '';
195
+ const opDepRefs = operation['x-micro-contracts-depend-on'] || [];
196
+ if (opDepRefs.length > 0) {
197
+ const parsedDeps = [];
198
+ for (const ref of opDepRefs) {
199
+ const parsed = parseDependencyRef(ref);
200
+ if (parsed) {
201
+ parsedDeps.push(parsed);
202
+ allDepsSet.add(ref);
203
+ }
204
+ }
205
+ if (parsedDeps.length > 0) {
206
+ operationLevelDeps.set(opId, parsedDeps);
207
+ }
208
+ }
209
+ }
210
+ }
211
+ // All unique deps
212
+ const allDeps = [];
213
+ for (const ref of allDepsSet) {
214
+ const parsed = parseDependencyRef(ref);
215
+ if (parsed)
216
+ allDeps.push(parsed);
217
+ }
218
+ return { moduleLevelDeps, operationLevelDeps, allDeps };
219
+ }
220
+ /**
221
+ * Get canonical extension value (supports both short and long forms)
222
+ */
223
+ export function getExtensionValue(obj, shortName, longName) {
224
+ return (obj[longName] ?? obj[shortName]);
225
+ }
226
+ // Check if a schema contains x-private properties
227
+ export function hasPrivateProperties(schema, spec, visited = new Set()) {
228
+ if (isReference(schema)) {
229
+ const refName = getRefName(schema.$ref);
230
+ if (visited.has(refName))
231
+ return false;
232
+ visited.add(refName);
233
+ const resolved = spec.components?.schemas?.[refName];
234
+ if (!resolved)
235
+ return false;
236
+ return hasPrivateProperties(resolved, spec, visited);
237
+ }
238
+ // Check if schema itself is private
239
+ if (schema['x-private'])
240
+ return true;
241
+ // Check properties
242
+ if (schema.properties) {
243
+ for (const propSchema of Object.values(schema.properties)) {
244
+ if (hasPrivateProperties(propSchema, spec, visited))
245
+ return true;
246
+ }
247
+ }
248
+ // Check array items
249
+ if (schema.items) {
250
+ if (hasPrivateProperties(schema.items, spec, visited))
251
+ return true;
252
+ }
253
+ // Check allOf/oneOf/anyOf
254
+ for (const composite of [schema.allOf, schema.oneOf, schema.anyOf]) {
255
+ if (composite) {
256
+ for (const s of composite) {
257
+ if (hasPrivateProperties(s, spec, visited))
258
+ return true;
259
+ }
260
+ }
261
+ }
262
+ return false;
263
+ }
264
+ // Collect all schemas referenced by a schema
265
+ export function collectReferencedSchemas(schema, spec, result = new Set()) {
266
+ if (isReference(schema)) {
267
+ const refName = getRefName(schema.$ref);
268
+ if (result.has(refName))
269
+ return result;
270
+ result.add(refName);
271
+ const resolved = spec.components?.schemas?.[refName];
272
+ if (resolved) {
273
+ collectReferencedSchemas(resolved, spec, result);
274
+ }
275
+ return result;
276
+ }
277
+ if (schema.properties) {
278
+ for (const propSchema of Object.values(schema.properties)) {
279
+ collectReferencedSchemas(propSchema, spec, result);
280
+ }
281
+ }
282
+ if (schema.items) {
283
+ collectReferencedSchemas(schema.items, spec, result);
284
+ }
285
+ for (const composite of [schema.allOf, schema.oneOf, schema.anyOf]) {
286
+ if (composite) {
287
+ for (const s of composite) {
288
+ collectReferencedSchemas(s, spec, result);
289
+ }
290
+ }
291
+ }
292
+ if (schema.additionalProperties && typeof schema.additionalProperties !== 'boolean') {
293
+ collectReferencedSchemas(schema.additionalProperties, spec, result);
294
+ }
295
+ return result;
296
+ }
297
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,226 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 950 870">
2
+ <defs>
3
+ <filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
4
+ <feDropShadow dx="2" dy="3" stdDeviation="3" flood-opacity="0.15"/>
5
+ </filter>
6
+ <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
7
+ <polygon points="0 0, 10 3.5, 0 7" fill="#64748b"/>
8
+ </marker>
9
+ <marker id="arrowhead-blue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
10
+ <polygon points="0 0, 10 3.5, 0 7" fill="#3b82f6"/>
11
+ </marker>
12
+ <marker id="arrowhead-teal" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
13
+ <polygon points="0 0, 10 3.5, 0 7" fill="#0d9488"/>
14
+ </marker>
15
+ <pattern id="stripe" patternUnits="userSpaceOnUse" width="8" height="8" patternTransform="rotate(45)">
16
+ <line x1="0" y1="0" x2="0" y2="8" stroke="#e2e8f0" stroke-width="4"/>
17
+ </pattern>
18
+ </defs>
19
+
20
+ <style>
21
+ .title { font: bold 13px 'Segoe UI', system-ui, sans-serif; fill: white; }
22
+ .title-dark { font: bold 13px 'Segoe UI', system-ui, sans-serif; fill: #1e293b; }
23
+ .subtitle { font: 11px 'Segoe UI', system-ui, sans-serif; fill: rgba(255,255,255,0.85); }
24
+ .item { font: 10px 'Segoe UI', system-ui, sans-serif; fill: #334155; }
25
+ .item-small { font: 9px 'Segoe UI', system-ui, sans-serif; fill: #64748b; }
26
+ .label { font: 10px 'Segoe UI', system-ui, sans-serif; fill: #64748b; }
27
+ .section-label { font: bold 11px 'Segoe UI', system-ui, sans-serif; fill: #475569; }
28
+ </style>
29
+
30
+ <!-- Background -->
31
+ <rect width="950" height="850" fill="#f8fafc"/>
32
+
33
+ <!-- Title -->
34
+ <text x="475" y="30" text-anchor="middle" class="title-dark" style="font-size:16px;">Vertical Slice Architecture with micro-contracts</text>
35
+
36
+ <!-- OpenAPI Layer -->
37
+ <g filter="url(#shadow)">
38
+ <rect x="50" y="50" width="850" height="50" rx="8" fill="#5b21b6"/>
39
+ </g>
40
+ <text x="475" y="75" text-anchor="middle" class="title" style="font-size:14px;">📄 OpenAPI Specs (Single Source of Truth)</text>
41
+ <text x="475" y="92" text-anchor="middle" class="subtitle">core.yaml · users.yaml · notifications.yaml</text>
42
+
43
+ <!-- Generate arrows -->
44
+ <path d="M 185 100 L 185 125" fill="none" stroke="#64748b" stroke-width="2" marker-end="url(#arrowhead)"/>
45
+ <path d="M 475 100 L 475 125" fill="none" stroke="#64748b" stroke-width="2" marker-end="url(#arrowhead)"/>
46
+ <path d="M 765 100 L 765 125" fill="none" stroke="#64748b" stroke-width="2" marker-end="url(#arrowhead)"/>
47
+ <text x="120" y="118" class="label">generate</text>
48
+ <text x="410" y="118" class="label">generate</text>
49
+ <text x="700" y="118" class="label">generate</text>
50
+
51
+ <!-- Contract Layer (3 boxes) -->
52
+ <!-- contract/core -->
53
+ <g filter="url(#shadow)">
54
+ <rect x="50" y="130" width="270" height="60" rx="6" fill="white" stroke="#16a34a" stroke-width="2"/>
55
+ <rect x="50" y="130" width="270" height="28" rx="6" fill="#16a34a"/>
56
+ <rect x="50" y="152" width="270" height="6" rx="0" fill="#16a34a"/>
57
+ </g>
58
+ <text x="185" y="149" text-anchor="middle" class="title" style="font-size:12px;">📦 contract/core/</text>
59
+ <text x="60" y="175" class="item-small" style="fill:#16a34a;font-weight:600;">Shared between server ↔ frontend</text>
60
+
61
+ <!-- contract/users -->
62
+ <g filter="url(#shadow)">
63
+ <rect x="340" y="130" width="270" height="60" rx="6" fill="white" stroke="#16a34a" stroke-width="2"/>
64
+ <rect x="340" y="130" width="270" height="28" rx="6" fill="#16a34a"/>
65
+ <rect x="340" y="152" width="270" height="6" rx="0" fill="#16a34a"/>
66
+ </g>
67
+ <text x="475" y="149" text-anchor="middle" class="title" style="font-size:12px;">📦 contract/users/</text>
68
+ <text x="350" y="175" class="item-small" style="fill:#16a34a;font-weight:600;">Shared between server ↔ frontend</text>
69
+
70
+ <!-- contract/notifications -->
71
+ <g filter="url(#shadow)">
72
+ <rect x="630" y="130" width="270" height="60" rx="6" fill="white" stroke="#16a34a" stroke-width="2"/>
73
+ <rect x="630" y="130" width="270" height="28" rx="6" fill="#16a34a"/>
74
+ <rect x="630" y="152" width="270" height="6" rx="0" fill="#16a34a"/>
75
+ </g>
76
+ <text x="765" y="149" text-anchor="middle" class="title" style="font-size:12px;">📦 contract/notifications/</text>
77
+ <text x="640" y="175" class="item-small" style="fill:#16a34a;font-weight:600;">Intra-module types (server only)</text>
78
+
79
+ <!-- Extract arrows to contract-published -->
80
+ <path d="M 185 190 L 185 235" fill="none" stroke="#0d9488" stroke-width="2" marker-end="url(#arrowhead-teal)"/>
81
+ <path d="M 475 190 L 475 235" fill="none" stroke="#0d9488" stroke-width="2" marker-end="url(#arrowhead-teal)"/>
82
+ <text x="200" y="218" class="item-small" style="fill:#0d9488;">extract</text>
83
+ <text x="490" y="218" class="item-small" style="fill:#0d9488;">extract</text>
84
+ <text x="765" y="218" class="item-small" style="fill:#94a3b8;">no x-micro-contracts-published</text>
85
+
86
+ <!-- contract-published Layer -->
87
+ <g filter="url(#shadow)">
88
+ <rect x="50" y="240" width="850" height="80" rx="8" fill="white" stroke="#0d9488" stroke-width="2"/>
89
+ <rect x="50" y="240" width="850" height="30" rx="8" fill="#0d9488"/>
90
+ <rect x="50" y="264" width="850" height="6" rx="0" fill="#0d9488"/>
91
+ </g>
92
+ <text x="475" y="260" text-anchor="middle" class="title" style="font-size:12px;">📦 packages/contract-published/ — Cross-module Shared Contracts (x-micro-contracts-published: true only)</text>
93
+ <text x="475" y="285" text-anchor="middle" class="item-small" style="fill:#0d9488;">Must maintain backward compatibility — Used for cross-module API calls</text>
94
+
95
+ <rect x="120" y="297" width="130" height="16" rx="3" fill="#ccfbf1" stroke="#0d9488"/>
96
+ <text x="185" y="308" text-anchor="middle" class="item-small" style="fill:#0f766e;font-size:8px;">contract-published/core/</text>
97
+
98
+ <rect x="410" y="297" width="130" height="16" rx="3" fill="#ccfbf1" stroke="#0d9488"/>
99
+ <text x="475" y="308" text-anchor="middle" class="item-small" style="fill:#0f766e;font-size:8px;">contract-published/users/</text>
100
+
101
+ <rect x="700" y="297" width="130" height="16" rx="3" fill="#f1f5f9" stroke="#cbd5e1"/>
102
+ <text x="765" y="308" text-anchor="middle" class="item-small" style="fill:#94a3b8;font-size:8px;">(none)</text>
103
+
104
+ <!-- Vertical Slice 1: Core -->
105
+ <g filter="url(#shadow)">
106
+ <rect x="50" y="370" width="270" height="295" rx="8" fill="white" stroke="#8b5cf6" stroke-width="2"/>
107
+ <rect x="50" y="370" width="270" height="35" rx="8" fill="#8b5cf6"/>
108
+ <rect x="50" y="399" width="270" height="6" rx="0" fill="#8b5cf6"/>
109
+ </g>
110
+ <text x="185" y="394" text-anchor="middle" class="title">🟣 core module</text>
111
+
112
+ <!-- Core - Server -->
113
+ <rect x="65" y="420" width="240" height="85" rx="4" fill="#faf5ff" stroke="#c4b5fd"/>
114
+ <text x="185" y="438" text-anchor="middle" class="item" style="font-weight:600;">🖥️ server/src/core/</text>
115
+ <text x="75" y="458" class="item-small">▸ routes.generated.ts</text>
116
+ <text x="75" y="472" class="item-small">▸ domains/ (hand-code)</text>
117
+ <text x="75" y="486" class="item-small">▸ models/</text>
118
+
119
+ <!-- Core - Frontend -->
120
+ <rect x="65" y="515" width="240" height="60" rx="4" fill="#f5f3ff" stroke="#c4b5fd"/>
121
+ <text x="185" y="533" text-anchor="middle" class="item" style="font-weight:600;">🌐 frontend/src/core/</text>
122
+ <text x="75" y="553" class="item-small">▸ api.generated.ts</text>
123
+ <text x="75" y="565" class="item-small">▸ ui/ (hand-code)</text>
124
+
125
+ <!-- Core - Deploy info -->
126
+ <rect x="65" y="585" width="240" height="28" rx="4" fill="#ede9fe" stroke="#a78bfa"/>
127
+ <text x="185" y="603" text-anchor="middle" class="item-small" style="fill:#6d28d9;">Deploy: api-server + web-app</text>
128
+
129
+ <!-- Core - imports note -->
130
+ <text x="185" y="635" text-anchor="middle" class="item-small" style="fill:#16a34a;">imports: contract/core/</text>
131
+ <text x="185" y="650" text-anchor="middle" class="item-small" style="fill:#16a34a;">(server ↔ frontend shared)</text>
132
+
133
+ <!-- Vertical Slice 2: Users -->
134
+ <g filter="url(#shadow)">
135
+ <rect x="340" y="370" width="270" height="295" rx="8" fill="white" stroke="#f97316" stroke-width="2"/>
136
+ <rect x="340" y="370" width="270" height="35" rx="8" fill="#f97316"/>
137
+ <rect x="340" y="399" width="270" height="6" rx="0" fill="#f97316"/>
138
+ </g>
139
+ <text x="475" y="394" text-anchor="middle" class="title">🟠 users module</text>
140
+
141
+ <!-- Users - Server -->
142
+ <rect x="355" y="420" width="240" height="85" rx="4" fill="#fff7ed" stroke="#fdba74"/>
143
+ <text x="475" y="438" text-anchor="middle" class="item" style="font-weight:600;">🖥️ server/src/users/</text>
144
+ <text x="365" y="458" class="item-small">▸ routes.generated.ts</text>
145
+ <text x="365" y="472" class="item-small">▸ domains/ (hand-code)</text>
146
+ <text x="365" y="486" class="item-small">▸ models/</text>
147
+
148
+ <!-- Users - Frontend -->
149
+ <rect x="355" y="515" width="240" height="60" rx="4" fill="#ffedd5" stroke="#fdba74"/>
150
+ <text x="475" y="533" text-anchor="middle" class="item" style="font-weight:600;">🌐 frontend/src/users/</text>
151
+ <text x="365" y="553" class="item-small">▸ api.generated.ts</text>
152
+ <text x="365" y="565" class="item-small">▸ ui/ (hand-code)</text>
153
+
154
+ <!-- Users - Deploy info -->
155
+ <rect x="355" y="585" width="240" height="28" rx="4" fill="#fed7aa" stroke="#fb923c"/>
156
+ <text x="475" y="603" text-anchor="middle" class="item-small" style="fill:#c2410c;">Deploy: same or separate</text>
157
+
158
+ <!-- Users - imports note -->
159
+ <text x="475" y="635" text-anchor="middle" class="item-small" style="fill:#16a34a;">imports: contract/users/</text>
160
+ <text x="475" y="650" text-anchor="middle" class="item-small" style="fill:#0d9488;">+ contract-published/core/ (cross-module)</text>
161
+
162
+ <!-- Vertical Slice 3: Notifications -->
163
+ <g filter="url(#shadow)">
164
+ <rect x="630" y="370" width="270" height="295" rx="8" fill="white" stroke="#06b6d4" stroke-width="2"/>
165
+ <rect x="630" y="370" width="270" height="35" rx="8" fill="#06b6d4"/>
166
+ <rect x="630" y="399" width="270" height="6" rx="0" fill="#06b6d4"/>
167
+ </g>
168
+ <text x="765" y="394" text-anchor="middle" class="title">🔵 notifications module</text>
169
+
170
+ <!-- Notifications - Server only -->
171
+ <rect x="645" y="420" width="240" height="85" rx="4" fill="#ecfeff" stroke="#67e8f9"/>
172
+ <text x="765" y="438" text-anchor="middle" class="item" style="font-weight:600;">🖥️ notification-service/</text>
173
+ <text x="655" y="458" class="item-small">▸ routes.generated.ts</text>
174
+ <text x="655" y="472" class="item-small">▸ domains/ (hand-code)</text>
175
+ <text x="655" y="486" class="item-small">▸ models/</text>
176
+
177
+ <!-- Notifications - No Frontend -->
178
+ <rect x="645" y="515" width="240" height="60" rx="4" fill="url(#stripe)" stroke="#94a3b8"/>
179
+ <text x="765" y="550" text-anchor="middle" class="item-small" style="fill:#94a3b8;">No frontend (API-only)</text>
180
+
181
+ <!-- Notifications - Deploy info -->
182
+ <rect x="645" y="585" width="240" height="28" rx="4" fill="#cffafe" stroke="#22d3ee"/>
183
+ <text x="765" y="603" text-anchor="middle" class="item-small" style="fill:#0e7490;">Deploy: separate microservice</text>
184
+
185
+ <!-- Notifications - imports note -->
186
+ <text x="765" y="635" text-anchor="middle" class="item-small" style="fill:#16a34a;">imports: contract/notifications/</text>
187
+ <text x="765" y="650" text-anchor="middle" class="item-small" style="fill:#0d9488;">+ contract-published/core/ (cross-module)</text>
188
+
189
+ <!-- Intra-module import arrows: contract/{module} → same module (BLUE) -->
190
+ <path d="M 100 190 L 100 370" fill="none" stroke="#3b82f6" stroke-width="2" marker-end="url(#arrowhead-blue)"/>
191
+ <path d="M 390 190 L 390 370" fill="none" stroke="#3b82f6" stroke-width="2" marker-end="url(#arrowhead-blue)"/>
192
+ <path d="M 680 190 L 680 370" fill="none" stroke="#3b82f6" stroke-width="2" marker-end="url(#arrowhead-blue)"/>
193
+ <text x="108" y="218" class="item-small" style="fill:#3b82f6;">import</text>
194
+ <text x="398" y="218" class="item-small" style="fill:#3b82f6;">import</text>
195
+ <text x="688" y="218" class="item-small" style="fill:#3b82f6;">import</text>
196
+
197
+ <!-- Cross-module import arrows: contract-published → other modules (TEAL dashed) -->
198
+ <path d="M 185 313 L 420 370" fill="none" stroke="#0d9488" stroke-width="1.5" stroke-dasharray="5,3"/>
199
+ <path d="M 185 313 L 700 370" fill="none" stroke="#0d9488" stroke-width="1.5" stroke-dasharray="5,3"/>
200
+
201
+ <!-- Section Label (below modules) -->
202
+ <text x="475" y="685" text-anchor="middle" class="section-label">↑ Vertical Slices (Feature-aligned Modules)</text>
203
+
204
+ <!-- Legend -->
205
+ <g transform="translate(50, 710)">
206
+ <rect width="850" height="50" rx="6" fill="#f1f5f9" stroke="#e2e8f0"/>
207
+ <text x="20" y="30" class="section-label">Legend:</text>
208
+
209
+ <line x1="120" y1="27" x2="155" y2="27" stroke="#3b82f6" stroke-width="2" marker-end="url(#arrowhead-blue)"/>
210
+ <text x="165" y="30" class="item">intra-module import</text>
211
+
212
+ <line x1="340" y1="27" x2="375" y2="27" stroke="#0d9488" stroke-width="2" marker-end="url(#arrowhead-teal)"/>
213
+ <text x="385" y="30" class="item">extract (x-micro-contracts-published)</text>
214
+
215
+ <line x1="540" y1="27" x2="575" y2="27" stroke="#0d9488" stroke-width="1.5" stroke-dasharray="5,3"/>
216
+ <text x="585" y="30" class="item">cross-module import</text>
217
+ </g>
218
+
219
+ <!-- Key insight callout -->
220
+ <g transform="translate(50, 775)">
221
+ <rect width="850" height="65" rx="6" fill="#fefce8" stroke="#facc15"/>
222
+ <text x="20" y="22" class="item" style="font-weight:600;fill:#854d0e;">💡 Key Insight:</text>
223
+ <text x="20" y="40" class="item" style="fill:#713f12;">contract/ = intra-module shared types (server ↔ frontend), can change freely</text>
224
+ <text x="20" y="55" class="item" style="fill:#713f12;">contract-published/ = cross-module shared types (x-micro-contracts-published: true only), must maintain backward compatibility</text>
225
+ </g>
226
+ </svg>