@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
@@ -0,0 +1,65 @@
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 generatePythonFastAPIFiles from './pythonFastAPI.js';
6
+ import generateNodeExpressFiles from './nodeExpress.js';
7
+ import { applyFastApiGoldenLayer, applyNodeExpressGoldenLayer } from './golden-bundle.js';
8
+ import { validateOpenApiInBundleStructural } from './openapi-structural.js';
9
+ import { normalizeGoldenHostPort } from './hostPort.js';
10
+ import { validateIrStructural, hasIrStructuralErrors, } from './ir-structural.js';
11
+ import { validateIrLint } from './ir-lint.js';
12
+ /**
13
+ * Generate FastAPI or Express project files + golden Docker/Makefile + OpenAPI **document-shape** check.
14
+ */
15
+ export async function runDeterministicExport(actualIR, target, opts = {}) {
16
+ const skipIr = Boolean(opts.skipIrStructuralValidation);
17
+ const skipLint = Boolean(opts.skipIrLint);
18
+ let irStructuralFindings = skipIr ? [] : validateIrStructural(actualIR);
19
+ if (!skipIr && hasIrStructuralErrors(irStructuralFindings)) {
20
+ return { files: {}, openApiStructuralWarnings: [], irStructuralFindings, irLintFindings: [] };
21
+ }
22
+ let irLintFindings = [];
23
+ if (!skipLint) {
24
+ const lintPass = validateIrLint(actualIR);
25
+ if (skipIr) {
26
+ // Dangerous mode: full structural pass is off, but parse/normalize failures still return IR-STRUCT-* from
27
+ // validateIrLint — fold those into irStructuralFindings so InkByte / CLI consumers block and log like normal.
28
+ const structFromLint = lintPass.filter((f) => f.code.startsWith('IR-STRUCT-'));
29
+ irLintFindings = lintPass.filter((f) => !f.code.startsWith('IR-STRUCT-'));
30
+ irStructuralFindings = structFromLint;
31
+ if (hasIrStructuralErrors(irStructuralFindings)) {
32
+ return { files: {}, openApiStructuralWarnings: [], irStructuralFindings, irLintFindings };
33
+ }
34
+ }
35
+ else {
36
+ irLintFindings = lintPass;
37
+ }
38
+ }
39
+ const t = String(target || '').toLowerCase();
40
+ let files = {};
41
+ const hostPort = normalizeGoldenHostPort(opts.hostPort ?? opts.goldenHostPort ?? process.env.ARCHRAD_HOST_PORT);
42
+ const goldenOpts = { hostPort };
43
+ if (t === 'python') {
44
+ files = await generatePythonFastAPIFiles(actualIR, opts).catch(() => ({}));
45
+ applyFastApiGoldenLayer(files, goldenOpts);
46
+ }
47
+ else if (t === 'node' || t === 'nodejs') {
48
+ files = await generateNodeExpressFiles(actualIR, opts).catch(() => ({}));
49
+ applyNodeExpressGoldenLayer(files, goldenOpts);
50
+ }
51
+ else {
52
+ throw new Error(`runDeterministicExport: unsupported target "${target}". Use "python", "node", or "nodejs".`);
53
+ }
54
+ const vr = validateOpenApiInBundleStructural(files);
55
+ const openApiStructuralWarnings = [];
56
+ if (!vr.ok && vr.path) {
57
+ for (const e of vr.errors) {
58
+ openApiStructuralWarnings.push(`${vr.path}: ${e}`);
59
+ }
60
+ }
61
+ else if (!vr.ok) {
62
+ openApiStructuralWarnings.push(...vr.errors);
63
+ }
64
+ return { files, openApiStructuralWarnings, irStructuralFindings, irLintFindings };
65
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Golden-path assets: IR-aligned OpenAPI is supplied by the caller (deterministic exporter).
3
+ * This module only applies Docker Compose, Dockerfile, Makefile, README section, and port patches.
4
+ *
5
+ * `hostPort` is the machine port (left side of docker publish). Container listens on 8080.
6
+ */
7
+ export type GoldenLayerOptions = {
8
+ /** Host port for `docker compose` publish (default 8080). Container port stays 8080. */
9
+ hostPort?: number;
10
+ };
11
+ export type GoldenStack = 'fastapi' | 'express';
12
+ /** Normalize generated main.py uvicorn port to 8080 if present. */
13
+ export declare function patchMainPyPort8080(content: string): string;
14
+ /** Merge golden Docker/Makefile/README into an existing FastAPI file map. Caller must already set openapi.yaml from the deterministic exporter. */
15
+ export declare function applyFastApiGoldenLayer(filesMap: Record<string, string>, options?: GoldenLayerOptions): void;
16
+ /** Normalize Express entry to listen on 8080 by default. */
17
+ export declare function patchExpressIndexPort8080(content: string): string;
18
+ export declare function mergePackageJsonScripts(pkg: string): string;
19
+ /** Golden layer for Express: expects openapi.yaml (and app files) from caller when available. */
20
+ export declare function applyNodeExpressGoldenLayer(filesMap: Record<string, string>, options?: GoldenLayerOptions): void;
21
+ //# sourceMappingURL=golden-bundle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"golden-bundle.d.ts","sourceRoot":"","sources":["../src/golden-bundle.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AA8EF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;AAiDhD,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAI3D;AAED,mJAAmJ;AACnJ,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,OAAO,GAAE,kBAAuB,GAC/B,IAAI,CAcN;AAED,4DAA4D;AAC5D,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIjE;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAU3D;AAED,iGAAiG;AACjG,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,OAAO,GAAE,kBAAuB,GAC/B,IAAI,CAkBN"}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Golden-path assets: IR-aligned OpenAPI is supplied by the caller (deterministic exporter).
3
+ * This module only applies Docker Compose, Dockerfile, Makefile, README section, and port patches.
4
+ *
5
+ * `hostPort` is the machine port (left side of docker publish). Container listens on 8080.
6
+ */
7
+ function fastApiCompose(hostPort) {
8
+ return `version: '3.8'
9
+
10
+ services:
11
+ api:
12
+ build: .
13
+ ports:
14
+ - "${hostPort}:8080"
15
+ volumes:
16
+ - .:/app
17
+ command: uvicorn app.main:app --host 0.0.0.0 --port 8080 --reload
18
+ environment:
19
+ - PYTHONUNBUFFERED=1
20
+ `;
21
+ }
22
+ const GOLDEN_DOCKERFILE = `FROM python:3.11-slim
23
+ WORKDIR /app
24
+ COPY requirements.txt .
25
+ RUN pip install --no-cache-dir -r requirements.txt
26
+ COPY . .
27
+ EXPOSE 8080
28
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]
29
+ `;
30
+ function nodeCompose(hostPort) {
31
+ return `version: '3.8'
32
+
33
+ services:
34
+ api:
35
+ build: .
36
+ ports:
37
+ - "${hostPort}:8080"
38
+ volumes:
39
+ - .:/app
40
+ working_dir: /app
41
+ command: sh -c "npm install && node --watch app/index.js"
42
+ environment:
43
+ - PORT=8080
44
+ - NODE_ENV=development
45
+ `;
46
+ }
47
+ const GOLDEN_NODE_DOCKERFILE = `FROM node:20-bookworm-slim
48
+ WORKDIR /app
49
+ COPY package.json ./
50
+ RUN npm install
51
+ COPY . .
52
+ EXPOSE 8080
53
+ ENV PORT=8080
54
+ CMD ["node", "app/index.js"]
55
+ `;
56
+ const GOLDEN_MAKEFILE = `.PHONY: run dev install
57
+ install:
58
+ pip install -r requirements.txt
59
+
60
+ # One command to hide Docker details (golden demo)
61
+ run:
62
+ docker compose up --build
63
+
64
+ dev: run
65
+ `;
66
+ const GOLDEN_NODE_MAKEFILE = `.PHONY: run install
67
+ install:
68
+ npm install
69
+
70
+ run:
71
+ docker compose up --build
72
+
73
+ dev: run
74
+ `;
75
+ const README_GOLDEN_MARKER = '<!-- ARCHRAD_GOLDEN_PATH -->';
76
+ function appendGoldenReadmeSection(readme, stack = 'fastapi', hostPort = 8080) {
77
+ const hp = hostPort;
78
+ const validationBlurb = stack === 'express'
79
+ ? `Try validation on a signup-style POST (empty body should fail schema checks):
80
+
81
+ \`\`\`bash
82
+ curl -sS -X POST http://localhost:${hp}/signup -H "Content-Type: application/json" -d '{}'
83
+ \`\`\`
84
+
85
+ Expect **400 Bad Request** (or **422** if mapped) with **details** when routes use Ajv/OpenAPI-style validation — field errors should list missing/invalid keys.`
86
+ : `Try validation on a signup-style POST (empty body should fail FastAPI/Pydantic checks):
87
+
88
+ \`\`\`bash
89
+ curl -sS -X POST http://localhost:${hp}/signup -H "Content-Type: application/json" -d '{}'
90
+ \`\`\`
91
+
92
+ Expect **422 Unprocessable Entity** (default FastAPI) or **400** if routes use custom validation — field errors should list missing/invalid keys.`;
93
+ const section = `
94
+
95
+ ${README_GOLDEN_MARKER}
96
+ ## Golden path (local API, ~60s)
97
+
98
+ \`\`\`bash
99
+ make run
100
+ # or: docker compose up --build
101
+ \`\`\`
102
+
103
+ Service listens on **http://localhost:${hp}** (maps host **${hp}** → container **8080**; hot reload: **FastAPI** = uvicorn --reload; **Express** = \`node --watch\` in compose).
104
+
105
+ ${validationBlurb}
106
+
107
+ OpenAPI contract for this blueprint (IR-aligned) is in \`openapi.yaml\` at the project root.
108
+
109
+ ### Zero lock-in
110
+
111
+ Generated services are **standard FastAPI or Express** stacks. They run with **Docker Compose** and **no ArchRad runtime**. If your org enabled optional policy SDK injection during export, remove \`sdk/archrad_*\` and related middleware—or set \`ARCHRAD_POLICY_SERVICE_URL\` empty—see upstream \`EXPORT_LOCK_IN_AUDIT.md\` in the main product repo.
112
+ `;
113
+ if (readme.includes(README_GOLDEN_MARKER))
114
+ return readme;
115
+ return (readme || '').trimEnd() + section;
116
+ }
117
+ /** Normalize generated main.py uvicorn port to 8080 if present. */
118
+ export function patchMainPyPort8080(content) {
119
+ return content
120
+ .replace(/uvicorn\.run\(\s*app\s*,\s*host=['"]0\.0\.0\.0['"]\s*,\s*port\s*=\s*8000\s*\)/g, "uvicorn.run(app, host='0.0.0.0', port=8080)")
121
+ .replace(/port\s*=\s*8000/g, 'port=8080');
122
+ }
123
+ /** Merge golden Docker/Makefile/README into an existing FastAPI file map. Caller must already set openapi.yaml from the deterministic exporter. */
124
+ export function applyFastApiGoldenLayer(filesMap, options = {}) {
125
+ const hostPort = options.hostPort ?? 8080;
126
+ filesMap['docker-compose.yml'] = fastApiCompose(hostPort);
127
+ filesMap['Dockerfile'] = GOLDEN_DOCKERFILE;
128
+ filesMap['Makefile'] = GOLDEN_MAKEFILE;
129
+ if (filesMap['app/main.py']) {
130
+ filesMap['app/main.py'] = patchMainPyPort8080(filesMap['app/main.py']);
131
+ }
132
+ filesMap['README.md'] = appendGoldenReadmeSection(filesMap['README.md'] || '# Generated API\n', 'fastapi', hostPort);
133
+ }
134
+ /** Normalize Express entry to listen on 8080 by default. */
135
+ export function patchExpressIndexPort8080(content) {
136
+ return content
137
+ .replace(/process\.env\.PORT\s*\|\|\s*3000/g, 'Number(process.env.PORT) || 8080')
138
+ .replace(/Number\(process\.env\.PORT\)\s*\|\|\s*3000/g, 'Number(process.env.PORT) || 8080');
139
+ }
140
+ export function mergePackageJsonScripts(pkg) {
141
+ try {
142
+ const j = JSON.parse(pkg);
143
+ j.scripts = j.scripts || {};
144
+ if (!j.scripts.start)
145
+ j.scripts.start = 'node app/index.js';
146
+ j.scripts.dev = 'node --watch app/index.js';
147
+ return JSON.stringify(j, null, 2);
148
+ }
149
+ catch {
150
+ return pkg;
151
+ }
152
+ }
153
+ /** Golden layer for Express: expects openapi.yaml (and app files) from caller when available. */
154
+ export function applyNodeExpressGoldenLayer(filesMap, options = {}) {
155
+ const hostPort = options.hostPort ?? 8080;
156
+ filesMap['docker-compose.yml'] = nodeCompose(hostPort);
157
+ filesMap['Dockerfile'] = GOLDEN_NODE_DOCKERFILE;
158
+ filesMap['Makefile'] = GOLDEN_NODE_MAKEFILE;
159
+ if (filesMap['app/index.js']) {
160
+ filesMap['app/index.js'] = patchExpressIndexPort8080(filesMap['app/index.js']);
161
+ }
162
+ if (filesMap['package.json']) {
163
+ filesMap['package.json'] = mergePackageJsonScripts(filesMap['package.json']);
164
+ }
165
+ filesMap['README.md'] = appendGoldenReadmeSection(filesMap['README.md'] || '# Generated Express API\n', 'express', hostPort);
166
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared graph predicates for structural validation and architecture lint (no imports from lint-graph / ir-structural).
3
+ */
4
+ /** Node types treated as HTTP-like for path/method checks and lint (aligned structural + IR-LINT). */
5
+ export declare function isHttpLikeType(t: string): boolean;
6
+ /** Datastore-like (unchanged semantics; kept adjacent for docs). */
7
+ export declare function isDbLikeType(t: string): boolean;
8
+ /** Queue / topic / stream node types → treat incoming edges as async boundaries when edge metadata is absent. */
9
+ export declare function isQueueLikeNodeType(t: string): boolean;
10
+ //# sourceMappingURL=graphPredicates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphPredicates.d.ts","sourceRoot":"","sources":["../src/graphPredicates.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,sGAAsG;AACtG,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAQjD;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAM/C;AAED,iHAAiH;AACjH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAStD"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Shared graph predicates for structural validation and architecture lint (no imports from lint-graph / ir-structural).
3
+ */
4
+ /** Node types treated as HTTP-like for path/method checks and lint (aligned structural + IR-LINT). */
5
+ export function isHttpLikeType(t) {
6
+ const s = String(t ?? '')
7
+ .trim()
8
+ .toLowerCase();
9
+ if (!s)
10
+ return false;
11
+ if (s === 'http' || s === 'https' || s === 'rest' || s === 'api')
12
+ return true;
13
+ if (s === 'gateway' || s === 'bff' || s === 'graphql' || s === 'grpc')
14
+ return true;
15
+ return /\b(api|gateway|bff|graphql|grpc)\b/.test(s);
16
+ }
17
+ /** Datastore-like (unchanged semantics; kept adjacent for docs). */
18
+ export function isDbLikeType(t) {
19
+ if (!t)
20
+ return false;
21
+ return (/\b(db|database|datastore)\b/.test(t) ||
22
+ /postgres|mongodb|mysql|sqlite|redis|cassandra|dynamo|sql|nosql|warehouse|s3/.test(t));
23
+ }
24
+ /** Queue / topic / stream node types → treat incoming edges as async boundaries when edge metadata is absent. */
25
+ export function isQueueLikeNodeType(t) {
26
+ const s = String(t ?? '')
27
+ .trim()
28
+ .toLowerCase();
29
+ if (!s)
30
+ return false;
31
+ return (/\b(queue|topic|stream|pubsub|event|bus)\b/.test(s) ||
32
+ /kafka|sns|sqs|amqp|mqtt|nats|rabbitmq|pulsar/.test(s));
33
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Host-side port for docker-compose publish (container keeps 8080).
3
+ */
4
+ export declare const DEFAULT_GOLDEN_HOST_PORT = 8080;
5
+ /** Valid TCP port for the host mapping; falls back to DEFAULT_GOLDEN_HOST_PORT. */
6
+ export declare function normalizeGoldenHostPort(value: unknown): number;
7
+ /**
8
+ * True if nothing is listening on 127.0.0.1:port (we can bind briefly).
9
+ * On permission errors, returns true so export is not blocked.
10
+ */
11
+ export declare function isLocalHostPortFree(port: number): Promise<boolean>;
12
+ //# sourceMappingURL=hostPort.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hostPort.d.ts","sourceRoot":"","sources":["../src/hostPort.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,mFAAmF;AACnF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAM9D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAoBlE"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Host-side port for docker-compose publish (container keeps 8080).
3
+ */
4
+ import { createServer } from 'node:net';
5
+ export const DEFAULT_GOLDEN_HOST_PORT = 8080;
6
+ /** Valid TCP port for the host mapping; falls back to DEFAULT_GOLDEN_HOST_PORT. */
7
+ export function normalizeGoldenHostPort(value) {
8
+ const n = typeof value === 'string' ? parseInt(value, 10) : Number(value);
9
+ if (!Number.isInteger(n) || n < 1 || n > 65535) {
10
+ return DEFAULT_GOLDEN_HOST_PORT;
11
+ }
12
+ return n;
13
+ }
14
+ /**
15
+ * True if nothing is listening on 127.0.0.1:port (we can bind briefly).
16
+ * On permission errors, returns true so export is not blocked.
17
+ */
18
+ export function isLocalHostPortFree(port) {
19
+ return new Promise((resolve) => {
20
+ const server = createServer();
21
+ server.unref();
22
+ server.once('error', (err) => {
23
+ if (err.code === 'EADDRINUSE') {
24
+ resolve(false);
25
+ return;
26
+ }
27
+ resolve(true);
28
+ });
29
+ server.once('listening', () => {
30
+ server.close(() => resolve(true));
31
+ });
32
+ try {
33
+ server.listen(port, '127.0.0.1');
34
+ }
35
+ catch {
36
+ resolve(true);
37
+ }
38
+ });
39
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @archrad/deterministic — IR structural validation, FastAPI/Express generators, OpenAPI **document-shape**
3
+ * checks, golden Docker/Makefile (no LLM). Semantic architecture / compliance lives in ArchRad Cloud.
4
+ */
5
+ export { findOpenApiInBundle, parseOpenApiString, validateOpenApiStructural, serializeOpenApiDoc, validateOpenApiInBundleStructural, } from './openapi-structural.js';
6
+ export { applyFastApiGoldenLayer, applyNodeExpressGoldenLayer, patchMainPyPort8080, patchExpressIndexPort8080, mergePackageJsonScripts, type GoldenStack, type GoldenLayerOptions, } from './golden-bundle.js';
7
+ export { DEFAULT_GOLDEN_HOST_PORT, normalizeGoldenHostPort, isLocalHostPortFree, } from './hostPort.js';
8
+ export * from './edgeConfigCodeGenerator.js';
9
+ export { default as generatePythonFastAPIFiles } from './pythonFastAPI.js';
10
+ export { default as generateNodeExpressFiles } from './nodeExpress.js';
11
+ export { runDeterministicExport, type DeterministicExportResult } from './exportPipeline.js';
12
+ export { diffExpectedExportAgainstFiles, diffExpectedExportAgainstDirectory, readDirectoryAsExportMap, runValidateDrift, runDriftCheckAgainstFiles, normalizeExportFileContent, type DriftFinding, type DriftCode, type ValidateDriftResult, type DriftCheckFilesResult, } from './validate-drift.js';
13
+ export { normalizeIrGraph, validateIrStructural, hasIrStructuralErrors, type IrStructuralFinding, type IrStructuralSeverity, type IrFindingLayer, } from './ir-structural.js';
14
+ export { materializeNormalizedGraph, normalizeNodeSlot, normalizeEdgeSlot, type NormalizedGraph, type NormalizedNode, type NormalizedEdge, type MaterializeResult, } from './ir-normalize.js';
15
+ export { validateIrLint } from './ir-lint.js';
16
+ export { runArchitectureLinting, LINT_RULE_REGISTRY } from './lint-rules.js';
17
+ export { buildParsedLintGraph, isParsedLintGraph, type ParsedLintGraph, type BuildParsedLintGraphResult, } from './lint-graph.js';
18
+ export { isHttpLikeType, isDbLikeType, isQueueLikeNodeType } from './graphPredicates.js';
19
+ export { sortFindings, shouldFailFromFindings, type ValidationExitPolicy } from './cli-findings.js';
20
+ export { parseYamlToCanonicalIr, canonicalIrToJsonString, YamlGraphParseError, } from './yamlToIr.js';
21
+ export { openApiDocumentToHttpNodes, openApiDocumentToCanonicalIr, openApiStringToCanonicalIr, openApiUnknownToCanonicalIr, OpenApiIngestError, type OpenApiHttpNode, } from './openapi-to-ir.js';
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,yBAAyB,EACzB,mBAAmB,EACnB,iCAAiC,GAClC,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,uBAAuB,EACvB,2BAA2B,EAC3B,mBAAmB,EACnB,yBAAyB,EACzB,uBAAuB,EACvB,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAEvB,cAAc,8BAA8B,CAAC;AAE7C,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAE,sBAAsB,EAAE,KAAK,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAE7F,OAAO,EACL,8BAA8B,EAC9B,kCAAkC,EAClC,wBAAwB,EACxB,gBAAgB,EAChB,yBAAyB,EACzB,0BAA0B,EAC1B,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,cAAc,GACpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,0BAA0B,EAC1B,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,iBAAiB,GACvB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC7E,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,0BAA0B,GAChC,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEzF,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEpG,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,0BAA0B,EAC1B,4BAA4B,EAC5B,0BAA0B,EAC1B,2BAA2B,EAC3B,kBAAkB,EAClB,KAAK,eAAe,GACrB,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @archrad/deterministic — IR structural validation, FastAPI/Express generators, OpenAPI **document-shape**
3
+ * checks, golden Docker/Makefile (no LLM). Semantic architecture / compliance lives in ArchRad Cloud.
4
+ */
5
+ export { findOpenApiInBundle, parseOpenApiString, validateOpenApiStructural, serializeOpenApiDoc, validateOpenApiInBundleStructural, } from './openapi-structural.js';
6
+ export { applyFastApiGoldenLayer, applyNodeExpressGoldenLayer, patchMainPyPort8080, patchExpressIndexPort8080, mergePackageJsonScripts, } from './golden-bundle.js';
7
+ export { DEFAULT_GOLDEN_HOST_PORT, normalizeGoldenHostPort, isLocalHostPortFree, } from './hostPort.js';
8
+ export * from './edgeConfigCodeGenerator.js';
9
+ export { default as generatePythonFastAPIFiles } from './pythonFastAPI.js';
10
+ export { default as generateNodeExpressFiles } from './nodeExpress.js';
11
+ export { runDeterministicExport } from './exportPipeline.js';
12
+ export { diffExpectedExportAgainstFiles, diffExpectedExportAgainstDirectory, readDirectoryAsExportMap, runValidateDrift, runDriftCheckAgainstFiles, normalizeExportFileContent, } from './validate-drift.js';
13
+ export { normalizeIrGraph, validateIrStructural, hasIrStructuralErrors, } from './ir-structural.js';
14
+ export { materializeNormalizedGraph, normalizeNodeSlot, normalizeEdgeSlot, } from './ir-normalize.js';
15
+ export { validateIrLint } from './ir-lint.js';
16
+ export { runArchitectureLinting, LINT_RULE_REGISTRY } from './lint-rules.js';
17
+ export { buildParsedLintGraph, isParsedLintGraph, } from './lint-graph.js';
18
+ export { isHttpLikeType, isDbLikeType, isQueueLikeNodeType } from './graphPredicates.js';
19
+ export { sortFindings, shouldFailFromFindings } from './cli-findings.js';
20
+ export { parseYamlToCanonicalIr, canonicalIrToJsonString, YamlGraphParseError, } from './yamlToIr.js';
21
+ export { openApiDocumentToHttpNodes, openApiDocumentToCanonicalIr, openApiStringToCanonicalIr, openApiUnknownToCanonicalIr, OpenApiIngestError, } from './openapi-to-ir.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Architecture lint (IR-LINT-*): thin entry — parses graph then runs visitor registry in `lint-rules.ts`.
3
+ */
4
+ import type { IrStructuralFinding } from './ir-structural.js';
5
+ /**
6
+ * Run architecture lint (IR-LINT-*). If the IR cannot be parsed (invalid root, empty graph, etc.),
7
+ * returns the same **structural** findings as `normalizeIrGraph` / `validateIrStructural` would surface
8
+ * for that shape — callers that only invoke `validateIrLint` still see blockers instead of a silent `[]`.
9
+ */
10
+ export declare function validateIrLint(ir: unknown): IrStructuralFinding[];
11
+ //# sourceMappingURL=ir-lint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ir-lint.d.ts","sourceRoot":"","sources":["../src/ir-lint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,OAAO,GAAG,mBAAmB,EAAE,CAIjE"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Architecture lint (IR-LINT-*): thin entry — parses graph then runs visitor registry in `lint-rules.ts`.
3
+ */
4
+ import { buildParsedLintGraph, isParsedLintGraph } from './lint-graph.js';
5
+ import { runArchitectureLinting } from './lint-rules.js';
6
+ /**
7
+ * Run architecture lint (IR-LINT-*). If the IR cannot be parsed (invalid root, empty graph, etc.),
8
+ * returns the same **structural** findings as `normalizeIrGraph` / `validateIrStructural` would surface
9
+ * for that shape — callers that only invoke `validateIrLint` still see blockers instead of a silent `[]`.
10
+ */
11
+ export function validateIrLint(ir) {
12
+ const built = buildParsedLintGraph(ir);
13
+ if (!isParsedLintGraph(built))
14
+ return built.findings;
15
+ return runArchitectureLinting(built);
16
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Parser boundary: external IR → internal normalized graph shape.
3
+ * Generators still receive the original IR object; use normalized form for validation/lint only.
4
+ */
5
+ export type NormalizedNode = {
6
+ id: string;
7
+ /** Lowercased from `type` or `kind` */
8
+ type: string;
9
+ name: string;
10
+ config: Record<string, unknown>;
11
+ schema: Record<string, unknown>;
12
+ };
13
+ export type NormalizedEdge = {
14
+ id: string;
15
+ from: string;
16
+ to: string;
17
+ config: Record<string, unknown>;
18
+ /** Preserved for lint (e.g. async / protocol); generators still use raw IR. */
19
+ metadata: Record<string, unknown>;
20
+ };
21
+ export type NormalizedGraph = {
22
+ metadata: Record<string, unknown>;
23
+ nodes: NormalizedNode[];
24
+ edges: NormalizedEdge[];
25
+ };
26
+ /**
27
+ * Coerce one node slot to the internal shape (invalid input → empty fields; structural rules flag issues).
28
+ */
29
+ export declare function normalizeNodeSlot(raw: unknown): NormalizedNode;
30
+ /**
31
+ * Coerce one edge slot to internal `from` / `to` (accepts legacy `source` / `target`).
32
+ */
33
+ export declare function normalizeEdgeSlot(raw: unknown): NormalizedEdge;
34
+ export type MaterializeResult = {
35
+ normalized: NormalizedGraph;
36
+ /** True when `edges` was present but not an array (treated as []). */
37
+ edgesInputWasMalformed: boolean;
38
+ };
39
+ /**
40
+ * Build internal normalized graph from an already-unwrapped `graph` object
41
+ * (`normalizeIrGraph` must have succeeded). Does not validate semantics.
42
+ *
43
+ * **Contract:** `normalized.edges[i]` corresponds 1:1 to `graph.edges[i]` when `edges` is an array;
44
+ * structural validation and lint assume this index alignment. If edges are filtered or merged later,
45
+ * keep positions or re-run materialization from the same raw array.
46
+ */
47
+ export declare function materializeNormalizedGraph(graph: Record<string, unknown>): MaterializeResult;
48
+ //# sourceMappingURL=ir-normalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ir-normalize.d.ts","sourceRoot":"","sources":["../src/ir-normalize.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB,CAAC;AAOF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAiB9D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAuB9D;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,eAAe,CAAC;IAC5B,sEAAsE;IACtE,sBAAsB,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,CAkB5F"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Parser boundary: external IR → internal normalized graph shape.
3
+ * Generators still receive the original IR object; use normalized form for validation/lint only.
4
+ */
5
+ function emptyRecord(obj) {
6
+ if (obj && typeof obj === 'object' && !Array.isArray(obj))
7
+ return obj;
8
+ return {};
9
+ }
10
+ /**
11
+ * Coerce one node slot to the internal shape (invalid input → empty fields; structural rules flag issues).
12
+ */
13
+ export function normalizeNodeSlot(raw) {
14
+ if (raw == null || typeof raw !== 'object') {
15
+ return { id: '', type: '', name: '', config: {}, schema: {} };
16
+ }
17
+ const r = raw;
18
+ const id = String(r.id ?? '').trim();
19
+ const type = String(r.type ?? r.kind ?? '')
20
+ .trim()
21
+ .toLowerCase();
22
+ const name = String(r.name ?? '').trim();
23
+ return {
24
+ id,
25
+ type,
26
+ name,
27
+ config: emptyRecord(r.config),
28
+ schema: emptyRecord(r.schema),
29
+ };
30
+ }
31
+ /**
32
+ * Coerce one edge slot to internal `from` / `to` (accepts legacy `source` / `target`).
33
+ */
34
+ export function normalizeEdgeSlot(raw) {
35
+ if (raw == null || typeof raw !== 'object') {
36
+ return { id: '', from: '', to: '', config: {}, metadata: {} };
37
+ }
38
+ const r = raw;
39
+ const from = String(r.from ?? r.source ?? '').trim();
40
+ const to = String(r.to ?? r.target ?? '').trim();
41
+ const id = String(r.id ?? '').trim();
42
+ const metadata = emptyRecord(r.metadata);
43
+ const topKind = r.kind;
44
+ if (topKind !== undefined && topKind !== null && String(topKind).trim() !== '') {
45
+ const k = String(topKind).trim();
46
+ if (metadata.kind == null || String(metadata.kind).trim() === '') {
47
+ metadata.kind = k;
48
+ }
49
+ }
50
+ return {
51
+ id,
52
+ from,
53
+ to,
54
+ config: emptyRecord(r.config),
55
+ metadata,
56
+ };
57
+ }
58
+ /**
59
+ * Build internal normalized graph from an already-unwrapped `graph` object
60
+ * (`normalizeIrGraph` must have succeeded). Does not validate semantics.
61
+ *
62
+ * **Contract:** `normalized.edges[i]` corresponds 1:1 to `graph.edges[i]` when `edges` is an array;
63
+ * structural validation and lint assume this index alignment. If edges are filtered or merged later,
64
+ * keep positions or re-run materialization from the same raw array.
65
+ */
66
+ export function materializeNormalizedGraph(graph) {
67
+ const metadata = emptyRecord(graph.metadata);
68
+ const nodesRaw = Array.isArray(graph.nodes) ? graph.nodes : [];
69
+ const nodes = nodesRaw.map((n) => normalizeNodeSlot(n));
70
+ const edgesMalformed = graph.edges !== undefined && !Array.isArray(graph.edges);
71
+ const edgesRaw = Array.isArray(graph.edges) ? graph.edges : [];
72
+ const edges = edgesRaw.map((e) => normalizeEdgeSlot(e));
73
+ return {
74
+ normalized: {
75
+ metadata,
76
+ nodes,
77
+ edges,
78
+ },
79
+ edgesInputWasMalformed: edgesMalformed,
80
+ };
81
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Deterministic structural validation of blueprint IR (graph JSON).
3
+ * OSS boundary: shape, references, cycles — not security/compliance semantics (ArchRad Cloud).
4
+ */
5
+ export type IrStructuralSeverity = 'error' | 'warning' | 'info';
6
+ /** IR shape/refs (IR-STRUCT-*) vs deterministic architecture heuristics (IR-LINT-*) */
7
+ export type IrFindingLayer = 'structural' | 'lint';
8
+ export type IrStructuralFinding = {
9
+ code: string;
10
+ severity: IrStructuralSeverity;
11
+ message: string;
12
+ /** Primary node id when relevant */
13
+ nodeId?: string;
14
+ /** Index in graph.edges[] */
15
+ edgeIndex?: number;
16
+ /** Short actionable hint (structural); also used as primary “Fix:” line in CLI when no suggestion */
17
+ fixHint?: string;
18
+ /** Set for IR-LINT-* findings */
19
+ layer?: IrFindingLayer;
20
+ /** Longer lint guidance (CLI “Suggestion:”) */
21
+ suggestion?: string;
22
+ /** Risk/context line (CLI “Impact:”) */
23
+ impact?: string;
24
+ };
25
+ /**
26
+ * Normalize product / CLI shapes to a single graph object.
27
+ * Accepts `{ graph: { nodes, edges } }` or a bare `{ nodes, edges }`.
28
+ */
29
+ export declare function normalizeIrGraph(ir: unknown): {
30
+ graph: Record<string, unknown>;
31
+ } | {
32
+ findings: IrStructuralFinding[];
33
+ };
34
+ /**
35
+ * Structural validation only: well-formed graph, edge references, directed cycles, HTTP node config.
36
+ * Same input → same findings (deterministic).
37
+ */
38
+ export declare function validateIrStructural(ir: unknown): IrStructuralFinding[];
39
+ export declare function hasIrStructuralErrors(findings: IrStructuralFinding[]): boolean;
40
+ //# sourceMappingURL=ir-structural.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ir-structural.d.ts","sourceRoot":"","sources":["../src/ir-structural.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEhE,uFAAuF;AACvF,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,MAAM,CAAC;AAEnD,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qGAAqG;IACrG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,+CAA+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAIF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG;IAAE,QAAQ,EAAE,mBAAmB,EAAE,CAAA;CAAE,CA8BtH;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,OAAO,GAAG,mBAAmB,EAAE,CAgOvE;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAE9E"}