@quolu/lattice 0.12.7 → 0.12.9
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 +3 -1
- package/src/todo-cli.mjs +5 -4
- package/src/todo-store.mjs +42 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quolu/lattice",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.9",
|
|
4
4
|
"description": "Lattice — phase-aware TODO graph compiler and conflict-aware orchestration runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,6 +49,8 @@
|
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"prepack": "npm --prefix sensor run build",
|
|
52
|
+
"verify:release-commit": "node scripts/verify-release-commit.mjs",
|
|
53
|
+
"prepublishOnly": "npm run verify:release-commit",
|
|
52
54
|
"test": "node scripts/run-product-tests.mjs",
|
|
53
55
|
"test:sensor": "npm --prefix sensor test",
|
|
54
56
|
"precheck": "node --check src/bridge-launch-agent.mjs",
|
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);
|
|
@@ -1747,13 +1764,17 @@ function phaseV3CarrySemantics(plan, taskId, taskIdMap, phaseIdMap,
|
|
|
1747
1764
|
? { ...ref, task_id: taskIdMap.get(ref.task_id) ?? ref.task_id } : ref;
|
|
1748
1765
|
const mapPhaseRef = (ref) => ref.project_id === plan.project_id && ref.plan_key === plan.plan_key
|
|
1749
1766
|
? { ...ref, phase_id: phaseIdMap.get(ref.phase_id) ?? ref.phase_id } : ref;
|
|
1767
|
+
// Phaseを持たない世代(todo_plan.v3)のtaskはphase_idを持たない。undefinedのまま
|
|
1768
|
+
// canonicalizeするとJSON treeでないとしてTypeErrorになり、typedな拒否理由が失われる。
|
|
1769
|
+
// nullへ正規化してcarry比較へ渡し、Phase割当ての獲得をcarry_semantics_changedとして拒否する。
|
|
1770
|
+
const mappedPhaseId = phaseIdMap.get(task.phase_id) ?? task.phase_id ?? null;
|
|
1750
1771
|
const mappedTask = reconciliationMetadata ? {
|
|
1751
1772
|
task_id: taskIdMap.get(task.task_id) ?? task.task_id, title: task.title, lane: task.lane,
|
|
1752
1773
|
compile_binding: task.compile_binding,
|
|
1753
|
-
phase_id:
|
|
1774
|
+
phase_id: mappedPhaseId,
|
|
1754
1775
|
} : { ...task,
|
|
1755
1776
|
task_id: taskIdMap.get(task.task_id) ?? task.task_id,
|
|
1756
|
-
phase_id:
|
|
1777
|
+
phase_id: mappedPhaseId,
|
|
1757
1778
|
parent_task_id: task.parent_task_id === null ? null
|
|
1758
1779
|
: taskIdMap.get(task.parent_task_id) ?? task.parent_task_id,
|
|
1759
1780
|
};
|
|
@@ -2575,7 +2596,19 @@ function validatePhaseV3SourceInventoryDiff(previousInventory, revision) {
|
|
|
2575
2596
|
for (const entry of previousInventory.active) {
|
|
2576
2597
|
const active = desired.active.find(({ task_id }) => task_id === entry.task_id);
|
|
2577
2598
|
const continued = active?.source_ref === entry.source_ref && active.source_digest === entry.source_digest;
|
|
2578
|
-
|
|
2599
|
+
const relocated = revision.source_cutover_batch.operations.some((operation, index) => {
|
|
2600
|
+
if (operation.source_ref !== entry.source_ref || operation.source_digest !== entry.source_digest) {
|
|
2601
|
+
return false;
|
|
2602
|
+
}
|
|
2603
|
+
const archiveRef = todoCutoverArchiveSourceRef(revision.source_cutover_batch, index);
|
|
2604
|
+
return operation.disposition === 'active'
|
|
2605
|
+
? desired.active.some((candidate) => candidate.task_id === operation.task_id
|
|
2606
|
+
&& candidate.source_ref === archiveRef && candidate.source_digest === operation.source_digest)
|
|
2607
|
+
: desired.excluded_tombstones.some((candidate) => candidate.source_ref === archiveRef
|
|
2608
|
+
&& candidate.source_digest === operation.source_digest);
|
|
2609
|
+
});
|
|
2610
|
+
if (!continued && !relocated
|
|
2611
|
+
&& !desiredTombstoneKeys.has(`${entry.source_ref}\0${entry.source_digest}`)) {
|
|
2579
2612
|
fail('REVISION_INVALID', 'predecessor_source_silently_dropped', { task_id: entry.task_id });
|
|
2580
2613
|
}
|
|
2581
2614
|
}
|
|
@@ -2904,7 +2937,12 @@ export async function applyPhaseTodoRevision(options = {}) {
|
|
|
2904
2937
|
const descriptor = currentManifest.members.find(({ plan_key }) => plan_key === revision.plan_key);
|
|
2905
2938
|
Object.assign(descriptor, { active_plan_version: revision.desired_plan.plan_version,
|
|
2906
2939
|
plan_ref: planRef, journal_ref: journalRef, snapshot_ref: snapshotRef,
|
|
2907
|
-
topology_digest: revision.desired_plan.topology_digest, journal_head_digest: genesis.event_digest
|
|
2940
|
+
topology_digest: revision.desired_plan.topology_digest, journal_head_digest: genesis.event_digest,
|
|
2941
|
+
// manifest v2はactive_revision_digestがgenesisのrevision_digestと一致することを読み取り時に
|
|
2942
|
+
// 要求する。v3昇格後のstoreでここを据え置くと、書込みは成功したように見えるのに以後の
|
|
2943
|
+
// readがmanifest_revision_binding_mismatchで落ちる。v1 memberはこのkeyを持てない。
|
|
2944
|
+
...(currentManifest.schema === 'lattice.todo_manifest.v2'
|
|
2945
|
+
? { active_revision_digest: revision.revision_digest } : {}) });
|
|
2908
2946
|
currentManifest.manifest_digest = todoSelfDigest(currentManifest, 'manifest_digest');
|
|
2909
2947
|
await atomicWrite(path.resolve(repoRoot, MANIFEST_REF), canonicalLine(currentManifest));
|
|
2910
2948
|
await protocolStage(options, 'phase_revision_manifest_activated');
|