@supernovae-st/nika 0.118.7 → 0.120.2
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/README.md +649 -162
- package/dist/index.cjs +809 -144
- package/dist/index.d.cts +315 -19
- package/dist/index.d.ts +315 -19
- package/dist/index.js +808 -144
- package/docs/architecture.md +75 -12
- package/docs/http-api.md +81 -8
- package/docs/migrating-to-0.116.md +21 -6
- package/docs/testing.md +27 -2
- package/openapi.json +1797 -1
- package/package.json +9 -8
package/dist/index.cjs
CHANGED
|
@@ -43,6 +43,7 @@ __export(index_exports, {
|
|
|
43
43
|
NikaTransportError: () => NikaTransportError,
|
|
44
44
|
isNikaRunSealedEvent: () => isNikaRunSealedEvent,
|
|
45
45
|
isNikaRunSettledEvent: () => isNikaRunSettledEvent,
|
|
46
|
+
isNikaRunSucceeded: () => isNikaRunSucceeded,
|
|
46
47
|
isNikaTerminalEvent: () => isNikaTerminalEvent
|
|
47
48
|
});
|
|
48
49
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -121,12 +122,19 @@ var NikaOperationError = class extends NikaError {
|
|
|
121
122
|
};
|
|
122
123
|
var NikaEventBufferOverflowError = class extends NikaError {
|
|
123
124
|
runId;
|
|
125
|
+
/** The bound that was exceeded: the view's `bufferSize`. */
|
|
124
126
|
limit;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
reason;
|
|
128
|
+
constructor(runId, limit, replay) {
|
|
129
|
+
super(replay === void 0 ? `Event subscriber for run ${runId} exceeded its ${limit}-event buffer` : `Run ${runId} had produced ${replay.observed} events when this view was opened, more than the ${limit} it can replay (${replay.retained} still retained). The run and its result are unaffected. ` + (replay.retained === replay.observed ? `Open the view with bufferSize >= ${replay.observed}` : `Set eventBufferSize >= ${replay.observed} for the next run`) + ", or observe the run live");
|
|
127
130
|
this.name = "NikaEventBufferOverflowError";
|
|
128
131
|
this.runId = runId;
|
|
129
132
|
this.limit = limit;
|
|
133
|
+
this.reason = replay === void 0 ? "live_backpressure" : "replay_truncated";
|
|
134
|
+
if (replay !== void 0) {
|
|
135
|
+
this.observed = replay.observed;
|
|
136
|
+
this.retained = replay.retained;
|
|
137
|
+
}
|
|
130
138
|
}
|
|
131
139
|
};
|
|
132
140
|
var NikaRunOwnershipError = class extends NikaError {
|
|
@@ -136,9 +144,6 @@ var NikaRunOwnershipError = class extends NikaError {
|
|
|
136
144
|
}
|
|
137
145
|
};
|
|
138
146
|
|
|
139
|
-
// src/lib/http-transport.ts
|
|
140
|
-
var import_node_crypto2 = require("crypto");
|
|
141
|
-
|
|
142
147
|
// src/lib/binary/error.ts
|
|
143
148
|
var NikaEngineUnavailable = class extends NikaError {
|
|
144
149
|
code = "NIKA_ENGINE_UNAVAILABLE";
|
|
@@ -628,6 +633,184 @@ function incompatible2(message, cause) {
|
|
|
628
633
|
);
|
|
629
634
|
}
|
|
630
635
|
|
|
636
|
+
// src/lib/literal-inputs.ts
|
|
637
|
+
var import_node_util = require("util");
|
|
638
|
+
var LITERAL_INPUTS_MAX_BYTES = 1024 * 1024;
|
|
639
|
+
function literalInputs(options) {
|
|
640
|
+
if (options.inputs === void 0) return void 0;
|
|
641
|
+
if (options.vars !== void 0) {
|
|
642
|
+
throw new NikaConfigurationError(
|
|
643
|
+
"run({ inputs, vars }): inputs and the deprecated vars alias cannot be combined; move every value into inputs"
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
return encodeLiteralInputs(options.inputs);
|
|
647
|
+
}
|
|
648
|
+
var STRICT_JSON = "inputs must be strict JSON (null, booleans, finite numbers, strings, plain arrays and plain objects)";
|
|
649
|
+
var NATIVE_OBJECT = Function.prototype.toString.call(Object);
|
|
650
|
+
var NATIVE_ARRAY = Function.prototype.toString.call(Array);
|
|
651
|
+
var LONE_SURROGATE = /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;
|
|
652
|
+
var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
653
|
+
var PATH_LIMIT = 240;
|
|
654
|
+
function encodeLiteralInputs(inputs) {
|
|
655
|
+
if (containerKind(inputs) !== "object") {
|
|
656
|
+
throw new NikaConfigurationError(
|
|
657
|
+
`run({ inputs }): inputs must be a plain object mapping declared workflow input names to strict JSON values; received ${describe(inputs)}`
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
const parts = [];
|
|
661
|
+
let bytes = 0;
|
|
662
|
+
const write = (chunk) => {
|
|
663
|
+
bytes += Buffer.byteLength(chunk);
|
|
664
|
+
if (bytes > LITERAL_INPUTS_MAX_BYTES) throw tooLarge();
|
|
665
|
+
parts.push(chunk);
|
|
666
|
+
};
|
|
667
|
+
const stack = [];
|
|
668
|
+
const open = /* @__PURE__ */ new Set();
|
|
669
|
+
const refuse = (segment, problem) => {
|
|
670
|
+
const path3 = stack.map((frame) => frame.segment).join("") + segment;
|
|
671
|
+
const shown = path3.length > PATH_LIMIT ? `${path3.slice(0, PATH_LIMIT / 2)}\u2026${path3.slice(-PATH_LIMIT / 2)}` : path3;
|
|
672
|
+
return new NikaConfigurationError(`run({ inputs }): ${shown} ${problem}; ${STRICT_JSON}`);
|
|
673
|
+
};
|
|
674
|
+
const enter = (value, segment, kind) => {
|
|
675
|
+
open.add(value);
|
|
676
|
+
if (kind === "array") {
|
|
677
|
+
write("[");
|
|
678
|
+
stack.push({ value, segment, length: value.length, index: 0 });
|
|
679
|
+
} else {
|
|
680
|
+
write("{");
|
|
681
|
+
const keys = Reflect.ownKeys(value);
|
|
682
|
+
stack.push({ value, segment, keys, length: keys.length, index: 0 });
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
enter(inputs, "inputs", "object");
|
|
686
|
+
for (let frame = stack.at(-1); frame; frame = stack.at(-1)) {
|
|
687
|
+
if (frame.index === frame.length) {
|
|
688
|
+
if (!frame.keys) {
|
|
689
|
+
const named = Reflect.ownKeys(frame.value).find((key2) => !isArrayMember(key2, frame.length));
|
|
690
|
+
if (named !== void 0) {
|
|
691
|
+
stack.pop();
|
|
692
|
+
throw refuse(frame.segment + keySegment(named), typeof named === "symbol" ? "is a symbol key, which JSON cannot carry" : "is a named array property, which JSON cannot carry");
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
write(frame.keys ? "}" : "]");
|
|
696
|
+
open.delete(frame.value);
|
|
697
|
+
stack.pop();
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
const position = frame.index;
|
|
701
|
+
frame.index += 1;
|
|
702
|
+
if (position > 0) write(",");
|
|
703
|
+
const key = frame.keys ? frame.keys[position] : String(position);
|
|
704
|
+
const segment = frame.keys ? keySegment(key) : `[${position}]`;
|
|
705
|
+
if (typeof key === "symbol") throw refuse(segment, "is a symbol key, which JSON cannot carry");
|
|
706
|
+
const member = Object.getOwnPropertyDescriptor(frame.value, key);
|
|
707
|
+
if (!member) {
|
|
708
|
+
throw refuse(segment, frame.keys ? "is not an own data property" : "is an array hole, which JSON cannot carry");
|
|
709
|
+
}
|
|
710
|
+
if ("get" in member || "set" in member) {
|
|
711
|
+
throw refuse(segment, "is an accessor property, which JSON cannot carry");
|
|
712
|
+
}
|
|
713
|
+
if (frame.keys) {
|
|
714
|
+
if (!member.enumerable) {
|
|
715
|
+
throw refuse(segment, "is a non-enumerable property, which JSON cannot carry");
|
|
716
|
+
}
|
|
717
|
+
if (LONE_SURROGATE.test(key)) {
|
|
718
|
+
throw refuse(segment, "is a key with a lone surrogate, which is not valid Unicode");
|
|
719
|
+
}
|
|
720
|
+
write(`${JSON.stringify(key)}:`);
|
|
721
|
+
}
|
|
722
|
+
const value = member.value;
|
|
723
|
+
switch (typeof value) {
|
|
724
|
+
case "string":
|
|
725
|
+
if (value.length > LITERAL_INPUTS_MAX_BYTES) throw tooLarge();
|
|
726
|
+
if (LONE_SURROGATE.test(value)) {
|
|
727
|
+
throw refuse(segment, "contains a lone surrogate, which is not valid Unicode");
|
|
728
|
+
}
|
|
729
|
+
write(JSON.stringify(value));
|
|
730
|
+
break;
|
|
731
|
+
case "number":
|
|
732
|
+
if (!Number.isFinite(value)) {
|
|
733
|
+
throw refuse(segment, "is a non-finite number, which JSON cannot carry");
|
|
734
|
+
}
|
|
735
|
+
write(JSON.stringify(value));
|
|
736
|
+
break;
|
|
737
|
+
case "boolean":
|
|
738
|
+
write(value ? "true" : "false");
|
|
739
|
+
break;
|
|
740
|
+
case "object": {
|
|
741
|
+
if (value === null) {
|
|
742
|
+
write("null");
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
if (open.has(value)) throw refuse(segment, "is a cycle, which JSON cannot carry");
|
|
746
|
+
const kind = containerKind(value);
|
|
747
|
+
if (!kind) throw refuse(segment, refusedObject(value));
|
|
748
|
+
enter(value, segment, kind);
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
default:
|
|
752
|
+
throw refuse(segment, `is ${typeof value === "undefined" ? "" : "a "}${typeof value}, which JSON cannot carry`);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return { json: parts.join(""), bytes };
|
|
756
|
+
}
|
|
757
|
+
function tooLarge() {
|
|
758
|
+
return new NikaConfigurationError(
|
|
759
|
+
`run({ inputs }): the serialized inputs map exceeds ${LITERAL_INPUTS_MAX_BYTES} bytes (1 MiB), the bound both transports share with the native engine`
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
function containerKind(value) {
|
|
763
|
+
if (value === null || typeof value !== "object" || import_node_util.types.isProxy(value)) return void 0;
|
|
764
|
+
const prototype = Object.getPrototypeOf(value);
|
|
765
|
+
if (Array.isArray(value)) return isRealmPrototype(prototype, NATIVE_ARRAY) ? "array" : void 0;
|
|
766
|
+
if (prototype === null) return "object";
|
|
767
|
+
return isRealmPrototype(prototype, NATIVE_OBJECT) ? "object" : void 0;
|
|
768
|
+
}
|
|
769
|
+
function isRealmPrototype(prototype, source) {
|
|
770
|
+
if (prototype === null || import_node_util.types.isProxy(prototype)) return false;
|
|
771
|
+
const constructor = ownValue(prototype, "constructor");
|
|
772
|
+
if (typeof constructor !== "function" || import_node_util.types.isProxy(constructor)) return false;
|
|
773
|
+
let native;
|
|
774
|
+
try {
|
|
775
|
+
native = Function.prototype.toString.call(constructor) === source;
|
|
776
|
+
} catch {
|
|
777
|
+
return false;
|
|
778
|
+
}
|
|
779
|
+
return native && ownValue(constructor, "prototype") === prototype;
|
|
780
|
+
}
|
|
781
|
+
function ownValue(target, key) {
|
|
782
|
+
return Object.getOwnPropertyDescriptor(target, key)?.value;
|
|
783
|
+
}
|
|
784
|
+
function isArrayMember(key, length) {
|
|
785
|
+
if (typeof key === "symbol") return false;
|
|
786
|
+
if (key === "length") return true;
|
|
787
|
+
const index = Number(key);
|
|
788
|
+
return Number.isInteger(index) && index >= 0 && index < length && String(index) === key;
|
|
789
|
+
}
|
|
790
|
+
function keySegment(key) {
|
|
791
|
+
if (typeof key === "symbol") return `[${String(key)}]`;
|
|
792
|
+
return IDENTIFIER.test(key) ? `.${key}` : `[${JSON.stringify(key)}]`;
|
|
793
|
+
}
|
|
794
|
+
function describe(value) {
|
|
795
|
+
if (value === null) return "null";
|
|
796
|
+
if (typeof value === "undefined") return "undefined";
|
|
797
|
+
if (typeof value !== "object") return `a ${typeof value}`;
|
|
798
|
+
if (import_node_util.types.isProxy(value)) return "a Proxy";
|
|
799
|
+
if (Array.isArray(value)) return "an array";
|
|
800
|
+
return refusedObject(value).replace(/^is /, "").replace(/, not a plain object or array$/, "");
|
|
801
|
+
}
|
|
802
|
+
function refusedObject(value) {
|
|
803
|
+
if (import_node_util.types.isProxy(value)) return "is a Proxy, which cannot be read without running its traps";
|
|
804
|
+
const custom = "is an object with a custom prototype, not a plain object or array";
|
|
805
|
+
const prototype = Object.getPrototypeOf(value);
|
|
806
|
+
if (prototype === null || import_node_util.types.isProxy(prototype)) return custom;
|
|
807
|
+
const constructor = ownValue(prototype, "constructor");
|
|
808
|
+
if (typeof constructor !== "function" || import_node_util.types.isProxy(constructor)) return custom;
|
|
809
|
+
if (ownValue(constructor, "prototype") !== prototype) return custom;
|
|
810
|
+
const name = ownValue(constructor, "name");
|
|
811
|
+
return typeof name === "string" && IDENTIFIER.test(name) && name.length <= 64 ? `is a ${name} instance, not a plain object or array` : custom;
|
|
812
|
+
}
|
|
813
|
+
|
|
631
814
|
// src/lib/sse/parser.ts
|
|
632
815
|
var SseParseError = class extends Error {
|
|
633
816
|
constructor(message, options) {
|
|
@@ -785,6 +968,100 @@ async function* decodeSse(stream, limits, signal) {
|
|
|
785
968
|
}
|
|
786
969
|
}
|
|
787
970
|
|
|
971
|
+
// src/lib/workflow-name.ts
|
|
972
|
+
var WORKFLOW_SUFFIX = ".nika";
|
|
973
|
+
var LEGACY_WORKFLOW_SUFFIXES = [".nika.yaml", ".nika.yml"];
|
|
974
|
+
var CONTAINED_CHAR = /[A-Za-z0-9/._-]/;
|
|
975
|
+
function workflowBasename(path3) {
|
|
976
|
+
const parts = path3.split(/[\\/]/);
|
|
977
|
+
return parts[parts.length - 1] ?? "";
|
|
978
|
+
}
|
|
979
|
+
function hasControlChar(value) {
|
|
980
|
+
for (const ch of value) {
|
|
981
|
+
const c = ch.codePointAt(0) ?? 0;
|
|
982
|
+
if (c <= 31 || c === 127) {
|
|
983
|
+
return true;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
function isLegacyWorkflowPath(path3) {
|
|
989
|
+
if (typeof path3 !== "string" || path3.length === 0) {
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
992
|
+
if (path3.endsWith("/") || path3.endsWith("\\")) {
|
|
993
|
+
return false;
|
|
994
|
+
}
|
|
995
|
+
const base = workflowBasename(path3);
|
|
996
|
+
return LEGACY_WORKFLOW_SUFFIXES.some((suffix) => base.endsWith(suffix));
|
|
997
|
+
}
|
|
998
|
+
function isCanonicalWorkflowFilename(basename) {
|
|
999
|
+
if (typeof basename !== "string" || basename.length === 0) {
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
if (hasControlChar(basename)) {
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
if (basename.includes("/") || basename.includes("\\")) {
|
|
1006
|
+
return false;
|
|
1007
|
+
}
|
|
1008
|
+
if (LEGACY_WORKFLOW_SUFFIXES.some((suffix) => basename.endsWith(suffix))) {
|
|
1009
|
+
return false;
|
|
1010
|
+
}
|
|
1011
|
+
if (!basename.endsWith(WORKFLOW_SUFFIX)) {
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
return basename.length > WORKFLOW_SUFFIX.length;
|
|
1015
|
+
}
|
|
1016
|
+
function isCanonicalWorkflowPath(path3) {
|
|
1017
|
+
if (typeof path3 !== "string" || path3.length === 0) {
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
1020
|
+
if (hasControlChar(path3)) {
|
|
1021
|
+
return false;
|
|
1022
|
+
}
|
|
1023
|
+
if (path3.endsWith("/") || path3.endsWith("\\")) {
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
1026
|
+
return isCanonicalWorkflowFilename(workflowBasename(path3));
|
|
1027
|
+
}
|
|
1028
|
+
function containedRelativeShape(value) {
|
|
1029
|
+
if (value.includes("\\") || value.startsWith("/")) {
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
1032
|
+
for (const ch of value) {
|
|
1033
|
+
if (!CONTAINED_CHAR.test(ch)) {
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
const segments = value.split("/");
|
|
1038
|
+
return segments.every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
1039
|
+
}
|
|
1040
|
+
function isContainedWorkflowName(value) {
|
|
1041
|
+
return typeof value === "string" && isCanonicalWorkflowPath(value) && containedRelativeShape(value);
|
|
1042
|
+
}
|
|
1043
|
+
function isRetiredByNameAttempt(value) {
|
|
1044
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1045
|
+
return false;
|
|
1046
|
+
}
|
|
1047
|
+
if (value.startsWith("/") || value.startsWith("./") || value.includes("\\")) {
|
|
1048
|
+
return false;
|
|
1049
|
+
}
|
|
1050
|
+
return isLegacyWorkflowPath(value);
|
|
1051
|
+
}
|
|
1052
|
+
function renameLegacyWorkflow(name) {
|
|
1053
|
+
if (name.endsWith(".nika.yaml")) {
|
|
1054
|
+
return `${name.slice(0, -".nika.yaml".length)}${WORKFLOW_SUFFIX}`;
|
|
1055
|
+
}
|
|
1056
|
+
if (name.endsWith(".nika.yml")) {
|
|
1057
|
+
return `${name.slice(0, -".nika.yml".length)}${WORKFLOW_SUFFIX}`;
|
|
1058
|
+
}
|
|
1059
|
+
return name;
|
|
1060
|
+
}
|
|
1061
|
+
function legacyWorkflowRenameMessage(name) {
|
|
1062
|
+
return `workflow names use ${WORKFLOW_SUFFIX}; rename ${name} to ${renameLegacyWorkflow(name)}`;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
788
1065
|
// src/lib/http-transport.ts
|
|
789
1066
|
var JOB_STATUSES = /* @__PURE__ */ new Set([
|
|
790
1067
|
"queued",
|
|
@@ -801,6 +1078,7 @@ var RETRY_BASE_MILLISECONDS = 100;
|
|
|
801
1078
|
var RETRY_MIN_MILLISECONDS = 25;
|
|
802
1079
|
var RETRY_MAX_MILLISECONDS = 5e3;
|
|
803
1080
|
var WORKFLOW_REFUSAL_STATUSES = /* @__PURE__ */ new Set([404, 422]);
|
|
1081
|
+
var JOB_INPUTS = "jobInputs";
|
|
804
1082
|
var HttpTransport = class {
|
|
805
1083
|
constructor(options) {
|
|
806
1084
|
this.options = options;
|
|
@@ -817,6 +1095,7 @@ var HttpTransport = class {
|
|
|
817
1095
|
"nika serve admission has no request envelope for model or nativeStrict overrides"
|
|
818
1096
|
);
|
|
819
1097
|
}
|
|
1098
|
+
refuseLegacyContainedName(workflow);
|
|
820
1099
|
if (isContainedWorkflowName(workflow)) return this.checkByName(workflow, options.signal);
|
|
821
1100
|
const captured = await this.captureSnapshot(workflow, options.signal, true);
|
|
822
1101
|
if (captured.bytes === void 0) return captured.report;
|
|
@@ -836,19 +1115,35 @@ var HttpTransport = class {
|
|
|
836
1115
|
return captured.report;
|
|
837
1116
|
}
|
|
838
1117
|
async startRun(workflow, options) {
|
|
1118
|
+
const inputs = literalInputs(options);
|
|
839
1119
|
if (options.vars && Object.keys(options.vars).length > 0 || options.model !== void 0 || options.maxCostUsd !== void 0) {
|
|
840
1120
|
throw this.gap(
|
|
841
1121
|
"runOptions",
|
|
842
|
-
"nika serve admission has no request envelope for vars, model, or maxCostUsd"
|
|
1122
|
+
"nika serve admission has no request envelope for vars, model, or maxCostUsd; literal workflow values ride inputs, and vars is the native --var operator channel"
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
refuseLegacyContainedName(workflow);
|
|
1126
|
+
const byName = isContainedWorkflowName(workflow);
|
|
1127
|
+
if (inputs && !byName) {
|
|
1128
|
+
throw this.gap(
|
|
1129
|
+
"snapshotInputs",
|
|
1130
|
+
"An execution snapshot freezes its inputs and takes no request overlay; run({ inputs }) over HTTP binds a workflow by its served name (listWorkflows())"
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
const idempotencyKey = options.idempotencyKey;
|
|
1134
|
+
if (idempotencyKey === void 0) {
|
|
1135
|
+
throw new NikaConfigurationError(
|
|
1136
|
+
"HTTP run() requires a caller-owned idempotencyKey; reuse the same key and request when retrying an uncertain admission"
|
|
843
1137
|
);
|
|
844
1138
|
}
|
|
845
|
-
const idempotencyKey = options.idempotencyKey ?? (0, import_node_crypto2.randomUUID)();
|
|
846
1139
|
if (Buffer.byteLength(idempotencyKey) < 1 || Buffer.byteLength(idempotencyKey) > 255) {
|
|
847
1140
|
throw new NikaTransportError(this.kind, "Idempotency-Key must be 1-255 bytes");
|
|
848
1141
|
}
|
|
849
|
-
if (
|
|
1142
|
+
if (byName) {
|
|
850
1143
|
await this.ensureServerIdentity();
|
|
851
|
-
|
|
1144
|
+
if (inputs) this.requireJobInputs();
|
|
1145
|
+
const body = inputs ? `{"workflow":${JSON.stringify(workflow)},"inputs":${inputs.json}}` : JSON.stringify({ workflow });
|
|
1146
|
+
const job2 = await this.admitJob(body, idempotencyKey);
|
|
852
1147
|
return this.httpRun(job2.id, 0, job2);
|
|
853
1148
|
}
|
|
854
1149
|
const captured = await this.captureSnapshot(workflow);
|
|
@@ -924,6 +1219,7 @@ var HttpTransport = class {
|
|
|
924
1219
|
return Object.freeze([...object.workflows]);
|
|
925
1220
|
}
|
|
926
1221
|
async workflow(name) {
|
|
1222
|
+
refuseLegacyContainedName(name);
|
|
927
1223
|
await this.ensureServerIdentity();
|
|
928
1224
|
const object = await this.json(`/v1/workflows/${workflowPath(name)}`, {
|
|
929
1225
|
method: "GET",
|
|
@@ -935,6 +1231,7 @@ var HttpTransport = class {
|
|
|
935
1231
|
return object;
|
|
936
1232
|
}
|
|
937
1233
|
async schedule(workflow, options) {
|
|
1234
|
+
refuseLegacyContainedName(workflow);
|
|
938
1235
|
await this.ensureScheduleCapability();
|
|
939
1236
|
const path3 = `/v1/schedules/${encodeURIComponent(options.id)}`;
|
|
940
1237
|
const headers = new Headers({ "Content-Type": "application/json" });
|
|
@@ -1026,6 +1323,7 @@ var HttpTransport = class {
|
|
|
1026
1323
|
const outputs = event ? eventOutputs(event) : durable?.outputs;
|
|
1027
1324
|
const error = event ? eventError(event) : durable?.settlement?.error ?? durable?.error;
|
|
1028
1325
|
const settlement = event ? eventSettlement(event, this.kind) : durable?.settlement;
|
|
1326
|
+
const evidence = event ? eventEvidence(event, this.kind) : durable?.evidence;
|
|
1029
1327
|
resolveDone({
|
|
1030
1328
|
id,
|
|
1031
1329
|
status: source.status,
|
|
@@ -1035,7 +1333,8 @@ var HttpTransport = class {
|
|
|
1035
1333
|
...outputs ? { outputs } : {},
|
|
1036
1334
|
...receipt ? { receipt } : {},
|
|
1037
1335
|
...error ? { error } : {},
|
|
1038
|
-
...settlement ? { settlement } : {}
|
|
1336
|
+
...settlement ? { settlement } : {},
|
|
1337
|
+
...evidence ? { evidence } : {}
|
|
1039
1338
|
});
|
|
1040
1339
|
};
|
|
1041
1340
|
if (attachedState && isObservationEnded(attachedState)) settle(attachedState);
|
|
@@ -1234,13 +1533,15 @@ var HttpTransport = class {
|
|
|
1234
1533
|
if (!event) throw new NikaProtocolError(this.kind, "SSE data was not an object");
|
|
1235
1534
|
const allowed = /* @__PURE__ */ new Set([
|
|
1236
1535
|
"sequence",
|
|
1536
|
+
"at",
|
|
1237
1537
|
"kind",
|
|
1238
1538
|
"status",
|
|
1239
1539
|
"code",
|
|
1240
1540
|
"message",
|
|
1241
1541
|
"outputs",
|
|
1242
1542
|
"receipt",
|
|
1243
|
-
"settlement"
|
|
1543
|
+
"settlement",
|
|
1544
|
+
"evidence"
|
|
1244
1545
|
]);
|
|
1245
1546
|
if (Object.keys(event).some((key) => !allowed.has(key))) {
|
|
1246
1547
|
throw new NikaProtocolError(this.kind, "SSE data contained fields outside the public projection");
|
|
@@ -1262,6 +1563,10 @@ var HttpTransport = class {
|
|
|
1262
1563
|
throw new NikaProtocolError(this.kind, `SSE data.${field} was not a string`);
|
|
1263
1564
|
}
|
|
1264
1565
|
}
|
|
1566
|
+
if (event.at !== void 0 && (typeof event.at !== "string" || !isCanonicalTimestamp(event.at))) {
|
|
1567
|
+
throw new NikaProtocolError(this.kind, "SSE data.at was not an RFC 3339 timestamp");
|
|
1568
|
+
}
|
|
1569
|
+
if (event.evidence !== void 0) journalEvidence(event.evidence, this.kind, "SSE data.evidence");
|
|
1265
1570
|
if (event.outputs !== void 0 && !machineObject(event.outputs)) {
|
|
1266
1571
|
throw new NikaProtocolError(this.kind, "SSE data.outputs was not an object");
|
|
1267
1572
|
}
|
|
@@ -1511,6 +1816,21 @@ var HttpTransport = class {
|
|
|
1511
1816
|
);
|
|
1512
1817
|
}
|
|
1513
1818
|
}
|
|
1819
|
+
/**
|
|
1820
|
+
* The resident must advertise named-input admission before a map is sent
|
|
1821
|
+
* (nika#1642). A resident from before the envelope answered 202 to an extra
|
|
1822
|
+
* `inputs` field and applied nothing, so an accepted POST negotiates nothing:
|
|
1823
|
+
* only the capability `/health` advertised does. Called after
|
|
1824
|
+
* `ensureServerIdentity()`.
|
|
1825
|
+
*/
|
|
1826
|
+
requireJobInputs() {
|
|
1827
|
+
const identity = this.remoteIdentity;
|
|
1828
|
+
if (identity?.supportedCapabilities.includes(JOB_INPUTS)) return;
|
|
1829
|
+
throw this.gap(
|
|
1830
|
+
JOB_INPUTS,
|
|
1831
|
+
`The connected nika serve ${identity?.engineVersion ?? "(unidentified)"} does not advertise ${JOB_INPUTS} (advertised: ${identity?.supportedCapabilities.join(", ") ?? "nothing"}); run({ inputs }) needs a resident that binds declared inputs by name. Nothing was posted: an older resident accepts the field and ignores its values`
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1514
1834
|
async captureSnapshot(workflow, signal, teach = false) {
|
|
1515
1835
|
const identity = await this.ensureLocalEngine();
|
|
1516
1836
|
const captured = await captureEngine(
|
|
@@ -1784,6 +2104,11 @@ var HttpTransport = class {
|
|
|
1784
2104
|
}
|
|
1785
2105
|
}
|
|
1786
2106
|
};
|
|
2107
|
+
function refuseLegacyContainedName(workflow) {
|
|
2108
|
+
if (isRetiredByNameAttempt(workflow)) {
|
|
2109
|
+
throw new NikaConfigurationError(legacyWorkflowRenameMessage(workflow));
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
1787
2112
|
function workflowPath(name) {
|
|
1788
2113
|
const segments = name.split("/");
|
|
1789
2114
|
if (name.startsWith("/") || name.includes("\\") || segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
|
|
@@ -1791,15 +2116,6 @@ function workflowPath(name) {
|
|
|
1791
2116
|
}
|
|
1792
2117
|
return segments.map(encodeURIComponent).join("/");
|
|
1793
2118
|
}
|
|
1794
|
-
function isContainedWorkflowName(value) {
|
|
1795
|
-
if (typeof value !== "string" || !value.endsWith(".nika.yaml")) return false;
|
|
1796
|
-
try {
|
|
1797
|
-
workflowPath(value);
|
|
1798
|
-
return true;
|
|
1799
|
-
} catch {
|
|
1800
|
-
return false;
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
2119
|
function scheduleBody(workflow, options) {
|
|
1804
2120
|
return {
|
|
1805
2121
|
workflow,
|
|
@@ -1906,7 +2222,8 @@ function durableJob(value, expectedId, transport) {
|
|
|
1906
2222
|
"outputs",
|
|
1907
2223
|
"receipt",
|
|
1908
2224
|
"error",
|
|
1909
|
-
"settlement"
|
|
2225
|
+
"settlement",
|
|
2226
|
+
"evidence"
|
|
1910
2227
|
]);
|
|
1911
2228
|
if (Object.keys(value).some((key) => !allowed.has(key))) {
|
|
1912
2229
|
throw new NikaProtocolError(transport, "Durable job response contained unknown fields");
|
|
@@ -1930,6 +2247,7 @@ function durableJob(value, expectedId, transport) {
|
|
|
1930
2247
|
const outputs = value.outputs === void 0 ? void 0 : machineObject(value.outputs);
|
|
1931
2248
|
const receipt = value.receipt === void 0 ? void 0 : machineObject(value.receipt);
|
|
1932
2249
|
const settlement = value.settlement === void 0 ? void 0 : readSettlement(value.settlement, transport, value.status);
|
|
2250
|
+
const evidence = value.evidence === void 0 ? void 0 : journalEvidence(value.evidence, transport, "Durable job response evidence");
|
|
1933
2251
|
if (value.outputs !== void 0 && !outputs) {
|
|
1934
2252
|
throw new NikaProtocolError(transport, "Durable job response outputs were malformed");
|
|
1935
2253
|
}
|
|
@@ -1953,9 +2271,24 @@ function durableJob(value, expectedId, transport) {
|
|
|
1953
2271
|
...outputs ? { outputs } : {},
|
|
1954
2272
|
...receipt ? { receipt: Object.freeze(receipt) } : {},
|
|
1955
2273
|
...error ? { error } : {},
|
|
1956
|
-
...settlement ? { settlement } : {}
|
|
2274
|
+
...settlement ? { settlement } : {},
|
|
2275
|
+
...evidence ? { evidence } : {}
|
|
1957
2276
|
};
|
|
1958
2277
|
}
|
|
2278
|
+
var JOURNAL_EVIDENCE_REASONS = /* @__PURE__ */ new Set(["write_failed", "record_refused"]);
|
|
2279
|
+
function journalEvidence(value, transport, where) {
|
|
2280
|
+
const evidence = machineObject(value);
|
|
2281
|
+
if (!evidence || Object.keys(evidence).length !== 2 || evidence.status !== "mirror_lost" || typeof evidence.reason !== "string" || !JOURNAL_EVIDENCE_REASONS.has(evidence.reason)) {
|
|
2282
|
+
throw new NikaProtocolError(transport, `${where} was not the resident's journal evidence`);
|
|
2283
|
+
}
|
|
2284
|
+
return Object.freeze({
|
|
2285
|
+
status: "mirror_lost",
|
|
2286
|
+
reason: evidence.reason
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
function eventEvidence(event, transport) {
|
|
2290
|
+
return event.evidence === void 0 ? void 0 : journalEvidence(event.evidence, transport, "SSE data.evidence");
|
|
2291
|
+
}
|
|
1959
2292
|
function assertReceiptIdentity(receipt, expectedJobId, transport, expectedExecutionId, expectedTraceId, expectedSnapshotDigest) {
|
|
1960
2293
|
const allowed = /* @__PURE__ */ new Set([
|
|
1961
2294
|
"job_id",
|
|
@@ -2083,7 +2416,104 @@ function isCanonicalTimestamp(value) {
|
|
|
2083
2416
|
|
|
2084
2417
|
// src/lib/native-process-transport.ts
|
|
2085
2418
|
var import_node_child_process2 = require("child_process");
|
|
2086
|
-
var
|
|
2419
|
+
var import_node_crypto2 = require("crypto");
|
|
2420
|
+
|
|
2421
|
+
// src/lib/legacy-run-refusals.ts
|
|
2422
|
+
var REFUSAL_EXITS = /* @__PURE__ */ new Set([2, 3]);
|
|
2423
|
+
var ENGINE_PREFIX = /^nika(?: [a-z-]+)?:\s*/;
|
|
2424
|
+
function legacyTeachingLine(line) {
|
|
2425
|
+
const machineCode = leadingCode(line);
|
|
2426
|
+
return machineCode ? { machineCode, message: line } : void 0;
|
|
2427
|
+
}
|
|
2428
|
+
async function legacyPrettyReport(opening, lines, limit, transport) {
|
|
2429
|
+
if (!opening.startsWith("{")) return void 0;
|
|
2430
|
+
let text = opening;
|
|
2431
|
+
let bytes = Buffer.byteLength(opening);
|
|
2432
|
+
for (; ; ) {
|
|
2433
|
+
const next = await lines.next();
|
|
2434
|
+
if (next.done) break;
|
|
2435
|
+
text += `
|
|
2436
|
+
${next.value}`;
|
|
2437
|
+
bytes += Buffer.byteLength(next.value) + 1;
|
|
2438
|
+
if (bytes > limit) {
|
|
2439
|
+
throw new NikaProtocolError(transport, `Native pre-run report exceeded ${limit} bytes`);
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
let value;
|
|
2443
|
+
try {
|
|
2444
|
+
value = JSON.parse(text);
|
|
2445
|
+
} catch {
|
|
2446
|
+
return { text };
|
|
2447
|
+
}
|
|
2448
|
+
const frame = machineObject(value);
|
|
2449
|
+
return frame ? { frame, text } : { text };
|
|
2450
|
+
}
|
|
2451
|
+
function legacyStderrRefusal(stderr, exitCode) {
|
|
2452
|
+
if (!REFUSAL_EXITS.has(exitCode)) return void 0;
|
|
2453
|
+
for (const raw of stderr.split("\n")) {
|
|
2454
|
+
const line = raw.trim().replace(ENGINE_PREFIX, "");
|
|
2455
|
+
const machineCode = leadingCode(line);
|
|
2456
|
+
if (machineCode) return { machineCode, message: line };
|
|
2457
|
+
}
|
|
2458
|
+
return void 0;
|
|
2459
|
+
}
|
|
2460
|
+
function leadingCode(text) {
|
|
2461
|
+
const code = /^NIKA-[A-Z0-9_-]+/.exec(text)?.[0].replace(/-+$/, "");
|
|
2462
|
+
return code && /\d/.test(code) ? code : void 0;
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
// src/lib/run-refusal.ts
|
|
2466
|
+
var RUN_REFUSED = "run_refused";
|
|
2467
|
+
var UNEXPLAINED = "The engine refused this workflow before admission";
|
|
2468
|
+
function isRunEvent(frame) {
|
|
2469
|
+
return typeof frame.kind === "string";
|
|
2470
|
+
}
|
|
2471
|
+
function preRunRefusal(frame, transport) {
|
|
2472
|
+
if (isRunEvent(frame)) return void 0;
|
|
2473
|
+
if (frame.clean === false && Array.isArray(frame.findings)) {
|
|
2474
|
+
return checkRefusal(frame.findings, transport);
|
|
2475
|
+
}
|
|
2476
|
+
const error = machineObject(frame.error);
|
|
2477
|
+
if (!error || typeof error.message !== "string" || error.message.length === 0) {
|
|
2478
|
+
return void 0;
|
|
2479
|
+
}
|
|
2480
|
+
const machineCode = typeof error.code === "string" && error.code.length > 0 ? error.code : void 0;
|
|
2481
|
+
return { ...machineCode ? { machineCode } : {}, message: error.message };
|
|
2482
|
+
}
|
|
2483
|
+
function runRefusalError(transport, refusal, exitCode) {
|
|
2484
|
+
return new NikaOperationError(
|
|
2485
|
+
"run",
|
|
2486
|
+
transport,
|
|
2487
|
+
refusal.machineCode ?? RUN_REFUSED,
|
|
2488
|
+
refusal.message,
|
|
2489
|
+
{
|
|
2490
|
+
status: exitCode,
|
|
2491
|
+
...refusal.findings ? { findings: refusal.findings } : {},
|
|
2492
|
+
...refusal.machineCode ? { machineCode: refusal.machineCode } : {}
|
|
2493
|
+
}
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2496
|
+
function checkRefusal(values, transport) {
|
|
2497
|
+
const findings = [];
|
|
2498
|
+
for (const value of values) {
|
|
2499
|
+
const finding = machineObject(value);
|
|
2500
|
+
if (!finding) {
|
|
2501
|
+
throw new NikaProtocolError(transport, "Pre-run refusal findings were malformed");
|
|
2502
|
+
}
|
|
2503
|
+
findings.push(finding);
|
|
2504
|
+
}
|
|
2505
|
+
const named = findings.find((finding) => typeof finding.code === "string" && finding.code) ?? findings[0];
|
|
2506
|
+
const machineCode = typeof named?.code === "string" && named.code ? named.code : void 0;
|
|
2507
|
+
const said = typeof named?.message === "string" && named.message ? named.message : void 0;
|
|
2508
|
+
const others = findings.length > 1 ? ` (+${findings.length - 1} more findings)` : "";
|
|
2509
|
+
return {
|
|
2510
|
+
...machineCode ? { machineCode } : {},
|
|
2511
|
+
message: `${[machineCode, said ?? UNEXPLAINED].filter(Boolean).join(" \xB7 ")}${others}`,
|
|
2512
|
+
findings
|
|
2513
|
+
};
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
// src/lib/native-process-transport.ts
|
|
2087
2517
|
var NativeProcessTransport = class {
|
|
2088
2518
|
constructor(options) {
|
|
2089
2519
|
this.options = options;
|
|
@@ -2108,7 +2538,8 @@ var NativeProcessTransport = class {
|
|
|
2108
2538
|
return { ...report, exitCode: captured.exitCode };
|
|
2109
2539
|
}
|
|
2110
2540
|
async startRun(workflow, options) {
|
|
2111
|
-
|
|
2541
|
+
const inputs = literalInputs(options);
|
|
2542
|
+
const identity = await this.ensureReady();
|
|
2112
2543
|
if (options.idempotencyKey !== void 0) {
|
|
2113
2544
|
throw new NikaCompatibilityError(
|
|
2114
2545
|
"idempotencyKey",
|
|
@@ -2116,13 +2547,26 @@ var NativeProcessTransport = class {
|
|
|
2116
2547
|
"idempotencyKey is an HTTP admission option"
|
|
2117
2548
|
);
|
|
2118
2549
|
}
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2550
|
+
if (inputs && !identity.supportedCapabilities.includes(INPUTS_LITERAL)) {
|
|
2551
|
+
throw new NikaCompatibilityError(
|
|
2552
|
+
INPUTS_LITERAL,
|
|
2553
|
+
this.kind,
|
|
2554
|
+
`Engine ${identity.engineVersion} at ${this.options.engine.bin} does not advertise ${INPUTS_LITERAL} (advertised: ${identity.supportedCapabilities.join(", ")}); run({ inputs }) needs the literal input channel \`nika run --inputs-json -\`. The SDK never falls back to --var, which reads @env: and coerces by declared type`
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2557
|
+
const args = ["run", workflow, "--json", ...runFlags(options, inputs !== void 0)];
|
|
2558
|
+
const spawnOptions = { cwd: this.options.cwd, shell: false };
|
|
2559
|
+
const child = inputs ? (0, import_node_child_process2.spawn)(this.options.engine.bin, args, { ...spawnOptions, stdio: ["pipe", "pipe", "pipe"] }) : (0, import_node_child_process2.spawn)(this.options.engine.bin, args, { ...spawnOptions, stdio: ["ignore", "pipe", "pipe"] });
|
|
2560
|
+
if (inputs && child.stdin) writeLiteralInputs(child.stdin, inputs.json);
|
|
2561
|
+
const engine = this.nativeProcess(child);
|
|
2562
|
+
let first;
|
|
2563
|
+
try {
|
|
2564
|
+
first = await this.admission(engine);
|
|
2565
|
+
} catch (cause) {
|
|
2566
|
+
await engine.stop();
|
|
2567
|
+
throw cause;
|
|
2568
|
+
}
|
|
2569
|
+
return this.processRun((0, import_node_crypto2.randomUUID)(), engine, first);
|
|
2126
2570
|
}
|
|
2127
2571
|
async attachRun(_id, _options) {
|
|
2128
2572
|
throw new NikaCompatibilityError(
|
|
@@ -2205,19 +2649,8 @@ RECEIPT MISMATCH: ${mismatch}
|
|
|
2205
2649
|
`)
|
|
2206
2650
|
};
|
|
2207
2651
|
}
|
|
2208
|
-
|
|
2652
|
+
nativeProcess(child) {
|
|
2209
2653
|
let settled = false;
|
|
2210
|
-
let lastEvent;
|
|
2211
|
-
let receipt;
|
|
2212
|
-
let outputs;
|
|
2213
|
-
let machineError;
|
|
2214
|
-
let settlement;
|
|
2215
|
-
let streamError;
|
|
2216
|
-
let refusal;
|
|
2217
|
-
let resolveDrained;
|
|
2218
|
-
const drained = new Promise((resolve) => {
|
|
2219
|
-
resolveDrained = resolve;
|
|
2220
|
-
});
|
|
2221
2654
|
let stderr = "";
|
|
2222
2655
|
child.stderr.setEncoding("utf8");
|
|
2223
2656
|
child.stderr.on("data", (chunk) => {
|
|
@@ -2225,94 +2658,154 @@ RECEIPT MISMATCH: ${mismatch}
|
|
|
2225
2658
|
});
|
|
2226
2659
|
const closed = new Promise((resolve, reject) => {
|
|
2227
2660
|
child.once("error", (cause) => {
|
|
2228
|
-
|
|
2661
|
+
reject(new NikaTransportError(
|
|
2229
2662
|
this.kind,
|
|
2230
2663
|
`Cannot spawn ${this.options.engine.bin}: ${cause.message}`,
|
|
2231
2664
|
{ cause }
|
|
2232
|
-
);
|
|
2233
|
-
reject(streamError);
|
|
2665
|
+
));
|
|
2234
2666
|
});
|
|
2235
2667
|
child.once("close", (code, signal) => {
|
|
2236
2668
|
settled = true;
|
|
2237
|
-
|
|
2238
|
-
else resolve(code ?? 3);
|
|
2669
|
+
resolve(code === null && signal ? 130 : code ?? 3);
|
|
2239
2670
|
});
|
|
2240
2671
|
});
|
|
2241
2672
|
closed.catch(() => {
|
|
2242
2673
|
});
|
|
2674
|
+
const lines = machineLines(child.stdout, this.options.machineBufferBytes, this.kind);
|
|
2675
|
+
return {
|
|
2676
|
+
child,
|
|
2677
|
+
lines,
|
|
2678
|
+
closed,
|
|
2679
|
+
settled: () => settled,
|
|
2680
|
+
diagnostics: () => stderr,
|
|
2681
|
+
stop: async () => {
|
|
2682
|
+
void lines.return(void 0).catch(() => {
|
|
2683
|
+
});
|
|
2684
|
+
child.stdin?.destroy();
|
|
2685
|
+
if (settled) return;
|
|
2686
|
+
child.kill("SIGTERM");
|
|
2687
|
+
if (await endsWithin(closed, STOP_GRACE_MILLISECONDS)) return;
|
|
2688
|
+
child.kill("SIGKILL");
|
|
2689
|
+
await endsWithin(closed, STOP_GRACE_MILLISECONDS);
|
|
2690
|
+
}
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
/**
|
|
2694
|
+
* The admission law (issue #121): resolve with the run's first event, or
|
|
2695
|
+
* reject before any Run exists. The first machine frame decides. A run
|
|
2696
|
+
* event is the engine's own admission evidence; an object without a `kind`
|
|
2697
|
+
* is its one pre-run refusal object; anything else proves neither and stays
|
|
2698
|
+
* a protocol fault. The SDK reads which one the engine wrote and judges
|
|
2699
|
+
* nothing itself.
|
|
2700
|
+
*/
|
|
2701
|
+
async admission(engine) {
|
|
2702
|
+
const { lines } = engine;
|
|
2243
2703
|
const kind = this.kind;
|
|
2244
|
-
const
|
|
2245
|
-
|
|
2246
|
-
const
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2704
|
+
const opening = await lines.next();
|
|
2705
|
+
if (opening.done) {
|
|
2706
|
+
const exitCode2 = await engine.closed;
|
|
2707
|
+
const taught = legacyStderrRefusal(engine.diagnostics(), exitCode2);
|
|
2708
|
+
if (taught) throw runRefusalError(kind, taught, exitCode2);
|
|
2709
|
+
throw new NikaProtocolError(kind, quoted(
|
|
2710
|
+
`Engine exited without a machine frame (exit ${exitCode2})`,
|
|
2711
|
+
engine.diagnostics()
|
|
2712
|
+
));
|
|
2713
|
+
}
|
|
2714
|
+
const line = opening.value;
|
|
2715
|
+
let frame;
|
|
2716
|
+
let unreadable;
|
|
2717
|
+
try {
|
|
2718
|
+
frame = parseMachineObject(line, kind);
|
|
2719
|
+
} catch (cause) {
|
|
2720
|
+
unreadable = cause;
|
|
2721
|
+
}
|
|
2722
|
+
let text = line;
|
|
2723
|
+
let refusal;
|
|
2724
|
+
if (frame) {
|
|
2725
|
+
if (isRunEvent(frame)) return frame;
|
|
2726
|
+
refusal = preRunRefusal(frame, kind);
|
|
2727
|
+
} else {
|
|
2728
|
+
refusal = legacyTeachingLine(line);
|
|
2729
|
+
if (!refusal) {
|
|
2730
|
+
const report = await legacyPrettyReport(
|
|
2731
|
+
line,
|
|
2732
|
+
lines,
|
|
2733
|
+
this.options.machineBufferBytes,
|
|
2734
|
+
kind
|
|
2735
|
+
);
|
|
2736
|
+
text = report?.text ?? line;
|
|
2737
|
+
if (!report?.frame) throw quotedProtocolError(unreadable, text, kind);
|
|
2738
|
+
refusal = preRunRefusal(report.frame, kind);
|
|
2250
2739
|
}
|
|
2251
|
-
|
|
2740
|
+
}
|
|
2741
|
+
if (!refusal) {
|
|
2742
|
+
throw new NikaProtocolError(kind, quoted(
|
|
2743
|
+
"The first machine frame was neither a run event nor a pre-run refusal object",
|
|
2744
|
+
text
|
|
2745
|
+
));
|
|
2746
|
+
}
|
|
2747
|
+
const extra = await lines.next();
|
|
2748
|
+
if (!extra.done) {
|
|
2749
|
+
throw new NikaProtocolError(kind, quoted(
|
|
2750
|
+
"Engine wrote more machine output after its pre-run refusal",
|
|
2751
|
+
extra.value
|
|
2752
|
+
));
|
|
2753
|
+
}
|
|
2754
|
+
const exitCode = await engine.closed;
|
|
2755
|
+
if (exitCode === 0) {
|
|
2756
|
+
throw new NikaProtocolError(kind, quoted(
|
|
2757
|
+
"Engine wrote a pre-run refusal but exited 0",
|
|
2758
|
+
refusal.message
|
|
2759
|
+
));
|
|
2760
|
+
}
|
|
2761
|
+
throw runRefusalError(kind, refusal, exitCode);
|
|
2762
|
+
}
|
|
2763
|
+
processRun(id, engine, first) {
|
|
2764
|
+
const { child, lines, closed } = engine;
|
|
2765
|
+
let lastEvent;
|
|
2766
|
+
let receipt;
|
|
2767
|
+
let outputs;
|
|
2768
|
+
let machineError;
|
|
2769
|
+
let settlement;
|
|
2770
|
+
let streamError;
|
|
2771
|
+
let resolveDrained;
|
|
2772
|
+
const drained = new Promise((resolve) => {
|
|
2773
|
+
resolveDrained = resolve;
|
|
2774
|
+
});
|
|
2775
|
+
const kind = this.kind;
|
|
2776
|
+
const observe = (event) => {
|
|
2777
|
+
lastEvent = event;
|
|
2778
|
+
receipt = eventReceipt(event) ?? receipt;
|
|
2779
|
+
outputs = eventOutputs(event) ?? outputs;
|
|
2780
|
+
const currentSettlement = eventSettlement(event, kind);
|
|
2781
|
+
machineError = currentSettlement ? eventError(event) : eventError(event) ?? machineError;
|
|
2782
|
+
settlement = currentSettlement ?? settlement;
|
|
2783
|
+
return event;
|
|
2252
2784
|
};
|
|
2253
2785
|
const events = {
|
|
2254
2786
|
[Symbol.asyncIterator]: async function* () {
|
|
2255
|
-
let buffer = "";
|
|
2256
|
-
child.stdout.setEncoding("utf8");
|
|
2257
2787
|
try {
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
buffer = buffer.slice(newline + 1);
|
|
2264
|
-
if (!line) continue;
|
|
2265
|
-
const event = lineEvent(line);
|
|
2266
|
-
if (!event) continue;
|
|
2267
|
-
lastEvent = event;
|
|
2268
|
-
receipt = eventReceipt(event) ?? receipt;
|
|
2269
|
-
outputs = eventOutputs(event) ?? outputs;
|
|
2270
|
-
const currentSettlement = eventSettlement(event, kind);
|
|
2271
|
-
machineError = currentSettlement ? eventError(event) : eventError(event) ?? machineError;
|
|
2272
|
-
settlement = currentSettlement ?? settlement;
|
|
2273
|
-
yield event;
|
|
2274
|
-
}
|
|
2275
|
-
if (Buffer.byteLength(buffer) > machineBufferBytes) {
|
|
2276
|
-
throw new NikaProtocolError(
|
|
2277
|
-
kind,
|
|
2278
|
-
`Native machine line exceeded ${machineBufferBytes} bytes`
|
|
2279
|
-
);
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
const tail = buffer.trim();
|
|
2283
|
-
if (tail) {
|
|
2284
|
-
const event = lineEvent(tail);
|
|
2285
|
-
if (event) {
|
|
2286
|
-
lastEvent = event;
|
|
2287
|
-
receipt = eventReceipt(event) ?? receipt;
|
|
2288
|
-
outputs = eventOutputs(event) ?? outputs;
|
|
2289
|
-
const currentSettlement = eventSettlement(event, kind);
|
|
2290
|
-
machineError = currentSettlement ? eventError(event) : eventError(event) ?? machineError;
|
|
2291
|
-
settlement = currentSettlement ?? settlement;
|
|
2292
|
-
yield event;
|
|
2293
|
-
}
|
|
2788
|
+
yield observe(first);
|
|
2789
|
+
for (; ; ) {
|
|
2790
|
+
const next = await lines.next();
|
|
2791
|
+
if (next.done) break;
|
|
2792
|
+
yield observe(machineLine(next.value, kind));
|
|
2294
2793
|
}
|
|
2295
2794
|
} catch (cause) {
|
|
2296
2795
|
streamError = cause instanceof Error ? cause : new Error(String(cause));
|
|
2297
|
-
if (!settled) child.kill("SIGTERM");
|
|
2796
|
+
if (!engine.settled()) child.kill("SIGTERM");
|
|
2298
2797
|
throw streamError;
|
|
2299
2798
|
} finally {
|
|
2799
|
+
void lines.return(void 0).catch(() => {
|
|
2800
|
+
});
|
|
2300
2801
|
resolveDrained();
|
|
2301
2802
|
}
|
|
2302
2803
|
}
|
|
2303
2804
|
};
|
|
2304
2805
|
const done = Promise.all([closed, drained]).then(([exitCode]) => {
|
|
2305
2806
|
if (streamError) throw streamError;
|
|
2306
|
-
if (refusal) {
|
|
2307
|
-
throw new NikaOperationError(
|
|
2308
|
-
"run",
|
|
2309
|
-
this.kind,
|
|
2310
|
-
refusal.code,
|
|
2311
|
-
refusal.line,
|
|
2312
|
-
{ status: exitCode }
|
|
2313
|
-
);
|
|
2314
|
-
}
|
|
2315
2807
|
const status = eventStatus(lastEvent) ?? statusForExit(exitCode, lastEvent?.kind);
|
|
2808
|
+
const stderr = engine.diagnostics();
|
|
2316
2809
|
return {
|
|
2317
2810
|
id,
|
|
2318
2811
|
status,
|
|
@@ -2336,12 +2829,12 @@ RECEIPT MISMATCH: ${mismatch}
|
|
|
2336
2829
|
throw new NikaCompatibilityError(
|
|
2337
2830
|
"runStatus",
|
|
2338
2831
|
this.kind,
|
|
2339
|
-
"A direct native process has no independent durable status authority; await run.
|
|
2832
|
+
"A direct native process has no independent durable status authority; await run.result()"
|
|
2340
2833
|
);
|
|
2341
2834
|
},
|
|
2342
2835
|
cancel: () => {
|
|
2343
2836
|
cancelPromise ??= Promise.resolve(
|
|
2344
|
-
settled ? {
|
|
2837
|
+
engine.settled() ? {
|
|
2345
2838
|
runId: id,
|
|
2346
2839
|
accepted: false,
|
|
2347
2840
|
status: "already_settled",
|
|
@@ -2355,11 +2848,7 @@ RECEIPT MISMATCH: ${mismatch}
|
|
|
2355
2848
|
);
|
|
2356
2849
|
return cancelPromise;
|
|
2357
2850
|
},
|
|
2358
|
-
cleanup:
|
|
2359
|
-
if (!settled) child.kill("SIGTERM");
|
|
2360
|
-
await closed.catch(() => {
|
|
2361
|
-
});
|
|
2362
|
-
}
|
|
2851
|
+
cleanup: () => engine.stop()
|
|
2363
2852
|
};
|
|
2364
2853
|
}
|
|
2365
2854
|
capture(args, signal) {
|
|
@@ -2403,14 +2892,56 @@ RECEIPT MISMATCH: ${mismatch}
|
|
|
2403
2892
|
});
|
|
2404
2893
|
});
|
|
2405
2894
|
}
|
|
2895
|
+
/** The verified engine's identity is kept: its capabilities gate what a run may ask. */
|
|
2406
2896
|
ensureReady() {
|
|
2407
|
-
this.ready ??= verifyNikaEngine(this.options.engine)
|
|
2897
|
+
this.ready ??= verifyNikaEngine(this.options.engine);
|
|
2408
2898
|
return this.ready;
|
|
2409
2899
|
}
|
|
2410
2900
|
};
|
|
2411
|
-
var
|
|
2412
|
-
|
|
2901
|
+
var INPUTS_LITERAL = "inputsLiteral";
|
|
2902
|
+
function writeLiteralInputs(stdin, json) {
|
|
2903
|
+
stdin.on("error", () => {
|
|
2904
|
+
});
|
|
2905
|
+
stdin.end(json);
|
|
2906
|
+
}
|
|
2907
|
+
var ENGINE_PREFIX2 = /^nika:\s*/;
|
|
2413
2908
|
var DIAGNOSTIC_EXCERPT_LIMIT = 240;
|
|
2909
|
+
var STOP_GRACE_MILLISECONDS = 2e3;
|
|
2910
|
+
function endsWithin(settling, milliseconds) {
|
|
2911
|
+
return new Promise((resolve) => {
|
|
2912
|
+
const timer = setTimeout(() => resolve(false), milliseconds);
|
|
2913
|
+
const ended = () => {
|
|
2914
|
+
clearTimeout(timer);
|
|
2915
|
+
resolve(true);
|
|
2916
|
+
};
|
|
2917
|
+
settling.then(ended, ended);
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
async function* machineLines(stdout, limit, kind) {
|
|
2921
|
+
const bounded = (raw) => {
|
|
2922
|
+
if (Buffer.byteLength(raw) > limit) {
|
|
2923
|
+
throw new NikaProtocolError(
|
|
2924
|
+
kind,
|
|
2925
|
+
`Native machine line exceeded ${limit} bytes; machineBufferBytes bounds one machine frame`
|
|
2926
|
+
);
|
|
2927
|
+
}
|
|
2928
|
+
return raw.trim();
|
|
2929
|
+
};
|
|
2930
|
+
let buffer = "";
|
|
2931
|
+
stdout.setEncoding("utf8");
|
|
2932
|
+
for await (const chunk of stdout) {
|
|
2933
|
+
buffer += String(chunk);
|
|
2934
|
+
let newline;
|
|
2935
|
+
while ((newline = buffer.indexOf("\n")) >= 0) {
|
|
2936
|
+
const line = bounded(buffer.slice(0, newline));
|
|
2937
|
+
buffer = buffer.slice(newline + 1);
|
|
2938
|
+
if (line) yield line;
|
|
2939
|
+
}
|
|
2940
|
+
bounded(buffer);
|
|
2941
|
+
}
|
|
2942
|
+
const tail = bounded(buffer);
|
|
2943
|
+
if (tail) yield tail;
|
|
2944
|
+
}
|
|
2414
2945
|
function reportObject(text) {
|
|
2415
2946
|
const trimmed = text.trim();
|
|
2416
2947
|
if (!trimmed) return void 0;
|
|
@@ -2423,32 +2954,35 @@ function reportObject(text) {
|
|
|
2423
2954
|
return machineObject(value);
|
|
2424
2955
|
}
|
|
2425
2956
|
function stripEnginePrefix(text) {
|
|
2426
|
-
return text.trim().replace(
|
|
2957
|
+
return text.trim().replace(ENGINE_PREFIX2, "");
|
|
2427
2958
|
}
|
|
2428
2959
|
function diagnosticExcerpt(text, limit = DIAGNOSTIC_EXCERPT_LIMIT) {
|
|
2429
2960
|
const single = stripEnginePrefix(text).replace(/\s+/g, " ").trim();
|
|
2430
2961
|
if (!single) return void 0;
|
|
2431
2962
|
return single.length > limit ? `${single.slice(0, limit)}\u2026` : single;
|
|
2432
2963
|
}
|
|
2433
|
-
function engineRefusal(line) {
|
|
2434
|
-
const code = REFUSAL_CODE.exec(line)?.[1];
|
|
2435
|
-
return code ? { code, line } : void 0;
|
|
2436
|
-
}
|
|
2437
2964
|
function machineLine(line, kind) {
|
|
2438
2965
|
try {
|
|
2439
2966
|
return parseMachineObject(line, kind);
|
|
2440
2967
|
} catch (cause) {
|
|
2441
|
-
|
|
2442
|
-
const excerpt = diagnosticExcerpt(line);
|
|
2443
|
-
throw new NikaProtocolError(
|
|
2444
|
-
kind,
|
|
2445
|
-
excerpt ? `${verdict}: ${excerpt}` : verdict,
|
|
2446
|
-
{ cause: cause instanceof Error ? cause : void 0 }
|
|
2447
|
-
);
|
|
2968
|
+
throw quotedProtocolError(cause, line, kind);
|
|
2448
2969
|
}
|
|
2449
2970
|
}
|
|
2450
|
-
function
|
|
2971
|
+
function quotedProtocolError(cause, text, kind) {
|
|
2972
|
+
const verdict = cause instanceof Error ? cause.message : "Engine machine output was unreadable";
|
|
2973
|
+
return new NikaProtocolError(
|
|
2974
|
+
kind,
|
|
2975
|
+
quoted(verdict, text),
|
|
2976
|
+
{ cause: cause instanceof Error ? cause : void 0 }
|
|
2977
|
+
);
|
|
2978
|
+
}
|
|
2979
|
+
function quoted(verdict, text) {
|
|
2980
|
+
const excerpt = diagnosticExcerpt(text);
|
|
2981
|
+
return excerpt ? `${verdict}: ${excerpt}` : verdict;
|
|
2982
|
+
}
|
|
2983
|
+
function runFlags(options, literal) {
|
|
2451
2984
|
const flags = [];
|
|
2985
|
+
if (literal) flags.push("--inputs-json", "-");
|
|
2452
2986
|
for (const [key, value] of Object.entries(options.vars ?? {})) {
|
|
2453
2987
|
flags.push("--var", `${key}=${String(value)}`);
|
|
2454
2988
|
}
|
|
@@ -2493,18 +3027,87 @@ function receiptMismatch(receipt, manifest) {
|
|
|
2493
3027
|
return void 0;
|
|
2494
3028
|
}
|
|
2495
3029
|
|
|
3030
|
+
// src/lib/run-events.ts
|
|
3031
|
+
var STARTED = /* @__PURE__ */ new Set(["workflow_started", "execution.started"]);
|
|
3032
|
+
var TASKS = /* @__PURE__ */ new Map([
|
|
3033
|
+
["task_scheduled", "task.scheduled"],
|
|
3034
|
+
["task_started", "task.started"],
|
|
3035
|
+
["task_completed", "task.completed"],
|
|
3036
|
+
["task_failed", "task.failed"]
|
|
3037
|
+
]);
|
|
3038
|
+
var SETTLEMENT = /* @__PURE__ */ new Set(["run_settled", "execution.settled"]);
|
|
3039
|
+
var SETTLEMENT_WORDS = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled"]);
|
|
3040
|
+
var SETTLED = /* @__PURE__ */ new Map([
|
|
3041
|
+
["run_settled", SETTLEMENT_WORDS],
|
|
3042
|
+
["execution.settled", SETTLEMENT_WORDS],
|
|
3043
|
+
["execution.cancelled", /* @__PURE__ */ new Set(["cancelled"])],
|
|
3044
|
+
["execution.refused", /* @__PURE__ */ new Set(["failed"])]
|
|
3045
|
+
]);
|
|
3046
|
+
var INTERRUPTED = /* @__PURE__ */ new Set([
|
|
3047
|
+
"workflow_interrupted",
|
|
3048
|
+
"execution.interrupted",
|
|
3049
|
+
"interrupted"
|
|
3050
|
+
]);
|
|
3051
|
+
function semanticRunEvent(raw, transport) {
|
|
3052
|
+
const stated = eventStatus(raw);
|
|
3053
|
+
const kind = semanticKind(raw.kind, stated);
|
|
3054
|
+
const named = kind !== "engine.event";
|
|
3055
|
+
const status = named ? stated : void 0;
|
|
3056
|
+
const task = kind.startsWith("task.") ? eventTask(raw) : void 0;
|
|
3057
|
+
const failed = kind === "task.failed" || kind === "run.settled" && status === "failed";
|
|
3058
|
+
const error = failed ? eventError(raw) : void 0;
|
|
3059
|
+
const sequence = transport === "http" && Number.isSafeInteger(raw.sequence) ? raw.sequence : void 0;
|
|
3060
|
+
return Object.freeze({
|
|
3061
|
+
kind,
|
|
3062
|
+
transport,
|
|
3063
|
+
...status !== void 0 ? { status } : {},
|
|
3064
|
+
...sequence !== void 0 ? { sequence } : {},
|
|
3065
|
+
...task !== void 0 ? { task } : {},
|
|
3066
|
+
...error !== void 0 ? { error } : {},
|
|
3067
|
+
raw
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
function semanticKind(kind, status) {
|
|
3071
|
+
if (typeof kind !== "string") return "engine.event";
|
|
3072
|
+
if (STARTED.has(kind)) return "run.started";
|
|
3073
|
+
const task = TASKS.get(kind);
|
|
3074
|
+
if (task !== void 0) return task;
|
|
3075
|
+
if (kind === "run_sealed") return "run.sealed";
|
|
3076
|
+
if (INTERRUPTED.has(kind)) return status === "interrupted" ? "run.interrupted" : "engine.event";
|
|
3077
|
+
if (status === void 0) return "engine.event";
|
|
3078
|
+
if (status === "paused") return SETTLEMENT.has(kind) ? "run.waiting" : "engine.event";
|
|
3079
|
+
return SETTLED.get(kind)?.has(status) === true ? "run.settled" : "engine.event";
|
|
3080
|
+
}
|
|
3081
|
+
function eventTask(raw) {
|
|
3082
|
+
if (typeof raw.task === "string") return raw.task;
|
|
3083
|
+
const fields = Array.isArray(raw.fields) ? raw.fields : [];
|
|
3084
|
+
for (const field of fields) {
|
|
3085
|
+
const row = machineObject(field);
|
|
3086
|
+
if (row?.key === "task" && typeof row.value === "string") return row.value;
|
|
3087
|
+
}
|
|
3088
|
+
return void 0;
|
|
3089
|
+
}
|
|
3090
|
+
|
|
2496
3091
|
// src/lib/run-session.ts
|
|
2497
3092
|
var RunSession = class {
|
|
2498
|
-
constructor(source, eventBufferSize) {
|
|
3093
|
+
constructor(source, eventBufferSize, transport) {
|
|
2499
3094
|
this.source = source;
|
|
2500
3095
|
this.eventBufferSize = eventBufferSize;
|
|
3096
|
+
this.transport = transport;
|
|
2501
3097
|
this.done = new Promise((resolve, reject) => {
|
|
2502
3098
|
this.resolveDone = resolve;
|
|
2503
3099
|
this.rejectDone = reject;
|
|
2504
3100
|
});
|
|
2505
3101
|
this.done.catch(() => {
|
|
2506
3102
|
});
|
|
2507
|
-
this.run = Object.freeze({
|
|
3103
|
+
this.run = Object.freeze({
|
|
3104
|
+
id: source.id,
|
|
3105
|
+
events: (options) => this.semanticEvents(options),
|
|
3106
|
+
result: () => this.done,
|
|
3107
|
+
status: () => this.status(),
|
|
3108
|
+
cancel: () => this.cancel(),
|
|
3109
|
+
done: this.done
|
|
3110
|
+
});
|
|
2508
3111
|
void this.observeSettlement();
|
|
2509
3112
|
void this.pump();
|
|
2510
3113
|
}
|
|
@@ -2516,7 +3119,30 @@ var RunSession = class {
|
|
|
2516
3119
|
rejectDone;
|
|
2517
3120
|
terminal = false;
|
|
2518
3121
|
cancelPromise;
|
|
2519
|
-
|
|
3122
|
+
/** Every frame the pump delivered; `history` holds at most the last `eventBufferSize`. */
|
|
3123
|
+
observed = 0;
|
|
3124
|
+
/**
|
|
3125
|
+
* The lifecycle vocabulary over the same bounded view: one history, one
|
|
3126
|
+
* queue, and a pure per-frame projection at the edge. No second event bus.
|
|
3127
|
+
*/
|
|
3128
|
+
semanticEvents(options = {}) {
|
|
3129
|
+
const view = this.events(options);
|
|
3130
|
+
const transport = this.transport;
|
|
3131
|
+
const lifecycle = {
|
|
3132
|
+
[Symbol.asyncIterator]: () => lifecycle,
|
|
3133
|
+
next: async () => {
|
|
3134
|
+
const step = await view.next();
|
|
3135
|
+
if (step.done) return { value: void 0, done: true };
|
|
3136
|
+
return { value: semanticRunEvent(step.value, transport), done: false };
|
|
3137
|
+
},
|
|
3138
|
+
return: async () => {
|
|
3139
|
+
await view.return?.();
|
|
3140
|
+
return { value: void 0, done: true };
|
|
3141
|
+
}
|
|
3142
|
+
};
|
|
3143
|
+
return lifecycle;
|
|
3144
|
+
}
|
|
3145
|
+
/** The protocol frames, exactly as the transport delivered them. */
|
|
2520
3146
|
events(options = {}) {
|
|
2521
3147
|
const requested = options.bufferSize ?? this.eventBufferSize;
|
|
2522
3148
|
if (!Number.isInteger(requested) || requested < 1 || requested > this.eventBufferSize) {
|
|
@@ -2524,8 +3150,11 @@ var RunSession = class {
|
|
|
2524
3150
|
`events bufferSize must be an integer from 1 to ${this.eventBufferSize}`
|
|
2525
3151
|
);
|
|
2526
3152
|
}
|
|
2527
|
-
if (this.
|
|
2528
|
-
throw new NikaEventBufferOverflowError(this.source.id, requested
|
|
3153
|
+
if (this.observed > requested) {
|
|
3154
|
+
throw new NikaEventBufferOverflowError(this.source.id, requested, {
|
|
3155
|
+
observed: this.observed,
|
|
3156
|
+
retained: this.history.length
|
|
3157
|
+
});
|
|
2529
3158
|
}
|
|
2530
3159
|
const subscription = new EventSubscription(
|
|
2531
3160
|
this.source.id,
|
|
@@ -2573,11 +3202,9 @@ var RunSession = class {
|
|
|
2573
3202
|
async pump() {
|
|
2574
3203
|
try {
|
|
2575
3204
|
for await (const event of this.source.events) {
|
|
3205
|
+
this.observed += 1;
|
|
2576
3206
|
this.history.push(event);
|
|
2577
|
-
if (this.history.length > this.eventBufferSize)
|
|
2578
|
-
this.history.shift();
|
|
2579
|
-
this.historyOverflowed = true;
|
|
2580
|
-
}
|
|
3207
|
+
if (this.history.length > this.eventBufferSize) this.history.shift();
|
|
2581
3208
|
for (const subscriber of [...this.subscribers]) subscriber.push(event);
|
|
2582
3209
|
}
|
|
2583
3210
|
this.resolveDone(await this.source.done);
|
|
@@ -2669,6 +3296,11 @@ var EventSubscription = class {
|
|
|
2669
3296
|
}
|
|
2670
3297
|
};
|
|
2671
3298
|
|
|
3299
|
+
// src/results.ts
|
|
3300
|
+
function isNikaRunSucceeded(result) {
|
|
3301
|
+
return result.status === "succeeded";
|
|
3302
|
+
}
|
|
3303
|
+
|
|
2672
3304
|
// src/events.ts
|
|
2673
3305
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
2674
3306
|
"succeeded",
|
|
@@ -2687,7 +3319,7 @@ function isNikaRunSealedEvent(event) {
|
|
|
2687
3319
|
}
|
|
2688
3320
|
|
|
2689
3321
|
// src/index.ts
|
|
2690
|
-
var DEFAULT_EVENT_BUFFER_SIZE =
|
|
3322
|
+
var DEFAULT_EVENT_BUFFER_SIZE = 4096;
|
|
2691
3323
|
var DEFAULT_MACHINE_BUFFER_BYTES = 64 * 1024;
|
|
2692
3324
|
var DEFAULT_REQUEST_TIMEOUT = 3e4;
|
|
2693
3325
|
var Nika = class {
|
|
@@ -2742,25 +3374,30 @@ var Nika = class {
|
|
|
2742
3374
|
return this.transport.check(workflowName(workflow), options);
|
|
2743
3375
|
}
|
|
2744
3376
|
/**
|
|
3377
|
+
* Resolves with the run's handle once the engine admitted it, and rejects
|
|
3378
|
+
* without one when the engine refused it. The handle owns the lifecycle:
|
|
3379
|
+
* `run.events()`, `run.result()`, `run.status()`, `run.cancel()`.
|
|
3380
|
+
*
|
|
2745
3381
|
* `Outputs` is the caller's projection of the engine-emitted outputs map;
|
|
2746
3382
|
* the SDK transports outputs without validating their shape.
|
|
2747
3383
|
*/
|
|
2748
3384
|
async run(workflow, options = {}) {
|
|
2749
3385
|
const source = await this.transport.startRun(workflowName(workflow), options);
|
|
2750
|
-
|
|
2751
|
-
this.sessions.set(session.run, session);
|
|
2752
|
-
return session.run;
|
|
3386
|
+
return this.own(source);
|
|
2753
3387
|
}
|
|
2754
|
-
/**
|
|
3388
|
+
/**
|
|
3389
|
+
* The one recovery door: reattach this client process to an
|
|
3390
|
+
* already-admitted durable HTTP job and get a full `NikaRun` back. Pass the
|
|
3391
|
+
* last `event.sequence` you fully processed as `lastEventId`. A native
|
|
3392
|
+
* process is process-bound and refuses with a typed compatibility error.
|
|
3393
|
+
*/
|
|
2755
3394
|
async attachRun(id, options = {}) {
|
|
2756
3395
|
const lastEventId = options.lastEventId ?? 0;
|
|
2757
3396
|
if (!Number.isSafeInteger(lastEventId) || lastEventId < 0) {
|
|
2758
3397
|
throw new RangeError("lastEventId must be a non-negative safe integer");
|
|
2759
3398
|
}
|
|
2760
3399
|
const source = await this.transport.attachRun(jobId(id), { lastEventId });
|
|
2761
|
-
|
|
2762
|
-
this.sessions.set(session.run, session);
|
|
2763
|
-
return session.run;
|
|
3400
|
+
return this.own(source);
|
|
2764
3401
|
}
|
|
2765
3402
|
/** List contained workflow names from a resident HTTP authority. */
|
|
2766
3403
|
listWorkflows() {
|
|
@@ -2770,13 +3407,34 @@ var Nika = class {
|
|
|
2770
3407
|
workflow(name) {
|
|
2771
3408
|
return this.transport.workflow(workflowName(name));
|
|
2772
3409
|
}
|
|
3410
|
+
/**
|
|
3411
|
+
* The run's events in the protocol vocabulary of its transport
|
|
3412
|
+
* (`workflow_*` · `task_*` · `run_*` natively, `execution.*` over HTTP).
|
|
3413
|
+
*
|
|
3414
|
+
* @deprecated Use `run.events()`: one lifecycle vocabulary on both
|
|
3415
|
+
* transports, with this same frame kept on `event.raw`. This wrapper is a
|
|
3416
|
+
* compatibility door for one release train, counted from publication: it
|
|
3417
|
+
* ships unchanged in the first published train that carries `run.events()`,
|
|
3418
|
+
* and the earliest train that may remove it is the one after, as announced
|
|
3419
|
+
* in that train's release notes. It accepts only a run this client created.
|
|
3420
|
+
*/
|
|
2773
3421
|
events(run, options = {}) {
|
|
2774
3422
|
return this.session(run).events(options);
|
|
2775
3423
|
}
|
|
3424
|
+
/**
|
|
3425
|
+
* @deprecated Use `run.cancel()`; both return the one memoized request.
|
|
3426
|
+
* Kept through the same compatibility window as `events(run)`. It accepts
|
|
3427
|
+
* only a run this client created.
|
|
3428
|
+
*/
|
|
2776
3429
|
cancel(run) {
|
|
2777
3430
|
return this.session(run).cancel();
|
|
2778
3431
|
}
|
|
2779
|
-
/**
|
|
3432
|
+
/**
|
|
3433
|
+
* Read the current durable status without waiting for terminal settlement.
|
|
3434
|
+
*
|
|
3435
|
+
* @deprecated Use `run.status()`. Kept through the same compatibility
|
|
3436
|
+
* window as `events(run)`. It accepts only a run this client created.
|
|
3437
|
+
*/
|
|
2780
3438
|
status(run) {
|
|
2781
3439
|
return this.session(run).status();
|
|
2782
3440
|
}
|
|
@@ -2793,6 +3451,12 @@ var Nika = class {
|
|
|
2793
3451
|
}
|
|
2794
3452
|
return this.transport.traceVerify(receipt, options);
|
|
2795
3453
|
}
|
|
3454
|
+
/** One session per admitted run, owned by this client and by no registry. */
|
|
3455
|
+
own(source) {
|
|
3456
|
+
const session = new RunSession(source, this.eventBufferSize, this.transportKind);
|
|
3457
|
+
this.sessions.set(session.run, session);
|
|
3458
|
+
return session.run;
|
|
3459
|
+
}
|
|
2796
3460
|
session(run) {
|
|
2797
3461
|
const session = this.sessions.get(run);
|
|
2798
3462
|
if (!session) throw new NikaRunOwnershipError();
|
|
@@ -2885,5 +3549,6 @@ function jobId(value) {
|
|
|
2885
3549
|
NikaTransportError,
|
|
2886
3550
|
isNikaRunSealedEvent,
|
|
2887
3551
|
isNikaRunSettledEvent,
|
|
3552
|
+
isNikaRunSucceeded,
|
|
2888
3553
|
isNikaTerminalEvent
|
|
2889
3554
|
});
|