@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/config/file-lock.ts
CHANGED
|
@@ -27,6 +27,7 @@ export interface FileLockOptions {
|
|
|
27
27
|
retryDelayMs?: number;
|
|
28
28
|
signal?: AbortSignal;
|
|
29
29
|
onAcquired?: () => void;
|
|
30
|
+
onContended?: () => void;
|
|
30
31
|
/** Stable host identity required to safely reclaim locks on a shared volume. */
|
|
31
32
|
ownerHostId?: string;
|
|
32
33
|
/** Previous local host identities accepted only when deciding stale-owner reclamation. */
|
|
@@ -84,13 +85,22 @@ export function isFileLockAcquireTimeout(error: unknown): error is FileLockAcqui
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
const DEFAULT_OPTIONS: Required<
|
|
87
|
-
Omit<FileLockOptions, "ownerHostId" | "previousOwnerHostIds" | "signal" | "onAcquired">
|
|
88
|
+
Omit<FileLockOptions, "ownerHostId" | "previousOwnerHostIds" | "signal" | "onAcquired" | "onContended">
|
|
88
89
|
> = {
|
|
89
90
|
staleMs: 10_000,
|
|
90
91
|
retries: 50,
|
|
91
92
|
retryDelayMs: 100,
|
|
92
93
|
};
|
|
93
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Windows can transiently deny a no-replace publication while another handle still
|
|
97
|
+
* has the staged or destination path open without delete sharing. The native result
|
|
98
|
+
* is explicitly pre-mutation in this case, so retrying the same source name cannot
|
|
99
|
+
* publish twice or clean up a committed namespace change.
|
|
100
|
+
*/
|
|
101
|
+
const PUBLICATION_SHARING_RETRY_ATTEMPTS = 3;
|
|
102
|
+
const PUBLICATION_SHARING_RETRY_DELAY_MS = 10;
|
|
103
|
+
|
|
94
104
|
/** Release retries cover transient handle denial and a competing exact-removal quarantine cleanup. */
|
|
95
105
|
export const FILE_LOCK_RELEASE_RETRY_ATTEMPTS = 20;
|
|
96
106
|
export const FILE_LOCK_RELEASE_RETRY_DELAY_MS = 25;
|
|
@@ -231,6 +241,7 @@ type LockInfoFileState = {
|
|
|
231
241
|
size: bigint;
|
|
232
242
|
mtimeNs: bigint;
|
|
233
243
|
ctimeNs: bigint;
|
|
244
|
+
birthtimeNs: bigint;
|
|
234
245
|
nlink: bigint;
|
|
235
246
|
};
|
|
236
247
|
|
|
@@ -245,6 +256,10 @@ type LockInfoPathState = {
|
|
|
245
256
|
|
|
246
257
|
function lockInfoFileState(stats: BigIntStats): LockInfoFileState | null {
|
|
247
258
|
if (stats.isSymbolicLink() || !stats.isFile()) return null;
|
|
259
|
+
// Some supported filesystems report creation time as missing or epoch zero. Keep
|
|
260
|
+
// the stable dev/inode identity and content evidence usable instead of wedging
|
|
261
|
+
// acquire/release/GC on a metadata field the filesystem cannot provide.
|
|
262
|
+
const birthtimeNs = typeof stats.birthtimeNs === "bigint" && stats.birthtimeNs > 0n ? stats.birthtimeNs : 0n;
|
|
248
263
|
return {
|
|
249
264
|
dev: stats.dev,
|
|
250
265
|
ino: stats.ino,
|
|
@@ -252,18 +267,21 @@ function lockInfoFileState(stats: BigIntStats): LockInfoFileState | null {
|
|
|
252
267
|
size: stats.size,
|
|
253
268
|
mtimeNs: stats.mtimeNs,
|
|
254
269
|
ctimeNs: stats.ctimeNs,
|
|
270
|
+
birthtimeNs,
|
|
255
271
|
nlink: stats.nlink,
|
|
256
272
|
};
|
|
257
273
|
}
|
|
258
274
|
|
|
259
275
|
function sameLockInfoFileState(left: LockInfoFileState, right: LockInfoFileState): boolean {
|
|
276
|
+
const birthtimeMatches =
|
|
277
|
+
left.birthtimeNs === 0n || right.birthtimeNs === 0n || left.birthtimeNs === right.birthtimeNs;
|
|
260
278
|
return (
|
|
261
279
|
left.dev === right.dev &&
|
|
262
280
|
left.ino === right.ino &&
|
|
263
281
|
left.mode === right.mode &&
|
|
264
282
|
left.size === right.size &&
|
|
265
283
|
left.mtimeNs === right.mtimeNs &&
|
|
266
|
-
|
|
284
|
+
birthtimeMatches &&
|
|
267
285
|
left.nlink === right.nlink
|
|
268
286
|
);
|
|
269
287
|
}
|
|
@@ -312,6 +330,7 @@ function fileLockDirIdentityFromPathState(state: LockInfoPathState, bytes: strin
|
|
|
312
330
|
infoSize: String(state.file.size),
|
|
313
331
|
infoMtimeNs: String(state.file.mtimeNs),
|
|
314
332
|
infoCtimeNs: String(state.file.ctimeNs),
|
|
333
|
+
infoBirthtimeNs: String(state.file.birthtimeNs),
|
|
315
334
|
infoSha256: crypto.createHash("sha256").update(bytes).digest("hex"),
|
|
316
335
|
};
|
|
317
336
|
}
|
|
@@ -679,6 +698,8 @@ function sameFileLockTreeAfterPublication(
|
|
|
679
698
|
staged: NativeDirectoryTreeSnapshot,
|
|
680
699
|
published: NativeDirectoryTreeSnapshot,
|
|
681
700
|
): boolean {
|
|
701
|
+
// ctime is intentionally omitted: native snapshots expose no birthtime, and a
|
|
702
|
+
// metadata-only change must not reject an otherwise identical published tree.
|
|
682
703
|
return (
|
|
683
704
|
staged.rootDev === published.rootDev &&
|
|
684
705
|
staged.rootIno === published.rootIno &&
|
|
@@ -694,7 +715,6 @@ function sameFileLockTreeAfterPublication(
|
|
|
694
715
|
entry.nlink === current.nlink &&
|
|
695
716
|
entry.size === current.size &&
|
|
696
717
|
entry.mtimeNs === current.mtimeNs &&
|
|
697
|
-
(entry.relativePath === "" || entry.ctimeNs === current.ctimeNs) &&
|
|
698
718
|
entry.sha256 === current.sha256
|
|
699
719
|
);
|
|
700
720
|
})
|
|
@@ -878,6 +898,7 @@ function isValidNativeNoReplaceResult(value: unknown): value is NativeNoReplaceR
|
|
|
878
898
|
"interrupted",
|
|
879
899
|
"identity_violation",
|
|
880
900
|
"durability_not_provable",
|
|
901
|
+
"sharing_violation",
|
|
881
902
|
"unknown",
|
|
882
903
|
] as const
|
|
883
904
|
).includes(value.reason as never) ||
|
|
@@ -953,25 +974,18 @@ function isValidNativeNoReplaceResult(value: unknown): value is NativeNoReplaceR
|
|
|
953
974
|
function isSuccessfulNativePublication(value: unknown, operation: "primary" | "directory"): boolean {
|
|
954
975
|
if (!isValidNativeNoReplaceResult(value) || !value.ok) return false;
|
|
955
976
|
if (operation === "directory") return value.primitive === "mkdirat_renameat_noreplace";
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
return false;
|
|
965
|
-
}
|
|
977
|
+
// Native code records the primitive it actually invoked in the receipt. That
|
|
978
|
+
// primitive is the producer-platform evidence; process.platform is only ambient
|
|
979
|
+
// capability metadata and may be overridden by a coordinator discovery test.
|
|
980
|
+
return (
|
|
981
|
+
value.primitive === "renameat2_noreplace" ||
|
|
982
|
+
value.primitive === "renameatx_np_excl" ||
|
|
983
|
+
value.primitive === "windows_rename_noreplace"
|
|
984
|
+
);
|
|
966
985
|
}
|
|
967
986
|
|
|
968
987
|
function isCommittedDirectoryVerificationFailure(value: unknown): value is NativeNoReplaceResult {
|
|
969
|
-
return (
|
|
970
|
-
process.platform === "linux" &&
|
|
971
|
-
isValidNativeNoReplaceResult(value) &&
|
|
972
|
-
!value.ok &&
|
|
973
|
-
value.mutationState === "committed"
|
|
974
|
-
);
|
|
988
|
+
return isValidNativeNoReplaceResult(value) && !value.ok && value.mutationState === "committed";
|
|
975
989
|
}
|
|
976
990
|
|
|
977
991
|
/**
|
|
@@ -995,6 +1009,35 @@ function isPreMutationUnsupportedRenameResult(value: unknown): value is NativeNo
|
|
|
995
1009
|
return true;
|
|
996
1010
|
}
|
|
997
1011
|
|
|
1012
|
+
function isPreMutationSharingViolation(value: unknown): value is NativeNoReplaceResult {
|
|
1013
|
+
return (
|
|
1014
|
+
isValidNativeNoReplaceResult(value) &&
|
|
1015
|
+
!value.ok &&
|
|
1016
|
+
value.code === "sharing_violation" &&
|
|
1017
|
+
value.mutationState === "not_committed" &&
|
|
1018
|
+
value.durabilityState === "not_attempted" &&
|
|
1019
|
+
value.reason === "sharing_violation" &&
|
|
1020
|
+
value.phase === "rename"
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
async function publishNoReplaceWithSharingRetry(
|
|
1025
|
+
publish: (sourcePath: string, destinationPath: string) => Promise<NativeNoReplaceResult>,
|
|
1026
|
+
sourcePath: string,
|
|
1027
|
+
destinationPath: string,
|
|
1028
|
+
): Promise<NativeNoReplaceResult> {
|
|
1029
|
+
let result = await publish(sourcePath, destinationPath);
|
|
1030
|
+
for (
|
|
1031
|
+
let attempt = 1;
|
|
1032
|
+
attempt < PUBLICATION_SHARING_RETRY_ATTEMPTS && isPreMutationSharingViolation(result);
|
|
1033
|
+
attempt++
|
|
1034
|
+
) {
|
|
1035
|
+
await Bun.sleep(PUBLICATION_SHARING_RETRY_DELAY_MS * attempt);
|
|
1036
|
+
result = await publish(sourcePath, destinationPath);
|
|
1037
|
+
}
|
|
1038
|
+
return result;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
998
1041
|
async function localLockKey(lockPath: string): Promise<string> {
|
|
999
1042
|
try {
|
|
1000
1043
|
return normalizeLockKey(await fs.realpath(lockPath));
|
|
@@ -1033,7 +1076,14 @@ type LockStaleSnapshot =
|
|
|
1033
1076
|
| { stale: false }
|
|
1034
1077
|
| { stale: true; owner: FileLockOwnerToken; identity: GenericFileLockDirIdentity };
|
|
1035
1078
|
|
|
1036
|
-
/**
|
|
1079
|
+
/**
|
|
1080
|
+
* Identity evidence carried by the generic stale verdict into a later removal.
|
|
1081
|
+
*
|
|
1082
|
+
* `ctimeNs` is a mutable metadata-change time on every supported filesystem, so
|
|
1083
|
+
* it is retained for diagnostics/legacy consumers but is not part of the lock
|
|
1084
|
+
* identity predicate. `infoBirthtimeNs` is the creation time that stays bound
|
|
1085
|
+
* to the file object.
|
|
1086
|
+
*/
|
|
1037
1087
|
export interface GenericFileLockDirIdentity {
|
|
1038
1088
|
rootDev: string;
|
|
1039
1089
|
rootIno: string;
|
|
@@ -1043,23 +1093,34 @@ export interface GenericFileLockDirIdentity {
|
|
|
1043
1093
|
infoSize: string;
|
|
1044
1094
|
infoMtimeNs: string;
|
|
1045
1095
|
infoCtimeNs: string;
|
|
1096
|
+
infoBirthtimeNs: string;
|
|
1046
1097
|
infoSha256: string;
|
|
1047
1098
|
}
|
|
1048
1099
|
|
|
1049
|
-
function
|
|
1100
|
+
function sameStableFileLockIdentity(left: GenericFileLockDirIdentity, right: GenericFileLockDirIdentity): boolean {
|
|
1050
1101
|
return (
|
|
1051
1102
|
left.rootDev === right.rootDev &&
|
|
1052
1103
|
left.rootIno === right.rootIno &&
|
|
1053
1104
|
left.infoDev === right.infoDev &&
|
|
1054
1105
|
left.infoIno === right.infoIno &&
|
|
1106
|
+
left.infoBirthtimeNs === right.infoBirthtimeNs
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/** Content/topology evidence kept separate from the stable file identity. */
|
|
1111
|
+
function sameFileLockContentEvidence(left: GenericFileLockDirIdentity, right: GenericFileLockDirIdentity): boolean {
|
|
1112
|
+
return (
|
|
1055
1113
|
left.infoNlink === right.infoNlink &&
|
|
1056
1114
|
left.infoSize === right.infoSize &&
|
|
1057
1115
|
left.infoMtimeNs === right.infoMtimeNs &&
|
|
1058
|
-
left.infoCtimeNs === right.infoCtimeNs &&
|
|
1059
1116
|
left.infoSha256 === right.infoSha256
|
|
1060
1117
|
);
|
|
1061
1118
|
}
|
|
1062
1119
|
|
|
1120
|
+
function sameGenericFileLockDirIdentity(left: GenericFileLockDirIdentity, right: GenericFileLockDirIdentity): boolean {
|
|
1121
|
+
return sameStableFileLockIdentity(left, right) && sameFileLockContentEvidence(left, right);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1063
1124
|
let nativeExactRemovalUsable: boolean | undefined;
|
|
1064
1125
|
let nativeExactRemovalProbePromise: Promise<boolean> | undefined;
|
|
1065
1126
|
|
|
@@ -1170,26 +1231,54 @@ async function removeVerifiedOwnedLockDirWithoutNative(
|
|
|
1170
1231
|
return "cleanup_failed";
|
|
1171
1232
|
}
|
|
1172
1233
|
const current = await captureFileLockDirIdentity(lockDir);
|
|
1173
|
-
if (!current || !
|
|
1234
|
+
if (!current || !sameStableFileLockIdentity(current, expected)) return "owner_changed";
|
|
1174
1235
|
const captured = nativeFileLockBindings().snapshotDirectoryTree(lockDir);
|
|
1175
1236
|
if (!captured.ok || !captured.snapshot) return "owner_changed";
|
|
1176
1237
|
const infoEntry = captured.snapshot.entries.find(entry => entry.relativePath === "info");
|
|
1177
1238
|
if (
|
|
1178
|
-
captured.snapshot.rootDev !== expected.rootDev ||
|
|
1179
|
-
captured.snapshot.rootIno !== expected.rootIno ||
|
|
1180
1239
|
!infoEntry ||
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
infoEntry
|
|
1240
|
+
!nativeFileLockInfoMatchesStableIdentity(
|
|
1241
|
+
captured.snapshot.rootDev,
|
|
1242
|
+
captured.snapshot.rootIno,
|
|
1243
|
+
infoEntry,
|
|
1244
|
+
expected,
|
|
1245
|
+
) ||
|
|
1246
|
+
!nativeFileLockInfoMatchesContentEvidence(infoEntry, expected)
|
|
1188
1247
|
)
|
|
1189
1248
|
return "owner_changed";
|
|
1190
1249
|
return await removeVerifiedLockDirWithoutNative(lockDir, captured.snapshot, owner);
|
|
1191
1250
|
}
|
|
1192
1251
|
|
|
1252
|
+
function nativeFileLockInfoMatchesStableIdentity(
|
|
1253
|
+
rootDev: string,
|
|
1254
|
+
rootIno: string,
|
|
1255
|
+
infoEntry: NativeDirectoryTreeSnapshot["entries"][number],
|
|
1256
|
+
expected: GenericFileLockDirIdentity,
|
|
1257
|
+
): boolean {
|
|
1258
|
+
return (
|
|
1259
|
+
rootDev === expected.rootDev &&
|
|
1260
|
+
rootIno === expected.rootIno &&
|
|
1261
|
+
infoEntry.dev === expected.infoDev &&
|
|
1262
|
+
infoEntry.ino === expected.infoIno
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function nativeFileLockInfoMatchesContentEvidence(
|
|
1267
|
+
infoEntry: NativeDirectoryTreeSnapshot["entries"][number],
|
|
1268
|
+
expected: GenericFileLockDirIdentity,
|
|
1269
|
+
): boolean {
|
|
1270
|
+
// Native snapshots expose a ctime-shaped field, but ctime is a mutable
|
|
1271
|
+
// metadata-change timestamp on every supported filesystem. The stable file
|
|
1272
|
+
// identity is checked separately; requiring ctime here would reject a
|
|
1273
|
+
// legitimate lock after chmod/ACL/indexer activity.
|
|
1274
|
+
return (
|
|
1275
|
+
infoEntry.nlink === expected.infoNlink &&
|
|
1276
|
+
infoEntry.size === expected.infoSize &&
|
|
1277
|
+
infoEntry.mtimeNs === expected.infoMtimeNs &&
|
|
1278
|
+
infoEntry.sha256 === expected.infoSha256
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1193
1282
|
export type GenericFileLockDirStaleVerdict = { stale: false } | { stale: true; identity: GenericFileLockDirIdentity };
|
|
1194
1283
|
|
|
1195
1284
|
/**
|
|
@@ -1253,17 +1342,17 @@ export async function removeFileLockDirForGc(
|
|
|
1253
1342
|
if (!infoEntry?.sha256) return "owner_changed";
|
|
1254
1343
|
const judgedDigest = crypto.createHash("sha256").update(onDiskBytes).digest("hex");
|
|
1255
1344
|
if (infoEntry.sha256 !== judgedDigest) return "owner_changed";
|
|
1345
|
+
const currentIdentity = await captureFileLockDirIdentity(nativeCapturePath);
|
|
1346
|
+
if (!currentIdentity || !sameStableFileLockIdentity(currentIdentity, expectedIdentity)) return "owner_changed";
|
|
1256
1347
|
if (
|
|
1257
1348
|
!captured.snapshot.rootDev ||
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
infoEntry
|
|
1265
|
-
infoEntry.ctimeNs !== expectedIdentity.infoCtimeNs ||
|
|
1266
|
-
infoEntry.sha256 !== expectedIdentity.infoSha256
|
|
1349
|
+
!nativeFileLockInfoMatchesStableIdentity(
|
|
1350
|
+
captured.snapshot.rootDev,
|
|
1351
|
+
captured.snapshot.rootIno,
|
|
1352
|
+
infoEntry,
|
|
1353
|
+
expectedIdentity,
|
|
1354
|
+
) ||
|
|
1355
|
+
!nativeFileLockInfoMatchesContentEvidence(infoEntry, expectedIdentity)
|
|
1267
1356
|
)
|
|
1268
1357
|
return "owner_changed";
|
|
1269
1358
|
let removed: NativeExactUnlinkResult;
|
|
@@ -1363,6 +1452,28 @@ function ownerLiveness(pid: number): OwnerLiveness {
|
|
|
1363
1452
|
}
|
|
1364
1453
|
}
|
|
1365
1454
|
|
|
1455
|
+
/**
|
|
1456
|
+
* Does this owner record belong to a machine other than the acquirer's?
|
|
1457
|
+
*
|
|
1458
|
+
* A host-qualified record is foreign unless the acquirer can prove it is its own:
|
|
1459
|
+
* either the identity matches, or it matches an identity this installation used
|
|
1460
|
+
* before. An acquirer carrying no host identity can prove nothing, so every
|
|
1461
|
+
* host-qualified record stays foreign to it. Foreign records fail closed — their
|
|
1462
|
+
* PID values and clocks are not meaningful here, so they are neither reclaimable
|
|
1463
|
+
* nor locally probeable for liveness.
|
|
1464
|
+
*
|
|
1465
|
+
* Shared by the reclamation verdict and the exhaustion diagnostic so the two can
|
|
1466
|
+
* never disagree about whose pid a record names.
|
|
1467
|
+
*/
|
|
1468
|
+
function lockRecordIsForeignHost(
|
|
1469
|
+
info: LockInfo,
|
|
1470
|
+
ownerHostId: string | undefined,
|
|
1471
|
+
previousOwnerHostIds: readonly string[],
|
|
1472
|
+
): boolean {
|
|
1473
|
+
if (ownerHostId === undefined) return info.owner_host_id !== undefined;
|
|
1474
|
+
return info.owner_host_id !== ownerHostId && !previousOwnerHostIds.includes(info.owner_host_id ?? "");
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1366
1477
|
async function staleLockSnapshot(
|
|
1367
1478
|
lockPath: string,
|
|
1368
1479
|
_staleMs: number,
|
|
@@ -1402,13 +1513,7 @@ async function staleLockSnapshot(
|
|
|
1402
1513
|
// A host-qualified lock may only be reclaimed after proving that its owner is
|
|
1403
1514
|
// local. Foreign and malformed host-qualified records fail closed: PID values
|
|
1404
1515
|
// and clocks are not meaningful across hosts.
|
|
1405
|
-
if (
|
|
1406
|
-
ownerHostId !== undefined &&
|
|
1407
|
-
info.owner_host_id !== ownerHostId &&
|
|
1408
|
-
!previousOwnerHostIds.includes(info.owner_host_id ?? "")
|
|
1409
|
-
)
|
|
1410
|
-
return { stale: false };
|
|
1411
|
-
if (ownerHostId === undefined && info.owner_host_id !== undefined) return { stale: false };
|
|
1516
|
+
if (lockRecordIsForeignHost(info, ownerHostId, previousOwnerHostIds)) return { stale: false };
|
|
1412
1517
|
if (ownerIncarnationChanged(info, startTimeCache)) {
|
|
1413
1518
|
if (!judgedIdentity) return { stale: false };
|
|
1414
1519
|
let currentIdentity: GenericFileLockDirIdentity | null;
|
|
@@ -1619,12 +1724,20 @@ async function tryAcquireLock(
|
|
|
1619
1724
|
renameNoReplacePathAsync,
|
|
1620
1725
|
renameDirectoryNoReplacePathAsync,
|
|
1621
1726
|
};
|
|
1622
|
-
const published = await
|
|
1727
|
+
const published = await publishNoReplaceWithSharingRetry(
|
|
1728
|
+
publication.renameNoReplacePathAsync,
|
|
1729
|
+
canonicalPendingPath,
|
|
1730
|
+
destinationPath,
|
|
1731
|
+
);
|
|
1623
1732
|
let publishedSuccessfully = isSuccessfulNativePublication(published, "primary");
|
|
1624
1733
|
if (published.ok && !publishedSuccessfully)
|
|
1625
1734
|
throw new Error("Failed to publish file lock: invalid primary success receipt.");
|
|
1626
1735
|
if (!published.ok && isPreMutationUnsupportedRenameResult(published)) {
|
|
1627
|
-
const fallback = await
|
|
1736
|
+
const fallback = await publishNoReplaceWithSharingRetry(
|
|
1737
|
+
publication.renameDirectoryNoReplacePathAsync,
|
|
1738
|
+
canonicalPendingPath,
|
|
1739
|
+
destinationPath,
|
|
1740
|
+
);
|
|
1628
1741
|
const fallbackSuccessfully = isSuccessfulNativePublication(fallback, "directory");
|
|
1629
1742
|
if (fallback.ok && !fallbackSuccessfully)
|
|
1630
1743
|
throw new Error("Failed to publish file lock: invalid directory success receipt.");
|
|
@@ -1783,16 +1896,16 @@ async function quarantineReleasedLock(
|
|
|
1783
1896
|
if (!captured.ok || !captured.snapshot) return false;
|
|
1784
1897
|
const infoEntry = captured.snapshot.entries.find(entry => entry.relativePath === "info");
|
|
1785
1898
|
if (!infoEntry?.sha256) return false;
|
|
1899
|
+
const currentIdentity = await captureFileLockDirIdentity(nativeCapturePath);
|
|
1900
|
+
if (!currentIdentity || !sameStableFileLockIdentity(currentIdentity, expectedIdentity)) return false;
|
|
1786
1901
|
if (
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
infoEntry
|
|
1794
|
-
infoEntry.ctimeNs !== expectedIdentity.infoCtimeNs ||
|
|
1795
|
-
infoEntry.sha256 !== expectedIdentity.infoSha256
|
|
1902
|
+
!nativeFileLockInfoMatchesStableIdentity(
|
|
1903
|
+
captured.snapshot.rootDev,
|
|
1904
|
+
captured.snapshot.rootIno,
|
|
1905
|
+
infoEntry,
|
|
1906
|
+
expectedIdentity,
|
|
1907
|
+
) ||
|
|
1908
|
+
!nativeFileLockInfoMatchesContentEvidence(infoEntry, expectedIdentity)
|
|
1796
1909
|
)
|
|
1797
1910
|
return false;
|
|
1798
1911
|
// Bind the owner generation to the snapshot before exact removal. A successor
|
|
@@ -1962,14 +2075,22 @@ async function lockHolderDescription(
|
|
|
1962
2075
|
info = bytes === null ? null : parseLockInfoBytes(bytes);
|
|
1963
2076
|
}
|
|
1964
2077
|
if (info) {
|
|
1965
|
-
// A lock record carrying a
|
|
2078
|
+
// A lock record carrying a FOREIGN owner_host_id belongs to another
|
|
1966
2079
|
// machine (shared-volume topic registry): its pid is meaningful only
|
|
1967
2080
|
// on that host, so probing the same numeric pid here could mislabel a
|
|
1968
2081
|
// coincident local process as the holder. Report the owner host with
|
|
1969
|
-
// unknown liveness instead.
|
|
1970
|
-
|
|
2082
|
+
// unknown liveness instead. The same predicate that decides whether
|
|
2083
|
+
// the record is reclaimable decides whether its pid is probeable, so a
|
|
2084
|
+
// holder this acquirer *may* reclaim is never described as opaque.
|
|
2085
|
+
if (lockRecordIsForeignHost(info, ownerHostId, previousOwnerHostIds)) {
|
|
2086
|
+
// An unqualified record carries no host provenance at all. A host-aware
|
|
2087
|
+
// acquirer still fails closed on it — its pid is never probed and its
|
|
2088
|
+
// lock is never reclaimed — but the diagnostic must say the provenance
|
|
2089
|
+
// is missing instead of naming a host it does not have.
|
|
2090
|
+
const provenance =
|
|
2091
|
+
info.owner_host_id === undefined ? "on an unrecorded host" : `on host ${info.owner_host_id}`;
|
|
1971
2092
|
return (
|
|
1972
|
-
`held by pid ${info.pid}
|
|
2093
|
+
`held by pid ${info.pid} ${provenance} (liveness unknown from this host)` +
|
|
1973
2094
|
` since ${new Date(info.timestamp).toISOString()}`
|
|
1974
2095
|
);
|
|
1975
2096
|
}
|
|
@@ -1977,13 +2098,18 @@ async function lockHolderDescription(
|
|
|
1977
2098
|
// record carries a start_time, the start-time identity match) so a dead
|
|
1978
2099
|
// holder whose pid was already reused is not mislabeled "(live)".
|
|
1979
2100
|
const alive = ownerIsAlive(info);
|
|
2101
|
+
const liveness = alive
|
|
2102
|
+
? "live"
|
|
2103
|
+
: ownerLiveness(info.pid) === "dead"
|
|
2104
|
+
? "dead but not reaped"
|
|
2105
|
+
: "liveness unknown";
|
|
1980
2106
|
return (
|
|
1981
2107
|
`held by pid ${info.pid}` +
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
2108
|
+
// Keep the host on a host-qualified local record: on a shared volume the
|
|
2109
|
+
// same pathname is contended from several hosts, so naming the one whose
|
|
2110
|
+
// pid space this verdict came from is what makes it actionable.
|
|
2111
|
+
(info.owner_host_id === undefined ? "" : " on this host") +
|
|
2112
|
+
` (${liveness})` +
|
|
1987
2113
|
` since ${new Date(info.timestamp).toISOString()}`
|
|
1988
2114
|
);
|
|
1989
2115
|
}
|
|
@@ -2022,6 +2148,7 @@ export async function acquireFileLock(filePath: string, options: FileLockOptions
|
|
|
2022
2148
|
}
|
|
2023
2149
|
const ownerToken = crypto.randomUUID();
|
|
2024
2150
|
const contentionStartTimes = new Map<string, string | null>();
|
|
2151
|
+
let contentionObserved = false;
|
|
2025
2152
|
let staleRemovalFailure: RecordedStaleRemovalFailure | undefined;
|
|
2026
2153
|
for (let attempt = 0; attempt < opts.retries; attempt++) {
|
|
2027
2154
|
if (opts.signal?.aborted) throw opts.signal.reason ?? new Error("File lock acquisition aborted");
|
|
@@ -2052,6 +2179,10 @@ export async function acquireFileLock(filePath: string, options: FileLockOptions
|
|
|
2052
2179
|
localLockStates.set(localKey, { owner: result, status: "held" });
|
|
2053
2180
|
return () => releaseLock(lockPath, result, localKey);
|
|
2054
2181
|
}
|
|
2182
|
+
if (!contentionObserved) {
|
|
2183
|
+
contentionObserved = true;
|
|
2184
|
+
opts.onContended?.();
|
|
2185
|
+
}
|
|
2055
2186
|
const pendingKey = await pendingLocalReleaseKey(lockPath, localKey);
|
|
2056
2187
|
const localState = localLockStates.get(pendingKey ?? localKey);
|
|
2057
2188
|
if (pendingKey !== undefined && localState?.status !== "held" && localState?.owner.owner_token !== undefined) {
|