create-walle 0.10.1 → 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 +4 -0
- 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 +6 -4
- package/package.json +1 -1
- 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/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/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 +5 -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-launch-health.js +29 -2
- package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
- package/template/coding-task-manager/lib/codex-paths.js +2 -1
- 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-schema-source-files.js +1 -0
- package/template/coding-task-manager/lib/feature-manifest.js +1 -1
- package/template/coding-task-manager/lib/fs-watcher.js +3 -2
- package/template/coding-task-manager/lib/harness-history-transition.js +3 -3
- 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 +55 -17
- 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/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-switch-runtime.js +57 -4
- package/template/coding-task-manager/lib/lead-model-switch.js +3 -2
- 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 +1 -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 +52 -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-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/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/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 +2 -2
- 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-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/read-pool-client.js +5 -2
- package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
- 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 +13 -1
- 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 +33 -2
- package/template/coding-task-manager/lib/root-prompt-flow.js +167 -33
- package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
- 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 +34 -37
- 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 +18 -0
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +9 -0
- package/template/coding-task-manager/lib/session-resource-sampler.js +19 -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-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 +92 -0
- package/template/coding-task-manager/lib/typing-worker-stages.js +60 -0
- 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/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/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 -1
- package/template/coding-task-manager/public/css/reviews.css +4 -0
- package/template/coding-task-manager/public/index.html +294 -223
- 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/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 -8
- package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
- 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 +4 -2
- 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/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 -1
- 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 +902 -239
- 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/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/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 +6 -4
- 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
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// WP-2 (docs/reader-claude-table-layout-parity-2026-10-25.html §1.2 the census, §6 the CLI sketch,
|
|
5
|
+
// §8 WP-2, D-7): the census that produced the design's parity floor, kept as a repo tool so a
|
|
6
|
+
// Claude Code upgrade that changes the Markdown-table layout is MEASURED, not guessed (R-8: the
|
|
7
|
+
// port pins one version and there is no runtime switch -- drift is caught here and by the golden
|
|
8
|
+
// fixtures).
|
|
9
|
+
//
|
|
10
|
+
// READ-ONLY. It opens the painted-row stores (`~/.walle/data/painted-rows`) and the provider
|
|
11
|
+
// transcripts (`~/.claude/projects`) read-only: never a database, never a file write. It prints
|
|
12
|
+
// numbers and 8-character session-id prefixes only -- never a painted row, never a cell.
|
|
13
|
+
//
|
|
14
|
+
// node bin/claude-table-parity.js [--painted DIR] [--projects DIR] [--json]
|
|
15
|
+
//
|
|
16
|
+
// For every session directory that has a painted-row store and a transcript: lex every assistant
|
|
17
|
+
// text block with the repo's own `marked`; every GFM `table` token is one table. Locate the drawing
|
|
18
|
+
// (the first body cell of at least 8 characters, its first 24 characters, searched in the painted
|
|
19
|
+
// rows -- one record per stretch file that holds a hit) and re-draw the table with the SHIPPED
|
|
20
|
+
// renderer (`lib/claude-markdown-ansi.emitMarkdownRows` -> `lib/claude-table-layout`) at every
|
|
21
|
+
// `cols` the session's stretch headers recorded. `predicted` is the form that re-draw took (a row
|
|
22
|
+
// carrying `│` means the box), `identical` the most rows that matched the painted window.
|
|
23
|
+
//
|
|
24
|
+
// Exits 0 always -- a missing store, a torn line or an unknown flag is not a census failure.
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
const os = require('os');
|
|
29
|
+
const marked = require('marked');
|
|
30
|
+
const { emitMarkdownRows } = require('../lib/claude-markdown-ansi');
|
|
31
|
+
|
|
32
|
+
// The renderer's own style object (lib/claude-transcript-terminal-renderer.js MARKDOWN_STYLE, as
|
|
33
|
+
// its test copies it). It is not exported; the comparison strips SGR anyway, but `cardLines`
|
|
34
|
+
// styles the `Header:` key through it, so the re-draw must carry it.
|
|
35
|
+
const MD_STYLE = Object.freeze({
|
|
36
|
+
bold: [1], boldOff: [22],
|
|
37
|
+
italic: [3], italicOff: [23],
|
|
38
|
+
del: [9], delOff: [29],
|
|
39
|
+
code: [38, 5, 180], codeOff: [39],
|
|
40
|
+
dim: [2],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const SGR_RE = /\x1b\[[0-9;?]*[ -\/]*[@-~]/g;
|
|
44
|
+
const CARD_RULE = '─'.repeat(40); // the vertical form's card separator (RULE_MAX)
|
|
45
|
+
const PROBE_MIN = 8; // a probe cell must be at least this long
|
|
46
|
+
const PROBE_LEN = 24; // ... and only its first 24 characters are searched for
|
|
47
|
+
const WINDOW_BEFORE = 40; // painted rows kept before the hit
|
|
48
|
+
const WINDOW_AFTER = 400; // painted rows kept after the hit
|
|
49
|
+
|
|
50
|
+
const stripSgr = (text) => String(text == null ? '' : text).replace(SGR_RE, '');
|
|
51
|
+
const trimEnd = (text) => String(text == null ? '' : text).replace(/\s+$/, '');
|
|
52
|
+
|
|
53
|
+
function parseArgs(argv) {
|
|
54
|
+
const opts = {
|
|
55
|
+
painted: path.join(os.homedir(), '.walle', 'data', 'painted-rows'),
|
|
56
|
+
projects: path.join(os.homedir(), '.claude', 'projects'),
|
|
57
|
+
json: false,
|
|
58
|
+
};
|
|
59
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
60
|
+
const arg = String(argv[i]);
|
|
61
|
+
const eq = arg.indexOf('=');
|
|
62
|
+
const name = eq < 0 ? arg : arg.slice(0, eq);
|
|
63
|
+
const inline = eq < 0 ? null : arg.slice(eq + 1);
|
|
64
|
+
if (name === '--painted') opts.painted = (inline != null ? inline : argv[i + 1]) || opts.painted;
|
|
65
|
+
else if (name === '--projects') opts.projects = (inline != null ? inline : argv[i + 1]) || opts.projects;
|
|
66
|
+
else if (name === '--json') opts.json = true;
|
|
67
|
+
}
|
|
68
|
+
return opts;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Immediate sub-directory names of `dir`, sorted (deterministic output). A missing root is a
|
|
72
|
+
// legitimate empty census on a machine that never ran the reader.
|
|
73
|
+
function listDirs(dir) {
|
|
74
|
+
let entries = [];
|
|
75
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return []; } /* expected: the store/projects root may not exist here */
|
|
76
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readText(file) {
|
|
80
|
+
try { return fs.readFileSync(file, 'utf8'); } catch { return ''; } /* expected: a store file may vanish under a live writer */
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// One `JSON<TAB>crc` line; the CRC is not verified (the stores are append-only and the census
|
|
84
|
+
// reads numbers, not truth).
|
|
85
|
+
function parseLine(prefix) {
|
|
86
|
+
try { return JSON.parse(prefix); } catch { return null; } // a torn / non-JSON line is not a record
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function findTranscript(projectsRoot, projectDirs, sessionId) {
|
|
90
|
+
for (const project of projectDirs) {
|
|
91
|
+
const file = path.join(projectsRoot, project, `${sessionId}.jsonl`);
|
|
92
|
+
if (fs.existsSync(file)) return file;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Every painted row of one session's store: `{ r, f, cols, t }` with `t` = SGR-stripped,
|
|
98
|
+
// right-trimmed text. `cols` is the width the surrounding stretch header recorded (a row written
|
|
99
|
+
// before the first header of the file keeps `null`).
|
|
100
|
+
function readPainted(dir) {
|
|
101
|
+
const rows = [];
|
|
102
|
+
for (const file of listFiles(dir).filter((f) => /^stretch-\d+\.jsonl$/.test(f)).sort()) {
|
|
103
|
+
let cols = null;
|
|
104
|
+
const text = readText(path.join(dir, file));
|
|
105
|
+
if (!text) continue;
|
|
106
|
+
for (const line of text.split('\n')) {
|
|
107
|
+
const tab = line.lastIndexOf('\t');
|
|
108
|
+
if (tab < 0) continue;
|
|
109
|
+
const record = parseLine(line.slice(0, tab));
|
|
110
|
+
if (!record) continue;
|
|
111
|
+
if (record.stretch != null && record.cols) cols = record.cols;
|
|
112
|
+
if (record.r != null) rows.push({ r: record.r, f: file, cols, t: trimEnd(stripSgr(record.a)) });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return rows;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function listFiles(dir) {
|
|
119
|
+
try { return fs.readdirSync(dir); } catch { return []; } /* expected: the session store may be pruned between readdir and read */
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Every `table` token of one assistant text block. The `|` pre-check only skips blocks that cannot
|
|
123
|
+
// hold a GFM table -- the decision is `marked`'s, never a local grammar.
|
|
124
|
+
function lexTables(text) {
|
|
125
|
+
if (!text.includes('|')) return [];
|
|
126
|
+
let tokens = [];
|
|
127
|
+
try { tokens = marked.lexer(text, { gfm: true }); } catch { return []; } // an unlexable block holds no table we can census
|
|
128
|
+
return tokens.filter((token) => token.type === 'table');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// A cell's plain text: `label (href)` for a link (R-3 -- the rendering, not the raw markdown, is
|
|
132
|
+
// what the layout measures) and the literal text everywhere else.
|
|
133
|
+
function cellText(tokens) {
|
|
134
|
+
let out = '';
|
|
135
|
+
for (const token of tokens || []) {
|
|
136
|
+
switch (token.type) {
|
|
137
|
+
case 'text': case 'escape': out += token.text; break;
|
|
138
|
+
case 'codespan': out += token.text; break;
|
|
139
|
+
case 'strong': case 'em': case 'del': out += cellText(token.tokens); break;
|
|
140
|
+
case 'link': {
|
|
141
|
+
const label = cellText(token.tokens);
|
|
142
|
+
out += label === token.href ? label : `${label} (${token.href})`;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case 'image': out += `${token.text} (${token.href})`; break;
|
|
146
|
+
case 'br': out += ' '; break;
|
|
147
|
+
case 'checkbox': break;
|
|
148
|
+
default: out += token.text != null ? token.text : (token.raw || '');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// The form the LIVE screen drew, read off the hit row and its neighbours: a row starting with the
|
|
155
|
+
// box border is `bordered`; otherwise a 40-dash card rule or a `Header:` label within
|
|
156
|
+
// `2 * columns + 2` rows of the hit says `cards`; anything else is `other` (a drawing we could not
|
|
157
|
+
// classify -- counted, never predicted against).
|
|
158
|
+
function observedForm(hit, painted, labels) {
|
|
159
|
+
if (/^\s*│/.test(hit.t)) return 'bordered';
|
|
160
|
+
const near = painted
|
|
161
|
+
.filter((p) => p.f === hit.f && p.r >= hit.r - 2 && p.r <= hit.r + 2 * labels.length + 2)
|
|
162
|
+
.map((p) => p.t)
|
|
163
|
+
.join('\n');
|
|
164
|
+
if (near.includes(CARD_RULE) || labels.some((label) => label && near.includes(`${label}:`))) return 'cards';
|
|
165
|
+
return 'other';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// The re-draw: the shipped renderer at `cols`, SGR-stripped and right-trimmed, exactly as the
|
|
169
|
+
// painted rows are.
|
|
170
|
+
function renderRows(markdown, cols) {
|
|
171
|
+
return emitMarkdownRows(markdown, cols, { markdown: marked, style: MD_STYLE })
|
|
172
|
+
.map((line) => trimEnd(stripSgr(line)));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// The prediction for one drawing: try every width the session's stretch headers recorded, align the
|
|
176
|
+
// emitted rows to the painted window by the offset with the most identical rows, keep the best
|
|
177
|
+
// width. `predicted` is the form the re-draw took (no `│` row => the stacked cards).
|
|
178
|
+
function predict(raw, widths, window) {
|
|
179
|
+
let best = null;
|
|
180
|
+
for (const cols of widths) {
|
|
181
|
+
const lines = renderRows(raw, cols);
|
|
182
|
+
let identical = 0;
|
|
183
|
+
for (let off = 0; off < window.length; off += 1) {
|
|
184
|
+
let same = 0;
|
|
185
|
+
for (let i = 0; i < lines.length; i += 1) if (lines[i] === window[off + i]) same += 1;
|
|
186
|
+
if (same > identical) identical = same;
|
|
187
|
+
}
|
|
188
|
+
const candidate = {
|
|
189
|
+
cols,
|
|
190
|
+
predicted: lines.some((line) => line.includes('│')) ? 'bordered' : 'cards',
|
|
191
|
+
identical,
|
|
192
|
+
emitted: lines.length,
|
|
193
|
+
};
|
|
194
|
+
if (!best || candidate.identical > best.identical) best = candidate;
|
|
195
|
+
}
|
|
196
|
+
return best;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// One table token -> its record(s): one per stretch file that holds the probe, or a single
|
|
200
|
+
// `notfound` record when no cell is long enough / no painted row matches.
|
|
201
|
+
function locateTable({ sid, index, token, painted, widths }) {
|
|
202
|
+
const header = (token.header || []).map((cell) => cellText(cell.tokens));
|
|
203
|
+
const rows = (token.rows || []).map((row) => row.map((cell) => cellText(cell.tokens)));
|
|
204
|
+
const base = { sid: sid.slice(0, 8), table: index, columns: header.length, rows: rows.length };
|
|
205
|
+
const probe = (rows[0] || []).find((cell) => cell && cell.length >= PROBE_MIN)
|
|
206
|
+
|| header.find((cell) => cell && cell.length >= PROBE_MIN)
|
|
207
|
+
|| '';
|
|
208
|
+
if (!probe) return [{ ...base, form: 'notfound', predicted: null, identical: 0, emitted: 0, cols: null }];
|
|
209
|
+
const key = probe.slice(0, PROBE_LEN);
|
|
210
|
+
const hits = new Map(); // file -> the first painted row of that file holding the probe
|
|
211
|
+
for (const row of painted) if (!hits.has(row.f) && row.t.includes(key)) hits.set(row.f, row);
|
|
212
|
+
if (hits.size === 0) return [{ ...base, form: 'notfound', predicted: null, identical: 0, emitted: 0, cols: null }];
|
|
213
|
+
const out = [];
|
|
214
|
+
for (const hit of hits.values()) {
|
|
215
|
+
const window = painted
|
|
216
|
+
.filter((p) => p.f === hit.f && p.r >= hit.r - WINDOW_BEFORE && p.r <= hit.r + WINDOW_AFTER)
|
|
217
|
+
.map((p) => p.t);
|
|
218
|
+
const candidates = widths.length ? widths : (hit.cols ? [hit.cols] : []);
|
|
219
|
+
const best = predict(token.raw, candidates, window) || { cols: hit.cols, predicted: null, identical: 0, emitted: 0 };
|
|
220
|
+
out.push({ ...base, at: hit.r, form: observedForm(hit, painted, header), ...best });
|
|
221
|
+
}
|
|
222
|
+
return out;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function census(opts) {
|
|
226
|
+
const storeDirs = listDirs(opts.painted);
|
|
227
|
+
const projectDirs = listDirs(opts.projects);
|
|
228
|
+
const records = [];
|
|
229
|
+
let sessionsWithStore = 0;
|
|
230
|
+
let sessionsPaired = 0;
|
|
231
|
+
let tables = 0;
|
|
232
|
+
for (const sid of storeDirs) {
|
|
233
|
+
sessionsWithStore += 1;
|
|
234
|
+
const transcript = findTranscript(opts.projects, projectDirs, sid);
|
|
235
|
+
if (!transcript) continue;
|
|
236
|
+
sessionsPaired += 1;
|
|
237
|
+
const painted = readPainted(path.join(opts.painted, sid));
|
|
238
|
+
const widths = [...new Set(painted.map((row) => row.cols).filter(Boolean))];
|
|
239
|
+
let index = 0;
|
|
240
|
+
for (const line of readText(transcript).split('\n')) {
|
|
241
|
+
const message = parseLine(line);
|
|
242
|
+
if (!message || !message.message || message.message.role !== 'assistant') continue;
|
|
243
|
+
const parts = Array.isArray(message.message.content)
|
|
244
|
+
? message.message.content
|
|
245
|
+
: [{ type: 'text', text: message.message.content }];
|
|
246
|
+
for (const part of parts) {
|
|
247
|
+
if (!part || part.type !== 'text' || typeof part.text !== 'string') continue;
|
|
248
|
+
for (const token of lexTables(part.text)) {
|
|
249
|
+
index += 1;
|
|
250
|
+
tables += 1;
|
|
251
|
+
records.push(...locateTable({ sid, index, token, painted, widths }));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const observed = { bordered: 0, cards: 0, other: 0, notfound: 0 };
|
|
258
|
+
for (const record of records) observed[record.form] = (observed[record.form] || 0) + 1;
|
|
259
|
+
// `drawings` = the drawings whose form we could read (bordered + cards) -- the denominator of
|
|
260
|
+
// predictedAgree, the number the design's floor (313 of 328, §1.2) is stated against. `other`
|
|
261
|
+
// hits and `notfound` tables are carried in `observed` and never predicted against.
|
|
262
|
+
const drawings = observed.bordered + observed.cards;
|
|
263
|
+
const found = records.filter((record) => record.form === 'bordered' || record.form === 'cards');
|
|
264
|
+
const predictedAgree = found.filter((record) => record.predicted === record.form).length;
|
|
265
|
+
const byteIdentical = found.filter((record) => record.emitted > 0 && record.identical === record.emitted).length;
|
|
266
|
+
const within2 = found.filter((record) => record.emitted > 0 && record.identical >= record.emitted - 2).length;
|
|
267
|
+
return {
|
|
268
|
+
summary: {
|
|
269
|
+
sessionsWithStore,
|
|
270
|
+
sessionsPaired,
|
|
271
|
+
tables,
|
|
272
|
+
drawings,
|
|
273
|
+
observed,
|
|
274
|
+
predictedAgree,
|
|
275
|
+
predictedDisagree: found.length - predictedAgree,
|
|
276
|
+
byteIdentical,
|
|
277
|
+
within2,
|
|
278
|
+
},
|
|
279
|
+
records,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function main() {
|
|
284
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
285
|
+
const { summary, records } = census(opts);
|
|
286
|
+
process.stdout.write(`${JSON.stringify(summary)}\n`);
|
|
287
|
+
if (opts.json) return;
|
|
288
|
+
// Disagreement list: the observed form the prediction missed, or a re-draw that lost more than
|
|
289
|
+
// two rows of the painted drawing. Numbers and an 8-character session prefix only -- no cell.
|
|
290
|
+
const disagreements = records
|
|
291
|
+
.filter((record) => record.form !== 'notfound'
|
|
292
|
+
&& (record.predicted !== record.form || record.identical < record.emitted - 2))
|
|
293
|
+
.sort((a, b) => (a.sid === b.sid ? a.table - b.table : a.sid.localeCompare(b.sid)));
|
|
294
|
+
for (const record of disagreements) {
|
|
295
|
+
process.stdout.write(`${record.sid} table#${record.table} cols=${record.cols} `
|
|
296
|
+
+ `observed=${record.form} predicted=${record.predicted} identical=${record.identical}/${record.emitted}\n`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
main();
|
|
302
|
+
} catch (error) {
|
|
303
|
+
// The census is a report, not a test: an unexpected failure is printed on stderr and still
|
|
304
|
+
// exits 0 (numbers only -- never a painted row or a cell).
|
|
305
|
+
process.stderr.write(`claude-table-parity: ${error && error.message ? error.message : String(error)}\n`);
|
|
306
|
+
}
|
|
307
|
+
process.exitCode = 0;
|
|
@@ -15,6 +15,9 @@ const {
|
|
|
15
15
|
bindObservedDatabaseGenerations,
|
|
16
16
|
} = require('./conversation-storage-generation-evidence');
|
|
17
17
|
const { inspectCompletedMarker } = require('./conversation-storage-completed-marker');
|
|
18
|
+
// WP-3 (clock-free r5 R-4..R-8): walle liveness from the write-lock identity record, the
|
|
19
|
+
// proven-owned daemon stop before the first walle write, and a fail-closed foreign holder.
|
|
20
|
+
const { ensureWalleOwnerStopped } = require('./conversation-storage-walle-owner');
|
|
18
21
|
// No-op when options.profile is absent so behaviour is byte-identical to today: this
|
|
19
22
|
// module only ever calls profile.measure/measureAsync around existing calls, never adds
|
|
20
23
|
// new work, so an absent profile just means the timing calls disappear.
|
|
@@ -475,7 +478,13 @@ function runAutomaticConversationMigration(options = {}) {
|
|
|
475
478
|
}
|
|
476
479
|
}
|
|
477
480
|
if (!state.irreversibleAt) {
|
|
478
|
-
|
|
481
|
+
// R-5/R-8: stop this instance's PROVEN-OWNED Wall-E before the first walle write, and
|
|
482
|
+
// probe only the databases this run will mutate -- `specs` included a live walle in a
|
|
483
|
+
// CTM-only rerun, which blocked the boot on a database it was never going to touch.
|
|
484
|
+
const walleSpecForPreflight = specs.find((spec) => spec.id === 'walle');
|
|
485
|
+
const walleOwner = walleSpecForPreflight && plannedDatabaseIds.has('walle')
|
|
486
|
+
? ensureWalleOwnerStopped(walleSpecForPreflight.path, options.walleOwner || {}) : null;
|
|
487
|
+
profile.measure('assert_databases_offline', () => _assertDatabasesOffline(plannedSpecs()));
|
|
479
488
|
const headroomDatabaseIds = new Set(plannedDatabaseIds);
|
|
480
489
|
if (headroomDatabaseIds.has('ctm') && specs.some((spec) => spec.id === 'walle')) {
|
|
481
490
|
headroomDatabaseIds.add('walle');
|
|
@@ -486,6 +495,7 @@ function runAutomaticConversationMigration(options = {}) {
|
|
|
486
495
|
options,
|
|
487
496
|
)),
|
|
488
497
|
databaseOwnership: 'offline',
|
|
498
|
+
...(walleOwner ? { walleOwner } : {}),
|
|
489
499
|
};
|
|
490
500
|
checkpoint('preflight');
|
|
491
501
|
checkpoint('inventory');
|
|
@@ -654,6 +664,11 @@ function runAutomaticConversationMigration(options = {}) {
|
|
|
654
664
|
expandWalleDependency = true;
|
|
655
665
|
} finally { ctmDb.close(); walleDb.close(); }
|
|
656
666
|
if (expandWalleDependency) {
|
|
667
|
+
// R-5/R-8 (still inside the !state.irreversibleAt guard above): the expansion adds
|
|
668
|
+
// walle AFTER the preflight probe, so the owner must be proven stopped here too,
|
|
669
|
+
// before rebuild_walle -- and walle alone is re-probed.
|
|
670
|
+
state.preflight.walleOwner = ensureWalleOwnerStopped(walleSpec.path, options.walleOwner || {});
|
|
671
|
+
_assertDatabasesOffline([walleSpec]);
|
|
657
672
|
plannedDatabaseIds.add('walle');
|
|
658
673
|
delete state.cleanBackups.walle;
|
|
659
674
|
state.restartPreflight.plannedDatabaseIds = specs
|
|
@@ -5,6 +5,7 @@ const Database = require('better-sqlite3');
|
|
|
5
5
|
const migration = require('../../scripts/conversation-storage-v2-migration');
|
|
6
6
|
const {
|
|
7
7
|
sanitizeInspectionViolations,
|
|
8
|
+
inspectionErrorClass,
|
|
8
9
|
} = require('../../shared/conversation-storage-v2-contract');
|
|
9
10
|
|
|
10
11
|
// Moved from conversation-storage-auto-migration-lib.js (WP-5 un-golf/split: keeping the
|
|
@@ -170,8 +171,14 @@ function inspectCompletedMarkerSpec(spec, expectedGeneration, generationEvidence
|
|
|
170
171
|
violations,
|
|
171
172
|
...(generationChanged ? { generationChanged: true } : {}),
|
|
172
173
|
};
|
|
173
|
-
} catch {
|
|
174
|
-
|
|
174
|
+
} catch (error) {
|
|
175
|
+
// REPORT: an inspection that throws still plans a rerun (RERUN_CLASS, unchanged), but the
|
|
176
|
+
// boot log now names the error class instead of an empty violation list (clock-free r6 OQ-5).
|
|
177
|
+
return {
|
|
178
|
+
clean: false,
|
|
179
|
+
reasonCodes: ['inspection_failed'],
|
|
180
|
+
violations: [{ code: 'inspection_failed', objectId: inspectionErrorClass(error), count: 1 }],
|
|
181
|
+
};
|
|
175
182
|
}
|
|
176
183
|
}
|
|
177
184
|
|
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
buildConversationMigrationStatus,
|
|
13
13
|
} = require('../lib/conversation-storage-migration-status');
|
|
14
14
|
const { rebindCompletedGenerations } = require('./conversation-storage-generation-evidence');
|
|
15
|
+
const { resolveInstanceTag } = require('../lib/instance-tag');
|
|
15
16
|
const { createPhaseProfile } = require('../lib/phase-profile');
|
|
16
17
|
const {
|
|
17
18
|
sanitizeInspectionViolations,
|
|
@@ -102,7 +103,13 @@ function buildConversationStorageServiceLog(result = {}) {
|
|
|
102
103
|
recoveryArtifactCount: status.recoveryArtifactCount,
|
|
103
104
|
cleanupPendingCount: status.cleanupPendingCount,
|
|
104
105
|
};
|
|
105
|
-
|
|
106
|
+
// R-6 (clock-free r4): restartPreflight describes a migration that was PLANNED by the
|
|
107
|
+
// boot that ran it. An already_complete / generation_rebound boot (result.skipped set)
|
|
108
|
+
// only re-reads the journal, so re-emitting the recorded object would print a stale
|
|
109
|
+
// re-run plan for a boot that ran no migration -- exactly what misled r3's census.
|
|
110
|
+
const restartPreflight = result.skipped == null
|
|
111
|
+
? _restartPreflightForServiceLog(result.restartPreflight)
|
|
112
|
+
: null;
|
|
106
113
|
if (restartPreflight) summary.restartPreflight = restartPreflight;
|
|
107
114
|
return summary;
|
|
108
115
|
}
|
|
@@ -129,6 +136,14 @@ function resolveAutomaticConversationMigrationOptions(env = process.env, homedir
|
|
|
129
136
|
},
|
|
130
137
|
batchLimit: Number(env.CTM_CONVERSATION_MIGRATION_BATCH_ROWS) || undefined,
|
|
131
138
|
batchByteLimit: Number(env.CTM_CONVERSATION_MIGRATION_BATCH_BYTES) || undefined,
|
|
139
|
+
// WP-3 (clock-free r5 R-5/R-6): the kill authority the boot migration uses to prove and
|
|
140
|
+
// stop THIS instance's Wall-E before it touches wall-e-brain.db. Same tag derivation the
|
|
141
|
+
// supervisor uses (server.js:5897), so a dev/test instance can never signal the primary's
|
|
142
|
+
// daemon. Kept out of buildConversationStorageServiceLog, which never serializes options.
|
|
143
|
+
walleOwner: {
|
|
144
|
+
configDir: ctmDataDir,
|
|
145
|
+
instanceTag: resolveInstanceTag({ env, configDir: ctmDataDir, port: parseInt(env.CTM_PORT || '3456', 10) }),
|
|
146
|
+
},
|
|
132
147
|
};
|
|
133
148
|
}
|
|
134
149
|
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-3 / R-4..R-8 of docs/clock-free-tests-followups-r5-2026-10-25.html §5.
|
|
4
|
+
//
|
|
5
|
+
// Liveness for the boot migration's destructive walle re-run comes from Wall-E's own
|
|
6
|
+
// write-lock identity record — `<db>.write-lock`, a 1,024-byte space-padded JSON
|
|
7
|
+
// `{pid,...}` that PERSISTS after release (shared/sqlite-write-lock.js:1-40,250, D-3) —
|
|
8
|
+
// not from a `BEGIN IMMEDIATE` probe. Probe P1 showed that an owner which is alive with an
|
|
9
|
+
// idle connection (or no connection at all) passes the transaction probe, and the purge
|
|
10
|
+
// then rewrites the database under it (the 2026-07-20 column-loss incident, r4 §1.1).
|
|
11
|
+
//
|
|
12
|
+
// The module is synchronous on purpose: bin/ctm-service-entry.js runs this migration
|
|
13
|
+
// BEFORE it requires ../server, and the stop has to finish before the first walle write.
|
|
14
|
+
//
|
|
15
|
+
// R-6: nothing here is ever signalled on a name match. The one pid that may receive a
|
|
16
|
+
// signal is the daemon pid read from `<configDir>/walle.pid`, and only after a single
|
|
17
|
+
// `ps -p <pid>` row proves it is this instance's own Wall-E (lib/walle-kill-authority.js).
|
|
18
|
+
// R-7: any other live write-lock holder stays fail-closed (`conversation_migration_database_busy`),
|
|
19
|
+
// which the caller maps to exit 78 exactly as before.
|
|
20
|
+
|
|
21
|
+
const fs = require('node:fs');
|
|
22
|
+
const path = require('node:path');
|
|
23
|
+
const { execFileSync } = require('node:child_process');
|
|
24
|
+
const { processTitleCandidates } = require('../lib/process-title');
|
|
25
|
+
const { isOwnedRow } = require('../lib/walle-kill-authority');
|
|
26
|
+
const { intFlag } = require('../lib/env-flags');
|
|
27
|
+
|
|
28
|
+
const WALLE_PID_FILE = 'walle.pid';
|
|
29
|
+
const WRITE_LOCK_SUFFIX = '.write-lock';
|
|
30
|
+
const PS_TIMEOUT_MS = 5000;
|
|
31
|
+
const PS_MAX_BUFFER = 1024 * 1024;
|
|
32
|
+
// One SIGKILL grace window after the graceful budget is spent; a killed-but-unreaped
|
|
33
|
+
// process is still a live pid until the kernel reaps it.
|
|
34
|
+
const SIGKILL_GRACE_MS = 1000;
|
|
35
|
+
// Same budget, default and clamp as lib/walle-supervisor.js:333,389 — a Wall-E that is
|
|
36
|
+
// legitimately draining inside its own WALLE_SHUTDOWN_DEADLINE_MS gets to finish.
|
|
37
|
+
const CHILD_EXIT_WAIT_ENV = 'WALLE_CHILD_EXIT_WAIT_MS';
|
|
38
|
+
const DEFAULT_CHILD_EXIT_WAIT_MS = 16000;
|
|
39
|
+
const MIN_CHILD_EXIT_WAIT_MS = 5000;
|
|
40
|
+
const MAX_CHILD_EXIT_WAIT_MS = 120000;
|
|
41
|
+
const DEFAULT_LOCK_RELEASE_WAIT_MS = 2000;
|
|
42
|
+
const DEFAULT_POLL_MS = 100;
|
|
43
|
+
|
|
44
|
+
function _childExitWaitMs() {
|
|
45
|
+
return Math.max(MIN_CHILD_EXIT_WAIT_MS, Math.min(MAX_CHILD_EXIT_WAIT_MS,
|
|
46
|
+
intFlag(CHILD_EXIT_WAIT_ENV, DEFAULT_CHILD_EXIT_WAIT_MS)));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// R-4: the owner identity, read off disk. null covers ENOENT (nothing ever wrote here),
|
|
50
|
+
// EISDIR and a torn read of a v2 in-place write — every one of those means "no usable
|
|
51
|
+
// identity", never "throw". Shape copied from shared/sqlite-write-lock.js:250 (_readIdentity)
|
|
52
|
+
// and wall-e/bin/brain-purge-legacy-ctm.js:236-245 (liveReason's first check).
|
|
53
|
+
function readWriteLockOwnerPid(dbPath) {
|
|
54
|
+
let record;
|
|
55
|
+
try {
|
|
56
|
+
record = JSON.parse(fs.readFileSync(`${dbPath}${WRITE_LOCK_SUFFIX}`, 'utf8'));
|
|
57
|
+
} catch {
|
|
58
|
+
/* expected: ENOENT (no writer yet), EISDIR (a directory sits at the path) and a torn
|
|
59
|
+
read of an in-place v2 write all mean "no identity readable right now". */
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const pid = Number(record?.pid);
|
|
63
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// R-4: only ESRCH proves the pid is gone. EPERM means the pid EXISTS but is owned by
|
|
67
|
+
// another user — kill(pid,0) probes existence, not killability — so every non-ESRCH
|
|
68
|
+
// error fails toward "alive" (shared/sqlite-write-lock.js:231-245).
|
|
69
|
+
function pidAlive(pid, kill = process.kill) {
|
|
70
|
+
const value = Number(pid);
|
|
71
|
+
if (!Number.isFinite(value) || value <= 0) return false;
|
|
72
|
+
try {
|
|
73
|
+
kill(value, 0);
|
|
74
|
+
return true;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return error?.code !== 'ESRCH';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// A pid that has exited but has not been REAPED is still a live pid for kill(pid,0): the
|
|
81
|
+
// kernel keeps a zombie's pid until its parent waits. libuv only waits when the event loop
|
|
82
|
+
// runs, and this migration blocks the loop for its whole duration (see the module comment:
|
|
83
|
+
// it runs before ../server is required), so a daemon this process spawned -- ppid === myPid,
|
|
84
|
+
// which lib/walle-kill-authority.js:17 explicitly accepts as owned -- stays kill(pid,0)-alive
|
|
85
|
+
// forever after it has exited. Measured on this host: `kill(pid,0)` says alive while
|
|
86
|
+
// `ps -o stat=` says "Z" for the whole blocked window, then the pid disappears once the loop
|
|
87
|
+
// turns. A zombie has exited, so it must read as gone -- otherwise the SIGTERM we just sent
|
|
88
|
+
// would be reported back to the caller as "a live holder that is not this instance's Wall-E".
|
|
89
|
+
// Only consulted when kill(pid,0) already says alive, so the common reparented-to-launchd
|
|
90
|
+
// daemon (ppid 1, reaped by init) never pays for it.
|
|
91
|
+
function _isZombiePid(pid) {
|
|
92
|
+
try {
|
|
93
|
+
const stdout = execFileSync('ps', ['-p', String(pid), '-o', 'stat='], {
|
|
94
|
+
encoding: 'utf8',
|
|
95
|
+
timeout: PS_TIMEOUT_MS,
|
|
96
|
+
maxBuffer: 4096,
|
|
97
|
+
});
|
|
98
|
+
return String(stdout).trim().startsWith('Z');
|
|
99
|
+
} catch {
|
|
100
|
+
/* expected: ps exits non-zero when the pid has no row -- it is gone, or it was reaped
|
|
101
|
+
between the two probes. Either way there is no zombie to report. */
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function _readWalleDaemonPid(configDir) {
|
|
107
|
+
if (!configDir) return null;
|
|
108
|
+
try {
|
|
109
|
+
const pid = Number.parseInt(fs.readFileSync(path.join(String(configDir), WALLE_PID_FILE), 'utf8').trim(), 10);
|
|
110
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
111
|
+
} catch {
|
|
112
|
+
/* expected: no pid file (Wall-E never started for this config dir) or an unreadable
|
|
113
|
+
one both mean "no daemon to stop" — R-7 still fails closed on the write-lock owner. */
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// The same synchronous sleep idiom the write-lock module uses (shared/sqlite-write-lock.js:226-229).
|
|
119
|
+
function sleepSync(ms) {
|
|
120
|
+
const buffer = new SharedArrayBuffer(4);
|
|
121
|
+
Atomics.wait(new Int32Array(buffer), 0, 0, ms);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let _parsePsWithPpid = null;
|
|
125
|
+
|
|
126
|
+
// Rows for ONE pid, parsed by the supervisor's own parser so ownership sees an identical
|
|
127
|
+
// row shape (lib/walle-supervisor.js:166-184,205-212). Loaded lazily: the migration runs
|
|
128
|
+
// before ../server boots, and the ps path is only reached when a live owner record exists.
|
|
129
|
+
function _psRow(pid) {
|
|
130
|
+
if (!_parsePsWithPpid) ({ parsePsWithPpid: _parsePsWithPpid } = require('../lib/walle-supervisor'));
|
|
131
|
+
try {
|
|
132
|
+
const stdout = execFileSync('ps', ['-p', String(pid), '-o', 'pid=,ppid=,command='], {
|
|
133
|
+
encoding: 'utf8',
|
|
134
|
+
timeout: PS_TIMEOUT_MS,
|
|
135
|
+
maxBuffer: PS_MAX_BUFFER,
|
|
136
|
+
});
|
|
137
|
+
return _parsePsWithPpid(stdout);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
// A non-zero exit is ps answering "no such process" -> an empty row list.
|
|
140
|
+
// Anything else (ps un-runnable, timed out) is the probe itself failing -> null, which
|
|
141
|
+
// ownership must read as "not proven owned", never as "there is no process".
|
|
142
|
+
return Number.isInteger(error?.status) ? [] : null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// R-4/R-5/R-7 liveness, one predicate: gone (ESRCH) or exited-and-not-yet-reaped (zombie).
|
|
147
|
+
function _hasExited(pid, ops) {
|
|
148
|
+
return !pidAlive(pid, ops.kill) || ops.zombie(pid);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Poll until the pid is gone, bounded by BOTH the deadline and an iteration cap: a caller
|
|
152
|
+
// that injects a `now` which does not advance must not spin this loop forever.
|
|
153
|
+
function _waitForExit(pid, timeoutMs, pollMs, ops) {
|
|
154
|
+
if (_hasExited(pid, ops)) return true;
|
|
155
|
+
const budget = Math.max(0, Number(timeoutMs) || 0);
|
|
156
|
+
const poll = Math.max(1, Number(pollMs) || DEFAULT_POLL_MS);
|
|
157
|
+
const deadline = ops.now() + budget;
|
|
158
|
+
const maxIterations = Math.ceil(budget / poll) + 1;
|
|
159
|
+
for (let index = 0; index < maxIterations; index += 1) {
|
|
160
|
+
if (ops.now() >= deadline) break;
|
|
161
|
+
ops.sleep(Math.max(1, Math.min(poll, deadline - ops.now())));
|
|
162
|
+
if (_hasExited(pid, ops)) return true;
|
|
163
|
+
}
|
|
164
|
+
return _hasExited(pid, ops);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// R-5/R-6/R-7. Returns { stopped, pid, waitedMs, signal }; throws
|
|
168
|
+
// `conversation_migration_database_busy` when a live holder survives (R-7).
|
|
169
|
+
function ensureWalleOwnerStopped(walleDbPath, options = {}) {
|
|
170
|
+
const injected = options.ops || {};
|
|
171
|
+
const ops = {
|
|
172
|
+
kill: typeof injected.kill === 'function' ? injected.kill : process.kill,
|
|
173
|
+
psRow: typeof injected.psRow === 'function' ? injected.psRow : _psRow,
|
|
174
|
+
sleep: typeof injected.sleep === 'function' ? injected.sleep : sleepSync,
|
|
175
|
+
now: typeof injected.now === 'function' ? injected.now : Date.now,
|
|
176
|
+
myPid: Number.isInteger(injected.myPid) ? injected.myPid : process.pid,
|
|
177
|
+
// A caller that injects its own process table owns liveness entirely -- probing the
|
|
178
|
+
// real ps for a fake pid would be meaningless (and would fork a process per poll). The
|
|
179
|
+
// real zombie probe is only armed on the real process.kill path.
|
|
180
|
+
zombie: typeof injected.zombie === 'function' ? injected.zombie
|
|
181
|
+
: (typeof injected.kill === 'function' ? () => false : _isZombiePid),
|
|
182
|
+
};
|
|
183
|
+
const instanceTag = options.instanceTag ? String(options.instanceTag) : null;
|
|
184
|
+
const childExitWaitMs = Number.isFinite(options.childExitWaitMs)
|
|
185
|
+
? Math.max(0, Number(options.childExitWaitMs)) : _childExitWaitMs();
|
|
186
|
+
const lockReleaseWaitMs = Number.isFinite(options.lockReleaseWaitMs)
|
|
187
|
+
? Math.max(0, Number(options.lockReleaseWaitMs)) : DEFAULT_LOCK_RELEASE_WAIT_MS;
|
|
188
|
+
const pollMs = Number.isFinite(options.pollMs) ? Math.max(1, Number(options.pollMs)) : DEFAULT_POLL_MS;
|
|
189
|
+
const startedAt = ops.now();
|
|
190
|
+
|
|
191
|
+
const ownerPid = readWriteLockOwnerPid(walleDbPath);
|
|
192
|
+
if (ownerPid === null || ownerPid === ops.myPid || _hasExited(ownerPid, ops)) {
|
|
193
|
+
// No usable record, our own pid, or a provably dead writer: nothing to stop, and no
|
|
194
|
+
// ownership probe is even attempted.
|
|
195
|
+
return { stopped: false, pid: null, waitedMs: 0, signal: null };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let daemonPid = null;
|
|
199
|
+
let signal = null;
|
|
200
|
+
if (options.configDir && instanceTag) {
|
|
201
|
+
const candidate = _readWalleDaemonPid(options.configDir);
|
|
202
|
+
if (candidate !== null && !_hasExited(candidate, ops)) daemonPid = candidate;
|
|
203
|
+
}
|
|
204
|
+
if (daemonPid !== null) {
|
|
205
|
+
const rows = ops.psRow(daemonPid);
|
|
206
|
+
const row = (Array.isArray(rows) ? rows : []).find((entry) => Number(entry?.pid) === daemonPid) || null;
|
|
207
|
+
const owned = isOwnedRow(row, {
|
|
208
|
+
titles: new Set(processTitleCandidates('walle', instanceTag)),
|
|
209
|
+
myPid: ops.myPid,
|
|
210
|
+
});
|
|
211
|
+
if (owned) {
|
|
212
|
+
ops.kill(daemonPid, 'SIGTERM');
|
|
213
|
+
signal = 'SIGTERM';
|
|
214
|
+
if (!_waitForExit(daemonPid, childExitWaitMs, pollMs, ops)) {
|
|
215
|
+
ops.kill(daemonPid, 'SIGKILL');
|
|
216
|
+
signal = 'SIGKILL';
|
|
217
|
+
_waitForExit(daemonPid, SIGKILL_GRACE_MS, pollMs, ops);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// The write-lock owner may be a different pid than the daemon (a worker, or a foreign
|
|
222
|
+
// process). It gets the same short grace — the lock is released by process exit — and
|
|
223
|
+
// then fails closed below. It is never signalled unless it IS the proven daemon pid.
|
|
224
|
+
if (!_hasExited(ownerPid, ops)) _waitForExit(ownerPid, lockReleaseWaitMs, pollMs, ops);
|
|
225
|
+
|
|
226
|
+
if (!_hasExited(ownerPid, ops)) {
|
|
227
|
+
const error = new Error(
|
|
228
|
+
`database is owned by a live process that is not this instance's Wall-E: walle pid=${ownerPid}`,
|
|
229
|
+
);
|
|
230
|
+
error.code = 'conversation_migration_database_busy';
|
|
231
|
+
error.database = 'walle';
|
|
232
|
+
error.ownerPid = ownerPid;
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
stopped: signal !== null,
|
|
237
|
+
pid: daemonPid,
|
|
238
|
+
waitedMs: Math.max(0, ops.now() - startedAt),
|
|
239
|
+
signal,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = {
|
|
244
|
+
ensureWalleOwnerStopped,
|
|
245
|
+
pidAlive,
|
|
246
|
+
readWriteLockOwnerPid,
|
|
247
|
+
};
|