@sema-agent/core 7.54.1 → 7.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +437 -0
- package/dist/agents/agent-transcript-tool.js +2 -1
- package/dist/agents/builtin-agents.d.ts +12 -14
- package/dist/agents/cc-peer-directory.d.ts +36 -0
- package/dist/agents/cc-peer-directory.js +138 -0
- package/dist/agents/cc-peer-wire.d.ts +105 -0
- package/dist/agents/cc-peer-wire.js +98 -0
- package/dist/agents/cc-uds-peer-channel.d.ts +40 -0
- package/dist/agents/cc-uds-peer-channel.js +457 -0
- package/dist/agents/cross-session-envelope.d.ts +4 -1
- package/dist/agents/cross-session-envelope.js +8 -1
- package/dist/agents/cross-session-judge.d.ts +2 -2
- package/dist/agents/list-agents-tool.js +2 -1
- package/dist/agents/observer.d.ts +4 -5
- package/dist/agents/observer.js +2 -1
- package/dist/agents/peer-admission.d.ts +6 -0
- package/dist/agents/peer-admission.js +4 -1
- package/dist/agents/peer-directory.d.ts +43 -9
- package/dist/agents/peer-directory.js +66 -23
- package/dist/agents/peer-notices.d.ts +1 -1
- package/dist/agents/peer-session-drain.d.ts +3 -1
- package/dist/agents/peer-session-drain.js +1 -0
- package/dist/agents/read-inbox-tool.js +2 -1
- package/dist/agents/retain-ledger.d.ts +1 -1
- package/dist/agents/roster-store.d.ts +1 -1
- package/dist/agents/send-message-tool.d.ts +10 -0
- package/dist/agents/send-message-tool.js +34 -5
- package/dist/agents/subagent.d.ts +34 -79
- package/dist/agents/subagent.js +45 -46
- package/dist/agents/tool-filter.d.ts +29 -46
- package/dist/agents/tool-filter.js +8 -31
- package/dist/agents/verify.d.ts +3 -3
- package/dist/brain/anthropic.js +14 -9
- package/dist/brain/errors.d.ts +15 -0
- package/dist/brain/errors.js +10 -0
- package/dist/brain/model-presets.d.ts +4 -4
- package/dist/brain/open-responses.js +15 -10
- package/dist/brain/openai.js +17 -12
- package/dist/brain/provider-dialect.d.ts +39 -3
- package/dist/brain/provider-dialect.js +28 -0
- package/dist/brain/reasoning.d.ts +1 -1
- package/dist/brain/request-params.d.ts +55 -5
- package/dist/brain/request-params.js +35 -3
- package/dist/brain/retry.d.ts +1 -1
- package/dist/brain/route-adjudicator.d.ts +15 -0
- package/dist/brain/route-adjudicator.js +15 -0
- package/dist/brain/stream-engine.js +6 -5
- package/dist/brain/stream-shared.d.ts +1 -1
- package/dist/core/advisor-verdict.d.ts +120 -0
- package/dist/core/advisor-verdict.js +92 -0
- package/dist/core/agent-definition.d.ts +7 -8
- package/dist/core/allow-entry.d.ts +49 -23
- package/dist/core/allow-entry.js +57 -32
- package/dist/core/allow-rule-shadow.d.ts +7 -0
- package/dist/core/approval-object.d.ts +2 -1
- package/dist/core/approval-object.js +6 -6
- package/dist/core/ask-origin.d.ts +63 -11
- package/dist/core/ask-origin.js +27 -5
- package/dist/core/ask-question.js +10 -4
- package/dist/core/auto-compaction.d.ts +11 -8
- package/dist/core/auto-compaction.js +4 -2
- package/dist/core/auto-mode-arming.d.ts +2 -2
- package/dist/core/auto-mode-defaults.d.ts +8 -8
- package/dist/core/auto-mode-prompt-assets.d.ts +3 -3
- package/dist/core/auto-mode-prompt.d.ts +1 -1
- package/dist/core/auto-mode.d.ts +17 -9
- package/dist/core/auto-mode.js +9 -6
- package/dist/core/background-agent-store.d.ts +3 -3
- package/dist/core/canonical-json.d.ts +1 -1
- package/dist/core/canonical-json.js +2 -2
- package/dist/core/checkpoint-requirements.d.ts +6 -3
- package/dist/core/checkpoint-requirements.js +4 -14
- package/dist/core/checkpoint-store.d.ts +80 -51
- package/dist/core/checkpoint-store.js +56 -74
- package/dist/core/child-clamp-seats.d.ts +6 -0
- package/dist/core/child-clamp-seats.js +3 -16
- package/dist/core/compliance.d.ts +2 -2
- package/dist/core/context-edit.d.ts +6 -7
- package/dist/core/cross-boundary-taint.d.ts +9 -3
- package/dist/core/cross-boundary-taint.js +5 -3
- package/dist/core/decision-receipt.js +2 -2
- package/dist/core/effective-path-target.d.ts +1 -1
- package/dist/core/engine-notice.d.ts +19 -5
- package/dist/core/exec-output-tail.d.ts +1 -1
- package/dist/core/file-history-retention.d.ts +1 -1
- package/dist/core/file-history-store.d.ts +59 -9
- package/dist/core/file-history-store.js +35 -17
- package/dist/core/gate-fold.js +3 -2
- package/dist/core/gate-lanes.js +12 -8
- package/dist/core/gate-outcome.d.ts +17 -4
- package/dist/core/gate-outcome.js +30 -0
- package/dist/core/governance-codes.d.ts +13 -3
- package/dist/core/governance-codes.js +10 -0
- package/dist/core/hands-band.d.ts +1 -1
- package/dist/core/hooks.d.ts +4 -4
- package/dist/core/host-target.d.ts +49 -0
- package/dist/core/host-target.js +59 -0
- package/dist/core/image-downsample.d.ts +1 -1
- package/dist/core/lsp-diagnostics.d.ts +4 -4
- package/dist/core/lsp.d.ts +3 -3
- package/dist/core/mailbox-store.d.ts +4 -0
- package/dist/core/mcp-connection-pool.d.ts +18 -2
- package/dist/core/mcp-connection-pool.js +53 -6
- package/dist/core/mcp-failure.d.ts +23 -4
- package/dist/core/mcp-failure.js +26 -11
- package/dist/core/mcp-listing-drift.d.ts +10 -0
- package/dist/core/mcp-listing-drift.js +6 -0
- package/dist/core/mcp-server-link.d.ts +173 -0
- package/dist/core/mcp-server-link.js +127 -0
- package/dist/core/mcp-server-spec.d.ts +4 -2
- package/dist/core/mcp-session-end.d.ts +26 -0
- package/dist/core/mcp-session-end.js +29 -0
- package/dist/core/mcp-startup-request.d.ts +17 -0
- package/dist/core/mcp-startup-request.js +17 -0
- package/dist/core/mcp.d.ts +13 -13
- package/dist/core/mcp.js +187 -162
- package/dist/core/media-byte-cap.d.ts +1 -1
- package/dist/core/memory-engine/engine.d.ts +9 -9
- package/dist/core/memory-engine/provenance-wording.d.ts +1 -1
- package/dist/core/memory-engine/tools.js +4 -1
- package/dist/core/mode-policy.d.ts +1 -1
- package/dist/core/namespaced-rule-name.d.ts +33 -7
- package/dist/core/namespaced-rule-name.js +39 -25
- package/dist/core/output-style.d.ts +1 -1
- package/dist/core/param-rule-reach.d.ts +15 -0
- package/dist/core/param-rule-reach.js +30 -0
- package/dist/core/parent-spec-seat.d.ts +8 -1
- package/dist/core/parent-spec-seat.js +4 -0
- package/dist/core/pause-registry.d.ts +16 -5
- package/dist/core/pause-registry.js +6 -2
- package/dist/core/peer-channel.d.ts +70 -0
- package/dist/core/peer-channel.js +1 -0
- package/dist/core/permission-rule-consent.d.ts +11 -5
- package/dist/core/permission-rule-consent.js +37 -30
- package/dist/core/permission-rule-model.d.ts +233 -64
- package/dist/core/permission-rule-model.js +384 -85
- package/dist/core/permission-rule-provider.d.ts +2 -1
- package/dist/core/permission-rule-store.d.ts +20 -25
- package/dist/core/permission-rule-store.js +1 -4
- package/dist/core/permission-rule-sync.d.ts +11 -2
- package/dist/core/permission-rule-sync.js +8 -1
- package/dist/core/permission-rules.d.ts +49 -11
- package/dist/core/permission-rules.js +123 -89
- package/dist/core/persisted-rule-arms.d.ts +4 -2
- package/dist/core/present-plan-tool.js +3 -1
- package/dist/core/protocol-naming.d.ts +2 -2
- package/dist/core/read-only-shell-table.d.ts +1 -1
- package/dist/core/read-only-shell.d.ts +1 -1
- package/dist/core/roles.d.ts +6 -2
- package/dist/core/roles.js +45 -44
- package/dist/core/rule-offer-read.d.ts +53 -0
- package/dist/core/rule-offer-read.js +146 -0
- package/dist/core/rule-owner.d.ts +46 -0
- package/dist/core/rule-owner.js +28 -0
- package/dist/core/runner/advisor-instructions.d.ts +70 -0
- package/dist/core/runner/advisor-instructions.js +61 -0
- package/dist/core/runner/advisor-tool.d.ts +33 -0
- package/dist/core/runner/advisor-tool.js +112 -0
- package/dist/core/runner/announce-once-ledger.d.ts +4 -26
- package/dist/core/runner/announce-once-ledger.js +14 -46
- package/dist/core/runner/approval-feedback.js +6 -3
- package/dist/core/runner/assemble-result.d.ts +1 -1
- package/dist/core/runner/caller-roster.d.ts +7 -0
- package/dist/core/runner/caller-roster.js +11 -0
- package/dist/core/runner/classifier-ladder.d.ts +27 -5
- package/dist/core/runner/classifier-ladder.js +12 -11
- package/dist/core/runner/compaction-restate.d.ts +10 -6
- package/dist/core/runner/compaction-restate.js +9 -3
- package/dist/core/runner/contracts.d.ts +13 -9
- package/dist/core/runner/conversation-segment.d.ts +28 -0
- package/dist/core/runner/conversation-segment.js +42 -0
- package/dist/core/runner/cross-boundary-lane.d.ts +6 -5
- package/dist/core/runner/cross-boundary-lane.js +26 -15
- package/dist/core/runner/decide-continuation.d.ts +7 -4
- package/dist/core/runner/decide-continuation.js +2 -2
- package/dist/core/runner/denial-limit-arms.d.ts +2 -2
- package/dist/core/runner/derived-route-fallback.d.ts +24 -24
- package/dist/core/runner/derived-route-fallback.js +9 -7
- package/dist/core/runner/gate-exit.d.ts +6 -0
- package/dist/core/runner/gate-exit.js +7 -2
- package/dist/core/runner/initial-run-state.js +1 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -2
- package/dist/core/runner/permission-rule-lanes.js +14 -7
- package/dist/core/runner/person-path-rows.js +1 -1
- package/dist/core/runner/prepare-advisor.d.ts +63 -0
- package/dist/core/runner/prepare-advisor.js +77 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-config-doors.d.ts +6 -4
- package/dist/core/runner/prepare-config-doors.js +18 -12
- package/dist/core/runner/prepare-delegation-surface.d.ts +2 -2
- package/dist/core/runner/prepare-delegation-surface.js +1 -1
- package/dist/core/runner/prepare-file-history.js +39 -39
- package/dist/core/runner/prepare-gate-stations.d.ts +1 -4
- package/dist/core/runner/prepare-gate-stations.js +2 -7
- package/dist/core/runner/prepare-inherited-gate.d.ts +5 -4
- package/dist/core/runner/prepare-inherited-gate.js +3 -3
- package/dist/core/runner/prepare-park-ask.js +7 -7
- package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
- package/dist/core/runner/prepare-policy-chain.js +4 -10
- package/dist/core/runner/prepare-project-context.d.ts +5 -2
- package/dist/core/runner/prepare-project-context.js +6 -3
- package/dist/core/runner/prepare-prompt-inputs.js +1 -1
- package/dist/core/runner/prepare-protocol-tools.d.ts +2 -2
- package/dist/core/runner/prepare-protocol-tools.js +3 -2
- package/dist/core/runner/prepare-safety-scan.d.ts +9 -12
- package/dist/core/runner/prepare-safety-scan.js +8 -6
- package/dist/core/runner/prepare-task.js +15 -18
- package/dist/core/runner/prepare-tool-disclosure-mount.js +6 -4
- package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -3
- package/dist/core/runner/prepare-wiring-manifest.js +2 -2
- package/dist/core/runner/resume-policy-outcome.js +1 -1
- package/dist/core/runner/retired-runner-deps.js +1 -1
- package/dist/core/runner/run-attachment-seats.js +0 -1
- package/dist/core/runner/run-compaction-machinery.d.ts +1 -1
- package/dist/core/runner/run-compaction-machinery.js +2 -12
- package/dist/core/runner/run-git-lane.d.ts +1 -0
- package/dist/core/runner/run-harness-handlers.js +2 -3
- package/dist/core/runner/run-leg.js +13 -0
- package/dist/core/runner/run-settle-and-teardown.js +1 -1
- package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
- package/dist/core/runner/run-turn-boundary.d.ts +1 -0
- package/dist/core/runner/run-turn-boundary.js +14 -12
- package/dist/core/runner/session-rule-policy.d.ts +22 -21
- package/dist/core/runner/session-rule-policy.js +13 -28
- package/dist/core/runner/side-call-account.d.ts +32 -8
- package/dist/core/runner/side-call-account.js +23 -1
- package/dist/core/runner/strict-output-schema.d.ts +1 -1
- package/dist/core/runner/sub-agent-deny-lists.js +4 -3
- package/dist/core/runner/synthetic-tools.d.ts +10 -13
- package/dist/core/runner/synthetic-tools.js +4 -2
- package/dist/core/runner/tool-disclosure.js +2 -1
- package/dist/core/runner/tool-face-overlay.d.ts +2 -1
- package/dist/core/runner/tool-face-overlay.js +17 -1
- package/dist/core/runner/tool-progress-lane.d.ts +2 -2
- package/dist/core/runner/tool-turn-repair-seat.d.ts +3 -2
- package/dist/core/runner/turn-attachments.d.ts +60 -67
- package/dist/core/runner-deps.d.ts +37 -37
- package/dist/core/runtime-caps.d.ts +4 -4
- package/dist/core/safety-merge-corpus.js +17 -1
- package/dist/core/same-plain-value.d.ts +40 -0
- package/dist/core/same-plain-value.js +49 -0
- package/dist/core/scheduler.d.ts +1 -1
- package/dist/core/sensitive-path-policy.d.ts +1 -1
- package/dist/core/sensitive-path-policy.js +1 -1
- package/dist/core/session-policy-store.d.ts +77 -22
- package/dist/core/session-policy-store.js +56 -14
- package/dist/core/session-reconcile.d.ts +2 -2
- package/dist/core/session-store.d.ts +80 -30
- package/dist/core/session-store.js +118 -76
- package/dist/core/session.d.ts +24 -46
- package/dist/core/shared-memory/tools.js +3 -1
- package/dist/core/shared-memory/types.d.ts +1 -1
- package/dist/core/side-query.d.ts +10 -0
- package/dist/core/side-query.js +5 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +29 -2
- package/dist/core/store-contracts/file-history-store-contract.js +43 -3
- package/dist/core/store-contracts/session-policy-store-contract.d.ts +7 -0
- package/dist/core/store-contracts/session-policy-store-contract.js +130 -0
- package/dist/core/store-contracts/session-repo-contract.js +125 -0
- package/dist/core/store-contracts/session-store-contract.d.ts +4 -7
- package/dist/core/store-contracts/session-store-contract.js +215 -41
- package/dist/core/store-contracts/workflow-journal-store-contract.js +26 -1
- package/dist/core/surrogate-safe-slice.d.ts +3 -3
- package/dist/core/task-event.d.ts +10 -4
- package/dist/core/task-notification.d.ts +3 -3
- package/dist/core/task-registry-agent.d.ts +4 -4
- package/dist/core/task-registry-shared.d.ts +4 -4
- package/dist/core/task-registry-shared.js +1 -0
- package/dist/core/task-registry.d.ts +4 -4
- package/dist/core/task-registry.js +3 -1
- package/dist/core/task-result.d.ts +3 -3
- package/dist/core/task-spec.d.ts +52 -52
- package/dist/core/task-stream.d.ts +6 -6
- package/dist/core/task-tool-shape.d.ts +4 -4
- package/dist/core/tighten-task-spec.d.ts +1 -1
- package/dist/core/tool-catalog-entries.d.ts +19 -2
- package/dist/core/tool-catalog-entries.js +57 -42
- package/dist/core/tool-conformance.js +1 -0
- package/dist/core/tool-errors.d.ts +2 -2
- package/dist/core/tool-face-reader.js +5 -3
- package/dist/core/tool-face.d.ts +21 -2
- package/dist/core/tool-face.js +1 -1
- package/dist/core/tool-model-gate.d.ts +3 -3
- package/dist/core/tool-name-aliases.d.ts +29 -8
- package/dist/core/tool-name-aliases.js +27 -3
- package/dist/core/tool-name-readings.d.ts +5 -8
- package/dist/core/tool-name-readings.js +1 -2
- package/dist/core/tool-policy.d.ts +24 -5
- package/dist/core/tool-policy.js +34 -10
- package/dist/core/tool-registry.d.ts +14 -7
- package/dist/core/tool-registry.js +17 -7
- package/dist/core/tool-result-store.js +2 -1
- package/dist/core/tool-roster.d.ts +47 -18
- package/dist/core/tool-roster.js +53 -32
- package/dist/core/tool-spec-members.js +1 -1
- package/dist/core/tool-spec.d.ts +35 -21
- package/dist/core/tools.js +3 -2
- package/dist/core/trace.d.ts +4 -0
- package/dist/core/untrusted-egress.d.ts +1 -1
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/core/warm-resume.d.ts +1 -1
- package/dist/core/wiring-manifest.d.ts +49 -0
- package/dist/core/wiring-manifest.js +13 -5
- package/dist/core/workflow-journal-store.d.ts +62 -0
- package/dist/core/workflow-journal-store.js +77 -1
- package/dist/core/workflow-run-store.d.ts +6 -0
- package/dist/core/workflow-run-store.js +2 -0
- package/dist/engine/compaction/compaction.d.ts +9 -9
- package/dist/engine/execution-env/node-execution-env.d.ts +19 -5
- package/dist/engine/execution-env/node-execution-env.js +43 -22
- package/dist/engine/harness/agent-harness.d.ts +14 -8
- package/dist/engine/harness/agent-harness.js +33 -26
- package/dist/engine/harness/types.d.ts +29 -7
- package/dist/engine/harness/types.js +12 -1
- package/dist/engine/llm/types.d.ts +9 -9
- package/dist/engine/llm/validation.d.ts +4 -1
- package/dist/engine/llm/validation.js +13 -1
- package/dist/engine/loop/agent-loop.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +10 -8
- package/dist/engine/loop/types.d.ts +15 -13
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
- package/dist/engine/session/memory-repo.d.ts +10 -11
- package/dist/engine/session/memory-repo.js +13 -8
- package/dist/engine/session/parent-chain-guard.d.ts +3 -2
- package/dist/engine/session/parent-chain-guard.js +1 -1
- package/dist/engine/session/repo-utils.d.ts +31 -3
- package/dist/engine/session/repo-utils.js +42 -5
- package/dist/index.d.ts +27 -15
- package/dist/index.js +21 -11
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/internal/harness.d.ts +3 -3
- package/dist/internal/harness.js +2 -2
- package/dist/internal/llm.d.ts +1 -1
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/goal.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +2 -2
- package/dist/orchestration/run-workflow-tool.js +5 -3
- package/dist/orchestration/workflow-size-guideline.d.ts +4 -4
- package/dist/orchestration/workflow-types.d.ts +18 -0
- package/dist/orchestration/workflow-types.js +6 -0
- package/dist/orchestration/workflow.d.ts +2 -13
- package/dist/orchestration/workflow.js +21 -31
- package/dist/prompt-assembly/event-registry.js +1 -0
- package/dist/prompt-assembly/tool-catalog.d.ts +3 -0
- package/dist/prompt-assembly/tool-catalog.js +3 -0
- package/dist/prompts/default.d.ts +14 -14
- package/dist/scenarios/full-body.d.ts +4 -4
- package/dist/stores/file/adoption/adopt.d.ts +4 -3
- package/dist/stores/file/adoption/adopt.js +24 -31
- package/dist/stores/file/adoption/marker.d.ts +6 -13
- package/dist/stores/file/adoption/marker.js +6 -15
- package/dist/stores/file/checkpoint-store.d.ts +3 -0
- package/dist/stores/file/checkpoint-store.js +9 -3
- package/dist/stores/file/file-history-store.d.ts +20 -19
- package/dist/stores/file/file-history-store.js +152 -135
- package/dist/stores/file/fs-atomic.d.ts +15 -0
- package/dist/stores/file/fs-atomic.js +89 -14
- package/dist/stores/file/index.d.ts +1 -1
- package/dist/stores/file/mailbox-store.d.ts +3 -0
- package/dist/stores/file/mailbox-store.js +3 -1
- package/dist/stores/file/permission-rule-adopt.d.ts +1 -1
- package/dist/stores/file/permission-rule-adopt.js +15 -16
- package/dist/stores/file/permission-rule-store.d.ts +18 -2
- package/dist/stores/file/permission-rule-store.js +21 -14
- package/dist/stores/file/session-policy-store.d.ts +3 -3
- package/dist/stores/file/session-store.d.ts +26 -11
- package/dist/stores/file/session-store.js +77 -40
- package/dist/stores/file/workflow-journal-store.d.ts +10 -4
- package/dist/stores/file/workflow-journal-store.js +28 -13
- package/dist/tools/artifact/artifact-text.d.ts +1 -1
- package/dist/tools/artifact/artifact-tool.js +2 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +3 -3
- package/dist/tools/fs/fs-bash.d.ts +2 -2
- package/dist/tools/fs/fs-bash.js +3 -1
- package/dist/tools/fs/fs-shared.d.ts +8 -8
- package/dist/tools/fs/gh-rate-limit.d.ts +1 -1
- package/dist/tools/fs/notebook.d.ts +2 -2
- package/dist/tools/fs/read-deny.d.ts +29 -7
- package/dist/tools/fs/read-deny.js +63 -22
- package/dist/tools/fs/removal-targets.d.ts +1 -1
- package/dist/tools/fs/safety.d.ts +9 -9
- package/dist/tools/fs/search.d.ts +6 -6
- package/dist/tools/fs/shell-read-check.js +6 -1
- package/dist/tools/fs/shell-read-judge.d.ts +4 -0
- package/dist/tools/fs/shell-read-judge.js +5 -2
- package/dist/tools/loop-tick.d.ts +2 -2
- package/dist/tools/scheduler-tools.js +5 -1
- package/dist/tools/task-list.d.ts +7 -7
- package/dist/tools/task-list.js +5 -1
- package/dist/tools/todo.js +2 -1
- package/dist/tools/web.d.ts +1 -1
- package/dist/tools/web.js +40 -37
- package/dist/tools/worktree.d.ts +3 -3
- package/dist/tools/worktree.js +3 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +5756 -5638
- package/wire-changes/core@7.55.0.json +3318 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +0 -39
- package/dist/core/store-contracts/session-store-incarnation-contract.js +0 -95
|
@@ -2,7 +2,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
2
2
|
import { appendFileSync, closeSync, existsSync, fsyncSync, openSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { thrownText } from "../../core/thrown-value.js";
|
|
5
|
-
import { applyFileRestoreViaEnv, boundaryPublishVerdict, worldStillMatchesMint, captureFileBackupViaEnv, modeProvenDifferent, previewFileDelta, describeTrackKey, fileHistoryExportDigest, resolveTrackKey, trackKeyOf, validateFileHistoryExport, } from "../../core/file-history-store.js";
|
|
5
|
+
import { applyFileRestoreViaEnv, boundaryPublishVerdict, worldStillMatchesMint, captureFileBackupViaEnv, modeProvenDifferent, previewFileDelta, describeTrackKey, fileHistoryExportDigest, caughtFault, DAMAGED_RECORD, resolveTrackKey, trackKeyOf, validateFileHistoryExport, } from "../../core/file-history-store.js";
|
|
6
6
|
import { fileHistoryBoundariesToKeep, resolveFileHistoryRetention } from "../../core/file-history-retention.js";
|
|
7
7
|
import { atomicWriteFile, ensureDir, sanitizePathComponent, sanitizeScope, writeThenLink } from "./fs-atomic.js";
|
|
8
8
|
import { guardCorruptSink } from "./corrupt-read-sink.js";
|
|
@@ -109,6 +109,12 @@ function nestedMapSet(map, scope, key, value) {
|
|
|
109
109
|
map.set(scope, (inner = new Map()));
|
|
110
110
|
inner.set(key, value);
|
|
111
111
|
}
|
|
112
|
+
function worstFault(faults) {
|
|
113
|
+
const terminal = faults.find((f) => f.permanence === "terminal");
|
|
114
|
+
if (terminal !== undefined)
|
|
115
|
+
return terminal;
|
|
116
|
+
return faults.length > 0 && faults.every((f) => f.permanence === "transient") ? faults[0] : {};
|
|
117
|
+
}
|
|
112
118
|
function safeRm(path) {
|
|
113
119
|
try {
|
|
114
120
|
rmSync(path, { force: true, recursive: true });
|
|
@@ -227,21 +233,21 @@ export class FileFileHistoryStore {
|
|
|
227
233
|
if (err.code === "ENOENT")
|
|
228
234
|
return { corrupt: false, tracked: new Map() };
|
|
229
235
|
this.disclose(path, `tracked set unreadable (${err.code ?? "unknown"}) — this scope's history is UNUSABLE until it is repaired`);
|
|
230
|
-
return { corrupt: true, tracked: new Map() };
|
|
236
|
+
return { corrupt: true, tracked: new Map(), fault: caughtFault(err) };
|
|
231
237
|
}
|
|
232
238
|
const parsed = parseJsonRecord(raw);
|
|
233
239
|
if (parsed === null) {
|
|
234
240
|
this.disclose(path, "unparseable tracked set (torn/zero-byte write?) — this scope's history is UNUSABLE until it is repaired");
|
|
235
|
-
return { corrupt: true, tracked: new Map() };
|
|
241
|
+
return { corrupt: true, tracked: new Map(), fault: DAMAGED_RECORD };
|
|
236
242
|
}
|
|
237
243
|
const rows = trackedRowsOf(parsed);
|
|
238
244
|
if (rows === null) {
|
|
239
245
|
this.disclose(path, "tracked set is not a pair array of [key, record] rows — this scope's history is UNUSABLE until it is repaired");
|
|
240
|
-
return { corrupt: true, tracked: new Map() };
|
|
246
|
+
return { corrupt: true, tracked: new Map(), fault: DAMAGED_RECORD };
|
|
241
247
|
}
|
|
242
248
|
if (parsed.digest !== undefined && (typeof parsed.digest !== "string" || parsed.digest !== trackedDigest(rows))) {
|
|
243
249
|
this.disclose(path, "tracked set fails its own integrity digest (edited in place, or written by something that is not this store) — this scope's history is UNUSABLE until it is repaired");
|
|
244
|
-
return { corrupt: true, tracked: new Map() };
|
|
250
|
+
return { corrupt: true, tracked: new Map(), fault: DAMAGED_RECORD };
|
|
245
251
|
}
|
|
246
252
|
return { corrupt: false, tracked: new Map(rows) };
|
|
247
253
|
}
|
|
@@ -256,6 +262,10 @@ export class FileFileHistoryStore {
|
|
|
256
262
|
atomicWriteFile(join(this.base, "tmp"), this.trackedPath(scope), JSON.stringify(doc));
|
|
257
263
|
}
|
|
258
264
|
readBoundary(path) {
|
|
265
|
+
const read = this.readBoundaryState(path);
|
|
266
|
+
return read instanceof Map ? read : null;
|
|
267
|
+
}
|
|
268
|
+
readBoundaryState(path) {
|
|
259
269
|
let raw;
|
|
260
270
|
try {
|
|
261
271
|
raw = readFileSync(path, "utf8");
|
|
@@ -263,33 +273,33 @@ export class FileFileHistoryStore {
|
|
|
263
273
|
catch (err) {
|
|
264
274
|
if (err.code === "ENOENT")
|
|
265
275
|
return null;
|
|
266
|
-
this.disclose(path, `boundary unreadable (${err.code ?? "unknown"}) —
|
|
267
|
-
return
|
|
276
|
+
this.disclose(path, `boundary unreadable (${err.code ?? "unknown"}) — not a usable boundary`);
|
|
277
|
+
return caughtFault(err);
|
|
268
278
|
}
|
|
269
279
|
{
|
|
270
280
|
const doc = parseJsonRecord(raw);
|
|
271
281
|
if (doc === null) {
|
|
272
|
-
this.disclose(path, "unparseable boundary (torn/zero-byte write?) —
|
|
273
|
-
return
|
|
282
|
+
this.disclose(path, "unparseable boundary (torn/zero-byte write?) — not a usable boundary");
|
|
283
|
+
return DAMAGED_RECORD;
|
|
274
284
|
}
|
|
275
285
|
if (doc.formatVersion !== "file-history/1" || !Array.isArray(doc.files)) {
|
|
276
|
-
this.disclose(path, "boundary is not a file-history/1 pair array —
|
|
277
|
-
return
|
|
286
|
+
this.disclose(path, "boundary is not a file-history/1 pair array — not a usable boundary");
|
|
287
|
+
return DAMAGED_RECORD;
|
|
278
288
|
}
|
|
279
289
|
const parsed = { formatVersion: doc.formatVersion, files: doc.files, digest: doc.digest };
|
|
280
290
|
for (const pair of parsed.files) {
|
|
281
291
|
if (!Array.isArray(pair) || pair.length !== 2 || typeof pair[0] !== "string" || !Number.isInteger(pair[1]) || pair[1] < 1) {
|
|
282
|
-
this.disclose(path, "boundary carries a malformed mapping row —
|
|
283
|
-
return
|
|
292
|
+
this.disclose(path, "boundary carries a malformed mapping row — not a usable boundary");
|
|
293
|
+
return DAMAGED_RECORD;
|
|
284
294
|
}
|
|
285
295
|
}
|
|
286
296
|
if (new Set(parsed.files.map(([k]) => k)).size !== parsed.files.length) {
|
|
287
|
-
this.disclose(path, "boundary maps the same path twice —
|
|
288
|
-
return
|
|
297
|
+
this.disclose(path, "boundary maps the same path twice — not a usable boundary");
|
|
298
|
+
return DAMAGED_RECORD;
|
|
289
299
|
}
|
|
290
300
|
if (typeof parsed.digest !== "string" || parsed.digest !== boundaryDigest(parsed.files)) {
|
|
291
|
-
this.disclose(path, "boundary fails its own integrity digest (rows added or dropped?) —
|
|
292
|
-
return
|
|
301
|
+
this.disclose(path, "boundary fails its own integrity digest (rows added or dropped?) — not a usable boundary");
|
|
302
|
+
return DAMAGED_RECORD;
|
|
293
303
|
}
|
|
294
304
|
return new Map(parsed.files);
|
|
295
305
|
}
|
|
@@ -304,23 +314,11 @@ export class FileFileHistoryStore {
|
|
|
304
314
|
return undefined;
|
|
305
315
|
}
|
|
306
316
|
}
|
|
307
|
-
unjudgeableBoundaryIdsOf(scope) {
|
|
308
|
-
const dir = this.boundaryDir(scope);
|
|
309
|
-
let names;
|
|
310
|
-
try {
|
|
311
|
-
names = readdirSync(dir);
|
|
312
|
-
}
|
|
313
|
-
catch (err) {
|
|
314
|
-
return err.code === "ENOENT" ? null : [];
|
|
315
|
-
}
|
|
316
|
-
const bad = names.filter((f) => f.endsWith(".json") && this.readBoundary(join(dir, f)) === null).map((f) => f.slice(0, -".json".length));
|
|
317
|
-
return bad.length > 0 ? bad : null;
|
|
318
|
-
}
|
|
319
317
|
carryConsumedIds(fromScope, toScope) {
|
|
320
318
|
const spent = this.readReapedIds(fromScope);
|
|
321
|
-
if (spent
|
|
319
|
+
if (!(spent instanceof Set)) {
|
|
322
320
|
this.disclose(this.reapedLogPath(fromScope), "source consumed-id ledger unreadable — the adoption is refused rather than published without its lineage's tombstones");
|
|
323
|
-
return
|
|
321
|
+
return spent;
|
|
324
322
|
}
|
|
325
323
|
if (spent.size === 0)
|
|
326
324
|
return true;
|
|
@@ -339,7 +337,7 @@ export class FileFileHistoryStore {
|
|
|
339
337
|
if (err.code === "ENOENT")
|
|
340
338
|
return new Set();
|
|
341
339
|
this.disclose(path, `consumed-id ledger unreadable (${err.code ?? "unknown"})`);
|
|
342
|
-
return
|
|
340
|
+
return caughtFault(err);
|
|
343
341
|
}
|
|
344
342
|
const cached = this.reapedCache.get(scope);
|
|
345
343
|
if (cached !== undefined && cached.size === size)
|
|
@@ -354,7 +352,7 @@ export class FileFileHistoryStore {
|
|
|
354
352
|
}
|
|
355
353
|
catch (err) {
|
|
356
354
|
this.disclose(path, `consumed-id ledger unreadable (${err.code ?? "unknown"})`);
|
|
357
|
-
return
|
|
355
|
+
return caughtFault(err);
|
|
358
356
|
}
|
|
359
357
|
}
|
|
360
358
|
markReaped(scope, ids) {
|
|
@@ -381,7 +379,7 @@ export class FileFileHistoryStore {
|
|
|
381
379
|
}
|
|
382
380
|
catch (err) {
|
|
383
381
|
this.disclose(path, `consumed-id ledger could not be appended (${err.code ?? "unknown"}) — the boundaries it names are NOT being reaped, so this scope is not bounded until it can be written`);
|
|
384
|
-
return
|
|
382
|
+
return caughtFault(err);
|
|
385
383
|
}
|
|
386
384
|
}
|
|
387
385
|
boundaryOrderOf(path) {
|
|
@@ -409,7 +407,7 @@ export class FileFileHistoryStore {
|
|
|
409
407
|
const { key, kind, family } = describeTrackKey(root, absPath);
|
|
410
408
|
const before = this.readTrackedState(scope);
|
|
411
409
|
if (before.corrupt)
|
|
412
|
-
return { ok: false, error: { code: "track_failed", message: `file history for scope "${scope}" is unreadable — refusing to mint a first-touch record over an unusable graph
|
|
410
|
+
return { ok: false, error: { code: "track_failed", message: `file history for scope "${scope}" is unreadable — refusing to mint a first-touch record over an unusable graph`, ...before.fault } };
|
|
413
411
|
const existing = before.tracked.get(key);
|
|
414
412
|
if (existing !== undefined) {
|
|
415
413
|
if (existing.observedByLaterTouch !== true) {
|
|
@@ -421,7 +419,7 @@ export class FileFileHistoryStore {
|
|
|
421
419
|
const cap = await captureFileBackupViaEnv(env, absPath, root, signal);
|
|
422
420
|
const after = this.readTrackedState(scope);
|
|
423
421
|
if (after.corrupt)
|
|
424
|
-
return { ok: false, error: { code: "track_failed", message: `file history for scope "${scope}" became unreadable during the first-touch capture — nothing was recorded
|
|
422
|
+
return { ok: false, error: { code: "track_failed", message: `file history for scope "${scope}" became unreadable during the first-touch capture — nothing was recorded`, ...after.fault } };
|
|
425
423
|
const tracked = after.tracked;
|
|
426
424
|
const raced = tracked.get(key);
|
|
427
425
|
if (raced !== undefined) {
|
|
@@ -452,7 +450,7 @@ export class FileFileHistoryStore {
|
|
|
452
450
|
return { ok: true, minted: true };
|
|
453
451
|
}
|
|
454
452
|
catch (err) {
|
|
455
|
-
return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} could not be persisted: ${thrownText(err)}
|
|
453
|
+
return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} could not be persisted: ${thrownText(err)}`, ...caughtFault(err) } };
|
|
456
454
|
}
|
|
457
455
|
}
|
|
458
456
|
async annulTrack(scope, absPath, root, verify) {
|
|
@@ -460,13 +458,16 @@ export class FileFileHistoryStore {
|
|
|
460
458
|
const key = trackKeyOf(root, absPath);
|
|
461
459
|
const state = this.readTrackedState(scope);
|
|
462
460
|
if (state.corrupt) {
|
|
463
|
-
return { ok: false, error: { code: "
|
|
461
|
+
return { ok: false, error: { code: "track_failed", message: `file history for scope "${scope}" is unreadable — the first-touch record (if any) was left in place; repair the graph before retraction can be judged`, ...state.fault } };
|
|
464
462
|
}
|
|
465
463
|
const tracked = state.tracked;
|
|
466
464
|
const rec = tracked.get(key);
|
|
467
465
|
if (rec === undefined)
|
|
468
466
|
return { ok: true };
|
|
469
|
-
|
|
467
|
+
const listed = this.listBoundaryIds(scope);
|
|
468
|
+
if (!Array.isArray(listed))
|
|
469
|
+
return { ok: false, error: { code: "track_failed", message: `scope "${scope}" has ${listed.what} — the first-touch record for ${absPath} was left in place, since whether a boundary references it cannot be judged`, ...listed.fault } };
|
|
470
|
+
for (const entryId of listed) {
|
|
470
471
|
const bp = this.tryBoundaryPath(scope, entryId);
|
|
471
472
|
const mapping = bp === null ? null : this.readBoundary(bp);
|
|
472
473
|
if (mapping?.has(key) === true) {
|
|
@@ -485,7 +486,7 @@ export class FileFileHistoryStore {
|
|
|
485
486
|
}
|
|
486
487
|
const fresh = this.readTrackedState(scope);
|
|
487
488
|
if (fresh.corrupt)
|
|
488
|
-
return { ok: false, error: { code: "
|
|
489
|
+
return { ok: false, error: { code: "track_failed", message: `file history for scope "${scope}" became unreadable — the record was left in place`, ...fresh.fault } };
|
|
489
490
|
const still = fresh.tracked.get(key);
|
|
490
491
|
if (still === undefined)
|
|
491
492
|
return { ok: true };
|
|
@@ -495,7 +496,10 @@ export class FileFileHistoryStore {
|
|
|
495
496
|
if (still.mintId !== rec.mintId || still.baseAvailable !== rec.baseAvailable || still.versions.length !== rec.versions.length || still.versions[0]?.blobHash !== rec.versions[0]?.blobHash || still.versions[0]?.backupTime !== rec.versions[0]?.backupTime) {
|
|
496
497
|
return { ok: false, error: { code: "conflict", message: `first-touch record for ${absPath} was replaced while this retraction was being proven — the record now there belongs to a newer edit` } };
|
|
497
498
|
}
|
|
498
|
-
|
|
499
|
+
const relisted = this.listBoundaryIds(scope);
|
|
500
|
+
if (!Array.isArray(relisted))
|
|
501
|
+
return { ok: false, error: { code: "track_failed", message: `scope "${scope}" came to have ${relisted.what} while this retraction was being proven — the record was left in place`, ...relisted.fault } };
|
|
502
|
+
for (const entryId of relisted) {
|
|
499
503
|
const bp2 = this.tryBoundaryPath(scope, entryId);
|
|
500
504
|
const mapping2 = bp2 === null ? null : this.readBoundary(bp2);
|
|
501
505
|
if (mapping2?.has(key) === true) {
|
|
@@ -507,53 +511,65 @@ export class FileFileHistoryStore {
|
|
|
507
511
|
return { ok: true };
|
|
508
512
|
}
|
|
509
513
|
catch (err) {
|
|
510
|
-
return { ok: false, error: { code: "track_failed", message: `annulling the first-touch record for ${absPath} failed: ${thrownText(err)}
|
|
514
|
+
return { ok: false, error: { code: "track_failed", message: `annulling the first-touch record for ${absPath} failed: ${thrownText(err)}`, ...caughtFault(err) } };
|
|
511
515
|
}
|
|
512
516
|
}
|
|
513
517
|
scopeIsEmptySync(scope) {
|
|
514
518
|
const state = this.readTrackedState(scope);
|
|
515
519
|
if (state.corrupt)
|
|
516
|
-
return
|
|
517
|
-
|
|
520
|
+
return { fault: state.fault, what: "an unreadable tracked set" };
|
|
521
|
+
const listed = this.listBoundaryIds(scope);
|
|
522
|
+
if (!Array.isArray(listed))
|
|
523
|
+
return listed;
|
|
524
|
+
return state.tracked.size === 0 && listed.length === 0 ? "empty" : "not_empty";
|
|
525
|
+
}
|
|
526
|
+
async importTargetOf(scope, data, when) {
|
|
527
|
+
const emptiness = this.scopeIsEmptySync(scope);
|
|
528
|
+
if (emptiness === "empty")
|
|
529
|
+
return "empty";
|
|
530
|
+
if (emptiness !== "not_empty") {
|
|
531
|
+
return { ok: false, error: { code: "adopt_failed", message: `importHistory target "${scope}" ${when === "before" ? "has" : "came to have"} ${emptiness.what} — a scope whose history cannot be judged is not an empty target; nothing was ${when === "before" ? "fetched or " : ""}published`, ...emptiness.fault } };
|
|
532
|
+
}
|
|
533
|
+
const existing = await this.exportHistory(scope);
|
|
534
|
+
if (existing.ok && existing.history !== null && fileHistoryExportDigest(existing.history) === fileHistoryExportDigest(data))
|
|
535
|
+
return "identical";
|
|
536
|
+
const message = when === "before"
|
|
537
|
+
? `importHistory target "${scope}" was judged not empty and not this envelope — it holds a DIFFERENT history, or it changed (or could not be read) between the two reads that judge it — a first import requires an empty target and a retry is legal only for the identical envelope (atomic visibility prevents half-graphs; this law prevents publishing the wrong complete graph over locally-grown history)`
|
|
538
|
+
: `importHistory target "${scope}" changed while this import was fetching blobs — it gained a DIFFERENT history, or changed (or could not be read) between the two reads that judge it — nothing was published`;
|
|
539
|
+
return { ok: false, error: { code: "conflict", message } };
|
|
518
540
|
}
|
|
519
541
|
publishStagedScopeSync(scope, staging) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
try {
|
|
526
|
-
return readdirSync(this.boundaryDir(scope));
|
|
527
|
-
}
|
|
528
|
-
catch {
|
|
529
|
-
return [];
|
|
530
|
-
}
|
|
531
|
-
})()) {
|
|
532
|
-
if (f.endsWith(".json"))
|
|
533
|
-
destroyed.push(f.slice(0, -".json".length));
|
|
534
|
-
}
|
|
535
|
-
if (!this.markReaped(scope, destroyed))
|
|
536
|
-
return "ledger_failed";
|
|
537
|
-
safeRm(target);
|
|
538
|
-
renameSync(staging, target);
|
|
542
|
+
const emptiness = this.scopeIsEmptySync(scope);
|
|
543
|
+
if (emptiness !== "empty")
|
|
544
|
+
return emptiness;
|
|
545
|
+
safeRm(this.scopeDir(scope));
|
|
546
|
+
renameSync(staging, this.scopeDir(scope));
|
|
539
547
|
return "published";
|
|
540
548
|
}
|
|
541
549
|
listBoundaryIds(scope) {
|
|
542
550
|
const dir = this.boundaryDir(scope);
|
|
543
|
-
let
|
|
551
|
+
let names;
|
|
544
552
|
try {
|
|
545
|
-
|
|
553
|
+
names = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
546
554
|
}
|
|
547
555
|
catch (err) {
|
|
548
|
-
if (err.code
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
return
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
556
|
+
if (err.code === "ENOENT")
|
|
557
|
+
return [];
|
|
558
|
+
this.disclose(dir, `boundary directory unreadable (${err.code ?? "unknown"}) — this scope's history cannot be judged until it can be listed`);
|
|
559
|
+
return { fault: caughtFault(err), what: "a boundary directory that cannot be listed" };
|
|
560
|
+
}
|
|
561
|
+
const ids = [];
|
|
562
|
+
const unusable = [];
|
|
563
|
+
const faults = [];
|
|
564
|
+
for (const f of names) {
|
|
565
|
+
const read = this.readBoundaryState(join(dir, f));
|
|
566
|
+
(read instanceof Map ? ids : unusable).push(f.slice(0, -".json".length));
|
|
567
|
+
if (!(read instanceof Map))
|
|
568
|
+
faults.push(read ?? {});
|
|
569
|
+
}
|
|
570
|
+
if (unusable.length === 0)
|
|
571
|
+
return ids;
|
|
572
|
+
return { fault: worstFault(faults), what: `boundaries that cannot be used (${unusable.slice(0, 3).map((i) => JSON.stringify(i)).join(", ")}${unusable.length > 3 ? ", …" : ""})` };
|
|
557
573
|
}
|
|
558
574
|
async snapshot(scope, entryId, env, root, signal) {
|
|
559
575
|
const bpath = this.tryBoundaryPath(scope, entryId);
|
|
@@ -562,8 +578,8 @@ export class FileFileHistoryStore {
|
|
|
562
578
|
if (this.readBoundary(bpath) !== null)
|
|
563
579
|
return { ok: true };
|
|
564
580
|
const consumed = this.readReapedIds(scope);
|
|
565
|
-
if (consumed
|
|
566
|
-
return { ok: false, error: { code: "snapshot_failed", message: `the consumed-entry ledger for scope "${scope}" cannot be read — refusing to mint a boundary while it is impossible to tell a fresh entry id from one retention already reaped
|
|
581
|
+
if (!(consumed instanceof Set)) {
|
|
582
|
+
return { ok: false, error: { code: "snapshot_failed", message: `the consumed-entry ledger for scope "${scope}" cannot be read — refusing to mint a boundary while it is impossible to tell a fresh entry id from one retention already reaped`, ...consumed } };
|
|
567
583
|
}
|
|
568
584
|
if (consumed.has(entryId)) {
|
|
569
585
|
return { ok: false, error: { code: "snapshot_failed", message: `entry "${entryId}" already had a boundary and it was reaped by retention — a fresh capture now would record post-turn bytes under a pre-turn key, so this entry has no boundary and cannot get one back` } };
|
|
@@ -582,7 +598,7 @@ export class FileFileHistoryStore {
|
|
|
582
598
|
const state = this.readTrackedState(scope);
|
|
583
599
|
if (state.corrupt) {
|
|
584
600
|
nestedSetAdd(this.spentAttempts, scope, entryId);
|
|
585
|
-
return { ok: false, error: { code: "snapshot_failed", message: `file history for scope "${scope}" is unreadable — a boundary minted over an unusable graph would claim to restore state that is not there
|
|
601
|
+
return { ok: false, error: { code: "snapshot_failed", message: `file history for scope "${scope}" is unreadable — a boundary minted over an unusable graph would claim to restore state that is not there`, ...state.fault } };
|
|
586
602
|
}
|
|
587
603
|
const tracked = state.tracked;
|
|
588
604
|
const staged = new Map();
|
|
@@ -628,7 +644,7 @@ export class FileFileHistoryStore {
|
|
|
628
644
|
const currentState = this.readTrackedState(scope);
|
|
629
645
|
if (currentState.corrupt) {
|
|
630
646
|
nestedSetAdd(this.spentAttempts, scope, entryId);
|
|
631
|
-
return { ok: false, error: { code: "snapshot_failed", message: `file history for scope "${scope}" became unreadable during the capture — no boundary was published
|
|
647
|
+
return { ok: false, error: { code: "snapshot_failed", message: `file history for scope "${scope}" became unreadable during the capture — no boundary was published`, ...currentState.fault } };
|
|
632
648
|
}
|
|
633
649
|
const current = currentState.tracked;
|
|
634
650
|
for (const [key, cap] of staged) {
|
|
@@ -663,9 +679,10 @@ export class FileFileHistoryStore {
|
|
|
663
679
|
catch (err) {
|
|
664
680
|
if (err.code !== "EEXIST")
|
|
665
681
|
throw err;
|
|
666
|
-
|
|
682
|
+
const existing = this.readBoundaryState(bpath);
|
|
683
|
+
if (!(existing instanceof Map)) {
|
|
667
684
|
nestedSetAdd(this.spentAttempts, scope, entryId);
|
|
668
|
-
return { ok: false, error: { code: "snapshot_failed", message: `a boundary file for entry "${entryId}" already exists but cannot be read — this turn has no usable boundary (the unreadable file is left in place for an operator rather than silently replaced)
|
|
685
|
+
return { ok: false, error: { code: "snapshot_failed", message: `a boundary file for entry "${entryId}" already exists but cannot be read — this turn has no usable boundary (the unreadable file is left in place for an operator rather than silently replaced)`, ...existing } };
|
|
669
686
|
}
|
|
670
687
|
}
|
|
671
688
|
this.applyBoundaryRetention(scope, entryId);
|
|
@@ -673,7 +690,7 @@ export class FileFileHistoryStore {
|
|
|
673
690
|
}
|
|
674
691
|
catch (err) {
|
|
675
692
|
nestedSetAdd(this.spentAttempts, scope, entryId);
|
|
676
|
-
return { ok: false, error: { code: "snapshot_failed", message: `boundary for entry "${entryId}" could not be committed: ${thrownText(err)}
|
|
693
|
+
return { ok: false, error: { code: "snapshot_failed", message: `boundary for entry "${entryId}" could not be committed: ${thrownText(err)}`, ...caughtFault(err) } };
|
|
677
694
|
}
|
|
678
695
|
finally {
|
|
679
696
|
this.activeCaptures.get(scope)?.delete(entryId);
|
|
@@ -685,13 +702,16 @@ export class FileFileHistoryStore {
|
|
|
685
702
|
let modeNotPreserved = 0;
|
|
686
703
|
try {
|
|
687
704
|
const bpath = this.tryBoundaryPath(scope, entryId);
|
|
688
|
-
const boundary = bpath === null ? null : this.
|
|
705
|
+
const boundary = bpath === null ? null : this.readBoundaryState(bpath);
|
|
689
706
|
if (boundary === null) {
|
|
690
707
|
return { outcome: "failed", ...ledger, error: { code: "not_found", message: `no history boundary for entry "${entryId}"` } };
|
|
691
708
|
}
|
|
709
|
+
if (!(boundary instanceof Map)) {
|
|
710
|
+
return { outcome: "failed", ...ledger, error: { code: "restore_failed", message: `the history boundary for entry "${entryId}" is there but cannot be used — NOTHING was restored`, ...boundary } };
|
|
711
|
+
}
|
|
692
712
|
const state = this.readTrackedState(scope);
|
|
693
713
|
if (state.corrupt) {
|
|
694
|
-
return { outcome: "failed", ...ledger, error: { code: "restore_failed", message: `file history for scope "${scope}" is unreadable — the boundary exists but its version records cannot be read, so NOTHING was restored
|
|
714
|
+
return { outcome: "failed", ...ledger, error: { code: "restore_failed", message: `file history for scope "${scope}" is unreadable — the boundary exists but its version records cannot be read, so NOTHING was restored`, ...state.fault } };
|
|
695
715
|
}
|
|
696
716
|
const tracked = state.tracked;
|
|
697
717
|
for (const key of [...tracked.keys()].sort()) {
|
|
@@ -767,7 +787,7 @@ export class FileFileHistoryStore {
|
|
|
767
787
|
}
|
|
768
788
|
catch (err) {
|
|
769
789
|
const touched = ledger.filesChanged.length > 0 || ledger.identicalSkipped.length > 0 || ledger.refused.length > 0 || ledger.failed.length > 0;
|
|
770
|
-
return { outcome: touched ? "partial" : "failed", ...ledger, error: { code: "restore_failed", message: thrownText(err) } };
|
|
790
|
+
return { outcome: touched ? "partial" : "failed", ...ledger, error: { code: "restore_failed", message: thrownText(err), ...caughtFault(err) } };
|
|
771
791
|
}
|
|
772
792
|
}
|
|
773
793
|
async canRestore(scope, entryId) {
|
|
@@ -854,7 +874,7 @@ export class FileFileHistoryStore {
|
|
|
854
874
|
const id = file.slice(0, -".json".length);
|
|
855
875
|
if (keep.has(id))
|
|
856
876
|
continue;
|
|
857
|
-
if (
|
|
877
|
+
if (this.markReaped(scope, [id]) !== true) {
|
|
858
878
|
survivors.add(id);
|
|
859
879
|
continue;
|
|
860
880
|
}
|
|
@@ -1006,28 +1026,25 @@ export class FileFileHistoryStore {
|
|
|
1006
1026
|
try {
|
|
1007
1027
|
if (fromScope === toScope)
|
|
1008
1028
|
return { ok: true };
|
|
1009
|
-
const
|
|
1010
|
-
if (
|
|
1011
|
-
return { ok: false, error: { code: "adopt_failed", message: `adoptScope target "${toScope}" has an unreadable tracked set — a scope whose bytes cannot be judged is not an empty target` } };
|
|
1012
|
-
}
|
|
1013
|
-
const targetTracked = targetState.tracked;
|
|
1014
|
-
const targetBoundaries = this.listBoundaryIds(toScope);
|
|
1015
|
-
if (targetTracked.size > 0 || targetBoundaries.length > 0) {
|
|
1029
|
+
const target = this.scopeIsEmptySync(toScope);
|
|
1030
|
+
if (target === "not_empty") {
|
|
1016
1031
|
return { ok: false, error: { code: "conflict", message: `adoptScope target "${toScope}" already has history of its own — adoption is legal only onto an EMPTY scope (the fork/resume boundary before the new session's first turn)` } };
|
|
1017
1032
|
}
|
|
1033
|
+
if (target !== "empty")
|
|
1034
|
+
return { ok: false, error: { code: "adopt_failed", message: `adoptScope target "${toScope}" has ${target.what} — a scope whose history cannot be judged is not an empty target; nothing was published`, ...target.fault } };
|
|
1018
1035
|
const sourceState = this.readTrackedState(fromScope);
|
|
1019
1036
|
if (sourceState.corrupt) {
|
|
1020
|
-
return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" has an unreadable tracked set — adopting it would publish boundaries with no version records behind them
|
|
1037
|
+
return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" has an unreadable tracked set — adopting it would publish boundaries with no version records behind them`, ...sourceState.fault } };
|
|
1021
1038
|
}
|
|
1022
1039
|
const source = sourceState.tracked;
|
|
1023
|
-
const unjudgeable = this.unjudgeableBoundaryIdsOf(fromScope);
|
|
1024
|
-
if (unjudgeable !== null) {
|
|
1025
|
-
return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" has ${unjudgeable.length > 0 ? `boundaries that cannot be judged (${unjudgeable.slice(0, 3).map((i) => JSON.stringify(i)).join(", ")}${unjudgeable.length > 3 ? ", …" : ""})` : "a boundary directory that cannot be listed"} — adopting it would publish a graph that silently drops those turns and hands their entry ids back to the fork; nothing was published` } };
|
|
1026
|
-
}
|
|
1027
1040
|
const sourceBoundaries = this.listBoundaryIds(fromScope);
|
|
1041
|
+
if (!Array.isArray(sourceBoundaries)) {
|
|
1042
|
+
return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" has ${sourceBoundaries.what} — adopting it would publish a graph that silently drops those turns and hands their entry ids back to the fork; nothing was published`, ...sourceBoundaries.fault } };
|
|
1043
|
+
}
|
|
1028
1044
|
if (source.size === 0 && sourceBoundaries.length === 0) {
|
|
1029
|
-
|
|
1030
|
-
|
|
1045
|
+
const carried = this.carryConsumedIds(fromScope, toScope);
|
|
1046
|
+
if (carried !== true) {
|
|
1047
|
+
return { ok: false, error: { code: "adopt_failed", message: `adoptScope could not carry the consumed-entry ledger from "${fromScope}" to "${toScope}" — refusing rather than reporting a carry that would let an already-consumed entry id be minted again in the fork`, ...carried } };
|
|
1031
1048
|
}
|
|
1032
1049
|
return { ok: true };
|
|
1033
1050
|
}
|
|
@@ -1035,15 +1052,16 @@ export class FileFileHistoryStore {
|
|
|
1035
1052
|
for (const v of rec.versions) {
|
|
1036
1053
|
if (v.blobHash === null)
|
|
1037
1054
|
continue;
|
|
1038
|
-
let
|
|
1055
|
+
let fault;
|
|
1039
1056
|
try {
|
|
1040
|
-
|
|
1057
|
+
if (createHash("sha256").update(readFileSync(this.blobPath(v.blobHash))).digest("hex") !== v.blobHash)
|
|
1058
|
+
fault = DAMAGED_RECORD;
|
|
1041
1059
|
}
|
|
1042
|
-
catch {
|
|
1043
|
-
|
|
1060
|
+
catch (err) {
|
|
1061
|
+
fault = err.code === "ENOENT" ? DAMAGED_RECORD : caughtFault(err);
|
|
1044
1062
|
}
|
|
1045
|
-
if (
|
|
1046
|
-
return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" references blob ${v.blobHash} for ${key}@v${v.version}, which is missing or fails content-address integrity — nothing was published
|
|
1063
|
+
if (fault !== undefined) {
|
|
1064
|
+
return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" references blob ${v.blobHash} for ${key}@v${v.version}, which is missing or fails content-address integrity — nothing was published`, ...fault } };
|
|
1047
1065
|
}
|
|
1048
1066
|
}
|
|
1049
1067
|
}
|
|
@@ -1068,16 +1086,17 @@ export class FileFileHistoryStore {
|
|
|
1068
1086
|
const bdoc = { formatVersion: "file-history/1", files: [...mapping], digest: boundaryDigest([...mapping]), ...(sourceCommittedAt !== undefined ? { committedAt: sourceCommittedAt } : {}) };
|
|
1069
1087
|
atomicWriteFile(join(this.base, "tmp"), join(staging, "boundaries", `${historyPathComponent(id)}.json`), JSON.stringify(bdoc));
|
|
1070
1088
|
}
|
|
1071
|
-
|
|
1089
|
+
const carried = this.carryConsumedIds(fromScope, toScope);
|
|
1090
|
+
if (carried !== true) {
|
|
1072
1091
|
safeRm(staging);
|
|
1073
|
-
return { ok: false, error: { code: "adopt_failed", message: `adoptScope could not carry the consumed-entry ledger from "${fromScope}" to "${toScope}" — nothing was published, because a fork missing its lineage's tombstones can mint an already-consumed entry id
|
|
1092
|
+
return { ok: false, error: { code: "adopt_failed", message: `adoptScope could not carry the consumed-entry ledger from "${fromScope}" to "${toScope}" — nothing was published, because a fork missing its lineage's tombstones can mint an already-consumed entry id`, ...carried } };
|
|
1074
1093
|
}
|
|
1075
1094
|
const published = this.publishStagedScopeSync(toScope, staging);
|
|
1076
1095
|
if (published !== "published") {
|
|
1077
1096
|
safeRm(staging);
|
|
1078
1097
|
return published === "not_empty"
|
|
1079
1098
|
? { ok: false, error: { code: "conflict", message: `adoptScope target "${toScope}" gained history of its own while this adoption was being staged — nothing was published` } }
|
|
1080
|
-
: { ok: false, error: { code: "adopt_failed", message: `adoptScope
|
|
1099
|
+
: { ok: false, error: { code: "adopt_failed", message: `adoptScope target "${toScope}" came to have ${published.what} while this adoption was being staged — a scope whose history cannot be judged is not an empty target; nothing was published`, ...published.fault } };
|
|
1081
1100
|
}
|
|
1082
1101
|
}
|
|
1083
1102
|
catch (err) {
|
|
@@ -1087,27 +1106,29 @@ export class FileFileHistoryStore {
|
|
|
1087
1106
|
return { ok: true };
|
|
1088
1107
|
}
|
|
1089
1108
|
catch (err) {
|
|
1090
|
-
return { ok: false, error: { code: "adopt_failed", message: `adoptScope failed: ${thrownText(err)}
|
|
1109
|
+
return { ok: false, error: { code: "adopt_failed", message: `adoptScope failed: ${thrownText(err)}`, ...caughtFault(err) } };
|
|
1091
1110
|
}
|
|
1092
1111
|
}
|
|
1093
1112
|
async exportHistory(scope) {
|
|
1113
|
+
const unexportable = (what, fault) => ({ ok: false, error: { code: "adopt_failed", message: `exportHistory scope "${scope}" has ${what} — its history cannot be exported whole, so nothing was exported`, ...fault } });
|
|
1094
1114
|
const state = this.readTrackedState(scope);
|
|
1095
|
-
if (state.corrupt)
|
|
1096
|
-
return
|
|
1097
|
-
}
|
|
1115
|
+
if (state.corrupt)
|
|
1116
|
+
return unexportable("an unreadable tracked set", state.fault);
|
|
1098
1117
|
const tracked = state.tracked;
|
|
1099
1118
|
const boundaryIds = this.listBoundaryIds(scope);
|
|
1119
|
+
if (!Array.isArray(boundaryIds))
|
|
1120
|
+
return unexportable(boundaryIds.what, boundaryIds.fault);
|
|
1100
1121
|
if (tracked.size === 0 && boundaryIds.length === 0)
|
|
1101
|
-
return null;
|
|
1122
|
+
return { ok: true, history: null };
|
|
1102
1123
|
const boundaries = [];
|
|
1103
1124
|
for (const entryId of [...boundaryIds].sort()) {
|
|
1104
1125
|
const bp = this.tryBoundaryPath(scope, entryId);
|
|
1105
1126
|
const mapping = bp === null ? null : this.readBoundary(bp);
|
|
1106
1127
|
if (mapping === null)
|
|
1107
|
-
|
|
1128
|
+
return unexportable(`a boundary ("${entryId}") that stopped being usable while it was exported`, {});
|
|
1108
1129
|
boundaries.push({ entryId, files: [...mapping.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([relOrAbsPath, version]) => ({ relOrAbsPath, version })) });
|
|
1109
1130
|
}
|
|
1110
|
-
|
|
1131
|
+
const history = {
|
|
1111
1132
|
formatVersion: "file-history/1",
|
|
1112
1133
|
tracked: [...tracked.entries()]
|
|
1113
1134
|
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
@@ -1120,20 +1141,17 @@ export class FileFileHistoryStore {
|
|
|
1120
1141
|
})),
|
|
1121
1142
|
boundaries,
|
|
1122
1143
|
};
|
|
1144
|
+
return { ok: true, history };
|
|
1123
1145
|
}
|
|
1124
1146
|
async importHistory(scope, data, srcGetBlob) {
|
|
1125
1147
|
try {
|
|
1126
1148
|
const shaped = validateFileHistoryExport(data);
|
|
1127
1149
|
if (!shaped.ok)
|
|
1128
1150
|
return shaped;
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
identicalRepublish = true;
|
|
1134
|
-
else
|
|
1135
|
-
return { ok: false, error: { code: "conflict", message: `importHistory target "${scope}" already holds a DIFFERENT history — a first import requires an empty target and a retry is legal only for the identical envelope (atomic visibility prevents half-graphs; this law prevents publishing the wrong complete graph over locally-grown history)` } };
|
|
1136
|
-
}
|
|
1151
|
+
const target = await this.importTargetOf(scope, data, "before");
|
|
1152
|
+
if (typeof target === "object")
|
|
1153
|
+
return target;
|
|
1154
|
+
const identicalRepublish = target === "identical";
|
|
1137
1155
|
const importJournal = join(this.scopeDir(scope), "staging", `import-${Date.now().toString(36)}-${randomBytes(6).toString("hex")}.jsonl`);
|
|
1138
1156
|
ensureDir(join(this.scopeDir(scope), "staging"));
|
|
1139
1157
|
appendFileSync(importJournal, `${JSON.stringify({ entryId: `import:${scope}`, pid: process.pid, startedAt: new Date().toISOString() })}\n`);
|
|
@@ -1147,7 +1165,7 @@ export class FileFileHistoryStore {
|
|
|
1147
1165
|
return await this.runImport(scope, data, srcGetBlob, importJournal, identicalRepublish);
|
|
1148
1166
|
}
|
|
1149
1167
|
catch (err) {
|
|
1150
|
-
return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${thrownText(err)}
|
|
1168
|
+
return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${thrownText(err)}`, ...caughtFault(err) } };
|
|
1151
1169
|
}
|
|
1152
1170
|
}
|
|
1153
1171
|
async runImport(scope, data, srcGetBlob, importJournal, identicalRepublish) {
|
|
@@ -1174,10 +1192,10 @@ export class FileFileHistoryStore {
|
|
|
1174
1192
|
bytes = await srcGetBlob(v.blobHash);
|
|
1175
1193
|
}
|
|
1176
1194
|
catch (err) {
|
|
1177
|
-
return { ok: false, error: { code: "unsupported_format", message: `source blob ${v.blobHash} fetch failed: ${thrownText(err)}
|
|
1195
|
+
return { ok: false, error: { code: "unsupported_format", message: `source blob ${v.blobHash} fetch failed: ${thrownText(err)}`, ...caughtFault(err) } };
|
|
1178
1196
|
}
|
|
1179
1197
|
if (bytes === undefined)
|
|
1180
|
-
return { ok: false, error: { code: "
|
|
1198
|
+
return { ok: false, error: { code: "adopt_failed", message: `source blob ${v.blobHash} is missing — the envelope's graph cannot be completed, so nothing is published` } };
|
|
1181
1199
|
if (createHash("sha256").update(bytes).digest("hex") !== v.blobHash) {
|
|
1182
1200
|
return { ok: false, error: { code: "unsupported_format", message: `source blob ${v.blobHash} fails content-address integrity — refused, nothing published` } };
|
|
1183
1201
|
}
|
|
@@ -1186,12 +1204,11 @@ export class FileFileHistoryStore {
|
|
|
1186
1204
|
}
|
|
1187
1205
|
if (identicalRepublish)
|
|
1188
1206
|
return { ok: true };
|
|
1189
|
-
const now = await this.
|
|
1190
|
-
if (now
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
return
|
|
1194
|
-
}
|
|
1207
|
+
const now = await this.importTargetOf(scope, data, "after");
|
|
1208
|
+
if (now === "identical")
|
|
1209
|
+
return { ok: true };
|
|
1210
|
+
if (now !== "empty")
|
|
1211
|
+
return now;
|
|
1195
1212
|
const staging = `${this.scopeDir(scope)}.importing-${process.pid}-${randomBytes(6).toString("hex")}`;
|
|
1196
1213
|
safeRm(staging);
|
|
1197
1214
|
try {
|
|
@@ -1218,13 +1235,13 @@ export class FileFileHistoryStore {
|
|
|
1218
1235
|
}
|
|
1219
1236
|
catch (err) {
|
|
1220
1237
|
safeRm(staging);
|
|
1221
|
-
return { ok: false, error: { code: "adopt_failed", message: `importHistory could not publish the staged graph for "${scope}": ${thrownText(err)} — nothing was published (a local storage fault, not a malformed envelope)
|
|
1238
|
+
return { ok: false, error: { code: "adopt_failed", message: `importHistory could not publish the staged graph for "${scope}": ${thrownText(err)} — nothing was published (a local storage fault, not a malformed envelope)`, ...caughtFault(err) } };
|
|
1222
1239
|
}
|
|
1223
1240
|
if (published !== "published") {
|
|
1224
1241
|
safeRm(staging);
|
|
1225
1242
|
return published === "not_empty"
|
|
1226
1243
|
? { ok: false, error: { code: "conflict", message: `importHistory target "${scope}" gained history of its own while this import was being staged — nothing was published` } }
|
|
1227
|
-
: { ok: false, error: { code: "adopt_failed", message: `importHistory
|
|
1244
|
+
: { ok: false, error: { code: "adopt_failed", message: `importHistory target "${scope}" came to have ${published.what} while this import was being staged — a scope whose history cannot be judged is not an empty target; nothing was published`, ...published.fault } };
|
|
1228
1245
|
}
|
|
1229
1246
|
}
|
|
1230
1247
|
catch (err) {
|
|
@@ -1234,7 +1251,7 @@ export class FileFileHistoryStore {
|
|
|
1234
1251
|
return { ok: true };
|
|
1235
1252
|
}
|
|
1236
1253
|
catch (err) {
|
|
1237
|
-
return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${thrownText(err)}
|
|
1254
|
+
return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${thrownText(err)}`, ...caughtFault(err) } };
|
|
1238
1255
|
}
|
|
1239
1256
|
finally {
|
|
1240
1257
|
safeRm(importJournal);
|