@quolu/lattice 0.46.1 → 0.47.0
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/README.ja.md +3 -1
- package/README.md +3 -1
- package/bin/lattice.mjs +9 -1
- package/package.json +1 -1
- package/src/artifact-contracts.mjs +0 -3
- package/src/hooks-cli.mjs +31 -8
- package/src/project-cli.mjs +12 -2
- package/src/rc3-scripted-campaign.mjs +15 -16
- package/src/runtime-cli.mjs +230 -146
- package/src/runtime-contracts.mjs +9 -5
- package/src/runtime-decision-verifier.mjs +26 -5
- package/src/runtime-engine.mjs +48 -5
- package/src/runtime-front-end.mjs +34 -15
- package/src/runtime-hold-recompile.mjs +11 -5
- package/src/runtime-managed-supervisor.mjs +54 -15
- package/src/runtime-scripted-adapter-controller.mjs +43 -19
- package/src/runtime-seam-resolve.mjs +4 -2
- package/src/seam-apply.mjs +3 -3
- package/src/seam-verification.mjs +38 -2
- package/src/sensor-node-runtime.mjs +53 -0
- package/src/sensor-runtime.mjs +5 -1
- package/src/todo-audit-pending.mjs +91 -0
- package/src/todo-cli.mjs +2 -0
- package/src/todo-dashboard-registry.mjs +2 -2
- package/src/todo-gantt-html-style.mjs +6 -2
- package/src/todo-gantt-html.mjs +30 -2
- package/src/todo-gantt-scope.mjs +9 -5
- package/src/todo-independence-contracts.mjs +10 -6
- package/src/todo-independence-guidance.mjs +2 -28
- package/src/todo-status.mjs +85 -9
- package/src/todo-store.mjs +9 -0
- package/src/witness-scaffold.mjs +13 -32
package/README.ja.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src=".github/og.png" alt="Lattice —
|
|
2
|
+
<img src=".github/og.png" alt="Lattice — 閉ざされて見えた山岳地形から複数の進行経路が現れる" width="100%">
|
|
3
|
+
<br>
|
|
4
|
+
<sub><em>この画像は、閉ざされているように見えた場所から複数の進行経路が現れ、自律した実行者たちが協調して動き出す瞬間を表しています。</em></sub>
|
|
3
5
|
</p>
|
|
4
6
|
|
|
5
7
|
# Lattice
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src=".github/og.png" alt="Lattice —
|
|
2
|
+
<img src=".github/og.png" alt="Lattice — several viable routes emerging through an apparently blocked mountain valley" width="100%">
|
|
3
|
+
<br>
|
|
4
|
+
<sub><em>This image represents several viable paths emerging from terrain that first appeared blocked, as autonomous executors begin moving in coordination.</em></sub>
|
|
3
5
|
</p>
|
|
4
6
|
|
|
5
7
|
# Lattice
|
package/bin/lattice.mjs
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
3
4
|
import { installPipeCloseGuard } from '../src/cli-stdio.mjs';
|
|
4
5
|
import { runRuntimeCli } from '../src/runtime-cli.mjs';
|
|
5
6
|
import { renderCliHelp } from '../src/cli-help.mjs';
|
|
7
|
+
import { relaunchSensorForNode22IfNeeded } from '../src/sensor-node-runtime.mjs';
|
|
6
8
|
import packageJson from '../package.json' with { type: 'json' };
|
|
7
9
|
|
|
8
10
|
installPipeCloseGuard();
|
|
9
11
|
|
|
10
12
|
const args = process.argv.slice(2);
|
|
13
|
+
const sensorRelaunchStatus = relaunchSensorForNode22IfNeeded({
|
|
14
|
+
args,
|
|
15
|
+
scriptPath: fileURLToPath(import.meta.url),
|
|
16
|
+
});
|
|
11
17
|
const help = renderCliHelp(args);
|
|
12
18
|
|
|
13
|
-
if (
|
|
19
|
+
if (sensorRelaunchStatus !== null) {
|
|
20
|
+
process.exitCode = sensorRelaunchStatus;
|
|
21
|
+
} else if (help !== null) {
|
|
14
22
|
process.stdout.write(help);
|
|
15
23
|
} else if (args.length === 1 && args[0] === '--version') {
|
|
16
24
|
process.stdout.write(`${packageJson.version}\n`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quolu/lattice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
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",
|
|
@@ -111,9 +111,6 @@ function inspectArray(value, state, depth) {
|
|
|
111
111
|
if (Object.getPrototypeOf(value) !== Array.prototype) {
|
|
112
112
|
invalidArtifact('array prototypeが不正');
|
|
113
113
|
}
|
|
114
|
-
if (value.length > MAX_COLLECTION_ITEMS) {
|
|
115
|
-
invalidArtifact('array item数が上限を超えている');
|
|
116
|
-
}
|
|
117
114
|
|
|
118
115
|
const ownKeys = Reflect.ownKeys(value);
|
|
119
116
|
if (ownKeys.some((key) => typeof key !== 'string')) {
|
package/src/hooks-cli.mjs
CHANGED
|
@@ -427,18 +427,41 @@ function hostHandler(host, command) {
|
|
|
427
427
|
: { type: 'command', command, timeout: 5, async: false, statusMessage: null };
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
-
async function
|
|
430
|
+
export async function resolveStableNodePath(execPath, {
|
|
431
|
+
platform = process.platform,
|
|
432
|
+
accessImpl = access,
|
|
433
|
+
realpathImpl = realpath,
|
|
434
|
+
} = {}) {
|
|
435
|
+
await accessImpl(execPath, fsConstants.X_OK);
|
|
436
|
+
if (platform !== 'darwin') return execPath;
|
|
437
|
+
const match = execPath.match(/^\/(opt\/homebrew|usr\/local)\/Cellar\/([^/]+)\/[^/]+\/bin\/node$/u);
|
|
438
|
+
if (match === null) return execPath;
|
|
439
|
+
const prefix = `/${match[1]}`;
|
|
440
|
+
const candidates = [path.join(prefix, 'bin/node'), path.join(prefix, 'opt', match[2], 'bin/node')];
|
|
441
|
+
const resolvedExec = await realpathImpl(execPath);
|
|
442
|
+
for (const candidate of candidates) {
|
|
443
|
+
try {
|
|
444
|
+
await accessImpl(candidate, fsConstants.X_OK);
|
|
445
|
+
if (await realpathImpl(candidate) === resolvedExec) return candidate;
|
|
446
|
+
} catch {
|
|
447
|
+
// A candidate is usable only when it exists and resolves to this running Node binary.
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return execPath;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function resolveCanonical(host, source, platform) {
|
|
431
454
|
const sourcePaths = [source.execPath, source.binPath];
|
|
432
455
|
if (sourcePaths.some((entry) => typeof entry !== 'string' || !path.isAbsolute(entry)
|
|
433
456
|
|| /[\0\r\n]/u.test(entry))) {
|
|
434
457
|
throw Object.assign(new Error('install source is not absolute'), { code: 'INSTALL_SOURCE_UNRESOLVED' });
|
|
435
458
|
}
|
|
436
459
|
try {
|
|
437
|
-
await
|
|
460
|
+
const executable = await resolveStableNodePath(source.execPath, { platform });
|
|
438
461
|
const script = await realpath(source.binPath);
|
|
439
462
|
if (/[\0\r\n]/u.test(script)) throw new Error('resolved install source has unsafe characters');
|
|
440
463
|
await access(script, fsConstants.R_OK | fsConstants.X_OK);
|
|
441
|
-
return [
|
|
464
|
+
return [executable, script, 'hooks', 'emit', '--host', host];
|
|
442
465
|
} catch (error) {
|
|
443
466
|
throw Object.assign(error, { code: 'INSTALL_SOURCE_UNRESOLVED' });
|
|
444
467
|
}
|
|
@@ -613,13 +636,13 @@ async function hostDirectory(home, host) {
|
|
|
613
636
|
}
|
|
614
637
|
}
|
|
615
638
|
|
|
616
|
-
async function mutate(host, env, stdout, uninstall, source, testHooks) {
|
|
639
|
+
async function mutate(host, env, stdout, uninstall, source, platform, testHooks) {
|
|
617
640
|
const home = env.HOME ?? os.homedir();
|
|
618
641
|
if (await hostDirectory(home, host) === null) {
|
|
619
642
|
return failure(stdout, 'HOST_NOT_PRESENT', 'host home directory is not present');
|
|
620
643
|
}
|
|
621
644
|
let current;
|
|
622
|
-
try { current = await resolveCanonical(host, source); } catch {
|
|
645
|
+
try { current = await resolveCanonical(host, source, platform); } catch {
|
|
623
646
|
return failure(stdout, 'INSTALL_SOURCE_UNRESOLVED', 'install source cannot be resolved');
|
|
624
647
|
}
|
|
625
648
|
const target = configPath(home, host);
|
|
@@ -708,7 +731,7 @@ async function status(host, env, stdout, source, platform, testHooks) {
|
|
|
708
731
|
const home = env.HOME ?? os.homedir();
|
|
709
732
|
const target = configPath(home, host);
|
|
710
733
|
let argv;
|
|
711
|
-
try { argv = await resolveCanonical(host, source); } catch {
|
|
734
|
+
try { argv = await resolveCanonical(host, source, platform); } catch {
|
|
712
735
|
writeJson(stdout, statusResult(host, target, null, 'unreadable', 0, false, 0));
|
|
713
736
|
return 1;
|
|
714
737
|
}
|
|
@@ -1023,8 +1046,8 @@ export async function runHooksCli({
|
|
|
1023
1046
|
if (command === 'status') return status(host, env, stdout, source, platform, testHooks);
|
|
1024
1047
|
return failure(stdout, 'HOST_PLATFORM_UNSUPPORTED', 'native Windows hooks are unsupported');
|
|
1025
1048
|
}
|
|
1026
|
-
if (command === 'install') return mutate(host, env, stdout, false, source, testHooks);
|
|
1027
|
-
if (command === 'uninstall') return mutate(host, env, stdout, true, source, testHooks);
|
|
1049
|
+
if (command === 'install') return mutate(host, env, stdout, false, source, platform, testHooks);
|
|
1050
|
+
if (command === 'uninstall') return mutate(host, env, stdout, true, source, platform, testHooks);
|
|
1028
1051
|
if (command === 'status') return status(host, env, stdout, source, platform, testHooks);
|
|
1029
1052
|
return emit(host, env, stdin, stdout, spawnImpl, gitTimeoutMs, testHooks);
|
|
1030
1053
|
}
|
package/src/project-cli.mjs
CHANGED
|
@@ -275,7 +275,17 @@ async function resolveProjectState({ cwd, cliVersion }) {
|
|
|
275
275
|
: todo.next_ready.length > 0
|
|
276
276
|
? { command: `lattice todo start --plan ${todo.next_ready[0].plan_key} --task ${todo.next_ready[0].task_id}${todo.next_ready.length > 1 ? ' --parallel-frontier' : ''}`,
|
|
277
277
|
reason: todo.next_ready.length > 1 ? 'parallel_frontier_present' : 'next_ready_present' }
|
|
278
|
-
|
|
278
|
+
// 監査待ちが在る限り「残作業なし」と答えない。優先順位はactive_run > next_ready >
|
|
279
|
+
// audit_pending > なしで、ready frontierが在る間はADR 0063の並列開始コマンドが勝つ
|
|
280
|
+
// ——ここを監査で上書きするとdispatchが再直列化する。監査待ちはtodo statusの
|
|
281
|
+
// audit_pending欄に常在するので、順位を下げても消えない。
|
|
282
|
+
// commandはverbatim実行可能で読み取り専用のものにする。`phase review`は
|
|
283
|
+
// `--reason <text>`のplaceholderを含みjournalを書き換えるので置かない
|
|
284
|
+
// (`todo phase status`の結果には既に両分岐のguidanceが載っている)。
|
|
285
|
+
: todo.audit_pending.length > 0
|
|
286
|
+
? { command: `lattice todo phase status --plan ${todo.audit_pending[0].plan_key}`,
|
|
287
|
+
reason: 'audit_pending' }
|
|
288
|
+
: { command: 'lattice todo status', reason: 'no_ready_task' };
|
|
279
289
|
const result = statusResult({
|
|
280
290
|
cli: { available: true, version: cliVersion },
|
|
281
291
|
project: { root: repoRoot, git_head: gitHead(repoRoot), project_id: store.project_id },
|
|
@@ -334,7 +344,7 @@ export async function runSessionContext({ cwd, stdout, cliVersion }) {
|
|
|
334
344
|
schema: SESSION_CONTEXT_SCHEMA,
|
|
335
345
|
// project discoveryの答えをそのまま埋める。hostは既存の検証器を再利用できる。
|
|
336
346
|
status: state.result,
|
|
337
|
-
// todoは`
|
|
347
|
+
// todoは`lattice todo status`のresultそのもの。新しい意味論を発明しない。
|
|
338
348
|
todo: state.todo,
|
|
339
349
|
independence,
|
|
340
350
|
result_digest: '',
|
|
@@ -467,31 +467,30 @@ async function runScopeViolation({ scaffold }) {
|
|
|
467
467
|
runId, plan, events, packets, todoId: 'TA', detect: detectCheckpointFindings, recordedAt: RUN_TIMESTAMP,
|
|
468
468
|
});
|
|
469
469
|
events = classified.events;
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
});
|
|
473
|
-
events = held.events;
|
|
470
|
+
events = await driveToClose({ runId, plan, events, packets, manifests, adapter });
|
|
471
|
+
const state = projectRuntimeState({ events });
|
|
474
472
|
return {
|
|
475
473
|
request,
|
|
476
474
|
plan,
|
|
477
475
|
manifests,
|
|
478
|
-
holdDecision: held.holdDecision,
|
|
479
476
|
events,
|
|
480
477
|
record: conditionRecord({
|
|
481
478
|
condition: 'scope_violation',
|
|
482
|
-
//
|
|
483
|
-
//
|
|
479
|
+
// directory名はRC3 artifact互換のため維持する。現契約では単独の予測超過は
|
|
480
|
+
// conflictではなく、観測を残したまま有効なreceiptを受理する。
|
|
484
481
|
expected: {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
482
|
+
observation_kinds: ['prediction_excess'],
|
|
483
|
+
conflict_finding_kinds: [],
|
|
484
|
+
frozen: false,
|
|
485
|
+
accepted: ['TA', 'TB'],
|
|
486
|
+
closed: true,
|
|
489
487
|
},
|
|
490
488
|
actual: {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
489
|
+
observation_kinds: [...new Set(classified.observations.map(({ kind }) => kind))],
|
|
490
|
+
conflict_finding_kinds: [...new Set(classified.findings.map(({ kind }) => kind))],
|
|
491
|
+
frozen: state.freeze !== null,
|
|
492
|
+
accepted: state.accepted,
|
|
493
|
+
closed: state.closed,
|
|
495
494
|
},
|
|
496
495
|
events,
|
|
497
496
|
plan,
|
|
@@ -1334,7 +1333,7 @@ export async function runRc3ScriptedCampaign(options = {}) {
|
|
|
1334
1333
|
const clean = await timed('clean_parallel', () => runCleanParallel({ scaffold }));
|
|
1335
1334
|
const late = await timed('late_path_conflict', () => runLateConflict({ scaffold }));
|
|
1336
1335
|
// 条件名`scope_violation`はRC3 manifestのdirectory名として凍結されているので動かさない。
|
|
1337
|
-
//
|
|
1336
|
+
// 中では単独の予測超過を観測し、conflictへ昇格しない現契約を検証する。
|
|
1338
1337
|
const scope = await timed('scope_violation', () => runScopeViolation({ scaffold }));
|
|
1339
1338
|
const semantic = await timed('semantic_unknown', () => runSemanticUnknown({ scaffold }));
|
|
1340
1339
|
const stale = await timed('stale_receipt', () => runStaleReceipt({ scaffold }));
|