@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,175 @@
1
+ const LOCAL_SIGNATURE = 0x04034b50;
2
+ const CENTRAL_SIGNATURE = 0x02014b50;
3
+ const END_SIGNATURE = 0x06054b50;
4
+ const UTF8_FLAG = 0x0800;
5
+ export class StoreZipError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "StoreZipError";
9
+ }
10
+ }
11
+ /** Store-only ZIP with fixed zero DOS timestamps and caller-defined entry order. */
12
+ export function createStoreZip(entries) {
13
+ if (entries.length === 0) {
14
+ throw new StoreZipError("ZIP requires at least one entry");
15
+ }
16
+ if (entries.length > 0xffff) {
17
+ throw new StoreZipError("ZIP64 entry counts are not supported");
18
+ }
19
+ const localParts = [];
20
+ const centralParts = [];
21
+ const names = new Set();
22
+ let offset = 0;
23
+ for (const entry of entries) {
24
+ if (names.has(entry.name)) {
25
+ throw new StoreZipError(`duplicate ZIP entry '${entry.name}'`);
26
+ }
27
+ names.add(entry.name);
28
+ const name = Buffer.from(entry.name, "utf8");
29
+ const data = Buffer.from(entry.data);
30
+ if (name.length === 0 || name.length > 0xffff || name.includes(0)) {
31
+ throw new StoreZipError("ZIP entry name length is invalid");
32
+ }
33
+ if (data.length > 0xffffffff) {
34
+ throw new StoreZipError("ZIP64 entry sizes are not supported");
35
+ }
36
+ const crc = crc32(data);
37
+ const local = Buffer.alloc(30);
38
+ local.writeUInt32LE(LOCAL_SIGNATURE, 0);
39
+ local.writeUInt16LE(20, 4);
40
+ local.writeUInt16LE(UTF8_FLAG, 6);
41
+ local.writeUInt16LE(0, 8);
42
+ local.writeUInt32LE(crc, 14);
43
+ local.writeUInt32LE(data.length, 18);
44
+ local.writeUInt32LE(data.length, 22);
45
+ local.writeUInt16LE(name.length, 26);
46
+ localParts.push(local, name, data);
47
+ const central = Buffer.alloc(46);
48
+ central.writeUInt32LE(CENTRAL_SIGNATURE, 0);
49
+ central.writeUInt16LE(20, 4);
50
+ central.writeUInt16LE(20, 6);
51
+ central.writeUInt16LE(UTF8_FLAG, 8);
52
+ central.writeUInt16LE(0, 10);
53
+ central.writeUInt32LE(crc, 16);
54
+ central.writeUInt32LE(data.length, 20);
55
+ central.writeUInt32LE(data.length, 24);
56
+ central.writeUInt16LE(name.length, 28);
57
+ central.writeUInt32LE(offset, 42);
58
+ centralParts.push(central, name);
59
+ offset += local.length + name.length + data.length;
60
+ if (offset > 0xffffffff) {
61
+ throw new StoreZipError("ZIP64 archive sizes are not supported");
62
+ }
63
+ }
64
+ const centralDirectory = Buffer.concat(centralParts);
65
+ const end = Buffer.alloc(22);
66
+ end.writeUInt32LE(END_SIGNATURE, 0);
67
+ end.writeUInt16LE(entries.length, 8);
68
+ end.writeUInt16LE(entries.length, 10);
69
+ end.writeUInt32LE(centralDirectory.length, 12);
70
+ end.writeUInt32LE(offset, 16);
71
+ return Buffer.concat([...localParts, centralDirectory, end]);
72
+ }
73
+ export function readStoreZip(input) {
74
+ const zip = Buffer.from(input);
75
+ if (zip.length < 22 || zip.readUInt32LE(zip.length - 22) !== END_SIGNATURE) {
76
+ throw new StoreZipError("ZIP end of central directory was not found");
77
+ }
78
+ const endOffset = zip.length - 22;
79
+ const diskNumber = zip.readUInt16LE(endOffset + 4);
80
+ const centralDisk = zip.readUInt16LE(endOffset + 6);
81
+ const diskCount = zip.readUInt16LE(endOffset + 8);
82
+ const count = zip.readUInt16LE(endOffset + 10);
83
+ const centralSize = zip.readUInt32LE(endOffset + 12);
84
+ const centralOffset = zip.readUInt32LE(endOffset + 16);
85
+ const commentLength = zip.readUInt16LE(endOffset + 20);
86
+ if (diskNumber !== 0 ||
87
+ centralDisk !== 0 ||
88
+ diskCount !== count ||
89
+ commentLength !== 0 ||
90
+ centralOffset + centralSize !== endOffset) {
91
+ throw new StoreZipError("unsupported or malformed ZIP directory");
92
+ }
93
+ const entries = [];
94
+ const names = new Set();
95
+ let cursor = centralOffset;
96
+ for (let index = 0; index < count; index += 1) {
97
+ assertRange(zip, cursor, 46, "ZIP central directory");
98
+ if (zip.readUInt32LE(cursor) !== CENTRAL_SIGNATURE) {
99
+ throw new StoreZipError("ZIP central directory signature is invalid");
100
+ }
101
+ const flags = zip.readUInt16LE(cursor + 8);
102
+ const method = zip.readUInt16LE(cursor + 10);
103
+ const expectedCrc = zip.readUInt32LE(cursor + 16);
104
+ const compressedSize = zip.readUInt32LE(cursor + 20);
105
+ const size = zip.readUInt32LE(cursor + 24);
106
+ const nameLength = zip.readUInt16LE(cursor + 28);
107
+ const extraLength = zip.readUInt16LE(cursor + 30);
108
+ const entryCommentLength = zip.readUInt16LE(cursor + 32);
109
+ const localOffset = zip.readUInt32LE(cursor + 42);
110
+ const centralEntryLength = 46 + nameLength + extraLength + entryCommentLength;
111
+ assertRange(zip, cursor, centralEntryLength, "ZIP central entry");
112
+ const nameBytes = zip.subarray(cursor + 46, cursor + 46 + nameLength);
113
+ const name = nameBytes.toString("utf8");
114
+ if (method !== 0 || compressedSize !== size || flags !== UTF8_FLAG) {
115
+ throw new StoreZipError("only deterministic UTF-8 store-only ZIP is supported");
116
+ }
117
+ if (names.has(name))
118
+ throw new StoreZipError(`duplicate ZIP entry '${name}'`);
119
+ names.add(name);
120
+ assertRange(zip, localOffset, 30, "ZIP local header");
121
+ if (zip.readUInt32LE(localOffset) !== LOCAL_SIGNATURE) {
122
+ throw new StoreZipError("ZIP local header signature is invalid");
123
+ }
124
+ const localFlags = zip.readUInt16LE(localOffset + 6);
125
+ const localMethod = zip.readUInt16LE(localOffset + 8);
126
+ const localCrc = zip.readUInt32LE(localOffset + 14);
127
+ const localCompressedSize = zip.readUInt32LE(localOffset + 18);
128
+ const localSize = zip.readUInt32LE(localOffset + 22);
129
+ const localNameLength = zip.readUInt16LE(localOffset + 26);
130
+ const localExtraLength = zip.readUInt16LE(localOffset + 28);
131
+ const localNameOffset = localOffset + 30;
132
+ const dataOffset = localNameOffset + localNameLength + localExtraLength;
133
+ assertRange(zip, localNameOffset, localNameLength, "ZIP local entry name");
134
+ assertRange(zip, dataOffset, size, "ZIP entry data");
135
+ const localName = zip.subarray(localNameOffset, localNameOffset + localNameLength);
136
+ if (!localName.equals(nameBytes) ||
137
+ localFlags !== flags ||
138
+ localMethod !== method ||
139
+ localCrc !== expectedCrc ||
140
+ localCompressedSize !== compressedSize ||
141
+ localSize !== size) {
142
+ throw new StoreZipError(`ZIP entry '${name}' headers do not agree`);
143
+ }
144
+ const data = zip.subarray(dataOffset, dataOffset + size);
145
+ if (crc32(data) !== expectedCrc) {
146
+ throw new StoreZipError(`ZIP entry '${name}' CRC does not match`);
147
+ }
148
+ entries.push({ name, data: Buffer.from(data), crc32: expectedCrc });
149
+ cursor += centralEntryLength;
150
+ }
151
+ if (cursor !== endOffset) {
152
+ throw new StoreZipError("ZIP central directory length does not match");
153
+ }
154
+ return entries;
155
+ }
156
+ export function crc32(input) {
157
+ let crc = 0xffffffff;
158
+ for (const byte of input) {
159
+ crc ^= byte;
160
+ for (let bit = 0; bit < 8; bit += 1) {
161
+ crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
162
+ }
163
+ }
164
+ return (crc ^ 0xffffffff) >>> 0;
165
+ }
166
+ function assertRange(buffer, offset, length, label) {
167
+ if (!Number.isSafeInteger(offset) ||
168
+ !Number.isSafeInteger(length) ||
169
+ offset < 0 ||
170
+ length < 0 ||
171
+ offset + length > buffer.length) {
172
+ throw new StoreZipError(`${label} is out of bounds`);
173
+ }
174
+ }
175
+ //# sourceMappingURL=zip-store.js.map
@@ -0,0 +1,12 @@
1
+ import { type ArchiveRunOutcome } from "../orchestration/archive-run.js";
2
+ export interface ArchiveRunCommandDependencies {
3
+ readonly execute: (input: {
4
+ networkPath: string;
5
+ runId: string;
6
+ reason: string;
7
+ profile?: string;
8
+ }) => Promise<ArchiveRunOutcome>;
9
+ readonly readFile?: (path: string) => string;
10
+ }
11
+ export declare function runArchiveRunCommand(args: readonly string[], dependencies?: ArchiveRunCommandDependencies): Promise<number>;
12
+ //# sourceMappingURL=archive-run-command.d.ts.map
@@ -0,0 +1,131 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { hostname } from "node:os";
3
+ import { loadNetworkDefinition } from "../domain/load-network.js";
4
+ import { archiveRun, } from "../orchestration/archive-run.js";
5
+ import { LeaseMonitor, NetworkLockManager, } from "../persistence/network-lock.js";
6
+ import { repositoryFromEnvironment, requiredEnvironment, } from "./resolve-node-command.js";
7
+ export async function runArchiveRunCommand(args, dependencies) {
8
+ const parsed = parseArguments(args);
9
+ if (parsed.errors.length > 0) {
10
+ process.stderr.write(`Invalid archive-run arguments:\n${parsed.errors.map((x) => `- ${x}`).join("\n")}\n`);
11
+ return 1;
12
+ }
13
+ try {
14
+ const reason = (dependencies?.readFile ?? ((path) => readFileSync(path, "utf8")))(parsed.reasonFile);
15
+ const outcome = dependencies === undefined
16
+ ? await productionArchive(parsed.networkPath, parsed.runId, reason, parsed.profile)
17
+ : await dependencies.execute({
18
+ networkPath: parsed.networkPath,
19
+ runId: parsed.runId,
20
+ reason,
21
+ ...(parsed.profile === undefined
22
+ ? {}
23
+ : { profile: parsed.profile }),
24
+ });
25
+ process.stdout.write(`${JSON.stringify(outcome)}\n`);
26
+ // 仕様 §5.3: exit 0 は ARCHIVED+RECORDED+解放済み、または ALREADY_ARCHIVED+解放済みのときだけ。
27
+ const success = (outcome.outcome === "ARCHIVED" &&
28
+ outcome.audit === "RECORDED" &&
29
+ outcome.lock_released) ||
30
+ (outcome.outcome === "ALREADY_ARCHIVED" && outcome.lock_released);
31
+ if (success)
32
+ process.stderr.write(`${outcome.outcome}: ${outcome.run_id} (revision ${outcome.run_revision})\n`);
33
+ else
34
+ process.stderr.write(`Error [${outcomeCode(outcome)}]: archive-run did not complete cleanly\n`);
35
+ return success ? 0 : 1;
36
+ }
37
+ catch (error) {
38
+ const code = typeof error === "object" &&
39
+ error !== null &&
40
+ "code" in error &&
41
+ typeof error.code === "string"
42
+ ? error.code
43
+ : "ARCHIVE_RUN_FAILED";
44
+ process.stderr.write(`Error [${code}]: ${error instanceof Error ? error.message : String(error)}\n`);
45
+ return 1;
46
+ }
47
+ }
48
+ async function productionArchive(networkPath, runId, reason, profileOption) {
49
+ const loaded = loadNetworkDefinition(networkPath);
50
+ if (loaded.definition === undefined || loaded.errors.length > 0)
51
+ throw new Error("network definition is invalid");
52
+ const profile = profileOption ?? requiredEnvironment("KSQL_FLOWNET_PROFILE");
53
+ const baseUrl = requiredEnvironment("KSQL_FLOWNET_BASE_URL").replace(/\/$/u, "");
54
+ const appId = Number(requiredEnvironment("KSQL_FLOWNET_STATE_APP_ID"));
55
+ const token = requiredEnvironment("KSQL_FLOWNET_STATE_API_TOKEN");
56
+ const leaseDuration = loaded.definition.network_lock.lease_duration_sec;
57
+ return archiveRun({
58
+ repository: repositoryFromEnvironment(),
59
+ runId,
60
+ requestedBy: requiredEnvironment("KSQL_FLOWNET_REQUESTED_BY"),
61
+ reason,
62
+ // ポーラー経由(cron 環境)では未設定のことがあるため、ホスト識別へフォールバックする。
63
+ servicePrincipal: process.env.KSQL_FLOWNET_SERVICE_PRINCIPAL?.trim() ||
64
+ process.env.KSQL_FLOWNET_HOST?.trim() ||
65
+ hostname(),
66
+ lockManagerFactory: (owner) => new NetworkLockManager({
67
+ baseUrl,
68
+ appId,
69
+ apiToken: token,
70
+ profile,
71
+ networkId: loaded.definition.network_id,
72
+ ownerInvocationId: owner,
73
+ ownerInstanceId: process.env.KSQL_FLOWNET_HOST ?? hostname(),
74
+ leaseDurationSec: leaseDuration,
75
+ }),
76
+ leaseMonitorFactory: (manager, reference) => new LeaseMonitor(manager, reference, {
77
+ leaseDurationSec: leaseDuration,
78
+ heartbeatIntervalSec: loaded.definition.network_lock.heartbeat_interval_sec,
79
+ }),
80
+ });
81
+ }
82
+ function outcomeCode(value) {
83
+ if ("code" in value)
84
+ return value.code;
85
+ if (value.outcome === "ALREADY_ARCHIVED")
86
+ return "ALREADY_ARCHIVED";
87
+ return value.lock_released
88
+ ? "ARCHIVE_RUN_FAILED"
89
+ : "RUN_ARCHIVED_LOCK_UNRELEASED";
90
+ }
91
+ function parseArguments(args) {
92
+ const result = { errors: [] };
93
+ const seen = new Set();
94
+ for (let index = 0; index < args.length; index += 1) {
95
+ const arg = args[index];
96
+ if (!arg.startsWith("--")) {
97
+ if (result.networkPath !== undefined)
98
+ result.errors.push("exactly one <network.yaml> path is required");
99
+ else
100
+ result.networkPath = arg;
101
+ continue;
102
+ }
103
+ if (!["--run-id", "--reason-file", "--profile"].includes(arg)) {
104
+ result.errors.push(`unknown option '${arg}'`);
105
+ continue;
106
+ }
107
+ const value = args[index + 1];
108
+ if (value === undefined || value.startsWith("--")) {
109
+ result.errors.push(`${arg} requires a value`);
110
+ continue;
111
+ }
112
+ if (seen.has(arg))
113
+ result.errors.push(`${arg} was specified more than once`);
114
+ seen.add(arg);
115
+ index += 1;
116
+ if (arg === "--run-id")
117
+ result.runId = value;
118
+ else if (arg === "--reason-file")
119
+ result.reasonFile = value;
120
+ else
121
+ result.profile = value;
122
+ }
123
+ if (result.networkPath === undefined)
124
+ result.errors.push("<network.yaml> is required");
125
+ if (result.runId === undefined)
126
+ result.errors.push("--run-id is required");
127
+ if (result.reasonFile === undefined)
128
+ result.errors.push("--reason-file is required");
129
+ return result;
130
+ }
131
+ //# sourceMappingURL=archive-run-command.js.map
@@ -0,0 +1,9 @@
1
+ import type { PersistenceRepository } from "../persistence/repository.js";
2
+ export interface CancelRunCommandDependencies {
3
+ readonly repository: PersistenceRepository;
4
+ readonly requestedBy: string;
5
+ readonly readFile?: (path: string) => string;
6
+ readonly now?: () => Date;
7
+ }
8
+ export declare function runCancelRunCommand(args: readonly string[], dependencies?: CancelRunCommandDependencies): Promise<number>;
9
+ //# sourceMappingURL=cancel-run-command.d.ts.map
@@ -0,0 +1,77 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { changeCancelRequest } from "../orchestration/cancel-request.js";
3
+ import { repositoryFromEnvironment, requiredEnvironment, } from "./resolve-node-command.js";
4
+ export async function runCancelRunCommand(args, dependencies) {
5
+ const parsed = parseArguments(args);
6
+ if (parsed.errors.length > 0) {
7
+ process.stderr.write(`Invalid cancel-run arguments:\n${parsed.errors.map((error) => `- ${error}`).join("\n")}\n`);
8
+ return 1;
9
+ }
10
+ try {
11
+ const runtime = dependencies ?? {
12
+ repository: repositoryFromEnvironment(),
13
+ requestedBy: requiredEnvironment("KSQL_FLOWNET_REQUESTED_BY"),
14
+ };
15
+ const reason = (runtime.readFile ?? ((path) => readFileSync(path, "utf8")))(parsed.reasonFile);
16
+ const result = await changeCancelRequest({
17
+ repository: runtime.repository,
18
+ runId: parsed.runId,
19
+ requestedBy: runtime.requestedBy,
20
+ reason,
21
+ release: parsed.release,
22
+ ...(runtime.now === undefined ? {} : { now: runtime.now }),
23
+ });
24
+ const state = result?.value.state ?? "RELEASED";
25
+ const revision = result === null ? "none" : String(result.revision);
26
+ process.stdout.write(`${state}: CANCEL:${parsed.runId} (revision ${revision}).\n`);
27
+ return 0;
28
+ }
29
+ catch (error) {
30
+ const code = typeof error === "object" &&
31
+ error !== null &&
32
+ "code" in error &&
33
+ typeof error.code === "string"
34
+ ? error.code
35
+ : "CANCEL_RUN_FAILED";
36
+ const message = error instanceof Error ? error.message : String(error);
37
+ process.stderr.write(`Error [${code}]: ${message}\n`);
38
+ return 1;
39
+ }
40
+ }
41
+ function parseArguments(args) {
42
+ const result = { release: false, errors: [] };
43
+ const values = new Map();
44
+ for (let index = 0; index < args.length; index += 1) {
45
+ const argument = args[index];
46
+ if (argument === "--release") {
47
+ if (result.release)
48
+ result.errors.push("--release was specified more than once");
49
+ result.release = true;
50
+ continue;
51
+ }
52
+ if (argument !== "--run-id" && argument !== "--reason-file") {
53
+ result.errors.push(`unknown option '${argument}'`);
54
+ continue;
55
+ }
56
+ const value = args[index + 1];
57
+ if (value === undefined || value.startsWith("--")) {
58
+ result.errors.push(`${argument} requires a value`);
59
+ continue;
60
+ }
61
+ if (values.has(argument))
62
+ result.errors.push(`${argument} was specified more than once`);
63
+ values.set(argument, value);
64
+ index += 1;
65
+ }
66
+ for (const option of ["--run-id", "--reason-file"])
67
+ if (!values.has(option))
68
+ result.errors.push(`${option} is required`);
69
+ return {
70
+ ...result,
71
+ ...(values.has("--run-id") ? { runId: values.get("--run-id") } : {}),
72
+ ...(values.has("--reason-file")
73
+ ? { reasonFile: values.get("--reason-file") }
74
+ : {}),
75
+ };
76
+ }
77
+ //# sourceMappingURL=cancel-run-command.js.map
@@ -0,0 +1,8 @@
1
+ import { type NetworkLockRecoveryDependencies } from "../persistence/network-lock-recovery.js";
2
+ export interface ForceUnlockNetworkCommandDependencies extends NetworkLockRecoveryDependencies {
3
+ readonly servicePrincipal: string;
4
+ readonly requestedBy: string;
5
+ readonly readFile?: (path: string) => string;
6
+ }
7
+ export declare function runForceUnlockNetworkCommand(args: readonly string[], dependencies?: ForceUnlockNetworkCommandDependencies): Promise<number>;
8
+ //# sourceMappingURL=force-unlock-network-command.d.ts.map
@@ -0,0 +1,129 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { forceUnlockNetwork, } from "../persistence/network-lock-recovery.js";
3
+ import { repositoryFromEnvironment, requiredEnvironment, } from "./resolve-node-command.js";
4
+ export async function runForceUnlockNetworkCommand(args, dependencies) {
5
+ const parsed = parseArguments(args);
6
+ if (parsed.errors.length > 0) {
7
+ process.stderr.write(`Invalid force-unlock-network 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 forceUnlockNetwork({
14
+ networkId: parsed.networkId,
15
+ profile: parsed.profile,
16
+ expectedOwnerInvocationId: parsed.expectedOwnerInvocationId,
17
+ reason,
18
+ evidenceRef: parsed.evidenceRef,
19
+ stopConfirmedBy: parsed.stopConfirmedBy,
20
+ stopEvidenceRef: parsed.stopEvidenceRef,
21
+ stopMethod: parsed.stopMethod,
22
+ servicePrincipal: runtime.servicePrincipal,
23
+ requestedBy: runtime.requestedBy,
24
+ }, runtime);
25
+ process.stdout.write(`RELEASED: ${result.lockKey} (previous owner ${result.previousOwnerInvocationId}, post revision ${result.postReleaseRevision}).\n`);
26
+ process.stdout.write("If a Node was running, use resolve-node to resolve it as UNKNOWN before resume.\n");
27
+ return 0;
28
+ }
29
+ catch (error) {
30
+ const code = errorCode(error, "FORCE_UNLOCK_NETWORK_FAILED");
31
+ const message = error instanceof Error ? error.message : String(error);
32
+ process.stderr.write(`Error [${code}]: ${message}\n`);
33
+ return 1;
34
+ }
35
+ }
36
+ function parseArguments(args) {
37
+ const errors = [];
38
+ const positional = [];
39
+ const values = new Map();
40
+ const allowed = new Set([
41
+ "--profile",
42
+ "--expected-owner-invocation-id",
43
+ "--reason-file",
44
+ "--evidence-ref",
45
+ "--stop-confirmed-by",
46
+ "--stop-evidence-ref",
47
+ "--stop-method",
48
+ ]);
49
+ for (let index = 0; index < args.length; index += 1) {
50
+ const argument = args[index];
51
+ if (!argument.startsWith("--")) {
52
+ positional.push(argument);
53
+ continue;
54
+ }
55
+ const value = args[index + 1];
56
+ if (value === undefined || value.startsWith("--")) {
57
+ errors.push(`${argument} requires a value`);
58
+ continue;
59
+ }
60
+ if (!allowed.has(argument))
61
+ errors.push(`unknown option '${argument}'`);
62
+ if (values.has(argument))
63
+ errors.push(`${argument} was specified more than once`);
64
+ values.set(argument, value);
65
+ index += 1;
66
+ }
67
+ if (positional.length === 0)
68
+ errors.push("<network_id> is required");
69
+ if (positional.length > 1) {
70
+ for (const argument of positional.slice(1))
71
+ errors.push(`unexpected argument '${argument}'`);
72
+ }
73
+ for (const option of allowed) {
74
+ if (!values.has(option))
75
+ errors.push(`${option} is required`);
76
+ }
77
+ return {
78
+ ...(positional[0] === undefined ? {} : { networkId: positional[0] }),
79
+ ...(values.get("--profile") === undefined
80
+ ? {}
81
+ : { profile: values.get("--profile") }),
82
+ ...(values.get("--expected-owner-invocation-id") === undefined
83
+ ? {}
84
+ : {
85
+ expectedOwnerInvocationId: values.get("--expected-owner-invocation-id"),
86
+ }),
87
+ ...(values.get("--reason-file") === undefined
88
+ ? {}
89
+ : { reasonFile: values.get("--reason-file") }),
90
+ ...(values.get("--evidence-ref") === undefined
91
+ ? {}
92
+ : { evidenceRef: values.get("--evidence-ref") }),
93
+ ...(values.get("--stop-confirmed-by") === undefined
94
+ ? {}
95
+ : { stopConfirmedBy: values.get("--stop-confirmed-by") }),
96
+ ...(values.get("--stop-evidence-ref") === undefined
97
+ ? {}
98
+ : { stopEvidenceRef: values.get("--stop-evidence-ref") }),
99
+ ...(values.get("--stop-method") === undefined
100
+ ? {}
101
+ : { stopMethod: values.get("--stop-method") }),
102
+ errors,
103
+ };
104
+ }
105
+ function productionDependencies() {
106
+ return {
107
+ baseUrl: requiredEnvironment("KSQL_FLOWNET_BASE_URL").replace(/\/$/u, ""),
108
+ stateAppId: positiveIntegerEnvironment("KSQL_FLOWNET_STATE_APP_ID"),
109
+ stateApiToken: requiredEnvironment("KSQL_FLOWNET_STATE_API_TOKEN"),
110
+ repository: repositoryFromEnvironment(),
111
+ servicePrincipal: requiredEnvironment("KSQL_FLOWNET_SERVICE_PRINCIPAL"),
112
+ requestedBy: requiredEnvironment("KSQL_FLOWNET_REQUESTED_BY"),
113
+ };
114
+ }
115
+ function positiveIntegerEnvironment(name) {
116
+ const value = Number(requiredEnvironment(name));
117
+ if (!Number.isSafeInteger(value) || value <= 0)
118
+ throw new Error(`${name} must be a positive integer`);
119
+ return value;
120
+ }
121
+ function errorCode(error, fallback) {
122
+ return typeof error === "object" &&
123
+ error !== null &&
124
+ "code" in error &&
125
+ typeof error.code === "string"
126
+ ? error.code
127
+ : fallback;
128
+ }
129
+ //# sourceMappingURL=force-unlock-network-command.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map