@archrad/deterministic 0.1.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 (93) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/CONTRIBUTING.md +15 -0
  3. package/LICENSE +17 -0
  4. package/README.md +284 -0
  5. package/SECURITY.md +26 -0
  6. package/biome.json +25 -0
  7. package/demo-validate.gif +0 -0
  8. package/dist/cli-findings.d.ts +23 -0
  9. package/dist/cli-findings.d.ts.map +1 -0
  10. package/dist/cli-findings.js +88 -0
  11. package/dist/cli.d.ts +7 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +341 -0
  14. package/dist/edgeConfigCodeGenerator.d.ts +55 -0
  15. package/dist/edgeConfigCodeGenerator.d.ts.map +1 -0
  16. package/dist/edgeConfigCodeGenerator.js +249 -0
  17. package/dist/exportPipeline.d.ts +23 -0
  18. package/dist/exportPipeline.d.ts.map +1 -0
  19. package/dist/exportPipeline.js +65 -0
  20. package/dist/golden-bundle.d.ts +21 -0
  21. package/dist/golden-bundle.d.ts.map +1 -0
  22. package/dist/golden-bundle.js +166 -0
  23. package/dist/graphPredicates.d.ts +10 -0
  24. package/dist/graphPredicates.d.ts.map +1 -0
  25. package/dist/graphPredicates.js +33 -0
  26. package/dist/hostPort.d.ts +12 -0
  27. package/dist/hostPort.d.ts.map +1 -0
  28. package/dist/hostPort.js +39 -0
  29. package/dist/index.d.ts +22 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +21 -0
  32. package/dist/ir-lint.d.ts +11 -0
  33. package/dist/ir-lint.d.ts.map +1 -0
  34. package/dist/ir-lint.js +16 -0
  35. package/dist/ir-normalize.d.ts +48 -0
  36. package/dist/ir-normalize.d.ts.map +1 -0
  37. package/dist/ir-normalize.js +81 -0
  38. package/dist/ir-structural.d.ts +40 -0
  39. package/dist/ir-structural.d.ts.map +1 -0
  40. package/dist/ir-structural.js +267 -0
  41. package/dist/lint-graph.d.ts +40 -0
  42. package/dist/lint-graph.d.ts.map +1 -0
  43. package/dist/lint-graph.js +133 -0
  44. package/dist/lint-rules.d.ts +40 -0
  45. package/dist/lint-rules.d.ts.map +1 -0
  46. package/dist/lint-rules.js +290 -0
  47. package/dist/nodeExpress.d.ts +2 -0
  48. package/dist/nodeExpress.d.ts.map +1 -0
  49. package/dist/nodeExpress.js +528 -0
  50. package/dist/openapi-structural.d.ts +26 -0
  51. package/dist/openapi-structural.d.ts.map +1 -0
  52. package/dist/openapi-structural.js +82 -0
  53. package/dist/openapi-to-ir.d.ts +26 -0
  54. package/dist/openapi-to-ir.d.ts.map +1 -0
  55. package/dist/openapi-to-ir.js +131 -0
  56. package/dist/pythonFastAPI.d.ts +2 -0
  57. package/dist/pythonFastAPI.d.ts.map +1 -0
  58. package/dist/pythonFastAPI.js +664 -0
  59. package/dist/validate-drift.d.ts +54 -0
  60. package/dist/validate-drift.d.ts.map +1 -0
  61. package/dist/validate-drift.js +184 -0
  62. package/dist/yamlToIr.d.ts +14 -0
  63. package/dist/yamlToIr.d.ts.map +1 -0
  64. package/dist/yamlToIr.js +39 -0
  65. package/docs/CONCEPT_ADOPTION_AND_LIMITS.md +47 -0
  66. package/docs/CUSTOM_RULES.md +87 -0
  67. package/docs/ENGINEERING_NOTES.md +42 -0
  68. package/docs/IR_CONTRACT.md +54 -0
  69. package/docs/STRUCTURAL_VS_SEMANTIC_VALIDATION.md +86 -0
  70. package/fixtures/demo-direct-db-layered.json +37 -0
  71. package/fixtures/demo-direct-db-violation.json +22 -0
  72. package/fixtures/ecommerce-with-warnings.json +89 -0
  73. package/fixtures/invalid-cycle.json +15 -0
  74. package/fixtures/invalid-edge-unknown-node.json +14 -0
  75. package/fixtures/minimal-graph.json +14 -0
  76. package/fixtures/minimal-graph.yaml +13 -0
  77. package/fixtures/payment-retry-demo.json +43 -0
  78. package/llms.txt +99 -0
  79. package/package.json +84 -0
  80. package/schemas/archrad-ir-graph-v1.schema.json +67 -0
  81. package/scripts/DEMO_GIF_STORYBOARD.md +100 -0
  82. package/scripts/GIF_RECORDING_STEP_BY_STEP.md +125 -0
  83. package/scripts/README_DEMO_RECORDING.md +314 -0
  84. package/scripts/SOCIAL_POST_DRIFT_AND_INGESTION.md +17 -0
  85. package/scripts/golden-path-demo.ps1 +25 -0
  86. package/scripts/golden-path-demo.sh +23 -0
  87. package/scripts/invoke-drift-check.ps1 +16 -0
  88. package/scripts/record-demo-drift.tape +50 -0
  89. package/scripts/record-demo-payment-retry.tape +36 -0
  90. package/scripts/record-demo-validate.tape +34 -0
  91. package/scripts/record-demo.tape +33 -0
  92. package/scripts/run-demo-drift-sequence.ps1 +45 -0
  93. package/scripts/run-demo-drift-sequence.sh +41 -0
package/dist/cli.js ADDED
@@ -0,0 +1,341 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * archrad — deterministic export without the hosted server.
4
+ * Usage: archrad export --ir graph.json --target python --out ./my-api
5
+ */
6
+ import { readFile, mkdir, writeFile } from 'node:fs/promises';
7
+ import { dirname, join, resolve } from 'node:path';
8
+ import { Command, Option } from 'commander';
9
+ import { runDeterministicExport } from './exportPipeline.js';
10
+ import { isLocalHostPortFree, normalizeGoldenHostPort } from './hostPort.js';
11
+ import { validateIrStructural, hasIrStructuralErrors } from './ir-structural.js';
12
+ import { validateIrLint } from './ir-lint.js';
13
+ import { printFindingsPretty, shouldFailFromFindings, sortFindings, } from './cli-findings.js';
14
+ import { parseYamlToCanonicalIr, canonicalIrToJsonString, YamlGraphParseError, } from './yamlToIr.js';
15
+ import { openApiStringToCanonicalIr, OpenApiIngestError } from './openapi-to-ir.js';
16
+ import { runValidateDrift } from './validate-drift.js';
17
+ async function writeTree(baseDir, files) {
18
+ for (const [rel, content] of Object.entries(files)) {
19
+ const dest = join(baseDir, rel);
20
+ await mkdir(dirname(dest), { recursive: true });
21
+ await writeFile(dest, content, 'utf8');
22
+ }
23
+ }
24
+ function parseMaxWarnings(v) {
25
+ if (v == null || v === '')
26
+ return undefined;
27
+ const n = parseInt(v, 10);
28
+ return Number.isFinite(n) ? n : undefined;
29
+ }
30
+ function exitPolicyFromOpts(opts) {
31
+ return {
32
+ failOnWarning: Boolean(opts.failOnWarning),
33
+ maxWarnings: parseMaxWarnings(opts.maxWarnings),
34
+ };
35
+ }
36
+ const program = new Command();
37
+ program
38
+ .name('archrad')
39
+ .description('Validate your architecture before you write code. Deterministic compiler + linter — FastAPI / Express (no LLM, no server).')
40
+ .version('0.1.0');
41
+ program
42
+ .command('validate')
43
+ .description('Validate your architecture before you write code — IR structural (IR-STRUCT-*) + architecture lint (IR-LINT-*)')
44
+ .requiredOption('-i, --ir <path>', 'Path to IR JSON (graph with nodes/edges or full wrapper)')
45
+ .option('--json', 'Print findings as JSON array to stdout')
46
+ .option('--skip-lint', 'Skip architecture lint (IR-LINT-*); structural only')
47
+ .option('--fail-on-warning', 'Exit with error if any warning (CI gate)')
48
+ .option('--max-warnings <n>', 'Exit with error if warning count is greater than n (e.g. 0 allows no warnings)')
49
+ .action(async (cmdOpts) => {
50
+ const irPath = resolve(cmdOpts.ir);
51
+ let ir;
52
+ try {
53
+ ir = JSON.parse(await readFile(irPath, 'utf8'));
54
+ }
55
+ catch {
56
+ console.error('archrad: invalid JSON in --ir file');
57
+ process.exitCode = 1;
58
+ return;
59
+ }
60
+ const noLint = Boolean(cmdOpts.skipLint);
61
+ const structural = validateIrStructural(ir);
62
+ const lint = noLint || hasIrStructuralErrors(structural) ? [] : validateIrLint(ir);
63
+ const combined = sortFindings([...structural, ...lint]);
64
+ if (cmdOpts.json) {
65
+ const forJson = combined.map((f) => ({
66
+ ...f,
67
+ layer: f.layer ?? (f.code.startsWith('IR-LINT-') ? 'lint' : 'structural'),
68
+ }));
69
+ console.log(JSON.stringify(forJson, null, 2));
70
+ }
71
+ else {
72
+ if (combined.length) {
73
+ printFindingsPretty(combined, 'archrad validate:');
74
+ }
75
+ else {
76
+ console.log('Validate your architecture before you write code.');
77
+ console.log('archrad: IR structural validation + architecture lint passed (no findings).');
78
+ }
79
+ }
80
+ const policy = exitPolicyFromOpts(cmdOpts);
81
+ if (shouldFailFromFindings(combined, policy)) {
82
+ process.exitCode = 1;
83
+ }
84
+ });
85
+ program
86
+ .command('yaml-to-ir')
87
+ .description('Convert YAML graph → canonical IR JSON (for validate / export without hand-editing JSON)')
88
+ .requiredOption('-y, --yaml <path>', 'Path to YAML blueprint (`graph:` wrapper or bare `nodes:`)')
89
+ .option('-o, --out <path>', 'Write JSON to file (default: print to stdout)')
90
+ .action(async (cmdOpts) => {
91
+ const yamlPath = resolve(cmdOpts.yaml);
92
+ let text;
93
+ try {
94
+ text = await readFile(yamlPath, 'utf8');
95
+ }
96
+ catch {
97
+ console.error('archrad yaml-to-ir: could not read --yaml file');
98
+ process.exitCode = 1;
99
+ return;
100
+ }
101
+ let ir;
102
+ try {
103
+ ir = parseYamlToCanonicalIr(text);
104
+ }
105
+ catch (e) {
106
+ if (e instanceof YamlGraphParseError) {
107
+ console.error(`archrad yaml-to-ir: ${e.message}`);
108
+ }
109
+ else {
110
+ console.error('archrad yaml-to-ir:', e);
111
+ }
112
+ process.exitCode = 1;
113
+ return;
114
+ }
115
+ const json = canonicalIrToJsonString(ir);
116
+ if (cmdOpts.out) {
117
+ const outPath = resolve(cmdOpts.out);
118
+ await mkdir(dirname(outPath), { recursive: true });
119
+ await writeFile(outPath, json, 'utf8');
120
+ console.log(`archrad: wrote IR JSON to ${outPath}`);
121
+ }
122
+ else {
123
+ process.stdout.write(json);
124
+ }
125
+ });
126
+ const ingest = program.command('ingest').description('Derive canonical IR from an external spec (structural surface — same JSON as yaml-to-ir for validate/export)');
127
+ ingest
128
+ .command('openapi')
129
+ .description('OpenAPI 3.x JSON/YAML → IR graph (HTTP nodes per operation; commit + archrad validate in CI)')
130
+ .requiredOption('-s, --spec <path>', 'Path to OpenAPI 3.x document (.json, .yaml, or .yml)')
131
+ .option('-o, --out <path>', 'Write IR JSON to file (default: print to stdout)')
132
+ .action(async (cmdOpts) => {
133
+ const specPath = resolve(cmdOpts.spec);
134
+ let text;
135
+ try {
136
+ text = await readFile(specPath, 'utf8');
137
+ }
138
+ catch {
139
+ console.error('archrad ingest openapi: could not read --spec file');
140
+ process.exitCode = 1;
141
+ return;
142
+ }
143
+ let ir;
144
+ try {
145
+ ir = openApiStringToCanonicalIr(text);
146
+ }
147
+ catch (e) {
148
+ if (e instanceof OpenApiIngestError) {
149
+ console.error(`archrad ingest openapi: ${e.message}`);
150
+ }
151
+ else {
152
+ console.error('archrad ingest openapi:', e);
153
+ }
154
+ process.exitCode = 1;
155
+ return;
156
+ }
157
+ const json = canonicalIrToJsonString(ir);
158
+ if (cmdOpts.out) {
159
+ const outPath = resolve(cmdOpts.out);
160
+ await mkdir(dirname(outPath), { recursive: true });
161
+ await writeFile(outPath, json, 'utf8');
162
+ console.log(`archrad ingest openapi: wrote IR JSON to ${outPath}`);
163
+ }
164
+ else {
165
+ process.stdout.write(json);
166
+ }
167
+ });
168
+ program
169
+ .command('export')
170
+ .description('Generate project files from a blueprint IR JSON file')
171
+ .requiredOption('-i, --ir <path>', 'Path to IR JSON (graph with nodes/edges or full wrapper)')
172
+ .requiredOption('-t, --target <name>', 'python | node | nodejs')
173
+ .requiredOption('-o, --out <dir>', 'Output directory')
174
+ .option('-p, --host-port <port>', 'Host port for docker compose publish (container stays 8080). Env: ARCHRAD_HOST_PORT')
175
+ .option('--skip-host-port-check', 'Do not check if host port is free on 127.0.0.1')
176
+ .option('--strict-host-port', 'Exit with error if host port is in use (implies check)')
177
+ .addOption(new Option('--danger-skip-ir-structural-validation', 'UNSAFE: skip validateIrStructural (invalid IR may still export; never use in CI)'))
178
+ .addOption(new Option('--skip-ir-structural-validation', 'Deprecated alias').hideHelp())
179
+ .option('--skip-ir-lint', 'Skip architecture lint (IR-LINT-*) during export')
180
+ .option('--fail-on-warning', 'Do not write output if IR structural or lint warnings exceed policy (with --max-warnings or any warning)')
181
+ .option('--max-warnings <n>', 'With export: fail if total IR warning count > n (structural + lint warnings)')
182
+ .action(async (cmdOpts) => {
183
+ const irPath = resolve(cmdOpts.ir);
184
+ const outDir = resolve(cmdOpts.out);
185
+ const raw = await readFile(irPath, 'utf8');
186
+ let ir;
187
+ try {
188
+ ir = JSON.parse(raw);
189
+ }
190
+ catch {
191
+ console.error('archrad: invalid JSON in --ir file');
192
+ process.exitCode = 1;
193
+ return;
194
+ }
195
+ const actualIR = ir.graph ? ir : { graph: ir };
196
+ const hostPort = normalizeGoldenHostPort(cmdOpts.hostPort ?? process.env.ARCHRAD_HOST_PORT);
197
+ if (!cmdOpts.skipHostPortCheck) {
198
+ const free = await isLocalHostPortFree(hostPort);
199
+ if (!free) {
200
+ const msg = `archrad: host port ${hostPort} appears in use on 127.0.0.1 (docker publish may fail). Use --host-port <n>, free the port, or --skip-host-port-check.`;
201
+ if (cmdOpts.strictHostPort) {
202
+ console.error(msg);
203
+ process.exitCode = 1;
204
+ return;
205
+ }
206
+ console.warn(`archrad: warning: ${msg}`);
207
+ }
208
+ }
209
+ const exportOpts = cmdOpts;
210
+ const skipStruct = Boolean(exportOpts.dangerSkipIrStructuralValidation || exportOpts.skipIrStructuralValidation);
211
+ try {
212
+ const { files, openApiStructuralWarnings, irStructuralFindings, irLintFindings } = await runDeterministicExport(actualIR, cmdOpts.target, {
213
+ hostPort,
214
+ skipIrStructuralValidation: skipStruct,
215
+ skipIrLint: cmdOpts.skipIrLint,
216
+ });
217
+ const combined = sortFindings([...irStructuralFindings, ...irLintFindings]);
218
+ if (combined.length) {
219
+ printFindingsPretty(combined, 'archrad export:');
220
+ }
221
+ const policy = exitPolicyFromOpts(cmdOpts);
222
+ const blockByPolicy = Object.keys(files).length > 0 &&
223
+ shouldFailFromFindings(combined, policy);
224
+ if (blockByPolicy) {
225
+ console.error('archrad: export aborted by --fail-on-warning / --max-warnings (no files written).');
226
+ process.exitCode = 1;
227
+ return;
228
+ }
229
+ if (Object.keys(files).length === 0) {
230
+ if (hasIrStructuralErrors(irStructuralFindings) && !cmdOpts.skipIrStructuralValidation) {
231
+ console.error('archrad: export aborted due to IR structural errors (fix graph or use archrad validate).');
232
+ }
233
+ else {
234
+ console.error('archrad: no files generated (check IR nodes/target)');
235
+ }
236
+ process.exitCode = 1;
237
+ return;
238
+ }
239
+ await writeTree(outDir, files);
240
+ console.log(`archrad: wrote ${Object.keys(files).length} files to ${outDir}`);
241
+ if (openApiStructuralWarnings.length) {
242
+ console.warn('archrad: OpenAPI document-shape warnings (parse + required fields, not Spectral lint):');
243
+ for (const w of openApiStructuralWarnings)
244
+ console.warn(` - ${w}`);
245
+ }
246
+ console.log('\nNext: cd to output, then `docker compose up --build` (see README in bundle).');
247
+ }
248
+ catch (e) {
249
+ console.error('archrad:', e?.message || String(e));
250
+ process.exitCode = 1;
251
+ }
252
+ });
253
+ program
254
+ .command('validate-drift')
255
+ .description('Compare on-disk export to a fresh deterministic export from IR (missing/modified files = drift; not semantic analysis)')
256
+ .requiredOption('-i, --ir <path>', 'Path to IR JSON (graph with nodes/edges or full wrapper)')
257
+ .requiredOption('-t, --target <name>', 'python | node | nodejs')
258
+ .requiredOption('-o, --out <dir>', 'Directory containing a previous archrad export to compare')
259
+ .option('-p, --host-port <port>', 'Host port for golden compose (must match export). Env: ARCHRAD_HOST_PORT')
260
+ .option('--skip-host-port-check', 'Do not check if host port is free on 127.0.0.1')
261
+ .addOption(new Option('--danger-skip-ir-structural-validation', 'UNSAFE: skip validateIrStructural during reference export'))
262
+ .addOption(new Option('--skip-ir-structural-validation', 'Deprecated alias').hideHelp())
263
+ .option('--skip-ir-lint', 'Skip architecture lint when building reference export')
264
+ .option('--strict-extra', 'Fail if output directory contains files not in the reference export')
265
+ .option('--json', 'Print drift findings and export metadata as JSON')
266
+ .action(async (cmdOpts) => {
267
+ const irPath = resolve(cmdOpts.ir);
268
+ const outDir = resolve(cmdOpts.out);
269
+ let ir;
270
+ try {
271
+ ir = JSON.parse(await readFile(irPath, 'utf8'));
272
+ }
273
+ catch {
274
+ console.error('archrad: invalid JSON in --ir file');
275
+ process.exitCode = 1;
276
+ return;
277
+ }
278
+ const actualIR = ir.graph ? ir : { graph: ir };
279
+ const hostPort = normalizeGoldenHostPort(cmdOpts.hostPort ?? process.env.ARCHRAD_HOST_PORT);
280
+ if (!cmdOpts.skipHostPortCheck) {
281
+ const free = await isLocalHostPortFree(hostPort);
282
+ if (!free) {
283
+ console.warn(`archrad: warning: host port ${hostPort} appears in use (use --skip-host-port-check to ignore)`);
284
+ }
285
+ }
286
+ const skipStruct = Boolean(cmdOpts.dangerSkipIrStructuralValidation || cmdOpts.skipIrStructuralValidation);
287
+ try {
288
+ const result = await runValidateDrift(actualIR, cmdOpts.target, outDir, {
289
+ hostPort,
290
+ skipIrStructuralValidation: skipStruct,
291
+ skipIrLint: cmdOpts.skipIrLint,
292
+ strictExtra: cmdOpts.strictExtra,
293
+ });
294
+ const combined = sortFindings([
295
+ ...result.exportResult.irStructuralFindings,
296
+ ...result.exportResult.irLintFindings,
297
+ ]);
298
+ if (combined.length && !cmdOpts.json) {
299
+ printFindingsPretty(combined, 'archrad validate-drift (reference export):');
300
+ }
301
+ if (cmdOpts.json) {
302
+ console.log(JSON.stringify({
303
+ ok: result.ok,
304
+ driftFindings: result.driftFindings,
305
+ extraBlocking: result.extraBlocking,
306
+ irStructuralFindings: result.exportResult.irStructuralFindings,
307
+ irLintFindings: result.exportResult.irLintFindings,
308
+ openApiStructuralWarnings: result.exportResult.openApiStructuralWarnings,
309
+ referenceFileCount: Object.keys(result.exportResult.files).length,
310
+ }, null, 2));
311
+ }
312
+ else {
313
+ if (result.driftFindings.length) {
314
+ console.error('archrad validate-drift:');
315
+ for (const f of result.driftFindings) {
316
+ const icon = f.code === 'DRIFT-EXTRA' ? 'ℹ️' : '❌';
317
+ console.error(`${icon} ${f.code}: ${f.path}`);
318
+ console.error(` ${f.message}`);
319
+ console.error('');
320
+ }
321
+ }
322
+ if (result.ok) {
323
+ console.log('archrad: no deterministic drift (on-disk export matches fresh export from IR).');
324
+ }
325
+ else {
326
+ console.error('archrad: drift detected — regenerate with `archrad export` or align the IR.');
327
+ }
328
+ }
329
+ if (!result.ok) {
330
+ process.exitCode = 1;
331
+ }
332
+ }
333
+ catch (e) {
334
+ console.error('archrad:', e?.message || String(e));
335
+ process.exitCode = 1;
336
+ }
337
+ });
338
+ program.parseAsync(process.argv).catch((e) => {
339
+ console.error(e);
340
+ process.exit(1);
341
+ });
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Edge Configuration Code Generator Utilities
3
+ * Provides utilities for code generators to use edge/connectivity configurations
4
+ */
5
+ export interface EdgeConfig {
6
+ edgeType?: string;
7
+ protocol?: string;
8
+ transport?: 'sync' | 'async' | 'streaming';
9
+ config?: {
10
+ timeout?: number;
11
+ retry?: {
12
+ maxAttempts?: number;
13
+ strategy?: 'exponential' | 'linear' | 'fixed';
14
+ backoffMs?: number;
15
+ };
16
+ circuitBreaker?: {
17
+ enabled?: boolean;
18
+ failureThreshold?: number;
19
+ resetTimeoutMs?: number;
20
+ };
21
+ authentication?: {
22
+ type?: string;
23
+ credentialRef?: {
24
+ vault?: string;
25
+ key?: string;
26
+ };
27
+ };
28
+ loadBalancing?: {
29
+ strategy?: string;
30
+ endpoints?: string[];
31
+ };
32
+ [key: string]: any;
33
+ };
34
+ }
35
+ /**
36
+ * Generate retry logic code from edge configuration
37
+ */
38
+ export declare function generateRetryCode(edgeConfig: EdgeConfig, language: 'python' | 'nodejs' | 'java' | 'csharp' | 'go'): string;
39
+ /**
40
+ * Generate circuit breaker code from edge configuration
41
+ */
42
+ export declare function generateCircuitBreakerCode(edgeConfig: EdgeConfig, language: 'python' | 'nodejs' | 'java' | 'csharp' | 'go'): string;
43
+ /**
44
+ * Generate HTTP client code with edge configuration
45
+ */
46
+ export declare function generateHttpClientCode(edgeConfig: EdgeConfig, url: string, method: string, language: 'python' | 'nodejs' | 'java' | 'csharp' | 'go'): string;
47
+ /**
48
+ * Get edge configuration for a specific edge
49
+ */
50
+ export declare function getEdgeConfig(edges: any[], fromNodeId: string, toNodeId: string): EdgeConfig | null;
51
+ /**
52
+ * Apply edge configuration to code generation context
53
+ */
54
+ export declare function applyEdgeConfigToContext(edges: any[], fromNodeId: string, toNodeId: string, context: Record<string, any>): void;
55
+ //# sourceMappingURL=edgeConfigCodeGenerator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edgeConfigCodeGenerator.d.ts","sourceRoot":"","sources":["../src/edgeConfigCodeGenerator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,WAAW,CAAC;IAC3C,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE;YACN,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,QAAQ,CAAC,EAAE,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;YAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;SACpB,CAAC;QACF,cAAc,CAAC,EAAE;YACf,OAAO,CAAC,EAAE,OAAO,CAAC;YAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;YAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;SACzB,CAAC;QACF,cAAc,CAAC,EAAE;YACf,IAAI,CAAC,EAAE,MAAM,CAAC;YACd,aAAa,CAAC,EAAE;gBACd,KAAK,CAAC,EAAE,MAAM,CAAC;gBACf,GAAG,CAAC,EAAE,MAAM,CAAC;aACd,CAAC;SACH,CAAC;QACF,aAAa,CAAC,EAAE;YACd,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;SACtB,CAAC;QACF,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,IAAI,GAAG,MAAM,CA+E1H;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,IAAI,GAAG,MAAM,CAmFnI;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,IAAI,GACvD,MAAM,CAgDR;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAanG;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,GAAG,EAAE,EACZ,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC3B,IAAI,CAaN"}
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Edge Configuration Code Generator Utilities
3
+ * Provides utilities for code generators to use edge/connectivity configurations
4
+ */
5
+ /**
6
+ * Generate retry logic code from edge configuration
7
+ */
8
+ export function generateRetryCode(edgeConfig, language) {
9
+ const retry = edgeConfig.config?.retry;
10
+ if (!retry || !retry.maxAttempts || retry.maxAttempts <= 0) {
11
+ return '';
12
+ }
13
+ const maxAttempts = retry.maxAttempts;
14
+ const strategy = retry.strategy || 'exponential';
15
+ const backoffMs = retry.backoffMs || 1000;
16
+ switch (language) {
17
+ case 'python':
18
+ if (strategy === 'exponential') {
19
+ return `
20
+ import time
21
+ import random
22
+
23
+ def retry_with_exponential_backoff(func, max_attempts=${maxAttempts}, base_delay=${backoffMs / 1000}):
24
+ """Retry function with exponential backoff"""
25
+ for attempt in range(max_attempts):
26
+ try:
27
+ return func()
28
+ except Exception as e:
29
+ if attempt == max_attempts - 1:
30
+ raise
31
+ delay = base_delay * (2 ** attempt) + random.uniform(0, 0.1)
32
+ time.sleep(delay)
33
+ raise Exception("Max retry attempts exceeded")
34
+ `;
35
+ }
36
+ else {
37
+ return `
38
+ import time
39
+
40
+ def retry_with_linear_backoff(func, max_attempts=${maxAttempts}, delay=${backoffMs / 1000}):
41
+ """Retry function with linear backoff"""
42
+ for attempt in range(max_attempts):
43
+ try:
44
+ return func()
45
+ except Exception as e:
46
+ if attempt == max_attempts - 1:
47
+ raise
48
+ time.sleep(delay)
49
+ raise Exception("Max retry attempts exceeded")
50
+ `;
51
+ }
52
+ case 'nodejs':
53
+ if (strategy === 'exponential') {
54
+ return `
55
+ const retryWithExponentialBackoff = async (fn, maxAttempts = ${maxAttempts}, baseDelay = ${backoffMs}) => {
56
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
57
+ try {
58
+ return await fn();
59
+ } catch (error) {
60
+ if (attempt === maxAttempts - 1) throw error;
61
+ const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 100;
62
+ await new Promise(resolve => setTimeout(resolve, delay));
63
+ }
64
+ }
65
+ };
66
+ `;
67
+ }
68
+ else {
69
+ return `
70
+ const retryWithLinearBackoff = async (fn, maxAttempts = ${maxAttempts}, delay = ${backoffMs}) => {
71
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
72
+ try {
73
+ return await fn();
74
+ } catch (error) {
75
+ if (attempt === maxAttempts - 1) throw error;
76
+ await new Promise(resolve => setTimeout(resolve, delay));
77
+ }
78
+ }
79
+ };
80
+ `;
81
+ }
82
+ default:
83
+ return `// Retry logic: ${maxAttempts} attempts, ${strategy} backoff, ${backoffMs}ms base delay`;
84
+ }
85
+ }
86
+ /**
87
+ * Generate circuit breaker code from edge configuration
88
+ */
89
+ export function generateCircuitBreakerCode(edgeConfig, language) {
90
+ const circuitBreaker = edgeConfig.config?.circuitBreaker;
91
+ if (!circuitBreaker?.enabled) {
92
+ return '';
93
+ }
94
+ const failureThreshold = circuitBreaker.failureThreshold || 5;
95
+ const resetTimeoutMs = circuitBreaker.resetTimeoutMs || 60000;
96
+ switch (language) {
97
+ case 'python':
98
+ return `
99
+ from datetime import datetime, timedelta
100
+
101
+ class CircuitBreaker:
102
+ def __init__(self, failure_threshold=${failureThreshold}, reset_timeout=${resetTimeoutMs / 1000}):
103
+ self.failure_threshold = failure_threshold
104
+ self.reset_timeout = reset_timeout
105
+ self.failure_count = 0
106
+ self.last_failure_time = None
107
+ self.state = 'CLOSED' # CLOSED, OPEN, HALF_OPEN
108
+
109
+ def call(self, func):
110
+ if self.state == 'OPEN':
111
+ if datetime.now() - self.last_failure_time > timedelta(seconds=self.reset_timeout):
112
+ self.state = 'HALF_OPEN'
113
+ else:
114
+ raise Exception("Circuit breaker is OPEN")
115
+
116
+ try:
117
+ result = func()
118
+ if self.state == 'HALF_OPEN':
119
+ self.state = 'CLOSED'
120
+ self.failure_count = 0
121
+ return result
122
+ except Exception as e:
123
+ self.failure_count += 1
124
+ self.last_failure_time = datetime.now()
125
+ if self.failure_count >= self.failure_threshold:
126
+ self.state = 'OPEN'
127
+ raise
128
+ `;
129
+ case 'nodejs':
130
+ return `
131
+ class CircuitBreaker {
132
+ constructor(failureThreshold = ${failureThreshold}, resetTimeout = ${resetTimeoutMs}) {
133
+ this.failureThreshold = failureThreshold;
134
+ this.resetTimeout = resetTimeout;
135
+ this.failureCount = 0;
136
+ this.lastFailureTime = null;
137
+ this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
138
+ }
139
+
140
+ async call(fn) {
141
+ if (this.state === 'OPEN') {
142
+ if (Date.now() - this.lastFailureTime > this.resetTimeout) {
143
+ this.state = 'HALF_OPEN';
144
+ } else {
145
+ throw new Error('Circuit breaker is OPEN');
146
+ }
147
+ }
148
+
149
+ try {
150
+ const result = await fn();
151
+ if (this.state === 'HALF_OPEN') {
152
+ this.state = 'CLOSED';
153
+ this.failureCount = 0;
154
+ }
155
+ return result;
156
+ } catch (error) {
157
+ this.failureCount++;
158
+ this.lastFailureTime = Date.now();
159
+ if (this.failureCount >= this.failureThreshold) {
160
+ this.state = 'OPEN';
161
+ }
162
+ throw error;
163
+ }
164
+ }
165
+ }
166
+ `;
167
+ default:
168
+ return `// Circuit breaker: ${failureThreshold} failures, ${resetTimeoutMs}ms reset timeout`;
169
+ }
170
+ }
171
+ /**
172
+ * Generate HTTP client code with edge configuration
173
+ */
174
+ export function generateHttpClientCode(edgeConfig, url, method, language) {
175
+ const protocol = edgeConfig.protocol || 'https';
176
+ const timeout = edgeConfig.config?.timeout || 30000;
177
+ const hasRetry = edgeConfig.config?.retry?.maxAttempts && edgeConfig.config.retry.maxAttempts > 0;
178
+ const hasCircuitBreaker = edgeConfig.config?.circuitBreaker?.enabled;
179
+ switch (language) {
180
+ case 'python':
181
+ const retryCode = hasRetry ? generateRetryCode(edgeConfig, 'python') : '';
182
+ const cbCode = hasCircuitBreaker ? generateCircuitBreakerCode(edgeConfig, 'python') : '';
183
+ return `
184
+ import requests
185
+ ${retryCode}
186
+ ${cbCode}
187
+
188
+ def make_request():
189
+ response = requests.${method.toLowerCase()}(
190
+ '${protocol}://${url}',
191
+ timeout=${timeout / 1000}
192
+ )
193
+ response.raise_for_status()
194
+ return response.json()
195
+
196
+ ${hasRetry ? 'result = retry_with_exponential_backoff(make_request)' : 'result = make_request()'}
197
+ `;
198
+ case 'nodejs':
199
+ const retryCodeJs = hasRetry ? generateRetryCode(edgeConfig, 'nodejs') : '';
200
+ const cbCodeJs = hasCircuitBreaker ? generateCircuitBreakerCode(edgeConfig, 'nodejs') : '';
201
+ return `
202
+ const axios = require('axios');
203
+ ${retryCodeJs}
204
+ ${cbCodeJs}
205
+
206
+ const makeRequest = async () => {
207
+ const response = await axios.${method.toLowerCase()}({
208
+ url: '${protocol}://${url}',
209
+ timeout: ${timeout}
210
+ });
211
+ return response.data;
212
+ };
213
+
214
+ ${hasRetry ? 'const result = await retryWithExponentialBackoff(makeRequest);' : 'const result = await makeRequest();'}
215
+ `;
216
+ default:
217
+ return `// HTTP ${method} request to ${protocol}://${url} with ${timeout}ms timeout`;
218
+ }
219
+ }
220
+ /**
221
+ * Get edge configuration for a specific edge
222
+ */
223
+ export function getEdgeConfig(edges, fromNodeId, toNodeId) {
224
+ const edge = edges.find((e) => (e.from === fromNodeId || e.source === fromNodeId) && (e.to === toNodeId || e.target === toNodeId));
225
+ if (!edge)
226
+ return null;
227
+ return {
228
+ edgeType: edge.edgeType || edge.type,
229
+ protocol: edge.protocol,
230
+ transport: edge.transport,
231
+ config: edge.config,
232
+ };
233
+ }
234
+ /**
235
+ * Apply edge configuration to code generation context
236
+ */
237
+ export function applyEdgeConfigToContext(edges, fromNodeId, toNodeId, context) {
238
+ const edgeConfig = getEdgeConfig(edges, fromNodeId, toNodeId);
239
+ if (edgeConfig) {
240
+ context.edgeType = edgeConfig.edgeType;
241
+ context.protocol = edgeConfig.protocol;
242
+ context.transport = edgeConfig.transport;
243
+ context.timeout = edgeConfig.config?.timeout;
244
+ context.hasRetry = !!(edgeConfig.config?.retry?.maxAttempts && edgeConfig.config.retry.maxAttempts > 0);
245
+ context.hasCircuitBreaker = edgeConfig.config?.circuitBreaker?.enabled;
246
+ context.retryConfig = edgeConfig.config?.retry;
247
+ context.circuitBreakerConfig = edgeConfig.config?.circuitBreaker;
248
+ }
249
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Full deterministic export for Python FastAPI and Node Express (no LLM).
3
+ * Used by the ArchRad server and the `archrad` CLI.
4
+ */
5
+ import { type IrStructuralFinding } from './ir-structural.js';
6
+ export type DeterministicExportResult = {
7
+ files: Record<string, string>;
8
+ /** Human-readable lines when generated OpenAPI fails **document-shape** checks (not full spec lint) */
9
+ openApiStructuralWarnings: string[];
10
+ /**
11
+ * IR-STRUCT-* from `validateIrStructural`, or — when **`skipIrStructuralValidation`** is set — the same
12
+ * codes surfaced by **`validateIrLint`** if the IR cannot be parsed (invalid root, empty graph, etc.).
13
+ * Errors block codegen; this field stays the single source for “graph does not compile.”
14
+ */
15
+ irStructuralFindings: IrStructuralFinding[];
16
+ /** IR-LINT-* heuristics only; does not include IR-STRUCT-* (those live in `irStructuralFindings`). */
17
+ irLintFindings: IrStructuralFinding[];
18
+ };
19
+ /**
20
+ * Generate FastAPI or Express project files + golden Docker/Makefile + OpenAPI **document-shape** check.
21
+ */
22
+ export declare function runDeterministicExport(actualIR: any, target: string, opts?: Record<string, any>): Promise<DeterministicExportResult>;
23
+ //# sourceMappingURL=exportPipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exportPipeline.d.ts","sourceRoot":"","sources":["../src/exportPipeline.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAC;AAG5B,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,uGAAuG;IACvG,yBAAyB,EAAE,MAAM,EAAE,CAAC;IACpC;;;;OAIG;IACH,oBAAoB,EAAE,mBAAmB,EAAE,CAAC;IAC5C,sGAAsG;IACtG,cAAc,EAAE,mBAAmB,EAAE,CAAC;CACvC,CAAC;AAEF;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,GAAG,EACb,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAC7B,OAAO,CAAC,yBAAyB,CAAC,CAwDpC"}