@quolu/lattice 0.63.6 → 0.63.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/LICENSE +147 -147
- package/README.ja.md +378 -378
- package/README.md +303 -303
- package/bin/lattice-mcp.mjs +0 -0
- package/bin/lattice-scripted-adapter.mjs +0 -0
- package/bin/lattice-scripted-worker.mjs +0 -0
- package/bin/lattice-work-order-adapter.mjs +0 -0
- package/bin/lattice.mjs +0 -0
- package/docs/bridge-setup.md +248 -248
- package/docs/schemas/lattice.executor_packet.v1.schema.json +57 -57
- package/docs/schemas/lattice.executor_receipt.v1.schema.json +66 -66
- package/docs/schemas/lattice.phase_todo_revision.v3.schema.json +360 -360
- package/docs/schemas/lattice.plan_create_input.v1.schema.json +56 -56
- package/docs/schemas/lattice.plan_create_input.v2.schema.json +72 -72
- package/docs/schemas/lattice.plan_create_input.v3.schema.json +81 -81
- package/docs/schemas/lattice.plan_create_input.v4.schema.json +357 -357
- package/docs/schemas/lattice.plan_scope_review.v1.schema.json +55 -55
- package/docs/schemas/lattice.run_request.v1.schema.json +238 -238
- package/docs/schemas/lattice.runtime_adapter_capabilities.v2.schema.json +55 -55
- package/docs/schemas/lattice.runtime_adapter_registration_input.v1.schema.json +78 -78
- package/docs/schemas/lattice.runtime_adapter_registration_input.v2.schema.json +86 -86
- package/docs/schemas/lattice.todo_extraction.v2.schema.json +298 -298
- package/docs/schemas/lattice.todo_extraction.v3.schema.json +150 -150
- package/docs/schemas/lattice.todo_extraction.v4.schema.json +161 -161
- package/docs/schemas/lattice.todo_revision.v2.schema.json +260 -260
- package/docs/schemas/lattice.todo_revision_set.v3.schema.json +363 -363
- package/docs/schemas/lattice.todo_structure_binding.v1.schema.json +47 -47
- package/docs/schemas/lattice.todo_structure_realization.v1.schema.json +55 -55
- package/docs/schemas/lattice.todo_structure_set.v1.schema.json +264 -264
- package/package.json +109 -109
- package/sensor/LICENSE +21 -21
- package/sensor/NOTICE +19 -19
- package/sensor/dist/bin/lattice-sensor.js +9 -9
- package/sensor/dist/db/index.js +24 -24
- package/sensor/dist/db/migrations.js +41 -41
- package/sensor/dist/db/queries.js +164 -164
- package/sensor/dist/db/schema.sql +205 -205
- package/sensor/dist/directory.js +5 -5
- package/sensor/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/sensor/dist/mcp/liveness-watchdog.js +53 -53
- package/sensor/dist/mcp/server-instructions.js +95 -95
- package/sensor/package.json +56 -56
- package/src/cli-help.mjs +1 -0
- package/src/todo-cli.mjs +27 -7
- package/src/todo-contracts.mjs +7 -2
- package/src/todo-store.mjs +113 -26
package/src/todo-store.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
734
|
-
|
|
735
|
-
||
|
|
736
|
-
|
|
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
|
|
1510
|
-
|
|
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'
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
//
|
|
1837
|
-
//
|
|
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
|
-
|
|
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: {
|
|
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
|
|
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,9 +2039,30 @@ 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
|
-
|
|
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
|
});
|
|
2053
|
+
if (options.materializedEvidence !== null && options.materializedEvidence !== undefined) {
|
|
2054
|
+
const asset = options.materializedEvidence;
|
|
2055
|
+
const descriptor = event.kind === 'done' ? event.payload.evidence : null;
|
|
2056
|
+
if (!exactRecord(asset, ['ref', 'bytes']) || !Buffer.isBuffer(asset.bytes)
|
|
2057
|
+
|| descriptor === null || asset.ref !== descriptor.path
|
|
2058
|
+
|| !asset.ref.startsWith(`${STORE_ROOT_REF}/evidence/`)
|
|
2059
|
+
|| sha256Bytes(asset.bytes) !== descriptor.content_digest) {
|
|
2060
|
+
throw new TypeError('materialized evidence input invalid');
|
|
2061
|
+
}
|
|
2062
|
+
// prospective replayが成立した後、journalより先に実体を同じstore lock内へ置く。
|
|
2063
|
+
// content-addressed pathなので再試行と同文面の並行要求も同じbytesへ収束する。
|
|
2064
|
+
await atomicWrite(path.resolve(repoRoot, asset.ref), asset.bytes);
|
|
2065
|
+
}
|
|
1979
2066
|
const eventBytes = canonicalLine(event);
|
|
1980
2067
|
const activeRef = member.descriptor.journal_ref;
|
|
1981
2068
|
const activeAbsolute = path.resolve(repoRoot, activeRef);
|