@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,198 @@
1
+ import type { CanonicalRecordKey } from "./canonical-record-key.js";
2
+ export type NodeStateStatus = "WAITING" | "RUNNING" | "SUCCESS" | "FAILED" | "BLOCKED" | "SKIPPED" | "CANCELLED" | "UNKNOWN";
3
+ export type NodeAttemptStatus = "RUNNING" | "SUCCESS" | "FAILED" | "CANCELLED" | "UNKNOWN";
4
+ export type NetworkRunStatus = "CREATED" | "RUNNING" | "SUCCESS" | "FAILED" | "CANCELLED" | "UNKNOWN";
5
+ export type RunInvocationStatus = "RUNNING" | "SUCCESS" | "FAILED" | "CANCELLED" | "UNKNOWN";
6
+ export type CancelRequestState = "REQUESTED" | "ACCEPTED" | "RELEASED";
7
+ export interface CancelRequest {
8
+ run_id: string;
9
+ state: CancelRequestState;
10
+ requested_by: string;
11
+ reason: string;
12
+ requested_at: string;
13
+ accepted_at: string | null;
14
+ released_at: string | null;
15
+ release_reason: string | null;
16
+ release_requested_by: string | null;
17
+ }
18
+ export interface ResolvedProfileSnapshot {
19
+ profile: string;
20
+ base_url: string;
21
+ guest_space_id: number | null;
22
+ timezone: string;
23
+ apps: Record<string, number>;
24
+ limits: {
25
+ max_api_calls: number | null;
26
+ max_read_rows: number | null;
27
+ batch_timeout_sec: number | null;
28
+ };
29
+ }
30
+ export interface NetworkRun {
31
+ run_id: string;
32
+ network_id: string;
33
+ business_key: string;
34
+ max_active_runs: number;
35
+ status: NetworkRunStatus;
36
+ lifecycle_status: "ACTIVE" | "ARCHIVED";
37
+ resume_allowed: boolean;
38
+ as_of: string | null;
39
+ definition_schema_version: number;
40
+ definition_sha256: string;
41
+ source_bundle_sha256: string;
42
+ source_bundle_attachment: string;
43
+ resolved_profile_snapshot: ResolvedProfileSnapshot;
44
+ resolved_profile_sha256: string;
45
+ ksql_flow_version: string;
46
+ engine_version: string;
47
+ dialect: number;
48
+ created_at: string;
49
+ started_at: string | null;
50
+ finished_at: string | null;
51
+ updated_at: string;
52
+ }
53
+ export interface RunInvocation {
54
+ invocation_id: string;
55
+ run_id: string;
56
+ mode: "NEW" | "RESUME" | "RERUN_FROM";
57
+ requested_by: string;
58
+ host: string;
59
+ started_at: string;
60
+ finished_at: string | null;
61
+ status: RunInvocationStatus;
62
+ result_code: string;
63
+ selected_node_ids: string[];
64
+ preserved_node_ids: string[];
65
+ blocked_node_ids: string[];
66
+ reason: string;
67
+ }
68
+ export interface NodeState {
69
+ node_state_id: string;
70
+ node_state_key: CanonicalRecordKey<"S1">;
71
+ run_id: string;
72
+ node_id: string;
73
+ job_id: string;
74
+ status: NodeStateStatus;
75
+ latest_attempt_no: number;
76
+ active_attempt_id: string | null;
77
+ revision: number;
78
+ idempotent: boolean;
79
+ trigger_rule: "all_success";
80
+ blocked_by: string[];
81
+ status_reason: string | null;
82
+ started_at: string | null;
83
+ finished_at: string | null;
84
+ updated_at: string;
85
+ }
86
+ export interface NodeAttempt {
87
+ node_attempt_id: string;
88
+ attempt_key: CanonicalRecordKey<"A1">;
89
+ run_id: string;
90
+ node_id: string;
91
+ job_id: string;
92
+ invocation_id: string;
93
+ attempt_no: number;
94
+ status: NodeAttemptStatus;
95
+ result_code: string;
96
+ execution_started_at: string | null;
97
+ runner_execution_started_at: string | null;
98
+ execution_id: string | null;
99
+ finished_at: string | null;
100
+ duration_sec: number | null;
101
+ error_message: string | null;
102
+ read_count: number;
103
+ written_count: number;
104
+ last_successful_chunk_no: number | null;
105
+ last_written_key: string | null;
106
+ /** D-09 start-protocol evidence. */
107
+ state_revision_before: number | null;
108
+ }
109
+ export interface AttemptResolution {
110
+ event_type: "ATTEMPT_RESOLVED";
111
+ resolution_type: "OUTCOME_CONFIRMED" | "NODE_MANUAL_COMPLETION_CONFIRMED" | "NODE_COMPENSATION_COMPLETED";
112
+ attempt_id: string;
113
+ resolved_outcome: "SUCCESS" | "FAILED" | "CANCELLED";
114
+ reason: string;
115
+ evidence_ref: string;
116
+ service_principal: string;
117
+ requested_by: string;
118
+ approved_by: string;
119
+ stop_confirmed_by: string;
120
+ stop_evidence_ref: string;
121
+ resolved_at: string;
122
+ }
123
+ export interface ReconciliationOperationAudit {
124
+ event_id: string;
125
+ event_type: "RECONCILIATION_REPAIR";
126
+ repair_type: "TERMINAL_ATTEMPT_APPLIED" | "ATTEMPT_RESOLUTION_APPLIED" | "RUN_AGGREGATE_RECOMPUTED" | "INVOCATION_FINALIZED";
127
+ run_id: string;
128
+ target_type: "NODE_STATE" | "NETWORK_RUN" | "RUN_INVOCATION";
129
+ target_id: string;
130
+ before: Record<string, unknown>;
131
+ after: Record<string, unknown>;
132
+ basis: string;
133
+ occurred_at: string;
134
+ }
135
+ export type LockRecoveryOutcome = "RELEASED" | "NOT_FOUND" | "NOT_RUNNING" | "CONFLICT" | "UNCONFIRMED";
136
+ export interface LockRecoveryResult {
137
+ kind: "LOCK_RECOVERY_RESULT";
138
+ formatVersion: 1;
139
+ jobKey: string;
140
+ recordId: string | null;
141
+ outcome: LockRecoveryOutcome;
142
+ before: {
143
+ batchId: string;
144
+ startedAt: string;
145
+ host: string;
146
+ } | null;
147
+ executedAt: string;
148
+ nextAction?: string;
149
+ }
150
+ export interface JobLockRecoveryOperationAudit {
151
+ event_id: string;
152
+ event_type: "JOB_LOCK_FORCE_UNLOCK_RECORDED";
153
+ run_id: string;
154
+ node_id: string;
155
+ job_id: string;
156
+ service_principal: string;
157
+ requested_by: string;
158
+ stop_confirmed_by: string;
159
+ reason: string;
160
+ evidence_ref: string;
161
+ recorded_at: string;
162
+ lock_recovery_result: LockRecoveryResult;
163
+ }
164
+ export interface NetworkLockForceReleaseOperationAudit {
165
+ event_id: string;
166
+ event_type: "NETWORK_LOCK_FORCE_RELEASED";
167
+ network_id: string;
168
+ profile: string;
169
+ lock_key: string;
170
+ record_id: string;
171
+ previous_owner_invocation_id: string;
172
+ previous_lease_token: string;
173
+ previous_heartbeat_at: string;
174
+ previous_lease_expires_at: string;
175
+ service_principal: string;
176
+ requested_by: string;
177
+ stop_confirmed_by: string;
178
+ stop_method: string;
179
+ stop_evidence_ref: string;
180
+ reason: string;
181
+ evidence_ref: string;
182
+ released_at: string;
183
+ post_release_revision: number;
184
+ }
185
+ export interface RunArchivedOperationAudit {
186
+ event_id: `archive_${string}`;
187
+ event_type: "RUN_ARCHIVED";
188
+ run_id: string;
189
+ result_code: "RUN_ARCHIVED";
190
+ requested_by: string;
191
+ reason: string;
192
+ archived_at: string;
193
+ previous_status: "FAILED" | "CANCELLED";
194
+ run_revision_before: number;
195
+ service_principal: string;
196
+ }
197
+ export type OperationAudit = ReconciliationOperationAudit | JobLockRecoveryOperationAudit | NetworkLockForceReleaseOperationAudit | RunArchivedOperationAudit;
198
+ //# sourceMappingURL=persistence-model.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=persistence-model.js.map
@@ -0,0 +1,3 @@
1
+ import type { NetworkRunStatus, NodeStateStatus } from "./persistence-model.js";
2
+ export declare function computeRunAggregateStatus(nodeStates: readonly NodeStateStatus[], startedAt: string | null): NetworkRunStatus;
3
+ //# sourceMappingURL=run-aggregate.d.ts.map
@@ -0,0 +1,15 @@
1
+ export function computeRunAggregateStatus(nodeStates, startedAt) {
2
+ if (nodeStates.includes("UNKNOWN"))
3
+ return "UNKNOWN";
4
+ if (nodeStates.includes("RUNNING"))
5
+ return "RUNNING";
6
+ if (nodeStates.some((status) => status === "FAILED" || status === "BLOCKED"))
7
+ return "FAILED";
8
+ if (nodeStates.includes("CANCELLED"))
9
+ return "CANCELLED";
10
+ if (nodeStates.length > 0 &&
11
+ nodeStates.every((status) => status === "SUCCESS"))
12
+ return "SUCCESS";
13
+ return startedAt === null ? "CREATED" : "RUNNING";
14
+ }
15
+ //# sourceMappingURL=run-aggregate.js.map
@@ -0,0 +1,11 @@
1
+ import type { NetworkDefinition, ValidationError } from "./network-definition.js";
2
+ export interface NetworkPathValidationResult {
3
+ readonly definition?: NetworkDefinition;
4
+ readonly errors: readonly ValidationError[];
5
+ }
6
+ export declare function validateSqlFiles(networkPath: string, nodes: readonly {
7
+ readonly id: string;
8
+ readonly sql: string;
9
+ }[]): ValidationError[];
10
+ export declare function validateNetworkPath(networkPath: string): NetworkPathValidationResult;
11
+ //# sourceMappingURL=validate-network-path.d.ts.map
@@ -0,0 +1,33 @@
1
+ import { accessSync, constants, statSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { loadNetworkDefinition } from "./load-network.js";
4
+ export function validateSqlFiles(networkPath, nodes) {
5
+ const errors = [];
6
+ for (const [index, node] of nodes.entries()) {
7
+ const sqlPath = resolve(dirname(networkPath), node.sql);
8
+ try {
9
+ accessSync(sqlPath, constants.R_OK);
10
+ if (!statSync(sqlPath).isFile())
11
+ throw new Error("path is not a file");
12
+ }
13
+ catch (error) {
14
+ errors.push({
15
+ code: "SQL_FILE_UNREADABLE",
16
+ path: `$.nodes/${index}/sql`,
17
+ message: `SQL file for node '${node.id}' is not readable: ${sqlPath} (${String(error)})`,
18
+ });
19
+ }
20
+ }
21
+ return errors;
22
+ }
23
+ export function validateNetworkPath(networkPath) {
24
+ const loaded = loadNetworkDefinition(networkPath);
25
+ const errors = [...loaded.errors];
26
+ if (loaded.definition !== undefined) {
27
+ errors.push(...validateSqlFiles(networkPath, loaded.definition.nodes));
28
+ }
29
+ return loaded.definition === undefined
30
+ ? { errors }
31
+ : { definition: loaded.definition, errors };
32
+ }
33
+ //# sourceMappingURL=validate-network-path.js.map
@@ -0,0 +1,3 @@
1
+ import type { ValidationResult } from "./network-definition.js";
2
+ export declare function validateNetworkDefinition(input: unknown): ValidationResult;
3
+ //# sourceMappingURL=validate-network.d.ts.map
@@ -0,0 +1,180 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ import { Ajv2020 } from "ajv/dist/2020.js";
4
+ import { stableTopologicalSort } from "../dag/topological-sort.js";
5
+ import { validateScheduledPeriodPolicy } from "./business-key.js";
6
+ const schemaPath = fileURLToPath(new URL("../../schemas/network-definition.schema.json", import.meta.url));
7
+ const schema = JSON.parse(readFileSync(schemaPath, "utf8"));
8
+ const ajv = new Ajv2020({ allErrors: true, strict: true });
9
+ const validateSchema = ajv.compile(schema);
10
+ function schemaError(error) {
11
+ const path = error.instancePath === "" ? "$" : `$${error.instancePath}`;
12
+ if (error.keyword === "additionalProperties") {
13
+ const property = String(error.params.additionalProperty);
14
+ return {
15
+ code: "UNKNOWN_PROPERTY",
16
+ path,
17
+ message: `unknown property '${property}'`,
18
+ };
19
+ }
20
+ return {
21
+ code: "SCHEMA_INVALID",
22
+ path,
23
+ message: error.message ?? "is invalid",
24
+ };
25
+ }
26
+ function semanticErrors(definition) {
27
+ const errors = [];
28
+ const nodeIds = new Set();
29
+ if (definition.business_key_policy.type === "scheduled_period") {
30
+ errors.push(...validateScheduledPeriodPolicy(definition.business_key_policy));
31
+ }
32
+ const { lease_duration_sec: lease, heartbeat_interval_sec: heartbeat } = definition.network_lock;
33
+ if (heartbeat >= lease) {
34
+ errors.push({
35
+ code: "NETWORK_LOCK_HEARTBEAT_NOT_LESS_THAN_LEASE",
36
+ path: "$.network_lock.heartbeat_interval_sec",
37
+ message: "must be less than lease_duration_sec",
38
+ });
39
+ }
40
+ if (heartbeat > lease / 3) {
41
+ errors.push({
42
+ code: "NETWORK_LOCK_HEARTBEAT_EXCEEDS_ONE_THIRD",
43
+ path: "$.network_lock.heartbeat_interval_sec",
44
+ message: "must be at most lease_duration_sec / 3",
45
+ });
46
+ }
47
+ definition.nodes.forEach((node, nodeIndex) => {
48
+ const nodePath = `$.nodes/${nodeIndex}`;
49
+ if (nodeIds.has(node.id)) {
50
+ errors.push({
51
+ code: "DUPLICATE_NODE_ID",
52
+ path: `${nodePath}/id`,
53
+ message: `duplicate node id '${node.id}'`,
54
+ });
55
+ }
56
+ nodeIds.add(node.id);
57
+ for (const [sourceName, pattern] of Object.entries(node.inputs ?? {})) {
58
+ const problem = inputPatternProblem(pattern);
59
+ if (problem !== null) {
60
+ errors.push({
61
+ code: "INPUT_PATTERN_INVALID",
62
+ path: `${nodePath}/inputs/${escapeJsonPointer(sourceName)}`,
63
+ message: problem,
64
+ });
65
+ }
66
+ }
67
+ for (const [sinkName, pattern] of Object.entries(node.outputs ?? {})) {
68
+ const problem = outputPatternProblem(pattern);
69
+ if (problem !== null) {
70
+ errors.push({
71
+ code: "OUTPUT_PATTERN_INVALID",
72
+ path: `${nodePath}/outputs/${escapeJsonPointer(sinkName)}`,
73
+ message: problem,
74
+ });
75
+ }
76
+ }
77
+ if (node.trigger_rule !== "all_success") {
78
+ errors.push({
79
+ code: "TRIGGER_RULE_UNSUPPORTED",
80
+ path: `${nodePath}/trigger_rule`,
81
+ message: `'${node.trigger_rule}' is reserved and not supported in Phase 1`,
82
+ });
83
+ }
84
+ const dependencies = new Set();
85
+ node.depends_on.forEach((dependency, dependencyIndex) => {
86
+ const dependencyPath = `${nodePath}/depends_on/${dependencyIndex}`;
87
+ if (dependency === node.id) {
88
+ errors.push({
89
+ code: "SELF_DEPENDENCY",
90
+ path: dependencyPath,
91
+ message: "self-dependency is not allowed",
92
+ });
93
+ }
94
+ if (dependencies.has(dependency)) {
95
+ errors.push({
96
+ code: "DUPLICATE_DEPENDENCY",
97
+ path: dependencyPath,
98
+ message: `duplicate dependency '${dependency}'`,
99
+ });
100
+ }
101
+ dependencies.add(dependency);
102
+ });
103
+ });
104
+ definition.nodes.forEach((node, nodeIndex) => {
105
+ node.depends_on.forEach((dependency, dependencyIndex) => {
106
+ if (!nodeIds.has(dependency)) {
107
+ errors.push({
108
+ code: "UNKNOWN_DEPENDENCY",
109
+ path: `$.nodes/${nodeIndex}/depends_on/${dependencyIndex}`,
110
+ message: `unknown dependency '${dependency}'`,
111
+ });
112
+ }
113
+ });
114
+ });
115
+ if (errors.some((error) => error.code === "DUPLICATE_NODE_ID")) {
116
+ return errors;
117
+ }
118
+ if (errors.some((error) => error.code === "UNKNOWN_DEPENDENCY")) {
119
+ return errors;
120
+ }
121
+ const sorted = stableTopologicalSort(definition.nodes);
122
+ if (sorted.cyclicNodeIds.length > 0) {
123
+ errors.push({
124
+ code: "CYCLE_DETECTED",
125
+ path: "$.nodes",
126
+ message: `cycle detected; nodes not sortable: ${sorted.cyclicNodeIds.join(", ")}`,
127
+ });
128
+ }
129
+ return errors;
130
+ }
131
+ const INPUT_PLACEHOLDERS = new Set(["business_key", "profile"]);
132
+ const OUTPUT_PLACEHOLDERS = new Set([
133
+ "business_key",
134
+ "profile",
135
+ "run_id",
136
+ "node_id",
137
+ ]);
138
+ function inputPatternProblem(pattern) {
139
+ const literal = pattern.replace(/\{([^{}]*)\}/gu, (_match, name) => INPUT_PLACEHOLDERS.has(name) ? "placeholder" : `{${name}}`);
140
+ return relativePatternProblem(pattern, literal, "input");
141
+ }
142
+ function outputPatternProblem(pattern) {
143
+ const literal = pattern.replace(/\{([^{}]*)\}/gu, (_match, name) => OUTPUT_PLACEHOLDERS.has(name) ? "placeholder" : `{${name}}`);
144
+ return relativePatternProblem(pattern, literal, "output");
145
+ }
146
+ function relativePatternProblem(pattern, literal, context) {
147
+ if (pattern.length === 0)
148
+ return `${context} pattern must not be empty`;
149
+ if (pattern.includes("\0"))
150
+ return `${context} pattern must not contain NUL`;
151
+ if (pattern.startsWith("/") ||
152
+ pattern.startsWith("\\") ||
153
+ /^[A-Za-z]:/u.test(pattern))
154
+ return `${context} pattern must be a relative path without a drive or UNC prefix`;
155
+ if (literal.includes("{") || literal.includes("}")) {
156
+ return `${context} pattern contains an unknown or malformed placeholder`;
157
+ }
158
+ if (pattern
159
+ .split(/[\\/]/u)
160
+ .some((segment) => segment === "." || segment === "..")) {
161
+ return `${context} pattern must not contain '.' or '..' path segments`;
162
+ }
163
+ return null;
164
+ }
165
+ function escapeJsonPointer(value) {
166
+ return value.replace(/~/gu, "~0").replace(/\//gu, "~1");
167
+ }
168
+ export function validateNetworkDefinition(input) {
169
+ if (!validateSchema(input)) {
170
+ return { errors: (validateSchema.errors ?? []).map(schemaError) };
171
+ }
172
+ const raw = input;
173
+ const definition = {
174
+ ...raw,
175
+ max_active_runs: raw.max_active_runs ?? 1,
176
+ };
177
+ const errors = semanticErrors(definition);
178
+ return { definition, errors };
179
+ }
180
+ //# sourceMappingURL=validate-network.js.map
@@ -0,0 +1,43 @@
1
+ import type { NodeAttempt, NodeState } from "../domain/persistence-model.js";
2
+ import type { PersistenceRepository, Versioned } from "../persistence/repository.js";
3
+ import { type JobLogReader } from "./job-log-reader.js";
4
+ import { type ResultClassification, type ResultFileReader } from "./result-classifier.js";
5
+ import type { RunRequest, RunSubprocess, SubprocessRunResult } from "./run-subprocess.js";
6
+ export interface AttemptExecutorInput extends RunRequest {
7
+ readonly attempt: Versioned<NodeAttempt>;
8
+ readonly nodeState: Versioned<NodeState>;
9
+ readonly executionStartedAt: string;
10
+ /** D-29 gate evaluated after subprocess completion and before result writes. */
11
+ readonly authorizeResultPersistence?: () => Promise<boolean>;
12
+ /** Scheduler-scoped retry for transport failures after subprocess completion. */
13
+ readonly runControlPlaneOperation?: <T>(operation: () => Promise<T>) => Promise<T>;
14
+ }
15
+ export interface AttemptExecutorOptions {
16
+ readonly repository: PersistenceRepository;
17
+ readonly runner: Pick<RunSubprocess, "run">;
18
+ readonly jobLogReader: JobLogReader;
19
+ readonly resultFileReader?: ResultFileReader;
20
+ readonly now?: () => string;
21
+ }
22
+ export interface AttemptExecutionOutcome {
23
+ readonly classification: ResultClassification;
24
+ readonly process: SubprocessRunResult;
25
+ readonly attempt: Versioned<NodeAttempt>;
26
+ readonly nodeState: Versioned<NodeState>;
27
+ readonly invocationResultCode: string | null;
28
+ }
29
+ export declare class AttemptResultPersistenceDeferredError extends Error {
30
+ readonly classification: ResultClassification;
31
+ readonly process: SubprocessRunResult;
32
+ readonly code = "NETWORK_LEASE_INTERRUPTED";
33
+ constructor(classification: ResultClassification, process: SubprocessRunResult);
34
+ }
35
+ /** FN-10 entry point for one already-created RUNNING attempt and node state. */
36
+ export declare class AttemptExecutor {
37
+ private readonly options;
38
+ private readonly now;
39
+ constructor(options: AttemptExecutorOptions);
40
+ execute(input: AttemptExecutorInput): Promise<AttemptExecutionOutcome>;
41
+ private readMarker;
42
+ }
43
+ //# sourceMappingURL=attempt-executor.d.ts.map