@quolu/lattice 0.60.4 → 0.60.6
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 +1 -1
- package/README.ja.md +1 -1
- package/README.md +1 -1
- package/bin/lattice-dashboard.mjs +7 -3
- package/bin/lattice.mjs +2 -4
- package/docs/schemas/lattice.executor_packet.v1.schema.json +1 -1
- package/docs/schemas/lattice.executor_receipt.v1.schema.json +1 -1
- package/docs/schemas/lattice.phase_todo_revision.v3.schema.json +1 -1
- package/docs/schemas/lattice.plan_create_input.v1.schema.json +1 -1
- package/docs/schemas/lattice.plan_create_input.v2.schema.json +1 -1
- package/docs/schemas/lattice.plan_create_input.v3.schema.json +1 -1
- package/docs/schemas/lattice.plan_create_input.v4.schema.json +1 -1
- package/docs/schemas/lattice.plan_scope_review.v1.schema.json +1 -1
- package/docs/schemas/lattice.run_request.v1.schema.json +1 -1
- package/docs/schemas/lattice.runtime_adapter_capabilities.v2.schema.json +1 -1
- package/docs/schemas/lattice.runtime_adapter_registration_input.v1.schema.json +1 -1
- package/docs/schemas/lattice.runtime_adapter_registration_input.v2.schema.json +1 -1
- package/docs/schemas/lattice.todo_extraction.v2.schema.json +1 -1
- package/docs/schemas/lattice.todo_extraction.v3.schema.json +1 -1
- package/docs/schemas/lattice.todo_extraction.v4.schema.json +1 -1
- package/docs/schemas/lattice.todo_revision.v2.schema.json +1 -1
- package/docs/schemas/lattice.todo_revision_set.v3.schema.json +1 -1
- package/docs/schemas/lattice.todo_structure_binding.v1.schema.json +1 -1
- package/docs/schemas/lattice.todo_structure_realization.v1.schema.json +1 -1
- package/docs/schemas/lattice.todo_structure_set.v1.schema.json +1 -1
- package/package.json +5 -4
- package/sensor/dist/bin/node-version-check.js +1 -1
- package/sensor/dist/bin/node-version-check.js.map +1 -1
- package/sensor/dist/sync/worktree.js +1 -1
- package/sensor/dist/sync/worktree.js.map +1 -1
- package/sensor/package.json +1 -1
- package/src/bridge-daemon.mjs +11 -3
- package/src/bridge-hub-landing.mjs +1 -1
- package/src/runtime-cli.mjs +7 -0
- package/src/runtime-managed-supervisor.mjs +9 -1
- package/src/sensor-node-runtime.mjs +3 -3
- package/src/todo-cli.mjs +5 -4
- package/src/todo-dashboard-registry.mjs +34 -13
- package/src/todo-gantt-live.mjs +18 -4
- package/src/todo-store-git-transaction.mjs +1 -1
- package/src/todo-store.mjs +128 -20
package/LICENSE
CHANGED
|
@@ -132,7 +132,7 @@ of your licenses.
|
|
|
132
132
|
|
|
133
133
|
---
|
|
134
134
|
|
|
135
|
-
Required Notice: Copyright 2026 quolu (kitepon-rgb) — https://github.com/kitepon
|
|
135
|
+
Required Notice: Copyright 2026 quolu (kitepon-rgb) — https://github.com/kitepon/Lattice
|
|
136
136
|
|
|
137
137
|
Patent notice: the design implemented by this software is the subject of Japanese
|
|
138
138
|
patent application 特願2026-178950 (filed 2026-07-27).
|
package/README.ja.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[kitepon.dev](https://kitepon.dev/)を運営する[クオ(@QLyun35332)](https://x.com/QLyun35332)が開発・メンテナンスしています。
|
|
10
10
|
|
|
11
11
|
[](https://www.npmjs.com/package/@quolu/lattice)
|
|
12
|
-
[](https://github.com/kitepon/Lattice/actions/workflows/ci.yml)
|
|
13
13
|
[](LICENSE)
|
|
14
14
|
[](https://nodejs.org/)
|
|
15
15
|
[](#特許)
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# Lattice
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/@quolu/lattice)
|
|
10
|
-
[](https://github.com/kitepon/Lattice/actions/workflows/ci.yml)
|
|
11
11
|
[](LICENSE)
|
|
12
12
|
[](https://nodejs.org/)
|
|
13
13
|
[](#patent)
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
forgetTodoDashboardDaemonRecord,
|
|
9
9
|
readVisibleTodoDashboardProjects,
|
|
10
10
|
todoDashboardMemberNeedsVisibility,
|
|
11
|
-
|
|
11
|
+
writeTodoDashboardDaemonRecord,
|
|
12
12
|
} from '../src/todo-dashboard-registry.mjs';
|
|
13
13
|
import {
|
|
14
14
|
createTodoGanttProjectRegistry,
|
|
@@ -117,8 +117,12 @@ function exit(code) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
await synchronize();
|
|
120
|
-
const dashboard = await startTodoGanttDashboardServer({
|
|
121
|
-
|
|
120
|
+
const dashboard = await startTodoGanttDashboardServer({
|
|
121
|
+
registry,
|
|
122
|
+
port,
|
|
123
|
+
onShutdown: process.platform === 'win32' ? () => exit(0) : null,
|
|
124
|
+
});
|
|
125
|
+
await writeTodoDashboardDaemonRecord({ port: dashboard.port, env });
|
|
122
126
|
const timer = setInterval(() => synchronize().then((count) => {
|
|
123
127
|
if (count === 0) exit(0);
|
|
124
128
|
}).catch(() => exit(1)), 1_000);
|
package/bin/lattice.mjs
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { installPipeCloseGuard } from '../src/cli-stdio.mjs';
|
|
4
4
|
import { renderCliHelp } from '../src/cli-help.mjs';
|
|
5
|
-
import {
|
|
5
|
+
import { installSqliteExperimentalWarningFilter } from '../src/sensor-node-runtime.mjs';
|
|
6
6
|
import packageJson from '../package.json' with { type: 'json' };
|
|
7
7
|
|
|
8
8
|
installPipeCloseGuard();
|
|
9
9
|
|
|
10
10
|
const args = process.argv.slice(2);
|
|
11
|
-
|
|
11
|
+
installSqliteExperimentalWarningFilter();
|
|
12
12
|
const help = renderCliHelp(args);
|
|
13
13
|
|
|
14
14
|
if (help !== null) {
|
|
@@ -145,8 +145,6 @@ if (help !== null) {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
restoreWarningHandler();
|
|
149
|
-
|
|
150
148
|
async function runRuntimeErrorsCli(rest) {
|
|
151
149
|
const runtimeErrors = await import('../src/runtime-errors.mjs');
|
|
152
150
|
const usage = () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.executor_packet.v1.schema.json",
|
|
4
4
|
"title": "lattice.executor_packet.v1",
|
|
5
5
|
"description": "The single context packet issued at dispatch. It is the machine record of the context an executor received.",
|
|
6
6
|
"$comment": "Runtime validation additionally enforces canonical JSON bytes, the self-digest rule for `packet_digest`, and that `context_content_digest` equals the SHA-256 of the canonical JSON projection of exactly {todo_id, task_ref, scope, base_sha, verifier_refs, forbidden_operations} — plan attribution fields are excluded so that an epoch rebind is provably content-preserving.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.executor_receipt.v1.schema.json",
|
|
4
4
|
"title": "lattice.executor_receipt.v1",
|
|
5
5
|
"description": "The receipt an executor returns for one dispatched packet. `packet_digest` is what attributes it to the packet it received.",
|
|
6
6
|
"$comment": "Runtime validation additionally enforces canonical JSON bytes and the self-digest rule for `receipt_digest`. Acceptance is decided by event order, not by the receipt's own claims: the binding is checked against the `executor_dispatched` / `epoch_rebound` event that recorded the handle, worktree and packet digest for that TODO.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.phase_todo_revision.v3.schema.json",
|
|
4
4
|
"title": "lattice.phase_todo_revision.v3",
|
|
5
5
|
"description": "`lattice todo revise-phase`が受理する最新契約。v1/v2のphase-audited desired state・task/phase migrationへ、runtime task migration・source inventory・per-ToDo source cutover batchを加えた12 keyの完全契約。",
|
|
6
6
|
"$comment": "Runtime validation (src/todo-revision.mjs validatePhaseTodoRevision) additionally enforces: canonical JSON+LF bytes; strict sort order on task_migration (by from_task_id), runtime_task_migration.entries (by predecessor_task_id), runtime_task_migration.entries[].successor_task_ids/evidence_digests, and source_inventory.excluded_tombstones/source_cutover_batch.operations (by source_ref); uniqueness constraints across all of those; desired_plan.plan_version === phaseTodoRevisionPlanVersion(...); runtime_task_migration projects onto task_migration exactly (validRuntimeTodoProjection) and its successor_task_ids cover exactly desired_plan.tasks; phase_migration's target phase set equals desired_plan.phases exactly; every reconciliation.*_digest field against its own recomputation; and revision_digest as the canonical self-digest over every other top-level field.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.plan_create_input.v1.schema.json",
|
|
4
4
|
"title": "lattice.plan_create_input.v1",
|
|
5
5
|
"description": "Canonical initial authoring input for lattice plan create.",
|
|
6
6
|
"$comment": "Runtime validation additionally enforces canonical JSON+LF bytes, strict ordering, unique task ids, graph validity, byte bounds, and input_digest as the canonical self-digest.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.plan_create_input.v2.schema.json",
|
|
4
4
|
"title": "lattice.plan_create_input.v2",
|
|
5
5
|
"description": "Canonical initial authoring input for a phase-controlled Lattice plan.",
|
|
6
6
|
"$comment": "Runtime validation additionally enforces canonical JSON+LF bytes, sorted identifiers, unique ids, acyclic task/phase graphs, byte bounds, and input_digest.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.plan_create_input.v3.schema.json",
|
|
4
4
|
"title": "lattice.plan_create_input.v3",
|
|
5
5
|
"description": "Canonical initial authoring input for a phase-audited plan whose task scheduling is controlled by the task DAG.",
|
|
6
6
|
"$comment": "Runtime validation additionally enforces canonical JSON+LF bytes, sorted identifiers and edges, unique ids, combined task/phase acyclicity, byte bounds, and input_digest.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.plan_create_input.v4.schema.json",
|
|
4
4
|
"title": "lattice.plan_create_input.v4",
|
|
5
5
|
"description": "Canonical initial authoring input for a phase-audited plan. Every task requires a design memo. After the complete plan is written, run `lattice plan scope-review` and confirm that every task preserves an owner-supplied work specification without adding a product purpose. あなたがこのToDoに対して、何も考えていないならば、設計メモに `NO_PLAN` と書いてください",
|
|
6
6
|
"$comment": "Runtime validation additionally enforces canonical JSON+LF bytes, sorted identifiers and edges, unique ids, combined task/phase acyclicity, byte bounds, non-empty design_memo, and input_digest.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.plan_scope_review.v1.schema.json",
|
|
4
4
|
"title": "lattice.plan_scope_review.v1",
|
|
5
5
|
"description": "AI-authored review performed after the complete plan is written and before it is registered. Every planned task is judged against the owner-supplied work specifications; task splitting and parallelization are allowed, but a new product purpose is not.",
|
|
6
6
|
"$comment": "Lattice validates complete task coverage, known work-spec references, verdict consistency, and digest binding. The operating AI owns the semantic judgment.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.run_request.v1.schema.json",
|
|
4
4
|
"title": "lattice.run_request.v1",
|
|
5
5
|
"description": "Canonical run request accepted by `lattice plan compile --request` and `lattice run start --request`.",
|
|
6
6
|
"$comment": "Runtime validation additionally enforces canonical JSON bytes and the self-digest rule: `request_digest` is the SHA-256 of the canonical JSON of the request with `request_digest` removed. Rejections are reported as `lattice.cli_error.v2` with `detail.reason` and `detail.path`.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.runtime_adapter_capabilities.v2.schema.json",
|
|
4
4
|
"title": "lattice.runtime_adapter_capabilities.v2",
|
|
5
5
|
"description": "controller handshakeとlaunch descriptorを束縛するruntime adapter能力契約。host_driven_epochはhostによるmanaged epoch駆動の受入宣言である。",
|
|
6
6
|
"type": "object",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.runtime_adapter_registration_input.v1.schema.json",
|
|
4
4
|
"title": "lattice.runtime_adapter_registration_input.v1",
|
|
5
5
|
"description": "lattice run adapter register の入力契約。digestと固定capabilitiesはCLIが導出する。",
|
|
6
6
|
"oneOf": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.runtime_adapter_registration_input.v2.schema.json",
|
|
4
4
|
"title": "lattice.runtime_adapter_registration_input.v2",
|
|
5
5
|
"description": "lattice run adapter register の入力契約。host_driven_epochでhostによるmanaged epoch駆動への同意を宣言し、digestと固定capabilitiesはCLIが導出する。",
|
|
6
6
|
"oneOf": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.todo_extraction.v2.schema.json",
|
|
4
4
|
"title": "lattice.todo_extraction.v2",
|
|
5
5
|
"description": "G4 migration-wave-only intermediate artifact authored by AI. Version 2 adds fail-closed historical in-progress registration without changing v1.",
|
|
6
6
|
"$comment": "Runtime validation additionally enforces UTF-8 byte bounds, strict sort order, unique and resolvable task ids, and extraction_digest as the canonical self-digest. Registration separately rejects an extraction with no register_* task.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.todo_extraction.v3.schema.json",
|
|
4
4
|
"title": "lattice.todo_extraction.v3",
|
|
5
5
|
"description": "AI-authored ToDo migration artifact. Every task carries a non-blank Markdown design_memo. After the complete plan is written, run `lattice plan scope-review` and confirm that every registered task preserves an owner-supplied work specification without adding a product purpose. If no plan exists, write NO_PLAN explicitly.",
|
|
6
6
|
"$comment": "あなたがこのToDoに対して、何も考えていないならば、設計メモに `NO_PLAN` と書いてください。Runtime validation also enforces UTF-8 byte bounds, exact keys, strict sort order, referential integrity, disposition consistency, and the canonical self-digest.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.todo_extraction.v4.schema.json",
|
|
4
4
|
"title": "lattice.todo_extraction.v4",
|
|
5
5
|
"description": "AI-authored ToDo migration artifact. Every task carries a non-blank Markdown design_memo. After the complete plan is written, run `lattice plan scope-review` and confirm that every registered task preserves an owner-supplied work specification without adding a product purpose. If no plan exists, write NO_PLAN explicitly. Runtime validation accepts at most one explicit, reasoned cross-plan dependency; an empty tasks array is reserved for that one dependency between existing tasks, with the top-level plan identity naming the source plan and both endpoint topology digests.",
|
|
6
6
|
"$comment": "あなたがこのToDoに対して、何も考えていないならば、設計メモに `NO_PLAN` と書いてください。Runtime validation also enforces UTF-8 byte bounds, exact keys, strict sort order, referential integrity, disposition consistency, the cross-plan dependency limit, and the canonical self-digest.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.todo_revision.v2.schema.json",
|
|
4
4
|
"title": "lattice.todo_revision.v2",
|
|
5
5
|
"description": "`lattice todo revise`が受理する最新契約。v1のdesired state・source inventory・reconciliationへ、per-ToDoのsource cutover batch(Markdown置換の証跡)を加える。",
|
|
6
6
|
"$comment": "Runtime validation (src/todo-revision.mjs validateTodoRevision) additionally enforces: canonical JSON+LF bytes; strict sort order on task_migration (by from_task_id) and source_inventory.excluded_tombstones/source_cutover_batch.operations (by source_ref); uniqueness of from_task_id/to_task_id/source_ref across those collections; desired_plan.plan_version === todoRevisionPlanVersion(...); reconciliation.reconciliation_digest === todoReconciliationDigest(...); revision_digest as the canonical self-digest over every other top-level field; and that every task_migration.to_task_id (other than 'removed') resolves to a desired_plan task.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.todo_revision_set.v3.schema.json",
|
|
4
4
|
"title": "lattice.todo_revision_set.v3",
|
|
5
5
|
"description": "`lattice todo revise-set`が受理する最新契約。複数planへ跨がるrevisionを1つのatomic書込みへ束ねる。v3のmemberは`lattice.todo_revision.v1/v2`(非phase plan)と`lattice.phase_todo_revision.v1/v2`(phase-audited plan)のいずれかであり、`lattice.phase_todo_revision.v3`はmemberとして受理されない——v3固有のruntime_task_migration/source_cutover_batchはrevise-setの外で単独revise-phaseとして扱う契約になっている。",
|
|
6
6
|
"$comment": "Runtime validation (src/todo-revision.mjs validateTodoRevisionSet) additionally enforces: canonical JSON+LF bytes; revisions.length in [2, 64]; each member's own digest chain (validateTodoRevision / validatePhaseTodoRevision); every member.project_id === project_id; v3はrevisions内に最低1つphase_todo_revision memberを含む必要がある; revisionsが厳密にplan_key昇順でソートされている(重複plan_key禁止); revision_set_digestは他の全top-level fieldへの正準self-digest。",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.todo_structure_binding.v1.schema.json",
|
|
4
4
|
"title": "lattice.todo_structure_binding.v1",
|
|
5
5
|
"description": "Immutable activation binding emitted by a successful authoritative planned compile.",
|
|
6
6
|
"$comment": "Runtime validation additionally enforces exact keys, strict timestamps, actor identity, and the canonical self-digest.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.todo_structure_realization.v1.schema.json",
|
|
4
4
|
"title": "lattice.todo_structure_realization.v1",
|
|
5
5
|
"description": "Append-only realized logical dataflow for one applicable task.",
|
|
6
6
|
"$comment": "Runtime validation additionally enforces canonical ordering, chain continuity, supersedes membership, structure-set identity, planned digest, UTF-8 byte bounds, and the canonical self-digest.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/kitepon
|
|
3
|
+
"$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.todo_structure_set.v1.schema.json",
|
|
4
4
|
"title": "lattice.todo_structure_set.v1",
|
|
5
5
|
"description": "AI-authored logical dataflow overlay for one registered Lattice plan version.",
|
|
6
6
|
"$comment": "Runtime validation additionally enforces UTF-8 byte bounds, strict canonical ordering, referential integrity, exact plan task coverage when supplied, unique data IDs, constant canonical-byte limits, the canonical self-digest, and plan-wide totals of at most 512 anchors, 4096 ports, 2048 operations, and 4096 sinks.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quolu/lattice",
|
|
3
|
-
"version": "0.60.
|
|
3
|
+
"version": "0.60.6",
|
|
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",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/kitepon
|
|
13
|
+
"url": "git+https://github.com/kitepon/Lattice.git"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=22.13 <25 || >=26"
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"verify:release-commit": "node scripts/verify-release-commit.mjs",
|
|
77
77
|
"prepublishOnly": "npm run verify:release-commit",
|
|
78
78
|
"test": "node scripts/run-product-tests.mjs",
|
|
79
|
+
"test:profile": "node scripts/run-product-tests.mjs",
|
|
79
80
|
"test:sensor": "npm --prefix sensor test",
|
|
80
81
|
"check": "node scripts/check-syntax.mjs",
|
|
81
82
|
"ci": "npm run test && npm run test:sensor && npm run check && npm run check:cli-surface && npm run check:open-questions && npm run check:reachability && npm run verify:todo-store",
|
|
@@ -86,9 +87,9 @@
|
|
|
86
87
|
"upstream:check": "node scripts/upstream-check.mjs",
|
|
87
88
|
"upstream:sync": "node scripts/upstream-sync.mjs"
|
|
88
89
|
},
|
|
89
|
-
"homepage": "https://github.com/kitepon
|
|
90
|
+
"homepage": "https://github.com/kitepon/Lattice#readme",
|
|
90
91
|
"bugs": {
|
|
91
|
-
"url": "https://github.com/kitepon
|
|
92
|
+
"url": "https://github.com/kitepon/Lattice/issues"
|
|
92
93
|
},
|
|
93
94
|
"keywords": [
|
|
94
95
|
"ai",
|
|
@@ -31,7 +31,7 @@ function buildNode25BlockBanner(nodeVersion) {
|
|
|
31
31
|
'Node.js 25.x has a V8 WASM JIT (turboshaft) Zone allocator bug that',
|
|
32
32
|
'crashes with `Fatal process out of memory: Zone` when LatticeSensor',
|
|
33
33
|
'compiles tree-sitter grammars. LatticeSensor WILL crash on this Node',
|
|
34
|
-
'version mid-indexing. See https://github.com/kitepon
|
|
34
|
+
'version mid-indexing. See https://github.com/kitepon/Lattice/issues/81',
|
|
35
35
|
'',
|
|
36
36
|
'Fix: install Node.js 22 LTS:',
|
|
37
37
|
' nvm install 22 && nvm use 22 # nvm',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-version-check.js","sourceRoot":"","sources":["../../src/bin/node-version-check.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAWH,wDAmBC;AAGD,4CAEC;AAmBD,sDAkBC;AAtED;;;;;;;;GAQG;AACH,SAAgB,sBAAsB,CAAC,WAAmB;IACxD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,OAAO;QACL,GAAG;QACH,gDAAgD,WAAW,EAAE;QAC7D,GAAG;QACH,qEAAqE;QACrE,qEAAqE;QACrE,sEAAsE;QACtE,
|
|
1
|
+
{"version":3,"file":"node-version-check.js","sourceRoot":"","sources":["../../src/bin/node-version-check.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAWH,wDAmBC;AAGD,4CAEC;AAmBD,sDAkBC;AAtED;;;;;;;;GAQG;AACH,SAAgB,sBAAsB,CAAC,WAAmB;IACxD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,OAAO;QACL,GAAG;QACH,gDAAgD,WAAW,EAAE;QAC7D,GAAG;QACH,qEAAqE;QACrE,qEAAqE;QACrE,sEAAsE;QACtE,wEAAwE;QACxE,EAAE;QACF,8BAA8B;QAC9B,+DAA+D;QAC/D,6EAA6E;QAC7E,EAAE;QACF,sDAAsD;QACtD,yDAAyD;QACzD,GAAG;KACJ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,wDAAwD;AACxD,SAAgB,gBAAgB,CAAC,SAAiB;IAChD,OAAO,SAAS,KAAK,EAAE,CAAC;AAC1B,CAAC;AAED;;;;;;GAMG;AACU,QAAA,cAAc,GAAG,EAAE,CAAC;AAEjC;;;;;;;GAOG;AACH,SAAgB,qBAAqB,CAAC,WAAmB;IACvD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,OAAO;QACL,GAAG;QACH,gDAAgD,WAAW,EAAE;QAC7D,GAAG;QACH,kCAAkC,sBAAc,gCAAgC;QAChF,yEAAyE;QACzE,sBAAsB;QACtB,EAAE;QACF,8BAA8B;QAC9B,+DAA+D;QAC/D,6EAA6E;QAC7E,EAAE;QACF,8CAA8C;QAC9C,yDAAyD;QACzD,GAAG;KACJ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -173,7 +173,7 @@ function worktreeMismatchNotice(m) {
|
|
|
173
173
|
/** Resolve symlinks where possible so tmp/realpath quirks don't break equality. */
|
|
174
174
|
function realpath(p) {
|
|
175
175
|
try {
|
|
176
|
-
return fs.realpathSync(path.resolve(p));
|
|
176
|
+
return fs.realpathSync.native(path.resolve(p));
|
|
177
177
|
}
|
|
178
178
|
catch {
|
|
179
179
|
return path.resolve(p);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktree.js","sourceRoot":"","sources":["../../src/sync/worktree.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"worktree.js","sourceRoot":"","sources":["../../src/sync/worktree.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcH,0CAgBC;AAUD,oCAeC;AAoBD,kEA8BC;AAGD,0DASC;AAOD,wDAOC;AAjID,uCAAyB;AACzB,2CAA6B;AAC7B,iDAA6C;AAE7C;;;;;;;GAOG;AACH,SAAgB,eAAe,CAAC,GAAW;IACzC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAA,4BAAY,EAAC,KAAK,EAAE,CAAC,WAAW,EAAE,iBAAiB,CAAC,EAAE;YAChE,GAAG,EAAE,GAAG;YACR,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,WAAW,EAAE,IAAI;YACjB,sEAAsE;YACtE,uEAAuE;YACvE,8DAA8D;YAC9D,OAAO,EAAE,IAAI;SACd,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,YAAY,CAAC,GAAW;IACtC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAA,4BAAY,EAAC,KAAK,EAAE,CAAC,WAAW,EAAE,kBAAkB,CAAC,EAAE;YACjE,GAAG,EAAE,GAAG;YACR,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,WAAW,EAAE,IAAI;YACjB,OAAO,EAAE,IAAI,EAAE,oCAAoC;SACpD,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,iEAAiE;QACjE,OAAO,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AASD;;;;;;;;;;GAUG;AACH,SAAgB,2BAA2B,CACzC,SAAiB,EACjB,SAAiB;IAEjB,MAAM,YAAY,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;IAChD,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IAE/B,MAAM,iBAAiB,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC9C,IAAI,YAAY,KAAK,iBAAiB;QAAE,OAAO,IAAI,CAAC;IAEpD,4EAA4E;IAC5E,0EAA0E;IAC1E,sEAAsE;IACtE,IAAI,eAAe,CAAC,iBAAiB,CAAC,KAAK,iBAAiB;QAAE,OAAO,IAAI,CAAC;IAE1E,2EAA2E;IAC3E,2EAA2E;IAC3E,0EAA0E;IAC1E,0EAA0E;IAC1E,2EAA2E;IAC3E,iFAAiF;IACjF,4EAA4E;IAC5E,wEAAwE;IACxE,6EAA6E;IAC7E,qEAAqE;IACrE,MAAM,cAAc,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IAClD,MAAM,WAAW,GAAG,YAAY,CAAC,iBAAiB,CAAC,CAAC;IACpD,IAAI,cAAc,IAAI,WAAW,IAAI,cAAc,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAEjF,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC;AACxD,CAAC;AAED,gEAAgE;AAChE,SAAgB,uBAAuB,CAAC,CAAwB;IAC9D,OAAO,CACL,qEAAqE;QACrE,iBAAiB,CAAC,CAAC,YAAY,IAAI;QACnC,iBAAiB,CAAC,CAAC,SAAS,IAAI;QAChC,mFAAmF;QACnF,uFAAuF;QACvF,6BAA6B,CAC9B,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAgB,sBAAsB,CAAC,CAAwB;IAC7D,OAAO,CACL,qEAAqE,CAAC,CAAC,SAAS,KAAK;QACrF,6BAA6B,CAAC,CAAC,YAAY,uCAAuC;QAClF,qFAAqF;QACrF,uBAAuB,CACxB,CAAC;AACJ,CAAC;AAED,mFAAmF;AACnF,SAAS,QAAQ,CAAC,CAAS;IACzB,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;AACH,CAAC"}
|
package/sensor/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"description": "Lattice-owned local structural sensor.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/kitepon
|
|
8
|
+
"url": "git+https://github.com/kitepon/Lattice.git"
|
|
9
9
|
},
|
|
10
10
|
"main": "dist/index.js",
|
|
11
11
|
"types": "dist/index.d.ts",
|
package/src/bridge-daemon.mjs
CHANGED
|
@@ -19,8 +19,9 @@ const STOP_RECEIPT_SCHEMA = 'lattice.bridge_stop_receipt.v1';
|
|
|
19
19
|
const ACTIVE_MARKER_SCHEMA = 'lattice.bridge_daemon_active.v1';
|
|
20
20
|
const CONTROL_MAX_BYTES = 65_536;
|
|
21
21
|
/** control fileの差し替え競合だけを吸収する再読の上限と間隔(`readStrictJson`が唯一の使用者)。 */
|
|
22
|
-
const CONTROL_READ_RETRY_LIMIT =
|
|
23
|
-
const CONTROL_READ_RETRY_DELAY_MS =
|
|
22
|
+
const CONTROL_READ_RETRY_LIMIT = 50;
|
|
23
|
+
const CONTROL_READ_RETRY_DELAY_MS = 10;
|
|
24
|
+
const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
24
25
|
|
|
25
26
|
export function bridgeDaemonDescriptorPath(env = process.env) {
|
|
26
27
|
return path.join(bridgeConfigPaths(env).root, 'bridge-daemon.json');
|
|
@@ -48,7 +49,14 @@ async function atomicDescriptor(ref, value) {
|
|
|
48
49
|
const temporary = `${ref}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
|
|
49
50
|
try {
|
|
50
51
|
await writeFile(temporary, `${JSON.stringify(value)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
51
|
-
|
|
52
|
+
for (let attempt = 0;; attempt += 1) {
|
|
53
|
+
try { await rename(temporary, ref); break; }
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (process.platform !== 'win32' || !['EPERM', 'EACCES'].includes(error?.code)
|
|
56
|
+
|| attempt >= CONTROL_READ_RETRY_LIMIT - 1) throw error;
|
|
57
|
+
await sleep(CONTROL_READ_RETRY_DELAY_MS);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
52
60
|
await chmod(ref, 0o600);
|
|
53
61
|
} finally { await rm(temporary, { force: true }); }
|
|
54
62
|
}
|
|
@@ -80,7 +80,7 @@ const LANDING_COPY = Object.freeze({
|
|
|
80
80
|
}),
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
const GITHUB_URL = 'https://github.com/kitepon
|
|
83
|
+
const GITHUB_URL = 'https://github.com/kitepon/Lattice';
|
|
84
84
|
|
|
85
85
|
export function escapeHtml(value) {
|
|
86
86
|
return String(value).replaceAll('&', '&').replaceAll('<', '<')
|
package/src/runtime-cli.mjs
CHANGED
|
@@ -4445,6 +4445,13 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
|
|
|
4445
4445
|
&& argv[0] === 'run' && argv[1] === 'activate'
|
|
4446
4446
|
&& argv[2] === '--run' && typeof argv[3] === 'string' && argv[3].length > 0) {
|
|
4447
4447
|
action = async () => {
|
|
4448
|
+
if (process.platform === 'win32') {
|
|
4449
|
+
return typedFailure(
|
|
4450
|
+
stderr,
|
|
4451
|
+
'PLATFORM_UNSUPPORTED',
|
|
4452
|
+
'Lattice managed runtime v1 is POSIX-only; use WSL2 on Windows',
|
|
4453
|
+
);
|
|
4454
|
+
}
|
|
4448
4455
|
const { repoRoot, runDir, runRef } = await resolveRunStore(cwd, argv[3]);
|
|
4449
4456
|
await requireLegacyRunMode(runDir, 'activate');
|
|
4450
4457
|
return runActivate({ runDir, runRef, repoRoot, stdout, requestId: requestIdOverride });
|
|
@@ -54,9 +54,17 @@ function sha256Bytes(bytes) { return createHash('sha256').update(bytes).digest('
|
|
|
54
54
|
const execFileAsync = promisify(execFile);
|
|
55
55
|
|
|
56
56
|
export async function observeManagedProcessStartIdentity(pid) {
|
|
57
|
+
if (!Number.isSafeInteger(pid) || pid < 1) {
|
|
58
|
+
fail('ADAPTER_CONTROLLER_UNAVAILABLE', `process start identity観測失敗: ${pid}`);
|
|
59
|
+
}
|
|
57
60
|
let startedIdentity;
|
|
58
61
|
try {
|
|
59
|
-
const
|
|
62
|
+
const executable = process.platform === 'win32' ? 'powershell.exe' : '/bin/ps';
|
|
63
|
+
const args = process.platform === 'win32'
|
|
64
|
+
? ['-NoProfile', '-NonInteractive', '-Command',
|
|
65
|
+
`[System.Diagnostics.Process]::GetProcessById(${pid}).StartTime.ToUniversalTime().Ticks`]
|
|
66
|
+
: ['-o', 'lstart=', '-p', String(pid)];
|
|
67
|
+
const { stdout } = await execFileAsync(executable, args, { encoding: 'utf8' });
|
|
60
68
|
startedIdentity = stdout.trim();
|
|
61
69
|
} catch { fail('ADAPTER_CONTROLLER_UNAVAILABLE', `process start identity観測失敗: ${pid}`); }
|
|
62
70
|
if (!startedIdentity) fail('ADAPTER_CONTROLLER_UNAVAILABLE', `process不在: ${pid}`);
|
|
@@ -4,15 +4,15 @@ export const SQLITE_EXPERIMENTAL_WARNING_MESSAGE =
|
|
|
4
4
|
|
|
5
5
|
export function sensorNodeRuntimeFlags(nodeVersion = process.versions.node) {
|
|
6
6
|
const major = Number(nodeVersion.split('.')[0]);
|
|
7
|
-
return major === 22 ? [SQLITE_EXPERIMENTAL_WARNING_FLAG] : [];
|
|
7
|
+
return major === 22 || major === 24 ? [SQLITE_EXPERIMENTAL_WARNING_FLAG] : [];
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Node 22
|
|
11
|
+
* Node 22/24 print node:sqlite's ExperimentalWarning to stderr. The CLI has a
|
|
12
12
|
* JSON-only output contract, so suppress that exact warning while node:sqlite loads.
|
|
13
13
|
* Every other process warning keeps Node's original behavior.
|
|
14
14
|
*/
|
|
15
|
-
export function
|
|
15
|
+
export function installSqliteExperimentalWarningFilter({
|
|
16
16
|
nodeVersion = process.versions.node,
|
|
17
17
|
processObject = process,
|
|
18
18
|
} = {}) {
|
package/src/todo-cli.mjs
CHANGED
|
@@ -3428,6 +3428,8 @@ export async function renderTodoGanttForProject({
|
|
|
3428
3428
|
async function serveGantt({ repoRoot, port, stdout, env, scope = DEFAULT_GANTT_SCOPE }) {
|
|
3429
3429
|
const initialStore = await readTodoStoreStable({ repoRoot });
|
|
3430
3430
|
const identity = await resolveProjectIdentity({ repoRoot, projectId: initialStore.project_id, env });
|
|
3431
|
+
let stop;
|
|
3432
|
+
const stopped = new Promise((resolve) => { stop = resolve; });
|
|
3431
3433
|
const live = await startTodoGanttLiveServer({
|
|
3432
3434
|
projectId: initialStore.project_id,
|
|
3433
3435
|
displayName: identity.displayName,
|
|
@@ -3444,6 +3446,7 @@ async function serveGantt({ repoRoot, port, stdout, env, scope = DEFAULT_GANTT_S
|
|
|
3444
3446
|
readHead: async () => ganttLiveHeadDigest({
|
|
3445
3447
|
repoRoot, store: await readTodoStoreStable({ repoRoot }),
|
|
3446
3448
|
}),
|
|
3449
|
+
onShutdown: process.platform === 'win32' ? stop : null,
|
|
3447
3450
|
});
|
|
3448
3451
|
const result = { schema: 'lattice.todo_gantt_live_result.v3', project_id: live.projectId,
|
|
3449
3452
|
resource_scope: 'project', selection_scope: scope,
|
|
@@ -3452,10 +3455,8 @@ async function serveGantt({ repoRoot, port, stdout, env, scope = DEFAULT_GANTT_S
|
|
|
3452
3455
|
host: live.host, port: live.port, project_path: live.projectPath,
|
|
3453
3456
|
url: live.url, events_url: live.eventsUrl };
|
|
3454
3457
|
stdout.write(`${JSON.stringify(result)}\n`);
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
process.once('SIGINT', stop); process.once('SIGTERM', stop);
|
|
3458
|
-
});
|
|
3458
|
+
process.once('SIGINT', stop); process.once('SIGTERM', stop);
|
|
3459
|
+
await stopped;
|
|
3459
3460
|
await live.close();
|
|
3460
3461
|
return null;
|
|
3461
3462
|
}
|
|
@@ -20,7 +20,8 @@ export const TODO_DASHBOARD_CODE_VERSION = packageJson.version;
|
|
|
20
20
|
const REGISTRY_SCHEMA = 'lattice.todo_dashboard_registry.v1';
|
|
21
21
|
const DAEMON_SCHEMA = 'lattice.todo_dashboard_daemon.v1';
|
|
22
22
|
const DEFAULT_PORT = 0;
|
|
23
|
-
|
|
23
|
+
// 登録簿の鮮度窓=dashboard配信寿命。heartbeatの露出判定へ流用しない(ADR 0165)。
|
|
24
|
+
export const TODO_DASHBOARD_STALE_MS = 7 * 24 * 60 * 60 * 1_000;
|
|
24
25
|
const LOCK_ATTEMPTS = 240;
|
|
25
26
|
const LOCK_WAIT_MS = 25;
|
|
26
27
|
const LOCK_STALE_MS = 30_000;
|
|
@@ -377,6 +378,17 @@ function signalIfPresent(signalProcess, pid, signal) {
|
|
|
377
378
|
}
|
|
378
379
|
}
|
|
379
380
|
|
|
381
|
+
async function requestDaemonShutdown(descriptor) {
|
|
382
|
+
try {
|
|
383
|
+
const response = await fetch(`http://127.0.0.1:${descriptor.port}/__lattice/shutdown`, {
|
|
384
|
+
method: 'POST', signal: AbortSignal.timeout(500),
|
|
385
|
+
});
|
|
386
|
+
return response.status === 202;
|
|
387
|
+
} catch {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
380
392
|
async function stopAttestedLegacyDaemon(descriptor, {
|
|
381
393
|
signalProcess = process.kill, isProcessAlive = processIsAlive, timeoutMs = 3_000,
|
|
382
394
|
} = {}) {
|
|
@@ -387,7 +399,7 @@ async function stopAttestedLegacyDaemon(descriptor, {
|
|
|
387
399
|
error.code = 'DASHBOARD_LEGACY_ATTESTATION_LOST';
|
|
388
400
|
throw error;
|
|
389
401
|
}
|
|
390
|
-
signalProcess(descriptor.pid, 'SIGTERM');
|
|
402
|
+
if (!await requestDaemonShutdown(descriptor)) signalProcess(descriptor.pid, 'SIGTERM');
|
|
391
403
|
if (await awaitDaemonStopped(descriptor, { isProcessAlive, deadline: Date.now() + timeoutMs })) return;
|
|
392
404
|
const error = new Error('legacy dashboard daemon did not stop');
|
|
393
405
|
error.code = 'DASHBOARD_LEGACY_STOP_FAILED';
|
|
@@ -406,7 +418,7 @@ async function stopStrayDaemon(descriptor, {
|
|
|
406
418
|
signalProcess = process.kill, isProcessAlive = processIsAlive, timeoutMs = 3_000,
|
|
407
419
|
} = {}) {
|
|
408
420
|
if (await daemonAttestation(descriptor) === null) return false;
|
|
409
|
-
signalIfPresent(signalProcess, descriptor.pid, 'SIGTERM');
|
|
421
|
+
if (!await requestDaemonShutdown(descriptor)) signalIfPresent(signalProcess, descriptor.pid, 'SIGTERM');
|
|
410
422
|
const half = Math.ceil(timeoutMs / 2);
|
|
411
423
|
if (await awaitDaemonStopped(descriptor, { isProcessAlive, deadline: Date.now() + half })) return true;
|
|
412
424
|
signalIfPresent(signalProcess, descriptor.pid, 'SIGKILL');
|
|
@@ -450,7 +462,7 @@ async function stopSpawnedReplacement(child, descriptor, {
|
|
|
450
462
|
|| !await isProcessAlive(child.pid);
|
|
451
463
|
return exited && await daemonAttestation(descriptor) === null;
|
|
452
464
|
};
|
|
453
|
-
child.kill('SIGTERM');
|
|
465
|
+
if (!await requestDaemonShutdown(descriptor)) child.kill('SIGTERM');
|
|
454
466
|
let deadline = Date.now() + Math.ceil(timeoutMs / 2);
|
|
455
467
|
while (Date.now() < deadline) {
|
|
456
468
|
if (await stopped()) return;
|
|
@@ -467,15 +479,24 @@ async function stopSpawnedReplacement(child, descriptor, {
|
|
|
467
479
|
throw error;
|
|
468
480
|
}
|
|
469
481
|
|
|
470
|
-
|
|
482
|
+
function currentDaemonRecord(port) {
|
|
483
|
+
return { schema: DAEMON_SCHEMA, pid: process.pid, port,
|
|
484
|
+
started_at: new Date().toISOString() };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export async function writeTodoDashboardDaemonRecord({ port, env = process.env }) {
|
|
471
488
|
if (!Number.isSafeInteger(port) || port <= 0 || port > 65_535) throw new TypeError('daemon port invalid');
|
|
472
489
|
const refs = paths(env);
|
|
473
490
|
await mkdir(refs.daemons, { recursive: true, mode: 0o700 });
|
|
474
|
-
const record =
|
|
475
|
-
started_at: new Date().toISOString() };
|
|
476
|
-
// 記録を先に置く。descriptorだけが在って記録が無い瞬間を作ると、その隙に起動側が
|
|
477
|
-
// 死んだ時に、まさに直そうとしている「誰からも観測されないdaemon」が生まれる。
|
|
491
|
+
const record = currentDaemonRecord(port);
|
|
478
492
|
await atomicJson(daemonRecordRef(refs, process.pid), record);
|
|
493
|
+
return record;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export async function writeTodoDashboardDaemonDescriptor({ port, env = process.env }) {
|
|
497
|
+
const refs = paths(env);
|
|
498
|
+
// 直接descriptorを公開する呼出面でも、孤児を再発見できるdaemon固有記録を先に置く。
|
|
499
|
+
const record = await writeTodoDashboardDaemonRecord({ port, env });
|
|
479
500
|
await atomicJson(refs.descriptor, record);
|
|
480
501
|
}
|
|
481
502
|
|
|
@@ -545,7 +566,8 @@ export async function ensureTodoDashboardDaemon({ env = process.env, spawnDaemon
|
|
|
545
566
|
const deadline = Date.now() + startupTimeoutMs;
|
|
546
567
|
while (Date.now() < deadline) {
|
|
547
568
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
548
|
-
const descriptor =
|
|
569
|
+
const descriptor = typeof child.pid === 'number'
|
|
570
|
+
? await readJson(daemonRecordRef(refs, child.pid), null) : null;
|
|
549
571
|
const healthy = await daemonHealthy(descriptor);
|
|
550
572
|
if (!healthy && typeof child.pid === 'number'
|
|
551
573
|
&& !await replacementIsProcessAlive(child.pid)) break;
|
|
@@ -557,17 +579,16 @@ export async function ensureTodoDashboardDaemon({ env = process.env, spawnDaemon
|
|
|
557
579
|
});
|
|
558
580
|
} catch (error) {
|
|
559
581
|
await stopSpawnedReplacement(child, descriptor, { isProcessAlive: replacementIsProcessAlive });
|
|
560
|
-
await atomicJson(refs.descriptor, legacy);
|
|
561
582
|
throw error;
|
|
562
583
|
}
|
|
563
584
|
}
|
|
585
|
+
await atomicJson(refs.descriptor, descriptor);
|
|
564
586
|
await reap(descriptor.pid);
|
|
565
587
|
return descriptor;
|
|
566
588
|
}
|
|
567
589
|
}
|
|
568
590
|
child.kill?.('SIGTERM');
|
|
569
|
-
if (legacy
|
|
570
|
-
else await rm(refs.descriptor, { force: true });
|
|
591
|
+
if (legacy === null) await rm(refs.descriptor, { force: true });
|
|
571
592
|
const error = new Error('dashboard daemon did not become ready');
|
|
572
593
|
error.code = 'DASHBOARD_DAEMON_UNAVAILABLE';
|
|
573
594
|
throw error;
|
package/src/todo-gantt-live.mjs
CHANGED
|
@@ -87,7 +87,7 @@ function dashboardHtml(projects) {
|
|
|
87
87
|
}).join('');
|
|
88
88
|
const content = rows.length === 0 ? '<p>アクティブなプロジェクトはありません。</p>'
|
|
89
89
|
: `<ul>${rows}</ul>`;
|
|
90
|
-
return `<!doctype html><html lang="ja"><head><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="Latticeが管理している公開中の工程と現在地を確認できます。"><meta property="og:title" content="公開中の工程表 — Lattice"><meta property="og:description" content="Latticeが管理している公開中の工程と現在地を確認できます。"><meta name="theme-color" content="#f7f3ea"><title>公開中の工程表 — Lattice</title><style>:root{color-scheme:light;--paper:#f7f3ea;--panel:#fffdf8;--ink:#201d19;--soft:#6c655d;--line:#d8d0c5;--cobalt:#315cbe;--orange:#e85f2a}*{box-sizing:border-box}body{min-height:100vh;margin:0;color:var(--ink);background:var(--paper);font:16px/1.7 system-ui,-apple-system,sans-serif}.shell{max-width:880px;margin:0 auto;padding:28px 22px 40px}.brand{display:flex;align-items:center;gap:9px;padding-bottom:24px;border-bottom:1px solid var(--line);font-size:.88rem}.brand a,.footer a{color:var(--ink);font-weight:800;text-decoration:none}.brand a:hover,.footer a:hover{color:var(--cobalt)}.brand span{color:var(--soft)}main{padding:64px 0 72px}.eyebrow{margin:0 0 8px;color:var(--orange);font-size:.76rem;font-weight:800;letter-spacing:.14em}.lead{max-width:620px;margin:0 0 34px;color:var(--soft)}h1{margin:0 0 14px;font-size:clamp(2rem,6vw,3.4rem);line-height:1.12;letter-spacing:-.04em}ul{display:grid;gap:12px;margin:0;padding:0;list-style:none}li a{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:20px;padding:18px 20px;border:1px solid var(--line);border-radius:12px;color:inherit;background:var(--panel);text-decoration:none;box-shadow:0 8px 28px rgba(48,39,27,.04)}li a:hover{border-color:var(--cobalt);transform:translateY(-1px)}li strong{font-size:1.04rem}li code{color:var(--soft);font-size:.78rem}li span{color:var(--cobalt);font-weight:800}.note{margin:28px 0 0;padding:16px 18px;border-left:3px solid var(--orange);color:var(--soft);background:rgba(255,253,248,.72);font-size:.88rem}.footer{display:flex;flex-wrap:wrap;justify-content:space-between;gap:16px;padding-top:20px;border-top:1px solid var(--line);color:var(--soft);font-size:.82rem}.footer nav{display:flex;gap:18px}@media(max-width:560px){.shell{padding:20px 16px 32px}main{padding:44px 0 56px}li a{grid-template-columns:minmax(0,1fr) auto;padding:16px}li code{grid-column:1/-1;grid-row:2}.footer{display:block}.footer nav{margin-top:10px}}</style></head><body><div class="shell"><header class="brand"><a href="https://kitepon.dev/">kitepon.dev</a><span aria-hidden="true">/</span><strong>Lattice</strong></header><main><p class="eyebrow">LIVE DEVELOPMENT</p><h1>公開中の工程表</h1><p class="lead">Latticeが管理しているプロジェクトの工程と、いまどこまで進んでいるかを公開データから確認できます。</p>${content}<p class="note">表示内容はLatticeの記録から自動生成されます。製品の紹介や使い方はGitHubをご覧ください。</p></main><footer class="footer"><span>kitepon.dev の開発工程を、Latticeで可視化しています。</span><nav aria-label="関連リンク"><a href="https://kitepon.dev/">kitepon.dev</a><a href="https://github.com/kitepon
|
|
90
|
+
return `<!doctype html><html lang="ja"><head><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="Latticeが管理している公開中の工程と現在地を確認できます。"><meta property="og:title" content="公開中の工程表 — Lattice"><meta property="og:description" content="Latticeが管理している公開中の工程と現在地を確認できます。"><meta name="theme-color" content="#f7f3ea"><title>公開中の工程表 — Lattice</title><style>:root{color-scheme:light;--paper:#f7f3ea;--panel:#fffdf8;--ink:#201d19;--soft:#6c655d;--line:#d8d0c5;--cobalt:#315cbe;--orange:#e85f2a}*{box-sizing:border-box}body{min-height:100vh;margin:0;color:var(--ink);background:var(--paper);font:16px/1.7 system-ui,-apple-system,sans-serif}.shell{max-width:880px;margin:0 auto;padding:28px 22px 40px}.brand{display:flex;align-items:center;gap:9px;padding-bottom:24px;border-bottom:1px solid var(--line);font-size:.88rem}.brand a,.footer a{color:var(--ink);font-weight:800;text-decoration:none}.brand a:hover,.footer a:hover{color:var(--cobalt)}.brand span{color:var(--soft)}main{padding:64px 0 72px}.eyebrow{margin:0 0 8px;color:var(--orange);font-size:.76rem;font-weight:800;letter-spacing:.14em}.lead{max-width:620px;margin:0 0 34px;color:var(--soft)}h1{margin:0 0 14px;font-size:clamp(2rem,6vw,3.4rem);line-height:1.12;letter-spacing:-.04em}ul{display:grid;gap:12px;margin:0;padding:0;list-style:none}li a{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:20px;padding:18px 20px;border:1px solid var(--line);border-radius:12px;color:inherit;background:var(--panel);text-decoration:none;box-shadow:0 8px 28px rgba(48,39,27,.04)}li a:hover{border-color:var(--cobalt);transform:translateY(-1px)}li strong{font-size:1.04rem}li code{color:var(--soft);font-size:.78rem}li span{color:var(--cobalt);font-weight:800}.note{margin:28px 0 0;padding:16px 18px;border-left:3px solid var(--orange);color:var(--soft);background:rgba(255,253,248,.72);font-size:.88rem}.footer{display:flex;flex-wrap:wrap;justify-content:space-between;gap:16px;padding-top:20px;border-top:1px solid var(--line);color:var(--soft);font-size:.82rem}.footer nav{display:flex;gap:18px}@media(max-width:560px){.shell{padding:20px 16px 32px}main{padding:44px 0 56px}li a{grid-template-columns:minmax(0,1fr) auto;padding:16px}li code{grid-column:1/-1;grid-row:2}.footer{display:block}.footer nav{margin-top:10px}}</style></head><body><div class="shell"><header class="brand"><a href="https://kitepon.dev/">kitepon.dev</a><span aria-hidden="true">/</span><strong>Lattice</strong></header><main><p class="eyebrow">LIVE DEVELOPMENT</p><h1>公開中の工程表</h1><p class="lead">Latticeが管理しているプロジェクトの工程と、いまどこまで進んでいるかを公開データから確認できます。</p>${content}<p class="note">表示内容はLatticeの記録から自動生成されます。製品の紹介や使い方はGitHubをご覧ください。</p></main><footer class="footer"><span>kitepon.dev の開発工程を、Latticeで可視化しています。</span><nav aria-label="関連リンク"><a href="https://kitepon.dev/">kitepon.dev</a><a href="https://github.com/kitepon/Lattice">GitHub</a></nav></footer></div></body></html>`;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
function notFoundHtml(code, path) {
|
|
@@ -181,10 +181,13 @@ function finishRequestFailure(response, code, path) {
|
|
|
181
181
|
} catch { response.destroy(); }
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
export async function startTodoGanttDashboardServer({
|
|
184
|
+
export async function startTodoGanttDashboardServer({
|
|
185
|
+
registry, port = 0, redirectSingleProject = false, onShutdown = null,
|
|
186
|
+
}) {
|
|
185
187
|
if (!Number.isSafeInteger(port) || port < 0 || port > 65_535) throw new TypeError('port must be 0..65535');
|
|
186
188
|
if (registry === null || typeof registry !== 'object' || typeof registry.get !== 'function'
|
|
187
189
|
|| typeof registry.list !== 'function') throw new TypeError('registry required');
|
|
190
|
+
if (onShutdown !== null && typeof onShutdown !== 'function') throw new TypeError('onShutdown must be a function');
|
|
188
191
|
const clientsByProject = new Map();
|
|
189
192
|
const lastHeads = new Map();
|
|
190
193
|
const lastHeartbeats = new Map();
|
|
@@ -208,6 +211,13 @@ export async function startTodoGanttDashboardServer({ registry, port = 0, redire
|
|
|
208
211
|
try {
|
|
209
212
|
const url = new URL(requestPath, `http://${LOOPBACK}`);
|
|
210
213
|
requestPath = url.pathname;
|
|
214
|
+
if (url.pathname === '/__lattice/shutdown' && request.method === 'POST' && onShutdown !== null) {
|
|
215
|
+
response.writeHead(202, { 'content-type': 'application/json; charset=utf-8',
|
|
216
|
+
'cache-control': 'no-store', 'x-content-type-options': 'nosniff' });
|
|
217
|
+
response.end(`${JSON.stringify({ schema: 'lattice.todo_dashboard_shutdown.v1', accepted: true })}\n`);
|
|
218
|
+
setImmediate(() => Promise.resolve().then(onShutdown).catch(() => {}));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
211
221
|
if (request.method !== 'GET') {
|
|
212
222
|
response.setHeader('allow', 'GET');
|
|
213
223
|
sendHttpError(response, 405, 'METHOD_NOT_ALLOWED', requestPath);
|
|
@@ -341,9 +351,13 @@ export async function startTodoGanttDashboardServer({ registry, port = 0, redire
|
|
|
341
351
|
});
|
|
342
352
|
}
|
|
343
353
|
|
|
344
|
-
export async function startTodoGanttLiveServer({
|
|
354
|
+
export async function startTodoGanttLiveServer({
|
|
355
|
+
projectId, displayName = projectId, port = 0, render, readHead, onShutdown = null,
|
|
356
|
+
}) {
|
|
345
357
|
const registry = createTodoGanttProjectRegistry([{ projectId, displayName, render, readHead }]);
|
|
346
|
-
const dashboard = await startTodoGanttDashboardServer({
|
|
358
|
+
const dashboard = await startTodoGanttDashboardServer({
|
|
359
|
+
registry, port, redirectSingleProject: true, onShutdown,
|
|
360
|
+
});
|
|
347
361
|
const path = projectPath(projectId);
|
|
348
362
|
return Object.freeze({
|
|
349
363
|
projectId,
|
|
@@ -117,7 +117,7 @@ async function actualIndexPath(repoRoot, env) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
async function syncPath(target) {
|
|
120
|
-
const handle = await open(target, fsConstants.
|
|
120
|
+
const handle = await open(target, fsConstants.O_RDWR);
|
|
121
121
|
try { await handle.sync(); } finally { await handle.close(); }
|
|
122
122
|
}
|
|
123
123
|
|
package/src/todo-store.mjs
CHANGED
|
@@ -1177,6 +1177,102 @@ function pinnedSourceLine(repoRoot, source, cache = null) {
|
|
|
1177
1177
|
throw new Error('line outside blob');
|
|
1178
1178
|
}
|
|
1179
1179
|
|
|
1180
|
+
// 検証対象objectの一括prefetch。event payloadに現れるevidence記述子とimport sourceを
|
|
1181
|
+
// 収集し、repoごとに1回の`cat-file --batch`でcacheへseedする。object 1個=spawn 1回の
|
|
1182
|
+
// 雪崩はWindowsで起動単価66〜100msになり、evidenceが育ったstoreのstatusを桁で遅くする。
|
|
1183
|
+
// 成功した読みだけをcacheへ入れ、missing・型不一致・batchごと失敗(repo不在等の境界)は
|
|
1184
|
+
// 何もcacheしない——正誤の裁定は従来どおりper-objectの検証経路が同じ結果を出す。
|
|
1185
|
+
function prefetchVerificationObjects(manifest, repoRoot, events, pinnedSourceCache) {
|
|
1186
|
+
const candidates = [];
|
|
1187
|
+
for (const event of events) {
|
|
1188
|
+
// 改版genesisはevent直下のstate_migrationでtask状態(evidence込み)を搬送する。
|
|
1189
|
+
// replayがcarry系だけを復元・検証するので、収集も同じpolicyへ揃える。
|
|
1190
|
+
if (Array.isArray(event.state_migration)) {
|
|
1191
|
+
for (const migration of event.state_migration) {
|
|
1192
|
+
if (['carry', 'carry_reconciled_metadata', 'acquire_phase'].includes(migration.state_policy)
|
|
1193
|
+
&& migration.state !== null && typeof migration.state === 'object'
|
|
1194
|
+
&& migration.state.evidence !== null && typeof migration.state.evidence === 'object') {
|
|
1195
|
+
candidates.push(migration.state.evidence);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
const payload = event.payload;
|
|
1200
|
+
if (payload === null || typeof payload !== 'object') continue;
|
|
1201
|
+
if (payload.evidence !== null && typeof payload.evidence === 'object') candidates.push(payload.evidence);
|
|
1202
|
+
if (payload.decision_evidence !== null && typeof payload.decision_evidence === 'object') {
|
|
1203
|
+
candidates.push(payload.decision_evidence);
|
|
1204
|
+
}
|
|
1205
|
+
if (Array.isArray(payload.evidence_slots)) {
|
|
1206
|
+
for (const slot of payload.evidence_slots) {
|
|
1207
|
+
if (slot !== null && typeof slot === 'object'
|
|
1208
|
+
&& slot.evidence !== null && typeof slot.evidence === 'object') candidates.push(slot.evidence);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
const repositories = new Map(manifest.repositories.map((repo) => [repo.repo_id, repo.path]));
|
|
1213
|
+
const oidsByRepo = new Map();
|
|
1214
|
+
const pinnedCommits = new Set();
|
|
1215
|
+
const pinnedSpecs = new Set();
|
|
1216
|
+
for (const candidate of candidates) {
|
|
1217
|
+
if (validateEvidenceDescriptor(candidate)) {
|
|
1218
|
+
const repoRef = repositories.get(candidate.repo_id);
|
|
1219
|
+
if (repoRef === undefined) continue;
|
|
1220
|
+
const absoluteRepo = path.resolve(repoRoot, repoRef);
|
|
1221
|
+
if (evidenceBlobCache.has(`${absoluteRepo}\0${candidate.git_blob_oid}`)) continue;
|
|
1222
|
+
let oids = oidsByRepo.get(absoluteRepo);
|
|
1223
|
+
if (oids === undefined) oidsByRepo.set(absoluteRepo, oids = new Set());
|
|
1224
|
+
oids.add(candidate.git_blob_oid);
|
|
1225
|
+
} else if (validateTodoImportSource(candidate) && pinnedSourceCache !== null) {
|
|
1226
|
+
if (!pinnedSourceCache.commits.has(candidate.source_commit)) pinnedCommits.add(candidate.source_commit);
|
|
1227
|
+
const spec = `${candidate.source_commit}:${candidate.origin_plan_ref}`;
|
|
1228
|
+
if (!pinnedSourceCache.blobs.has(spec)) pinnedSpecs.add(spec);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
for (const [absoluteRepo, oids] of oidsByRepo) {
|
|
1232
|
+
let entries;
|
|
1233
|
+
try {
|
|
1234
|
+
entries = gitCatFileBatch([...oids], {
|
|
1235
|
+
cwd: absoluteRepo, maxBodyBytes: TODO_LIMITS.narrativeSectionBytes + 1,
|
|
1236
|
+
});
|
|
1237
|
+
} catch { continue; }
|
|
1238
|
+
let index = 0;
|
|
1239
|
+
for (const oid of oids) {
|
|
1240
|
+
const entry = entries[index];
|
|
1241
|
+
index += 1;
|
|
1242
|
+
// seedしないだけで正誤は変えない: 上限超過bodyは単体読みのmaxBufferでは読めない
|
|
1243
|
+
// (=従来は未検証)ので、batch同乗の予算合算で検証済みへ昇格させない。cacheが
|
|
1244
|
+
// 満杯なら追い出さずseedを止める——seeding自身の追い出しは自分のseedを連鎖破壊し、
|
|
1245
|
+
// 513件以上で全件個別spawnへ戻る。溢れた分はper-item経路が従来どおり扱う。
|
|
1246
|
+
if (entry.missing || entry.type !== 'blob'
|
|
1247
|
+
|| entry.bytes.length > TODO_LIMITS.narrativeSectionBytes
|
|
1248
|
+
|| evidenceBlobCache.size >= EVIDENCE_BLOB_CACHE_LIMIT) continue;
|
|
1249
|
+
evidenceBlobCache.set(`${absoluteRepo}\0${oid}`, entry.bytes);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
const pinnedNames = [...pinnedCommits, ...pinnedSpecs];
|
|
1253
|
+
if (pinnedNames.length === 0) return;
|
|
1254
|
+
let entries;
|
|
1255
|
+
try {
|
|
1256
|
+
entries = gitCatFileBatch(pinnedNames, {
|
|
1257
|
+
cwd: repoRoot, maxBodyBytes: TODO_LIMITS.narrativeSectionBytes + 1,
|
|
1258
|
+
});
|
|
1259
|
+
} catch { return; }
|
|
1260
|
+
let index = 0;
|
|
1261
|
+
for (const commit of pinnedCommits) {
|
|
1262
|
+
const entry = entries[index];
|
|
1263
|
+
index += 1;
|
|
1264
|
+
if (!entry.missing && entry.type === 'commit') pinnedSourceCache.commits.add(commit);
|
|
1265
|
+
}
|
|
1266
|
+
for (const spec of pinnedSpecs) {
|
|
1267
|
+
const entry = entries[index];
|
|
1268
|
+
index += 1;
|
|
1269
|
+
if (!entry.missing && entry.type === 'blob'
|
|
1270
|
+
&& entry.bytes.length <= TODO_LIMITS.narrativeSectionBytes) {
|
|
1271
|
+
pinnedSourceCache.blobs.set(spec, entry.bytes);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1180
1276
|
function markdownCheckboxState(lineBytes) {
|
|
1181
1277
|
if (lineBytes.length >= 3 && lineBytes[0] === 0xef && lineBytes[1] === 0xbb && lineBytes[2] === 0xbf) return null;
|
|
1182
1278
|
let line;
|
|
@@ -1221,7 +1317,7 @@ function narrativeAnchorSource(value) {
|
|
|
1221
1317
|
});
|
|
1222
1318
|
}
|
|
1223
1319
|
|
|
1224
|
-
function materializeImportedNarrativeAnchors(repoRoot, planInput, sources) {
|
|
1320
|
+
function materializeImportedNarrativeAnchors(repoRoot, planInput, sources, cache = null) {
|
|
1225
1321
|
if (sources === undefined) return planInput;
|
|
1226
1322
|
if (!['lattice.todo_plan.v2', 'lattice.todo_plan.v6'].includes(planInput?.schema)
|
|
1227
1323
|
|| !Array.isArray(sources)
|
|
@@ -1241,7 +1337,7 @@ function materializeImportedNarrativeAnchors(repoRoot, planInput, sources) {
|
|
|
1241
1337
|
if (source !== undefined && task.narrative_ref === source.origin_plan_ref
|
|
1242
1338
|
&& ['checked', 'unchecked'].includes(source.checkbox_state)) {
|
|
1243
1339
|
try {
|
|
1244
|
-
const line = pinnedSourceLine(repoRoot, source);
|
|
1340
|
+
const line = pinnedSourceLine(repoRoot, source, cache);
|
|
1245
1341
|
if (markdownCheckboxState(line) === source.checkbox_state) {
|
|
1246
1342
|
narrativeAnchor = {
|
|
1247
1343
|
origin_plan_ref: source.origin_plan_ref,
|
|
@@ -1259,7 +1355,7 @@ function materializeImportedNarrativeAnchors(repoRoot, planInput, sources) {
|
|
|
1259
1355
|
};
|
|
1260
1356
|
}
|
|
1261
1357
|
|
|
1262
|
-
function verifyPlanNarrativeAnchors(repoRoot, plan, trustedPlan = null) {
|
|
1358
|
+
function verifyPlanNarrativeAnchors(repoRoot, plan, trustedPlan = null, cache = null) {
|
|
1263
1359
|
if (!['lattice.todo_plan.v2', 'lattice.todo_plan.v3', 'lattice.todo_plan.v4',
|
|
1264
1360
|
'lattice.todo_plan.v5', 'lattice.todo_plan.v6', 'lattice.todo_plan.v7'].includes(plan.schema)) return;
|
|
1265
1361
|
const trusted = new Map((['lattice.todo_plan.v2', 'lattice.todo_plan.v3', 'lattice.todo_plan.v4',
|
|
@@ -1273,7 +1369,7 @@ function verifyPlanNarrativeAnchors(repoRoot, plan, trustedPlan = null) {
|
|
|
1273
1369
|
if (previous !== undefined
|
|
1274
1370
|
&& canonicalizeTodoArtifact(previous) === canonicalizeTodoArtifact(anchor)) continue;
|
|
1275
1371
|
try {
|
|
1276
|
-
const line = pinnedSourceLine(repoRoot, anchor);
|
|
1372
|
+
const line = pinnedSourceLine(repoRoot, anchor, cache);
|
|
1277
1373
|
if (sha256Bytes(line) !== anchor.source_line_digest || markdownCheckboxState(line) === null) {
|
|
1278
1374
|
throw new Error('anchor mismatch');
|
|
1279
1375
|
}
|
|
@@ -1374,6 +1470,7 @@ export async function readTodoStore(options = {}) {
|
|
|
1374
1470
|
&& descriptor.active_revision_digest !== plan.plan_digest) {
|
|
1375
1471
|
fail('STORE_INCONSISTENT', 'manifest_revision_binding_mismatch');
|
|
1376
1472
|
}
|
|
1473
|
+
prefetchVerificationObjects(manifest, repoRoot, journal.events, pinnedSourceCache);
|
|
1377
1474
|
const verifyEvidence = evidenceVerifier(manifest, repoRoot, options.forWrite === true);
|
|
1378
1475
|
const verifyImportSource = importSourceVerifier(repoRoot, options.forWrite === true, pinnedSourceCache);
|
|
1379
1476
|
const tasks = replay(plan, journal.events, {
|
|
@@ -1471,7 +1568,13 @@ async function atomicWrite(absolute, bytes) {
|
|
|
1471
1568
|
let handle;
|
|
1472
1569
|
try {
|
|
1473
1570
|
handle = await open(temporary, 'wx', 0o600);
|
|
1474
|
-
await handle.writeFile(bytes);
|
|
1571
|
+
await handle.writeFile(bytes);
|
|
1572
|
+
// WSL2でのNTFSパス(/mnt/以下)ではfsyncがEIOを返すことがある。データ自体は書き込まれている。
|
|
1573
|
+
try { await handle.sync(); } catch (syncError) {
|
|
1574
|
+
if (!(process.platform === 'linux' && /^\/mnt\//.test(absolute) && syncError?.code === 'EIO')) throw syncError;
|
|
1575
|
+
process.stderr.write(`lattice: fsync skipped (WSL2/NTFS EIO) ${absolute}\n`);
|
|
1576
|
+
}
|
|
1577
|
+
await handle.close(); handle = null;
|
|
1475
1578
|
await rename(temporary, absolute);
|
|
1476
1579
|
const directory = await open(path.dirname(absolute), 'r');
|
|
1477
1580
|
// Windowsはdirectory handleのfsyncを許さず常にEPERM/EINVALを返す(Node仕様)。
|
|
@@ -1493,7 +1596,11 @@ async function atomicWriteMode(absolute, bytes, mode) {
|
|
|
1493
1596
|
try {
|
|
1494
1597
|
handle = await open(temporary, 'wx', mode);
|
|
1495
1598
|
await handle.writeFile(bytes); await handle.chmod(mode);
|
|
1496
|
-
await handle.sync();
|
|
1599
|
+
try { await handle.sync(); } catch (syncError) {
|
|
1600
|
+
if (!(process.platform === 'linux' && /^\/mnt\//.test(absolute) && syncError?.code === 'EIO')) throw syncError;
|
|
1601
|
+
process.stderr.write(`lattice: fsync skipped (WSL2/NTFS EIO) ${absolute}\n`);
|
|
1602
|
+
}
|
|
1603
|
+
await handle.close(); handle = null;
|
|
1497
1604
|
await rename(temporary, absolute);
|
|
1498
1605
|
await fsyncDirectory(path.dirname(absolute));
|
|
1499
1606
|
} finally {
|
|
@@ -1549,23 +1656,20 @@ function processIsDead(pid) {
|
|
|
1549
1656
|
|
|
1550
1657
|
async function acquireCrossPlanRecoveryClaim(recoveryClaimsRef) {
|
|
1551
1658
|
await mkdir(recoveryClaimsRef, { recursive: true, mode: 0o700 });
|
|
1552
|
-
const
|
|
1659
|
+
const claimOrder = process.hrtime.bigint().toString().padStart(20, '0');
|
|
1660
|
+
const claimName = `${claimOrder}-${process.pid}-${randomBytes(8).toString('hex')}`;
|
|
1553
1661
|
const claimRef = path.join(recoveryClaimsRef, claimName);
|
|
1554
1662
|
await mkdir(claimRef, { mode: 0o700 });
|
|
1555
1663
|
for (const entry of await readdir(recoveryClaimsRef, { withFileTypes: true })) {
|
|
1556
1664
|
if (!entry.isDirectory() || entry.name === claimName) continue;
|
|
1557
|
-
const
|
|
1665
|
+
const parts = entry.name.split('-', 3);
|
|
1666
|
+
const pid = Number(parts.length === 2 ? parts[0] : parts[1]);
|
|
1558
1667
|
if (Number.isSafeInteger(pid) && pid > 0 && processIsDead(pid)) {
|
|
1559
1668
|
await rm(path.join(recoveryClaimsRef, entry.name), { recursive: true, force: true });
|
|
1560
1669
|
}
|
|
1561
1670
|
}
|
|
1562
|
-
const candidates =
|
|
1563
|
-
|
|
1564
|
-
mtimeNs: (await lstat(path.join(recoveryClaimsRef, name), { bigint: true })).mtimeNs,
|
|
1565
|
-
})));
|
|
1566
|
-
candidates.sort((left, right) => left.mtimeNs < right.mtimeNs ? -1
|
|
1567
|
-
: left.mtimeNs > right.mtimeNs ? 1 : left.name < right.name ? -1 : 1);
|
|
1568
|
-
if (candidates[0]?.name === claimName) return claimRef;
|
|
1671
|
+
const candidates = (await readdir(recoveryClaimsRef)).sort();
|
|
1672
|
+
if (candidates[0] === claimName) return claimRef;
|
|
1569
1673
|
await rmdir(claimRef);
|
|
1570
1674
|
return null;
|
|
1571
1675
|
}
|
|
@@ -1857,10 +1961,13 @@ export async function appendTodoEvent(options = {}) {
|
|
|
1857
1961
|
}
|
|
1858
1962
|
// Validate the prospective history, including hard evidence and transitions, before any write.
|
|
1859
1963
|
validateMergedTransition(store, member, event);
|
|
1964
|
+
const appendSourceCache = { commits: new Set(), blobs: new Map() };
|
|
1965
|
+
prefetchVerificationObjects(store.manifest, repoRoot,
|
|
1966
|
+
[...member.journal.events, event], appendSourceCache);
|
|
1860
1967
|
replay(member.plan, [...member.journal.events, event], {
|
|
1861
1968
|
now: options.now ? new Date(options.now) : new Date(),
|
|
1862
1969
|
verifyEvidence: evidenceVerifier(store.manifest, repoRoot, true),
|
|
1863
|
-
verifyImportSource: importSourceVerifier(repoRoot, true),
|
|
1970
|
+
verifyImportSource: importSourceVerifier(repoRoot, true, appendSourceCache),
|
|
1864
1971
|
});
|
|
1865
1972
|
const eventBytes = canonicalLine(event);
|
|
1866
1973
|
const activeRef = member.descriptor.journal_ref;
|
|
@@ -1879,7 +1986,7 @@ export async function appendTodoEvent(options = {}) {
|
|
|
1879
1986
|
const tasks = replay(member.plan, [...member.journal.events, event], {
|
|
1880
1987
|
now: options.now ? new Date(options.now) : new Date(),
|
|
1881
1988
|
verifyEvidence: evidenceVerifier(store.manifest, repoRoot, false),
|
|
1882
|
-
verifyImportSource: importSourceVerifier(repoRoot, false),
|
|
1989
|
+
verifyImportSource: importSourceVerifier(repoRoot, false, appendSourceCache),
|
|
1883
1990
|
});
|
|
1884
1991
|
const snapshot = snapshotFor(member.plan, [...member.journal.events, event], tasks);
|
|
1885
1992
|
await atomicWrite(path.resolve(repoRoot, member.descriptor.snapshot_ref), canonicalLine(snapshot));
|
|
@@ -2059,14 +2166,15 @@ function historicalImportInputs(value, timestampKey) {
|
|
|
2059
2166
|
}
|
|
2060
2167
|
|
|
2061
2168
|
function prepareImportedArtifacts(repoRoot, options, projectId, memberPlans) {
|
|
2169
|
+
const importSourceCache = { commits: new Set(), blobs: new Map() };
|
|
2062
2170
|
const planInput = materializeImportedNarrativeAnchors(
|
|
2063
|
-
repoRoot, options.plan, options.narrativeAnchorSources,
|
|
2171
|
+
repoRoot, options.plan, options.narrativeAnchorSources, importSourceCache,
|
|
2064
2172
|
);
|
|
2065
2173
|
const plan = buildTodoPlan(planInput);
|
|
2066
2174
|
if (plan.project_id !== projectId || plan.predecessor_plan_digest !== null) {
|
|
2067
2175
|
throw new TypeError('imported plan must be a project-local genesis plan');
|
|
2068
2176
|
}
|
|
2069
|
-
verifyPlanNarrativeAnchors(repoRoot, plan);
|
|
2177
|
+
verifyPlanNarrativeAnchors(repoRoot, plan, null, importSourceCache);
|
|
2070
2178
|
const genesis = buildPlanGenesis(plan, { ...options.genesis, historical_import: true });
|
|
2071
2179
|
const events = [genesis];
|
|
2072
2180
|
const inProgressTasks = options.inProgressTasks ?? [];
|
|
@@ -2084,7 +2192,7 @@ function prepareImportedArtifacts(repoRoot, options, projectId, memberPlans) {
|
|
|
2084
2192
|
}
|
|
2085
2193
|
for (const input of inProgressTasks) events.push(buildHistoricalStart(plan, events.at(-1), input, genesis));
|
|
2086
2194
|
for (const input of completedTasks) events.push(buildHistoricalDone(plan, events.at(-1), input, genesis));
|
|
2087
|
-
const verifyImportSource = importSourceVerifier(repoRoot, true);
|
|
2195
|
+
const verifyImportSource = importSourceVerifier(repoRoot, true, importSourceCache);
|
|
2088
2196
|
const tasks = replay(plan, events, {
|
|
2089
2197
|
now: options.now ? new Date(options.now) : new Date(), verifyImportSource,
|
|
2090
2198
|
});
|