@rex0220/ksql-flownet 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +81 -0
  3. package/README.md +117 -0
  4. package/dist/bundle/bundle-builder.d.ts +49 -0
  5. package/dist/bundle/bundle-builder.js +208 -0
  6. package/dist/bundle/index.d.ts +4 -0
  7. package/dist/bundle/index.js +4 -0
  8. package/dist/bundle/upload.d.ts +28 -0
  9. package/dist/bundle/upload.js +72 -0
  10. package/dist/bundle/zip-store.d.ts +17 -0
  11. package/dist/bundle/zip-store.js +175 -0
  12. package/dist/cli/archive-run-command.d.ts +12 -0
  13. package/dist/cli/archive-run-command.js +131 -0
  14. package/dist/cli/cancel-run-command.d.ts +9 -0
  15. package/dist/cli/cancel-run-command.js +77 -0
  16. package/dist/cli/force-unlock-network-command.d.ts +8 -0
  17. package/dist/cli/force-unlock-network-command.js +129 -0
  18. package/dist/cli/index.d.ts +3 -0
  19. package/dist/cli/index.js +132 -0
  20. package/dist/cli/plan-command.d.ts +2 -0
  21. package/dist/cli/plan-command.js +100 -0
  22. package/dist/cli/poll-requests-command.d.ts +3 -0
  23. package/dist/cli/poll-requests-command.js +74 -0
  24. package/dist/cli/record-job-unlock-command.d.ts +11 -0
  25. package/dist/cli/record-job-unlock-command.js +78 -0
  26. package/dist/cli/resolve-node-command.d.ts +13 -0
  27. package/dist/cli/resolve-node-command.js +174 -0
  28. package/dist/cli/run-network-command.d.ts +14 -0
  29. package/dist/cli/run-network-command.js +418 -0
  30. package/dist/cli/status-command.d.ts +5 -0
  31. package/dist/cli/status-command.js +137 -0
  32. package/dist/cli/validate-command.d.ts +6 -0
  33. package/dist/cli/validate-command.js +17 -0
  34. package/dist/dag/descendants.d.ts +9 -0
  35. package/dist/dag/descendants.js +67 -0
  36. package/dist/dag/topological-sort.d.ts +7 -0
  37. package/dist/dag/topological-sort.js +32 -0
  38. package/dist/domain/business-key.d.ts +15 -0
  39. package/dist/domain/business-key.js +249 -0
  40. package/dist/domain/canonical-lock-key.d.ts +17 -0
  41. package/dist/domain/canonical-lock-key.js +60 -0
  42. package/dist/domain/canonical-record-key.d.ts +18 -0
  43. package/dist/domain/canonical-record-key.js +70 -0
  44. package/dist/domain/load-network.d.ts +4 -0
  45. package/dist/domain/load-network.js +47 -0
  46. package/dist/domain/network-definition.d.ts +47 -0
  47. package/dist/domain/network-definition.js +7 -0
  48. package/dist/domain/persistence-model.d.ts +198 -0
  49. package/dist/domain/persistence-model.js +2 -0
  50. package/dist/domain/run-aggregate.d.ts +3 -0
  51. package/dist/domain/run-aggregate.js +15 -0
  52. package/dist/domain/validate-network-path.d.ts +11 -0
  53. package/dist/domain/validate-network-path.js +33 -0
  54. package/dist/domain/validate-network.d.ts +3 -0
  55. package/dist/domain/validate-network.js +180 -0
  56. package/dist/executor/attempt-executor.d.ts +43 -0
  57. package/dist/executor/attempt-executor.js +236 -0
  58. package/dist/executor/job-log-reader.d.ts +37 -0
  59. package/dist/executor/job-log-reader.js +91 -0
  60. package/dist/executor/ksql-flow-cli.d.ts +86 -0
  61. package/dist/executor/ksql-flow-cli.js +161 -0
  62. package/dist/executor/preflight.d.ts +41 -0
  63. package/dist/executor/preflight.js +150 -0
  64. package/dist/executor/result-classifier.d.ts +69 -0
  65. package/dist/executor/result-classifier.js +237 -0
  66. package/dist/executor/run-subprocess.d.ts +64 -0
  67. package/dist/executor/run-subprocess.js +175 -0
  68. package/dist/io/input-baseline.d.ts +19 -0
  69. package/dist/io/input-baseline.js +214 -0
  70. package/dist/io/io-config.d.ts +6 -0
  71. package/dist/io/io-config.js +33 -0
  72. package/dist/io/io-path.d.ts +37 -0
  73. package/dist/io/io-path.js +279 -0
  74. package/dist/orchestration/archive-run.d.ts +53 -0
  75. package/dist/orchestration/archive-run.js +323 -0
  76. package/dist/orchestration/cancel-request.d.ts +18 -0
  77. package/dist/orchestration/cancel-request.js +67 -0
  78. package/dist/orchestration/ensure-run.d.ts +83 -0
  79. package/dist/orchestration/ensure-run.js +632 -0
  80. package/dist/orchestration/job-lock-audit.d.ts +24 -0
  81. package/dist/orchestration/job-lock-audit.js +123 -0
  82. package/dist/orchestration/orphan-attempt-adjudication.d.ts +39 -0
  83. package/dist/orchestration/orphan-attempt-adjudication.js +167 -0
  84. package/dist/orchestration/reconciliation.d.ts +41 -0
  85. package/dist/orchestration/reconciliation.js +526 -0
  86. package/dist/orchestration/resolve-node.d.ts +29 -0
  87. package/dist/orchestration/resolve-node.js +105 -0
  88. package/dist/orchestration/run-activity.d.ts +13 -0
  89. package/dist/orchestration/run-activity.js +14 -0
  90. package/dist/orchestration/sequential-scheduler.d.ts +79 -0
  91. package/dist/orchestration/sequential-scheduler.js +706 -0
  92. package/dist/orchestration/status.d.ts +91 -0
  93. package/dist/orchestration/status.js +171 -0
  94. package/dist/persistence/in-memory-repository.d.ts +41 -0
  95. package/dist/persistence/in-memory-repository.js +333 -0
  96. package/dist/persistence/kintone/client.d.ts +36 -0
  97. package/dist/persistence/kintone/client.js +93 -0
  98. package/dist/persistence/kintone/design-notes.d.ts +38 -0
  99. package/dist/persistence/kintone/design-notes.js +38 -0
  100. package/dist/persistence/kintone/repository.d.ts +44 -0
  101. package/dist/persistence/kintone/repository.js +836 -0
  102. package/dist/persistence/kintone/schema.d.ts +116 -0
  103. package/dist/persistence/kintone/schema.js +115 -0
  104. package/dist/persistence/network-lock-reader.d.ts +24 -0
  105. package/dist/persistence/network-lock-reader.js +43 -0
  106. package/dist/persistence/network-lock-recovery.d.ts +66 -0
  107. package/dist/persistence/network-lock-recovery.js +392 -0
  108. package/dist/persistence/network-lock.d.ts +97 -0
  109. package/dist/persistence/network-lock.js +408 -0
  110. package/dist/persistence/repository.d.ts +101 -0
  111. package/dist/persistence/repository.js +11 -0
  112. package/dist/persistence/state-transition.d.ts +4 -0
  113. package/dist/persistence/state-transition.js +25 -0
  114. package/dist/requests/flownet-child-client.d.ts +78 -0
  115. package/dist/requests/flownet-child-client.js +243 -0
  116. package/dist/requests/kintone-request-store.d.ts +44 -0
  117. package/dist/requests/kintone-request-store.js +202 -0
  118. package/dist/requests/poll-requests-config.d.ts +26 -0
  119. package/dist/requests/poll-requests-config.js +105 -0
  120. package/dist/requests/request-model.d.ts +57 -0
  121. package/dist/requests/request-model.js +305 -0
  122. package/dist/requests/request-poller.d.ts +33 -0
  123. package/dist/requests/request-poller.js +353 -0
  124. package/dist/requests/request-result.d.ts +18 -0
  125. package/dist/requests/request-result.js +215 -0
  126. package/dist/requests/start-request.d.ts +26 -0
  127. package/dist/requests/start-request.js +83 -0
  128. package/package.json +63 -0
  129. package/schemas/.gitkeep +1 -0
  130. package/schemas/network-definition.schema.json +121 -0
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ const HELP_TEXT = `Usage: ksql-flownet [options] [command]
5
+
6
+ kSQL-FlowNet Control Plane CLI
7
+
8
+ Options:
9
+ -h, --help display help for command
10
+ -V, --version display version
11
+
12
+ Commands:
13
+ validate <network> validate a network definition and its SQL files
14
+ plan <network> [--scheduled-for <timestamp>] [--business-key <key>]
15
+ display the business key and stable execution plan (read-only)
16
+ run-network <network> [--business-key <key>] [--scheduled-for <timestamp>]
17
+ [--resume] [--resume-run <run_id>] [--rerun-from <node_id>]
18
+ [--json]
19
+ [--ksql-flow-bin <path>] [--ksql-flow-config <path>]
20
+ [--ksql-flow-workdir <path>]
21
+ ensure and execute a Network Run sequentially
22
+ poll-requests [--check]
23
+ claim and process app operation requests (one-shot),
24
+ or validate configuration and read access without writes
25
+ resolve-node --run-id <run_id> --node-id <node_id> --to <status>
26
+ --reason-file <path> --evidence-ref <ref>
27
+ --stop-confirmed-by <subject> --stop-evidence-ref <ref>
28
+ [--manual-completion | --compensation] [--approved-by <subject>]
29
+ resolve an UNKNOWN or non-idempotent FAILED node
30
+ record-job-unlock --result-file <path> --run-id <run_id> --node-id <node_id>
31
+ --reason-file <path> --evidence-ref <ref>
32
+ --stop-confirmed-by <subject>
33
+ associate a kSQL-Flow LOCK_RECOVERY_RESULT with audit
34
+ force-unlock-network <network_id> --profile <profile>
35
+ --expected-owner-invocation-id <invocation_id>
36
+ --reason-file <path> --evidence-ref <ref>
37
+ --stop-confirmed-by <subject> --stop-evidence-ref <ref>
38
+ --stop-method <method>
39
+ force-release a stale Network lock with an audit record
40
+ cancel-run --run-id <run_id> [--release] --reason-file <path>
41
+ request or release a Run hold at the next node boundary
42
+ archive-run <network.yaml> --run-id <run_id> --reason-file <path> [--profile <profile>]
43
+ archive a terminal failed or cancelled Run
44
+ status <network_id> --profile <profile>
45
+ [--run-id <run_id> | --business-key <key>] [--json]
46
+ inspect lock and Run recovery state (read-only)
47
+ `;
48
+ function getVersion() {
49
+ const packageJsonPath = fileURLToPath(new URL("../../package.json", import.meta.url));
50
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
51
+ if (typeof parsed !== "object" ||
52
+ parsed === null ||
53
+ !("version" in parsed) ||
54
+ typeof parsed.version !== "string") {
55
+ throw new Error("package.json does not contain a valid version");
56
+ }
57
+ return parsed.version;
58
+ }
59
+ function printHelp() {
60
+ process.stdout.write(HELP_TEXT);
61
+ }
62
+ async function main(args) {
63
+ const [command, ...commandArgs] = args;
64
+ if (command === undefined || command === "--help" || command === "-h") {
65
+ printHelp();
66
+ return;
67
+ }
68
+ if (command === "--version" || command === "-V") {
69
+ process.stdout.write(`${getVersion()}\n`);
70
+ return;
71
+ }
72
+ if (command === "validate") {
73
+ if (commandArgs.length !== 1) {
74
+ process.stderr.write("Error: validate requires exactly one <network> path.\n");
75
+ process.exitCode = 1;
76
+ return;
77
+ }
78
+ const { runValidateCommand } = await import("./validate-command.js");
79
+ process.exitCode = runValidateCommand(commandArgs[0]);
80
+ return;
81
+ }
82
+ if (command === "plan") {
83
+ const { runPlanCommand } = await import("./plan-command.js");
84
+ process.exitCode = runPlanCommand(commandArgs);
85
+ return;
86
+ }
87
+ if (command === "run-network") {
88
+ const { runRunNetworkCommand } = await import("./run-network-command.js");
89
+ process.exitCode = await runRunNetworkCommand(commandArgs);
90
+ return;
91
+ }
92
+ if (command === "poll-requests") {
93
+ const { runPollRequestsCommand } = await import("./poll-requests-command.js");
94
+ process.exitCode = await runPollRequestsCommand(commandArgs);
95
+ return;
96
+ }
97
+ if (command === "resolve-node") {
98
+ const { runResolveNodeCommand } = await import("./resolve-node-command.js");
99
+ process.exitCode = await runResolveNodeCommand(commandArgs);
100
+ return;
101
+ }
102
+ if (command === "record-job-unlock") {
103
+ const { runRecordJobUnlockCommand } = await import("./record-job-unlock-command.js");
104
+ process.exitCode = await runRecordJobUnlockCommand(commandArgs);
105
+ return;
106
+ }
107
+ if (command === "force-unlock-network") {
108
+ const { runForceUnlockNetworkCommand } = await import("./force-unlock-network-command.js");
109
+ process.exitCode = await runForceUnlockNetworkCommand(commandArgs);
110
+ return;
111
+ }
112
+ if (command === "status") {
113
+ const { runStatusCommand } = await import("./status-command.js");
114
+ process.exitCode = await runStatusCommand(commandArgs);
115
+ return;
116
+ }
117
+ if (command === "cancel-run") {
118
+ const { runCancelRunCommand } = await import("./cancel-run-command.js");
119
+ process.exitCode = await runCancelRunCommand(commandArgs);
120
+ return;
121
+ }
122
+ if (command === "archive-run") {
123
+ const { runArchiveRunCommand } = await import("./archive-run-command.js");
124
+ process.exitCode = await runArchiveRunCommand(commandArgs);
125
+ return;
126
+ }
127
+ process.stderr.write(`Error: unknown command '${command}'.\n`);
128
+ process.stderr.write("Run 'ksql-flownet --help' for usage.\n");
129
+ process.exitCode = 1;
130
+ }
131
+ await main(process.argv.slice(2));
132
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,2 @@
1
+ export declare function runPlanCommand(args: readonly string[]): number;
2
+ //# sourceMappingURL=plan-command.d.ts.map
@@ -0,0 +1,100 @@
1
+ import { stableTopologicalSort } from "../dag/topological-sort.js";
2
+ import { resolveBusinessKey } from "../domain/business-key.js";
3
+ import { validateNetworkPath, writeValidationErrors, } from "./validate-command.js";
4
+ function parsePlanArguments(args) {
5
+ let networkPath;
6
+ let scheduledFor;
7
+ let businessKey;
8
+ const errors = [];
9
+ for (let index = 0; index < args.length; index += 1) {
10
+ const argument = args[index];
11
+ if (argument === "--scheduled-for" || argument === "--business-key") {
12
+ const value = args[index + 1];
13
+ if (value === undefined || value.startsWith("--")) {
14
+ errors.push(`${argument} requires a value`);
15
+ continue;
16
+ }
17
+ if (argument === "--scheduled-for") {
18
+ if (scheduledFor !== undefined)
19
+ errors.push(`${argument} was specified more than once`);
20
+ scheduledFor = value;
21
+ }
22
+ else {
23
+ if (businessKey !== undefined)
24
+ errors.push(`${argument} was specified more than once`);
25
+ businessKey = value;
26
+ }
27
+ index += 1;
28
+ continue;
29
+ }
30
+ if (argument.startsWith("-")) {
31
+ errors.push(`unknown option '${argument}'`);
32
+ continue;
33
+ }
34
+ if (networkPath === undefined)
35
+ networkPath = argument;
36
+ else
37
+ errors.push(`unexpected argument '${argument}'`);
38
+ }
39
+ if (networkPath === undefined)
40
+ errors.push("plan requires one <network> path");
41
+ const result = { errors };
42
+ if (networkPath !== undefined)
43
+ Object.assign(result, { networkPath });
44
+ if (scheduledFor !== undefined)
45
+ Object.assign(result, { scheduledFor });
46
+ if (businessKey !== undefined)
47
+ Object.assign(result, { businessKey });
48
+ return result;
49
+ }
50
+ export function runPlanCommand(args) {
51
+ const parsed = parsePlanArguments(args);
52
+ if (parsed.errors.length > 0 || parsed.networkPath === undefined) {
53
+ process.stderr.write(`Invalid plan arguments:\n${parsed.errors.map((error) => `- ${error}`).join("\n")}\n`);
54
+ return 1;
55
+ }
56
+ const validated = validateNetworkPath(parsed.networkPath);
57
+ const errors = [...validated.errors];
58
+ let businessKey;
59
+ if (validated.definition !== undefined) {
60
+ const input = {
61
+ networkId: validated.definition.network_id,
62
+ policy: validated.definition.business_key_policy,
63
+ ...(parsed.scheduledFor === undefined
64
+ ? {}
65
+ : { scheduledFor: parsed.scheduledFor }),
66
+ ...(parsed.businessKey === undefined
67
+ ? {}
68
+ : { businessKey: parsed.businessKey }),
69
+ };
70
+ const resolved = resolveBusinessKey(input);
71
+ for (const error of resolved.errors) {
72
+ if (!errors.some((existing) => existing.code === error.code &&
73
+ existing.path === error.path &&
74
+ existing.message === error.message)) {
75
+ errors.push(error);
76
+ }
77
+ }
78
+ businessKey = resolved.businessKey;
79
+ }
80
+ if (errors.length > 0) {
81
+ writeValidationErrors(errors);
82
+ return 1;
83
+ }
84
+ if (validated.definition === undefined || businessKey === undefined) {
85
+ process.stderr.write("Error: plan could not be produced.\n");
86
+ return 1;
87
+ }
88
+ const nodeById = new Map(validated.definition.nodes.map((node) => [node.id, node]));
89
+ const order = stableTopologicalSort(validated.definition.nodes).order;
90
+ const lines = order.map((nodeId, index) => {
91
+ const node = nodeById.get(nodeId);
92
+ if (node === undefined)
93
+ throw new Error(`missing sorted node '${nodeId}'`);
94
+ const dependencies = node.depends_on.length === 0 ? "-" : node.depends_on.join(", ");
95
+ return `${index + 1}. ${node.id} | job_id=${node.job_id} | idempotent=${String(node.idempotent)} | depends_on=${dependencies}`;
96
+ });
97
+ process.stdout.write(`Business key: ${businessKey}\nExecution plan:\n${lines.join("\n")}\n`);
98
+ return 0;
99
+ }
100
+ //# sourceMappingURL=plan-command.js.map
@@ -0,0 +1,3 @@
1
+ import { type RequestPollerDependencies } from "../requests/request-poller.js";
2
+ export declare function runPollRequestsCommand(args: readonly string[], dependencies?: RequestPollerDependencies): Promise<number>;
3
+ //# sourceMappingURL=poll-requests-command.d.ts.map
@@ -0,0 +1,74 @@
1
+ import { hostname } from "node:os";
2
+ import { FlownetChildClient } from "../requests/flownet-child-client.js";
3
+ import { KintoneRequestStore } from "../requests/kintone-request-store.js";
4
+ import { DEFAULT_REQUEST_HEARTBEAT_INTERVAL_MS, DEFAULT_REQUEST_STALE_AFTER_MS, loadPollRequestsConfig, } from "../requests/poll-requests-config.js";
5
+ import { pollRequests, } from "../requests/request-poller.js";
6
+ import { requiredEnvironment } from "./resolve-node-command.js";
7
+ export async function runPollRequestsCommand(args, dependencies) {
8
+ const checkOnly = args.length === 1 && args[0] === "--check";
9
+ if (args.length > 0 && !checkOnly) {
10
+ process.stderr.write("Invalid poll-requests arguments: only --check is accepted.\n");
11
+ return 1;
12
+ }
13
+ try {
14
+ const resolvedDependencies = dependencies ?? productionDependencies();
15
+ if (checkOnly) {
16
+ await resolvedDependencies.store.listRequested();
17
+ process.stdout.write(`poll-requests check: ok networks=${resolvedDependencies.config.networks.length} request_app=readable\n`);
18
+ return 0;
19
+ }
20
+ const summary = await pollRequests(resolvedDependencies);
21
+ process.stdout.write(`poll-requests: requested=${summary.requested} claimed=${summary.claimed} completed=${summary.completed} cancelled=${summary.cancelled} invalid=${summary.invalid} stale=${summary.stale} skipped=${summary.skippedMalformed}\n`);
22
+ return 0;
23
+ }
24
+ catch (error) {
25
+ const code = errorCode(error);
26
+ const message = error instanceof Error ? error.message : String(error);
27
+ process.stderr.write(`Error [${code}]: ${message}\n`);
28
+ return 1;
29
+ }
30
+ }
31
+ function productionDependencies() {
32
+ const baseUrl = requiredEnvironment("KSQL_FLOWNET_BASE_URL").replace(/\/$/u, "");
33
+ const appId = positiveIntegerEnvironment("KSQL_FLOWNET_REQUEST_APP_ID");
34
+ const apiToken = requiredEnvironment("KSQL_FLOWNET_REQUEST_API_TOKEN");
35
+ const heartbeatIntervalMs = integerEnvironment("KSQL_FLOWNET_REQUEST_HEARTBEAT_INTERVAL_MS", DEFAULT_REQUEST_HEARTBEAT_INTERVAL_MS);
36
+ const staleAfterMs = integerEnvironment("KSQL_FLOWNET_REQUEST_STALE_AFTER_MS", DEFAULT_REQUEST_STALE_AFTER_MS);
37
+ const config = loadPollRequestsConfig(requiredEnvironment("KSQL_FLOWNET_REQUEST_ALLOWLIST_PATH"), { heartbeatIntervalMs, staleAfterMs });
38
+ return {
39
+ store: new KintoneRequestStore({ baseUrl, appId, apiToken }),
40
+ child: new FlownetChildClient({
41
+ profile: requiredEnvironment("KSQL_FLOWNET_PROFILE"),
42
+ }),
43
+ config,
44
+ host: process.env.KSQL_FLOWNET_HOST ?? hostname(),
45
+ log(code, detail) {
46
+ process.stderr.write(`Warning [${code}]: ${detail}\n`);
47
+ },
48
+ };
49
+ }
50
+ function positiveIntegerEnvironment(name) {
51
+ const value = Number(requiredEnvironment(name));
52
+ if (!Number.isSafeInteger(value) || value < 1)
53
+ throw new Error(`${name} must be a positive integer`);
54
+ return value;
55
+ }
56
+ function integerEnvironment(name, fallback) {
57
+ const raw = process.env[name];
58
+ if (raw === undefined || raw.trim() === "")
59
+ return fallback;
60
+ const value = Number(raw);
61
+ if (!Number.isSafeInteger(value) || value < 1)
62
+ throw new Error(`${name} must be a positive integer`);
63
+ return value;
64
+ }
65
+ function errorCode(error) {
66
+ if (typeof error === "object" &&
67
+ error !== null &&
68
+ "code" in error &&
69
+ typeof error.code === "string") {
70
+ return error.code;
71
+ }
72
+ return "POLL_REQUESTS_FAILED";
73
+ }
74
+ //# sourceMappingURL=poll-requests-command.js.map
@@ -0,0 +1,11 @@
1
+ import type { PersistenceRepository } from "../persistence/repository.js";
2
+ export interface RecordJobUnlockCommandDependencies {
3
+ repository: PersistenceRepository;
4
+ servicePrincipal: string;
5
+ requestedBy: string;
6
+ readFile?: (path: string) => string;
7
+ now?: () => Date;
8
+ uuid?: () => string;
9
+ }
10
+ export declare function runRecordJobUnlockCommand(args: readonly string[], dependencies?: RecordJobUnlockCommandDependencies): Promise<number>;
11
+ //# sourceMappingURL=record-job-unlock-command.d.ts.map
@@ -0,0 +1,78 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { recordJobUnlock } from "../orchestration/job-lock-audit.js";
3
+ import { repositoryFromEnvironment, requiredEnvironment, } from "./resolve-node-command.js";
4
+ export async function runRecordJobUnlockCommand(args, dependencies) {
5
+ const parsed = parseArguments(args);
6
+ if (parsed.errors.length > 0) {
7
+ process.stderr.write(`Invalid record-job-unlock arguments:\n${parsed.errors.map((error) => `- ${error}`).join("\n")}\n`);
8
+ return 1;
9
+ }
10
+ try {
11
+ const runtime = dependencies ?? {
12
+ repository: repositoryFromEnvironment(),
13
+ servicePrincipal: requiredEnvironment("KSQL_FLOWNET_SERVICE_PRINCIPAL"),
14
+ requestedBy: requiredEnvironment("KSQL_FLOWNET_REQUESTED_BY"),
15
+ };
16
+ const read = runtime.readFile ?? ((path) => readFileSync(path, "utf8"));
17
+ const rawResult = read(parsed.values.get("--result-file"));
18
+ const audit = await recordJobUnlock({
19
+ repository: runtime.repository,
20
+ runId: parsed.values.get("--run-id"),
21
+ nodeId: parsed.values.get("--node-id"),
22
+ result: JSON.parse(rawResult),
23
+ reason: read(parsed.values.get("--reason-file")),
24
+ evidenceRef: parsed.values.get("--evidence-ref"),
25
+ servicePrincipal: runtime.servicePrincipal,
26
+ requestedBy: runtime.requestedBy,
27
+ stopConfirmedBy: parsed.values.get("--stop-confirmed-by"),
28
+ ...(runtime.now === undefined ? {} : { now: runtime.now }),
29
+ ...(runtime.uuid === undefined ? {} : { uuid: runtime.uuid }),
30
+ });
31
+ process.stdout.write(`RECORDED: ${audit.lock_recovery_result.outcome} for ${audit.lock_recovery_result.jobKey} (${audit.event_id}).\n`);
32
+ return 0;
33
+ }
34
+ catch (error) {
35
+ const code = typeof error === "object" && error !== null && "code" in error
36
+ ? String(error.code)
37
+ : "RECORD_JOB_UNLOCK_FAILED";
38
+ const message = error instanceof Error ? error.message : String(error);
39
+ process.stderr.write(`Error [${code}]: ${message}\n`);
40
+ return 1;
41
+ }
42
+ }
43
+ function parseArguments(args) {
44
+ const values = new Map();
45
+ const errors = [];
46
+ const allowed = new Set([
47
+ "--result-file",
48
+ "--run-id",
49
+ "--node-id",
50
+ "--reason-file",
51
+ "--evidence-ref",
52
+ "--stop-confirmed-by",
53
+ ]);
54
+ for (let index = 0; index < args.length; index += 1) {
55
+ const key = args[index];
56
+ if (!key.startsWith("--")) {
57
+ errors.push(`unexpected argument '${key}'`);
58
+ continue;
59
+ }
60
+ const value = args[index + 1];
61
+ if (value === undefined || value.startsWith("--")) {
62
+ errors.push(`${key} requires a value`);
63
+ continue;
64
+ }
65
+ if (!allowed.has(key))
66
+ errors.push(`unknown option '${key}'`);
67
+ if (values.has(key))
68
+ errors.push(`${key} was specified more than once`);
69
+ values.set(key, value);
70
+ index += 1;
71
+ }
72
+ for (const key of allowed) {
73
+ if (!values.has(key))
74
+ errors.push(`${key} is required`);
75
+ }
76
+ return { values, errors };
77
+ }
78
+ //# sourceMappingURL=record-job-unlock-command.js.map
@@ -0,0 +1,13 @@
1
+ import { KintonePersistenceRepository } from "../persistence/kintone/repository.js";
2
+ import type { PersistenceRepository } from "../persistence/repository.js";
3
+ export interface ResolveNodeCommandDependencies {
4
+ repository: PersistenceRepository;
5
+ servicePrincipal: string;
6
+ requestedBy: string;
7
+ readFile?: (path: string) => string;
8
+ now?: () => Date;
9
+ }
10
+ export declare function runResolveNodeCommand(args: readonly string[], dependencies?: ResolveNodeCommandDependencies): Promise<number>;
11
+ export declare function repositoryFromEnvironment(): KintonePersistenceRepository;
12
+ export declare function requiredEnvironment(name: string): string;
13
+ //# sourceMappingURL=resolve-node-command.d.ts.map
@@ -0,0 +1,174 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolveNode, } from "../orchestration/resolve-node.js";
3
+ import { KintonePersistenceRepository } from "../persistence/kintone/repository.js";
4
+ export async function runResolveNodeCommand(args, dependencies) {
5
+ const parsed = parseArguments(args);
6
+ if (parsed.errors.length > 0) {
7
+ process.stderr.write(`Invalid resolve-node arguments:\n${parsed.errors.map((error) => `- ${error}`).join("\n")}\n`);
8
+ return 1;
9
+ }
10
+ try {
11
+ const runtime = dependencies ?? productionDependencies();
12
+ const reason = (runtime.readFile ?? ((path) => readFileSync(path, "utf8")))(parsed.reasonFile);
13
+ const result = await resolveNode({
14
+ repository: runtime.repository,
15
+ runId: parsed.runId,
16
+ nodeId: parsed.nodeId,
17
+ outcome: parsed.outcome,
18
+ resolutionType: parsed.manualCompletion
19
+ ? "NODE_MANUAL_COMPLETION_CONFIRMED"
20
+ : parsed.compensation
21
+ ? "NODE_COMPENSATION_COMPLETED"
22
+ : "OUTCOME_CONFIRMED",
23
+ reason,
24
+ evidenceRef: parsed.evidenceRef,
25
+ servicePrincipal: runtime.servicePrincipal,
26
+ requestedBy: runtime.requestedBy,
27
+ approvedBy: parsed.approvedBy ?? "",
28
+ stopConfirmedBy: parsed.stopConfirmedBy,
29
+ stopEvidenceRef: parsed.stopEvidenceRef,
30
+ ...(runtime.now === undefined ? {} : { now: runtime.now }),
31
+ });
32
+ process.stdout.write(`RESOLVED: ${parsed.runId}/${parsed.nodeId} -> ${result.nodeState.value.status} (revision ${result.nodeState.revision}).\n`);
33
+ return 0;
34
+ }
35
+ catch (error) {
36
+ const code = errorCode(error, "RESOLVE_NODE_FAILED");
37
+ const message = error instanceof Error ? error.message : String(error);
38
+ process.stderr.write(`Error [${code}]: ${message}\n`);
39
+ return 1;
40
+ }
41
+ }
42
+ function parseArguments(args) {
43
+ const result = {
44
+ manualCompletion: false,
45
+ compensation: false,
46
+ errors: [],
47
+ };
48
+ const values = new Map();
49
+ for (let index = 0; index < args.length; index += 1) {
50
+ const argument = args[index];
51
+ if (argument === "--manual-completion" || argument === "--compensation") {
52
+ const key = argument.slice(2);
53
+ if (key === "manual-completion") {
54
+ if (result.manualCompletion)
55
+ result.errors.push(`${argument} was specified more than once`);
56
+ result.manualCompletion = true;
57
+ }
58
+ else {
59
+ if (result.compensation)
60
+ result.errors.push(`${argument} was specified more than once`);
61
+ result.compensation = true;
62
+ }
63
+ continue;
64
+ }
65
+ if (!argument.startsWith("--")) {
66
+ result.errors.push(`unexpected argument '${argument}'`);
67
+ continue;
68
+ }
69
+ const value = args[index + 1];
70
+ if (value === undefined || value.startsWith("--")) {
71
+ result.errors.push(`${argument} requires a value`);
72
+ continue;
73
+ }
74
+ if (values.has(argument))
75
+ result.errors.push(`${argument} was specified more than once`);
76
+ values.set(argument, value);
77
+ index += 1;
78
+ }
79
+ const allowed = new Set([
80
+ "--run-id",
81
+ "--node-id",
82
+ "--to",
83
+ "--reason-file",
84
+ "--evidence-ref",
85
+ "--stop-confirmed-by",
86
+ "--stop-evidence-ref",
87
+ "--approved-by",
88
+ ]);
89
+ for (const key of values.keys()) {
90
+ if (!allowed.has(key))
91
+ result.errors.push(`unknown option '${key}'`);
92
+ }
93
+ for (const key of [
94
+ "--run-id",
95
+ "--node-id",
96
+ "--to",
97
+ "--reason-file",
98
+ "--evidence-ref",
99
+ "--stop-confirmed-by",
100
+ "--stop-evidence-ref",
101
+ ]) {
102
+ if (!values.has(key))
103
+ result.errors.push(`${key} is required`);
104
+ }
105
+ const outcome = values.get("--to");
106
+ if (outcome !== undefined &&
107
+ outcome !== "SUCCESS" &&
108
+ outcome !== "FAILED" &&
109
+ outcome !== "CANCELLED") {
110
+ result.errors.push("--to must be SUCCESS, FAILED, or CANCELLED");
111
+ }
112
+ if (result.manualCompletion && result.compensation)
113
+ result.errors.push("--manual-completion and --compensation are mutually exclusive");
114
+ return {
115
+ ...result,
116
+ ...(values.has("--run-id") ? { runId: values.get("--run-id") } : {}),
117
+ ...(values.has("--node-id") ? { nodeId: values.get("--node-id") } : {}),
118
+ ...(outcome === "SUCCESS" || outcome === "FAILED" || outcome === "CANCELLED"
119
+ ? { outcome }
120
+ : {}),
121
+ ...(values.has("--reason-file")
122
+ ? { reasonFile: values.get("--reason-file") }
123
+ : {}),
124
+ ...(values.has("--evidence-ref")
125
+ ? { evidenceRef: values.get("--evidence-ref") }
126
+ : {}),
127
+ ...(values.has("--stop-confirmed-by")
128
+ ? { stopConfirmedBy: values.get("--stop-confirmed-by") }
129
+ : {}),
130
+ ...(values.has("--stop-evidence-ref")
131
+ ? { stopEvidenceRef: values.get("--stop-evidence-ref") }
132
+ : {}),
133
+ ...(values.has("--approved-by")
134
+ ? { approvedBy: values.get("--approved-by") }
135
+ : {}),
136
+ };
137
+ }
138
+ function productionDependencies() {
139
+ return {
140
+ repository: repositoryFromEnvironment(),
141
+ servicePrincipal: requiredEnvironment("KSQL_FLOWNET_SERVICE_PRINCIPAL"),
142
+ requestedBy: requiredEnvironment("KSQL_FLOWNET_REQUESTED_BY"),
143
+ };
144
+ }
145
+ export function repositoryFromEnvironment() {
146
+ return new KintonePersistenceRepository({
147
+ baseUrl: requiredEnvironment("KSQL_FLOWNET_BASE_URL").replace(/\/$/u, ""),
148
+ stateAppId: positiveIntegerEnvironment("KSQL_FLOWNET_STATE_APP_ID"),
149
+ stateApiToken: requiredEnvironment("KSQL_FLOWNET_STATE_API_TOKEN"),
150
+ auditAppId: positiveIntegerEnvironment("KSQL_FLOWNET_AUDIT_APP_ID"),
151
+ auditApiToken: requiredEnvironment("KSQL_FLOWNET_AUDIT_API_TOKEN"),
152
+ });
153
+ }
154
+ export function requiredEnvironment(name) {
155
+ const value = process.env[name];
156
+ if (value === undefined || value.trim() === "")
157
+ throw new Error(`${name} is required`);
158
+ return value;
159
+ }
160
+ function positiveIntegerEnvironment(name) {
161
+ const value = Number(requiredEnvironment(name));
162
+ if (!Number.isSafeInteger(value) || value <= 0)
163
+ throw new Error(`${name} must be a positive integer`);
164
+ return value;
165
+ }
166
+ function errorCode(error, fallback) {
167
+ return typeof error === "object" &&
168
+ error !== null &&
169
+ "code" in error &&
170
+ typeof error.code === "string"
171
+ ? error.code
172
+ : fallback;
173
+ }
174
+ //# sourceMappingURL=resolve-node-command.js.map
@@ -0,0 +1,14 @@
1
+ import { ensureRun, type EnsureRunInput } from "../orchestration/ensure-run.js";
2
+ import { type SequentialSchedulerSummary } from "../orchestration/sequential-scheduler.js";
3
+ export type EnsureRunInvoker = (input: Omit<EnsureRunInput, "repository" | "lockManager" | "executor" | "bundleStore">) => ReturnType<typeof ensureRun>;
4
+ export interface RunNetworkCommandDependencies {
5
+ readonly invoke: EnsureRunInvoker;
6
+ readonly schedule: (result: Awaited<ReturnType<EnsureRunInvoker>>) => Promise<SequentialSchedulerSummary>;
7
+ readonly profile: string;
8
+ readonly requestedBy: string;
9
+ readonly host: string;
10
+ }
11
+ export declare function runRunNetworkCommand(args: readonly string[], dependencies?: RunNetworkCommandDependencies): Promise<number>;
12
+ export declare function resolveOwnerInstanceId(host: string, configured?: string | undefined, pid?: number): string;
13
+ export declare function ksqlFlowBinArgsEnvironment(environment?: NodeJS.ProcessEnv): string[];
14
+ //# sourceMappingURL=run-network-command.d.ts.map