adfinem 0.0.0 → 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.
- package/CHANGELOG.md +10 -0
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +29 -0
- package/LICENSE +21 -0
- package/README.md +86 -2
- package/SECURITY.md +13 -0
- package/catalogs/.gitkeep +0 -0
- package/catalogs/api-operations.yaml +21 -0
- package/catalogs/batches.yaml +74 -0
- package/catalogs/queries.yaml +75 -0
- package/config/environments.yaml +13 -0
- package/dist/actions/assert-db.js +3 -0
- package/dist/actions/run-eod.js +3 -0
- package/dist/adapters/api/api-collections.js +296 -0
- package/dist/adapters/api/body-utils.js +9 -0
- package/dist/adapters/api/rest-client.js +557 -0
- package/dist/adapters/api/soap-client.js +5 -0
- package/dist/adapters/db/assertions.js +87 -0
- package/dist/adapters/db/oracle-client.js +115 -0
- package/dist/adapters/db/query-catalog.js +75 -0
- package/dist/adapters/unix/batch-catalog.js +71 -0
- package/dist/adapters/unix/batch-input-files.js +36 -0
- package/dist/adapters/unix/batch-runner.js +382 -0
- package/dist/adapters/unix/ssh-client.js +228 -0
- package/dist/app/server.js +826 -0
- package/dist/cli.js +465 -0
- package/dist/config/environments.js +138 -0
- package/dist/config/registry.js +18 -0
- package/dist/config/secrets.js +123 -0
- package/dist/dsl/parser.js +20 -0
- package/dist/dsl/schema.js +182 -0
- package/dist/dsl/types.js +1 -0
- package/dist/dsl/validator.js +264 -0
- package/dist/engine/captures.js +68 -0
- package/dist/engine/context.js +69 -0
- package/dist/engine/evidence.js +33 -0
- package/dist/engine/known-errors.js +129 -0
- package/dist/engine/retry.js +13 -0
- package/dist/engine/runner.js +710 -0
- package/dist/engine/step-result.js +58 -0
- package/dist/flows/catalog-normalizer.js +72 -0
- package/dist/flows/compiler.js +237 -0
- package/dist/flows/concat.js +130 -0
- package/dist/flows/parser.js +21 -0
- package/dist/flows/schema.js +142 -0
- package/dist/flows/types.js +1 -0
- package/dist/flows/validator.js +470 -0
- package/dist/reports/html-report.js +112 -0
- package/dist/reports/junit-report.js +48 -0
- package/docs/.gitkeep +0 -0
- package/docs/DB_UNIX_OPERATIONS.md +118 -0
- package/docs/FLOW_BUILDER.md +87 -0
- package/flows/account_processing_cycle.flow.yaml +88 -0
- package/flows/new_flow.flow.yaml +22 -0
- package/package.json +92 -7
- package/scenarios/smoke/account-processing-smoke.yaml +44 -0
- package/scenarios/smoke/api-db-batch-check.yaml +40 -0
- package/src/actions/assert-db.ts +6 -0
- package/src/actions/run-eod.ts +6 -0
- package/src/adapters/api/api-collections.ts +375 -0
- package/src/adapters/api/body-utils.ts +10 -0
- package/src/adapters/api/rest-client.ts +587 -0
- package/src/adapters/api/soap-client.ts +7 -0
- package/src/adapters/db/assertions.ts +83 -0
- package/src/adapters/db/oracle-client.ts +133 -0
- package/src/adapters/db/query-catalog.ts +80 -0
- package/src/adapters/unix/batch-catalog.ts +81 -0
- package/src/adapters/unix/batch-input-files.ts +39 -0
- package/src/adapters/unix/batch-runner.ts +456 -0
- package/src/adapters/unix/ssh-client.ts +248 -0
- package/src/app/server.ts +913 -0
- package/src/cli.ts +466 -0
- package/src/config/environments.ts +193 -0
- package/src/config/registry.ts +23 -0
- package/src/config/secrets.ts +128 -0
- package/src/dsl/parser.ts +24 -0
- package/src/dsl/schema.ts +189 -0
- package/src/dsl/types.ts +371 -0
- package/src/dsl/validator.ts +282 -0
- package/src/engine/captures.ts +66 -0
- package/src/engine/context.ts +76 -0
- package/src/engine/evidence.ts +35 -0
- package/src/engine/known-errors.ts +145 -0
- package/src/engine/retry.ts +11 -0
- package/src/engine/runner.ts +746 -0
- package/src/engine/step-result.ts +64 -0
- package/src/flows/catalog-normalizer.ts +86 -0
- package/src/flows/compiler.ts +247 -0
- package/src/flows/concat.ts +149 -0
- package/src/flows/parser.ts +27 -0
- package/src/flows/schema.ts +154 -0
- package/src/flows/types.ts +130 -0
- package/src/flows/validator.ts +468 -0
- package/src/llm/system-prompt.md +9 -0
- package/src/reports/html-report.ts +113 -0
- package/src/reports/junit-report.ts +55 -0
- package/src/types/oracledb.d.ts +1 -0
- package/templates/.gitkeep +0 -0
- package/templates/api/create-test-case.json +5 -0
- package/templates/api/record-test-activity.json +6 -0
- package/tsconfig.json +15 -0
- package/vite.config.ts +17 -0
- package/web/index.html +12 -0
- package/web/src/App.tsx +6588 -0
- package/web/src/main.tsx +10 -0
- package/web/src/styles.css +3147 -0
- package/index.js +0 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export class RunContext {
|
|
2
|
+
values = new Map();
|
|
3
|
+
constructor(scenario) {
|
|
4
|
+
for (const [key, value] of Object.entries(scenario.variables ?? {})) {
|
|
5
|
+
this.values.set(key, value);
|
|
6
|
+
}
|
|
7
|
+
for (const [key, value] of Object.entries(scenario.tenant ?? {})) {
|
|
8
|
+
this.values.set(`tenant.${key}`, value);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
set(name, value) {
|
|
12
|
+
this.values.set(name, value);
|
|
13
|
+
}
|
|
14
|
+
get(name) {
|
|
15
|
+
return this.values.get(name);
|
|
16
|
+
}
|
|
17
|
+
snapshot() {
|
|
18
|
+
return Object.fromEntries(this.values);
|
|
19
|
+
}
|
|
20
|
+
restore(snapshot) {
|
|
21
|
+
this.values.clear();
|
|
22
|
+
for (const [key, value] of Object.entries(snapshot))
|
|
23
|
+
this.values.set(key, value);
|
|
24
|
+
}
|
|
25
|
+
resolve(value) {
|
|
26
|
+
return this.resolveValue(value);
|
|
27
|
+
}
|
|
28
|
+
resolveValue(value) {
|
|
29
|
+
if (typeof value === "string") {
|
|
30
|
+
const exact = /^\$\{([^}]+)\}$/.exec(value);
|
|
31
|
+
if (exact) {
|
|
32
|
+
const resolved = this.values.get(exact[1]);
|
|
33
|
+
if (resolved === undefined || resolved === null) {
|
|
34
|
+
throw new Error(`Variable '${exact[1]}' is not available in run context.`);
|
|
35
|
+
}
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
38
|
+
const postmanExact = /^\{\{([A-Za-z0-9_.-]+)\}\}$/.exec(value);
|
|
39
|
+
if (postmanExact) {
|
|
40
|
+
const resolved = this.values.get(postmanExact[1]);
|
|
41
|
+
if (resolved === undefined || resolved === null) {
|
|
42
|
+
throw new Error(`Variable '${postmanExact[1]}' is not available in run context.`);
|
|
43
|
+
}
|
|
44
|
+
return resolved;
|
|
45
|
+
}
|
|
46
|
+
return value
|
|
47
|
+
.replace(/\$\{([^}]+)\}/g, (_match, name) => {
|
|
48
|
+
const resolved = this.values.get(name);
|
|
49
|
+
if (resolved === undefined || resolved === null) {
|
|
50
|
+
throw new Error(`Variable '${name}' is not available in run context.`);
|
|
51
|
+
}
|
|
52
|
+
return String(resolved);
|
|
53
|
+
})
|
|
54
|
+
.replace(/\{\{([A-Za-z0-9_.-]+)\}\}/g, (_match, name) => {
|
|
55
|
+
const resolved = this.values.get(name);
|
|
56
|
+
if (resolved === undefined || resolved === null) {
|
|
57
|
+
throw new Error(`Variable '${name}' is not available in run context.`);
|
|
58
|
+
}
|
|
59
|
+
return String(resolved);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (Array.isArray(value))
|
|
63
|
+
return value.map((item) => this.resolveValue(item));
|
|
64
|
+
if (value && typeof value === "object") {
|
|
65
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, this.resolveValue(entry)]));
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { applyEvidenceVisibility, evidenceVisibilityMode } from "../config/secrets.js";
|
|
4
|
+
export class EvidenceWriter {
|
|
5
|
+
runDir;
|
|
6
|
+
visibility;
|
|
7
|
+
constructor(runDir, visibility = evidenceVisibilityMode()) {
|
|
8
|
+
this.runDir = runDir;
|
|
9
|
+
this.visibility = visibility;
|
|
10
|
+
}
|
|
11
|
+
async init() {
|
|
12
|
+
await mkdir(this.runDir, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
async writeJson(name, value) {
|
|
15
|
+
const path = join(this.runDir, sanitizeName(name));
|
|
16
|
+
await writeFile(path, JSON.stringify(applyEvidenceVisibility(value, this.visibility), null, 2), "utf8");
|
|
17
|
+
return path;
|
|
18
|
+
}
|
|
19
|
+
async writeJsonPath(relativePath, value) {
|
|
20
|
+
const path = join(this.runDir, ...relativePath.split(/[\\/]+/).map(sanitizeName));
|
|
21
|
+
await mkdir(dirname(path), { recursive: true });
|
|
22
|
+
await writeFile(path, JSON.stringify(applyEvidenceVisibility(value, this.visibility), null, 2), "utf8");
|
|
23
|
+
return path;
|
|
24
|
+
}
|
|
25
|
+
async writeText(name, value) {
|
|
26
|
+
const path = join(this.runDir, sanitizeName(name));
|
|
27
|
+
await writeFile(path, value, "utf8");
|
|
28
|
+
return path;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function sanitizeName(name) {
|
|
32
|
+
return name.replace(/[^A-Za-z0-9_.-]/g, "_");
|
|
33
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
export function explainKnownError(error) {
|
|
2
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
3
|
+
const text = `${err.name}: ${err.message}`;
|
|
4
|
+
const hints = [];
|
|
5
|
+
let message = err.message;
|
|
6
|
+
if (/Invalid JSON for --params|Invalid JSON value for --param/i.test(text)) {
|
|
7
|
+
hints.push("In PowerShell, prefer repeated --param name=value options instead of --params JSON.");
|
|
8
|
+
hints.push("Example: --param case_id=CASE-1001 --param case_id=CASE-1002");
|
|
9
|
+
}
|
|
10
|
+
if (/Invalid JSON for --input/i.test(text)) {
|
|
11
|
+
hints.push("In PowerShell, wrap JSON input in single quotes or escape double quotes.");
|
|
12
|
+
hints.push("Example: --input '{\"application_id\":\"000000000000000000001452\"}'");
|
|
13
|
+
}
|
|
14
|
+
if (err instanceof SyntaxError && (/json/i.test(err.message) || /Expected property name|Unexpected token|Unexpected end/.test(err.message))) {
|
|
15
|
+
message = `Invalid JSON argument: ${err.message}`;
|
|
16
|
+
hints.push("In PowerShell, prefer repeated --param name=value options instead of --params JSON.");
|
|
17
|
+
hints.push("Example: --param case_id=CASE-1001 --param case_id=CASE-1002");
|
|
18
|
+
}
|
|
19
|
+
if (/ADFINEM_DB_USER|ADFINEM_DB_PASSWORD|ADFINEM_DB_CONNECT_STRING/.test(text)) {
|
|
20
|
+
hints.push("Set ADFINEM_DB_USER, ADFINEM_DB_PASSWORD, and ADFINEM_DB_CONNECT_STRING in .env or your shell.");
|
|
21
|
+
hints.push("Example connect string: <host>:1521/<service>");
|
|
22
|
+
}
|
|
23
|
+
if (/DPI-1047|Cannot locate an Oracle Client library/i.test(text)) {
|
|
24
|
+
message = "Oracle client libraries are not available to node-oracledb.";
|
|
25
|
+
hints.push("Install/configure Oracle Instant Client and make sure its directory is on PATH.");
|
|
26
|
+
}
|
|
27
|
+
if (/oracledb\.getConnection is not a function|did not expose getConnection/i.test(text)) {
|
|
28
|
+
message = "The oracledb module loaded in an unexpected shape.";
|
|
29
|
+
hints.push("Run npm install again, then npm run build. This runner supports both ESM default and direct oracledb exports.");
|
|
30
|
+
}
|
|
31
|
+
addOracleHint(text, hints);
|
|
32
|
+
addSshHint(text, hints);
|
|
33
|
+
addCatalogHint(text, hints);
|
|
34
|
+
return { message, hints: unique(hints) };
|
|
35
|
+
}
|
|
36
|
+
export function formatKnownError(error) {
|
|
37
|
+
const explained = explainKnownError(error);
|
|
38
|
+
if (!explained.hints.length)
|
|
39
|
+
return explained.message;
|
|
40
|
+
return [
|
|
41
|
+
explained.message,
|
|
42
|
+
"",
|
|
43
|
+
"Hints:",
|
|
44
|
+
...explained.hints.map((hint) => `- ${hint}`)
|
|
45
|
+
].join("\n");
|
|
46
|
+
}
|
|
47
|
+
function addOracleHint(text, hints) {
|
|
48
|
+
if (/ORA-01017/.test(text)) {
|
|
49
|
+
hints.push("Oracle rejected the username/password. Check ADFINEM_DB_USER and ADFINEM_DB_PASSWORD.");
|
|
50
|
+
}
|
|
51
|
+
if (/ORA-28000/.test(text)) {
|
|
52
|
+
hints.push("Oracle account is locked. Unlock/reset the DB user before rerunning.");
|
|
53
|
+
}
|
|
54
|
+
if (/ORA-12154/.test(text)) {
|
|
55
|
+
hints.push("Oracle could not resolve the connect identifier. Check ADFINEM_DB_CONNECT_STRING.");
|
|
56
|
+
}
|
|
57
|
+
if (/ORA-12514/.test(text)) {
|
|
58
|
+
hints.push("Oracle listener does not know the requested service. Check the service name in ADFINEM_DB_CONNECT_STRING.");
|
|
59
|
+
}
|
|
60
|
+
if (/ORA-12541|ECONNREFUSED/.test(text)) {
|
|
61
|
+
hints.push("Oracle listener connection was refused. Check host, port, VPN, and listener status.");
|
|
62
|
+
}
|
|
63
|
+
if (/ORA-12170|ETIMEDOUT|NJS-510|NJS-511/.test(text)) {
|
|
64
|
+
hints.push("Oracle connection timed out. Check network/VPN/firewall and DB host reachability.");
|
|
65
|
+
}
|
|
66
|
+
if (/ORA-00942/.test(text)) {
|
|
67
|
+
hints.push("Table or view does not exist for this schema, or the DB user lacks privileges.");
|
|
68
|
+
}
|
|
69
|
+
if (/ORA-00904/.test(text)) {
|
|
70
|
+
hints.push("Invalid column name. Check the Action Library SQL against the target environment schema.");
|
|
71
|
+
}
|
|
72
|
+
if (/ORA-01036|NJS-098/.test(text)) {
|
|
73
|
+
hints.push("SQL bind mismatch. Check Action Library parameter names and supplied --param values.");
|
|
74
|
+
}
|
|
75
|
+
if (/ORA-01722/.test(text)) {
|
|
76
|
+
hints.push("Invalid number. Use json: for numeric --param values, for example --param amount=json:111.");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function addSshHint(text, hints) {
|
|
80
|
+
if (/Unknown SSH hostRef/.test(text)) {
|
|
81
|
+
hints.push("Add the hostRef under sshHosts in config/environments.yaml.");
|
|
82
|
+
}
|
|
83
|
+
if (/requires host and username/.test(text)) {
|
|
84
|
+
hints.push("Set the SSH host and username in config/environments.yaml/.env.");
|
|
85
|
+
}
|
|
86
|
+
if (/All configured authentication methods failed|Permission denied/i.test(text)) {
|
|
87
|
+
hints.push("SSH authentication failed. Check ADFINEM_SSH_USER, ADFINEM_SSH_PASSWORD, or ADFINEM_SSH_PRIVATE_KEY_PATH.");
|
|
88
|
+
}
|
|
89
|
+
if (/ENOTFOUND|getaddrinfo/.test(text)) {
|
|
90
|
+
hints.push("SSH host name could not be resolved. Check the host value and VPN/DNS.");
|
|
91
|
+
}
|
|
92
|
+
if (/ECONNREFUSED/.test(text)) {
|
|
93
|
+
hints.push("SSH connection was refused. Check host, port, firewall, and sshd status.");
|
|
94
|
+
}
|
|
95
|
+
if (/timed out|ETIMEDOUT/i.test(text)) {
|
|
96
|
+
hints.push("SSH command or connection timed out. Check server load, VPN, and the Action Library timeoutSeconds.");
|
|
97
|
+
}
|
|
98
|
+
if (/exit code 127|No such file or directory|command not found|not found/i.test(text)) {
|
|
99
|
+
hints.push("The Unix command did not find the batch script. Set ADFINEM_BATCH_WORKDIR in .env, or put an absolute script path in the Action Library batch template.");
|
|
100
|
+
hints.push("If the script lives on the remote server, ADFINEM_BATCH_WORKDIR must be the remote directory that contains reconcile_nightly.sh, generate_report.sh, and the other batch scripts.");
|
|
101
|
+
hints.push("If the command works only after an interactive SSH login, enable 'Run commands in login shell' for the SSH host in Environments.");
|
|
102
|
+
}
|
|
103
|
+
if (/ENOENT.*private|no such file/i.test(text)) {
|
|
104
|
+
hints.push("SSH private key path does not exist. Check ADFINEM_SSH_PRIVATE_KEY_PATH.");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function addCatalogHint(text, hints) {
|
|
108
|
+
if (/Unknown API operation/.test(text)) {
|
|
109
|
+
hints.push("Run against an API operation present in the Action Library file catalogs/api-operations.yaml.");
|
|
110
|
+
}
|
|
111
|
+
if (/Unknown query/.test(text)) {
|
|
112
|
+
hints.push("Run against a query template present in the Action Library file catalogs/queries.yaml.");
|
|
113
|
+
}
|
|
114
|
+
if (/Unknown batch/.test(text)) {
|
|
115
|
+
hints.push("Run against a batch template present in the Action Library file catalogs/batches.yaml.");
|
|
116
|
+
}
|
|
117
|
+
if (/Missing required query param|Missing required batch arg/.test(text)) {
|
|
118
|
+
hints.push("Supply missing values with --param name=value or scenario params/input.");
|
|
119
|
+
}
|
|
120
|
+
if (/Capture expression .* did not match/i.test(text)) {
|
|
121
|
+
hints.push("A required capture did not exist in the action output. If this value is legitimately optional for the flow, prefix the capture expression with optional: in the Action Library template.");
|
|
122
|
+
}
|
|
123
|
+
if (/must be string\[\]|must contain at least one value/.test(text)) {
|
|
124
|
+
hints.push("For list params, pass one value or repeat --param. Example: --param case_id=CASE-1001 --param case_id=CASE-1002");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function unique(values) {
|
|
128
|
+
return [...new Set(values)];
|
|
129
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export async function withTimeout(promise, timeoutMs, label) {
|
|
2
|
+
let timeout;
|
|
3
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
4
|
+
timeout = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms.`)), timeoutMs);
|
|
5
|
+
});
|
|
6
|
+
try {
|
|
7
|
+
return await Promise.race([promise, timeoutPromise]);
|
|
8
|
+
}
|
|
9
|
+
finally {
|
|
10
|
+
if (timeout)
|
|
11
|
+
clearTimeout(timeout);
|
|
12
|
+
}
|
|
13
|
+
}
|