@gajae-code/coding-agent 0.17.1 → 0.17.4
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/CHANGELOG.md +266 -0
- package/dist/types/async/job-manager.d.ts +7 -6
- package/dist/types/cli/daemon-cli.d.ts +3 -1
- package/dist/types/cli/github-release.d.ts +17 -0
- package/dist/types/cli/public-command-entry.d.ts +56 -0
- package/dist/types/cli/public-command-errors.d.ts +126 -0
- package/dist/types/cli/public-command-evidence.d.ts +99 -0
- package/dist/types/cli/public-command-help.d.ts +65 -0
- package/dist/types/cli/public-command-registry.d.ts +69 -0
- package/dist/types/cli/session-picker.d.ts +1 -1
- package/dist/types/cli/setup-cli.d.ts +1 -0
- package/dist/types/cli/skills-cli.d.ts +5 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/commands/daemon.d.ts +4 -50
- package/dist/types/commands/sdk.d.ts +20 -37
- package/dist/types/commands/setup.d.ts +3 -0
- package/dist/types/commands/skills.d.ts +9 -0
- package/dist/types/config/autorouting-tier-map.d.ts +24 -0
- package/dist/types/config/file-lock.d.ts +10 -1
- package/dist/types/config/model-profile-activation.d.ts +63 -4
- package/dist/types/config/model-registry.d.ts +20 -2
- package/dist/types/config/settings-schema.d.ts +11 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
- package/dist/types/coordinator-mcp/server.d.ts +3 -0
- package/dist/types/daemon/operator-contract.d.ts +5 -1
- package/dist/types/eval/js/worker-protocol.d.ts +2 -0
- package/dist/types/extensibility/extensions/types.d.ts +12 -0
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
- package/dist/types/extensibility/shared-events.d.ts +3 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
- package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/parse.d.ts +9 -1
- package/dist/types/main.d.ts +22 -0
- package/dist/types/modes/acp/acp-agent.d.ts +94 -1
- package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
- package/dist/types/modes/components/session-selector.d.ts +5 -2
- package/dist/types/modes/components/thinking-selector.d.ts +2 -1
- package/dist/types/modes/components/user-message-selector.d.ts +5 -2
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/runtime/process-lifecycle.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +56 -0
- package/dist/types/runtime-mcp/pool.d.ts +17 -0
- package/dist/types/runtime-mcp/redaction.d.ts +20 -0
- package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
- package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
- package/dist/types/sdk/acp/adapter.d.ts +27 -0
- package/dist/types/sdk/acp/final-text.d.ts +8 -0
- package/dist/types/sdk/broker/broker.d.ts +13 -2
- package/dist/types/sdk/broker/ensure.d.ts +46 -1
- package/dist/types/sdk/broker/identity.d.ts +3 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
- package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
- package/dist/types/sdk/broker/runtime.d.ts +1 -1
- package/dist/types/sdk/broker/session-index.d.ts +22 -3
- package/dist/types/sdk/broker/spawn-authority.d.ts +4 -0
- package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
- package/dist/types/sdk/bus/index.d.ts +19 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
- package/dist/types/sdk/cli/master-cli.d.ts +6 -10
- package/dist/types/sdk/cli/rows.d.ts +19 -1
- package/dist/types/sdk/cli/session-cli.d.ts +22 -5
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/guides/cli.d.ts +3 -4
- package/dist/types/sdk/guides/verify.d.ts +2 -0
- package/dist/types/sdk/host/host.d.ts +9 -0
- package/dist/types/sdk/host/session-runtime.d.ts +31 -0
- package/dist/types/sdk/host/types.d.ts +10 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/prompt-deadline-flush.d.ts +83 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +38 -1
- package/dist/types/sdk/prompt-failure.d.ts +30 -1
- package/dist/types/sdk/prompt-status.d.ts +8 -0
- package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
- package/dist/types/sdk/router/replay-retention-gap-cache.d.ts +13 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -0
- package/dist/types/sdk/transport/relay.d.ts +1 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +73 -2
- package/dist/types/sdk/transport/stdio.d.ts +5 -1
- package/dist/types/session/agent-session.d.ts +168 -21
- package/dist/types/session/fallback-chain-controller.d.ts +6 -6
- package/dist/types/session/fold-coordinator.d.ts +17 -3
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
- package/dist/types/session/session-manager.d.ts +64 -1
- package/dist/types/session/session-work-lease.d.ts +14 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
- package/dist/types/setup/provider-onboarding.d.ts +81 -1
- package/dist/types/thinking.d.ts +3 -1
- package/dist/types/tools/bash.d.ts +37 -11
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/steer-fold.d.ts +31 -0
- package/dist/types/utils/git.d.ts +28 -2
- package/dist/types/utils/mupdf-embedded.d.ts +2 -0
- package/dist/types/utils/mupdf-wasm-embedded.d.ts +2 -0
- package/dist/types/utils/mupdf.d.ts +4 -0
- package/dist/types/utils/session-title-generation.d.ts +6 -0
- package/dist/types/utils/title-generator.d.ts +11 -2
- package/dist/types/web/scrapers/types.d.ts +3 -0
- package/package.json +18 -10
- package/scripts/build-binary.ts +9 -3
- package/scripts/compile-args.ts +0 -9
- package/scripts/embed-mupdf.ts +52 -0
- package/scripts/generate-sdk-operation-inventory.ts +31 -1
- package/scripts/vendor-markit.ts +196 -0
- package/src/async/job-manager.ts +60 -9
- package/src/cli/daemon-cli.ts +53 -18
- package/src/cli/github-release.ts +87 -5
- package/src/cli/mcp-cli.ts +38 -4
- package/src/cli/public-command-entry.ts +450 -0
- package/src/cli/public-command-errors.ts +581 -0
- package/src/cli/public-command-evidence.ts +717 -0
- package/src/cli/public-command-help.ts +360 -0
- package/src/cli/public-command-registry.ts +535 -0
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +9 -1
- package/src/cli/skills-cli.ts +46 -2
- package/src/cli.ts +61 -1
- package/src/commands/daemon.ts +65 -69
- package/src/commands/sdk.ts +232 -100
- package/src/commands/setup.ts +2 -0
- package/src/commands/skills.ts +11 -0
- package/src/config/autorouting-tier-map.ts +16 -0
- package/src/config/file-lock.ts +199 -68
- package/src/config/model-profile-activation.ts +406 -53
- package/src/config/model-registry.ts +204 -25
- package/src/config/settings-schema.ts +26 -2
- package/src/coordinator-mcp/policy.ts +10 -5
- package/src/coordinator-mcp/question-gate-codec.ts +17 -0
- package/src/coordinator-mcp/question-state.ts +175 -8
- package/src/coordinator-mcp/server.ts +528 -87
- package/src/coordinator-mcp/session-reaper.ts +6 -4
- package/src/crash/record-loader.ts +6 -2
- package/src/daemon/operator-contract.ts +26 -12
- package/src/debug/report-bundle.ts +114 -24
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
- package/src/eval/js/context-manager.ts +8 -8
- package/src/eval/js/worker-core.ts +9 -4
- package/src/eval/js/worker-protocol.ts +2 -0
- package/src/extensibility/extensions/runner.ts +52 -4
- package/src/extensibility/extensions/types.ts +10 -0
- package/src/extensibility/gjc-plugins/registry.ts +196 -37
- package/src/extensibility/runtime-skill-discovery.ts +61 -3
- package/src/extensibility/shared-events.ts +3 -1
- package/src/gjc-runtime/managed-owner-admission.ts +67 -50
- package/src/gjc-runtime/session-state-lock.ts +165 -24
- package/src/gjc-runtime/ultragoal-succession.ts +31 -3
- package/src/internal-urls/docs-index.generated.ts +21 -19
- package/src/internal-urls/embedded-protocol.ts +83 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/parse.ts +21 -1
- package/src/internal-urls/router.ts +4 -2
- package/src/main.ts +123 -55
- package/src/modes/acp/acp-agent.ts +593 -47
- package/src/modes/acp/acp-event-mapper.ts +9 -0
- package/src/modes/acp/startup-provenance.ts +59 -0
- package/src/modes/components/custom-provider-wizard.ts +308 -7
- package/src/modes/components/model-selector.ts +31 -15
- package/src/modes/components/oauth-selector.ts +9 -0
- package/src/modes/components/session-selector.ts +47 -4
- package/src/modes/components/status-line/segments.ts +6 -1
- package/src/modes/components/thinking-selector.ts +7 -3
- package/src/modes/components/user-message-selector.ts +57 -15
- package/src/modes/controllers/command-controller.ts +58 -32
- package/src/modes/controllers/extension-ui-controller.ts +33 -7
- package/src/modes/controllers/input-controller.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +177 -18
- package/src/modes/interactive-mode.ts +21 -7
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
- package/src/modes/types.ts +2 -2
- package/src/prompts/agent-fragments/browser-aside.md +1 -0
- package/src/prompts/system/title-system.md +1 -1
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/job.md +1 -1
- package/src/prompts/tools/read.md +2 -0
- package/src/runtime/network-prewarm-service.test.ts +36 -1
- package/src/runtime/network-prewarm-service.ts +28 -3
- package/src/runtime/process-lifecycle.ts +22 -11
- package/src/runtime-mcp/config.ts +16 -1
- package/src/runtime-mcp/manager.ts +689 -55
- package/src/runtime-mcp/pool.test.ts +31 -0
- package/src/runtime-mcp/pool.ts +304 -28
- package/src/runtime-mcp/redaction.ts +42 -0
- package/src/runtime-mcp/startup-policy.ts +7 -0
- package/src/runtime-mcp/tool-bridge.ts +71 -0
- package/src/runtime-mcp/transports/http.ts +108 -25
- package/src/sdk/acp/adapter.ts +90 -9
- package/src/sdk/acp/final-text.ts +19 -2
- package/src/sdk/broker/broker.ts +378 -52
- package/src/sdk/broker/ensure.ts +97 -19
- package/src/sdk/broker/identity.ts +9 -1
- package/src/sdk/broker/lifecycle-ledger.ts +95 -31
- package/src/sdk/broker/lifecycle.ts +690 -141
- package/src/sdk/broker/runtime.ts +1 -1
- package/src/sdk/broker/session-index.ts +97 -4
- package/src/sdk/broker/spawn-authority.ts +35 -7
- package/src/sdk/broker/stderr-drainer.ts +59 -0
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
- package/src/sdk/bus/index.ts +721 -84
- package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
- package/src/sdk/bus/telegram-daemon.ts +10 -3
- package/src/sdk/cli/master-cli.ts +47 -36
- package/src/sdk/cli/rows.ts +30 -7
- package/src/sdk/cli/session-cli.ts +687 -184
- package/src/sdk/client/client.ts +18 -0
- package/src/sdk/guides/catalog.ts +2 -2
- package/src/sdk/guides/cli.ts +20 -62
- package/src/sdk/guides/verify.ts +14 -0
- package/src/sdk/host/host.ts +49 -5
- package/src/sdk/host/session-runtime.test.ts +636 -9
- package/src/sdk/host/session-runtime.ts +499 -118
- package/src/sdk/host/types.ts +12 -1
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
- package/src/sdk/host/websocket-transport.ts +40 -6
- package/src/sdk/index.ts +11 -0
- package/src/sdk/prompt-deadline-flush.ts +334 -0
- package/src/sdk/prompt-deadline-manager.ts +136 -4
- package/src/sdk/prompt-failure.ts +43 -1
- package/src/sdk/prompt-status.ts +8 -0
- package/src/sdk/prompt-tool-boundary.ts +121 -0
- package/src/sdk/protocol/operation-inventory.generated.json +106 -0
- package/src/sdk/router/replay-retention-gap-cache.ts +27 -0
- package/src/sdk/router/session-router.ts +31 -4
- package/src/sdk/session.ts +194 -16
- package/src/sdk/startup-capability.ts +1 -1
- package/src/sdk/transport/relay.ts +71 -6
- package/src/sdk/transport/serve-cli.ts +494 -49
- package/src/sdk/transport/socket.ts +12 -11
- package/src/sdk/transport/stdio.ts +7 -3
- package/src/session/agent-session.ts +2437 -400
- package/src/session/fallback-chain-controller.ts +6 -7
- package/src/session/fold-coordinator.ts +24 -5
- package/src/session/internal/managed-session-scope.ts +58 -18
- package/src/session/internal/managed-session-storage.ts +304 -11
- package/src/session/session-manager.ts +382 -44
- package/src/session/session-work-lease.ts +34 -0
- package/src/session-import/codex.ts +12 -3
- package/src/setup/model-onboarding-guidance.ts +1 -1
- package/src/setup/provider-onboarding.ts +397 -24
- package/src/slash-commands/builtin-registry.ts +258 -14
- package/src/task/index.ts +14 -2
- package/src/thinking.ts +21 -2
- package/src/tools/bash.ts +98 -78
- package/src/tools/fetch.ts +119 -20
- package/src/tools/job.ts +151 -25
- package/src/tools/output-meta.ts +15 -2
- package/src/tools/path-utils.ts +27 -3
- package/src/tools/read.ts +3 -3
- package/src/tools/steer-fold.ts +107 -0
- package/src/tools/tool-catalog.generated.ts +29 -2
- package/src/tools/tool-errors.ts +4 -0
- package/src/utils/git.ts +53 -3
- package/src/utils/markit.ts +55 -41
- package/src/utils/mupdf-assets.d.ts +4 -0
- package/src/utils/mupdf-embedded.ts +4 -0
- package/src/utils/mupdf-wasm-embedded.ts +3 -0
- package/src/utils/mupdf-wasm.ts +24 -2
- package/src/utils/mupdf.ts +129 -0
- package/src/utils/session-title-generation.ts +29 -0
- package/src/utils/title-generator.ts +46 -2
- package/src/web/scrapers/types.ts +24 -3
- package/vendor/markit-ai/LICENSE +21 -0
- package/vendor/markit-ai/MANIFEST.json +412 -0
- package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
- package/vendor/markit-ai/dist/commands/config.js +139 -0
- package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
- package/vendor/markit-ai/dist/commands/convert.js +113 -0
- package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/formats.js +92 -0
- package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/init.js +29 -0
- package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/onboard.js +61 -0
- package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
- package/vendor/markit-ai/dist/commands/plugin.js +58 -0
- package/vendor/markit-ai/dist/config.d.ts +26 -0
- package/vendor/markit-ai/dist/config.js +42 -0
- package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
- package/vendor/markit-ai/dist/converters/audio.js +104 -0
- package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
- package/vendor/markit-ai/dist/converters/csv.js +83 -0
- package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/docx.js +57 -0
- package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/epub.js +128 -0
- package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
- package/vendor/markit-ai/dist/converters/github.js +148 -0
- package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/html.js +30 -0
- package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/image.js +124 -0
- package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
- package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
- package/vendor/markit-ai/dist/converters/iwork.js +391 -0
- package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/json.js +21 -0
- package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
- package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
- package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
- package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
- package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
- package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
- package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
- package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
- package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
- package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
- package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
- package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
- package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
- package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
- package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
- package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
- package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/pptx.js +289 -0
- package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
- package/vendor/markit-ai/dist/converters/rss.js +138 -0
- package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
- package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
- package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/xml.js +18 -0
- package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/yaml.js +17 -0
- package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/zip.js +57 -0
- package/vendor/markit-ai/dist/index.d.ts +30 -0
- package/vendor/markit-ai/dist/index.js +26 -0
- package/vendor/markit-ai/dist/main.d.ts +2 -0
- package/vendor/markit-ai/dist/main.js +191 -0
- package/vendor/markit-ai/dist/markit.d.ts +45 -0
- package/vendor/markit-ai/dist/markit.js +267 -0
- package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
- package/vendor/markit-ai/dist/plugins/api.js +42 -0
- package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
- package/vendor/markit-ai/dist/plugins/index.js +3 -0
- package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
- package/vendor/markit-ai/dist/plugins/installer.js +176 -0
- package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
- package/vendor/markit-ai/dist/plugins/loader.js +61 -0
- package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
- package/vendor/markit-ai/dist/plugins/types.js +1 -0
- package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
- package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
- package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
- package/vendor/markit-ai/dist/providers/index.js +56 -0
- package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
- package/vendor/markit-ai/dist/providers/openai.js +76 -0
- package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
- package/vendor/markit-ai/dist/providers/types.js +1 -0
- package/vendor/markit-ai/dist/types.d.ts +36 -0
- package/vendor/markit-ai/dist/types.js +1 -0
- package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
- package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
- package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
- package/vendor/markit-ai/dist/utils/output.js +31 -0
- package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
- package/vendor/markit-ai/dist/utils/turndown.js +70 -0
- package/vendor/markit-ai/package.json +74 -0
- package/vendor/markit-ai.patch +49 -0
package/src/sdk/broker/ensure.ts
CHANGED
|
@@ -70,6 +70,72 @@ const SPAWN_LOCK_RETRY_DELAY_MS = 50;
|
|
|
70
70
|
const SPAWN_LOCK_TARGET_NAME = "broker.spawn";
|
|
71
71
|
const STARTUP_LOCK_TARGET_NAME = "broker.startup";
|
|
72
72
|
|
|
73
|
+
export interface BrokerStartupLockTestHooks {
|
|
74
|
+
onAcquired?: () => void;
|
|
75
|
+
onContended?: () => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Test-only phase observation for detached broker startup regressions. The
|
|
80
|
+
* signal is opt-in through the child environment and never affects broker
|
|
81
|
+
* ownership or error handling when the test seam is absent.
|
|
82
|
+
*/
|
|
83
|
+
export async function emitBrokerStartupTestSignal(signal: string): Promise<void> {
|
|
84
|
+
const directory = process.env.GJC_SDK_TEST_BROKER_SIGNAL_DIR;
|
|
85
|
+
if (!directory || !/^[a-z0-9-]+$/.test(signal)) return;
|
|
86
|
+
try {
|
|
87
|
+
await fs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
88
|
+
await fs.writeFile(path.join(directory, signal), `${process.pid}\n`);
|
|
89
|
+
} catch {
|
|
90
|
+
// Test observability must never change broker startup semantics.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The parent discovery budget published as a composition rather than a number.
|
|
96
|
+
* A test that stages a fence contention has to time itself against these legs,
|
|
97
|
+
* and retyping the sum makes it win or lose on scheduler noise while letting a
|
|
98
|
+
* change to any single leg silently re-tune it (#5604). This is the one source
|
|
99
|
+
* of truth for how the budget is built; nothing may restate the arithmetic.
|
|
100
|
+
*/
|
|
101
|
+
export const BROKER_DISCOVERY_BUDGET = {
|
|
102
|
+
staleRetirementMs: STALE_BROKER_RETIREMENT_TIMEOUT_MS,
|
|
103
|
+
startupLockWaitMs: STARTUP_LOCK_WAIT_MS,
|
|
104
|
+
publicationMs: BROKER_PUBLICATION_TIMEOUT_MS,
|
|
105
|
+
/** What the startup fence grants its holder: retirement plus one publication attempt. */
|
|
106
|
+
fenceOperationMs: STALE_BROKER_RETIREMENT_TIMEOUT_MS + BROKER_PUBLICATION_TIMEOUT_MS,
|
|
107
|
+
/**
|
|
108
|
+
* When a child spawned under the parent's spawn lock gives up on the fence,
|
|
109
|
+
* measured from that lock acquisition: the parent retires the stale incumbent
|
|
110
|
+
* first, then the child exhausts its own fence wait. Not a production
|
|
111
|
+
* constant -- an emergent property of the two legs that precede publication.
|
|
112
|
+
*/
|
|
113
|
+
childFenceWaitMs: STALE_BROKER_RETIREMENT_TIMEOUT_MS + STARTUP_LOCK_WAIT_MS,
|
|
114
|
+
discoveryMs: DISCOVERY_TIMEOUT_MS,
|
|
115
|
+
} as const;
|
|
116
|
+
|
|
117
|
+
const sleep = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Clock the discovery budget is measured on. The default is the real one; a test
|
|
121
|
+
* installs a virtual clock to assert the composition order -- stale retirement,
|
|
122
|
+
* then the child fence, then startup and publication -- in-process instead of
|
|
123
|
+
* racing three wall clocks against each other (#5604). Reaping keeps its own
|
|
124
|
+
* {@link ReapTiming} seam and is deliberately not driven from here.
|
|
125
|
+
*/
|
|
126
|
+
export interface EnsureBrokerTiming {
|
|
127
|
+
now(): number;
|
|
128
|
+
sleep(ms: number): Promise<void>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const REAL_ENSURE_BROKER_TIMING: EnsureBrokerTiming = { now: Date.now, sleep };
|
|
132
|
+
let ensureBrokerTiming: EnsureBrokerTiming = REAL_ENSURE_BROKER_TIMING;
|
|
133
|
+
|
|
134
|
+
/** Test hook: drives the discovery budget on a controllable clock. */
|
|
135
|
+
export function setEnsureBrokerTimingForTest(timing: EnsureBrokerTiming | undefined): void {
|
|
136
|
+
ensureBrokerTiming = timing ?? REAL_ENSURE_BROKER_TIMING;
|
|
137
|
+
}
|
|
138
|
+
|
|
73
139
|
type SpawnLockOptions = Pick<FileLockOptions, "retries" | "retryDelayMs" | "signal">;
|
|
74
140
|
|
|
75
141
|
interface BrokerLockHostIdentity {
|
|
@@ -104,7 +170,7 @@ async function readBrokerDiscoveryBeforeDeadline(
|
|
|
104
170
|
heartbeatTtlMs: number | undefined,
|
|
105
171
|
deadline: number,
|
|
106
172
|
): Promise<BrokerDiscovery | null> {
|
|
107
|
-
const remainingMs = deadline -
|
|
173
|
+
const remainingMs = deadline - ensureBrokerTiming.now();
|
|
108
174
|
if (!Number.isFinite(remainingMs)) return await readBrokerDiscovery(agentDir, heartbeatTtlMs);
|
|
109
175
|
if (remainingMs <= 0) throw new Error("Timed out reading SDK broker discovery.");
|
|
110
176
|
const read = readBrokerDiscovery(agentDir, heartbeatTtlMs);
|
|
@@ -151,14 +217,17 @@ async function acquireSpawnLock(agentDir: string, options: SpawnLockOptions = {}
|
|
|
151
217
|
export async function withBrokerStartupLock<T>(
|
|
152
218
|
agentDir: string,
|
|
153
219
|
operation: (deadline: number) => Promise<T>,
|
|
220
|
+
testHooks: BrokerStartupLockTestHooks = {},
|
|
154
221
|
): Promise<T> {
|
|
155
222
|
const hostIdentity = await brokerLockHostIdentity(agentDir);
|
|
156
223
|
return withFileLock(
|
|
157
224
|
path.join(agentDir, "sdk", STARTUP_LOCK_TARGET_NAME),
|
|
158
|
-
() => operation(
|
|
225
|
+
() => operation(ensureBrokerTiming.now() + BROKER_DISCOVERY_BUDGET.fenceOperationMs),
|
|
159
226
|
{
|
|
160
227
|
retries: Math.ceil(STARTUP_LOCK_WAIT_MS / SPAWN_LOCK_RETRY_DELAY_MS),
|
|
161
228
|
retryDelayMs: SPAWN_LOCK_RETRY_DELAY_MS,
|
|
229
|
+
onAcquired: testHooks.onAcquired,
|
|
230
|
+
onContended: testHooks.onContended,
|
|
162
231
|
...hostIdentity,
|
|
163
232
|
},
|
|
164
233
|
);
|
|
@@ -275,7 +344,6 @@ const DEFAULT_REAP_TIMING: ReapTiming = {
|
|
|
275
344
|
gracefulMs: REAP_GRACEFUL_MS,
|
|
276
345
|
killVerifyMs: REAP_SIGKILL_CAP_MS,
|
|
277
346
|
};
|
|
278
|
-
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
|
279
347
|
|
|
280
348
|
/**
|
|
281
349
|
* Terminate and reap a detached broker this process spawned, targeting the exact
|
|
@@ -410,9 +478,9 @@ export async function closeBrokerClientBeforeDeadline(
|
|
|
410
478
|
): Promise<void> {
|
|
411
479
|
const close = client.close();
|
|
412
480
|
void close.catch(() => undefined);
|
|
413
|
-
const remainingMs = deadline -
|
|
481
|
+
const remainingMs = deadline - ensureBrokerTiming.now();
|
|
414
482
|
if (remainingMs <= 0) return;
|
|
415
|
-
await Promise.race([close,
|
|
483
|
+
await Promise.race([close, ensureBrokerTiming.sleep(remainingMs)]);
|
|
416
484
|
}
|
|
417
485
|
|
|
418
486
|
async function retireUnusableBroker(
|
|
@@ -420,7 +488,7 @@ async function retireUnusableBroker(
|
|
|
420
488
|
settings: EnsureBrokerSettings,
|
|
421
489
|
outerDeadline = Number.POSITIVE_INFINITY,
|
|
422
490
|
): Promise<void> {
|
|
423
|
-
const deadline = Math.min(outerDeadline,
|
|
491
|
+
const deadline = Math.min(outerDeadline, ensureBrokerTiming.now() + BROKER_DISCOVERY_BUDGET.staleRetirementMs);
|
|
424
492
|
let shutdownSucceeded = false;
|
|
425
493
|
try {
|
|
426
494
|
const current = await readBrokerDiscoveryBeforeDeadline(settings.agentDir, settings.heartbeatTtlMs, deadline);
|
|
@@ -443,10 +511,10 @@ async function retireUnusableBroker(
|
|
|
443
511
|
} catch {}
|
|
444
512
|
}
|
|
445
513
|
// Wait for the stale identity to disappear (owner-fenced: pid+incarnation).
|
|
446
|
-
while (
|
|
514
|
+
while (ensureBrokerTiming.now() < deadline) {
|
|
447
515
|
const current = await readBrokerDiscoveryBeforeDeadline(settings.agentDir, settings.heartbeatTtlMs, deadline);
|
|
448
516
|
if (!sameBrokerOwner(current, stale)) break;
|
|
449
|
-
await sleep(STALE_BROKER_POLL_MS);
|
|
517
|
+
await ensureBrokerTiming.sleep(STALE_BROKER_POLL_MS);
|
|
450
518
|
}
|
|
451
519
|
}
|
|
452
520
|
|
|
@@ -459,6 +527,7 @@ export async function reconcileBrokerGenerationForStartup(
|
|
|
459
527
|
if (!current) return undefined;
|
|
460
528
|
if (await isBrokerReusable(current)) return current;
|
|
461
529
|
await retireUnusableBroker(current, settings, deadline);
|
|
530
|
+
await emitBrokerStartupTestSignal("retirement-finished");
|
|
462
531
|
const replacement = await readBrokerDiscoveryBeforeDeadline(settings.agentDir, settings.heartbeatTtlMs, deadline);
|
|
463
532
|
return replacement && (await isBrokerReusable(replacement)) ? replacement : undefined;
|
|
464
533
|
}
|
|
@@ -506,7 +575,7 @@ function createFixtureLease(owner: BrokerOwner, child: ChildProcess): ExactFixtu
|
|
|
506
575
|
|
|
507
576
|
async function ensureBrokerOnce(settings: EnsureBrokerSettings, initiator: EnsureInitiator): Promise<EnsureOutcome> {
|
|
508
577
|
const initialDiscoveryDeadline =
|
|
509
|
-
|
|
578
|
+
ensureBrokerTiming.now() + (initiator === "fixture-lease" ? FIXTURE_DISCOVERY_TIMEOUT_MS : DISCOVERY_TIMEOUT_MS);
|
|
510
579
|
const priorOwner = owners.get(settings.agentDir);
|
|
511
580
|
const existing = await readBrokerDiscoveryBeforeDeadline(
|
|
512
581
|
settings.agentDir,
|
|
@@ -514,7 +583,11 @@ async function ensureBrokerOnce(settings: EnsureBrokerSettings, initiator: Ensur
|
|
|
514
583
|
initialDiscoveryDeadline,
|
|
515
584
|
);
|
|
516
585
|
const restartIntent = await readBrokerRestartIntent(settings.agentDir);
|
|
517
|
-
if (
|
|
586
|
+
if (
|
|
587
|
+
restartIntent &&
|
|
588
|
+
restartIntent.expiresAt > ensureBrokerTiming.now() &&
|
|
589
|
+
restartIntent.requestId !== settings.restartRequestId
|
|
590
|
+
)
|
|
518
591
|
throw new Error("broker_restart_in_progress");
|
|
519
592
|
if (initiator === "fixture-lease" && (priorOwner || existing)) throw fixtureLeaseUnavailable();
|
|
520
593
|
if (priorOwner) {
|
|
@@ -543,9 +616,14 @@ async function ensureBrokerOnce(settings: EnsureBrokerSettings, initiator: Ensur
|
|
|
543
616
|
const releaseSpawnLock = initiator === "fixture-lease" ? async () => {} : await acquireSpawnLock(settings.agentDir);
|
|
544
617
|
try {
|
|
545
618
|
const deadline =
|
|
546
|
-
|
|
619
|
+
ensureBrokerTiming.now() +
|
|
620
|
+
(initiator === "fixture-lease" ? FIXTURE_DISCOVERY_TIMEOUT_MS : DISCOVERY_TIMEOUT_MS);
|
|
547
621
|
const lockedIntent = await readBrokerRestartIntent(settings.agentDir);
|
|
548
|
-
if (
|
|
622
|
+
if (
|
|
623
|
+
lockedIntent &&
|
|
624
|
+
lockedIntent.expiresAt > ensureBrokerTiming.now() &&
|
|
625
|
+
lockedIntent.requestId !== settings.restartRequestId
|
|
626
|
+
)
|
|
549
627
|
throw new Error("broker_restart_in_progress");
|
|
550
628
|
// The lock winner may still find a discovery published by an earlier winner
|
|
551
629
|
// that finished between our first read and the lock acquisition.
|
|
@@ -572,7 +650,7 @@ async function ensureBrokerOnce(settings: EnsureBrokerSettings, initiator: Ensur
|
|
|
572
650
|
// parent's diagnostic handle must not discard exact ownership of a live child.
|
|
573
651
|
await spawnLog?.handle.close().catch(() => undefined);
|
|
574
652
|
let discoveryError: unknown;
|
|
575
|
-
while (
|
|
653
|
+
while (ensureBrokerTiming.now() < deadline) {
|
|
576
654
|
if (spawnError || child.exitCode !== null || child.signalCode !== null) break;
|
|
577
655
|
try {
|
|
578
656
|
const discovered = await readBrokerDiscoveryBeforeDeadline(
|
|
@@ -582,7 +660,7 @@ async function ensureBrokerOnce(settings: EnsureBrokerSettings, initiator: Ensur
|
|
|
582
660
|
);
|
|
583
661
|
if (discovered) {
|
|
584
662
|
if (!(await isBrokerReusable(discovered))) {
|
|
585
|
-
await sleep(50);
|
|
663
|
+
await ensureBrokerTiming.sleep(50);
|
|
586
664
|
continue;
|
|
587
665
|
}
|
|
588
666
|
if (owner.markReady(discovered)) {
|
|
@@ -596,7 +674,7 @@ async function ensureBrokerOnce(settings: EnsureBrokerSettings, initiator: Ensur
|
|
|
596
674
|
} catch (error) {
|
|
597
675
|
discoveryError = error;
|
|
598
676
|
}
|
|
599
|
-
await sleep(50);
|
|
677
|
+
await ensureBrokerTiming.sleep(50);
|
|
600
678
|
}
|
|
601
679
|
const exitedBeforeDiscovery = child.exitCode !== null || child.signalCode !== null;
|
|
602
680
|
if (exitedBeforeDiscovery && child.exitCode === 0) {
|
|
@@ -616,7 +694,7 @@ async function ensureBrokerOnce(settings: EnsureBrokerSettings, initiator: Ensur
|
|
|
616
694
|
await owner.stop();
|
|
617
695
|
return { kind: "external-discovery", discovery: winner };
|
|
618
696
|
}
|
|
619
|
-
await sleep(50);
|
|
697
|
+
await ensureBrokerTiming.sleep(50);
|
|
620
698
|
}
|
|
621
699
|
} catch {
|
|
622
700
|
// fall through to cleanup + failure
|
|
@@ -649,8 +727,8 @@ async function ensureBrokerOnce(settings: EnsureBrokerSettings, initiator: Ensur
|
|
|
649
727
|
// reaches the end of its publication allowance, so keep the parent attached
|
|
650
728
|
// to the exact agent dir and give that incumbent one final bounded chance to
|
|
651
729
|
// publish before treating the replacement failure as terminal.
|
|
652
|
-
const recoveryDeadline = Math.min(deadline,
|
|
653
|
-
while (
|
|
730
|
+
const recoveryDeadline = Math.min(deadline, ensureBrokerTiming.now() + BROKER_DISCOVERY_BUDGET.publicationMs);
|
|
731
|
+
while (ensureBrokerTiming.now() < recoveryDeadline) {
|
|
654
732
|
try {
|
|
655
733
|
const incumbent = await readBrokerDiscoveryBeforeDeadline(
|
|
656
734
|
settings.agentDir,
|
|
@@ -665,7 +743,7 @@ async function ensureBrokerOnce(settings: EnsureBrokerSettings, initiator: Ensur
|
|
|
665
743
|
// Keep the bounded retry alive; a transient read failure is not
|
|
666
744
|
// authority to classify the incumbent as failed.
|
|
667
745
|
}
|
|
668
|
-
await sleep(50);
|
|
746
|
+
await ensureBrokerTiming.sleep(50);
|
|
669
747
|
}
|
|
670
748
|
}
|
|
671
749
|
const spawnLogTail = exitedBeforeDiscovery && spawnLog ? await readBrokerSpawnLogTail(spawnLog.path) : "";
|
|
@@ -43,8 +43,16 @@ export async function deriveIdempotencyIdentity(
|
|
|
43
43
|
agentDir: string,
|
|
44
44
|
operation: string,
|
|
45
45
|
callerKey: string,
|
|
46
|
-
|
|
46
|
+
targetHash?: string,
|
|
47
47
|
): Promise<string> {
|
|
48
|
+
const key = await getBrokerIdentityKey(agentDir);
|
|
49
|
+
return createHmac("sha256", Buffer.from(key, "hex"))
|
|
50
|
+
.update(`4|${operation}|${callerKey}|${targetHash ?? ""}`)
|
|
51
|
+
.digest("hex");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Identity format used by lifecycle rows written before target binding. */
|
|
55
|
+
export async function deriveLegacyIdentity(agentDir: string, operation: string, callerKey: string): Promise<string> {
|
|
48
56
|
const key = await getBrokerIdentityKey(agentDir);
|
|
49
57
|
return createHmac("sha256", Buffer.from(key, "hex")).update(`3|${operation}|${callerKey}`).digest("hex");
|
|
50
58
|
}
|
|
@@ -100,6 +100,32 @@ export type BeginResult =
|
|
|
100
100
|
| { kind: "idempotency_conflict" }
|
|
101
101
|
| { kind: "terminal_uncertain"; entry: LifecycleLedgerEntry }
|
|
102
102
|
| { kind: "in_progress"; entry: LifecycleLedgerEntry };
|
|
103
|
+
|
|
104
|
+
type BoundedSourceReadRejection = "not-regular-file" | "oversized-by-stat" | "oversized-by-read" | "read-error";
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Why a terminal read-back produced no entry. `absent` means nothing terminal is
|
|
108
|
+
* recorded for the request; `rejected` means the source or a row was refused, which
|
|
109
|
+
* is evidence of a damaged ledger rather than of an unpersisted operation. Collapsing
|
|
110
|
+
* the two lets a corrupt source or row read as "not yet written" and unfence the session.
|
|
111
|
+
*/
|
|
112
|
+
export type TerminalReadBackRejection =
|
|
113
|
+
| "bounds"
|
|
114
|
+
| "shape"
|
|
115
|
+
| "undecodable"
|
|
116
|
+
| "row-not-attributable"
|
|
117
|
+
| "partial-row"
|
|
118
|
+
| BoundedSourceReadRejection;
|
|
119
|
+
export type TerminalReadBack =
|
|
120
|
+
| { kind: "terminal"; entry: LifecycleLedgerEntry }
|
|
121
|
+
| { kind: "absent" }
|
|
122
|
+
| { kind: "rejected"; reason: TerminalReadBackRejection };
|
|
123
|
+
|
|
124
|
+
type BoundedSourceRead =
|
|
125
|
+
| { kind: "absent" }
|
|
126
|
+
| { kind: "source"; source: Buffer }
|
|
127
|
+
| { kind: "rejected"; reason: BoundedSourceReadRejection };
|
|
128
|
+
|
|
103
129
|
const terminal = (s: LifecycleState) => s === "terminal_ok" || s === "terminal_error";
|
|
104
130
|
const final = (s: LifecycleState) => terminal(s) || s === "terminal_uncertain";
|
|
105
131
|
export interface LifecycleLedgerLimits {
|
|
@@ -117,6 +143,11 @@ function canonicalCleanupSessionId(value: unknown): value is string {
|
|
|
117
143
|
}
|
|
118
144
|
|
|
119
145
|
const DEFAULT_LIFECYCLE_LEDGER_LIMITS: Required<LifecycleLedgerLimits> = {
|
|
146
|
+
// Terminal evidence intentionally has no time-based expiry: forgetting a
|
|
147
|
+
// completed close would allow a delayed duplicate to reach a later host
|
|
148
|
+
// generation. The row/byte bounds below are storage limits; compaction keeps
|
|
149
|
+
// each identity's accepted anchor and latest terminal state instead of
|
|
150
|
+
// expiring replay authority.
|
|
120
151
|
maxBytes: 64 * 1024 * 1024,
|
|
121
152
|
maxLineBytes: 8 * 1024 * 1024,
|
|
122
153
|
maxRows: 10_000,
|
|
@@ -376,9 +407,11 @@ export class LifecycleLedger {
|
|
|
376
407
|
* uncertainly persists that conclusion, and refusing to read it back would report a
|
|
377
408
|
* synced row as unpersisted and replace its true reason with a generic one.
|
|
378
409
|
*/
|
|
379
|
-
async readTerminal(identity: string, requestHash: string): Promise<
|
|
380
|
-
const
|
|
381
|
-
if (
|
|
410
|
+
async readTerminal(identity: string, requestHash: string): Promise<TerminalReadBack> {
|
|
411
|
+
const sourceRead = await this.#readBoundedSourceReadOnly();
|
|
412
|
+
if (sourceRead.kind === "absent") return sourceRead;
|
|
413
|
+
if (sourceRead.kind === "rejected") return sourceRead;
|
|
414
|
+
const { source } = sourceRead;
|
|
382
415
|
let prior: LifecycleLedgerEntry | undefined;
|
|
383
416
|
let latest: LifecycleLedgerEntry | undefined;
|
|
384
417
|
let rows = 0;
|
|
@@ -391,15 +424,16 @@ export class LifecycleLedger {
|
|
|
391
424
|
lineStart = offset + 1;
|
|
392
425
|
if (line.length === 0) continue;
|
|
393
426
|
rows += 1;
|
|
394
|
-
if (rows > this.#limits.maxRows || line.length > this.#limits.maxLineBytes)
|
|
427
|
+
if (rows > this.#limits.maxRows || line.length > this.#limits.maxLineBytes)
|
|
428
|
+
return { kind: "rejected", reason: "bounds" };
|
|
395
429
|
let entry: LifecycleLedgerEntry;
|
|
396
430
|
try {
|
|
397
431
|
const value = parseLifecycleJson(line);
|
|
398
432
|
assertSupportedStateVersion(this.#file, value);
|
|
399
|
-
if (!isLifecycleLedgerEntry(value)) return
|
|
433
|
+
if (!isLifecycleLedgerEntry(value)) return { kind: "rejected", reason: "shape" };
|
|
400
434
|
entry = value;
|
|
401
435
|
} catch {
|
|
402
|
-
return
|
|
436
|
+
return { kind: "rejected", reason: "undecodable" };
|
|
403
437
|
}
|
|
404
438
|
if (entry.identity !== identity) continue;
|
|
405
439
|
if (
|
|
@@ -407,7 +441,7 @@ export class LifecycleLedger {
|
|
|
407
441
|
!hasValidTerminalDigests(entry) ||
|
|
408
442
|
!this.#isValidHistoryContinuation(prior, entry)
|
|
409
443
|
)
|
|
410
|
-
return
|
|
444
|
+
return { kind: "rejected", reason: "row-not-attributable" };
|
|
411
445
|
prior = entry;
|
|
412
446
|
latest = entry;
|
|
413
447
|
}
|
|
@@ -417,9 +451,11 @@ export class LifecycleLedger {
|
|
|
417
451
|
// a partially persisted row from this identity without inspecting arbitrary
|
|
418
452
|
// malformed data as a valid record.
|
|
419
453
|
const identityMarker = Buffer.from(`"identity":${JSON.stringify(identity)}`);
|
|
420
|
-
if (tail.includes(identityMarker)) return
|
|
454
|
+
if (tail.includes(identityMarker)) return { kind: "rejected", reason: "partial-row" };
|
|
421
455
|
}
|
|
422
|
-
|
|
456
|
+
// No matching row, or one that has not reached a terminal state yet, is genuine
|
|
457
|
+
// absence: nothing was rejected, there is simply nothing terminal to read back.
|
|
458
|
+
return latest && final(latest.state) ? { kind: "terminal", entry: latest } : { kind: "absent" };
|
|
423
459
|
}
|
|
424
460
|
|
|
425
461
|
/**
|
|
@@ -512,19 +548,21 @@ export class LifecycleLedger {
|
|
|
512
548
|
if (handle) await handle.close();
|
|
513
549
|
}
|
|
514
550
|
}
|
|
515
|
-
async #readBoundedSourceReadOnly(): Promise<
|
|
551
|
+
async #readBoundedSourceReadOnly(): Promise<BoundedSourceRead> {
|
|
516
552
|
let handle: fs.FileHandle | undefined;
|
|
517
553
|
try {
|
|
518
|
-
|
|
554
|
+
const nonBlocking = process.platform === "win32" ? 0 : fsSync.constants.O_NONBLOCK;
|
|
555
|
+
handle = await fs.open(this.#file, fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW | nonBlocking);
|
|
519
556
|
const stat = await handle.stat({ bigint: true });
|
|
520
|
-
if (!stat.isFile()
|
|
557
|
+
if (!stat.isFile()) return { kind: "rejected", reason: "not-regular-file" };
|
|
558
|
+
if (stat.size > BigInt(this.#limits.maxBytes)) return { kind: "rejected", reason: "oversized-by-stat" };
|
|
521
559
|
const bytes = Buffer.alloc(Number(stat.size) + 1);
|
|
522
560
|
const { bytesRead } = await handle.read(bytes, 0, bytes.length, 0);
|
|
523
|
-
if (bytesRead > this.#limits.maxBytes) return
|
|
524
|
-
return bytes.subarray(0, bytesRead);
|
|
561
|
+
if (bytesRead > this.#limits.maxBytes) return { kind: "rejected", reason: "oversized-by-read" };
|
|
562
|
+
return { kind: "source", source: bytes.subarray(0, bytesRead) };
|
|
525
563
|
} catch (error) {
|
|
526
|
-
if ((error as NodeJS.ErrnoException).code === "ENOENT") return
|
|
527
|
-
return
|
|
564
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return { kind: "absent" };
|
|
565
|
+
return { kind: "rejected", reason: "read-error" };
|
|
528
566
|
} finally {
|
|
529
567
|
if (handle) await handle.close();
|
|
530
568
|
}
|
|
@@ -621,7 +659,7 @@ export class LifecycleLedger {
|
|
|
621
659
|
for (const [identity, latest] of compacted) {
|
|
622
660
|
const anchor = anchors.get(identity);
|
|
623
661
|
if (!anchor) throw new Error("Lifecycle ledger compaction requires an accepted identity anchor.");
|
|
624
|
-
snapshot.push(anchor);
|
|
662
|
+
snapshot.push(replacement?.identity === identity && latest.state === "accepted" ? latest : anchor);
|
|
625
663
|
if (latest.state !== "accepted") snapshot.push(latest);
|
|
626
664
|
}
|
|
627
665
|
const contents = Buffer.from(snapshot.map(entry => `${JSON.stringify(entry)}\n`).join(""));
|
|
@@ -663,16 +701,24 @@ export class LifecycleLedger {
|
|
|
663
701
|
}
|
|
664
702
|
return replacement !== undefined;
|
|
665
703
|
}
|
|
666
|
-
findByOperationKey(operationKey: string): LifecycleLedgerEntry | undefined {
|
|
667
|
-
return [...this.#byIdentity.values()].
|
|
704
|
+
findByOperationKey(operationKey: string, fingerprint?: string): LifecycleLedgerEntry | undefined {
|
|
705
|
+
return [...this.#byIdentity.values()].findLast(
|
|
706
|
+
entry =>
|
|
707
|
+
entry.operationKey === operationKey && (fingerprint === undefined || entry.fingerprint === fingerprint),
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
findAnyByOperationKey(operationKey: string): LifecycleLedgerEntry | undefined {
|
|
711
|
+
return [...this.#byIdentity.values()].findLast(entry => entry.operationKey === operationKey);
|
|
668
712
|
}
|
|
669
713
|
/**
|
|
670
714
|
* Legacy target-inclusive rows predate the operation/key index. Their opaque
|
|
671
715
|
* identities cannot establish that a different target is safe, so callers
|
|
672
716
|
* must reject rather than create a second admission.
|
|
673
717
|
*/
|
|
674
|
-
hasLegacyIdentity(): boolean {
|
|
675
|
-
return [...this.#byIdentity.values()].some(
|
|
718
|
+
hasLegacyIdentity(excludedIdentities?: ReadonlySet<string>): boolean {
|
|
719
|
+
return [...this.#byIdentity.values()].some(
|
|
720
|
+
entry => entry.operationKey === undefined && !excludedIdentities?.has(entry.identity),
|
|
721
|
+
);
|
|
676
722
|
}
|
|
677
723
|
async migrateIdentity(
|
|
678
724
|
from: string,
|
|
@@ -680,15 +726,32 @@ export class LifecycleLedger {
|
|
|
680
726
|
metadata: { operationKey: string; fingerprint: string },
|
|
681
727
|
): Promise<LifecycleLedgerEntry | undefined> {
|
|
682
728
|
return this.#mutate(async () => {
|
|
683
|
-
|
|
729
|
+
const existing = this.#byIdentity.get(to);
|
|
684
730
|
const entry = this.#byIdentity.get(from);
|
|
685
|
-
if (!entry) return
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
731
|
+
if (!entry) return existing;
|
|
732
|
+
if (existing) {
|
|
733
|
+
if (entry.operationKey === undefined) {
|
|
734
|
+
await this.#compact({ ...entry, ...metadata, ts: Date.now() });
|
|
735
|
+
}
|
|
736
|
+
return existing;
|
|
737
|
+
}
|
|
738
|
+
// A migrated identity needs the same accepted anchor as a fresh request;
|
|
739
|
+
// appending only a terminal row would be quarantined on the next restart.
|
|
740
|
+
await this.#append({
|
|
741
|
+
version: SDK_STATE_VERSION,
|
|
742
|
+
identity: to,
|
|
743
|
+
requestHash: entry.requestHash,
|
|
744
|
+
...metadata,
|
|
745
|
+
state: "accepted",
|
|
746
|
+
ts: Date.now(),
|
|
747
|
+
});
|
|
748
|
+
const migrated =
|
|
749
|
+
entry.state === "accepted"
|
|
750
|
+
? this.#byIdentity.get(to)
|
|
751
|
+
: await this.#append({ ...entry, identity: to, ...metadata, ts: Date.now() });
|
|
752
|
+
// Retire metadata-free legacy rows by rewriting their latest row in place;
|
|
753
|
+
// appending a duplicate terminal row would violate the ledger history rules.
|
|
754
|
+
if (entry.operationKey === undefined) await this.#compact({ ...entry, ...metadata, ts: Date.now() });
|
|
692
755
|
return migrated;
|
|
693
756
|
});
|
|
694
757
|
}
|
|
@@ -723,7 +786,7 @@ export class LifecycleLedger {
|
|
|
723
786
|
async begin(
|
|
724
787
|
identity: string,
|
|
725
788
|
requestHash: string,
|
|
726
|
-
metadata: { operationKey?: string; fingerprint?: string } = {},
|
|
789
|
+
metadata: { operationKey?: string; fingerprint?: string; intendedSessionId?: string } = {},
|
|
727
790
|
): Promise<BeginResult> {
|
|
728
791
|
return this.#mutate(async () => this.#begin(identity, requestHash, metadata));
|
|
729
792
|
}
|
|
@@ -731,7 +794,7 @@ export class LifecycleLedger {
|
|
|
731
794
|
async #begin(
|
|
732
795
|
identity: string,
|
|
733
796
|
requestHash: string,
|
|
734
|
-
metadata: { operationKey?: string; fingerprint?: string },
|
|
797
|
+
metadata: { operationKey?: string; fingerprint?: string; intendedSessionId?: string },
|
|
735
798
|
): Promise<BeginResult> {
|
|
736
799
|
const prior = this.#byIdentity.get(identity);
|
|
737
800
|
if (!prior)
|
|
@@ -743,6 +806,7 @@ export class LifecycleLedger {
|
|
|
743
806
|
requestHash,
|
|
744
807
|
operationKey: metadata.operationKey,
|
|
745
808
|
fingerprint: metadata.fingerprint,
|
|
809
|
+
intendedSessionId: metadata.intendedSessionId,
|
|
746
810
|
state: "accepted",
|
|
747
811
|
ts: Date.now(),
|
|
748
812
|
}),
|