taskforce-loop-engineering 0.15.15 → 0.15.16
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,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.15.16 - 2026-09-13
|
|
6
|
+
|
|
7
|
+
- Resolve canonical project identifiers from nested checkpoint completion metadata so migrated project tasks continue against the correct authoritative backlog.
|
|
8
|
+
- Keep future external-action authorization gates deferred while safe local project work remains actionable.
|
|
9
|
+
- Add regression coverage for checkpoint-only project metadata and premature deferred-gate reconciliation.
|
|
10
|
+
|
|
5
11
|
## 0.15.15 - 2026-08-31
|
|
6
12
|
|
|
7
13
|
- Lock a task-level accepted terminal checkpoint monotonically so later broader-project governance cannot reopen or overwrite the completed task.
|
package/lib/core.mjs
CHANGED
|
@@ -1214,7 +1214,12 @@ function requirementIdsFromCheckpoint(checkpoint, specs) {
|
|
|
1214
1214
|
}
|
|
1215
1215
|
|
|
1216
1216
|
function inferProjectSpec(task, checkpoint, specs, requirementIds = []) {
|
|
1217
|
-
const explicit = checkpoint?.project_id
|
|
1217
|
+
const explicit = checkpoint?.project_id
|
|
1218
|
+
?? checkpoint?.projectId
|
|
1219
|
+
?? checkpoint?.project_completion?.project
|
|
1220
|
+
?? checkpoint?.projectCompletion?.project
|
|
1221
|
+
?? task?.project_id
|
|
1222
|
+
?? task?.projectId;
|
|
1218
1223
|
if (explicit) return specs.find((spec) => spec.project === explicit) ?? null;
|
|
1219
1224
|
const text = `${task?.title ?? ''}\n${task?.body ?? ''}`.toLowerCase();
|
|
1220
1225
|
const candidates = specs.map((spec) => ({
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { mkdtemp, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { access, mkdtemp, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { doctorReport, enqueueTask, notifyHumanInputRequests, projectStatus, queueStatus, reconcileProjectGates, resolveHumanInput, routeLoopMessage, taskRuntimeDirFor, writeTaskContract } from '../lib/core.mjs';
|
|
@@ -176,6 +176,46 @@ const futureNotice = await notifyHumanInputRequests(root, { queue, notifyCommand
|
|
|
176
176
|
assert.equal(futureNotice.results.some((item) => item.taskId === futureGateTask.task.id), false);
|
|
177
177
|
assert.equal((await queueStatus(root, queue)).waiting, 0);
|
|
178
178
|
|
|
179
|
+
// Older queue carriers can lack project metadata while the worker checkpoint
|
|
180
|
+
// records the canonical project id inside project_completion. Resolve that id
|
|
181
|
+
// before evaluating deferred gates so safe local backlog keeps running.
|
|
182
|
+
const nestedProjectTask = await enqueueTask(root, {
|
|
183
|
+
queue,
|
|
184
|
+
title: 'Continue R1-R7',
|
|
185
|
+
task: 'Continue the next safe local milestone',
|
|
186
|
+
sourceChannel: 'test',
|
|
187
|
+
sourceTarget: 'owner',
|
|
188
|
+
sourceAccount: 'main'
|
|
189
|
+
});
|
|
190
|
+
const nestedProjectDir = path.join(taskRuntimeDirFor(root, queue, nestedProjectTask.task.id), 'checkpoints');
|
|
191
|
+
await mkdir(nestedProjectDir, { recursive: true });
|
|
192
|
+
await writeFile(path.join(nestedProjectDir, 'cp1.json'), `${JSON.stringify({
|
|
193
|
+
version: 1,
|
|
194
|
+
task_id: nestedProjectTask.task.id,
|
|
195
|
+
checkpoint_id: 'cp1',
|
|
196
|
+
status: 'ready_for_acceptance',
|
|
197
|
+
blockers: [],
|
|
198
|
+
project_completion: { project: 'openreel', status: 'in_progress' },
|
|
199
|
+
next_action: 'Implement LOCAL-02 locally.',
|
|
200
|
+
deferred_gates: [{
|
|
201
|
+
id: 'production-later',
|
|
202
|
+
action: 'production_deploy',
|
|
203
|
+
required_authority: 'Owner authorization after local candidate acceptance.',
|
|
204
|
+
authorization_state: 'missing',
|
|
205
|
+
needed_when: 'now',
|
|
206
|
+
materialize: true
|
|
207
|
+
}]
|
|
208
|
+
}, null, 2)}\n`);
|
|
209
|
+
const nestedProjectNotice = await notifyHumanInputRequests(root, { queue, notifyCommand: '/bin/true' });
|
|
210
|
+
assert.equal(nestedProjectNotice.results.some((item) => item.taskId === nestedProjectTask.task.id), false);
|
|
211
|
+
const nestedProjectStatus = await queueStatus(root, queue);
|
|
212
|
+
assert.equal(nestedProjectStatus.waiting, 0);
|
|
213
|
+
await access(path.join(root, nestedProjectTask.file));
|
|
214
|
+
await assert.rejects(
|
|
215
|
+
access(path.join(taskRuntimeDirFor(root, queue, nestedProjectTask.task.id), 'human_input_gate.json')),
|
|
216
|
+
{ code: 'ENOENT' }
|
|
217
|
+
);
|
|
218
|
+
|
|
179
219
|
// Conditional policy boundaries are not current blockers. Plain prose in
|
|
180
220
|
// deferred_gates must not materialize a gate without a concrete action and
|
|
181
221
|
// authority requirement.
|