bitfab 0.61.2 → 0.61.4
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/dist/autoTrace.cjs.map +1 -1
- package/dist/autoTrace.d.cts +1 -0
- package/dist/autoTrace.d.ts +1 -0
- package/dist/autoTrace.js +1 -1
- package/dist/{chunk-INA6W7OB.js → chunk-54EGXUZ3.js} +32 -3
- package/dist/chunk-54EGXUZ3.js.map +1 -0
- package/dist/{chunk-BMJNDIVA.js → chunk-K4XCWQBC.js} +1 -1
- package/dist/chunk-K4XCWQBC.js.map +1 -0
- package/dist/{chunk-A4UVO6BQ.js → chunk-PSAEQ4OW.js} +480 -15
- package/dist/chunk-PSAEQ4OW.js.map +1 -0
- package/dist/{chunk-ONZFMRRF.js → chunk-VWRCLZCA.js} +30 -3
- package/dist/chunk-VWRCLZCA.js.map +1 -0
- package/dist/{chunk-MTK6PZHA.js → chunk-WPYBJGTG.js} +21 -3
- package/dist/chunk-WPYBJGTG.js.map +1 -0
- package/dist/{chunk-LUOCT4HG.js → chunk-XGRQMQ2R.js} +38 -7
- package/dist/chunk-XGRQMQ2R.js.map +1 -0
- package/dist/{http-2R6MSRVA.js → http-C2TCBBQV.js} +2 -2
- package/dist/{http-KM6QWKAP.js → http-ONLJPP6W.js} +2 -2
- package/dist/index.cjs +568 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -3
- package/dist/index.d.ts +131 -3
- package/dist/index.js +4 -4
- package/dist/node.cjs +568 -15
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +4 -4
- package/dist/{replay-6UUBJBLK.js → replay-RM24QKEA.js} +3 -3
- package/dist/replayCli.js +2 -2
- package/dist/seedCli.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-A4UVO6BQ.js.map +0 -1
- package/dist/chunk-BMJNDIVA.js.map +0 -1
- package/dist/chunk-INA6W7OB.js.map +0 -1
- package/dist/chunk-LUOCT4HG.js.map +0 -1
- package/dist/chunk-MTK6PZHA.js.map +0 -1
- package/dist/chunk-ONZFMRRF.js.map +0 -1
- /package/dist/{http-2R6MSRVA.js.map → http-C2TCBBQV.js.map} +0 -0
- /package/dist/{http-KM6QWKAP.js.map → http-ONLJPP6W.js.map} +0 -0
- /package/dist/{replay-6UUBJBLK.js.map → replay-RM24QKEA.js.map} +0 -0
package/dist/node.cjs
CHANGED
|
@@ -97,7 +97,7 @@ var __version__, __packageName__;
|
|
|
97
97
|
var init_version_generated = __esm({
|
|
98
98
|
"src/version.generated.ts"() {
|
|
99
99
|
"use strict";
|
|
100
|
-
__version__ = "0.61.
|
|
100
|
+
__version__ = "0.61.4";
|
|
101
101
|
__packageName__ = "bitfab";
|
|
102
102
|
}
|
|
103
103
|
});
|
|
@@ -1084,6 +1084,7 @@ var init_simulationPlan = __esm({
|
|
|
1084
1084
|
"input_meta",
|
|
1085
1085
|
"output",
|
|
1086
1086
|
"output_meta",
|
|
1087
|
+
"replay_recording",
|
|
1087
1088
|
"prompt"
|
|
1088
1089
|
];
|
|
1089
1090
|
SimulationPlan = class {
|
|
@@ -2292,7 +2293,7 @@ function warnDroppedExternalTrace(error) {
|
|
|
2292
2293
|
function experimentIdWireFields(experimentId) {
|
|
2293
2294
|
return { experimentId, testRunId: experimentId };
|
|
2294
2295
|
}
|
|
2295
|
-
var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, REPLAY_COMPLETE_REQUEST_TIMEOUT_MS, OTLP_TRACES_ENDPOINT, RETRYABLE_STATUSES, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCLE_TIMEOUT_MS2, pendingTracePromises, liveHttpClients, carrierSeq, HttpClient;
|
|
2296
|
+
var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, REPLAY_COMPLETE_REQUEST_TIMEOUT_MS, REPLAY_INTERRUPT_REQUEST_TIMEOUT_MS, REPLAY_HEARTBEAT_REQUEST_TIMEOUT_MS, OTLP_TRACES_ENDPOINT, RETRYABLE_STATUSES, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCLE_TIMEOUT_MS2, pendingTracePromises, liveHttpClients, carrierSeq, HttpClient;
|
|
2296
2297
|
var init_http = __esm({
|
|
2297
2298
|
"src/http.ts"() {
|
|
2298
2299
|
"use strict";
|
|
@@ -2311,6 +2312,8 @@ var init_http = __esm({
|
|
|
2311
2312
|
init_warnOnce();
|
|
2312
2313
|
REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS = 3e5;
|
|
2313
2314
|
REPLAY_COMPLETE_REQUEST_TIMEOUT_MS = 12e4;
|
|
2315
|
+
REPLAY_INTERRUPT_REQUEST_TIMEOUT_MS = 5e3;
|
|
2316
|
+
REPLAY_HEARTBEAT_REQUEST_TIMEOUT_MS = 5e3;
|
|
2314
2317
|
OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
2315
2318
|
RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
2316
2319
|
EXIT_FLUSH_TIMEOUT_MS = 5e3;
|
|
@@ -2898,8 +2901,11 @@ var init_http = __esm({
|
|
|
2898
2901
|
* Start a replay session by fetching historical traces.
|
|
2899
2902
|
* Blocking call - creates an experiment and returns lightweight item references.
|
|
2900
2903
|
*/
|
|
2901
|
-
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions) {
|
|
2904
|
+
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetIds, graderIds, dbBranchSettings, attempts, includeOriginalMetadata, onlyWithAssertions, judgeAssertions, experimentalSelectiveReplay) {
|
|
2902
2905
|
const payload = { traceFunctionKey };
|
|
2906
|
+
if (experimentalSelectiveReplay !== void 0) {
|
|
2907
|
+
payload.experimentalSelectiveReplay = experimentalSelectiveReplay;
|
|
2908
|
+
}
|
|
2903
2909
|
if (limit !== void 0) {
|
|
2904
2910
|
payload.limit = limit;
|
|
2905
2911
|
}
|
|
@@ -3120,6 +3126,28 @@ var init_http = __esm({
|
|
|
3120
3126
|
{ timeout: REPLAY_COMPLETE_REQUEST_TIMEOUT_MS }
|
|
3121
3127
|
);
|
|
3122
3128
|
}
|
|
3129
|
+
async interruptReplay(experimentId) {
|
|
3130
|
+
try {
|
|
3131
|
+
await this.request(
|
|
3132
|
+
"/api/sdk/replay/interrupt",
|
|
3133
|
+
experimentIdWireFields(experimentId),
|
|
3134
|
+
{ timeout: REPLAY_INTERRUPT_REQUEST_TIMEOUT_MS }
|
|
3135
|
+
);
|
|
3136
|
+
return true;
|
|
3137
|
+
} catch {
|
|
3138
|
+
return false;
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
async heartbeatReplay(experimentId) {
|
|
3142
|
+
try {
|
|
3143
|
+
await this.request(
|
|
3144
|
+
"/api/sdk/replay/heartbeat",
|
|
3145
|
+
experimentIdWireFields(experimentId),
|
|
3146
|
+
{ timeout: REPLAY_HEARTBEAT_REQUEST_TIMEOUT_MS }
|
|
3147
|
+
);
|
|
3148
|
+
} catch {
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3123
3151
|
async getReseedSource(traceId) {
|
|
3124
3152
|
return this.get(
|
|
3125
3153
|
`/api/sdk/traces/${encodeURIComponent(traceId)}/reseedSource`
|
|
@@ -3623,6 +3651,399 @@ var init_integrationFixture = __esm({
|
|
|
3623
3651
|
}
|
|
3624
3652
|
});
|
|
3625
3653
|
|
|
3654
|
+
// src/replayHeartbeat.ts
|
|
3655
|
+
function startReplayHeartbeat(send) {
|
|
3656
|
+
let stopped = false;
|
|
3657
|
+
let inFlight;
|
|
3658
|
+
const timer = setInterval(() => {
|
|
3659
|
+
if (stopped || inFlight !== void 0) {
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
inFlight = send().catch(() => void 0).finally(() => {
|
|
3663
|
+
inFlight = void 0;
|
|
3664
|
+
});
|
|
3665
|
+
}, REPLAY_HEARTBEAT_INTERVAL_MS);
|
|
3666
|
+
unrefTimer(timer);
|
|
3667
|
+
return {
|
|
3668
|
+
stop: async () => {
|
|
3669
|
+
stopped = true;
|
|
3670
|
+
clearInterval(timer);
|
|
3671
|
+
await inFlight;
|
|
3672
|
+
}
|
|
3673
|
+
};
|
|
3674
|
+
}
|
|
3675
|
+
var REPLAY_HEARTBEAT_INTERVAL_MS;
|
|
3676
|
+
var init_replayHeartbeat = __esm({
|
|
3677
|
+
"src/replayHeartbeat.ts"() {
|
|
3678
|
+
"use strict";
|
|
3679
|
+
init_unrefTimer();
|
|
3680
|
+
REPLAY_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
3681
|
+
}
|
|
3682
|
+
});
|
|
3683
|
+
|
|
3684
|
+
// src/replayInterrupt.ts
|
|
3685
|
+
function signalCapableProcess() {
|
|
3686
|
+
if (typeof process === "undefined" || typeof process.on !== "function") {
|
|
3687
|
+
return void 0;
|
|
3688
|
+
}
|
|
3689
|
+
return process;
|
|
3690
|
+
}
|
|
3691
|
+
function describeReplayInterrupt(experimentId, markedInterrupted) {
|
|
3692
|
+
if (markedInterrupted) {
|
|
3693
|
+
return `[replay] Experiment ${experimentId} interrupted.`;
|
|
3694
|
+
}
|
|
3695
|
+
return `[replay] Experiment ${experimentId} stopped, but Bitfab could not mark it interrupted. It will be marked interrupted within 15 minutes. A resume in the next two minutes may be refused as still running.`;
|
|
3696
|
+
}
|
|
3697
|
+
function reportInterrupted(experimentId, markedInterrupted) {
|
|
3698
|
+
try {
|
|
3699
|
+
console.error(describeReplayInterrupt(experimentId, markedInterrupted));
|
|
3700
|
+
} catch {
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
function installReplayInterruptHandlers(target) {
|
|
3704
|
+
const host = signalCapableProcess();
|
|
3705
|
+
if (host === void 0) {
|
|
3706
|
+
return { remove: () => {
|
|
3707
|
+
}, interrupted: () => false };
|
|
3708
|
+
}
|
|
3709
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
3710
|
+
const removeHandlers = () => {
|
|
3711
|
+
for (const [signal, handler] of handlers) {
|
|
3712
|
+
host.removeListener(signal, handler);
|
|
3713
|
+
}
|
|
3714
|
+
handlers.clear();
|
|
3715
|
+
};
|
|
3716
|
+
let interrupting = false;
|
|
3717
|
+
const handleSignal = async (signal) => {
|
|
3718
|
+
const hostHandlesSignal = host.listenerCount(signal) > 1;
|
|
3719
|
+
const markedInterrupted = await target.interrupt();
|
|
3720
|
+
removeHandlers();
|
|
3721
|
+
if (target.onInterrupt !== void 0) {
|
|
3722
|
+
try {
|
|
3723
|
+
target.onInterrupt({
|
|
3724
|
+
experimentId: target.experimentId,
|
|
3725
|
+
experimentUrl: target.experimentUrl,
|
|
3726
|
+
signal,
|
|
3727
|
+
markedInterrupted
|
|
3728
|
+
});
|
|
3729
|
+
} catch {
|
|
3730
|
+
}
|
|
3731
|
+
return;
|
|
3732
|
+
}
|
|
3733
|
+
reportInterrupted(target.experimentId, markedInterrupted);
|
|
3734
|
+
if (!hostHandlesSignal) {
|
|
3735
|
+
host.kill(host.pid, signal);
|
|
3736
|
+
}
|
|
3737
|
+
};
|
|
3738
|
+
for (const signal of REPLAY_INTERRUPT_SIGNALS) {
|
|
3739
|
+
const handler = () => {
|
|
3740
|
+
if (interrupting) {
|
|
3741
|
+
return;
|
|
3742
|
+
}
|
|
3743
|
+
interrupting = true;
|
|
3744
|
+
void handleSignal(signal);
|
|
3745
|
+
};
|
|
3746
|
+
handlers.set(signal, handler);
|
|
3747
|
+
host.on(signal, handler);
|
|
3748
|
+
}
|
|
3749
|
+
return { remove: removeHandlers, interrupted: () => interrupting };
|
|
3750
|
+
}
|
|
3751
|
+
var REPLAY_INTERRUPT_SIGNALS;
|
|
3752
|
+
var init_replayInterrupt = __esm({
|
|
3753
|
+
"src/replayInterrupt.ts"() {
|
|
3754
|
+
"use strict";
|
|
3755
|
+
REPLAY_INTERRUPT_SIGNALS = [
|
|
3756
|
+
"SIGINT",
|
|
3757
|
+
"SIGTERM"
|
|
3758
|
+
];
|
|
3759
|
+
}
|
|
3760
|
+
});
|
|
3761
|
+
|
|
3762
|
+
// src/selectiveReplay.ts
|
|
3763
|
+
function replayNodeKey(node) {
|
|
3764
|
+
return JSON.stringify([node.traceFunctionKey, node.spanName]);
|
|
3765
|
+
}
|
|
3766
|
+
function jsonFingerprint(value) {
|
|
3767
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3768
|
+
function encode(input) {
|
|
3769
|
+
if (input === null || typeof input === "boolean") {
|
|
3770
|
+
return JSON.stringify(input);
|
|
3771
|
+
}
|
|
3772
|
+
if (typeof input === "string" && !input.startsWith("<unserializable")) {
|
|
3773
|
+
return JSON.stringify(input);
|
|
3774
|
+
}
|
|
3775
|
+
if (typeof input === "number" && Number.isFinite(input) && !Object.is(input, -0)) {
|
|
3776
|
+
return JSON.stringify(input);
|
|
3777
|
+
}
|
|
3778
|
+
if (typeof input !== "object" || input === null || seen.has(input)) {
|
|
3779
|
+
throw new Error("Not a lossless JSON value");
|
|
3780
|
+
}
|
|
3781
|
+
seen.add(input);
|
|
3782
|
+
const prototype = Object.getPrototypeOf(input);
|
|
3783
|
+
if (prototype !== (Array.isArray(input) ? Array.prototype : Object.prototype)) {
|
|
3784
|
+
throw new Error("Not a plain JSON object");
|
|
3785
|
+
}
|
|
3786
|
+
if (Object.getOwnPropertySymbols(input).length > 0) {
|
|
3787
|
+
throw new Error("Symbol properties are not recorded");
|
|
3788
|
+
}
|
|
3789
|
+
const keys = Object.keys(input);
|
|
3790
|
+
if (Object.getOwnPropertyNames(input).length !== keys.length + (Array.isArray(input) ? 1 : 0)) {
|
|
3791
|
+
throw new Error("Non-enumerable properties are not recorded");
|
|
3792
|
+
}
|
|
3793
|
+
if (Array.isArray(input) && (keys.length !== input.length || keys.some((key, index) => key !== String(index)))) {
|
|
3794
|
+
throw new Error("Not a dense JSON array");
|
|
3795
|
+
}
|
|
3796
|
+
const properties = keys.map((key) => {
|
|
3797
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
3798
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
3799
|
+
throw new Error("Accessor properties are not replay inputs");
|
|
3800
|
+
}
|
|
3801
|
+
return `${Array.isArray(input) ? "" : `${JSON.stringify(key)}:`}${encode(descriptor.value)}`;
|
|
3802
|
+
});
|
|
3803
|
+
return Array.isArray(input) ? `[${properties.join(",")}]` : `{${properties.join(",")}}`;
|
|
3804
|
+
}
|
|
3805
|
+
try {
|
|
3806
|
+
return encode(value);
|
|
3807
|
+
} catch {
|
|
3808
|
+
return void 0;
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
var init_selectiveReplay = __esm({
|
|
3812
|
+
"src/selectiveReplay.ts"() {
|
|
3813
|
+
"use strict";
|
|
3814
|
+
}
|
|
3815
|
+
});
|
|
3816
|
+
|
|
3817
|
+
// src/selectiveReplayRuntime.ts
|
|
3818
|
+
function validateSelectiveReplayOptions(options) {
|
|
3819
|
+
const validNodes = (nodes) => Array.isArray(nodes) && nodes.length <= 100 && nodes.every(
|
|
3820
|
+
(node) => typeof node?.traceFunctionKey === "string" && node.traceFunctionKey.length > 0 && node.traceFunctionKey.length <= 500 && typeof node.spanName === "string" && node.spanName.length > 0 && node.spanName.length <= 500
|
|
3821
|
+
);
|
|
3822
|
+
if (!options || !validNodes(options.mustRun) || options.mustRun.length === 0 || Object.keys(options).some((key) => key !== "mustRun")) {
|
|
3823
|
+
throw new BitfabError(
|
|
3824
|
+
"experimentalSelectiveReplay accepts only 1\u2013100 mustRun with traceFunctionKey and spanName. Declare replayReusable on eligible spans, not reusableNodes in the replay request."
|
|
3825
|
+
);
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
function policyKey(options) {
|
|
3829
|
+
return JSON.stringify(options.mustRun.map(replayNodeKey).sort());
|
|
3830
|
+
}
|
|
3831
|
+
var SelectiveReplayRuntime;
|
|
3832
|
+
var init_selectiveReplayRuntime = __esm({
|
|
3833
|
+
"src/selectiveReplayRuntime.ts"() {
|
|
3834
|
+
"use strict";
|
|
3835
|
+
init_errors();
|
|
3836
|
+
init_selectiveReplay();
|
|
3837
|
+
SelectiveReplayRuntime = class {
|
|
3838
|
+
constructor(plan, options) {
|
|
3839
|
+
this.plan = plan;
|
|
3840
|
+
this.options = options;
|
|
3841
|
+
this.nodes = /* @__PURE__ */ new Map();
|
|
3842
|
+
this.children = /* @__PURE__ */ new Map();
|
|
3843
|
+
this.runtimeParents = /* @__PURE__ */ new Map();
|
|
3844
|
+
this.consumed = /* @__PURE__ */ new Map();
|
|
3845
|
+
this.requiredScopes = /* @__PURE__ */ new Set();
|
|
3846
|
+
this.recordings = /* @__PURE__ */ new Map();
|
|
3847
|
+
this.reached = /* @__PURE__ */ new Set();
|
|
3848
|
+
this.decisions = [];
|
|
3849
|
+
this.assertionNames = /* @__PURE__ */ new Set();
|
|
3850
|
+
this.assertionScopes = /* @__PURE__ */ new Set();
|
|
3851
|
+
this.observedNames = /* @__PURE__ */ new Map();
|
|
3852
|
+
this.unresolvedAssertions = [];
|
|
3853
|
+
validateSelectiveReplayOptions(options);
|
|
3854
|
+
if (plan?.version !== 4 || !Array.isArray(plan.nodes) || plan.nodes.length === 0 || plan.nodes.length > 500 || !Array.isArray(plan.unresolvedMustRun) || !Array.isArray(plan.assertions) || !plan.options) {
|
|
3855
|
+
throw new BitfabError(
|
|
3856
|
+
"Server did not return a compatible selective replay plan. Update the server or disable experimentalSelectiveReplay."
|
|
3857
|
+
);
|
|
3858
|
+
}
|
|
3859
|
+
validateSelectiveReplayOptions(plan.options);
|
|
3860
|
+
if (policyKey(plan.options) !== policyKey(options)) {
|
|
3861
|
+
throw new BitfabError(
|
|
3862
|
+
"Selective replay plan does not match the requested mustRun nodes."
|
|
3863
|
+
);
|
|
3864
|
+
}
|
|
3865
|
+
this.required = new Set(options.mustRun.map(replayNodeKey));
|
|
3866
|
+
for (const node of plan.nodes) {
|
|
3867
|
+
if (typeof node.id !== "string" || this.nodes.has(node.id) || typeof node.traceFunctionKey !== "string" || typeof node.spanName !== "string" || typeof node.mustRun !== "boolean" || typeof node.reusable !== "boolean") {
|
|
3868
|
+
throw new BitfabError("Invalid selective replay node.");
|
|
3869
|
+
}
|
|
3870
|
+
this.nodes.set(node.id, node);
|
|
3871
|
+
if (node.recording !== void 0) {
|
|
3872
|
+
this.recordings.set(node.id, {
|
|
3873
|
+
input: node.recording.kwargs === void 0 ? jsonFingerprint(node.recording.input) : void 0,
|
|
3874
|
+
output: jsonFingerprint(node.recording.output)
|
|
3875
|
+
});
|
|
3876
|
+
}
|
|
3877
|
+
if (node.parentId !== null) {
|
|
3878
|
+
const siblings = this.children.get(node.parentId) ?? [];
|
|
3879
|
+
siblings.push(node);
|
|
3880
|
+
this.children.set(node.parentId, siblings);
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
const root = this.nodes.get(plan.rootId);
|
|
3884
|
+
if (!root || root.parentId !== null || !root.mustRun) {
|
|
3885
|
+
throw new BitfabError("Selective replay requires a live recorded root.");
|
|
3886
|
+
}
|
|
3887
|
+
const assertionIds = /* @__PURE__ */ new Set();
|
|
3888
|
+
let wholeTrace = false;
|
|
3889
|
+
for (const assertion of plan.assertions) {
|
|
3890
|
+
const target = assertion?.target;
|
|
3891
|
+
if (typeof assertion?.id !== "string" || assertion.id.length === 0 || assertionIds.has(assertion.id) || target !== null && (typeof target !== "object" || target.kind !== "output" && target.kind !== "span")) {
|
|
3892
|
+
throw new BitfabError(
|
|
3893
|
+
"Invalid selective replay assertion requirements."
|
|
3894
|
+
);
|
|
3895
|
+
}
|
|
3896
|
+
assertionIds.add(assertion.id);
|
|
3897
|
+
if (target === null) {
|
|
3898
|
+
wholeTrace = true;
|
|
3899
|
+
} else if (target.kind === "span") {
|
|
3900
|
+
if (typeof target.name !== "string" || target.name.length === 0 || target.occurrence !== void 0 && target.occurrence !== "first" && target.occurrence !== "last" && !(Number.isInteger(target.occurrence) && typeof target.occurrence === "number" && target.occurrence >= 0)) {
|
|
3901
|
+
throw new BitfabError("Invalid selective replay assertion target.");
|
|
3902
|
+
}
|
|
3903
|
+
this.assertionNames.add(target.name);
|
|
3904
|
+
const count = plan.nodes.filter(
|
|
3905
|
+
(node) => node.spanName === target.name
|
|
3906
|
+
).length;
|
|
3907
|
+
if (count <= (typeof target.occurrence === "number" ? target.occurrence : 0)) {
|
|
3908
|
+
this.unresolvedAssertions.push(assertion.id);
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
this.fullTraceForAssertions = wholeTrace || this.unresolvedAssertions.length > 0;
|
|
3913
|
+
const recordedKeys = new Set(plan.nodes.map(replayNodeKey));
|
|
3914
|
+
const unresolved = options.mustRun.filter(
|
|
3915
|
+
(node) => !recordedKeys.has(replayNodeKey(node))
|
|
3916
|
+
);
|
|
3917
|
+
if (JSON.stringify(unresolved.map(replayNodeKey).sort()) !== JSON.stringify(plan.unresolvedMustRun.map(replayNodeKey).sort())) {
|
|
3918
|
+
throw new BitfabError(
|
|
3919
|
+
"Selective replay plan has incomplete mustRun coverage."
|
|
3920
|
+
);
|
|
3921
|
+
}
|
|
3922
|
+
for (const node of plan.nodes) {
|
|
3923
|
+
const ancestors = /* @__PURE__ */ new Set([node.id]);
|
|
3924
|
+
let requiredByManifest = this.required.has(replayNodeKey(node));
|
|
3925
|
+
let requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(node.spanName);
|
|
3926
|
+
let parentId = node.parentId;
|
|
3927
|
+
while (parentId !== null) {
|
|
3928
|
+
const parent = this.nodes.get(parentId);
|
|
3929
|
+
if (!parent || ancestors.has(parentId) || node.mustRun && !parent.mustRun) {
|
|
3930
|
+
throw new BitfabError("Invalid selective replay ancestry.");
|
|
3931
|
+
}
|
|
3932
|
+
ancestors.add(parentId);
|
|
3933
|
+
requiredByManifest || (requiredByManifest = this.required.has(replayNodeKey(parent)));
|
|
3934
|
+
requiredForAssertion || (requiredForAssertion = this.assertionNames.has(parent.spanName));
|
|
3935
|
+
parentId = parent.parentId;
|
|
3936
|
+
}
|
|
3937
|
+
if (!ancestors.has(plan.rootId) || (requiredByManifest || unresolved.length > 0) && !node.mustRun) {
|
|
3938
|
+
throw new BitfabError("Selective replay plan could hide required code.");
|
|
3939
|
+
}
|
|
3940
|
+
if (requiredForAssertion && !node.mustRun) {
|
|
3941
|
+
throw new BitfabError(
|
|
3942
|
+
"Selective replay plan could hide assertion evidence."
|
|
3943
|
+
);
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
enter(params) {
|
|
3948
|
+
const identity = {
|
|
3949
|
+
traceFunctionKey: params.traceFunctionKey,
|
|
3950
|
+
spanName: params.spanName
|
|
3951
|
+
};
|
|
3952
|
+
const key = replayNodeKey(identity);
|
|
3953
|
+
const fingerprint = jsonFingerprint(params.inputs);
|
|
3954
|
+
let node;
|
|
3955
|
+
let matchReason = "unmatched-call";
|
|
3956
|
+
if (params.parentSpanId === null) {
|
|
3957
|
+
node = this.nodes.get(this.plan.rootId);
|
|
3958
|
+
} else {
|
|
3959
|
+
const recordedParent = this.runtimeParents.get(params.parentSpanId);
|
|
3960
|
+
const consumed = this.consumed.get(params.parentSpanId) ?? /* @__PURE__ */ new Set();
|
|
3961
|
+
this.consumed.set(params.parentSpanId, consumed);
|
|
3962
|
+
const candidates = recordedParent ? (this.children.get(recordedParent) ?? []).filter(
|
|
3963
|
+
(candidate) => replayNodeKey(candidate) === key && !consumed.has(candidate.id)
|
|
3964
|
+
) : [];
|
|
3965
|
+
const exact = fingerprint === void 0 ? [] : candidates.filter(
|
|
3966
|
+
(candidate) => this.recordings.get(candidate.id)?.input === fingerprint
|
|
3967
|
+
);
|
|
3968
|
+
node = exact.length === 1 ? exact[0] : exact.length === 0 && candidates.length === 1 ? candidates[0] : void 0;
|
|
3969
|
+
if (node) {
|
|
3970
|
+
consumed.add(node.id);
|
|
3971
|
+
} else if (candidates.length > 0) {
|
|
3972
|
+
matchReason = "ambiguous-call";
|
|
3973
|
+
for (const candidate of candidates) {
|
|
3974
|
+
consumed.add(candidate.id);
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
this.runtimeParents.set(params.spanId, node?.id ?? null);
|
|
3979
|
+
const requiredByManifest = this.required.has(key) || params.parentSpanId !== null && this.requiredScopes.has(params.parentSpanId);
|
|
3980
|
+
if (requiredByManifest) {
|
|
3981
|
+
this.requiredScopes.add(params.spanId);
|
|
3982
|
+
}
|
|
3983
|
+
const requiredForAssertion = this.fullTraceForAssertions || this.assertionNames.has(params.spanName) || params.parentSpanId !== null && this.assertionScopes.has(params.parentSpanId);
|
|
3984
|
+
if (requiredForAssertion) {
|
|
3985
|
+
this.assertionScopes.add(params.spanId);
|
|
3986
|
+
}
|
|
3987
|
+
const mustRun = params.parentSpanId === null || requiredByManifest || node?.mustRun === true || requiredForAssertion || this.plan.unresolvedMustRun.length > 0;
|
|
3988
|
+
const recording = node === void 0 ? void 0 : this.recordings.get(node.id);
|
|
3989
|
+
const equalInputs = fingerprint !== void 0 && recording?.input === fingerprint;
|
|
3990
|
+
const reusable = recording?.output !== void 0 && equalInputs && params.canReuseOutput;
|
|
3991
|
+
const reason = mustRun ? requiredByManifest ? "must-run" : requiredForAssertion ? "assertion" : node?.reason ?? "unresolved-must-run" : !node ? matchReason : !equalInputs ? "input-drift-or-missing-recording" : !params.canReuseOutput ? "unsupported-output-boundary" : !params.safetyMock && !params.reuseAllowed ? "reuse-not-declared" : node.reason;
|
|
3992
|
+
const decision = (action, why) => {
|
|
3993
|
+
this.decisions.push({
|
|
3994
|
+
...identity,
|
|
3995
|
+
originalSpanId: node?.id ?? null,
|
|
3996
|
+
action,
|
|
3997
|
+
reason: why
|
|
3998
|
+
});
|
|
3999
|
+
};
|
|
4000
|
+
if (params.safetyMock && (mustRun || !reusable)) {
|
|
4001
|
+
decision("blocked", `safety-conflict:${reason}`);
|
|
4002
|
+
this.failure = new BitfabError(
|
|
4003
|
+
`Selective replay cannot execute or safely reuse safety-mocked node ${params.spanName} (${reason}).`
|
|
4004
|
+
);
|
|
4005
|
+
throw this.failure;
|
|
4006
|
+
}
|
|
4007
|
+
if (!mustRun && reusable && (params.safetyMock || node?.reusable === true && params.reuseAllowed)) {
|
|
4008
|
+
decision("mock", params.safetyMock ? "safety-mock" : "unchanged-inputs");
|
|
4009
|
+
return {
|
|
4010
|
+
mock: true,
|
|
4011
|
+
output: JSON.parse(recording.output)
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
4014
|
+
decision("run", reason);
|
|
4015
|
+
this.observedNames.set(
|
|
4016
|
+
params.spanName,
|
|
4017
|
+
(this.observedNames.get(params.spanName) ?? 0) + 1
|
|
4018
|
+
);
|
|
4019
|
+
if (this.required.has(key)) {
|
|
4020
|
+
this.reached.add(key);
|
|
4021
|
+
}
|
|
4022
|
+
return { mock: false };
|
|
4023
|
+
}
|
|
4024
|
+
assertNoSafetyConflict() {
|
|
4025
|
+
if (this.failure) {
|
|
4026
|
+
throw this.failure;
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
report() {
|
|
4030
|
+
return {
|
|
4031
|
+
decisions: this.decisions,
|
|
4032
|
+
mustRunNotReached: this.options.mustRun.filter(
|
|
4033
|
+
(node) => !this.reached.has(replayNodeKey(node))
|
|
4034
|
+
),
|
|
4035
|
+
unresolvedMustRun: this.plan.unresolvedMustRun,
|
|
4036
|
+
assertionIds: this.plan.assertions.map((assertion) => assertion.id),
|
|
4037
|
+
unresolvedAssertions: this.unresolvedAssertions,
|
|
4038
|
+
assertionTargetsNotReached: this.plan.assertions.flatMap(
|
|
4039
|
+
({ id, target }) => target?.kind === "span" && (this.observedNames.get(target.name) ?? 0) <= (typeof target.occurrence === "number" ? target.occurrence : 0) ? [id] : []
|
|
4040
|
+
)
|
|
4041
|
+
};
|
|
4042
|
+
}
|
|
4043
|
+
};
|
|
4044
|
+
}
|
|
4045
|
+
});
|
|
4046
|
+
|
|
3626
4047
|
// src/replay.ts
|
|
3627
4048
|
var replay_exports = {};
|
|
3628
4049
|
__export(replay_exports, {
|
|
@@ -3878,7 +4299,7 @@ function buildMockTree(rootNode) {
|
|
|
3878
4299
|
}
|
|
3879
4300
|
return { spans };
|
|
3880
4301
|
}
|
|
3881
|
-
async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun) {
|
|
4302
|
+
async function processItem(httpClient, serverItem, fn, experimentId, mockStrategy, resolvedOverrides, replayedTraceId, attempt, includeDbBranchLease, dbBranchSettings, adaptInputs, dryRun, selectiveOptions) {
|
|
3882
4303
|
let lease = includeDbBranchLease ? serverItem.dbBranchLease : void 0;
|
|
3883
4304
|
let leaseError = includeDbBranchLease ? serverItem.dbBranchLeaseError : void 0;
|
|
3884
4305
|
let dbSnapshotRef = serverItem.dbSnapshotRef;
|
|
@@ -3891,6 +4312,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3891
4312
|
let replayError = null;
|
|
3892
4313
|
let replayDurationMs = null;
|
|
3893
4314
|
let replayStarted = null;
|
|
4315
|
+
let selectiveReplay;
|
|
3894
4316
|
const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
|
|
3895
4317
|
const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
|
|
3896
4318
|
try {
|
|
@@ -3923,6 +4345,17 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3923
4345
|
originalTraceId
|
|
3924
4346
|
);
|
|
3925
4347
|
}
|
|
4348
|
+
if (selectiveOptions !== void 0) {
|
|
4349
|
+
if (serverItem.selectiveReplayPlan === void 0) {
|
|
4350
|
+
throw new BitfabError(
|
|
4351
|
+
"Server did not return a selective replay plan. Update the server or disable experimentalSelectiveReplay."
|
|
4352
|
+
);
|
|
4353
|
+
}
|
|
4354
|
+
selectiveReplay = new SelectiveReplayRuntime(
|
|
4355
|
+
serverItem.selectiveReplayPlan,
|
|
4356
|
+
selectiveOptions
|
|
4357
|
+
);
|
|
4358
|
+
}
|
|
3926
4359
|
const span = await httpClient.getExternalSpan(originalSpanId, {
|
|
3927
4360
|
view: "replay"
|
|
3928
4361
|
});
|
|
@@ -3942,7 +4375,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
3942
4375
|
});
|
|
3943
4376
|
}
|
|
3944
4377
|
const hasOverrides = resolvedOverrides.length > 0;
|
|
3945
|
-
const needTree = mockStrategy === "all" || mockStrategy === "marked" || hasOverrides;
|
|
4378
|
+
const needTree = selectiveReplay === void 0 && (mockStrategy === "all" || mockStrategy === "marked" || hasOverrides);
|
|
3946
4379
|
const includeOutputs = mockStrategy === "all";
|
|
3947
4380
|
let mockTree;
|
|
3948
4381
|
if (needTree) {
|
|
@@ -4017,6 +4450,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
4017
4450
|
sourceBitfabTraceId: originalTraceId,
|
|
4018
4451
|
replayAttempt: attempt,
|
|
4019
4452
|
mockTree,
|
|
4453
|
+
selectiveReplay,
|
|
4020
4454
|
callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
|
|
4021
4455
|
mockStrategy,
|
|
4022
4456
|
mockOverrides: hasOverrides ? resolvedOverrides : void 0,
|
|
@@ -4028,6 +4462,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
4028
4462
|
() => fn(...inputs)
|
|
4029
4463
|
);
|
|
4030
4464
|
result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
|
|
4465
|
+
selectiveReplay?.assertNoSafetyConflict();
|
|
4031
4466
|
replayDurationMs = Math.round(performance.now() - replayStarted);
|
|
4032
4467
|
} catch (e) {
|
|
4033
4468
|
if (replayStarted !== null) {
|
|
@@ -4079,6 +4514,7 @@ async function processItem(httpClient, serverItem, fn, experimentId, mockStrateg
|
|
|
4079
4514
|
traceError,
|
|
4080
4515
|
replayError,
|
|
4081
4516
|
durationMs: replayDurationMs,
|
|
4517
|
+
...selectiveReplay === void 0 ? {} : { selectiveReplay: selectiveReplay.report() },
|
|
4082
4518
|
originalDurationMs,
|
|
4083
4519
|
originalTokens: serverItem.originalTokens ?? serverItem.tokens ?? null,
|
|
4084
4520
|
originalModel,
|
|
@@ -4183,6 +4619,14 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4183
4619
|
"force only applies when resuming an experiment. Pass it together with resume."
|
|
4184
4620
|
);
|
|
4185
4621
|
}
|
|
4622
|
+
if (options?.experimentalSelectiveReplay !== void 0) {
|
|
4623
|
+
validateSelectiveReplayOptions(options.experimentalSelectiveReplay);
|
|
4624
|
+
if ((options.mock ?? "marked") !== "marked" || normalizeMockOverrides(options.mockOverride).length > 0 || registeredOverrides.length > 0) {
|
|
4625
|
+
throw new BitfabError(
|
|
4626
|
+
"experimentalSelectiveReplay requires mock: 'marked' and cannot be combined with mock overrides."
|
|
4627
|
+
);
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4186
4630
|
if (options?.traceIds !== void 0) {
|
|
4187
4631
|
if (options.traceIds.length === 0) {
|
|
4188
4632
|
throw new BitfabError("traceIds must contain at least one trace ID.");
|
|
@@ -4224,7 +4668,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4224
4668
|
const includeDbBranchLease = dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true;
|
|
4225
4669
|
let session;
|
|
4226
4670
|
if (options?.resume === void 0) {
|
|
4227
|
-
const
|
|
4671
|
+
const started = await httpClient.startReplay(
|
|
4228
4672
|
traceFunctionKey,
|
|
4229
4673
|
// limit is meaningless with explicit traceIds (the ID list determines
|
|
4230
4674
|
// the count), so it's omitted from the request entirely.
|
|
@@ -4244,9 +4688,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4244
4688
|
requestedAttempts,
|
|
4245
4689
|
options?.adaptInputs !== void 0,
|
|
4246
4690
|
options?.onlyWithAssertions,
|
|
4247
|
-
options?.judgeAssertions
|
|
4691
|
+
options?.judgeAssertions,
|
|
4692
|
+
options?.experimentalSelectiveReplay
|
|
4248
4693
|
);
|
|
4249
|
-
if (
|
|
4694
|
+
if (started.items.length === 0) {
|
|
4250
4695
|
try {
|
|
4251
4696
|
console.warn(
|
|
4252
4697
|
`Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
|
|
@@ -4255,10 +4700,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4255
4700
|
}
|
|
4256
4701
|
}
|
|
4257
4702
|
session = {
|
|
4258
|
-
experimentId:
|
|
4259
|
-
experimentUrl:
|
|
4703
|
+
experimentId: started.experimentId,
|
|
4704
|
+
experimentUrl: started.experimentUrl,
|
|
4260
4705
|
attempts: requestedAttempts,
|
|
4261
|
-
work: expandReplayWork(
|
|
4706
|
+
work: expandReplayWork(started.items, requestedAttempts),
|
|
4262
4707
|
finished: [],
|
|
4263
4708
|
includeDbBranchLease,
|
|
4264
4709
|
dbBranchSettings: resolveDbBranchSettings(options?.dbBranch)
|
|
@@ -4273,9 +4718,68 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4273
4718
|
options.dbBranch
|
|
4274
4719
|
);
|
|
4275
4720
|
}
|
|
4276
|
-
const { experimentId
|
|
4721
|
+
const { experimentId } = session;
|
|
4722
|
+
const fullExperimentUrl = `${serviceUrl}${session.experimentUrl}`;
|
|
4723
|
+
const heartbeat = startReplayHeartbeat(
|
|
4724
|
+
() => httpClient.heartbeatReplay(experimentId)
|
|
4725
|
+
);
|
|
4726
|
+
const interruptReplay = async () => {
|
|
4727
|
+
await heartbeat.stop();
|
|
4728
|
+
return httpClient.interruptReplay(experimentId);
|
|
4729
|
+
};
|
|
4730
|
+
const interruptHandlers = installReplayInterruptHandlers({
|
|
4731
|
+
experimentId,
|
|
4732
|
+
experimentUrl: fullExperimentUrl,
|
|
4733
|
+
interrupt: interruptReplay,
|
|
4734
|
+
onInterrupt: options?.onInterrupt
|
|
4735
|
+
});
|
|
4736
|
+
let completeReturned = false;
|
|
4737
|
+
const markCompleteReturned = () => {
|
|
4738
|
+
completeReturned = true;
|
|
4739
|
+
void heartbeat.stop();
|
|
4740
|
+
};
|
|
4741
|
+
try {
|
|
4742
|
+
return await runReplaySession(
|
|
4743
|
+
httpClient,
|
|
4744
|
+
fn,
|
|
4745
|
+
session,
|
|
4746
|
+
fullExperimentUrl,
|
|
4747
|
+
maxConcurrency,
|
|
4748
|
+
interruptHandlers.remove,
|
|
4749
|
+
markCompleteReturned,
|
|
4750
|
+
interruptHandlers.interrupted,
|
|
4751
|
+
options,
|
|
4752
|
+
registeredOverrides
|
|
4753
|
+
);
|
|
4754
|
+
} catch (error) {
|
|
4755
|
+
if (!completeReturned && !interruptHandlers.interrupted()) {
|
|
4756
|
+
await interruptReplay();
|
|
4757
|
+
}
|
|
4758
|
+
throw error;
|
|
4759
|
+
} finally {
|
|
4760
|
+
interruptHandlers.remove();
|
|
4761
|
+
await heartbeat.stop();
|
|
4762
|
+
}
|
|
4763
|
+
}
|
|
4764
|
+
function throwIfInterrupted(interrupted, items, experimentId, experimentUrl) {
|
|
4765
|
+
if (!interrupted()) {
|
|
4766
|
+
return;
|
|
4767
|
+
}
|
|
4768
|
+
const cause = new BitfabError(
|
|
4769
|
+
`Experiment ${experimentId} was interrupted, so it was not completed or graded. Resume it by passing { resume: "${experimentId}" } to replay, or with bitfab-replay --resume ${experimentId}.`,
|
|
4770
|
+
experimentUrl
|
|
4771
|
+
);
|
|
4772
|
+
throw new ReplayError(
|
|
4773
|
+
cause.message,
|
|
4774
|
+
items,
|
|
4775
|
+
experimentId,
|
|
4776
|
+
experimentUrl,
|
|
4777
|
+
cause
|
|
4778
|
+
);
|
|
4779
|
+
}
|
|
4780
|
+
async function runReplaySession(httpClient, fn, session, fullExperimentUrl, maxConcurrency, stopInterruptingOnSignal, markCompleteReturned, interrupted, options, registeredOverrides) {
|
|
4781
|
+
const { experimentId, attempts } = session;
|
|
4277
4782
|
const mockStrategy = options?.mock ?? "marked";
|
|
4278
|
-
const fullExperimentUrl = `${serviceUrl}${experimentUrl}`;
|
|
4279
4783
|
try {
|
|
4280
4784
|
options?.onExperimentStart?.({
|
|
4281
4785
|
experimentId,
|
|
@@ -4306,7 +4810,8 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4306
4810
|
session.includeDbBranchLease,
|
|
4307
4811
|
session.dbBranchSettings,
|
|
4308
4812
|
options?.adaptInputs,
|
|
4309
|
-
options?.dryRun === true
|
|
4813
|
+
options?.dryRun === true,
|
|
4814
|
+
options?.experimentalSelectiveReplay
|
|
4310
4815
|
)
|
|
4311
4816
|
);
|
|
4312
4817
|
const total = tasks.length + carriedOverItems.length;
|
|
@@ -4379,6 +4884,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4379
4884
|
traceError: item.traceError,
|
|
4380
4885
|
replayError: item.replayError,
|
|
4381
4886
|
durationMs: item.durationMs,
|
|
4887
|
+
...item.selectiveReplay === void 0 ? {} : { selectiveReplay: item.selectiveReplay },
|
|
4382
4888
|
originalDurationMs: item.originalDurationMs,
|
|
4383
4889
|
originalTokens: item.originalTokens,
|
|
4384
4890
|
originalModel: item.originalModel,
|
|
@@ -4423,7 +4929,15 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4423
4929
|
);
|
|
4424
4930
|
const resultItems = [...carriedOverItems, ...ranItems];
|
|
4425
4931
|
if (options?.dryRun === true) {
|
|
4932
|
+
throwIfInterrupted(
|
|
4933
|
+
interrupted,
|
|
4934
|
+
resultItems,
|
|
4935
|
+
experimentId,
|
|
4936
|
+
fullExperimentUrl
|
|
4937
|
+
);
|
|
4938
|
+
stopInterruptingOnSignal();
|
|
4426
4939
|
await httpClient.completeReplay(experimentId).catch(() => void 0);
|
|
4940
|
+
markCompleteReturned();
|
|
4427
4941
|
const dryResult = {
|
|
4428
4942
|
items: resultItems,
|
|
4429
4943
|
experimentId,
|
|
@@ -4448,12 +4962,15 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4448
4962
|
ranItems[index].traceId = readBack;
|
|
4449
4963
|
}
|
|
4450
4964
|
}
|
|
4965
|
+
throwIfInterrupted(interrupted, resultItems, experimentId, fullExperimentUrl);
|
|
4966
|
+
stopInterruptingOnSignal();
|
|
4451
4967
|
const completeResult = await preserveReplayFailure(
|
|
4452
4968
|
() => httpClient.completeReplay(experimentId),
|
|
4453
4969
|
resultItems,
|
|
4454
4970
|
experimentId,
|
|
4455
4971
|
fullExperimentUrl
|
|
4456
4972
|
);
|
|
4973
|
+
markCompleteReturned();
|
|
4457
4974
|
const serverTraceIds = completeResult.traceIds;
|
|
4458
4975
|
const replayTokens = completeResult.tokens;
|
|
4459
4976
|
if (serverTraceIds !== void 0) {
|
|
@@ -4557,6 +5074,9 @@ var init_replay = __esm({
|
|
|
4557
5074
|
init_mockOverride();
|
|
4558
5075
|
init_randomUuid();
|
|
4559
5076
|
init_replayContext();
|
|
5077
|
+
init_replayHeartbeat();
|
|
5078
|
+
init_replayInterrupt();
|
|
5079
|
+
init_selectiveReplayRuntime();
|
|
4560
5080
|
init_serialize();
|
|
4561
5081
|
REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
4562
5082
|
MAX_REPLAY_ATTEMPTS = 100;
|
|
@@ -4568,7 +5088,8 @@ var init_replay = __esm({
|
|
|
4568
5088
|
"attempts",
|
|
4569
5089
|
"graderIds",
|
|
4570
5090
|
"onlyWithAssertions",
|
|
4571
|
-
"dryRun"
|
|
5091
|
+
"dryRun",
|
|
5092
|
+
"experimentalSelectiveReplay"
|
|
4572
5093
|
];
|
|
4573
5094
|
CARRIED_OVER_ERROR = "This item errored in an earlier run of this experiment, before it was resumed.";
|
|
4574
5095
|
BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
|
|
@@ -9269,6 +9790,9 @@ var Bitfab = class {
|
|
|
9269
9790
|
...options.mockOnReplay !== void 0 && {
|
|
9270
9791
|
mockOnReplay: options.mockOnReplay
|
|
9271
9792
|
},
|
|
9793
|
+
...options.replayReusable !== void 0 && {
|
|
9794
|
+
replayReusable: options.replayReusable
|
|
9795
|
+
},
|
|
9272
9796
|
...options.finalize !== void 0 && { finalize: options.finalize }
|
|
9273
9797
|
};
|
|
9274
9798
|
}
|
|
@@ -9417,6 +9941,9 @@ var Bitfab = class {
|
|
|
9417
9941
|
...mockOnReplay !== void 0 && {
|
|
9418
9942
|
mockOnReplay
|
|
9419
9943
|
},
|
|
9944
|
+
...nodeConfiguration?.replayReusable !== void 0 && {
|
|
9945
|
+
replayReusable: nodeConfiguration.replayReusable
|
|
9946
|
+
},
|
|
9420
9947
|
...nodeConfiguration?.finalize !== void 0 && {
|
|
9421
9948
|
finalize: nodeConfiguration.finalize
|
|
9422
9949
|
}
|
|
@@ -10214,6 +10741,7 @@ var Bitfab = class {
|
|
|
10214
10741
|
instrumentation: options.instrumentation ?? "span",
|
|
10215
10742
|
functionId: options.functionId,
|
|
10216
10743
|
captureContent: options.captureContent ?? true,
|
|
10744
|
+
replayReusable: options.replayReusable,
|
|
10217
10745
|
autoTraceDefinition: options.autoTraceDefinition,
|
|
10218
10746
|
declaredNode: options.declaredNode === true,
|
|
10219
10747
|
nestedTrace: options.nestedTrace,
|
|
@@ -10333,6 +10861,27 @@ var Bitfab = class {
|
|
|
10333
10861
|
return result;
|
|
10334
10862
|
};
|
|
10335
10863
|
const replayCtxForMock = getReplayContext();
|
|
10864
|
+
if (replayCtxForMock?.selectiveReplay) {
|
|
10865
|
+
const decision = replayCtxForMock.selectiveReplay.enter({
|
|
10866
|
+
traceFunctionKey,
|
|
10867
|
+
spanName: baseSpanParams.spanName ?? traceFunctionKey,
|
|
10868
|
+
spanId,
|
|
10869
|
+
parentSpanId,
|
|
10870
|
+
inputs: args,
|
|
10871
|
+
safetyMock: !isRootSpan && options.mockOnReplay === true,
|
|
10872
|
+
reuseAllowed: options.replayReusable === true,
|
|
10873
|
+
canReuseOutput: options.finalize === void 0 && options.replayOutput === void 0 && !fn.constructor.name.includes("Generator")
|
|
10874
|
+
});
|
|
10875
|
+
if (decision.mock) {
|
|
10876
|
+
void sendSpan({
|
|
10877
|
+
result: decision.output,
|
|
10878
|
+
mocked: true,
|
|
10879
|
+
mockTarget: "output",
|
|
10880
|
+
mockSource: "recorded"
|
|
10881
|
+
});
|
|
10882
|
+
return fnReturnsPromise ? Promise.resolve(decision.output) : decision.output;
|
|
10883
|
+
}
|
|
10884
|
+
}
|
|
10336
10885
|
if (replayCtxForMock?.mockTree && !isRootSpan) {
|
|
10337
10886
|
const counters = replayCtxForMock.callCounters;
|
|
10338
10887
|
const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
|
|
@@ -10756,6 +11305,9 @@ var Bitfab = class {
|
|
|
10756
11305
|
span_origin: makeSpanOrigin(params.instrumentation),
|
|
10757
11306
|
span_data: {
|
|
10758
11307
|
name: params.spanName,
|
|
11308
|
+
...params.replayReusable !== void 0 && {
|
|
11309
|
+
replay_reusable: params.replayReusable
|
|
11310
|
+
},
|
|
10759
11311
|
type: params.spanType,
|
|
10760
11312
|
...params.functionId !== void 0 && {
|
|
10761
11313
|
function_id: params.functionId,
|
|
@@ -11307,6 +11859,7 @@ init_replay();
|
|
|
11307
11859
|
// src/replayRegistry.ts
|
|
11308
11860
|
init_errors();
|
|
11309
11861
|
init_replay();
|
|
11862
|
+
init_replayInterrupt();
|
|
11310
11863
|
function defineReplayRegistry(registry) {
|
|
11311
11864
|
return registry;
|
|
11312
11865
|
}
|