@quolu/lattice 0.57.1 → 0.57.3
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/bin/lattice-bridge.mjs +5 -2
- package/docs/bridge-setup.md +5 -0
- package/package.json +1 -1
- package/src/bridge-cli.mjs +4 -0
- package/src/bridge-launch-agent.mjs +6 -4
- package/src/bridge-startup-folder.mjs +3 -4
- package/src/runtime-pull-intake.mjs +8 -4
- package/src/todo-cli.mjs +3 -0
- package/src/todo-store.mjs +37 -13
package/bin/lattice-bridge.mjs
CHANGED
|
@@ -140,12 +140,15 @@ timer = setInterval(async () => {
|
|
|
140
140
|
// the terminal identity file) reaches this catch.
|
|
141
141
|
try {
|
|
142
142
|
const heartbeat = await hubHeartbeat.tick({ config });
|
|
143
|
-
|
|
143
|
+
// 直前に書いた指紋は、健全な状態へ戻った時にだけ捨てる。判定より前に捨てると
|
|
144
|
+
// 比較相手が毎周期nullになり、同じ結果をpoll周期ごとに書き続ける。stderrの
|
|
145
|
+
// 消費者が居ない常駐(WindowsのStartup launcher、LaunchAgent)では、この
|
|
146
|
+
// 書込みが積み上がること自体が常駐を不安定にする。
|
|
144
147
|
if (['unreachable', 'rejected', 'partial'].includes(heartbeat?.state)) {
|
|
145
148
|
const fingerprint = JSON.stringify(heartbeat);
|
|
146
149
|
if (fingerprint !== hubHeartbeatError) process.stderr.write(`${fingerprint}\n`);
|
|
147
150
|
hubHeartbeatError = fingerprint;
|
|
148
|
-
}
|
|
151
|
+
} else hubHeartbeatError = null;
|
|
149
152
|
} catch (error) {
|
|
150
153
|
const failure = { schema: 'lattice.bridge_daemon_error.v1',
|
|
151
154
|
code: error?.code ?? 'BRIDGE_HUB_HEARTBEAT_FAILED',
|
package/docs/bridge-setup.md
CHANGED
|
@@ -54,6 +54,11 @@ bridgeが無効な間は以下すべてnullで、bridgeを有効にしている
|
|
|
54
54
|
| `node_path`・`node_exists` | 起動するNode実行体と、それが今も存在するか |
|
|
55
55
|
| `bridge_path`・`bridge_exists` | 起動するbridge scriptと、それが今も存在するか |
|
|
56
56
|
| `error` | `unreadable`のときだけtyped code(例`BRIDGE_LAUNCH_AGENT_PLIST_UNSAFE`) |
|
|
57
|
+
| `error=BRIDGE_PERSISTENCE_STATE_SPLIT` | launcherまたはplistの片割れだけが残った状態。手でfileを消さず、`lattice bridge reconfigure --json`で常駐設定を揃える(0.57.2以降) |
|
|
58
|
+
|
|
59
|
+
`BRIDGE_PERSISTENCE_STATE_SPLIT`は、常駐設定の一方(macOSのplist、またはWindowsのlauncher/descriptor)だけが
|
|
60
|
+
存在することを示す。これは復旧対象そのものが壊れている状態なので、手作業でfileを削除せず、
|
|
61
|
+
`lattice bridge reconfigure --json`を実行して正規のinstall経路で両方を再生成する。
|
|
57
62
|
|
|
58
63
|
**`runtime`** — いま応答しているprocess自身の申告。`state`は`running`/`not_running`/`unattested`/
|
|
59
64
|
`descriptor_invalid`で、`running`以外では各値がnullになる。`running`でも、identityを返さない
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quolu/lattice",
|
|
3
|
-
"version": "0.57.
|
|
3
|
+
"version": "0.57.3",
|
|
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/bridge-cli.mjs
CHANGED
|
@@ -112,6 +112,10 @@ async function bridgePersistence({ launchAgent, env }) {
|
|
|
112
112
|
// `loaded` is launchd-specific; the Windows Startup folder has no such
|
|
113
113
|
// concept and reports null rather than pretending to know.
|
|
114
114
|
const loaded = typeof snapshot?.loaded === 'boolean' ? snapshot.loaded : null;
|
|
115
|
+
if (snapshot?.split === true) {
|
|
116
|
+
return { state: 'unreadable', ...UNREADABLE_PERSISTENCE, loaded,
|
|
117
|
+
error: 'BRIDGE_PERSISTENCE_STATE_SPLIT' };
|
|
118
|
+
}
|
|
115
119
|
if (described === null) return { state: 'not_installed', ...UNREADABLE_PERSISTENCE, loaded, error: null };
|
|
116
120
|
return { state: 'installed', loaded, ...described, error: null };
|
|
117
121
|
} catch (error) {
|
|
@@ -247,10 +247,8 @@ export async function snapshotBridgeLaunchAgent({ env = process.env,
|
|
|
247
247
|
await prepareDirectory(refs.directory, uid);
|
|
248
248
|
const content = await strictPlist(refs.plist, uid);
|
|
249
249
|
const loaded = await loadedState(runner, uid);
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
return Object.freeze({ installed: content !== null, loaded, content });
|
|
250
|
+
const split = loaded && content === null;
|
|
251
|
+
return Object.freeze({ installed: content !== null, loaded, split, content });
|
|
254
252
|
}
|
|
255
253
|
|
|
256
254
|
const PROGRAM_ARGUMENTS_PATTERN =
|
|
@@ -346,6 +344,10 @@ export async function restoreBridgeLaunchAgent({ snapshot, listen = null, env =
|
|
|
346
344
|
await prepareDirectory(refs.directory, uid);
|
|
347
345
|
const stopped = await bootoutIfLoaded({ runner, uid });
|
|
348
346
|
if (stopped) await waitStopped({ listen, env });
|
|
347
|
+
if (snapshot.split === true) {
|
|
348
|
+
await rm(refs.plist, { force: true });
|
|
349
|
+
return snapshot;
|
|
350
|
+
}
|
|
349
351
|
if (snapshot.installed) await atomicPlist(refs.plist, snapshot.content);
|
|
350
352
|
else await rm(refs.plist, { force: true });
|
|
351
353
|
if (snapshot.loaded) {
|
|
@@ -239,10 +239,9 @@ export async function snapshotBridgeStartupFolder({ env = process.env } = {}) {
|
|
|
239
239
|
await prepareDirectory(refs.runtimeDirectory);
|
|
240
240
|
const launcherContent = await strictFile(refs.launcher);
|
|
241
241
|
const descriptorContent = await strictFile(refs.descriptor);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
return Object.freeze({ installed: launcherContent !== null, launcherContent, descriptorContent });
|
|
242
|
+
const split = (launcherContent === null) !== (descriptorContent === null);
|
|
243
|
+
return Object.freeze({ installed: launcherContent !== null && descriptorContent !== null,
|
|
244
|
+
split, launcherContent, descriptorContent });
|
|
246
245
|
}
|
|
247
246
|
|
|
248
247
|
async function pathPresent(ref) {
|
|
@@ -681,8 +681,10 @@ export async function intakePullTask({ repoRoot, runDir, taskId, environment = p
|
|
|
681
681
|
affected_tests: [], graph_evidence: [], witness_provenance: {}, manifest_digest: '',
|
|
682
682
|
};
|
|
683
683
|
manifest.manifest_digest = selfDigest(manifest, 'manifest_digest');
|
|
684
|
-
verdict.state
|
|
685
|
-
|
|
684
|
+
if (verdict.state !== 'hold') {
|
|
685
|
+
verdict.state = 'hold'; verdict.reason = 'boundary_unverified';
|
|
686
|
+
verdict.next_action = 'declare_and_recompile'; verdict.detail = { cause: error.code };
|
|
687
|
+
}
|
|
686
688
|
}
|
|
687
689
|
const constraint = verdict.state === 'none'
|
|
688
690
|
? planningConstraint(verdict.artifact, taskId, state, member) : null;
|
|
@@ -760,8 +762,10 @@ export async function intakePullTask({ repoRoot, runDir, taskId, environment = p
|
|
|
760
762
|
affected_tests: [], graph_evidence: [], witness_provenance: {}, manifest_digest: '',
|
|
761
763
|
};
|
|
762
764
|
manifest.manifest_digest = selfDigest(manifest, 'manifest_digest');
|
|
763
|
-
verdict.state
|
|
764
|
-
|
|
765
|
+
if (verdict.state !== 'hold') {
|
|
766
|
+
verdict.state = 'hold'; verdict.reason = 'boundary_unverified';
|
|
767
|
+
verdict.next_action = 'declare_and_recompile'; verdict.detail = { cause: error.code };
|
|
768
|
+
}
|
|
765
769
|
}
|
|
766
770
|
const packet = packetFor({ meta: current.meta, taskId, baseSha, manifest });
|
|
767
771
|
const worktreePath = await ensureScriptedWorktree({ repoRoot, runDir, packet });
|
package/src/todo-cli.mjs
CHANGED
|
@@ -1759,6 +1759,9 @@ async function independenceCompile({ repoRoot, planKey, inputRef }) {
|
|
|
1759
1759
|
previousArtifact,
|
|
1760
1760
|
});
|
|
1761
1761
|
const { ref } = await writeTodoIndependenceArtifact({ repoRoot, artifact });
|
|
1762
|
+
// compile成功後にだけ、受理した宣言をruntime readerが要求するcanonical bytesで固定する。
|
|
1763
|
+
// inputがstore自身でも外部draftでも、唯一の正規writerを通して同じ契約へ収束させる。
|
|
1764
|
+
await writeTodoWitnessSet({ repoRoot, witnessSet });
|
|
1762
1765
|
|
|
1763
1766
|
const result = {
|
|
1764
1767
|
schema: 'lattice.todo_independence_compile_result.v2',
|
package/src/todo-store.mjs
CHANGED
|
@@ -2286,8 +2286,19 @@ function localTaskRef(ref, plan, taskId) {
|
|
|
2286
2286
|
&& ref.task_id === taskId;
|
|
2287
2287
|
}
|
|
2288
2288
|
|
|
2289
|
+
function removedTaskIdsFor(revision) {
|
|
2290
|
+
return new Set(revision.task_migration
|
|
2291
|
+
.filter(({ to_task_id, state_policy }) => to_task_id === 'removed' && state_policy === 'removed')
|
|
2292
|
+
.map(({ from_task_id }) => from_task_id));
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
function removedTaskRef(ref, plan, removedTaskIds) {
|
|
2296
|
+
return ref.project_id === plan.project_id && ref.plan_key === plan.plan_key
|
|
2297
|
+
&& removedTaskIds.has(ref.task_id);
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2289
2300
|
function taskSemantics(plan, taskId, idMap, {
|
|
2290
|
-
reconciliationMetadata = false, includeDesignMemo = false,
|
|
2301
|
+
reconciliationMetadata = false, includeDesignMemo = false, removedTaskIds = new Set(),
|
|
2291
2302
|
} = {}) {
|
|
2292
2303
|
const task = plan.tasks.find(({ task_id }) => task_id === taskId);
|
|
2293
2304
|
if (!task) return null;
|
|
@@ -2302,17 +2313,23 @@ function taskSemantics(plan, taskId, idMap, {
|
|
|
2302
2313
|
compile_binding: task.compile_binding, parent_task_id: mapId(task.parent_task_id ?? null),
|
|
2303
2314
|
};
|
|
2304
2315
|
const edges = plan.hard_dependencies
|
|
2305
|
-
.filter(({ from, to }) =>
|
|
2316
|
+
.filter(({ from, to }) => !removedTaskRef(from, plan, removedTaskIds)
|
|
2317
|
+
&& !removedTaskRef(to, plan, removedTaskIds)
|
|
2318
|
+
&& (localTaskRef(from, plan, taskId) || localTaskRef(to, plan, taskId)))
|
|
2306
2319
|
.map(({ from, to }) => ({ from: mappedNodeRef(from, plan, idMap), to: mappedNodeRef(to, plan, idMap) }))
|
|
2307
2320
|
.sort((left, right) => canonicalizeTodoArtifact(left) < canonicalizeTodoArtifact(right) ? -1 : 1);
|
|
2308
2321
|
const joins = plan.joins
|
|
2309
|
-
.
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2322
|
+
.flatMap((join) => {
|
|
2323
|
+
if (removedTaskRef(join.before, plan, removedTaskIds)) return [];
|
|
2324
|
+
const after = join.after.filter((ref) => !removedTaskRef(ref, plan, removedTaskIds));
|
|
2325
|
+
if (after.length === 0 || (!localTaskRef(join.before, plan, taskId)
|
|
2326
|
+
&& !after.some((ref) => localTaskRef(ref, plan, taskId)))) return [];
|
|
2327
|
+
return [{ ...join,
|
|
2328
|
+
after: after.map((ref) => mappedNodeRef(ref, plan, idMap))
|
|
2313
2329
|
.sort((left, right) => canonicalizeTodoArtifact(left) < canonicalizeTodoArtifact(right) ? -1 : 1),
|
|
2314
|
-
|
|
2315
|
-
|
|
2330
|
+
before: mappedNodeRef(join.before, plan, idMap),
|
|
2331
|
+
}];
|
|
2332
|
+
}).sort((left, right) => left.id < right.id ? -1 : 1);
|
|
2316
2333
|
const phaseAcceptDependencies = isDecoupledPhaseTodoPlanSchema(plan.schema)
|
|
2317
2334
|
? plan.phase_accept_dependencies
|
|
2318
2335
|
.filter(({ to }) => localTaskRef(to, plan, taskId))
|
|
@@ -2323,7 +2340,7 @@ function taskSemantics(plan, taskId, idMap, {
|
|
|
2323
2340
|
}
|
|
2324
2341
|
|
|
2325
2342
|
function phaseV3CarrySemantics(plan, taskId, taskIdMap, phaseIdMap,
|
|
2326
|
-
{ reconciliationMetadata = false, includeDesignMemo = false } = {}) {
|
|
2343
|
+
{ reconciliationMetadata = false, includeDesignMemo = false, removedTaskIds = new Set() } = {}) {
|
|
2327
2344
|
const task = plan.tasks.find(({ task_id: id }) => id === taskId);
|
|
2328
2345
|
if (task === undefined) return null;
|
|
2329
2346
|
const mapTaskRef = (ref) => ref.project_id === plan.project_id && ref.plan_key === plan.plan_key
|
|
@@ -2351,13 +2368,17 @@ function phaseV3CarrySemantics(plan, taskId, taskIdMap, phaseIdMap,
|
|
|
2351
2368
|
const incoming = [];
|
|
2352
2369
|
const outgoing = [];
|
|
2353
2370
|
for (const edge of plan.hard_dependencies) {
|
|
2371
|
+
if (removedTaskRef(edge.from, plan, removedTaskIds)
|
|
2372
|
+
|| removedTaskRef(edge.to, plan, removedTaskIds)) continue;
|
|
2354
2373
|
const mapped = { kind: 'hard', from: mapTaskRef(edge.from), to: mapTaskRef(edge.to) };
|
|
2355
2374
|
if (localTaskRef(edge.to, plan, taskId)) incoming.push(mapped);
|
|
2356
2375
|
if (localTaskRef(edge.from, plan, taskId)) outgoing.push(mapped);
|
|
2357
2376
|
}
|
|
2358
2377
|
for (const join of plan.joins) {
|
|
2378
|
+
if (removedTaskRef(join.before, plan, removedTaskIds)) continue;
|
|
2359
2379
|
const to = mapTaskRef(join.before);
|
|
2360
2380
|
for (const after of join.after) {
|
|
2381
|
+
if (removedTaskRef(after, plan, removedTaskIds)) continue;
|
|
2361
2382
|
const tuple = { kind: 'join', join_id: join.id, from: mapTaskRef(after), to };
|
|
2362
2383
|
if (localTaskRef(join.before, plan, taskId)) incoming.push(tuple);
|
|
2363
2384
|
if (localTaskRef(after, plan, taskId)) outgoing.push(tuple);
|
|
@@ -2375,11 +2396,12 @@ function validatePhaseV3Carry(previous, revision, migration, idMap, state) {
|
|
|
2375
2396
|
const reconciliationMetadata = migration.state_policy === 'carry_reconciled_metadata';
|
|
2376
2397
|
const predecessorTask = previous.plan.tasks.find(({ task_id }) => task_id === migration.from_task_id);
|
|
2377
2398
|
const includeDesignMemo = !reconciliationMetadata && typeof predecessorTask?.design_memo === 'string';
|
|
2399
|
+
const removedTaskIds = removedTaskIdsFor(revision);
|
|
2378
2400
|
const phaseIdMap = new Map(revision.phase_migration
|
|
2379
2401
|
.filter(({ from_phase_id, to_phase_id }) => from_phase_id !== null && to_phase_id !== 'removed')
|
|
2380
2402
|
.map(({ from_phase_id, to_phase_id }) => [from_phase_id, to_phase_id]));
|
|
2381
2403
|
const before = phaseV3CarrySemantics(previous.plan, migration.from_task_id, idMap, phaseIdMap,
|
|
2382
|
-
{ reconciliationMetadata, includeDesignMemo });
|
|
2404
|
+
{ reconciliationMetadata, includeDesignMemo, removedTaskIds });
|
|
2383
2405
|
const after = phaseV3CarrySemantics(revision.desired_plan, migration.to_task_id,
|
|
2384
2406
|
new Map(), new Map(),
|
|
2385
2407
|
{ reconciliationMetadata, includeDesignMemo });
|
|
@@ -2412,8 +2434,9 @@ function validateAcquirePhaseCarry(previous, revision, migration, idMap) {
|
|
|
2412
2434
|
.map(({ from_phase_id, to_phase_id }) => [from_phase_id, to_phase_id]));
|
|
2413
2435
|
const predecessorTask = previous.plan.tasks.find(({ task_id }) => task_id === migration.from_task_id);
|
|
2414
2436
|
const includeDesignMemo = typeof predecessorTask?.design_memo === 'string';
|
|
2437
|
+
const removedTaskIds = removedTaskIdsFor(revision);
|
|
2415
2438
|
const before = phaseV3CarrySemantics(previous.plan, migration.from_task_id, idMap, phaseIdMap,
|
|
2416
|
-
{ includeDesignMemo });
|
|
2439
|
+
{ includeDesignMemo, removedTaskIds });
|
|
2417
2440
|
const after = phaseV3CarrySemantics(revision.desired_plan, migration.to_task_id, new Map(), new Map(),
|
|
2418
2441
|
{ includeDesignMemo });
|
|
2419
2442
|
if (before.task.phase_id !== null) {
|
|
@@ -2448,6 +2471,7 @@ function stateMigrationFor(previous, revision) {
|
|
|
2448
2471
|
const idMap = new Map(revision.task_migration
|
|
2449
2472
|
.filter(({ to_task_id }) => to_task_id !== 'removed')
|
|
2450
2473
|
.map(({ from_task_id, to_task_id }) => [from_task_id, to_task_id]));
|
|
2474
|
+
const removedTaskIds = removedTaskIdsFor(revision);
|
|
2451
2475
|
const states = new Map(previous.tasks.map((state) => [state.task_id, state]));
|
|
2452
2476
|
return revision.task_migration.map((migration) => {
|
|
2453
2477
|
const carriesState = ['carry', 'carry_reconciled_metadata', 'acquire_phase'].includes(migration.state_policy);
|
|
@@ -2467,7 +2491,7 @@ function stateMigrationFor(previous, revision) {
|
|
|
2467
2491
|
.find(({ task_id }) => task_id === migration.from_task_id);
|
|
2468
2492
|
const includeDesignMemo = typeof predecessorTask?.design_memo === 'string';
|
|
2469
2493
|
const before = taskSemantics(previous.plan, migration.from_task_id, idMap,
|
|
2470
|
-
{ includeDesignMemo });
|
|
2494
|
+
{ includeDesignMemo, removedTaskIds });
|
|
2471
2495
|
const after = taskSemantics(revision.desired_plan, migration.to_task_id, new Map(),
|
|
2472
2496
|
{ includeDesignMemo });
|
|
2473
2497
|
if (canonicalizeTodoArtifact(before) !== canonicalizeTodoArtifact(after)) {
|
|
@@ -2482,7 +2506,7 @@ function stateMigrationFor(previous, revision) {
|
|
|
2482
2506
|
const includeDesignMemo = !reconciliationMetadata
|
|
2483
2507
|
&& typeof predecessorTask?.design_memo === 'string';
|
|
2484
2508
|
const before = taskSemantics(previous.plan, migration.from_task_id, idMap,
|
|
2485
|
-
{ reconciliationMetadata, includeDesignMemo });
|
|
2509
|
+
{ reconciliationMetadata, includeDesignMemo, removedTaskIds });
|
|
2486
2510
|
const after = taskSemantics(revision.desired_plan, migration.to_task_id, new Map(),
|
|
2487
2511
|
{ reconciliationMetadata, includeDesignMemo });
|
|
2488
2512
|
if (canonicalizeTodoArtifact(before) !== canonicalizeTodoArtifact(after)) {
|