@quolu/lattice 0.67.3 → 0.67.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quolu/lattice",
3
- "version": "0.67.3",
3
+ "version": "0.67.4",
4
4
  "description": "Schedulability compiler for multi-agent development: observe real code boundaries, refactor the conflicting seam, recompile the plan for parallel execution",
5
5
  "author": {
6
6
  "name": "Quo / クオ at kitepon.dev",
package/src/todo-cli.mjs CHANGED
@@ -1052,7 +1052,8 @@ async function dependencyConnect({
1052
1052
  async function dependencyDisconnect({
1053
1053
  repoRoot, env, fromPlanKey, fromTaskId, toPlanKey, toTaskId, reason,
1054
1054
  }) {
1055
- const store = await readTodoStore({ repoRoot });
1055
+ // 修理扉: 越境依存の固定digestが陳腐化した store でも disconnect だけは通す(todo-store.mjs 参照)
1056
+ const store = await readTodoStore({ repoRoot, tolerateStaleBindings: true });
1056
1057
  const live = projectTodoCrossPlanDependencies(store.members).filter((dependency) => (
1057
1058
  dependency.from.plan_key === fromPlanKey && dependency.from.task_id === fromTaskId
1058
1059
  && dependency.to.plan_key === toPlanKey && dependency.to.task_id === toTaskId
@@ -1068,6 +1069,7 @@ async function dependencyDisconnect({
1068
1069
  .some((event) => event.event_digest === target.event_digest));
1069
1070
  const { event } = await appendTodoEvent({
1070
1071
  repoRoot, writer: createTodoStoreWriter({ caller: 'g5-authoring' }), planKey: owner.plan.plan_key,
1072
+ tolerateStaleBindings: true,
1071
1073
  event: {
1072
1074
  kind: 'cross_plan_dependency_removed', actor: mutationActor(env),
1073
1075
  payload: { target_event_digest: target.event_digest, reason },
@@ -879,7 +879,13 @@ function snapshotFor(plan, events, tasks) {
879
879
  return snapshot;
880
880
  }
881
881
 
882
- function validateMergedGraph(members, crossPlanDependencies = []) {
882
+ function validateMergedGraph(members, crossPlanDependencies = [], options = {}) {
883
+ // tolerateStaleBindings は dependency disconnect 専用の修理扉。plan revise が越境依存の
884
+ // 固定digestを陳腐化させると、通常loadは binding_stale で fail closed するが、その修理
885
+ // コマンド自身も load を要するため、扉が無いと修理経路が存在しない(実被弾 2026-08-29:
886
+ // evidence-2 改訂で store 全体が読めなくなり、disconnect も同エラーで詰んだ)。
887
+ // 許容するのは digest 不一致だけで、dangling / missing は修理扉でも fail する。
888
+ const tolerateStale = options.tolerateStaleBindings === true;
883
889
  const tasks = new Map();
884
890
  for (const member of members) for (const task of member.plan.tasks) {
885
891
  tasks.set(`${member.plan.project_id}\0${member.plan.plan_key}\0${task.task_id}`, member.plan.topology_digest);
@@ -900,7 +906,7 @@ function validateMergedGraph(members, crossPlanDependencies = []) {
900
906
  if (topology === undefined) fail('STORE_INCONSISTENT', 'dangling_dependency');
901
907
  if ((ref.project_id !== ownerPlan.project_id || ref.plan_key !== ownerPlan.plan_key)
902
908
  && ref.expected_topology_digest === undefined) fail('STORE_INCONSISTENT', 'cross_plan_binding_missing');
903
- if (ref.expected_topology_digest !== undefined && topology !== ref.expected_topology_digest) {
909
+ if (!tolerateStale && ref.expected_topology_digest !== undefined && topology !== ref.expected_topology_digest) {
904
910
  fail('STORE_INCONSISTENT', 'binding_stale');
905
911
  }
906
912
  return key;
@@ -911,7 +917,7 @@ function validateMergedGraph(members, crossPlanDependencies = []) {
911
917
  if (topology === undefined) fail('STORE_INCONSISTENT', 'dangling_phase_dependency');
912
918
  if ((ref.project_id !== ownerPlan.project_id || ref.plan_key !== ownerPlan.plan_key)
913
919
  && ref.expected_topology_digest === undefined) fail('STORE_INCONSISTENT', 'cross_plan_binding_missing');
914
- if (ref.expected_topology_digest !== undefined && topology !== ref.expected_topology_digest) {
920
+ if (!tolerateStale && ref.expected_topology_digest !== undefined && topology !== ref.expected_topology_digest) {
915
921
  fail('STORE_INCONSISTENT', 'binding_stale');
916
922
  }
917
923
  return key;
@@ -1629,7 +1635,9 @@ export async function readTodoStore(options = {}) {
1629
1635
  snapshot: snapshotStale ? expectedSnapshot : snapshot,
1630
1636
  tasks, phases, coordination, snapshot_stale: snapshotStale });
1631
1637
  }
1632
- validateMergedGraph(loaded, projectTodoCrossPlanDependencies(loaded));
1638
+ validateMergedGraph(loaded, projectTodoCrossPlanDependencies(loaded), {
1639
+ tolerateStaleBindings: options.tolerateStaleBindings === true,
1640
+ });
1633
1641
  return {
1634
1642
  schema: 'lattice.todo_store_read.v1', project_id: manifest.project_id, manifest,
1635
1643
  members: loaded, snapshot_stale: loaded.some((member) => member.snapshot_stale),
@@ -2036,6 +2044,10 @@ export async function appendTodoEvent(options = {}) {
2036
2044
  const store = await readTodoStore({
2037
2045
  repoRoot, forWrite: true, now: options.now, evidenceRepair,
2038
2046
  evidenceRepairCapability: EVIDENCE_REPAIR_CAPABILITY,
2047
+ // 修理扉は除去event(tombstone)の書込みだけに開く。それ以外の書込みは従来どおり
2048
+ // binding_stale で fail closed する(validateMergedGraph の注記を参照)
2049
+ tolerateStaleBindings: options.tolerateStaleBindings === true
2050
+ && options.event.kind === 'cross_plan_dependency_removed',
2039
2051
  });
2040
2052
  const member = store.members.find(({ descriptor }) => descriptor.plan_key === options.planKey);
2041
2053
  if (!member) fail('STORE_INCONSISTENT', 'plan_not_active');