create-walle 0.10.0 → 0.10.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 +88 -41
- package/lib/owner-write-safety.js +135 -0
- package/lib/public-runtime.js +13 -5
- package/lib/release-schema.json +3 -2
- package/lib/runtime-closure-policy.json +7 -5
- package/package.json +10 -4
- package/template/bin/deps-doctor.js +1 -1
- package/template/bin/node-bin.sh +11 -0
- package/template/coding-task-manager/api-prompts.js +10 -22
- package/template/coding-task-manager/bin/capture-codex-rollout-records.js +417 -0
- package/template/coding-task-manager/bin/census-codex-lead-history.js +325 -0
- package/template/coding-task-manager/bin/claude-hook.js +7 -3
- package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
- package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
- package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
- package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
- package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
- package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
- package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
- package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
- package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
- package/template/coding-task-manager/bin/seam-replay-codex.js +301 -0
- package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
- package/template/coding-task-manager/db.js +705 -286
- package/template/coding-task-manager/git-utils.js +44 -0
- package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
- package/template/coding-task-manager/lib/agent-presets.js +15 -0
- package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
- package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
- package/template/coding-task-manager/lib/api/host-stats.js +98 -0
- package/template/coding-task-manager/lib/api/ingress.js +35 -12
- package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
- package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
- package/template/coding-task-manager/lib/api/machines.js +461 -0
- package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
- package/template/coding-task-manager/lib/api/providers.js +53 -6
- package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
- package/template/coding-task-manager/lib/api/sessions.js +160 -8
- package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
- package/template/coding-task-manager/lib/api/worktrees.js +38 -37
- package/template/coding-task-manager/lib/approval-drift.js +16 -1
- package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
- package/template/coding-task-manager/lib/approval-scope.js +68 -4
- package/template/coding-task-manager/lib/auth-context.js +32 -2
- package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
- package/template/coding-task-manager/lib/auth-rules.js +186 -45
- package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
- package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
- package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
- package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
- package/template/coding-task-manager/lib/claude-session-title.js +6 -30
- package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
- package/template/coding-task-manager/lib/client-diag-handler.js +25 -0
- package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
- package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
- package/template/coding-task-manager/lib/codex-history-terminal-renderer.js +33 -33
- package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
- package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
- package/template/coding-task-manager/lib/codex-local-links.js +267 -0
- package/template/coding-task-manager/lib/codex-markdown-lists.js +160 -0
- package/template/coding-task-manager/lib/codex-native-history-prefix.js +4 -0
- package/template/coding-task-manager/lib/codex-paths.js +2 -1
- package/template/coding-task-manager/lib/codex-source-handoff.js +186 -0
- package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
- package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
- package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
- package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
- package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
- package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
- package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
- package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
- package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
- package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
- package/template/coding-task-manager/lib/db-domains.js +21 -2
- package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
- package/template/coding-task-manager/lib/db-owner-worker-client.js +22 -0
- package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
- package/template/coding-task-manager/lib/feature-manifest.js +1 -3
- package/template/coding-task-manager/lib/fs-watcher.js +3 -2
- package/template/coding-task-manager/lib/harness-history-transition.js +41 -14
- package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
- package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
- package/template/coding-task-manager/lib/headless-term-service.js +60 -23
- package/template/coding-task-manager/lib/history-transition-read-op.js +179 -23
- package/template/coding-task-manager/lib/host-control-dial.js +167 -0
- package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
- package/template/coding-task-manager/lib/host-handoff.js +51 -13
- package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
- package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
- package/template/coding-task-manager/lib/host-stats.js +328 -0
- package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
- package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
- package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
- package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
- package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
- package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
- package/template/coding-task-manager/lib/instance-manifest.js +13 -1
- package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
- package/template/coding-task-manager/lib/kernel/diag-log.js +12 -0
- package/template/coding-task-manager/lib/launch-model-override.js +160 -1
- package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
- package/template/coding-task-manager/lib/lead-model-observer.js +17 -2
- package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
- package/template/coding-task-manager/lib/lead-model-switch.js +11 -2
- package/template/coding-task-manager/lib/lead-model-transition-runtime.js +42 -4
- package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
- package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
- package/template/coding-task-manager/lib/login-tripwire.js +18 -0
- package/template/coding-task-manager/lib/machine-pairing.js +238 -0
- package/template/coding-task-manager/lib/machine-pool.js +409 -0
- package/template/coding-task-manager/lib/machine-registry.js +749 -0
- package/template/coding-task-manager/lib/machine-self.js +66 -0
- package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +56 -4
- package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
- package/template/coding-task-manager/lib/media-journal.js +84 -27
- package/template/coding-task-manager/lib/media-store-options.js +43 -15
- package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
- package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
- package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +16 -3
- package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
- package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
- package/template/coding-task-manager/lib/model-proxy/collaboration-codec.js +94 -0
- package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
- package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
- package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
- package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
- package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
- package/template/coding-task-manager/lib/model-proxy/responses-face.js +24 -2
- package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
- package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
- package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
- package/template/coding-task-manager/lib/native-history-control.js +62 -14
- package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
- package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
- package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
- package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
- package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
- package/template/coding-task-manager/lib/prompt-admission-trace.js +69 -0
- package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
- package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
- package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
- package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
- package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
- package/template/coding-task-manager/lib/raw-history-block.js +3 -1
- package/template/coding-task-manager/lib/read-pool-client.js +36 -24
- package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
- package/template/coding-task-manager/lib/read-pool-telemetry.js +14 -5
- package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
- package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
- package/template/coding-task-manager/lib/remote-command-context.js +12 -0
- package/template/coding-task-manager/lib/remote-command-runtime.js +18 -5
- package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
- package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
- package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
- package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
- package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
- package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
- package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
- package/template/coding-task-manager/lib/restart-preflight.js +9 -2
- package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
- package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
- package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
- package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
- package/template/coding-task-manager/lib/resume-cwd.js +13 -0
- package/template/coding-task-manager/lib/resume-from-store.js +12 -1
- package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
- package/template/coding-task-manager/lib/resume-preflight.js +21 -16
- package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
- package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
- package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
- package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
- package/template/coding-task-manager/lib/root-prompt-admission.js +43 -11
- package/template/coding-task-manager/lib/root-prompt-flow.js +175 -38
- package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
- package/template/coding-task-manager/lib/server-memory-sampler-runtime.js +10 -0
- package/template/coding-task-manager/lib/server-memory-sampler.js +41 -2
- package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
- package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
- package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
- package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
- package/template/coding-task-manager/lib/session-create/identity.js +20 -1
- package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
- package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
- package/template/coding-task-manager/lib/session-create/provider-process.js +38 -39
- package/template/coding-task-manager/lib/session-create/response.js +30 -12
- package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
- package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
- package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
- package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
- package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
- package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
- package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
- package/template/coding-task-manager/lib/session-host-manager.js +152 -206
- package/template/coding-task-manager/lib/session-jobs.js +17 -9
- package/template/coding-task-manager/lib/session-lifecycle.js +20 -50
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +68 -3
- package/template/coding-task-manager/lib/session-resource-sampler.js +67 -9
- package/template/coding-task-manager/lib/session-standup.js +67 -4
- package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
- package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
- package/template/coding-task-manager/lib/session-status.js +2 -2
- package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
- package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
- package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
- package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
- package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
- package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
- package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
- package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
- package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
- package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
- package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
- package/template/coding-task-manager/lib/transcript-link.js +77 -0
- package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
- package/template/coding-task-manager/lib/transcript-source.js +8 -3
- package/template/coding-task-manager/lib/transcript-store.js +40 -0
- package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
- package/template/coding-task-manager/lib/typing-interaction-trace.js +136 -2
- package/template/coding-task-manager/lib/typing-worker-stages.js +208 -1
- package/template/coding-task-manager/lib/usage-ledger/claude-parse.js +53 -2
- package/template/coding-task-manager/lib/usage-ledger/ingest.js +13 -4
- package/template/coding-task-manager/lib/usage-ledger/read.js +14 -0
- package/template/coding-task-manager/lib/usage-ledger/schema.js +49 -1
- package/template/coding-task-manager/lib/usage-ledger/store.js +337 -6
- package/template/coding-task-manager/lib/usage-ledger/task-read.js +123 -5
- package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
- package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
- package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
- package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
- package/template/coding-task-manager/lib/ws/input.js +1 -0
- package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
- package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
- package/template/coding-task-manager/lib/ws-router.js +68 -1
- package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
- package/template/coding-task-manager/oauth-proxy.js +99 -55
- package/template/coding-task-manager/package-lock.json +24 -0
- package/template/coding-task-manager/package.json +3 -0
- package/template/coding-task-manager/protocol/message-table.json +0 -10
- package/template/coding-task-manager/protocol/messages.js +0 -10
- package/template/coding-task-manager/protocol/ws-schema.json +0 -20
- package/template/coding-task-manager/protocol/ws-schema.md +0 -2
- package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
- package/template/coding-task-manager/providers/codex-mcp.js +14 -5
- package/template/coding-task-manager/public/css/app.css +279 -2
- package/template/coding-task-manager/public/css/reviews.css +4 -0
- package/template/coding-task-manager/public/index.html +297 -240
- package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
- package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
- package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
- package/template/coding-task-manager/public/js/background-work.js +13 -7
- package/template/coding-task-manager/public/js/client-health-core.js +81 -0
- package/template/coding-task-manager/public/js/client-health-main.js +61 -0
- package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
- package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
- package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
- package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
- package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
- package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
- package/template/coding-task-manager/public/js/history-scroller.js +187 -9
- package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
- package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
- package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
- package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -4
- package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
- package/template/coding-task-manager/public/js/protocol.js +0 -10
- package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
- package/template/coding-task-manager/public/js/reviews.js +7 -3
- package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
- package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
- package/template/coding-task-manager/public/js/row-join.js +18 -3
- package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
- package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
- package/template/coding-task-manager/public/js/session-usage-view.js +1 -0
- package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
- package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
- package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
- package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
- package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
- package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
- package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
- package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
- package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
- package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
- package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
- package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -3
- package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
- package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
- package/template/coding-task-manager/public/js/walle-session.js +30 -7
- package/template/coding-task-manager/public/m/app.css +226 -8
- package/template/coding-task-manager/public/m/app.js +502 -9
- package/template/coding-task-manager/public/m/index.html +38 -1
- package/template/coding-task-manager/public/m/machines.js +1313 -0
- package/template/coding-task-manager/public/m/pool-failover.js +391 -0
- package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
- package/template/coding-task-manager/public/m/sw.js +5 -0
- package/template/coding-task-manager/queue-engine.js +13 -0
- package/template/coding-task-manager/server/approvals.js +5 -6
- package/template/coding-task-manager/server/boot.js +136 -17
- package/template/coding-task-manager/server.js +964 -425
- package/template/coding-task-manager/session-integrity.js +7 -9
- package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
- package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
- package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
- package/template/coding-task-manager/workers/session-host-process.js +14 -3
- package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
- package/template/coding-task-manager/workers/worktree-worker.js +34 -0
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/scripts/conversation-storage-v2-migration.js +78 -11
- package/template/shared/conversation-storage-v2-contract.js +42 -6
- package/template/shared/fixtures/worker-status-payloads.json +392 -0
- package/template/shared/git-remote-key.js +83 -0
- package/template/shared/host-identity.js +98 -2
- package/template/shared/owner-write-safety.js +135 -0
- package/template/shared/public-runtime.js +13 -5
- package/template/shared/release-schema.json +3 -2
- package/template/shared/report-citations.js +14 -0
- package/template/shared/sqlite-write-lock.js +361 -223
- package/template/shared/storage/contract.js +35 -3
- package/template/wall-e/agent.js +21 -0
- package/template/wall-e/api-walle.js +5 -0
- package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
- package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
- package/template/wall-e/brain/helper-rules-schema.js +129 -0
- package/template/wall-e/brain/migrations.js +51 -22
- package/template/wall-e/brain/model-deployment-facts.js +31 -1
- package/template/wall-e/brain/owner-call-whitelist.js +16 -0
- package/template/wall-e/brain/schema-ensures.js +22 -0
- package/template/wall-e/brain.js +1 -1
- package/template/wall-e/coding/host-capabilities.js +2 -2
- package/template/wall-e/coding/journal-dispatcher.js +51 -0
- package/template/wall-e/coding/session-journal.js +28 -40
- package/template/wall-e/coding-orchestrator.js +23 -26
- package/template/wall-e/fly.toml +1 -1
- package/template/wall-e/http/auth.js +18 -1
- package/template/wall-e/http/blog-views-api.js +318 -0
- package/template/wall-e/http/helper-rules-api.js +321 -0
- package/template/wall-e/http/model-proxy-providers.js +3 -1
- package/template/wall-e/http/telemetry-api.js +12 -0
- package/template/wall-e/lib/blog-view-policy.js +88 -0
- package/template/wall-e/lib/blog-view-store.js +169 -0
- package/template/wall-e/lib/client-config-lock.js +2 -1
- package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
- package/template/wall-e/lib/error-kinds.js +10 -1
- package/template/wall-e/lib/feature-manifest.js +1 -1
- package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
- package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
- package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
- package/template/wall-e/lib/helper-rules/grade.js +277 -0
- package/template/wall-e/lib/helper-rules/label.js +247 -0
- package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
- package/template/wall-e/lib/helper-rules/store.js +667 -0
- package/template/wall-e/lib/mcp-integration.js +5 -6
- package/template/wall-e/lib/runtime-worker-pool.js +1 -0
- package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
- package/template/wall-e/lib/telemetry-db-schema.js +37 -0
- package/template/wall-e/lib/telemetry-process-client.js +76 -1
- package/template/wall-e/llm/default-fallback.js +4 -0
- package/template/wall-e/llm/model-catalog.js +13 -4
- package/template/wall-e/llm/model-roles.js +6 -0
- package/template/wall-e/llm/portkey-http.js +150 -0
- package/template/wall-e/llm/portkey-sync.js +15 -145
- package/template/wall-e/llm/worker-mcp-routing.js +61 -0
- package/template/wall-e/llm/worker-route.js +43 -1
- package/template/wall-e/loops/brain-custom-writes.js +45 -0
- package/template/wall-e/mcp-protocol-contract.js +41 -1
- package/template/wall-e/mcp-server.js +13 -2
- package/template/wall-e/mcp-worker-schemas.js +20 -2
- package/template/wall-e/memory/ctm-context-client.js +25 -5
- package/template/wall-e/shared/release-schema.json +3 -2
- package/template/wall-e/shared/sqlite-write-lock.js +361 -223
- package/template/wall-e/shared/storage/contract.js +35 -3
- package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
- package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
- package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
- package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
- package/template/wall-e/tools/local-tools.js +12 -5
- package/template/wall-e/workers/brain-owner-worker.js +9 -0
- package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
- package/template/wall-e/workers/telemetry-worker.js +8 -0
- package/template/coding-task-manager/lib/giant-freeze-park.js +0 -31
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// Write-lock protocol 2 — the EXCLUSION primitive is a kernel lock, not a file.
|
|
4
|
+
//
|
|
5
|
+
// A write hold is `BEGIN EXCLUSIVE` on `<db>.write-lock.mutex`, a SQLite database
|
|
6
|
+
// that is NEVER written (no table, no pragma that writes, rollback journal — never
|
|
7
|
+
// WAL). SQLite's own fcntl locks are the exclusion, so a SIGKILLed holder's lock is
|
|
8
|
+
// released by the OS and no age-based stealing is needed or allowed. Nothing else in
|
|
9
|
+
// the process may open that .mutex path: POSIX advisory locks are per-process, so an
|
|
10
|
+
// unrelated open()+close() on the same file silently drops the holder's lock (D-6,
|
|
11
|
+
// enforced by bin/lint-shared-copy-parity.js). Holder identity is a fixed 1,024-byte
|
|
12
|
+
// space-padded JSON record at the old claim path, pwritten in place at offset 0
|
|
13
|
+
// through an fd kept open for the life of the handle (D-3). The fd is opened
|
|
14
|
+
// O_RDWR | O_CREAT — never 'a+': on Linux pwrite(2) ignores the offset on an
|
|
15
|
+
// O_APPEND fd and appends, so the record would grow by 1 KiB per acquire (§1.2).
|
|
16
|
+
//
|
|
17
|
+
// The identity record persists after release, so a reader sees "the last writer",
|
|
18
|
+
// not "a write is in flight". `_fence` re-checks the path after every kernel-lock
|
|
19
|
+
// acquire, because a v1 process (or a boot sweep) can replace the record's inode
|
|
20
|
+
// under a live v2 holder (D-8b); a v1 claim found at the path is honored while its
|
|
21
|
+
// pid is alive and reaped once when it is not (D-8c).
|
|
22
|
+
|
|
3
23
|
const fs = require('node:fs');
|
|
4
24
|
const path = require('node:path');
|
|
5
25
|
const crypto = require('node:crypto');
|
|
@@ -9,10 +29,15 @@ const { isMainThread, threadId } = require('node:worker_threads');
|
|
|
9
29
|
const WRITE_SQL_RE = /^\s*(?:INSERT|UPDATE|DELETE|REPLACE|CREATE|ALTER|DROP|VACUUM|REINDEX|ANALYZE|ATTACH|DETACH|BEGIN|COMMIT|ROLLBACK)\b/i;
|
|
10
30
|
const WRITE_PRAGMA_RE = /^\s*(?:journal_mode|wal_checkpoint|user_version|foreign_keys|synchronous|locking_mode|auto_vacuum|optimize)\b/i;
|
|
11
31
|
const DEFAULT_TIMEOUT_MS = 0;
|
|
12
|
-
const DEFAULT_STALE_MS = 10 * 60 * 1000;
|
|
13
32
|
const DEFAULT_POLL_MS = 25;
|
|
14
|
-
|
|
15
|
-
|
|
33
|
+
// D-1e: the lock DB is `<db>.write-lock.mutex`, NOT `<db>.write-lock.db` — code that
|
|
34
|
+
// enumerates *.db in a data dir (production-update.js, both test template copiers)
|
|
35
|
+
// would otherwise open and checkpoint the lock file as if it were a database.
|
|
36
|
+
const LOCK_DB_SUFFIX = '.mutex';
|
|
37
|
+
// D-3: the identity record is exactly this many bytes, written in place at offset 0.
|
|
38
|
+
const IDENTITY_RECORD_BYTES = 1024;
|
|
39
|
+
// D-8: the marker that distinguishes a v2 record from a pre-WP-1 claim.
|
|
40
|
+
const LOCK_PROTOCOL = 2;
|
|
16
41
|
// R3 (round-2 WP-0, §6.2): a foreign (not-this-pid) hold at or beyond this age gets
|
|
17
42
|
// exactly one incident-log record + /healthz.write_lock projection. A CONSTANT, not
|
|
18
43
|
// an env flag (F-4) — tests inject a smaller value via opts.foreignHoldIncidentMs.
|
|
@@ -33,6 +58,9 @@ const MAIN_THREAD_WRITE_ALLOWANCE_POLICIES = Object.freeze({
|
|
|
33
58
|
const globalState = globalThis[GLOBAL_STATE_KEY] || (globalThis[GLOBAL_STATE_KEY] = {
|
|
34
59
|
token: `${process.pid}:${threadId}:${crypto.randomBytes(8).toString('hex')}`,
|
|
35
60
|
locks: new Map(),
|
|
61
|
+
// D-7: per-thread, strongly referenced lock handles, keyed by canonical lock path.
|
|
62
|
+
// IndexedDB-style handles ({ db, begin, commit, identityFd, ... }) — see _lockHandle.
|
|
63
|
+
lockDbs: new Map(),
|
|
36
64
|
mainWriteEnforcement: false,
|
|
37
65
|
mainWriteAllowanceStorage: new AsyncLocalStorage(),
|
|
38
66
|
mainWriteInventory: new Set(),
|
|
@@ -53,6 +81,7 @@ if (!(globalState.mainWriteInventory instanceof Set)) globalState.mainWriteInven
|
|
|
53
81
|
if (!(globalState.mainWriteEscapeLastAt instanceof Map)) globalState.mainWriteEscapeLastAt = new Map();
|
|
54
82
|
if (!(globalState.mainWriteEscapeBySite instanceof Map)) globalState.mainWriteEscapeBySite = new Map();
|
|
55
83
|
if (!Number.isFinite(globalState.mainWriteEscapeCount)) globalState.mainWriteEscapeCount = 0;
|
|
84
|
+
if (!(globalState.lockDbs instanceof Map)) globalState.lockDbs = new Map();
|
|
56
85
|
if (!(globalState.mainWriteAllowanceStorage instanceof AsyncLocalStorage)) {
|
|
57
86
|
globalState.mainWriteAllowanceStorage = new AsyncLocalStorage();
|
|
58
87
|
}
|
|
@@ -215,37 +244,21 @@ function _pidAlive(pid) {
|
|
|
215
244
|
}
|
|
216
245
|
}
|
|
217
246
|
|
|
218
|
-
|
|
247
|
+
// D-3: the holder record, read off disk. null covers ENOENT, an unreadable record and
|
|
248
|
+
// a torn read (a v2 holder pwrites in place, so a concurrent reader can observe a
|
|
249
|
+
// half-updated buffer) — every one of those means "no usable identity", never "throw".
|
|
250
|
+
function _readIdentity(lockPath) {
|
|
219
251
|
try {
|
|
220
252
|
return JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
221
253
|
} catch {
|
|
254
|
+
/* expected: ENOENT (no record yet), EISDIR (a directory sits at the path) and a
|
|
255
|
+
torn read of a v2 in-place write all mean "no identity readable right now" —
|
|
256
|
+
the caller decides via the error code, and a thief that cannot be identified
|
|
257
|
+
must never be granted the lock. */
|
|
222
258
|
return null;
|
|
223
259
|
}
|
|
224
260
|
}
|
|
225
261
|
|
|
226
|
-
function _inspectLock(lockPath) {
|
|
227
|
-
let stat = null;
|
|
228
|
-
try {
|
|
229
|
-
stat = fs.statSync(lockPath);
|
|
230
|
-
} catch (error) {
|
|
231
|
-
if (error?.code === 'ENOENT') return { exists: false, stat: null, raw: '', info: null, parseError: null };
|
|
232
|
-
return { exists: true, stat: null, raw: '', info: null, parseError: error };
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
let raw = '';
|
|
236
|
-
try {
|
|
237
|
-
raw = fs.readFileSync(lockPath, 'utf8');
|
|
238
|
-
try {
|
|
239
|
-
return { exists: true, stat, raw, info: JSON.parse(raw), parseError: null };
|
|
240
|
-
} catch (parseError) {
|
|
241
|
-
return { exists: true, stat, raw, info: null, parseError };
|
|
242
|
-
}
|
|
243
|
-
} catch (error) {
|
|
244
|
-
if (error?.code === 'ENOENT') return { exists: false, stat: null, raw: '', info: null, parseError: null };
|
|
245
|
-
return { exists: true, stat, raw, info: null, parseError: error };
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
262
|
// Canonical path is stable for the process lifetime (the parent directory's symlink
|
|
250
263
|
// chain doesn't change), so cache it. _acquire runs this on every write — without the
|
|
251
264
|
// cache it pays a realpathSync.native syscall per write, which is pure overhead on the
|
|
@@ -283,83 +296,222 @@ function _canonicalLockPath(lockPath) {
|
|
|
283
296
|
return result;
|
|
284
297
|
}
|
|
285
298
|
|
|
286
|
-
|
|
299
|
+
// D-7: the per-thread lock handle. Created lazily, once per canonical lock path, and
|
|
300
|
+
// kept strongly referenced in globalState.lockDbs — an unreachable holder connection
|
|
301
|
+
// is garbage-collected and its lock silently released, which is exactly the failure
|
|
302
|
+
// this map exists to rule out.
|
|
303
|
+
//
|
|
304
|
+
// The kernel lock and the identity fd are opened on FIRST ACQUIRE (`_openLockResources`),
|
|
305
|
+
// not here, because installSqliteWriteLock calls this for every wrapped connection —
|
|
306
|
+
// including the no-op stubs the boot-sweep tests install — and opening/creating files
|
|
307
|
+
// at install time would both break those tests and leave a record for a database that
|
|
308
|
+
// was never written. `refs` (D-7b) is therefore live from install onward.
|
|
309
|
+
function _lockHandle(lockPath, canonical, opts) {
|
|
310
|
+
const existing = globalState.lockDbs.get(canonical);
|
|
311
|
+
if (existing) return existing;
|
|
312
|
+
if (typeof opts.Database !== 'function') {
|
|
313
|
+
throw new TypeError('createSqliteWriteLock: opts.Database is required');
|
|
314
|
+
}
|
|
315
|
+
const handle = {
|
|
316
|
+
Database: opts.Database,
|
|
317
|
+
db: null, // D-1: opened on first acquire, from opts.Database (D-5)
|
|
318
|
+
begin: null,
|
|
319
|
+
commit: null,
|
|
320
|
+
identityFd: null, // D-3: O_RDWR|O_CREAT fd on lockPath, never 'a+'
|
|
321
|
+
identityIno: 0,
|
|
322
|
+
acquireSeq: 0, // ++ per acquire → the token suffix
|
|
323
|
+
refs: 0, // wrapped connections currently open on this lock path (D-7b)
|
|
324
|
+
refsWarned: false,
|
|
325
|
+
};
|
|
326
|
+
globalState.lockDbs.set(canonical, handle);
|
|
327
|
+
return handle;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// D-3/D-8c: the identity fd. Created with O_EXCL so "we made this record file" is a fact, and
|
|
331
|
+
// creating it is a race we can lose but never a steal: EEXIST means something is already at the
|
|
332
|
+
// path — a v2 record from a previous holder (which _fence adopts; D-3 deliberately keeps the
|
|
333
|
+
// record after release) or a pre-WP-1 claim (which _fence leaves alone and reports busy while
|
|
334
|
+
// its holder lives). An unopenable object at the path (EISDIR: a directory, the fixture the
|
|
335
|
+
// fail-fast tests use) is the same "somebody else's claim" case. Both leave identityFd null, so
|
|
336
|
+
// _fence classifies whatever is there on every acquire it takes. Never O_APPEND (§1.2).
|
|
337
|
+
function _openIdentityFd(handle, lockPath) {
|
|
287
338
|
try {
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
339
|
+
const fd = fs.openSync(lockPath, fs.constants.O_RDWR | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600);
|
|
340
|
+
handle.identityFd = fd;
|
|
341
|
+
handle.identityIno = fs.fstatSync(fd).ino;
|
|
342
|
+
return;
|
|
343
|
+
} catch (error) {
|
|
344
|
+
if (error?.code !== 'EEXIST' && error?.code !== 'EISDIR') throw error;
|
|
345
|
+
handle.identityFd = null;
|
|
346
|
+
handle.identityIno = -1;
|
|
347
|
+
}
|
|
291
348
|
}
|
|
292
349
|
|
|
293
|
-
function
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
350
|
+
function _openLockResources(handle, lockPath) {
|
|
351
|
+
if (handle.db) return handle;
|
|
352
|
+
// D-5: the constructor of the connection being wrapped, never a fresh require — a
|
|
353
|
+
// second copy of the SQLite library in one process breaks its in-process lock table.
|
|
354
|
+
handle.db = new handle.Database(`${lockPath}${LOCK_DB_SUFFIX}`, { timeout: 0 });
|
|
355
|
+
handle.begin = handle.db.prepare('BEGIN EXCLUSIVE');
|
|
356
|
+
handle.commit = handle.db.prepare('COMMIT');
|
|
357
|
+
_openIdentityFd(handle, lockPath);
|
|
358
|
+
return handle;
|
|
298
359
|
}
|
|
299
360
|
|
|
300
|
-
function
|
|
301
|
-
|
|
302
|
-
if (a.info?.token || b.info?.token) return a.info?.token && a.info.token === b.info?.token;
|
|
303
|
-
if (!a.stat || !b.stat) return false;
|
|
304
|
-
if (a.raw || b.raw) return a.raw === b.raw && a.stat.size === b.stat.size && Math.abs(a.stat.mtimeMs - b.stat.mtimeMs) < 1;
|
|
305
|
-
return a.stat.dev === b.stat.dev &&
|
|
306
|
-
a.stat.ino === b.stat.ino &&
|
|
307
|
-
a.stat.size === b.stat.size &&
|
|
308
|
-
Math.abs(a.stat.mtimeMs - b.stat.mtimeMs) < 1 &&
|
|
309
|
-
Math.abs(a.stat.ctimeMs - b.stat.ctimeMs) < 1;
|
|
361
|
+
function _retainHandle(handle) {
|
|
362
|
+
handle.refs += 1;
|
|
310
363
|
}
|
|
311
364
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
365
|
+
// D-7b: the handle is released with the LAST wrapped connection, because every test
|
|
366
|
+
// file (and every db.close()/reopen) would otherwise leak two fds and one SQLite
|
|
367
|
+
// connection per database. Closing the lock DB through SQLite while another thread's
|
|
368
|
+
// connection holds the lock is safe — the unix VFS works around POSIX advisory locking
|
|
369
|
+
// quirks (How To Corrupt §2.2).
|
|
370
|
+
function _releaseHandle(canonical) {
|
|
371
|
+
const handle = globalState.lockDbs.get(canonical);
|
|
372
|
+
if (!handle) return false;
|
|
373
|
+
handle.refs = Math.max(0, handle.refs - 1);
|
|
374
|
+
if (handle.refs > 0) return false;
|
|
375
|
+
if (handle.db && handle.db.inTransaction) {
|
|
376
|
+
// A close() while a hold is still open is a caller bug. Dropping the handle here
|
|
377
|
+
// would release a lock somebody still believes they hold; keep it and say so once.
|
|
378
|
+
if (!handle.refsWarned) {
|
|
379
|
+
handle.refsWarned = true;
|
|
380
|
+
try {
|
|
381
|
+
console.warn(`[sqlite-write-lock] close() with an open write-lock hold; keeping the lock handle: ${canonical}`);
|
|
382
|
+
} catch {
|
|
383
|
+
/* expected: console.warn on a torn-down stdio (worker exiting) must not turn a
|
|
384
|
+
caller's close() bug into a different, less legible failure. */
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
globalState.lockDbs.delete(canonical);
|
|
390
|
+
_closeHandleResources(handle);
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function _closeHandleResources(handle) {
|
|
395
|
+
if (handle.db) {
|
|
396
|
+
try { handle.db.close(); } catch {
|
|
397
|
+
/* expected: the lock DB may already be closed by closeSqliteWriteLocksForTests or
|
|
398
|
+
by an earlier teardown in the same test; the identity fd below is the part
|
|
399
|
+
that must not be forgotten, and it is closed unconditionally. */
|
|
400
|
+
}
|
|
401
|
+
handle.db = null;
|
|
402
|
+
}
|
|
403
|
+
if (handle.identityFd !== null) {
|
|
404
|
+
try { fs.closeSync(handle.identityFd); } catch {
|
|
405
|
+
/* expected: an fd already closed (EBADF) or a double release is not an error —
|
|
406
|
+
the handle is being torn down either way. */
|
|
407
|
+
}
|
|
408
|
+
handle.identityFd = null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// D-8b: adopt the record file at lockPath — either because it went missing (recreate
|
|
413
|
+
// with O_EXCL, so exactly one process wins the race) or because the inode under us was
|
|
414
|
+
// replaced (reopen the existing file). Returns false when another process won the
|
|
415
|
+
// O_EXCL race or removed the path first; the acquire loop then re-fences.
|
|
416
|
+
function _adoptIdentity(handle, lockPath, flags) {
|
|
417
|
+
let fd;
|
|
316
418
|
try {
|
|
317
|
-
fs.
|
|
318
|
-
return true;
|
|
419
|
+
fd = fs.openSync(lockPath, flags, 0o600);
|
|
319
420
|
} catch (error) {
|
|
320
|
-
|
|
421
|
+
if (error?.code === 'EEXIST' || error?.code === 'ENOENT') return false;
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
424
|
+
try { fs.closeSync(handle.identityFd); } catch {
|
|
425
|
+
/* expected: the old fd is a plain fd on the record file (not the lock DB) and may already
|
|
426
|
+
be closed if a previous adopt lost a race, or be null when the first open found somebody
|
|
427
|
+
else's object at the path (EEXIST/EISDIR) — the new fd is what matters either way. */
|
|
428
|
+
}
|
|
429
|
+
handle.identityFd = fd;
|
|
430
|
+
handle.identityIno = fs.fstatSync(fd).ino;
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// D-1d: a failed BEGIN EXCLUSIVE needs no cleanup — but if the connection is somehow
|
|
435
|
+
// inside a transaction anyway, the state is not one this module understands: roll back
|
|
436
|
+
// and fail loudly rather than loop forever against it.
|
|
437
|
+
function _tryKernelLock(handle) {
|
|
438
|
+
try {
|
|
439
|
+
handle.begin.run();
|
|
440
|
+
return true;
|
|
441
|
+
} catch (e) {
|
|
442
|
+
if (handle.db.inTransaction) {
|
|
443
|
+
handle.db.prepare('ROLLBACK').run();
|
|
444
|
+
throw Object.assign(new Error('write-lock DB left in a transaction'), { code: 'SQLITE_WRITE_LOCK_STATE' });
|
|
445
|
+
}
|
|
446
|
+
if (e.code === 'SQLITE_BUSY') return false;
|
|
447
|
+
throw e;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// D-8: what is at the lock path? 'v2' — our record (possibly a torn read of an
|
|
452
|
+
// in-place write, or empty); 'v1-live' — a pre-WP-1 claim whose holder is alive, or
|
|
453
|
+
// anything unparseable that is not a torn v2 record; 'v1-dead' — a v1 claim whose
|
|
454
|
+
// holder is provably gone (the single cutover-only reason this module removes a path).
|
|
455
|
+
function _classifyRecord(lockPath, st) {
|
|
456
|
+
let raw = '';
|
|
457
|
+
try {
|
|
458
|
+
raw = fs.readFileSync(lockPath, 'utf8');
|
|
459
|
+
} catch {
|
|
460
|
+
/* expected: the path can vanish between the fence's statSync and this read; an
|
|
461
|
+
unreadable record is classified as a live foreign claim below, never as ours. */
|
|
321
462
|
}
|
|
463
|
+
if (st.size === 0) return 'v2';
|
|
464
|
+
let info;
|
|
465
|
+
try {
|
|
466
|
+
info = JSON.parse(raw);
|
|
467
|
+
} catch {
|
|
468
|
+
return st.size === IDENTITY_RECORD_BYTES ? 'v2' : 'v1-live';
|
|
469
|
+
}
|
|
470
|
+
if (info && info.protocol === LOCK_PROTOCOL) return 'v2';
|
|
471
|
+
// v1 writes a complete record before linkSync (sqlite-write-lock-v1.js:381-383), so a
|
|
472
|
+
// v1 claim is never empty or torn — anything else with a dead pid is reapable.
|
|
473
|
+
return _pidAlive(info && info.pid) ? 'v1-live' : 'v1-dead';
|
|
322
474
|
}
|
|
323
475
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
476
|
+
// D-8b/D-8c: runs only after the kernel lock is held. The inode can change under a live
|
|
477
|
+
// holder (a v1 steal, a boot sweep, another process's adopt), and the first draft's
|
|
478
|
+
// "different inode ⇒ busy" would then spin this process busy until it restarted.
|
|
479
|
+
function _fence(handle, lockPath) {
|
|
480
|
+
let st;
|
|
481
|
+
try {
|
|
482
|
+
st = fs.statSync(lockPath);
|
|
483
|
+
} catch (error) {
|
|
484
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
485
|
+
st = null;
|
|
329
486
|
}
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
// lives on the per-thread globalThis), so "same pid, different token" is NOT
|
|
350
|
-
// proof of a dead owner by itself — a live sibling worker_thread of THIS
|
|
351
|
-
// process has our pid and a different token. Only when the record claims our
|
|
352
|
-
// exact pid AND threadId with a different launch-random is it provably from
|
|
353
|
-
// a previous (dead) process that our pid was recycled from.
|
|
354
|
-
if (
|
|
355
|
-
info.pid === process.pid
|
|
356
|
-
&& typeof info.token === 'string'
|
|
357
|
-
&& info.token !== globalState.token
|
|
358
|
-
&& info.token.startsWith(`${process.pid}:${threadId}:`)
|
|
359
|
-
) {
|
|
360
|
-
return 'dead-owner';
|
|
487
|
+
if (st === null) {
|
|
488
|
+
return _adoptIdentity(handle, lockPath, fs.constants.O_RDWR | fs.constants.O_CREAT | fs.constants.O_EXCL) ? 'ok' : 'busy';
|
|
489
|
+
}
|
|
490
|
+
if (st.ino === handle.identityIno) return 'ok';
|
|
491
|
+
if (st.isDirectory()) return 'busy';
|
|
492
|
+
switch (_classifyRecord(lockPath, st)) {
|
|
493
|
+
case 'v2':
|
|
494
|
+
return _adoptIdentity(handle, lockPath, fs.constants.O_RDWR) ? 'ok' : 'busy';
|
|
495
|
+
case 'v1-dead':
|
|
496
|
+
try {
|
|
497
|
+
fs.unlinkSync(lockPath);
|
|
498
|
+
} catch {
|
|
499
|
+
/* expected: another process may reap the same dead v1 claim first (ENOENT), or
|
|
500
|
+
the path may have been replaced (a directory) — either way the next loop
|
|
501
|
+
iteration re-fences and the outcome is decided there. */
|
|
502
|
+
}
|
|
503
|
+
return _adoptIdentity(handle, lockPath, fs.constants.O_RDWR | fs.constants.O_CREAT | fs.constants.O_EXCL) ? 'ok' : 'busy';
|
|
504
|
+
default:
|
|
505
|
+
return 'busy';
|
|
361
506
|
}
|
|
362
|
-
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// D-3: one in-place 1,024-byte write at offset 0 on the fd adopted above. No close, no
|
|
510
|
+
// fsync, no unlink — release does not touch the record.
|
|
511
|
+
function _writeIdentity(handle, info) {
|
|
512
|
+
const buf = Buffer.alloc(IDENTITY_RECORD_BYTES, 0x20);
|
|
513
|
+
buf.write(JSON.stringify({ protocol: LOCK_PROTOCOL, ...info }));
|
|
514
|
+
fs.writeSync(handle.identityFd, buf, 0, buf.length, 0);
|
|
363
515
|
}
|
|
364
516
|
|
|
365
517
|
// Pure deadline-slicing helper shared by every wait branch in the acquire loop:
|
|
@@ -374,31 +526,6 @@ function _remainingWaitMs(deadline, sliceMs, now = Date.now()) {
|
|
|
374
526
|
return Math.min(sliceMs, remaining);
|
|
375
527
|
}
|
|
376
528
|
|
|
377
|
-
function _writeLockClaim(lockPath, payload) {
|
|
378
|
-
const dir = path.dirname(lockPath);
|
|
379
|
-
const base = path.basename(lockPath);
|
|
380
|
-
const tempPath = path.join(dir, `.${base}.${process.pid}.${threadId}.${Date.now()}.${crypto.randomBytes(4).toString('hex')}.tmp`);
|
|
381
|
-
const text = JSON.stringify(payload);
|
|
382
|
-
try {
|
|
383
|
-
fs.writeFileSync(tempPath, text, { flag: 'wx', mode: 0o600 });
|
|
384
|
-
fs.linkSync(tempPath, lockPath);
|
|
385
|
-
} catch (error) {
|
|
386
|
-
if (error?.code !== 'EEXIST' && ['EPERM', 'ENOTSUP', 'EOPNOTSUPP', 'EXDEV'].includes(error?.code)) {
|
|
387
|
-
let fd = null;
|
|
388
|
-
try {
|
|
389
|
-
fd = fs.openSync(lockPath, 'wx', 0o600);
|
|
390
|
-
fs.writeFileSync(fd, text);
|
|
391
|
-
return;
|
|
392
|
-
} finally {
|
|
393
|
-
try { if (fd !== null) fs.closeSync(fd); } catch {}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
throw error;
|
|
397
|
-
} finally {
|
|
398
|
-
try { fs.unlinkSync(tempPath); } catch {}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
529
|
function _durationMs(value, fallback, min = 0) {
|
|
403
530
|
const n = Number(value);
|
|
404
531
|
return Number.isFinite(n) ? Math.max(min, Math.trunc(n)) : fallback;
|
|
@@ -584,23 +711,14 @@ function _resetWriteLockForeignHoldForTests(store = globalState) {
|
|
|
584
711
|
|
|
585
712
|
function _acquire(lockPath, opts = {}) {
|
|
586
713
|
const timeoutMs = _durationMs(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, 0);
|
|
587
|
-
const staleMs = _durationMs(opts.staleMs ?? DEFAULT_STALE_MS, DEFAULT_STALE_MS, 1000);
|
|
588
714
|
// Floor of 1ms (not 5) so a background writer can poll tightly and reliably win
|
|
589
715
|
// the lock in the brief window after the main thread releases — the main thread
|
|
590
716
|
// can never wait, so the worker winning fast is how we avoid starving it.
|
|
591
717
|
const pollMs = _durationMs(opts.pollMs ?? DEFAULT_POLL_MS, DEFAULT_POLL_MS, 1);
|
|
592
|
-
const claimStaleMs = _durationMs(opts.claimStaleMs ?? DEFAULT_CLAIM_STALE_MS, DEFAULT_CLAIM_STALE_MS, 1);
|
|
593
|
-
const staleRecoveryTimeoutMs = _durationMs(
|
|
594
|
-
opts.staleRecoveryTimeoutMs ?? DEFAULT_STALE_RECOVERY_TIMEOUT_MS,
|
|
595
|
-
DEFAULT_STALE_RECOVERY_TIMEOUT_MS,
|
|
596
|
-
0
|
|
597
|
-
);
|
|
598
718
|
const { now = Date.now, sleepSync = _sleepSync } = opts;
|
|
599
719
|
const startedAt = now();
|
|
600
720
|
const deadline = startedAt + timeoutMs;
|
|
601
|
-
const dir = path.dirname(lockPath);
|
|
602
721
|
const canonical = _canonicalLockPath(lockPath);
|
|
603
|
-
let staleRecoveryDeadline = 0;
|
|
604
722
|
let contended = false;
|
|
605
723
|
const held = globalState.locks.get(canonical);
|
|
606
724
|
if (held) {
|
|
@@ -610,94 +728,82 @@ function _acquire(lockPath, opts = {}) {
|
|
|
610
728
|
contended: false,
|
|
611
729
|
release: () => {
|
|
612
730
|
held.depth -= 1;
|
|
613
|
-
if (held.depth <= 0) {
|
|
614
|
-
globalState.locks.delete(canonical);
|
|
615
|
-
_releaseLockFile(held.lockPath, held.token);
|
|
616
|
-
}
|
|
617
731
|
},
|
|
618
732
|
};
|
|
619
733
|
}
|
|
620
734
|
|
|
621
|
-
|
|
735
|
+
const handle = _lockHandle(lockPath, canonical, opts);
|
|
736
|
+
// v1 parity (sqlite-write-lock-v1.js:621): a cached handle can outlive the directory it was
|
|
737
|
+
// created in — coding-task-manager/db.js keeps its per-domain opens by name across initDb()
|
|
738
|
+
// calls — and the identity open below would then fail ENOENT and report a busy lock that names
|
|
739
|
+
// nobody. mkdir on a directory that exists writes nothing, so the steady state is unchanged.
|
|
740
|
+
try {
|
|
741
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
742
|
+
} catch {
|
|
743
|
+
/* expected: a directory that cannot be created (EACCES/ENOTDIR/a removed temp root) is not a
|
|
744
|
+
lock verdict — the identity open below reports the real error, and a live foreign holder is
|
|
745
|
+
still attributed by the fence. */
|
|
746
|
+
}
|
|
747
|
+
_openLockResources(handle, lockPath);
|
|
622
748
|
|
|
623
749
|
while (true) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
} catch (err) {
|
|
660
|
-
if (err?.code !== 'EEXIST') throw err;
|
|
661
|
-
contended = true;
|
|
662
|
-
const observed = _inspectLock(lockPath);
|
|
663
|
-
// R3 (round-2 WP-0, §6.2): a FOREIGN (not-this-pid) holder at/beyond the
|
|
664
|
-
// incident threshold gets exactly one incident record + /healthz projection,
|
|
665
|
-
// independent of staleness (a live foreign hold is never "stale" by the
|
|
666
|
-
// 10-minute ceiling below, but 30s is already an incident-worthy hold).
|
|
667
|
-
if (observed?.info && observed.info.pid !== process.pid) {
|
|
668
|
-
_maybeRecordForeignHold(observed, opts);
|
|
669
|
-
}
|
|
670
|
-
const staleReason = _staleReason(observed, staleMs, claimStaleMs, now());
|
|
671
|
-
if (staleReason) {
|
|
672
|
-
if (_unlinkIfUnchanged(lockPath, observed)) continue;
|
|
673
|
-
if (!staleRecoveryDeadline) staleRecoveryDeadline = now() + staleRecoveryTimeoutMs;
|
|
674
|
-
if (now() < staleRecoveryDeadline) {
|
|
675
|
-
// Was: unconditional `_sleepSync(pollMs)`, ignoring the CALLER's deadline —
|
|
676
|
-
// a 0ms caller could be dragged through the full staleRecoveryTimeoutMs
|
|
677
|
-
// (default 5000ms) when the stale lock couldn't be unlinked (it changed
|
|
678
|
-
// under us). _remainingWaitMs makes the caller's deadline always win.
|
|
679
|
-
const wait = _remainingWaitMs(deadline, pollMs, now());
|
|
680
|
-
if (wait < 0) throw _lockBusyError(lockPath, observed.info);
|
|
681
|
-
sleepSync(wait);
|
|
682
|
-
continue;
|
|
683
|
-
}
|
|
684
|
-
} else if (!observed.info) {
|
|
685
|
-
// Was: unconditional `_sleepSync(waitMs)` here too — same bypass for a
|
|
686
|
-
// still-forming (orphaned) claim.
|
|
687
|
-
const slice = Math.min(pollMs, Math.max(1, claimStaleMs - _lockAgeMs(observed, now())));
|
|
688
|
-
const wait = _remainingWaitMs(deadline, slice, now());
|
|
689
|
-
if (wait < 0) throw _lockBusyError(lockPath, observed.info);
|
|
690
|
-
sleepSync(wait);
|
|
691
|
-
continue;
|
|
692
|
-
} else {
|
|
693
|
-
staleRecoveryDeadline = 0;
|
|
694
|
-
}
|
|
695
|
-
{
|
|
696
|
-
const wait = _remainingWaitMs(deadline, pollMs, now());
|
|
697
|
-
if (wait < 0) throw _lockBusyError(lockPath, observed.info);
|
|
698
|
-
sleepSync(wait);
|
|
750
|
+
if (_tryKernelLock(handle)) {
|
|
751
|
+
if (_fence(handle, lockPath) === 'ok') {
|
|
752
|
+
// R3 (round-2 WP-0, §6.2): identity fields on the holder record — role/argv0/
|
|
753
|
+
// purpose let a contended waiter (and /healthz) attribute WHO holds the lock,
|
|
754
|
+
// not just that it's busy.
|
|
755
|
+
const acquiredAtMs = now();
|
|
756
|
+
_writeIdentity(handle, {
|
|
757
|
+
pid: process.pid,
|
|
758
|
+
thread_id: threadId,
|
|
759
|
+
token: `${globalState.token}:${++handle.acquireSeq}`,
|
|
760
|
+
created_at_ms: acquiredAtMs,
|
|
761
|
+
acquiredAt: acquiredAtMs,
|
|
762
|
+
// WALL_E_PROCESS_ROLE (not CTM_PROCESS_ROLE — the §6.2 spec text names the
|
|
763
|
+
// latter, but that's CTM's own foreign-owner marker, always unset for wall-e's
|
|
764
|
+
// OWN daemon/workers; WALL_E_PROCESS_ROLE is the identity every legitimate
|
|
765
|
+
// wall-e opener actually sets — see lib/live-data-role-guard.js's enumeration.
|
|
766
|
+
// Also avoids a NEW raw CTM_* env read here, which the flag-read ratchet forbids).
|
|
767
|
+
role: opts.role || process.env.WALL_E_PROCESS_ROLE || 'unknown',
|
|
768
|
+
argv0: opts.argv0 || path.basename(process.argv[1] || ''),
|
|
769
|
+
purpose: String(opts.purpose || opts.label || '').slice(0, 80),
|
|
770
|
+
label: opts.label || '',
|
|
771
|
+
});
|
|
772
|
+
const entry = { depth: 1, lockPath, token: globalState.token };
|
|
773
|
+
globalState.locks.set(canonical, entry);
|
|
774
|
+
return {
|
|
775
|
+
waitedMs: now() - startedAt,
|
|
776
|
+
contended,
|
|
777
|
+
// D-1: release is COMMIT — never an unlink, never a record rewrite.
|
|
778
|
+
release: () => {
|
|
779
|
+
entry.depth -= 1;
|
|
780
|
+
if (entry.depth > 0) return;
|
|
781
|
+
globalState.locks.delete(canonical);
|
|
782
|
+
handle.commit.run();
|
|
783
|
+
},
|
|
784
|
+
};
|
|
699
785
|
}
|
|
786
|
+
// A live foreign claim (a pre-WP-1 process, or a directory) owns the path:
|
|
787
|
+
// give the kernel lock back and wait like any other busy acquirer.
|
|
788
|
+
handle.commit.run();
|
|
789
|
+
contended = true;
|
|
790
|
+
} else {
|
|
791
|
+
contended = true;
|
|
792
|
+
}
|
|
793
|
+
// R3 (round-2 WP-0, §6.2): a FOREIGN (not-this-pid) holder at/beyond the incident
|
|
794
|
+
// threshold gets exactly one incident record + /healthz projection. Both contended
|
|
795
|
+
// branches end in the same busy error below, so both must attribute the holder the same
|
|
796
|
+
// way: under D-3 the record at the path is the identity of whoever is inside its hold,
|
|
797
|
+
// and under D-8c it is the only identity a live pre-WP-1 claim has.
|
|
798
|
+
const info = _readIdentity(lockPath);
|
|
799
|
+
if (info && info.pid !== process.pid) {
|
|
800
|
+
_maybeRecordForeignHold({ info }, opts);
|
|
700
801
|
}
|
|
802
|
+
const wait = _remainingWaitMs(deadline, pollMs, now());
|
|
803
|
+
// D-4: no age-based stealing. A holder that is alive keeps the lock; the waiter
|
|
804
|
+
// fails busy at its own deadline and the incident log is the escalation.
|
|
805
|
+
if (wait < 0) throw _lockBusyError(lockPath, _readIdentity(lockPath));
|
|
806
|
+
sleepSync(wait);
|
|
701
807
|
}
|
|
702
808
|
}
|
|
703
809
|
|
|
@@ -928,21 +1034,27 @@ function containsLikelyWriteSql(sql) {
|
|
|
928
1034
|
});
|
|
929
1035
|
}
|
|
930
1036
|
|
|
931
|
-
// One-pass boot sweep for a single lock file: crashed processes leave their
|
|
932
|
-
//
|
|
933
|
-
//
|
|
934
|
-
//
|
|
935
|
-
//
|
|
936
|
-
// path
|
|
937
|
-
//
|
|
938
|
-
//
|
|
1037
|
+
// One-pass boot sweep for a single lock file: crashed processes leave their record
|
|
1038
|
+
// behind forever otherwise (nothing else ever revisits a lock path once no writer
|
|
1039
|
+
// contends for it). Only ever removes a record whose holder pid is provably dead
|
|
1040
|
+
// (ESRCH via _pidAlive) — a live holder, or a record we can't parse/attribute, is left
|
|
1041
|
+
// alone. Protocol-agnostic: a dead v2 process's record is removed too, and any live v2
|
|
1042
|
+
// holder of that path adopts the new inode on its next acquire (D-8b). The re-inspect
|
|
1043
|
+
// before the unlink is the race guard that replaces _unlinkIfUnchanged; this never
|
|
1044
|
+
// touches `${lockPath}.mutex`. Safe to call repeatedly (idempotent) and safe under a
|
|
1045
|
+
// real concurrent acquirer.
|
|
939
1046
|
function _bootSweepLockFile(lockPath, logger = console) {
|
|
940
|
-
const
|
|
941
|
-
if (!inspected.exists) return false;
|
|
942
|
-
const pid = inspected.info?.pid;
|
|
1047
|
+
const pid = _readIdentity(lockPath)?.pid;
|
|
943
1048
|
if (pid == null || _pidAlive(pid)) return false;
|
|
944
|
-
|
|
945
|
-
|
|
1049
|
+
// Re-inspect: the record may have been replaced (a new writer, another sweep) since
|
|
1050
|
+
// the read above — only remove the very record whose dead pid we proved.
|
|
1051
|
+
if (Number(_readIdentity(lockPath)?.pid) !== Number(pid)) return false;
|
|
1052
|
+
try {
|
|
1053
|
+
fs.unlinkSync(lockPath);
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
return error?.code === 'ENOENT';
|
|
1056
|
+
}
|
|
1057
|
+
if (logger && typeof logger.warn === 'function') {
|
|
946
1058
|
try {
|
|
947
1059
|
logger.warn(`[sqlite-write-lock] boot sweep removed stale lock (dead pid=${pid}): ${lockPath}`);
|
|
948
1060
|
} catch {
|
|
@@ -951,13 +1063,26 @@ function _bootSweepLockFile(lockPath, logger = console) {
|
|
|
951
1063
|
an already-successful boot sweep into a startup failure */
|
|
952
1064
|
}
|
|
953
1065
|
}
|
|
954
|
-
return
|
|
1066
|
+
return true;
|
|
955
1067
|
}
|
|
956
1068
|
|
|
957
1069
|
function lockBootSweepEnabled(env = process.env) {
|
|
958
1070
|
return String(env.WALLE_LOCK_BOOT_SWEEP ?? '1') !== '0';
|
|
959
1071
|
}
|
|
960
1072
|
|
|
1073
|
+
// Test seam only: tests that call createSqliteWriteLock directly have no wrapped
|
|
1074
|
+
// db.close() to release the handle through (D-7b), so they call this in after().
|
|
1075
|
+
function closeSqliteWriteLocksForTests() {
|
|
1076
|
+
const entries = [...globalState.lockDbs.entries()];
|
|
1077
|
+
globalState.lockDbs.clear();
|
|
1078
|
+
for (const [canonical, handle] of entries) {
|
|
1079
|
+
if (handle.db && handle.db.inTransaction) {
|
|
1080
|
+
throw new Error(`closeSqliteWriteLocksForTests: lock DB still in a transaction: ${canonical}`);
|
|
1081
|
+
}
|
|
1082
|
+
_closeHandleResources(handle);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
961
1086
|
function installSqliteWriteLock(db, dbPath, opts = {}) {
|
|
962
1087
|
if (!db || db.__sqliteWriteLockInstalled) return db;
|
|
963
1088
|
const lockPath = opts.lockPath || `${dbPath}.write-lock`;
|
|
@@ -965,12 +1090,24 @@ function installSqliteWriteLock(db, dbPath, opts = {}) {
|
|
|
965
1090
|
try {
|
|
966
1091
|
_bootSweepLockFile(lockPath, opts.logger || console);
|
|
967
1092
|
} catch {
|
|
968
|
-
/* expected:
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
acquire-time path rather than abort DB install */
|
|
1093
|
+
/* expected: a record the sweep can't remove is left for the acquire-time fence to
|
|
1094
|
+
handle instead — a thrown error here (e.g. an unreadable lock dir) must fall
|
|
1095
|
+
through to that same path rather than abort DB install */
|
|
972
1096
|
}
|
|
973
1097
|
}
|
|
1098
|
+
// D-7b: one handle per (thread, lock path), refcounted per wrapped connection and
|
|
1099
|
+
// released by the LAST db.close(). D-5: the lock DB comes from the constructor of the
|
|
1100
|
+
// connection being wrapped.
|
|
1101
|
+
const canonical = _canonicalLockPath(lockPath);
|
|
1102
|
+
_retainHandle(_lockHandle(lockPath, canonical, { ...opts, Database: db.constructor }));
|
|
1103
|
+
if (typeof db.close === 'function') {
|
|
1104
|
+
const originalClose = db.close.bind(db);
|
|
1105
|
+
db.close = (...a) => {
|
|
1106
|
+
const result = originalClose(...a);
|
|
1107
|
+
_releaseHandle(canonical);
|
|
1108
|
+
return result;
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
974
1111
|
const upstreamOnEvent = typeof opts.onEvent === 'function' ? opts.onEvent : null;
|
|
975
1112
|
let eventSequence = 0;
|
|
976
1113
|
let lastEvent = null;
|
|
@@ -1051,8 +1188,9 @@ module.exports = {
|
|
|
1051
1188
|
suspendWriteLockHoldBudget,
|
|
1052
1189
|
// WP-P3 test seams: internal, hermetic-tests-only. Not part of the public API.
|
|
1053
1190
|
_remainingWaitMsForTests: _remainingWaitMs,
|
|
1054
|
-
_staleReasonForTests: _staleReason,
|
|
1055
1191
|
_bootSweepLockFileForTests: _bootSweepLockFile,
|
|
1192
|
+
// D-7b: direct createSqliteWriteLock callers (no wrapped db.close) release here.
|
|
1193
|
+
closeSqliteWriteLocksForTests,
|
|
1056
1194
|
_currentLockTokenForTests: () => globalState.token,
|
|
1057
1195
|
_resetWriteLockForeignHoldForTests,
|
|
1058
1196
|
};
|