taskforce-loop-engineering 0.15.6 → 0.15.7
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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.15.7 - 2026-08-24
|
|
6
|
+
|
|
7
|
+
- Prevent accepted phase checkpoints from producing `ready_to_apply` when they explicitly mark project completion as `in_progress`, including both string and object completion forms.
|
|
8
|
+
- Preserve unresolved checkpoint risks in the final judgement and treat a concrete continuation action as project work that must continue instead of terminal completion.
|
|
9
|
+
- Use an accepted terminal contract as the authoritative project-completion source when no separate acceptance ledger is configured.
|
|
10
|
+
- Reconcile stale optional post-completion authorization gates into `done` rather than `canceled`, while retaining fail-closed behavior for real blockers.
|
|
11
|
+
- Add regression coverage for premature terminal judgement, terminal-contract status projection, and historical stale-gate reconciliation.
|
|
12
|
+
|
|
5
13
|
## 0.15.6 - 2026-08-24
|
|
6
14
|
|
|
7
15
|
- Allow project configurations to use an external authoritative `backlogSource` without duplicating an embedded backlog registry.
|
package/lib/core.mjs
CHANGED
|
@@ -1173,20 +1173,34 @@ async function projectSpecs(root) {
|
|
|
1173
1173
|
return specs;
|
|
1174
1174
|
}
|
|
1175
1175
|
|
|
1176
|
-
|
|
1176
|
+
function acceptedTerminalRecord(record) {
|
|
1177
|
+
if (!record || typeof record !== 'object') return false;
|
|
1178
|
+
const accepted = record.status === 'accepted' || record.terminalState?.accepted === true || record.terminalAccepted === true;
|
|
1179
|
+
const unmet = Array.isArray(record.unmet) ? record.unmet : [];
|
|
1180
|
+
const blockers = Array.isArray(record.blockers) ? record.blockers : [];
|
|
1181
|
+
return accepted && unmet.length === 0 && blockers.length === 0;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
async function projectAcceptanceRecord(root, spec) {
|
|
1177
1185
|
const configuredAcceptanceLedger = spec?.acceptanceLedger ?? spec?.ledger;
|
|
1178
|
-
|
|
1186
|
+
const configuredTerminalContract = spec?.terminalContract;
|
|
1187
|
+
const configured = configuredAcceptanceLedger ?? configuredTerminalContract;
|
|
1188
|
+
if (!configured) return null;
|
|
1179
1189
|
try {
|
|
1180
|
-
const
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
&& Array.isArray(ledger.blockers) && ledger.blockers.length === 0;
|
|
1190
|
+
const kind = configuredAcceptanceLedger ? 'project acceptance ledger' : 'project terminal contract';
|
|
1191
|
+
const file = path.resolve(root, safeRelativePath(configured, kind));
|
|
1192
|
+
const record = await readJson(file);
|
|
1193
|
+
return { file, record, source: configuredAcceptanceLedger ? 'acceptance_ledger' : 'terminal_contract' };
|
|
1185
1194
|
} catch {
|
|
1186
|
-
return
|
|
1195
|
+
return null;
|
|
1187
1196
|
}
|
|
1188
1197
|
}
|
|
1189
1198
|
|
|
1199
|
+
async function projectTerminalAccepted(root, spec) {
|
|
1200
|
+
const acceptance = await projectAcceptanceRecord(root, spec);
|
|
1201
|
+
return acceptedTerminalRecord(acceptance?.record);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1190
1204
|
function requirementIdsFromCheckpoint(checkpoint, specs) {
|
|
1191
1205
|
const explicit = checkpoint?.requirement_ids ?? checkpoint?.requirementIds;
|
|
1192
1206
|
if (Array.isArray(explicit)) return [...new Set(explicit.map(String))];
|
|
@@ -1284,12 +1298,19 @@ export async function reconcileProjectGates(root, options = {}) {
|
|
|
1284
1298
|
await writeJson(full, reconciled);
|
|
1285
1299
|
if (found?.subdir === 'waiting' && task?.waitingGateId === gate.gate_id) {
|
|
1286
1300
|
const resumeSafeBacklog = invalid.code === 'safe_backlog_actionable';
|
|
1287
|
-
const
|
|
1288
|
-
const
|
|
1301
|
+
const projectAccepted = invalid.code === 'project_accepted_optional_deferred';
|
|
1302
|
+
const destination = resumeSafeBacklog ? 'inbox' : projectAccepted ? 'done' : 'canceled';
|
|
1303
|
+
const destinationFile = path.join(queueSubdirFor(root, queue, destination), path.basename(found.file));
|
|
1304
|
+
const restored = {
|
|
1305
|
+
...task,
|
|
1306
|
+
status: resumeSafeBacklog ? 'queued' : projectAccepted ? 'completed' : invalid.status,
|
|
1307
|
+
gateReconciliation: reconciled.reconciliation
|
|
1308
|
+
};
|
|
1289
1309
|
delete restored.waitingGateId;
|
|
1290
1310
|
delete restored.waitKind;
|
|
1291
1311
|
delete restored.waitingSince;
|
|
1292
|
-
if (
|
|
1312
|
+
if (projectAccepted) restored.completedAt ??= now;
|
|
1313
|
+
else if (!resumeSafeBacklog) restored.canceledAt = now;
|
|
1293
1314
|
else restored.requeuedAt = now;
|
|
1294
1315
|
await writeJson(destinationFile, restored);
|
|
1295
1316
|
await rm(found.file, { force: true });
|
|
@@ -1806,6 +1827,23 @@ export async function projectStatus(root, options = {}) {
|
|
|
1806
1827
|
acceptanceLedger = { file: path.relative(root, ledgerFile), readable: false, error: error.message };
|
|
1807
1828
|
}
|
|
1808
1829
|
}
|
|
1830
|
+
let terminalContract = null;
|
|
1831
|
+
if (!acceptanceLedger && spec.terminalContract) {
|
|
1832
|
+
const terminalFile = path.resolve(root, safeRelativePath(spec.terminalContract, 'project terminal contract'));
|
|
1833
|
+
try {
|
|
1834
|
+
const contract = await readJson(terminalFile);
|
|
1835
|
+
terminalContract = {
|
|
1836
|
+
file: path.relative(root, terminalFile),
|
|
1837
|
+
status: contract.status ?? null,
|
|
1838
|
+
terminalAccepted: contract.terminalState?.accepted === true,
|
|
1839
|
+
updatedAt: contract.updatedAt ?? contract.terminalState?.acceptedAt ?? null,
|
|
1840
|
+
unmet: Array.isArray(contract.unmet) ? contract.unmet : [],
|
|
1841
|
+
blockers: Array.isArray(contract.blockers) ? contract.blockers : []
|
|
1842
|
+
};
|
|
1843
|
+
} catch (error) {
|
|
1844
|
+
terminalContract = { file: path.relative(root, terminalFile), readable: false, error: error.message };
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1809
1847
|
const hasEmbeddedBacklog = Array.isArray(spec.backlog) && spec.backlog.length > 0;
|
|
1810
1848
|
const registryItems = hasEmbeddedBacklog ? spec.backlog : [];
|
|
1811
1849
|
const sourceItems = backlog ? await readJson(backlogFile).then((loaded) => loaded.items ?? loaded.tasks ?? []) : [];
|
|
@@ -1822,7 +1860,10 @@ export async function projectStatus(root, options = {}) {
|
|
|
1822
1860
|
}
|
|
1823
1861
|
}
|
|
1824
1862
|
}
|
|
1825
|
-
const
|
|
1863
|
+
const completionRecord = acceptanceLedger?.readable !== false && acceptanceLedger
|
|
1864
|
+
? acceptanceLedger
|
|
1865
|
+
: terminalContract?.readable !== false ? terminalContract : null;
|
|
1866
|
+
const projectCompletion = acceptedTerminalRecord(completionRecord)
|
|
1826
1867
|
? 'accepted'
|
|
1827
1868
|
: 'in_progress';
|
|
1828
1869
|
const needsAttention = [];
|
|
@@ -1832,6 +1873,7 @@ export async function projectStatus(root, options = {}) {
|
|
|
1832
1873
|
if (queues.some((queue) => queue.status.locked)) needsAttention.push('queue_locked');
|
|
1833
1874
|
if (drift.length > 0) needsAttention.push('authoritative_source_drift');
|
|
1834
1875
|
if (acceptanceLedger?.readable === false) needsAttention.push('acceptance_ledger_unreadable');
|
|
1876
|
+
if (!acceptanceLedger && terminalContract?.readable === false) needsAttention.push('terminal_contract_unreadable');
|
|
1835
1877
|
return {
|
|
1836
1878
|
version: 1,
|
|
1837
1879
|
project,
|
|
@@ -1843,13 +1885,15 @@ export async function projectStatus(root, options = {}) {
|
|
|
1843
1885
|
totals,
|
|
1844
1886
|
backlog,
|
|
1845
1887
|
acceptanceLedger,
|
|
1888
|
+
terminalContract,
|
|
1846
1889
|
authority: {
|
|
1847
|
-
terminalContract: spec.terminalContract ?? null,
|
|
1890
|
+
terminalContract: terminalContract?.file ?? spec.terminalContract ?? null,
|
|
1848
1891
|
backlog: backlog?.file ?? null,
|
|
1849
1892
|
acceptanceLedger: acceptanceLedger?.file ?? null,
|
|
1850
|
-
|
|
1893
|
+
completionSource: acceptanceLedger ? 'acceptance_ledger' : terminalContract ? 'terminal_contract' : null,
|
|
1894
|
+
rule: 'acceptance ledger determines project completion when configured; otherwise the terminal contract is authoritative; backlog source determines remaining work; registry is a projection validated for drift'
|
|
1851
1895
|
},
|
|
1852
|
-
consistency: { ok: drift.length === 0 && acceptanceLedger?.readable !== false, drift },
|
|
1896
|
+
consistency: { ok: drift.length === 0 && acceptanceLedger?.readable !== false && terminalContract?.readable !== false, drift },
|
|
1853
1897
|
projectCompletion,
|
|
1854
1898
|
latestIntake,
|
|
1855
1899
|
needsAttention,
|
|
@@ -3587,6 +3631,26 @@ function aggregateCriticStatus(baseStatus, criticReviews) {
|
|
|
3587
3631
|
return 'accepted';
|
|
3588
3632
|
}
|
|
3589
3633
|
|
|
3634
|
+
function projectCompletionStatus(value) {
|
|
3635
|
+
if (typeof value === 'string') return value;
|
|
3636
|
+
if (value && typeof value === 'object') return value.status ?? null;
|
|
3637
|
+
return null;
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
function continuationNextAction(value) {
|
|
3641
|
+
const action = String(value ?? '').trim().toLowerCase();
|
|
3642
|
+
if (!action) return false;
|
|
3643
|
+
return ![
|
|
3644
|
+
'acceptance_review',
|
|
3645
|
+
'final_judge',
|
|
3646
|
+
'ready_to_apply',
|
|
3647
|
+
'apply',
|
|
3648
|
+
'report',
|
|
3649
|
+
'complete',
|
|
3650
|
+
'completed'
|
|
3651
|
+
].includes(action);
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3590
3654
|
function buildCheckpointReview(contract, acceptancePlan, checkpoint) {
|
|
3591
3655
|
const baseStatus = checkpointReviewStatus(checkpoint);
|
|
3592
3656
|
const failed = [];
|
|
@@ -3709,6 +3773,8 @@ export async function writeAcceptanceReviews(root, queue, task, taskContract, ac
|
|
|
3709
3773
|
checkpointCreatedAt: checkpoint.created_at ?? null,
|
|
3710
3774
|
createdAt: checkpoint.created_at ?? review.created_at,
|
|
3711
3775
|
projectCompletion: checkpoint.project_completion ?? null,
|
|
3776
|
+
checkpointRisks: Array.isArray(checkpoint.risks) ? checkpoint.risks : [],
|
|
3777
|
+
checkpointNextAction: checkpoint.next_action ?? null,
|
|
3712
3778
|
deferredGates: materializableDeferredGates(checkpoint),
|
|
3713
3779
|
status: review.status,
|
|
3714
3780
|
file: path.relative(root, reviewFile)
|
|
@@ -3781,8 +3847,11 @@ export function buildFinalJudgement(contract, acceptancePlan, devPlan, checkpoin
|
|
|
3781
3847
|
const blockedCount = effectiveReviews.filter((item) => item.status === 'blocked').length;
|
|
3782
3848
|
const dispatchStatus = context.dispatchStatus ?? 'unknown';
|
|
3783
3849
|
const dispatchFailure = context.dispatchFailureClassification ?? null;
|
|
3784
|
-
const projectCompletionAccepted = effectiveReviews.some((review) => review.projectCompletion
|
|
3785
|
-
const projectCompletionInProgress = effectiveReviews.some((review) => review.projectCompletion
|
|
3850
|
+
const projectCompletionAccepted = effectiveReviews.some((review) => projectCompletionStatus(review.projectCompletion) === 'accepted');
|
|
3851
|
+
const projectCompletionInProgress = effectiveReviews.some((review) => projectCompletionStatus(review.projectCompletion) === 'in_progress');
|
|
3852
|
+
const explicitContinuationReviews = effectiveReviews.filter((review) =>
|
|
3853
|
+
(review.checkpointRisks?.length ?? 0) > 0 && continuationNextAction(review.checkpointNextAction)
|
|
3854
|
+
);
|
|
3786
3855
|
const deferredGateCount = effectiveReviews.reduce((count, review) => count + (review.deferredGates?.length ?? 0), 0);
|
|
3787
3856
|
let outcome = 'needs_revision';
|
|
3788
3857
|
|
|
@@ -3834,9 +3903,11 @@ export function buildFinalJudgement(contract, acceptancePlan, devPlan, checkpoin
|
|
|
3834
3903
|
outcome = 'needs_revision';
|
|
3835
3904
|
reasons.push(`Accepted checkpoints (${acceptedCount}) do not cover required checkpoints (${Math.max(requiredCheckpoints, 1)}).`);
|
|
3836
3905
|
nextActions.push('Complete and review the remaining planned checkpoints.');
|
|
3837
|
-
} else if ((contract.task_scope === 'project' || projectCompletionInProgress || deferredGateCount > 0) && !projectCompletionAccepted) {
|
|
3906
|
+
} else if ((contract.task_scope === 'project' || projectCompletionInProgress || explicitContinuationReviews.length > 0 || deferredGateCount > 0) && !projectCompletionAccepted) {
|
|
3838
3907
|
outcome = 'project_in_progress';
|
|
3839
3908
|
reasons.push('The latest milestone is accepted, but the project terminal contract is not accepted.');
|
|
3909
|
+
if (projectCompletionInProgress) reasons.push('The latest effective checkpoint explicitly marks project completion as in_progress.');
|
|
3910
|
+
if (explicitContinuationReviews.length > 0) reasons.push(`${explicitContinuationReviews.length} accepted checkpoint(s) record unresolved risks and an explicit continuation action.`);
|
|
3840
3911
|
if (deferredGateCount > 0) reasons.push(`${deferredGateCount} deferred authorization gate(s) remain and must be materialized as waiting gates when no unrelated safe work is actionable.`);
|
|
3841
3912
|
nextActions.push('Reread the authoritative project ledger and continue with the next safe actionable backlog item, or wait on a structured authorization gate.');
|
|
3842
3913
|
} else if (contract.requires_human_gate) {
|
|
@@ -3876,12 +3947,14 @@ export function buildFinalJudgement(contract, acceptancePlan, devPlan, checkpoin
|
|
|
3876
3947
|
revise: reviseCount,
|
|
3877
3948
|
blocked: blockedCount,
|
|
3878
3949
|
deferred_gates: deferredGateCount,
|
|
3950
|
+
explicit_continuations: explicitContinuationReviews.length,
|
|
3879
3951
|
rubric_items: Array.isArray(acceptancePlan?.rubric) ? acceptancePlan.rubric.length : 0,
|
|
3880
3952
|
automation_suggestions: Array.isArray(acceptancePlan?.automation) ? acceptancePlan.automation.length : 0
|
|
3881
3953
|
},
|
|
3882
3954
|
residual_risks: [
|
|
3883
3955
|
...(contract.requires_human_gate ? ['Human gate still required before external or high-risk action.'] : []),
|
|
3884
|
-
...(context.verificationFailed ? ['Configured verification has failing commands.'] : [])
|
|
3956
|
+
...(context.verificationFailed ? ['Configured verification has failing commands.'] : []),
|
|
3957
|
+
...explicitContinuationReviews.flatMap((review) => review.checkpointRisks ?? [])
|
|
3885
3958
|
],
|
|
3886
3959
|
created_at: new Date().toISOString()
|
|
3887
3960
|
};
|
package/package.json
CHANGED
|
@@ -132,6 +132,34 @@ assert.equal(inferTaskScope({ body: 'Continue the overall project with a product
|
|
|
132
132
|
assert.equal(judgement.outcome, 'project_in_progress');
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
{
|
|
136
|
+
const devPlan = { checkpoints: [{ id: 'cp1' }] };
|
|
137
|
+
const reviews = { reviews: [{
|
|
138
|
+
checkpointId: 'cp2', milestoneId: 'cp1', sequence: 2, status: 'accepted',
|
|
139
|
+
projectCompletion: 'in_progress',
|
|
140
|
+
checkpointRisks: ['Creative/perceptual director scoring is not wired into the production inspector.'],
|
|
141
|
+
checkpointNextAction: 'wire_director_perceptual_quality_and_quote_cost_without_paid_calls'
|
|
142
|
+
}] };
|
|
143
|
+
const judgement = buildFinalJudgement(baseContract, basePlan, devPlan, { count: 2 }, reviews, { dispatchStatus: 'completed' });
|
|
144
|
+
assert.equal(judgement.outcome, 'project_in_progress');
|
|
145
|
+
assert.equal(judgement.coverage.explicit_continuations, 1);
|
|
146
|
+
assert.deepEqual(judgement.residual_risks, ['Creative/perceptual director scoring is not wired into the production inspector.']);
|
|
147
|
+
assert.match(judgement.reasons.join(' '), /explicitly marks project completion as in_progress/);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
{
|
|
151
|
+
const devPlan = { checkpoints: [{ id: 'cp1' }] };
|
|
152
|
+
const reviews = { reviews: [{
|
|
153
|
+
checkpointId: 'cp2', milestoneId: 'cp1', sequence: 2, status: 'accepted',
|
|
154
|
+
projectCompletion: null,
|
|
155
|
+
checkpointRisks: ['A required production integration remains incomplete.'],
|
|
156
|
+
checkpointNextAction: 'implement_remaining_production_integration'
|
|
157
|
+
}] };
|
|
158
|
+
const judgement = buildFinalJudgement(baseContract, basePlan, devPlan, { count: 2 }, reviews, { dispatchStatus: 'completed' });
|
|
159
|
+
assert.equal(judgement.outcome, 'project_in_progress');
|
|
160
|
+
assert.equal(judgement.coverage.explicit_continuations, 1);
|
|
161
|
+
}
|
|
162
|
+
|
|
135
163
|
{
|
|
136
164
|
const devPlan = { checkpoints: [{ id: 'cp1' }] };
|
|
137
165
|
const reviews = { reviews: [{
|
|
@@ -105,6 +105,24 @@ const externalOnly = await projectStatus(root, { project: 'openreel' });
|
|
|
105
105
|
assert.equal(externalOnly.backlog.file, 'project/backlog.json');
|
|
106
106
|
assert.equal(externalOnly.backlog.count, 1);
|
|
107
107
|
assert.equal(externalOnly.consistency.ok, true);
|
|
108
|
+
|
|
109
|
+
// When a project deliberately uses its terminal contract as the completion
|
|
110
|
+
// ledger, accepted terminal bytes must project accepted status and suppress
|
|
111
|
+
// optional post-completion authorization gates.
|
|
112
|
+
const terminalContractFile = path.join(root, 'project', 'terminal-contract.json');
|
|
113
|
+
await writeFile(terminalContractFile, `${JSON.stringify({
|
|
114
|
+
status: 'accepted', terminalState: { accepted: true }, unmet: [], blockers: []
|
|
115
|
+
}, null, 2)}\n`);
|
|
116
|
+
await writeFile(projectFile, `${JSON.stringify({
|
|
117
|
+
...spec,
|
|
118
|
+
backlog: undefined,
|
|
119
|
+
backlogSource: 'project/backlog.json',
|
|
120
|
+
acceptanceLedger: undefined,
|
|
121
|
+
terminalContract: 'project/terminal-contract.json'
|
|
122
|
+
}, null, 2)}\n`);
|
|
123
|
+
const terminalOnly = await projectStatus(root, { project: 'openreel' });
|
|
124
|
+
assert.equal(terminalOnly.projectCompletion, 'accepted');
|
|
125
|
+
assert.equal(terminalOnly.authority.completionSource, 'terminal_contract');
|
|
108
126
|
await writeFile(projectFile, `${JSON.stringify({
|
|
109
127
|
...spec,
|
|
110
128
|
backlogSource: 'project/backlog.json',
|
|
@@ -176,6 +194,23 @@ await writeFile(authoritativeLedger, `${JSON.stringify({ status: 'accepted', unm
|
|
|
176
194
|
await reconcileProjectGates(root, { queue });
|
|
177
195
|
assert.equal((await queueStatus(root, queue)).waiting, 0);
|
|
178
196
|
assert.equal(JSON.parse(await readFile(path.join(root, deferredResult.ledger), 'utf8')).status, 'superseded');
|
|
197
|
+
const legacyTerminal = await enqueueTask(root, { queue, title: 'Legacy accepted-project gate', task: 'Project openreel terminal bookkeeping', projectId: 'openreel' });
|
|
198
|
+
const legacyCheckpointDir = path.join(taskRuntimeDirFor(root, queue, legacyTerminal.task.id), 'checkpoints');
|
|
199
|
+
await mkdir(legacyCheckpointDir, { recursive: true });
|
|
200
|
+
await writeFile(path.join(legacyCheckpointDir, 'cp-terminal.json'), `${JSON.stringify({ checkpoint_id: 'cp-terminal', status: 'ready_for_acceptance', blockers: [] }, null, 2)}\n`);
|
|
201
|
+
const legacyGateId = `${legacyTerminal.task.id}:cp-terminal`;
|
|
202
|
+
const legacyGateFile = path.join(root, 'runtime', 'loops', queue, 'human-input', 'gates', `${legacyTerminal.task.id}.cp-terminal.json`);
|
|
203
|
+
await writeFile(legacyGateFile, `${JSON.stringify({
|
|
204
|
+
gate_id: legacyGateId, task_id: legacyTerminal.task.id, checkpoint_id: 'cp-terminal', project_id: 'openreel',
|
|
205
|
+
requirement_ids: [], contract_hash: 'legacy', gate_kind: 'deferred_authorization', status: 'waiting_for_human'
|
|
206
|
+
}, null, 2)}\n`);
|
|
207
|
+
const legacyInbox = path.join(root, legacyTerminal.file);
|
|
208
|
+
const legacyWaiting = path.join(root, 'runtime', 'loops', queue, 'waiting', path.basename(legacyTerminal.file));
|
|
209
|
+
await writeFile(legacyWaiting, `${JSON.stringify({ ...legacyTerminal.task, status: 'waiting_for_human', waitingGateId: legacyGateId }, null, 2)}\n`);
|
|
210
|
+
await rename(legacyInbox, `${legacyInbox}.moved`);
|
|
211
|
+
await reconcileProjectGates(root, { queue });
|
|
212
|
+
assert.equal((await queueStatus(root, queue)).done >= 1, true, 'accepted project task must close in done, not canceled');
|
|
213
|
+
assert.equal(JSON.parse(await readFile(legacyGateFile, 'utf8')).status, 'superseded');
|
|
179
214
|
const acceptedOptional = await enqueueTask(root, { queue, title: 'OpenReel optional operations transfer', task: 'Project openreel optional post-completion transfer', projectId: 'openreel', sourceChannel: 'test', sourceTarget: 'owner' });
|
|
180
215
|
const acceptedOptionalDir = path.join(taskRuntimeDirFor(root, queue, acceptedOptional.task.id), 'checkpoints');
|
|
181
216
|
await mkdir(acceptedOptionalDir, { recursive: true });
|