@quolu/lattice 0.1.0 → 0.12.2
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.md +87 -4
- package/bin/lattice-bridge.mjs +87 -0
- package/bin/lattice-dashboard.mjs +70 -0
- package/bin/lattice-mcp.mjs +9 -9
- package/bin/lattice.mjs +61 -8
- package/docs/bridge-setup.md +87 -0
- package/docs/schemas/lattice.plan_create_input.v1.schema.json +56 -0
- package/docs/schemas/lattice.plan_create_input.v2.schema.json +72 -0
- package/docs/schemas/lattice.plan_create_input.v3.schema.json +81 -0
- package/package.json +19 -7
- package/sensor/dist/bin/command-supervision.d.ts +1 -1
- package/sensor/dist/bin/command-supervision.js +4 -4
- package/sensor/dist/bin/command-supervision.js.map +1 -1
- package/sensor/dist/bin/fatal-handler.js +2 -2
- package/sensor/dist/bin/fatal-handler.js.map +1 -1
- package/sensor/dist/bin/{codegraph.js → lattice-sensor.js} +207 -328
- package/sensor/dist/bin/node-version-check.d.ts +7 -5
- package/sensor/dist/bin/node-version-check.d.ts.map +1 -1
- package/sensor/dist/bin/node-version-check.js +19 -14
- package/sensor/dist/bin/node-version-check.js.map +1 -1
- package/sensor/dist/context/index.d.ts +1 -1
- package/sensor/dist/context/index.js +6 -6
- package/sensor/dist/context/index.js.map +1 -1
- package/sensor/dist/db/index.d.ts +5 -5
- package/sensor/dist/db/index.d.ts.map +1 -1
- package/sensor/dist/db/index.js +7 -7
- package/sensor/dist/db/index.js.map +1 -1
- package/sensor/dist/db/migrations.js +1 -1
- package/sensor/dist/db/migrations.js.map +1 -1
- package/sensor/dist/db/queries.d.ts +1 -1
- package/sensor/dist/db/queries.js +5 -5
- package/sensor/dist/db/queries.js.map +1 -1
- package/sensor/dist/db/schema.sql +2 -2
- package/sensor/dist/db/sqlite-adapter.d.ts +2 -2
- package/sensor/dist/db/sqlite-adapter.js +3 -3
- package/sensor/dist/db/sqlite-adapter.js.map +1 -1
- package/sensor/dist/db/wal-valve.d.ts +1 -1
- package/sensor/dist/db/wal-valve.d.ts.map +1 -1
- package/sensor/dist/db/wal-valve.js +2 -2
- package/sensor/dist/db/wal-valve.js.map +1 -1
- package/sensor/dist/directory.d.ts +27 -63
- package/sensor/dist/directory.d.ts.map +1 -1
- package/sensor/dist/directory.js +78 -134
- package/sensor/dist/directory.js.map +1 -1
- package/sensor/dist/errors.d.ts +15 -15
- package/sensor/dist/errors.d.ts.map +1 -1
- package/sensor/dist/errors.js +21 -21
- package/sensor/dist/errors.js.map +1 -1
- package/sensor/dist/extraction/dynamic-import.d.ts +1 -1
- package/sensor/dist/extraction/dynamic-import.js +1 -1
- package/sensor/dist/extraction/extraction-version.d.ts +1 -1
- package/sensor/dist/extraction/extraction-version.js +1 -1
- package/sensor/dist/extraction/generated-detection.d.ts +1 -1
- package/sensor/dist/extraction/generated-detection.js +1 -1
- package/sensor/dist/extraction/grammars.d.ts +3 -3
- package/sensor/dist/extraction/grammars.js +5 -5
- package/sensor/dist/extraction/grammars.js.map +1 -1
- package/sensor/dist/extraction/index.d.ts +6 -6
- package/sensor/dist/extraction/index.js +50 -50
- package/sensor/dist/extraction/index.js.map +1 -1
- package/sensor/dist/extraction/kernel/decode.d.ts +1 -1
- package/sensor/dist/extraction/kernel/decode.js +1 -1
- package/sensor/dist/extraction/kernel/index.d.ts +3 -3
- package/sensor/dist/extraction/kernel/index.js +6 -6
- package/sensor/dist/extraction/kernel/index.js.map +1 -1
- package/sensor/dist/extraction/kernel/layout.d.ts +1 -1
- package/sensor/dist/extraction/kernel/layout.js +1 -1
- package/sensor/dist/extraction/kernel/loader.d.ts +6 -6
- package/sensor/dist/extraction/kernel/loader.js +13 -13
- package/sensor/dist/extraction/kernel/loader.js.map +1 -1
- package/sensor/dist/extraction/languages/cfquery.d.ts +1 -1
- package/sensor/dist/extraction/languages/cfquery.js +1 -1
- package/sensor/dist/extraction/languages/lua.d.ts +11 -0
- package/sensor/dist/extraction/languages/lua.d.ts.map +1 -1
- package/sensor/dist/extraction/languages/lua.js +1 -0
- package/sensor/dist/extraction/languages/lua.js.map +1 -1
- package/sensor/dist/extraction/parse-pool.d.ts +5 -5
- package/sensor/dist/extraction/parse-pool.d.ts.map +1 -1
- package/sensor/dist/extraction/parse-pool.js +4 -4
- package/sensor/dist/extraction/parse-worker.js +1 -1
- package/sensor/dist/extraction/parse-worker.js.map +1 -1
- package/sensor/dist/extraction/store-writer.d.ts +1 -1
- package/sensor/dist/extraction/store-writer.js +1 -1
- package/sensor/dist/extraction/svelte-extractor.d.ts +1 -1
- package/sensor/dist/extraction/svelte-extractor.js +1 -1
- package/sensor/dist/extraction/tree-sitter-helpers.js +1 -1
- package/sensor/dist/extraction/tree-sitter.d.ts +2 -2
- package/sensor/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/sensor/dist/extraction/tree-sitter.js +71 -4
- package/sensor/dist/extraction/tree-sitter.js.map +1 -1
- package/sensor/dist/extraction/wasm-runtime-flags.d.ts +2 -2
- package/sensor/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
- package/sensor/dist/extraction/wasm-runtime-flags.js +5 -5
- package/sensor/dist/extraction/wasm-runtime-flags.js.map +1 -1
- package/sensor/dist/index.d.ts +36 -36
- package/sensor/dist/index.d.ts.map +1 -1
- package/sensor/dist/index.js +71 -71
- package/sensor/dist/index.js.map +1 -1
- package/sensor/dist/mcp/daemon-manager.js +1 -1
- package/sensor/dist/mcp/daemon-paths.d.ts +3 -3
- package/sensor/dist/mcp/daemon-paths.js +7 -7
- package/sensor/dist/mcp/daemon-paths.js.map +1 -1
- package/sensor/dist/mcp/daemon-registry.d.ts +2 -2
- package/sensor/dist/mcp/daemon-registry.js +5 -5
- package/sensor/dist/mcp/daemon.d.ts +7 -7
- package/sensor/dist/mcp/daemon.d.ts.map +1 -1
- package/sensor/dist/mcp/daemon.js +21 -21
- package/sensor/dist/mcp/daemon.js.map +1 -1
- package/sensor/dist/mcp/dynamic-boundaries.js +1 -1
- package/sensor/dist/mcp/engine.d.ts +9 -9
- package/sensor/dist/mcp/engine.d.ts.map +1 -1
- package/sensor/dist/mcp/engine.js +44 -44
- package/sensor/dist/mcp/engine.js.map +1 -1
- package/sensor/dist/mcp/index.d.ts +14 -14
- package/sensor/dist/mcp/index.d.ts.map +1 -1
- package/sensor/dist/mcp/index.js +41 -41
- package/sensor/dist/mcp/index.js.map +1 -1
- package/sensor/dist/mcp/liveness-watchdog.js +7 -7
- package/sensor/dist/mcp/liveness-watchdog.js.map +1 -1
- package/sensor/dist/mcp/ppid-watchdog.d.ts +3 -3
- package/sensor/dist/mcp/ppid-watchdog.js +2 -2
- package/sensor/dist/mcp/proxy.d.ts.map +1 -1
- package/sensor/dist/mcp/proxy.js +37 -37
- package/sensor/dist/mcp/proxy.js.map +1 -1
- package/sensor/dist/mcp/query-pool.d.ts +3 -3
- package/sensor/dist/mcp/query-pool.js +7 -7
- package/sensor/dist/mcp/query-pool.js.map +1 -1
- package/sensor/dist/mcp/query-worker.d.ts +2 -2
- package/sensor/dist/mcp/query-worker.js +7 -7
- package/sensor/dist/mcp/query-worker.js.map +1 -1
- package/sensor/dist/mcp/server-instructions.d.ts +10 -12
- package/sensor/dist/mcp/server-instructions.d.ts.map +1 -1
- package/sensor/dist/mcp/server-instructions.js +39 -37
- package/sensor/dist/mcp/server-instructions.js.map +1 -1
- package/sensor/dist/mcp/session.d.ts +2 -2
- package/sensor/dist/mcp/session.js +18 -18
- package/sensor/dist/mcp/session.js.map +1 -1
- package/sensor/dist/mcp/startup-handshake.d.ts +3 -3
- package/sensor/dist/mcp/startup-handshake.d.ts.map +1 -1
- package/sensor/dist/mcp/startup-handshake.js +3 -3
- package/sensor/dist/mcp/startup-handshake.js.map +1 -1
- package/sensor/dist/mcp/tools.d.ts +53 -53
- package/sensor/dist/mcp/tools.d.ts.map +1 -1
- package/sensor/dist/mcp/tools.js +180 -216
- package/sensor/dist/mcp/tools.js.map +1 -1
- package/sensor/dist/mcp/transport.d.ts +1 -1
- package/sensor/dist/mcp/transport.js +6 -6
- package/sensor/dist/mcp/transport.js.map +1 -1
- package/sensor/dist/mcp/version.d.ts +2 -2
- package/sensor/dist/mcp/version.d.ts.map +1 -1
- package/sensor/dist/mcp/version.js +3 -3
- package/sensor/dist/mcp/version.js.map +1 -1
- package/sensor/dist/project-config.d.ts +6 -6
- package/sensor/dist/project-config.d.ts.map +1 -1
- package/sensor/dist/project-config.js +10 -10
- package/sensor/dist/project-config.js.map +1 -1
- package/sensor/dist/resolution/callback-synthesizer.js +4 -4
- package/sensor/dist/resolution/callback-synthesizer.js.map +1 -1
- package/sensor/dist/resolution/frameworks/drupal.d.ts +3 -3
- package/sensor/dist/resolution/frameworks/drupal.js +3 -3
- package/sensor/dist/resolution/frameworks/fabric.js +1 -1
- package/sensor/dist/resolution/frameworks/fabric.js.map +1 -1
- package/sensor/dist/resolution/frameworks/java.js +1 -1
- package/sensor/dist/resolution/frameworks/java.js.map +1 -1
- package/sensor/dist/resolution/frameworks/svelte.js +1 -1
- package/sensor/dist/resolution/frameworks/svelte.js.map +1 -1
- package/sensor/dist/resolution/goframe-synthesizer.d.ts +1 -1
- package/sensor/dist/resolution/goframe-synthesizer.js +1 -1
- package/sensor/dist/resolution/index.d.ts +1 -1
- package/sensor/dist/resolution/index.js +9 -9
- package/sensor/dist/resolution/index.js.map +1 -1
- package/sensor/dist/resolution/name-matcher.js +2 -2
- package/sensor/dist/resolution/name-matcher.js.map +1 -1
- package/sensor/dist/resolution/resolver-pool.d.ts +2 -2
- package/sensor/dist/resolution/resolver-pool.js +4 -4
- package/sensor/dist/resolution/resolver-pool.js.map +1 -1
- package/sensor/dist/resolution/resolver-worker.js +2 -2
- package/sensor/dist/resolution/resolver-worker.js.map +1 -1
- package/sensor/dist/resolution/workspace-packages.js +1 -1
- package/sensor/dist/resolution/workspace-packages.js.map +1 -1
- package/sensor/dist/search/identifier-segments.d.ts +1 -1
- package/sensor/dist/search/identifier-segments.js +1 -1
- package/sensor/dist/search/query-utils.d.ts +1 -0
- package/sensor/dist/search/query-utils.d.ts.map +1 -1
- package/sensor/dist/search/query-utils.js +10 -3
- package/sensor/dist/search/query-utils.js.map +1 -1
- package/sensor/dist/sync/git-hooks.d.ts +9 -9
- package/sensor/dist/sync/git-hooks.d.ts.map +1 -1
- package/sensor/dist/sync/git-hooks.js +15 -15
- package/sensor/dist/sync/git-hooks.js.map +1 -1
- package/sensor/dist/sync/watch-policy.d.ts +2 -2
- package/sensor/dist/sync/watch-policy.js +5 -5
- package/sensor/dist/sync/watch-policy.js.map +1 -1
- package/sensor/dist/sync/watcher.d.ts +2 -2
- package/sensor/dist/sync/watcher.d.ts.map +1 -1
- package/sensor/dist/sync/watcher.js +16 -16
- package/sensor/dist/sync/watcher.js.map +1 -1
- package/sensor/dist/sync/worktree.d.ts +5 -5
- package/sensor/dist/sync/worktree.d.ts.map +1 -1
- package/sensor/dist/sync/worktree.js +9 -9
- package/sensor/dist/sync/worktree.js.map +1 -1
- package/sensor/dist/telemetry/index.d.ts +8 -8
- package/sensor/dist/telemetry/index.d.ts.map +1 -1
- package/sensor/dist/telemetry/index.js +16 -16
- package/sensor/dist/telemetry/index.js.map +1 -1
- package/sensor/dist/types.d.ts +2 -2
- package/sensor/dist/types.js +1 -1
- package/sensor/dist/ui/color.d.ts +2 -2
- package/sensor/dist/ui/color.js +2 -2
- package/sensor/dist/ui/glyphs.d.ts +3 -3
- package/sensor/dist/ui/glyphs.js +7 -7
- package/sensor/dist/ui/glyphs.js.map +1 -1
- package/sensor/dist/utils.d.ts +4 -4
- package/sensor/dist/utils.js +8 -8
- package/sensor/dist/utils.js.map +1 -1
- package/sensor/package.json +8 -13
- package/src/artifact-contracts.mjs +2 -2
- package/src/boundary-compiler.mjs +28 -28
- package/src/boundary-observation-compiler-v2.mjs +10 -10
- package/src/bounded-seam.mjs +144 -0
- package/src/bridge-cli.mjs +227 -0
- package/src/bridge-config.mjs +346 -0
- package/src/bridge-daemon.mjs +328 -0
- package/src/bridge-launch-agent.mjs +311 -0
- package/src/bridge-server.mjs +309 -0
- package/src/cli-help.mjs +163 -0
- package/src/control-compiler.mjs +30 -30
- package/src/dag-chain.mjs +261 -0
- package/src/factory-diagnostics.mjs +8 -7
- package/src/hash-chain.mjs +76 -0
- package/src/node-version-guard.mjs +5 -4
- package/src/project-cli.mjs +392 -0
- package/src/project-identity.mjs +71 -0
- package/src/rc1-evidence-bundle.mjs +16 -16
- package/src/rc1-v4-campaign.mjs +15 -14
- package/src/rc1-v5-artifact-set.mjs +2 -2
- package/src/rc1-v5-campaign.mjs +15 -14
- package/src/rc1-v6-artifact-set.mjs +18 -18
- package/src/rc1-v6-campaign.mjs +16 -16
- package/src/rc1-v6-causal-binding.mjs +16 -14
- package/src/rc1-v6-measurement.mjs +20 -38
- package/src/rc2-artifact-set.mjs +45 -40
- package/src/rc2-campaign.mjs +48 -66
- package/src/rc2-delivery-policy-front-end.mjs +30 -30
- package/src/rc2-rc1-transfer-front-end.mjs +5 -5
- package/src/rc3-actual-dogfood.mjs +6 -6
- package/src/rc3-dogfood-scaffold.mjs +12 -7
- package/src/rc3-scripted-campaign.mjs +75 -21
- package/src/rc4-stage1-dogfood.mjs +24 -7
- package/src/runtime-cli.mjs +2424 -45
- package/src/runtime-contracts.mjs +7 -7
- package/src/runtime-control-store.mjs +547 -0
- package/src/runtime-controller-protocol.mjs +517 -0
- package/src/runtime-direct-os-observer.mjs +284 -0
- package/src/runtime-engine.mjs +2 -2
- package/src/runtime-errors.mjs +1 -1
- package/src/runtime-event-store.mjs +15 -43
- package/src/runtime-front-end.mjs +26 -26
- package/src/runtime-gate-store.mjs +477 -0
- package/src/runtime-hold-recompile.mjs +214 -23
- package/src/runtime-lifecycle-lock.mjs +290 -0
- package/src/runtime-managed-supervisor.mjs +1276 -0
- package/src/runtime-multi-epoch-store.mjs +761 -0
- package/src/runtime-projection.mjs +62 -0
- package/src/seam-transform.mjs +38 -38
- package/src/{codegraph-adapter.mjs → sensor-adapter.mjs} +7 -7
- package/src/sensor-cli.mjs +107 -0
- package/src/sensor-runtime.mjs +48 -0
- package/src/timestamp-contract.mjs +8 -0
- package/src/todo-chain.mjs +178 -0
- package/src/todo-cli.mjs +1032 -0
- package/src/todo-contracts.mjs +501 -0
- package/src/todo-dashboard-registry.mjs +318 -0
- package/src/todo-gantt-html.mjs +416 -0
- package/src/todo-gantt-layout.mjs +603 -0
- package/src/todo-gantt-live.mjs +269 -0
- package/src/todo-gantt-presentation.mjs +217 -0
- package/src/todo-gantt-svg.mjs +249 -0
- package/src/todo-markdown-renderer.mjs +260 -0
- package/src/todo-migration.mjs +260 -0
- package/src/todo-narrative-anchor.mjs +130 -0
- package/src/todo-revision.mjs +425 -0
- package/src/todo-status.mjs +340 -0
- package/src/todo-store.mjs +3451 -0
- package/src/treatment-compiler.mjs +28 -28
- package/src/treatment-runner.mjs +42 -44
- package/sensor/dist/bin/codegraph.d.ts +0 -26
- package/sensor/dist/bin/codegraph.d.ts.map +0 -1
- package/sensor/dist/bin/codegraph.js.map +0 -1
- package/sensor/dist/bin/uninstall.d.ts +0 -14
- package/sensor/dist/bin/uninstall.d.ts.map +0 -1
- package/sensor/dist/bin/uninstall.js +0 -36
- package/sensor/dist/bin/uninstall.js.map +0 -1
- package/sensor/dist/installer/beta-signup.d.ts +0 -54
- package/sensor/dist/installer/beta-signup.d.ts.map +0 -1
- package/sensor/dist/installer/beta-signup.js +0 -178
- package/sensor/dist/installer/beta-signup.js.map +0 -1
- package/sensor/dist/installer/config-writer.d.ts +0 -28
- package/sensor/dist/installer/config-writer.d.ts.map +0 -1
- package/sensor/dist/installer/config-writer.js +0 -91
- package/sensor/dist/installer/config-writer.js.map +0 -1
- package/sensor/dist/installer/index.d.ts +0 -142
- package/sensor/dist/installer/index.d.ts.map +0 -1
- package/sensor/dist/installer/index.js +0 -622
- package/sensor/dist/installer/index.js.map +0 -1
- package/sensor/dist/installer/instructions-template.d.ts +0 -41
- package/sensor/dist/installer/instructions-template.d.ts.map +0 -1
- package/sensor/dist/installer/instructions-template.js +0 -53
- package/sensor/dist/installer/instructions-template.js.map +0 -1
- package/sensor/dist/installer/targets/antigravity.d.ts +0 -57
- package/sensor/dist/installer/targets/antigravity.d.ts.map +0 -1
- package/sensor/dist/installer/targets/antigravity.js +0 -308
- package/sensor/dist/installer/targets/antigravity.js.map +0 -1
- package/sensor/dist/installer/targets/claude.d.ts +0 -62
- package/sensor/dist/installer/targets/claude.d.ts.map +0 -1
- package/sensor/dist/installer/targets/claude.js +0 -454
- package/sensor/dist/installer/targets/claude.js.map +0 -1
- package/sensor/dist/installer/targets/codex.d.ts +0 -18
- package/sensor/dist/installer/targets/codex.d.ts.map +0 -1
- package/sensor/dist/installer/targets/codex.js +0 -185
- package/sensor/dist/installer/targets/codex.js.map +0 -1
- package/sensor/dist/installer/targets/cursor.d.ts +0 -35
- package/sensor/dist/installer/targets/cursor.d.ts.map +0 -1
- package/sensor/dist/installer/targets/cursor.js +0 -254
- package/sensor/dist/installer/targets/cursor.js.map +0 -1
- package/sensor/dist/installer/targets/gemini.d.ts +0 -26
- package/sensor/dist/installer/targets/gemini.d.ts.map +0 -1
- package/sensor/dist/installer/targets/gemini.js +0 -165
- package/sensor/dist/installer/targets/gemini.js.map +0 -1
- package/sensor/dist/installer/targets/hermes.d.ts +0 -18
- package/sensor/dist/installer/targets/hermes.d.ts.map +0 -1
- package/sensor/dist/installer/targets/hermes.js +0 -359
- package/sensor/dist/installer/targets/hermes.js.map +0 -1
- package/sensor/dist/installer/targets/kiro.d.ts +0 -27
- package/sensor/dist/installer/targets/kiro.d.ts.map +0 -1
- package/sensor/dist/installer/targets/kiro.js +0 -178
- package/sensor/dist/installer/targets/kiro.js.map +0 -1
- package/sensor/dist/installer/targets/opencode.d.ts +0 -38
- package/sensor/dist/installer/targets/opencode.d.ts.map +0 -1
- package/sensor/dist/installer/targets/opencode.js +0 -288
- package/sensor/dist/installer/targets/opencode.js.map +0 -1
- package/sensor/dist/installer/targets/registry.d.ts +0 -35
- package/sensor/dist/installer/targets/registry.d.ts.map +0 -1
- package/sensor/dist/installer/targets/registry.js +0 -91
- package/sensor/dist/installer/targets/registry.js.map +0 -1
- package/sensor/dist/installer/targets/shared.d.ts +0 -101
- package/sensor/dist/installer/targets/shared.d.ts.map +0 -1
- package/sensor/dist/installer/targets/shared.js +0 -264
- package/sensor/dist/installer/targets/shared.js.map +0 -1
- package/sensor/dist/installer/targets/toml.d.ts +0 -52
- package/sensor/dist/installer/targets/toml.d.ts.map +0 -1
- package/sensor/dist/installer/targets/toml.js +0 -147
- package/sensor/dist/installer/targets/toml.js.map +0 -1
- package/sensor/dist/installer/targets/types.d.ts +0 -108
- package/sensor/dist/installer/targets/types.d.ts.map +0 -1
- package/sensor/dist/installer/targets/types.js +0 -16
- package/sensor/dist/installer/targets/types.js.map +0 -1
- package/sensor/dist/upgrade/index.d.ts +0 -171
- package/sensor/dist/upgrade/index.d.ts.map +0 -1
- package/sensor/dist/upgrade/index.js +0 -659
- package/sensor/dist/upgrade/index.js.map +0 -1
- package/sensor/dist/upgrade/remove-binary.d.ts +0 -87
- package/sensor/dist/upgrade/remove-binary.d.ts.map +0 -1
- package/sensor/dist/upgrade/remove-binary.js +0 -289
- package/sensor/dist/upgrade/remove-binary.js.map +0 -1
- package/sensor/dist/upgrade/update-check.d.ts +0 -92
- package/sensor/dist/upgrade/update-check.d.ts.map +0 -1
- package/sensor/dist/upgrade/update-check.js +0 -258
- package/sensor/dist/upgrade/update-check.js.map +0 -1
- package/src/bootstrap.mjs +0 -88
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import {
|
|
5
|
+
mkdir, open, readFile, realpath, rename, rm, writeFile,
|
|
6
|
+
} from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
const REGISTRY_SCHEMA = 'lattice.todo_dashboard_registry.v1';
|
|
10
|
+
const DAEMON_SCHEMA = 'lattice.todo_dashboard_daemon.v1';
|
|
11
|
+
const DEFAULT_PORT = 0;
|
|
12
|
+
export const TODO_DASHBOARD_STALE_MS = 2 * 60 * 60 * 1_000;
|
|
13
|
+
const LOCK_ATTEMPTS = 240;
|
|
14
|
+
const LOCK_WAIT_MS = 25;
|
|
15
|
+
const LOCK_STALE_MS = 30_000;
|
|
16
|
+
|
|
17
|
+
function identifier(value) {
|
|
18
|
+
return typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function runtimeDir(env) {
|
|
22
|
+
const configured = env.LATTICE_DASHBOARD_RUNTIME_DIR;
|
|
23
|
+
return typeof configured === 'string' && path.isAbsolute(configured)
|
|
24
|
+
? configured : path.join(homedir(), '.lattice', 'dashboard');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function paths(env) {
|
|
28
|
+
const root = runtimeDir(env);
|
|
29
|
+
return {
|
|
30
|
+
root,
|
|
31
|
+
registry: path.join(root, 'projects.json'),
|
|
32
|
+
descriptor: path.join(root, 'daemon.json'),
|
|
33
|
+
lock: path.join(root, 'registry.lock'),
|
|
34
|
+
startupLock: path.join(root, 'daemon-start.lock'),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function validEntry(entry) {
|
|
39
|
+
return entry !== null && typeof entry === 'object' && !Array.isArray(entry)
|
|
40
|
+
&& Object.keys(entry).sort().join(',') === 'display_name,last_seen_at,project_id,repo_root,session_id'
|
|
41
|
+
&& identifier(entry.project_id) && identifier(entry.session_id)
|
|
42
|
+
&& typeof entry.display_name === 'string' && entry.display_name.length > 0
|
|
43
|
+
&& typeof entry.repo_root === 'string' && path.isAbsolute(entry.repo_root)
|
|
44
|
+
&& Number.isFinite(Date.parse(entry.last_seen_at));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function validateRegistry(value) {
|
|
48
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)
|
|
49
|
+
|| value.schema !== REGISTRY_SCHEMA || !Array.isArray(value.projects)
|
|
50
|
+
|| !value.projects.every(validEntry)) {
|
|
51
|
+
const error = new Error('dashboard registry schema invalid');
|
|
52
|
+
error.code = 'DASHBOARD_REGISTRY_INVALID';
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
const ids = new Set();
|
|
56
|
+
for (const entry of value.projects) {
|
|
57
|
+
if (ids.has(entry.project_id)) {
|
|
58
|
+
const error = new Error(`dashboard project duplicate: ${entry.project_id}`);
|
|
59
|
+
error.code = 'DASHBOARD_REGISTRY_INVALID';
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
ids.add(entry.project_id);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function readJson(ref, missing) {
|
|
68
|
+
let bytes;
|
|
69
|
+
try { bytes = await readFile(ref, 'utf8'); } catch (error) {
|
|
70
|
+
if (error?.code === 'ENOENT') return missing;
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
try { return JSON.parse(bytes); } catch {
|
|
74
|
+
const error = new Error(`invalid JSON: ${ref}`);
|
|
75
|
+
error.code = 'DASHBOARD_REGISTRY_INVALID';
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function atomicJson(ref, value) {
|
|
81
|
+
const temporary = `${ref}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
|
|
82
|
+
await writeFile(temporary, `${JSON.stringify(value)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
83
|
+
await rename(temporary, ref);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function withLock(lockRef, action) {
|
|
87
|
+
for (let attempt = 0; attempt < LOCK_ATTEMPTS; attempt += 1) {
|
|
88
|
+
let handle;
|
|
89
|
+
try {
|
|
90
|
+
handle = await open(lockRef, 'wx', 0o600);
|
|
91
|
+
await handle.writeFile(`${JSON.stringify({ pid: process.pid, created_at: new Date().toISOString() })}\n`);
|
|
92
|
+
try { return await action(); } finally {
|
|
93
|
+
await handle.close();
|
|
94
|
+
await rm(lockRef, { force: true });
|
|
95
|
+
}
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
98
|
+
try {
|
|
99
|
+
const lock = JSON.parse(await readFile(lockRef, 'utf8'));
|
|
100
|
+
let alive = Number.isSafeInteger(lock.pid) && lock.pid > 0;
|
|
101
|
+
if (alive) {
|
|
102
|
+
try { process.kill(lock.pid, 0); } catch { alive = false; }
|
|
103
|
+
}
|
|
104
|
+
if (!alive || Date.now() - Date.parse(lock.created_at) > LOCK_STALE_MS) {
|
|
105
|
+
await rm(lockRef, { force: true });
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
} catch (lockError) {
|
|
109
|
+
if (lockError?.code === 'ENOENT') continue;
|
|
110
|
+
}
|
|
111
|
+
await new Promise((resolve) => setTimeout(resolve, LOCK_WAIT_MS));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const error = new Error('dashboard registry lock timeout');
|
|
115
|
+
error.code = 'DASHBOARD_REGISTRY_BUSY';
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function readActiveTodoDashboardProjects({ env = process.env, now = Date.now() } = {}) {
|
|
120
|
+
const ref = paths(env).registry;
|
|
121
|
+
const document = validateRegistry(await readJson(ref, { schema: REGISTRY_SCHEMA, projects: [] }));
|
|
122
|
+
return document.projects.filter((entry) => now - Date.parse(entry.last_seen_at) <= TODO_DASHBOARD_STALE_MS)
|
|
123
|
+
.sort((left, right) => left.display_name.localeCompare(right.display_name, 'ja')
|
|
124
|
+
|| left.project_id.localeCompare(right.project_id, 'en'));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function registerTodoDashboardActivity({
|
|
128
|
+
repoRoot, projectId, displayName = projectId, sessionId, env = process.env,
|
|
129
|
+
now = new Date(),
|
|
130
|
+
}) {
|
|
131
|
+
if (!path.isAbsolute(repoRoot) || !identifier(projectId) || !identifier(sessionId)
|
|
132
|
+
|| typeof displayName !== 'string' || displayName.length === 0 || displayName !== displayName.trim()
|
|
133
|
+
|| !(now instanceof Date) || !Number.isFinite(now.getTime())) {
|
|
134
|
+
throw new TypeError('dashboard activity is invalid');
|
|
135
|
+
}
|
|
136
|
+
const canonicalRoot = await realpath(repoRoot);
|
|
137
|
+
const refs = paths(env);
|
|
138
|
+
await mkdir(refs.root, { recursive: true, mode: 0o700 });
|
|
139
|
+
await withLock(refs.lock, async () => {
|
|
140
|
+
const current = validateRegistry(await readJson(refs.registry, { schema: REGISTRY_SCHEMA, projects: [] }));
|
|
141
|
+
const cutoff = now.getTime() - TODO_DASHBOARD_STALE_MS;
|
|
142
|
+
const projects = current.projects.filter((entry) => Date.parse(entry.last_seen_at) >= cutoff
|
|
143
|
+
&& entry.project_id !== projectId);
|
|
144
|
+
projects.push({ project_id: projectId, display_name: displayName, repo_root: canonicalRoot,
|
|
145
|
+
session_id: sessionId, last_seen_at: now.toISOString() });
|
|
146
|
+
projects.sort((left, right) => left.project_id.localeCompare(right.project_id, 'en'));
|
|
147
|
+
await atomicJson(refs.registry, { schema: REGISTRY_SCHEMA, projects });
|
|
148
|
+
});
|
|
149
|
+
return { projectId, displayName, repoRoot: canonicalRoot, sessionId };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function validDaemonDescriptor(descriptor) {
|
|
153
|
+
return descriptor !== null && typeof descriptor === 'object' && !Array.isArray(descriptor)
|
|
154
|
+
&& Object.keys(descriptor).sort().join(',') === 'pid,port,schema,started_at'
|
|
155
|
+
&& descriptor.schema === DAEMON_SCHEMA
|
|
156
|
+
&& Number.isSafeInteger(descriptor.pid) && descriptor.pid > 0
|
|
157
|
+
&& Number.isSafeInteger(descriptor.port) && descriptor.port > 0 && descriptor.port <= 65_535
|
|
158
|
+
&& typeof descriptor.started_at === 'string' && Number.isFinite(Date.parse(descriptor.started_at));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function daemonAttestation(descriptor) {
|
|
162
|
+
if (!validDaemonDescriptor(descriptor)) return null;
|
|
163
|
+
try {
|
|
164
|
+
const response = await fetch(`http://127.0.0.1:${descriptor.port}/__lattice/health`, {
|
|
165
|
+
signal: AbortSignal.timeout(500),
|
|
166
|
+
});
|
|
167
|
+
if (response.status !== 200) return null;
|
|
168
|
+
const body = await response.json();
|
|
169
|
+
if (body?.schema !== 'lattice.todo_dashboard_health.v1' || body.pid !== descriptor.pid
|
|
170
|
+
|| !Array.isArray(body.project_ids)) return null;
|
|
171
|
+
const keys = Object.keys(body).sort().join(',');
|
|
172
|
+
if (keys === 'pid,port,project_ids,schema' && body.port === descriptor.port) return 'current';
|
|
173
|
+
if (keys === 'pid,project_ids,schema') return 'legacy';
|
|
174
|
+
return null;
|
|
175
|
+
} catch { return null; }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function daemonHealthy(descriptor) {
|
|
179
|
+
return await daemonAttestation(descriptor) === 'current';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function processIsAlive(pid) {
|
|
183
|
+
try { process.kill(pid, 0); return true; } catch (error) {
|
|
184
|
+
if (error?.code === 'ESRCH') return false;
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function stopAttestedLegacyDaemon(descriptor, {
|
|
190
|
+
signalProcess = process.kill, isProcessAlive = processIsAlive, timeoutMs = 3_000,
|
|
191
|
+
} = {}) {
|
|
192
|
+
const attestation = await daemonAttestation(descriptor);
|
|
193
|
+
if (attestation !== 'legacy') {
|
|
194
|
+
if (!await isProcessAlive(descriptor.pid) && attestation === null) return;
|
|
195
|
+
const error = new Error('legacy dashboard daemon attestation was lost before signal');
|
|
196
|
+
error.code = 'DASHBOARD_LEGACY_ATTESTATION_LOST';
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
signalProcess(descriptor.pid, 'SIGTERM');
|
|
200
|
+
const deadline = Date.now() + timeoutMs;
|
|
201
|
+
while (Date.now() < deadline) {
|
|
202
|
+
const alive = await isProcessAlive(descriptor.pid);
|
|
203
|
+
const stillServing = await daemonAttestation(descriptor) !== null;
|
|
204
|
+
if (!alive && !stillServing) return;
|
|
205
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
206
|
+
}
|
|
207
|
+
const error = new Error('legacy dashboard daemon did not stop');
|
|
208
|
+
error.code = 'DASHBOARD_LEGACY_STOP_FAILED';
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function stopSpawnedReplacement(child, descriptor, {
|
|
213
|
+
isProcessAlive = processIsAlive, timeoutMs = 3_000,
|
|
214
|
+
} = {}) {
|
|
215
|
+
const stopped = async () => {
|
|
216
|
+
const exited = child.exitCode !== null || child.signalCode !== null
|
|
217
|
+
|| !await isProcessAlive(child.pid);
|
|
218
|
+
return exited && await daemonAttestation(descriptor) === null;
|
|
219
|
+
};
|
|
220
|
+
child.kill('SIGTERM');
|
|
221
|
+
let deadline = Date.now() + Math.ceil(timeoutMs / 2);
|
|
222
|
+
while (Date.now() < deadline) {
|
|
223
|
+
if (await stopped()) return;
|
|
224
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
225
|
+
}
|
|
226
|
+
child.kill('SIGKILL');
|
|
227
|
+
deadline = Date.now() + Math.floor(timeoutMs / 2);
|
|
228
|
+
while (Date.now() < deadline) {
|
|
229
|
+
if (await stopped()) return;
|
|
230
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
231
|
+
}
|
|
232
|
+
const error = new Error('replacement dashboard daemon rollback did not stop');
|
|
233
|
+
error.code = 'DASHBOARD_REPLACEMENT_ROLLBACK_FAILED';
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export async function writeTodoDashboardDaemonDescriptor({ port, env = process.env }) {
|
|
238
|
+
if (!Number.isSafeInteger(port) || port <= 0 || port > 65_535) throw new TypeError('daemon port invalid');
|
|
239
|
+
const refs = paths(env);
|
|
240
|
+
await mkdir(refs.root, { recursive: true, mode: 0o700 });
|
|
241
|
+
await atomicJson(refs.descriptor, { schema: DAEMON_SCHEMA, pid: process.pid, port,
|
|
242
|
+
started_at: new Date().toISOString() });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function ensureTodoDashboardDaemon({ env = process.env, spawnDaemon = spawn,
|
|
246
|
+
signalProcess = process.kill, isProcessAlive = processIsAlive, startupTimeoutMs = 4_000,
|
|
247
|
+
legacyStopTimeoutMs = 3_000, replacementIsProcessAlive = processIsAlive } = {}) {
|
|
248
|
+
const refs = paths(env);
|
|
249
|
+
await mkdir(refs.root, { recursive: true, mode: 0o700 });
|
|
250
|
+
return withLock(refs.startupLock, async () => {
|
|
251
|
+
const existing = await readJson(refs.descriptor, null);
|
|
252
|
+
const existingAttestation = await daemonAttestation(existing);
|
|
253
|
+
if (existingAttestation === 'current') return existing;
|
|
254
|
+
const legacy = existingAttestation === 'legacy' ? existing : null;
|
|
255
|
+
const portText = env.LATTICE_DASHBOARD_PORT;
|
|
256
|
+
const configuredPort = typeof portText === 'string' && /^(?:0|[1-9][0-9]{0,4})$/u.test(portText)
|
|
257
|
+
&& Number(portText) <= 65_535 ? Number(portText) : DEFAULT_PORT;
|
|
258
|
+
const port = legacy === null ? configuredPort : 0;
|
|
259
|
+
if (legacy === null) await rm(refs.descriptor, { force: true });
|
|
260
|
+
const child = spawnDaemon(process.execPath, [path.resolve(import.meta.dirname, '../bin/lattice-dashboard.mjs')], {
|
|
261
|
+
detached: true,
|
|
262
|
+
stdio: 'ignore',
|
|
263
|
+
env: { ...env, LATTICE_DASHBOARD_RUNTIME_DIR: refs.root, LATTICE_DASHBOARD_PORT: String(port) },
|
|
264
|
+
});
|
|
265
|
+
child.unref();
|
|
266
|
+
const deadline = Date.now() + startupTimeoutMs;
|
|
267
|
+
while (Date.now() < deadline) {
|
|
268
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
269
|
+
const descriptor = await readJson(refs.descriptor, null);
|
|
270
|
+
if (await daemonHealthy(descriptor) && descriptor.pid !== legacy?.pid) {
|
|
271
|
+
if (legacy !== null) {
|
|
272
|
+
try {
|
|
273
|
+
await stopAttestedLegacyDaemon(legacy, {
|
|
274
|
+
signalProcess, isProcessAlive, timeoutMs: legacyStopTimeoutMs,
|
|
275
|
+
});
|
|
276
|
+
} catch (error) {
|
|
277
|
+
await stopSpawnedReplacement(child, descriptor, { isProcessAlive: replacementIsProcessAlive });
|
|
278
|
+
await atomicJson(refs.descriptor, legacy);
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return descriptor;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
child.kill?.('SIGTERM');
|
|
286
|
+
if (legacy !== null) await atomicJson(refs.descriptor, legacy);
|
|
287
|
+
else await rm(refs.descriptor, { force: true });
|
|
288
|
+
const error = new Error('dashboard daemon did not become ready');
|
|
289
|
+
error.code = 'DASHBOARD_DAEMON_UNAVAILABLE';
|
|
290
|
+
throw error;
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export async function ensureTodoDashboardActivity(options) {
|
|
295
|
+
const registered = await registerTodoDashboardActivity(options);
|
|
296
|
+
const daemon = await ensureTodoDashboardDaemon({ env: options.env });
|
|
297
|
+
const deadline = Date.now() + 2_500;
|
|
298
|
+
let visible = false;
|
|
299
|
+
while (!visible && Date.now() < deadline) {
|
|
300
|
+
try {
|
|
301
|
+
const response = await fetch(`http://127.0.0.1:${daemon.port}/__lattice/health`, {
|
|
302
|
+
signal: AbortSignal.timeout(500),
|
|
303
|
+
});
|
|
304
|
+
const health = response.status === 200 ? await response.json() : null;
|
|
305
|
+
visible = health?.schema === 'lattice.todo_dashboard_health.v1'
|
|
306
|
+
&& health.port === daemon.port
|
|
307
|
+
&& health.project_ids?.includes(registered.projectId);
|
|
308
|
+
} catch { visible = false; }
|
|
309
|
+
if (!visible) await new Promise((resolve) => setTimeout(resolve, 50));
|
|
310
|
+
}
|
|
311
|
+
if (!visible) {
|
|
312
|
+
const error = new Error('registered project did not become visible');
|
|
313
|
+
error.code = 'DASHBOARD_PROJECT_UNAVAILABLE';
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
return { ...registered, host: '127.0.0.1', port: daemon.port,
|
|
317
|
+
url: `http://127.0.0.1:${daemon.port}/projects/${encodeURIComponent(registered.projectId)}/` };
|
|
318
|
+
}
|