@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
|
@@ -178,3 +178,65 @@ export function projectRuntimeState(options = {}) {
|
|
|
178
178
|
closed,
|
|
179
179
|
};
|
|
180
180
|
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* managed runの一時状態をevent prefixだけから表示用に投影する。
|
|
184
|
+
*
|
|
185
|
+
* TODO本体の状態機械とは独立したoverlayであり、hold/carry-over/redispatchを
|
|
186
|
+
* 相互排他的に保つ。receipt accepted後は運用上の一時状態ではなくなるため
|
|
187
|
+
* overlayから除く。intake_frozenはevent prefix上の論理状態だけを表し、中央
|
|
188
|
+
* write gateまで含むmanaged runtimeの実効freeze判定は所有しない。
|
|
189
|
+
*/
|
|
190
|
+
export function projectRuntimeStatusOverlays(options = {}) {
|
|
191
|
+
if (options === null || typeof options !== 'object' || Array.isArray(options)
|
|
192
|
+
|| !Array.isArray(options.events)) {
|
|
193
|
+
invalidProjection('eventsの配列が必要');
|
|
194
|
+
}
|
|
195
|
+
const { events } = options;
|
|
196
|
+
// sequence順、未知event、subject等の既存projection契約を先に検証する。
|
|
197
|
+
const runtimeState = projectRuntimeState({ events });
|
|
198
|
+
const overlays = new Map();
|
|
199
|
+
|
|
200
|
+
const setOverlay = (todoId, state) => {
|
|
201
|
+
if (typeof todoId !== 'string' || todoId.length === 0) {
|
|
202
|
+
invalidProjection(`${state} overlayのtodo idが不正`);
|
|
203
|
+
}
|
|
204
|
+
overlays.set(todoId, state);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
for (const event of events) {
|
|
208
|
+
const todoRef = event.subject?.kind === 'todo' ? event.subject.ref : null;
|
|
209
|
+
switch (event.kind) {
|
|
210
|
+
case 'hold_decided':
|
|
211
|
+
for (const todoId of event.payload?.hold_set ?? []) setOverlay(todoId, 'held');
|
|
212
|
+
for (const todoId of event.payload?.continue_set ?? []) setOverlay(todoId, 'carry_over');
|
|
213
|
+
break;
|
|
214
|
+
case 'carry_over_witnessed':
|
|
215
|
+
case 'epoch_rebound':
|
|
216
|
+
if (todoRef === null) invalidProjection(`${event.kind}はtodo subjectが必要`);
|
|
217
|
+
setOverlay(todoRef, 'carry_over');
|
|
218
|
+
break;
|
|
219
|
+
case 'context_invalidated':
|
|
220
|
+
if (todoRef === null) invalidProjection('context_invalidatedはtodo subjectが必要');
|
|
221
|
+
if (event.payload?.reauthorized_via === 'epoch_rebind') setOverlay(todoRef, 'carry_over');
|
|
222
|
+
else if (event.payload?.reauthorized_via === 'redispatch') setOverlay(todoRef, 'redispatch');
|
|
223
|
+
break;
|
|
224
|
+
case 'receipt_accepted':
|
|
225
|
+
if (todoRef === null) invalidProjection('receipt_acceptedはtodo subjectが必要');
|
|
226
|
+
overlays.delete(todoRef);
|
|
227
|
+
break;
|
|
228
|
+
default:
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const members = (state) => sortedArray(
|
|
234
|
+
[...overlays.entries()].filter(([, value]) => value === state).map(([todoId]) => todoId),
|
|
235
|
+
);
|
|
236
|
+
return {
|
|
237
|
+
held: members('held'),
|
|
238
|
+
carry_over: members('carry_over'),
|
|
239
|
+
redispatch: members('redispatch'),
|
|
240
|
+
intake_frozen: runtimeState.freeze !== null,
|
|
241
|
+
};
|
|
242
|
+
}
|
package/src/seam-transform.mjs
CHANGED
|
@@ -11,10 +11,11 @@ import {
|
|
|
11
11
|
validateTransformArtifact,
|
|
12
12
|
} from './artifact-contracts.mjs';
|
|
13
13
|
import { runIsolatedTransform } from './isolation-runner.mjs';
|
|
14
|
+
import { isCanonicalUtcTimestamp } from './timestamp-contract.mjs';
|
|
14
15
|
|
|
15
16
|
const SHA256 = /^[0-9a-f]{64}$/;
|
|
16
17
|
const SHA1 = /^[0-9a-f]{40}$/;
|
|
17
|
-
const
|
|
18
|
+
const PORTABLE_SENSOR_PROJECTION = 'lattice.sensor_portable_outcome.v1';
|
|
18
19
|
const CANDIDATE_ID = 'extract-dispatch-policies';
|
|
19
20
|
const FIXTURE_ENTRY = 'research/fixtures/dispatch-record/src/dispatch-record.mjs';
|
|
20
21
|
const CHANNEL_PATH = 'research/fixtures/dispatch-record/src/dispatch-channel.mjs';
|
|
@@ -139,8 +140,8 @@ function portableOutcomeRecord(value) {
|
|
|
139
140
|
&& SHA256.test(value.result_digest);
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
function
|
|
143
|
-
if (!exactRecord(
|
|
143
|
+
function assertPortableLatticeSensorEvidence(sensor, { querySet, graphEvidence }) {
|
|
144
|
+
if (!exactRecord(sensor, [
|
|
144
145
|
'version',
|
|
145
146
|
'file_count',
|
|
146
147
|
'node_count',
|
|
@@ -155,52 +156,52 @@ function assertPortableCodegraphEvidence(codegraph, { querySet, graphEvidence })
|
|
|
155
156
|
'portable_outcomes_equal',
|
|
156
157
|
'outcomes',
|
|
157
158
|
])
|
|
158
|
-
|| typeof
|
|
159
|
-
||
|
|
160
|
-
|| !Number.isSafeInteger(
|
|
161
|
-
||
|
|
162
|
-
|| !Number.isSafeInteger(
|
|
163
|
-
||
|
|
164
|
-
|| !Number.isSafeInteger(
|
|
165
|
-
||
|
|
166
|
-
|| !exactRecord(
|
|
167
|
-
|| Object.values(
|
|
168
|
-
||
|
|
169
|
-
||
|
|
170
|
-
||
|
|
171
|
-
|| !Array.isArray(
|
|
172
|
-
||
|
|
173
|
-
||
|
|
159
|
+
|| typeof sensor.version !== 'string'
|
|
160
|
+
|| sensor.version.length === 0
|
|
161
|
+
|| !Number.isSafeInteger(sensor.file_count)
|
|
162
|
+
|| sensor.file_count <= 0
|
|
163
|
+
|| !Number.isSafeInteger(sensor.node_count)
|
|
164
|
+
|| sensor.node_count <= 0
|
|
165
|
+
|| !Number.isSafeInteger(sensor.edge_count)
|
|
166
|
+
|| sensor.edge_count <= 0
|
|
167
|
+
|| !exactRecord(sensor.pending_changes, ['added', 'modified', 'removed'])
|
|
168
|
+
|| Object.values(sensor.pending_changes).some((count) => count !== 0)
|
|
169
|
+
|| sensor.pending_refs !== 0
|
|
170
|
+
|| sensor.worktree_mismatch !== null
|
|
171
|
+
|| sensor.fresh_index_repetitions !== 2
|
|
172
|
+
|| !Array.isArray(sensor.raw_outcomes_digests)
|
|
173
|
+
|| sensor.raw_outcomes_digests.length !== 2
|
|
174
|
+
|| sensor.raw_outcomes_digests.some((digest) => (
|
|
174
175
|
typeof digest !== 'string' || !SHA256.test(digest)
|
|
175
176
|
))
|
|
176
|
-
|| new Set(
|
|
177
|
-
||
|
|
178
|
-
|| typeof
|
|
179
|
-
|| !SHA256.test(
|
|
180
|
-
||
|
|
181
|
-
|| !Array.isArray(
|
|
182
|
-
||
|
|
183
|
-
||
|
|
184
|
-
fail('portable
|
|
177
|
+
|| new Set(sensor.raw_outcomes_digests).size !== 2
|
|
178
|
+
|| sensor.raw_outcomes_equal !== false
|
|
179
|
+
|| typeof sensor.portable_outcomes_digest !== 'string'
|
|
180
|
+
|| !SHA256.test(sensor.portable_outcomes_digest)
|
|
181
|
+
|| sensor.portable_outcomes_equal !== true
|
|
182
|
+
|| !Array.isArray(sensor.outcomes)
|
|
183
|
+
|| sensor.outcomes.length === 0
|
|
184
|
+
|| sensor.outcomes.some((outcome) => !portableOutcomeRecord(outcome))) {
|
|
185
|
+
fail('portable LatticeSensor portability proofが不正');
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
if (!Array.isArray(querySet.queries)
|
|
188
|
-
||
|
|
189
|
-
||
|
|
189
|
+
|| sensor.outcomes.length !== querySet.queries.length
|
|
190
|
+
|| sensor.outcomes.some((outcome, index) => (
|
|
190
191
|
outcome.id !== querySet.queries[index]?.id
|
|
191
192
|
|| outcome.operation !== querySet.queries[index]?.operation
|
|
192
193
|
))) {
|
|
193
|
-
fail('portable
|
|
194
|
+
fail('portable LatticeSensor evidenceがquery setと一致しない');
|
|
194
195
|
}
|
|
195
196
|
|
|
196
|
-
const compiledGraphEvidence =
|
|
197
|
+
const compiledGraphEvidence = sensor.outcomes.map((outcome) => ({
|
|
197
198
|
id: outcome.id,
|
|
198
199
|
operation: outcome.operation,
|
|
199
200
|
status: outcome.outcome,
|
|
200
201
|
result_digest: outcome.result_digest,
|
|
201
202
|
}));
|
|
202
203
|
if (JSON.stringify(compiledGraphEvidence) !== JSON.stringify(graphEvidence)) {
|
|
203
|
-
fail('portable
|
|
204
|
+
fail('portable LatticeSensor evidenceがboundary manifestのgraph evidenceと一致しない');
|
|
204
205
|
}
|
|
205
206
|
}
|
|
206
207
|
|
|
@@ -220,17 +221,16 @@ function assertControlCompilationEvidence(evidence, {
|
|
|
220
221
|
'executor_head',
|
|
221
222
|
'graph_digest_projection',
|
|
222
223
|
'input_digests',
|
|
223
|
-
'
|
|
224
|
+
'sensor',
|
|
224
225
|
'artifacts',
|
|
225
226
|
'observed_facts',
|
|
226
227
|
'presentation_note',
|
|
227
228
|
])
|
|
228
229
|
|| evidence.schema !== 'lattice.rc1.control_compilation_evidence.v2'
|
|
229
|
-
|| evidence.graph_digest_projection !==
|
|
230
|
+
|| evidence.graph_digest_projection !== PORTABLE_SENSOR_PROJECTION) {
|
|
230
231
|
fail('portable control compilation evidence v2またはprojectionが不正');
|
|
231
232
|
}
|
|
232
|
-
if (
|
|
233
|
-
|| Number.isNaN(Date.parse(evidence.observed_at))
|
|
233
|
+
if (!isCanonicalUtcTimestamp(evidence.observed_at)
|
|
234
234
|
|| typeof evidence.head !== 'string'
|
|
235
235
|
|| !SHA1.test(evidence.head)
|
|
236
236
|
|| typeof evidence.executor_head !== 'string'
|
|
@@ -250,7 +250,7 @@ function assertControlCompilationEvidence(evidence, {
|
|
|
250
250
|
|| !evidenceArtifactSet(evidence.artifacts.shared_state_negative)) {
|
|
251
251
|
fail('control compilation evidenceまたはcontrol baseが不正');
|
|
252
252
|
}
|
|
253
|
-
|
|
253
|
+
assertPortableLatticeSensorEvidence(evidence.sensor, { querySet, graphEvidence });
|
|
254
254
|
if (evidence.input_digests.query_set !== querySetDigest
|
|
255
255
|
|| evidence.input_digests.code_snapshot !== codeSnapshotDigest
|
|
256
256
|
|| evidence.artifacts.control.boundary_manifest.digest !== manifestDigest
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
1
|
import { lstat, realpath } from 'node:fs/promises';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
|
|
4
|
+
import { spawnSensorCli } from './sensor-runtime.mjs';
|
|
5
|
+
|
|
5
6
|
const OPERATIONS = new Set(['status', 'query', 'callers', 'callees', 'impact', 'affected']);
|
|
6
7
|
const ANSI_ESCAPE = /\u001B\[[0-?]*[ -/]*[@-~]/g;
|
|
7
8
|
|
|
@@ -23,11 +24,11 @@ function portableCopy(value, insideNode = false) {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
|
-
*
|
|
27
|
+
* LatticeSensor raw outcomeから環境依存telemetryだけを除くdigest projectionを返す。
|
|
27
28
|
* @param {unknown} outcome
|
|
28
29
|
* @returns {unknown}
|
|
29
30
|
*/
|
|
30
|
-
export function
|
|
31
|
+
export function portableSensorOutcome(outcome) {
|
|
31
32
|
const portable = portableCopy(outcome);
|
|
32
33
|
if (!isPlainObject(portable)
|
|
33
34
|
|| portable.operation !== 'status'
|
|
@@ -208,9 +209,8 @@ function emptyAffectedData(target) {
|
|
|
208
209
|
|
|
209
210
|
function defaultExecutor({ args, cwd }) {
|
|
210
211
|
return new Promise((resolve) => {
|
|
211
|
-
const child =
|
|
212
|
+
const child = spawnSensorCli(args, {
|
|
212
213
|
cwd,
|
|
213
|
-
shell: false,
|
|
214
214
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
215
215
|
});
|
|
216
216
|
let stdout = '';
|
|
@@ -389,11 +389,11 @@ async function collectOne({ cwd, query, execute, inspectAffectedPath }) {
|
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
/**
|
|
392
|
-
* Collects
|
|
392
|
+
* Collects LatticeSensor CLI evidence without turning command failures or empty results into independence.
|
|
393
393
|
* @param {{ cwd: string, querySet: { queries?: object[] }, execute?: Function }} options
|
|
394
394
|
* @returns {Promise<{ cwd: string, outcomes: object[] }>}
|
|
395
395
|
*/
|
|
396
|
-
export async function
|
|
396
|
+
export async function collectSensorEvidence({
|
|
397
397
|
cwd,
|
|
398
398
|
querySet,
|
|
399
399
|
execute = defaultExecutor,
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import sensorPackage from '../sensor/package.json' with { type: 'json' };
|
|
4
|
+
import { spawnSensorCli } from './sensor-runtime.mjs';
|
|
5
|
+
|
|
6
|
+
const MAX_CAPTURE_BYTES = 1024 * 1024;
|
|
7
|
+
const MAX_DIAGNOSTIC_BYTES = 16 * 1024;
|
|
8
|
+
|
|
9
|
+
function writeJson(stream, value) {
|
|
10
|
+
stream.write(`${JSON.stringify(value)}\n`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function usage(stderr) {
|
|
14
|
+
writeJson(stderr, {
|
|
15
|
+
schema: 'lattice.cli_error.v2',
|
|
16
|
+
code: 'USAGE',
|
|
17
|
+
message: 'usage: lattice sensor <init|sync> [path] --json',
|
|
18
|
+
});
|
|
19
|
+
return 2;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parse(argv) {
|
|
23
|
+
if (argv.at(-1) !== '--json') return null;
|
|
24
|
+
const words = argv.slice(0, -1);
|
|
25
|
+
if (!['init', 'sync'].includes(words[0]) || words.length > 2) return null;
|
|
26
|
+
return { command: words[0], path: words[1] ?? '.' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function execute(command, projectPath) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
let captured = 0;
|
|
32
|
+
const stderrChunks = [];
|
|
33
|
+
let diagnosticBytes = 0;
|
|
34
|
+
const hasIndex = existsSync(path.resolve(projectPath, '.lattice/sensor', 'sensor.db'));
|
|
35
|
+
const sensorArgs = command === 'init' && hasIndex
|
|
36
|
+
? ['index', projectPath, '--quiet']
|
|
37
|
+
: [command, projectPath];
|
|
38
|
+
const child = spawnSensorCli(sensorArgs, {
|
|
39
|
+
env: {
|
|
40
|
+
...process.env,
|
|
41
|
+
LATTICE_SENSOR_NO_UPDATE_CHECK: '1',
|
|
42
|
+
DO_NOT_TRACK: '1',
|
|
43
|
+
NO_COLOR: '1',
|
|
44
|
+
},
|
|
45
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
46
|
+
});
|
|
47
|
+
const observe = (chunk, retain = false) => {
|
|
48
|
+
captured += chunk.length;
|
|
49
|
+
if (retain && diagnosticBytes < MAX_DIAGNOSTIC_BYTES) {
|
|
50
|
+
const retained = chunk.subarray(0, MAX_DIAGNOSTIC_BYTES - diagnosticBytes);
|
|
51
|
+
stderrChunks.push(retained);
|
|
52
|
+
diagnosticBytes += retained.length;
|
|
53
|
+
}
|
|
54
|
+
if (captured > MAX_CAPTURE_BYTES) child.kill('SIGTERM');
|
|
55
|
+
};
|
|
56
|
+
child.stdout.on('data', observe);
|
|
57
|
+
child.stderr.on('data', (chunk) => observe(chunk, true));
|
|
58
|
+
child.once('error', reject);
|
|
59
|
+
child.once('close', (code, signal) => resolve({
|
|
60
|
+
code,
|
|
61
|
+
signal,
|
|
62
|
+
overflow: captured > MAX_CAPTURE_BYTES,
|
|
63
|
+
stderr: Buffer.concat(stderrChunks).toString('utf8').trim(),
|
|
64
|
+
}));
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function runSensorCli({ argv, stdout, stderr }) {
|
|
69
|
+
const request = parse(argv);
|
|
70
|
+
if (!request) return usage(stderr);
|
|
71
|
+
try {
|
|
72
|
+
const result = await execute(request.command, request.path);
|
|
73
|
+
if (result.code !== 0 || result.signal || result.overflow) {
|
|
74
|
+
const notInitialized = request.command === 'sync' && /not initialized/u.test(result.stderr);
|
|
75
|
+
writeJson(stderr, {
|
|
76
|
+
schema: 'lattice.cli_error.v2',
|
|
77
|
+
code: result.overflow ? 'LATTICE_SENSOR_OUTPUT_LIMIT'
|
|
78
|
+
: notInitialized ? 'LATTICE_SENSOR_NOT_INITIALIZED' : 'LATTICE_SENSOR_COMMAND_FAILED',
|
|
79
|
+
message: `LatticeSensor ${request.command} failed`,
|
|
80
|
+
detail: {
|
|
81
|
+
exit_code: result.code,
|
|
82
|
+
signal: result.signal,
|
|
83
|
+
stderr: result.stderr,
|
|
84
|
+
next_action: notInitialized ? `lattice sensor init ${request.path} --json` : null,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
writeJson(stdout, {
|
|
90
|
+
schema: 'lattice.sensor_command_result.v1',
|
|
91
|
+
provider: 'lattice',
|
|
92
|
+
sensor_owner: 'lattice',
|
|
93
|
+
sensor_version: sensorPackage.version,
|
|
94
|
+
command: request.command,
|
|
95
|
+
status: 'ok',
|
|
96
|
+
});
|
|
97
|
+
return 0;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
writeJson(stderr, {
|
|
100
|
+
schema: 'lattice.cli_error.v2',
|
|
101
|
+
code: error?.code === 'LATTICE_SENSOR_UNAVAILABLE' ? error.code : 'LATTICE_SENSOR_COMMAND_FAILED',
|
|
102
|
+
message: `LatticeSensor ${request.command} failed`,
|
|
103
|
+
detail: { cause: error?.message ?? 'unknown error' },
|
|
104
|
+
});
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { lstatSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
export const LATTICE_SENSOR_CLI = fileURLToPath(
|
|
6
|
+
new URL('../sensor/dist/bin/lattice-sensor.js', import.meta.url),
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
export class SensorRuntimeError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'SensorRuntimeError';
|
|
13
|
+
this.code = 'LATTICE_SENSOR_UNAVAILABLE';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function assertBundledSensor() {
|
|
18
|
+
try {
|
|
19
|
+
const info = lstatSync(LATTICE_SENSOR_CLI);
|
|
20
|
+
if (!info.isFile() || info.isSymbolicLink()) throw new Error('not a regular file');
|
|
21
|
+
} catch (error) {
|
|
22
|
+
throw new SensorRuntimeError(`bundled sensor is unavailable: ${error.message}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function sensorCliInvocation(args) {
|
|
27
|
+
if (!Array.isArray(args) || !args.every((arg) => typeof arg === 'string')) {
|
|
28
|
+
throw new TypeError('sensor args must be an array of strings');
|
|
29
|
+
}
|
|
30
|
+
assertBundledSensor();
|
|
31
|
+
return Object.freeze({ command: process.execPath, args: Object.freeze([LATTICE_SENSOR_CLI, ...args]) });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function invokeSensorCli(run, args, options) {
|
|
35
|
+
if (typeof run !== 'function') throw new TypeError('run must be a function');
|
|
36
|
+
const invocation = sensorCliInvocation(args);
|
|
37
|
+
return run(invocation.command, invocation.args, options);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function spawnSensorCli(args, options = {}) {
|
|
41
|
+
const invocation = sensorCliInvocation(args);
|
|
42
|
+
return spawn(invocation.command, invocation.args, { ...options, shell: false });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function spawnSensorCliSync(args, options = {}) {
|
|
46
|
+
const invocation = sensorCliInvocation(args);
|
|
47
|
+
return spawnSync(invocation.command, invocation.args, { ...options, shell: false });
|
|
48
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const CANONICAL_UTC_MILLISECONDS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
|
|
2
|
+
|
|
3
|
+
/** JavaScript Dateの暦日正規化(例: 2月30日)を受理しないUTC timestamp契約。 */
|
|
4
|
+
export function isCanonicalUtcTimestamp(value) {
|
|
5
|
+
if (typeof value !== 'string' || !CANONICAL_UTC_MILLISECONDS.test(value)) return false;
|
|
6
|
+
const parsed = new Date(value);
|
|
7
|
+
return !Number.isNaN(parsed.valueOf()) && parsed.toISOString() === value;
|
|
8
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { analyzeDagChains, DagCycleError } from './dag-chain.mjs';
|
|
2
|
+
|
|
3
|
+
const ASSUMPTIONS = Object.freeze({
|
|
4
|
+
unit_duration: true,
|
|
5
|
+
capacity_ignored: true,
|
|
6
|
+
conflict_ignored: true,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export class TodoChainProjectionError extends Error {
|
|
10
|
+
constructor(code, message, options) {
|
|
11
|
+
super(message, options);
|
|
12
|
+
this.name = 'TodoChainProjectionError';
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class TodoChainCycleError extends TodoChainProjectionError {
|
|
18
|
+
constructor(options) {
|
|
19
|
+
super('TODO_CHAIN_CYCLE', 'merged todo topology contains a cycle', options);
|
|
20
|
+
this.name = 'TodoChainCycleError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function fail(code, message) {
|
|
25
|
+
throw new TodoChainProjectionError(code, message);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isPlainObject(value) {
|
|
29
|
+
return value !== null
|
|
30
|
+
&& typeof value === 'object'
|
|
31
|
+
&& !Array.isArray(value)
|
|
32
|
+
&& Object.getPrototypeOf(value) === Object.prototype;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeNodeRef(value, context) {
|
|
36
|
+
if (!isPlainObject(value)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `${context} must be a node ref`);
|
|
37
|
+
const { project_id: projectId, plan_key: planKey, task_id: taskId } = value;
|
|
38
|
+
if (typeof projectId !== 'string' || typeof planKey !== 'string' || typeof taskId !== 'string') {
|
|
39
|
+
fail('TODO_CHAIN_INVALID_TOPOLOGY', `${context} node ref fields must be strings`);
|
|
40
|
+
}
|
|
41
|
+
return { project_id: projectId, plan_key: planKey, task_id: taskId };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function nodeKey(ref) {
|
|
45
|
+
return JSON.stringify([ref.project_id, ref.plan_key, ref.task_id]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function compareRefs(left, right) {
|
|
49
|
+
for (const field of ['project_id', 'plan_key', 'task_id']) {
|
|
50
|
+
if (left[field] < right[field]) return -1;
|
|
51
|
+
if (left[field] > right[field]) return 1;
|
|
52
|
+
}
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function requireArray(value, context) {
|
|
57
|
+
if (!Array.isArray(value)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `${context} must be an array`);
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeOptions(options) {
|
|
62
|
+
if (options === undefined) options = {};
|
|
63
|
+
if (!isPlainObject(options)) {
|
|
64
|
+
fail('TODO_CHAIN_INVALID_OPTIONS', 'options must be an object');
|
|
65
|
+
}
|
|
66
|
+
const allowedKeys = new Set(['countCap', 'representativeLimit']);
|
|
67
|
+
for (const key of Reflect.ownKeys(options)) {
|
|
68
|
+
if (typeof key !== 'string' || !allowedKeys.has(key)) {
|
|
69
|
+
fail('TODO_CHAIN_INVALID_OPTIONS', `unknown option: ${String(key)}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const countCap = Object.hasOwn(options, 'countCap') ? options.countCap : 1_000_000;
|
|
74
|
+
const representativeLimit = Object.hasOwn(options, 'representativeLimit')
|
|
75
|
+
? options.representativeLimit
|
|
76
|
+
: 8;
|
|
77
|
+
if (!Number.isSafeInteger(countCap) || countCap < 1 || countCap >= Number.MAX_SAFE_INTEGER) {
|
|
78
|
+
fail(
|
|
79
|
+
'TODO_CHAIN_INVALID_OPTIONS',
|
|
80
|
+
'countCap must be a safe integer from 1 through Number.MAX_SAFE_INTEGER - 1',
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (
|
|
84
|
+
!Number.isSafeInteger(representativeLimit)
|
|
85
|
+
|| representativeLimit < 0
|
|
86
|
+
|| representativeLimit > 8
|
|
87
|
+
) {
|
|
88
|
+
fail('TODO_CHAIN_INVALID_OPTIONS', 'representativeLimit must be a safe integer from 0 through 8');
|
|
89
|
+
}
|
|
90
|
+
return { countCap, representativeLimit };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function projectTodoChainV1(
|
|
94
|
+
mergedTodoTopology,
|
|
95
|
+
options,
|
|
96
|
+
) {
|
|
97
|
+
const { countCap, representativeLimit } = normalizeOptions(options);
|
|
98
|
+
if (!isPlainObject(mergedTodoTopology)) {
|
|
99
|
+
fail('TODO_CHAIN_INVALID_TOPOLOGY', 'mergedTodoTopology must be an object');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const nodes = requireArray(mergedTodoTopology.nodes, 'nodes');
|
|
103
|
+
const hardEdges = requireArray(mergedTodoTopology.hard_edges, 'hard_edges');
|
|
104
|
+
const joins = requireArray(mergedTodoTopology.joins, 'joins');
|
|
105
|
+
const refsByKey = new Map();
|
|
106
|
+
|
|
107
|
+
for (let index = 0; index < nodes.length; index += 1) {
|
|
108
|
+
const ref = normalizeNodeRef(nodes[index], `nodes[${index}]`);
|
|
109
|
+
const key = nodeKey(ref);
|
|
110
|
+
if (refsByKey.has(key)) fail('TODO_CHAIN_DUPLICATE_NODE', `duplicate node ref at nodes[${index}]`);
|
|
111
|
+
refsByKey.set(key, ref);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const edgeKeys = new Set();
|
|
115
|
+
const edges = [];
|
|
116
|
+
const addEdge = (fromValue, toValue, context) => {
|
|
117
|
+
const fromRef = normalizeNodeRef(fromValue, `${context}.from`);
|
|
118
|
+
const toRef = normalizeNodeRef(toValue, `${context}.to`);
|
|
119
|
+
const from = nodeKey(fromRef);
|
|
120
|
+
const to = nodeKey(toRef);
|
|
121
|
+
if (!refsByKey.has(from) || !refsByKey.has(to)) {
|
|
122
|
+
fail('TODO_CHAIN_DANGLING_EDGE', `${context} references a node absent from nodes`);
|
|
123
|
+
}
|
|
124
|
+
const edgeKey = JSON.stringify([from, to]);
|
|
125
|
+
if (edgeKeys.has(edgeKey)) return;
|
|
126
|
+
edgeKeys.add(edgeKey);
|
|
127
|
+
edges.push([from, to]);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
for (let index = 0; index < hardEdges.length; index += 1) {
|
|
131
|
+
const edge = hardEdges[index];
|
|
132
|
+
if (!isPlainObject(edge)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `hard_edges[${index}] must be an object`);
|
|
133
|
+
addEdge(edge.from, edge.to, `hard_edges[${index}]`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (let joinIndex = 0; joinIndex < joins.length; joinIndex += 1) {
|
|
137
|
+
const join = joins[joinIndex];
|
|
138
|
+
if (!isPlainObject(join)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `joins[${joinIndex}] must be an object`);
|
|
139
|
+
const after = requireArray(join.after, `joins[${joinIndex}].after`);
|
|
140
|
+
for (let afterIndex = 0; afterIndex < after.length; afterIndex += 1) {
|
|
141
|
+
addEdge(
|
|
142
|
+
after[afterIndex],
|
|
143
|
+
join.before,
|
|
144
|
+
`joins[${joinIndex}].after[${afterIndex}]`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let analysis;
|
|
150
|
+
try {
|
|
151
|
+
analysis = analyzeDagChains([...refsByKey.keys()], edges, {
|
|
152
|
+
countCap,
|
|
153
|
+
representativeLimit,
|
|
154
|
+
compare: (left, right) => compareRefs(refsByKey.get(left), refsByKey.get(right)),
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
if (error instanceof DagCycleError) throw new TodoChainCycleError({ cause: error });
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const toRef = (key) => ({ ...refsByKey.get(key) });
|
|
162
|
+
return {
|
|
163
|
+
schema: 'lattice.todo_chain.v1',
|
|
164
|
+
maximum_dependency_depth: analysis.maximumDepth,
|
|
165
|
+
longest_chain_node_refs: analysis.longestChainNodes.map(toRef),
|
|
166
|
+
longest_chain_edges: analysis.longestChainEdges.map(([from, to]) => ({
|
|
167
|
+
from: toRef(from),
|
|
168
|
+
to: toRef(to),
|
|
169
|
+
})),
|
|
170
|
+
longest_chain_count: analysis.longestChainCount,
|
|
171
|
+
limits: {
|
|
172
|
+
count_cap: countCap,
|
|
173
|
+
representative_limit: representativeLimit,
|
|
174
|
+
},
|
|
175
|
+
representative_chains: analysis.representativeChains.map((chain) => chain.map(toRef)),
|
|
176
|
+
assumptions: { ...ASSUMPTIONS },
|
|
177
|
+
};
|
|
178
|
+
}
|