@quolu/lattice 0.63.5 → 0.63.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.
Files changed (62) hide show
  1. package/LICENSE +147 -147
  2. package/README.ja.md +378 -378
  3. package/README.md +303 -303
  4. package/bin/lattice-mcp.mjs +0 -0
  5. package/bin/lattice-scripted-adapter.mjs +0 -0
  6. package/bin/lattice-scripted-worker.mjs +0 -0
  7. package/bin/lattice-work-order-adapter.mjs +0 -0
  8. package/bin/lattice.mjs +0 -0
  9. package/docs/bridge-setup.md +248 -248
  10. package/docs/schemas/lattice.executor_packet.v1.schema.json +57 -57
  11. package/docs/schemas/lattice.executor_receipt.v1.schema.json +66 -66
  12. package/docs/schemas/lattice.phase_todo_revision.v3.schema.json +360 -360
  13. package/docs/schemas/lattice.plan_create_input.v1.schema.json +56 -56
  14. package/docs/schemas/lattice.plan_create_input.v2.schema.json +72 -72
  15. package/docs/schemas/lattice.plan_create_input.v3.schema.json +81 -81
  16. package/docs/schemas/lattice.plan_create_input.v4.schema.json +357 -357
  17. package/docs/schemas/lattice.plan_scope_review.v1.schema.json +55 -55
  18. package/docs/schemas/lattice.run_request.v1.schema.json +238 -238
  19. package/docs/schemas/lattice.runtime_adapter_capabilities.v2.schema.json +55 -55
  20. package/docs/schemas/lattice.runtime_adapter_registration_input.v1.schema.json +78 -78
  21. package/docs/schemas/lattice.runtime_adapter_registration_input.v2.schema.json +86 -86
  22. package/docs/schemas/lattice.todo_extraction.v2.schema.json +298 -298
  23. package/docs/schemas/lattice.todo_extraction.v3.schema.json +150 -150
  24. package/docs/schemas/lattice.todo_extraction.v4.schema.json +161 -161
  25. package/docs/schemas/lattice.todo_revision.v2.schema.json +260 -260
  26. package/docs/schemas/lattice.todo_revision_set.v3.schema.json +363 -363
  27. package/docs/schemas/lattice.todo_structure_binding.v1.schema.json +47 -47
  28. package/docs/schemas/lattice.todo_structure_realization.v1.schema.json +55 -55
  29. package/docs/schemas/lattice.todo_structure_set.v1.schema.json +264 -264
  30. package/package.json +109 -109
  31. package/sensor/LICENSE +21 -21
  32. package/sensor/NOTICE +19 -19
  33. package/sensor/dist/bin/lattice-sensor.js +9 -9
  34. package/sensor/dist/db/index.js +24 -24
  35. package/sensor/dist/db/migrations.js +41 -41
  36. package/sensor/dist/db/queries.js +164 -164
  37. package/sensor/dist/db/schema.sql +205 -205
  38. package/sensor/dist/directory.js +5 -5
  39. package/sensor/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
  40. package/sensor/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  41. package/sensor/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  42. package/sensor/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  43. package/sensor/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  44. package/sensor/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  45. package/sensor/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
  46. package/sensor/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
  47. package/sensor/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
  48. package/sensor/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  49. package/sensor/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  50. package/sensor/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
  51. package/sensor/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
  52. package/sensor/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
  53. package/sensor/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  54. package/sensor/dist/mcp/liveness-watchdog.js +53 -53
  55. package/sensor/dist/mcp/server-instructions.js +95 -95
  56. package/sensor/package.json +56 -56
  57. package/src/cli-help.mjs +1 -0
  58. package/src/runtime-decision-verifier.mjs +6 -11
  59. package/src/runtime-diff-observer.mjs +8 -4
  60. package/src/todo-cli.mjs +4 -1
  61. package/src/todo-contracts.mjs +7 -2
  62. package/src/todo-store.mjs +100 -26
@@ -59,6 +59,7 @@ const CROSS_PLAN_RECOVERY_CLAIMS_REF = `${STORE_ROOT_REF}/.cross-plan-recovery`;
59
59
  const CROSS_PLAN_IMPORT_TRANSACTION_SCHEMA = 'lattice.todo_cross_plan_import_transaction.v1';
60
60
  const SOURCE_CUTOVER_BARRIER_REF = `${STORE_ROOT_REF}/source-cutover-recovery.json`;
61
61
  const SOURCE_CUTOVER_RECOVERY_CAPABILITY = Symbol('lattice.todo.source-cutover-recovery');
62
+ const EVIDENCE_REPAIR_CAPABILITY = Symbol('lattice.todo.evidence-repair');
62
63
  const MAX_FUTURE_SKEW_MS = 5 * 60 * 1_000;
63
64
  const WRITER_CALLERS = new Set(['g4-migration', 'g5-authoring']);
64
65
  const TODO_REVISION_SCHEMAS = Object.freeze([
@@ -534,8 +535,11 @@ function replay(plan, events, { now = new Date(), verifyEvidence, verifyImportSo
534
535
  Object.assign(state, structuredClone(migration.state), { evidence_unverified: false });
535
536
  if (state.evidence !== null) {
536
537
  const evidence = state.evidence;
537
- const context = { plan_key: plan.plan_key, task_id: migration.to_task_id };
538
- if (state.imported) {
538
+ const context = { plan_key: plan.plan_key, task_id: migration.to_task_id,
539
+ event_digest: event.event_digest };
540
+ // importedは完了の来歴であって現在のdescriptor型ではない。promotion後の
541
+ // imported doneは通常evidenceを持つため、descriptor自身から検証器を選ぶ。
542
+ if (validateTodoImportSource(evidence)) {
539
543
  if (verifyImportSource) {
540
544
  pendingVerifications.set(`task:${migration.to_task_id}`, () => verifyImportSource(evidence, context));
541
545
  }
@@ -655,7 +659,7 @@ function replay(plan, events, { now = new Date(), verifyEvidence, verifyImportSo
655
659
  if (verifyImportSource) {
656
660
  const evidence = event.payload.evidence;
657
661
  pendingVerifications.set(`task:${event.task_id}`, () => verifyImportSource(evidence, {
658
- plan_key: plan.plan_key, task_id: event.task_id,
662
+ plan_key: plan.plan_key, task_id: event.task_id, event_digest: event.event_digest,
659
663
  }));
660
664
  }
661
665
  state.status = 'in-progress';
@@ -709,7 +713,7 @@ function replay(plan, events, { now = new Date(), verifyEvidence, verifyImportSo
709
713
  if (verifyEvidence) {
710
714
  const evidence = event.payload.evidence;
711
715
  pendingVerifications.set(`task:${event.task_id}`, () => verifyEvidence(evidence, {
712
- plan_key: plan.plan_key, task_id: event.task_id,
716
+ plan_key: plan.plan_key, task_id: event.task_id, event_digest: event.event_digest,
713
717
  }));
714
718
  }
715
719
  state.status = 'done'; state.done_at = event.recorded_at; state.evidence = event.payload.evidence;
@@ -721,7 +725,7 @@ function replay(plan, events, { now = new Date(), verifyEvidence, verifyImportSo
721
725
  if (verifyImportSource) {
722
726
  const evidence = event.payload.evidence;
723
727
  pendingVerifications.set(`task:${event.task_id}`, () => verifyImportSource(evidence, {
724
- plan_key: plan.plan_key, task_id: event.task_id,
728
+ plan_key: plan.plan_key, task_id: event.task_id, event_digest: event.event_digest,
725
729
  }));
726
730
  }
727
731
  state.status = 'done'; state.done_at = event.payload.completed_at === 'unknown_requires_evidence'
@@ -730,15 +734,27 @@ function replay(plan, events, { now = new Date(), verifyEvidence, verifyImportSo
730
734
  completion.set(event.task_id, { mode: 'historical_import', completed_at: event.payload.completed_at });
731
735
  } else {
732
736
  const current = completion.get(event.task_id);
733
- if (state.status !== 'done' || current?.mode !== 'historical_import'
734
- || current.completed_at !== 'unknown_requires_evidence'
735
- || doneDigest.get(event.task_id) !== event.payload.target_done_digest) {
736
- fail('STORE_INCONSISTENT', 'invalid_evidence_promotion');
737
+ const expectedTarget = doneDigest.get(event.task_id) ?? null;
738
+ if (state.status !== 'done' || current === undefined
739
+ || state.imported !== event.payload.imported
740
+ || expectedTarget !== event.payload.target_done_digest) {
741
+ fail('STORE_INCONSISTENT', 'invalid_evidence_promotion', {
742
+ task_id: event.task_id,
743
+ status: state.status,
744
+ current_completion_mode: current?.mode ?? null,
745
+ current_imported: state.imported,
746
+ requested_imported: event.payload.imported,
747
+ expected_target_done_digest: expectedTarget,
748
+ actual_target_done_digest: event.payload.target_done_digest,
749
+ eligible_status: 'done',
750
+ next_action: state.status === 'done'
751
+ ? 'retry_against_latest_done_state' : 'complete_the_task_before_promoting_evidence',
752
+ });
737
753
  }
738
754
  if (verifyEvidence) {
739
755
  const evidence = event.payload.evidence;
740
756
  pendingVerifications.set(`task:${event.task_id}`, () => verifyEvidence(evidence, {
741
- plan_key: plan.plan_key, task_id: event.task_id,
757
+ plan_key: plan.plan_key, task_id: event.task_id, event_digest: event.event_digest,
742
758
  }));
743
759
  }
744
760
  state.evidence = event.payload.evidence;
@@ -1143,7 +1159,7 @@ function readEvidenceBlob(absoluteRepo, oid) {
1143
1159
  return entry.bytes;
1144
1160
  }
1145
1161
 
1146
- function evidenceVerifier(manifest, repoRoot, hard) {
1162
+ function evidenceVerifier(manifest, repoRoot, hard, repair = null) {
1147
1163
  const repositories = new Map(manifest.repositories.map((repo) => [repo.repo_id, repo.path]));
1148
1164
  return (descriptor, context = {}) => {
1149
1165
  if (!validateEvidenceDescriptor(descriptor)) fail('STORE_INCONSISTENT', 'evidence_descriptor_invalid');
@@ -1166,6 +1182,15 @@ function evidenceVerifier(manifest, repoRoot, hard) {
1166
1182
  if (sha256Bytes(bytes) !== descriptor.content_digest) throw new Error('digest mismatch');
1167
1183
  return true;
1168
1184
  } catch {
1185
+ const repairTarget = repair !== null
1186
+ && typeof repair.taskId === 'string'
1187
+ && context.plan_key === repair.planKey
1188
+ && typeof context.task_id === 'string'
1189
+ && context.task_id === repair.taskId;
1190
+ // evidence promotionだけは、同じtaskの過去eventを新しいhard-verified eventで
1191
+ // supersedeするためにここを通る。prospective event自身と他taskは免除しない。
1192
+ if (repairTarget
1193
+ && (repair.eventDigest === null || context.event_digest !== repair.eventDigest)) return false;
1169
1194
  if (hard) fail('STORE_INCONSISTENT', 'evidence_unverified', {
1170
1195
  ...context,
1171
1196
  next_action: context.plan_key === undefined
@@ -1325,13 +1350,20 @@ function liveReplacementPreservesListStructure(lineBytes, replacement) {
1325
1350
  return source !== null && target !== null && source[1] === target[1] && source[2] === target[2];
1326
1351
  }
1327
1352
 
1328
- function importSourceVerifier(repoRoot, hard, cache = null) {
1353
+ function importSourceVerifier(repoRoot, hard, cache = null, repair = null) {
1329
1354
  return (descriptor, context = {}) => {
1330
1355
  if (!validateTodoImportSource(descriptor)) fail('STORE_INCONSISTENT', 'import_source_descriptor_invalid');
1331
1356
  try {
1332
1357
  pinnedSourceLine(repoRoot, descriptor, cache);
1333
1358
  return true;
1334
1359
  } catch {
1360
+ const repairTarget = repair !== null
1361
+ && typeof repair.taskId === 'string'
1362
+ && context.plan_key === repair.planKey
1363
+ && typeof context.task_id === 'string'
1364
+ && context.task_id === repair.taskId;
1365
+ if (repairTarget
1366
+ && (repair.eventDigest === null || context.event_digest !== repair.eventDigest)) return false;
1335
1367
  if (hard) fail('STORE_INCONSISTENT', 'import_source_unverified', {
1336
1368
  ...context,
1337
1369
  next_action: 'verify_source_commit_origin_path_and_line_then_retry',
@@ -1506,8 +1538,14 @@ export async function readTodoStore(options = {}) {
1506
1538
  fail('STORE_INCONSISTENT', 'manifest_revision_binding_mismatch');
1507
1539
  }
1508
1540
  prefetchVerificationObjects(manifest, repoRoot, journal.events, pinnedSourceCache);
1509
- const verifyEvidence = evidenceVerifier(manifest, repoRoot, options.forWrite === true);
1510
- const verifyImportSource = importSourceVerifier(repoRoot, options.forWrite === true, pinnedSourceCache);
1541
+ const evidenceRepair = options.evidenceRepairCapability === EVIDENCE_REPAIR_CAPABILITY
1542
+ ? options.evidenceRepair ?? null : null;
1543
+ const verifyEvidence = evidenceVerifier(
1544
+ manifest, repoRoot, options.forWrite === true, evidenceRepair,
1545
+ );
1546
+ const verifyImportSource = importSourceVerifier(
1547
+ repoRoot, options.forWrite === true, pinnedSourceCache, evidenceRepair,
1548
+ );
1511
1549
  const tasks = replay(plan, journal.events, {
1512
1550
  now: options.now ? new Date(options.now) : new Date(),
1513
1551
  verifyEvidence: options.forWrite === true ? verifyEvidence : undefined,
@@ -1516,8 +1554,13 @@ export async function readTodoStore(options = {}) {
1516
1554
  const expectedSnapshot = snapshotFor(plan, journal.events, structuredClone(tasks));
1517
1555
  // Read-time evidence failure is annotation, never store rejection.
1518
1556
  for (const task of tasks) if (task.evidence !== null) {
1557
+ const context = {
1558
+ plan_key: plan.plan_key,
1559
+ task_id: task.task_id,
1560
+ event_digest: null,
1561
+ };
1519
1562
  const verified = validateTodoImportSource(task.evidence)
1520
- ? verifyImportSource(task.evidence) : verifyEvidence(task.evidence);
1563
+ ? verifyImportSource(task.evidence, context) : verifyEvidence(task.evidence, context);
1521
1564
  if (!verified) task.evidence_unverified = true;
1522
1565
  }
1523
1566
  let snapshot = null;
@@ -1829,17 +1872,33 @@ function resolveTargetedEvent(input, storeMember) {
1829
1872
  payload: { ...input.payload, target_done_digest: targetDigest },
1830
1873
  };
1831
1874
  }
1832
- if (input.kind === 'done' && exactRecord(input.payload, [
1833
- 'done_mode', 'imported', 'evidence',
1834
- ]) && input.payload.done_mode === 'evidence_promotion' && input.payload.imported === true) {
1835
- // Same binding as `reopen`: an imported completion carried across a revision
1836
- // is bound to the carrying `plan_genesis`, not to a `done` event. Whether the
1837
- // completion is actually eligible for promotion stays replay's decision.
1875
+ if (input.kind === 'done'
1876
+ && (exactRecord(input.payload, ['done_mode', 'evidence'])
1877
+ || exactRecord(input.payload, ['done_mode', 'imported', 'evidence']))
1878
+ && input.payload.done_mode === 'evidence_promotion') {
1879
+ // Same binding as `reopen`: a completion carried across a revision is bound
1880
+ // to the carrying `plan_genesis`, while an authored/re-promoted completion is
1881
+ // bound to its latest done event. imported/authored is canonical state, not a
1882
+ // caller assertion; resolving both under the store lock avoids a stale read.
1838
1883
  const targetDigest = resolveDoneBindingDigest(storeMember, input.task_id);
1839
- if (targetDigest === null) fail('STORE_INCONSISTENT', 'invalid_evidence_promotion');
1884
+ const state = storeMember.tasks.find(({ task_id: taskId }) => taskId === input.task_id);
1885
+ if (targetDigest === null || state?.status !== 'done') {
1886
+ fail('STORE_INCONSISTENT', 'invalid_evidence_promotion', {
1887
+ task_id: input.task_id,
1888
+ status: state?.status ?? null,
1889
+ expected_target_done_digest: targetDigest,
1890
+ eligible_status: 'done',
1891
+ next_action: 'complete_the_task_before_promoting_evidence',
1892
+ });
1893
+ }
1840
1894
  return {
1841
1895
  ...input,
1842
- payload: { ...input.payload, target_done_digest: targetDigest },
1896
+ payload: {
1897
+ done_mode: 'evidence_promotion',
1898
+ imported: state.imported,
1899
+ target_done_digest: targetDigest,
1900
+ evidence: input.payload.evidence,
1901
+ },
1843
1902
  };
1844
1903
  }
1845
1904
  if (input.kind === 'phase_reopen' && exactRecord(input.payload, ['reason', 'override_reason'])) {
@@ -1940,7 +1999,14 @@ export async function appendTodoEvent(options = {}) {
1940
1999
  requireWriter(options.writer, 'g5-authoring');
1941
2000
  const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
1942
2001
  return withLock(repoRoot, async () => {
1943
- const store = await readTodoStore({ repoRoot, forWrite: true, now: options.now });
2002
+ const evidenceRepair = options.event.kind === 'done'
2003
+ && options.event.payload?.done_mode === 'evidence_promotion'
2004
+ ? { planKey: options.planKey, taskId: options.event.task_id, eventDigest: null }
2005
+ : null;
2006
+ const store = await readTodoStore({
2007
+ repoRoot, forWrite: true, now: options.now, evidenceRepair,
2008
+ evidenceRepairCapability: EVIDENCE_REPAIR_CAPABILITY,
2009
+ });
1944
2010
  const member = store.members.find(({ descriptor }) => descriptor.plan_key === options.planKey);
1945
2011
  if (!member) fail('STORE_INCONSISTENT', 'plan_not_active');
1946
2012
  // planへ帰属するeventは別chainへ積む。task状態もPhase状態も直接は動かさないので、
@@ -1973,8 +2039,16 @@ export async function appendTodoEvent(options = {}) {
1973
2039
  [...member.journal.events, event], appendSourceCache);
1974
2040
  replay(member.plan, [...member.journal.events, event], {
1975
2041
  now: options.now ? new Date(options.now) : new Date(),
1976
- verifyEvidence: evidenceVerifier(store.manifest, repoRoot, true),
1977
- verifyImportSource: importSourceVerifier(repoRoot, true, appendSourceCache),
2042
+ verifyEvidence: evidenceVerifier(store.manifest, repoRoot, true, {
2043
+ planKey: member.plan.plan_key,
2044
+ taskId: event.task_id,
2045
+ eventDigest: event.event_digest,
2046
+ }),
2047
+ verifyImportSource: importSourceVerifier(repoRoot, true, appendSourceCache, {
2048
+ planKey: member.plan.plan_key,
2049
+ taskId: event.task_id,
2050
+ eventDigest: event.event_digest,
2051
+ }),
1978
2052
  });
1979
2053
  const eventBytes = canonicalLine(event);
1980
2054
  const activeRef = member.descriptor.journal_ref;