@quolu/lattice 0.12.7 → 0.12.8
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/package.json +1 -1
- package/src/todo-cli.mjs +5 -4
- package/src/todo-store.mjs +30 -1
package/package.json
CHANGED
package/src/todo-cli.mjs
CHANGED
|
@@ -307,7 +307,7 @@ async function mutate({ repoRoot, env, planKey, taskId, kind, payload, evidenceR
|
|
|
307
307
|
planKey,
|
|
308
308
|
event: { kind, task_id: taskId, actor, payload: eventPayload },
|
|
309
309
|
});
|
|
310
|
-
const task = snapshot.tasks.find(({ task_id: current }) => current ===
|
|
310
|
+
const task = snapshot.tasks.find(({ task_id: current }) => current === event.task_id);
|
|
311
311
|
const result = {
|
|
312
312
|
schema: 'lattice.todo_mutation_result.v1',
|
|
313
313
|
project_id: event.project_id,
|
|
@@ -328,9 +328,10 @@ async function mutate({ repoRoot, env, planKey, taskId, kind, payload, evidenceR
|
|
|
328
328
|
|
|
329
329
|
async function startTask({ repoRoot, env, planKey, taskId, overrideReason, parallelFrontier }) {
|
|
330
330
|
const projection = projectTodoStatus(await readTodoStore({ repoRoot }));
|
|
331
|
-
const
|
|
332
|
-
task.plan_key === planKey && task.task_id === taskId
|
|
331
|
+
const readyTask = projection.next_ready.find((task) => (
|
|
332
|
+
task.plan_key === planKey && task.task_id.toLowerCase() === taskId.toLowerCase()
|
|
333
333
|
));
|
|
334
|
+
const targetReady = readyTask !== undefined;
|
|
334
335
|
if (parallelFrontier && !targetReady) {
|
|
335
336
|
throw new TodoStoreError('PARALLEL_DISPATCH_INVALID', 'parallel_frontier_not_applicable');
|
|
336
337
|
}
|
|
@@ -344,7 +345,7 @@ async function startTask({ repoRoot, env, planKey, taskId, overrideReason, paral
|
|
|
344
345
|
serial_reason_flag: '--override-reason',
|
|
345
346
|
});
|
|
346
347
|
}
|
|
347
|
-
return mutate({ repoRoot, env, planKey, taskId, kind: 'start',
|
|
348
|
+
return mutate({ repoRoot, env, planKey, taskId: readyTask?.task_id ?? taskId, kind: 'start',
|
|
348
349
|
payload: { override_reason: overrideReason }, evidenceRef: null });
|
|
349
350
|
}
|
|
350
351
|
|
package/src/todo-store.mjs
CHANGED
|
@@ -1117,6 +1117,22 @@ function resolveTargetedEvent(input, storeMember) {
|
|
|
1117
1117
|
return input;
|
|
1118
1118
|
}
|
|
1119
1119
|
|
|
1120
|
+
function resolveCanonicalTaskId(plan, requestedTaskId) {
|
|
1121
|
+
if (requestedTaskId === null || requestedTaskId === undefined) return requestedTaskId;
|
|
1122
|
+
const exact = plan.tasks.find(({ task_id: taskId }) => taskId === requestedTaskId);
|
|
1123
|
+
if (exact) return exact.task_id;
|
|
1124
|
+
const folded = requestedTaskId.toLowerCase();
|
|
1125
|
+
const matches = plan.tasks.filter(({ task_id: taskId }) => taskId.toLowerCase() === folded);
|
|
1126
|
+
if (matches.length === 0) fail('TASK_NOT_FOUND', 'task_not_found', {
|
|
1127
|
+
requested_task_id: requestedTaskId,
|
|
1128
|
+
});
|
|
1129
|
+
if (matches.length > 1) fail('TASK_ID_AMBIGUOUS', 'task_id_case_ambiguous', {
|
|
1130
|
+
requested_task_id: requestedTaskId,
|
|
1131
|
+
matching_task_ids: matches.map(({ task_id: taskId }) => taskId).sort(),
|
|
1132
|
+
});
|
|
1133
|
+
return matches[0].task_id;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1120
1136
|
export async function appendTodoEvent(options = {}) {
|
|
1121
1137
|
requireWriter(options.writer, 'g5-authoring');
|
|
1122
1138
|
const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
|
|
@@ -1126,6 +1142,7 @@ export async function appendTodoEvent(options = {}) {
|
|
|
1126
1142
|
if (!member) fail('STORE_INCONSISTENT', 'plan_not_active');
|
|
1127
1143
|
const input = resolveTargetedEvent({
|
|
1128
1144
|
...options.event,
|
|
1145
|
+
task_id: resolveCanonicalTaskId(member.plan, options.event.task_id),
|
|
1129
1146
|
recorded_at: options.event.recorded_at ?? new Date().toISOString(),
|
|
1130
1147
|
}, member);
|
|
1131
1148
|
const event = nextEvent(input, member);
|
|
@@ -2575,7 +2592,19 @@ function validatePhaseV3SourceInventoryDiff(previousInventory, revision) {
|
|
|
2575
2592
|
for (const entry of previousInventory.active) {
|
|
2576
2593
|
const active = desired.active.find(({ task_id }) => task_id === entry.task_id);
|
|
2577
2594
|
const continued = active?.source_ref === entry.source_ref && active.source_digest === entry.source_digest;
|
|
2578
|
-
|
|
2595
|
+
const relocated = revision.source_cutover_batch.operations.some((operation, index) => {
|
|
2596
|
+
if (operation.source_ref !== entry.source_ref || operation.source_digest !== entry.source_digest) {
|
|
2597
|
+
return false;
|
|
2598
|
+
}
|
|
2599
|
+
const archiveRef = todoCutoverArchiveSourceRef(revision.source_cutover_batch, index);
|
|
2600
|
+
return operation.disposition === 'active'
|
|
2601
|
+
? desired.active.some((candidate) => candidate.task_id === operation.task_id
|
|
2602
|
+
&& candidate.source_ref === archiveRef && candidate.source_digest === operation.source_digest)
|
|
2603
|
+
: desired.excluded_tombstones.some((candidate) => candidate.source_ref === archiveRef
|
|
2604
|
+
&& candidate.source_digest === operation.source_digest);
|
|
2605
|
+
});
|
|
2606
|
+
if (!continued && !relocated
|
|
2607
|
+
&& !desiredTombstoneKeys.has(`${entry.source_ref}\0${entry.source_digest}`)) {
|
|
2579
2608
|
fail('REVISION_INVALID', 'predecessor_source_silently_dropped', { task_id: entry.task_id });
|
|
2580
2609
|
}
|
|
2581
2610
|
}
|