@brainervirus/workit-pi 2.2.0 → 2.2.1
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/worker.js +1 -0
- package/dist/workit.js +92 -9
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -8563,6 +8563,7 @@ var resolverInputSchema = object({
|
|
|
8563
8563
|
// packages/workit-core/src/core/task-engine.ts
|
|
8564
8564
|
var verifiedWorkers = new WeakMap;
|
|
8565
8565
|
var dispatchReservations = new WeakMap;
|
|
8566
|
+
var retryPause = new Int32Array(new SharedArrayBuffer(4));
|
|
8566
8567
|
// packages/workit-pi/src/worker-protocol.ts
|
|
8567
8568
|
var MAX_WORKER_LINE_BYTES = 64 * 1024;
|
|
8568
8569
|
var MAX_WORKER_STDERR_BYTES = 64 * 1024;
|
package/dist/workit.js
CHANGED
|
@@ -10022,7 +10022,7 @@ class TaskStore {
|
|
|
10022
10022
|
if (!current.data)
|
|
10023
10023
|
return failure2("not_found", "workspace not found");
|
|
10024
10024
|
if (current.data.revision !== expected)
|
|
10025
|
-
return this.
|
|
10025
|
+
return this.workspaceConflict(expected, current.data.revision);
|
|
10026
10026
|
const previousBytes = this.snapshotBytes(this.workspacePath);
|
|
10027
10027
|
if (!previousBytes)
|
|
10028
10028
|
return failure2("storage_error", "workspace snapshot disappeared during mutation");
|
|
@@ -10065,7 +10065,7 @@ class TaskStore {
|
|
|
10065
10065
|
if (task.data.revision !== expected)
|
|
10066
10066
|
return this.conflict(expected, task.data.revision);
|
|
10067
10067
|
if (workspace.data.revision !== input.expectedWorkspaceRevision)
|
|
10068
|
-
return this.
|
|
10068
|
+
return this.workspaceConflict(input.expectedWorkspaceRevision, workspace.data.revision);
|
|
10069
10069
|
const previousWorkspaceBytes = this.snapshotBytes(this.workspacePath);
|
|
10070
10070
|
const previousTaskBytes = this.snapshotBytes(this.taskPath(input.taskId));
|
|
10071
10071
|
if (!previousWorkspaceBytes || !previousTaskBytes)
|
|
@@ -10875,6 +10875,9 @@ class TaskStore {
|
|
|
10875
10875
|
actualRevision: actual
|
|
10876
10876
|
});
|
|
10877
10877
|
}
|
|
10878
|
+
workspaceConflict(expected, actual) {
|
|
10879
|
+
return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
|
|
10880
|
+
}
|
|
10878
10881
|
taskPath(taskId) {
|
|
10879
10882
|
return path4.join(this.tasksDir, `${taskId}.json`);
|
|
10880
10883
|
}
|
|
@@ -13728,8 +13731,10 @@ values are still concurrency-checked, so never copy revisions between calls.
|
|
|
13728
13731
|
A busy result means another live Workit call holds the checkout lock: retry the
|
|
13729
13732
|
same call; it is not a recovery condition. A lock left by a dead process is
|
|
13730
13733
|
reclaimed on the next write, and \`workit doctor --fix-lock\` clears it on demand.
|
|
13731
|
-
|
|
13732
|
-
|
|
13734
|
+
An omitted revision absorbs a concurrent write: Workit re-reads, re-checks
|
|
13735
|
+
policy, and reapplies the call, returning busy under persistent contention. A
|
|
13736
|
+
revision_conflict means a revision you passed is stale: re-read the record
|
|
13737
|
+
before deciding whether to retry.
|
|
13733
13738
|
A solo edit does not need writer acquisition; use it when concurrent checkout
|
|
13734
13739
|
writers need coordination. Record only observed facts and checks. Evidence can
|
|
13735
13740
|
become stale when its bound candidate changes; reconcile findings against the
|
|
@@ -15113,6 +15118,29 @@ var applyWorkerLifecycle = (input) => {
|
|
|
15113
15118
|
return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during lifecycle observation");
|
|
15114
15119
|
};
|
|
15115
15120
|
var refsWithinScope = (refs, scope) => refs.every((ref) => ref.kind !== "file" || bindingCovers(scope, { description: "", paths: [ref.path], exclusions: [] }));
|
|
15121
|
+
var REVISION_RETRY_ATTEMPTS = 8;
|
|
15122
|
+
var retryPause = new Int32Array(new SharedArrayBuffer(4));
|
|
15123
|
+
var filledRevisions = (request) => {
|
|
15124
|
+
const value = typeof request === "object" && request !== null ? request : {};
|
|
15125
|
+
return {
|
|
15126
|
+
task: value.expectedRevision === undefined,
|
|
15127
|
+
workspace: value.expectedWorkspaceRevision === undefined
|
|
15128
|
+
};
|
|
15129
|
+
};
|
|
15130
|
+
var filledConflict = (result, filled) => !result.ok && result.code === "revision_conflict" && (("actualRevision" in result.details) && filled.task || ("actualWorkspaceRevision" in result.details) && filled.workspace);
|
|
15131
|
+
var retryFilledRevisions = (filled, run) => {
|
|
15132
|
+
if (!filled.task && !filled.workspace)
|
|
15133
|
+
return run();
|
|
15134
|
+
let result = run();
|
|
15135
|
+
const deadline = Date.now() + defaultLockTimeout();
|
|
15136
|
+
for (let attempt = 1;filledConflict(result, filled); attempt += 1) {
|
|
15137
|
+
if (attempt >= REVISION_RETRY_ATTEMPTS || Date.now() >= deadline)
|
|
15138
|
+
return failure2("busy", "records kept changing under concurrent writers; retry the call");
|
|
15139
|
+
Atomics.wait(retryPause, 0, 0, Math.floor(Math.random() * 4 * attempt) + 1);
|
|
15140
|
+
result = run();
|
|
15141
|
+
}
|
|
15142
|
+
return result;
|
|
15143
|
+
};
|
|
15116
15144
|
var trustedNow = (context) => typeof context.now === "function" ? context.now() : context.now;
|
|
15117
15145
|
var mapRef = (ref, ids) => {
|
|
15118
15146
|
if (ref.kind !== "record")
|
|
@@ -15360,6 +15388,9 @@ class WorkitCore {
|
|
|
15360
15388
|
if (input.expectedWorkspaceRevision === undefined)
|
|
15361
15389
|
input.expectedWorkspaceRevision = workspace ? workspace.revision : null;
|
|
15362
15390
|
}
|
|
15391
|
+
retryOmittedRevisions(request, run) {
|
|
15392
|
+
return retryFilledRevisions(filledRevisions(request), run);
|
|
15393
|
+
}
|
|
15363
15394
|
helperEntry(task, requireSession = false, requireActive = false) {
|
|
15364
15395
|
const workerId = this.context.workerId ?? null;
|
|
15365
15396
|
if (workerId === null)
|
|
@@ -15391,6 +15422,9 @@ class WorkitCore {
|
|
|
15391
15422
|
return null;
|
|
15392
15423
|
}
|
|
15393
15424
|
state(request) {
|
|
15425
|
+
return request?.action === "import" ? this.retryOmittedRevisions(request, () => this.stateOnce(request)) : this.stateOnce(request);
|
|
15426
|
+
}
|
|
15427
|
+
stateOnce(request) {
|
|
15394
15428
|
const root = this.contextRootError();
|
|
15395
15429
|
if (!root.ok)
|
|
15396
15430
|
return root;
|
|
@@ -15492,6 +15526,9 @@ class WorkitCore {
|
|
|
15492
15526
|
});
|
|
15493
15527
|
}
|
|
15494
15528
|
task(request) {
|
|
15529
|
+
return this.retryOmittedRevisions(request, () => this.taskOnce(request));
|
|
15530
|
+
}
|
|
15531
|
+
taskOnce(request) {
|
|
15495
15532
|
const root = this.contextRootError();
|
|
15496
15533
|
if (!root.ok)
|
|
15497
15534
|
return root;
|
|
@@ -15591,6 +15628,9 @@ class WorkitCore {
|
|
|
15591
15628
|
}
|
|
15592
15629
|
}
|
|
15593
15630
|
policy(request) {
|
|
15631
|
+
return this.retryOmittedRevisions(request, () => this.policyOnce(request));
|
|
15632
|
+
}
|
|
15633
|
+
policyOnce(request) {
|
|
15594
15634
|
const root = this.contextRootError();
|
|
15595
15635
|
if (!root.ok)
|
|
15596
15636
|
return root;
|
|
@@ -15637,6 +15677,9 @@ class WorkitCore {
|
|
|
15637
15677
|
return success(changed.data.revision, null, changed.data.policy);
|
|
15638
15678
|
}
|
|
15639
15679
|
evidence(request) {
|
|
15680
|
+
return this.retryOmittedRevisions(request, () => this.evidenceOnce(request));
|
|
15681
|
+
}
|
|
15682
|
+
evidenceOnce(request) {
|
|
15640
15683
|
const root = this.contextRootError();
|
|
15641
15684
|
if (!root.ok)
|
|
15642
15685
|
return root;
|
|
@@ -15721,6 +15764,23 @@ class WorkitCore {
|
|
|
15721
15764
|
observeStandingDecision(request) {
|
|
15722
15765
|
return this.recordDecision(request, undefined, true, true);
|
|
15723
15766
|
}
|
|
15767
|
+
commitTask(taskId, expected, filled, recheck, update) {
|
|
15768
|
+
let revision = expected;
|
|
15769
|
+
let first = true;
|
|
15770
|
+
return retryFilledRevisions({ task: filled, workspace: false }, () => {
|
|
15771
|
+
if (!first) {
|
|
15772
|
+
const fresh = this.store.readTask(taskId);
|
|
15773
|
+
if (!fresh.ok)
|
|
15774
|
+
return fresh;
|
|
15775
|
+
const checked = recheck(fresh.data);
|
|
15776
|
+
if (!checked.ok)
|
|
15777
|
+
return checked;
|
|
15778
|
+
revision = fresh.data.revision;
|
|
15779
|
+
}
|
|
15780
|
+
first = false;
|
|
15781
|
+
return this.store.mutateTask(taskId, revision, update, trustedNow(this.context));
|
|
15782
|
+
});
|
|
15783
|
+
}
|
|
15724
15784
|
recordDecision(request, nativeObservation, nativeRequired = false, standing = false) {
|
|
15725
15785
|
const root = this.contextRootError();
|
|
15726
15786
|
if (!root.ok)
|
|
@@ -15745,6 +15805,7 @@ class WorkitCore {
|
|
|
15745
15805
|
return task;
|
|
15746
15806
|
if ((this.context.workerId ?? null) !== null)
|
|
15747
15807
|
return failure2("permission_denied", "helpers cannot record or revoke decisions");
|
|
15808
|
+
const filled = input.expectedRevision === undefined;
|
|
15748
15809
|
this.fillRevisions(input, task.data);
|
|
15749
15810
|
if (input.action === "record") {
|
|
15750
15811
|
if (task.data.status === "closed")
|
|
@@ -15793,7 +15854,20 @@ class WorkitCore {
|
|
|
15793
15854
|
const nativeProvenance = native?.ok ? retireNativeAuthority(native.data) : null;
|
|
15794
15855
|
if (native?.ok && !nativeProvenance)
|
|
15795
15856
|
return failure2("permission_denied", "native decision authority was retired");
|
|
15796
|
-
|
|
15857
|
+
let standingProvenance = standingApproval?.ok === true ? standingApproval.data : null;
|
|
15858
|
+
const recheck = (fresh) => {
|
|
15859
|
+
if (fresh.status === "closed")
|
|
15860
|
+
return failure2("invalid_transition", "closed task cannot record a decision");
|
|
15861
|
+
if (!standingProvenance)
|
|
15862
|
+
return success(null, null, null);
|
|
15863
|
+
const again = verifyStandingApproval(this.store.root, fresh, this.context.caller, input.binding);
|
|
15864
|
+
if (again.ok)
|
|
15865
|
+
standingProvenance = again.data;
|
|
15866
|
+
return again;
|
|
15867
|
+
};
|
|
15868
|
+
const changed = this.commitTask(task.data.id, input.expectedRevision, filled, recheck, (current, mutation) => {
|
|
15869
|
+
if (current.status === "closed")
|
|
15870
|
+
return failure2("invalid_transition", "closed task cannot record a decision");
|
|
15797
15871
|
const latestContent = verifyDecisionContent(this.store, input.binding);
|
|
15798
15872
|
if (!latestContent.ok)
|
|
15799
15873
|
return latestContent;
|
|
@@ -15805,14 +15879,14 @@ class WorkitCore {
|
|
|
15805
15879
|
const entry = {
|
|
15806
15880
|
id: newId(),
|
|
15807
15881
|
recordedAt: mutation.now,
|
|
15808
|
-
provenance:
|
|
15882
|
+
provenance: standingProvenance ?? nativeProvenance ?? provenance(this.context, "agent_reported"),
|
|
15809
15883
|
data
|
|
15810
15884
|
};
|
|
15811
15885
|
return success(mutation.revision, null, {
|
|
15812
15886
|
...current,
|
|
15813
15887
|
decisions: [...current.decisions, entry]
|
|
15814
15888
|
});
|
|
15815
|
-
}
|
|
15889
|
+
});
|
|
15816
15890
|
if (!changed.ok)
|
|
15817
15891
|
return changed;
|
|
15818
15892
|
return success(changed.data.revision, null, changed.data.decisions.at(-1));
|
|
@@ -15826,7 +15900,7 @@ class WorkitCore {
|
|
|
15826
15900
|
return failure2("invalid_transition", "decision is already revoked");
|
|
15827
15901
|
if (decision.data.consumption)
|
|
15828
15902
|
return failure2("permission_denied", "consumed decision cannot be revoked");
|
|
15829
|
-
const changed = this.
|
|
15903
|
+
const changed = this.commitTask(task.data.id, input.expectedRevision, filled, () => success(null, null, null), (current, mutation) => {
|
|
15830
15904
|
const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
|
|
15831
15905
|
if (!entry)
|
|
15832
15906
|
return failure2("not_found", "decision not found");
|
|
@@ -15843,13 +15917,16 @@ class WorkitCore {
|
|
|
15843
15917
|
...current,
|
|
15844
15918
|
decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? updated : candidate)
|
|
15845
15919
|
});
|
|
15846
|
-
}
|
|
15920
|
+
});
|
|
15847
15921
|
if (!changed.ok)
|
|
15848
15922
|
return changed;
|
|
15849
15923
|
const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
|
|
15850
15924
|
return entry ? success(changed.data.revision, null, entry) : failure2("recovery_required", "decision disappeared");
|
|
15851
15925
|
}
|
|
15852
15926
|
finding(request) {
|
|
15927
|
+
return this.retryOmittedRevisions(request, () => this.findingOnce(request));
|
|
15928
|
+
}
|
|
15929
|
+
findingOnce(request) {
|
|
15853
15930
|
const root = this.contextRootError();
|
|
15854
15931
|
if (!root.ok)
|
|
15855
15932
|
return root;
|
|
@@ -15973,6 +16050,9 @@ class WorkitCore {
|
|
|
15973
16050
|
return entry ? success(changed.data.revision, null, entry) : failure2("recovery_required", "finding disappeared");
|
|
15974
16051
|
}
|
|
15975
16052
|
worker(request) {
|
|
16053
|
+
return this.retryOmittedRevisions(request, () => this.workerOnce(request));
|
|
16054
|
+
}
|
|
16055
|
+
workerOnce(request) {
|
|
15976
16056
|
const root = this.contextRootError();
|
|
15977
16057
|
if (!root.ok)
|
|
15978
16058
|
return root;
|
|
@@ -16320,6 +16400,9 @@ class WorkitCore {
|
|
|
16320
16400
|
return applied;
|
|
16321
16401
|
}
|
|
16322
16402
|
writer(request) {
|
|
16403
|
+
return this.retryOmittedRevisions(request, () => this.writerOnce(request));
|
|
16404
|
+
}
|
|
16405
|
+
writerOnce(request) {
|
|
16323
16406
|
const root = this.contextRootError();
|
|
16324
16407
|
if (!root.ok)
|
|
16325
16408
|
return root;
|