@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
|
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { chmod, mkdtemp, mkdir, rm, stat, symlink, writeFile, readFile } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
+
import { isGovernanceRetryClass } from "../governance-codes.js";
|
|
6
7
|
import { NodeExecutionEnv } from "../../internal/harness.js";
|
|
7
8
|
import { beginContract } from "./contract-harness.js";
|
|
8
9
|
function probeOver(inner) {
|
|
@@ -34,6 +35,13 @@ function envWithFailingRead(inner, fail) {
|
|
|
34
35
|
},
|
|
35
36
|
});
|
|
36
37
|
}
|
|
38
|
+
const NAMES_A_FAULT = { track_failed: true, snapshot_failed: true, restore_failed: true, adopt_failed: true, unsupported_format: true, not_found: false, conflict: false, aborted: false };
|
|
39
|
+
function assertPermanenceWord(error, what) {
|
|
40
|
+
const p = error?.permanence;
|
|
41
|
+
assert.ok(p === undefined || isGovernanceRetryClass(p), `${what}: permanence must be absent or a GovernanceRetryClass word, got ${typeof p === "string" ? `"${p}"` : typeof p}`);
|
|
42
|
+
if (p !== undefined)
|
|
43
|
+
assert.ok(error !== undefined && NAMES_A_FAULT[error.code] === true, `${what}: ${error?.code} is not a fault and carries no permanence`);
|
|
44
|
+
}
|
|
37
45
|
async function withTree(fn) {
|
|
38
46
|
const dir = await mkdtemp(join(tmpdir(), "fh-kit-"));
|
|
39
47
|
try {
|
|
@@ -52,9 +60,20 @@ export async function fileHistoryStoreContract(make, runAssertion) {
|
|
|
52
60
|
const r = await store.restore("sc", "e1", env, dir);
|
|
53
61
|
assert.equal(r.outcome, "failed");
|
|
54
62
|
assert.equal(r.error?.code, "not_found");
|
|
63
|
+
assertPermanenceWord(r.error, "a boundary never made (not a fault)");
|
|
55
64
|
assert.deepEqual(r.filesChanged, []);
|
|
56
65
|
assert.deepEqual(r.refused, []);
|
|
57
66
|
assert.equal(await store.reap("sc", []), 0);
|
|
67
|
+
if (store.exportHistory !== undefined)
|
|
68
|
+
assert.deepEqual(await store.exportHistory("sc"), { ok: true, history: null }, "nothing to export is `ok` with `history: null` — the one meaning of null");
|
|
69
|
+
await writeFile(join(dir, "f.txt"), "v1");
|
|
70
|
+
assert.deepEqual(await store.trackEdit("sc", join(dir, "f.txt"), env, dir), { ok: true, minted: true });
|
|
71
|
+
assert.deepEqual(await store.snapshot("sc", "b1", env, dir), { ok: true });
|
|
72
|
+
await writeFile(join(dir, "f.txt"), "v2-edited");
|
|
73
|
+
const never = await store.restore("sc", "never-made", env, dir);
|
|
74
|
+
assert.equal(never.error?.code, "not_found");
|
|
75
|
+
assert.deepEqual([never.filesChanged, never.identicalSkipped, never.refused, never.failed], [[], [], [], []], "a boundary never made has an empty ledger");
|
|
76
|
+
assert.equal(await readFile(join(dir, "f.txt"), "utf8"), "v2-edited", "a boundary never made touches nothing");
|
|
58
77
|
});
|
|
59
78
|
});
|
|
60
79
|
run("first-touch idempotence: a second trackEdit for the same path is a cheap no-op (ZERO env reads)", async () => {
|
|
@@ -186,6 +205,8 @@ export async function fileHistoryStoreContract(make, runAssertion) {
|
|
|
186
205
|
assert.equal(busy.ok, false);
|
|
187
206
|
if (!busy.ok)
|
|
188
207
|
assert.equal(busy.error.code, "conflict");
|
|
208
|
+
if (!busy.ok)
|
|
209
|
+
assertPermanenceWord(busy.error, "the target law's conflict");
|
|
189
210
|
assert.equal(await store.canRestore("fresh", "b"), true);
|
|
190
211
|
});
|
|
191
212
|
});
|
|
@@ -301,10 +322,14 @@ export async function fileHistoryStoreContract(make, runAssertion) {
|
|
|
301
322
|
});
|
|
302
323
|
const t = await store.trackEdit("sc", join(dir, "f.txt"), hostile, dir);
|
|
303
324
|
assert.equal(typeof t.ok, "boolean");
|
|
325
|
+
if (!t.ok)
|
|
326
|
+
assertPermanenceWord(t.error, "trackEdit over a hostile env");
|
|
304
327
|
const sHealthy = await store.trackEdit("sc2", join(dir, "f.txt"), env, dir);
|
|
305
328
|
assert.deepEqual(sHealthy, { ok: true, minted: true });
|
|
306
329
|
const snap = await store.snapshot("sc2", "b", hostile, dir);
|
|
307
330
|
assert.equal(snap.ok, false);
|
|
331
|
+
if (!snap.ok)
|
|
332
|
+
assertPermanenceWord(snap.error, "snapshot over a hostile env");
|
|
308
333
|
const r = await store.restore("sc2", "nope", hostile, dir);
|
|
309
334
|
assert.equal(r.outcome, "failed");
|
|
310
335
|
});
|
|
@@ -364,7 +389,8 @@ export async function fileHistoryStoreContract(make, runAssertion) {
|
|
|
364
389
|
await shed.write(join(dir, "keep.txt"), "turn-1");
|
|
365
390
|
assert.deepEqual(await src.snapshot("sc", "b1", env, dir), { ok: true });
|
|
366
391
|
assert.equal(await src.reap("sc", ["b0", "b1"]), 0);
|
|
367
|
-
const
|
|
392
|
+
const exported = await src.exportHistory("sc");
|
|
393
|
+
const envelope = exported.ok ? exported.history : null;
|
|
368
394
|
assert.ok(envelope !== null, "a scope with history must export an envelope");
|
|
369
395
|
assert.equal(envelope.formatVersion, "file-history/1");
|
|
370
396
|
assert.ok(envelope.tracked.every((t) => (t.keyKind === "root-relative" || t.keyKind === "absolute") && (t.keyFamily === "posix" || t.keyFamily === "win")), "every tracked row must carry its key's self-description (kind + family) — a bare key cannot be judged after it crosses machines");
|
|
@@ -409,6 +435,17 @@ export async function fileHistoryStoreContract(make, runAssertion) {
|
|
|
409
435
|
});
|
|
410
436
|
const m = await store.importHistory("sc", missingBlob, async () => undefined);
|
|
411
437
|
assert.equal(m.ok, false);
|
|
438
|
+
if (!m.ok)
|
|
439
|
+
assert.equal(m.error.code, "adopt_failed", "a source that cannot supply a referenced blob");
|
|
440
|
+
if (!m.ok)
|
|
441
|
+
assertPermanenceWord(m.error, "importHistory whose source has no such blob");
|
|
442
|
+
assert.equal(await store.canRestore("sc", "b2"), false);
|
|
443
|
+
const thrown = await store.importHistory("sc", missingBlob, async () => {
|
|
444
|
+
throw Object.assign(new Error("source blob unreadable"), { code: "EACCES" });
|
|
445
|
+
});
|
|
446
|
+
assert.equal(thrown.ok, false);
|
|
447
|
+
if (!thrown.ok)
|
|
448
|
+
assertPermanenceWord(thrown.error, "importHistory whose source fetch threw");
|
|
412
449
|
assert.equal(await store.canRestore("sc", "b2"), false);
|
|
413
450
|
const duplicated = wireEnvelope({
|
|
414
451
|
formatVersion: "file-history/1",
|
|
@@ -507,7 +544,9 @@ export async function fileHistoryStoreContract(make, runAssertion) {
|
|
|
507
544
|
await shed.write(join(dir, "f.txt"), "source-pristine");
|
|
508
545
|
assert.deepEqual(await src.trackEdit("sc", join(dir, "f.txt"), env, dir), { ok: true, minted: true });
|
|
509
546
|
assert.deepEqual(await src.snapshot("sc", "b1", env, dir), { ok: true });
|
|
510
|
-
const
|
|
547
|
+
const exported = await src.exportHistory("sc");
|
|
548
|
+
assert.ok(exported.ok && exported.history !== null, "a scope with history must export an envelope");
|
|
549
|
+
const envelope = exported.history;
|
|
511
550
|
const dst = make();
|
|
512
551
|
assert.deepEqual(await dst.importHistory("target", envelope, shed.fetch), { ok: true });
|
|
513
552
|
assert.deepEqual(await dst.importHistory("target", envelope, shed.fetch), { ok: true }, "the same envelope arriving twice is a re-publish, not a conflict");
|
|
@@ -515,7 +554,8 @@ export async function fileHistoryStoreContract(make, runAssertion) {
|
|
|
515
554
|
assert.deepEqual(await dst.trackEdit("target", join(dir, "local.txt"), env, dir), { ok: true, minted: true });
|
|
516
555
|
assert.deepEqual(await dst.snapshot("target", "b-local", env, dir), { ok: true });
|
|
517
556
|
const other = await src.exportHistory("sc");
|
|
518
|
-
|
|
557
|
+
assert.ok(other.ok && other.history !== null);
|
|
558
|
+
const conflict = await dst.importHistory("target", other.history, shed.fetch);
|
|
519
559
|
assert.equal(conflict.ok, false);
|
|
520
560
|
if (!conflict.ok)
|
|
521
561
|
assert.equal(conflict.error.code, "conflict");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type SessionPolicyStore } from "../session-policy-store.js";
|
|
2
|
+
import { type ContractAssertionRunner } from "./contract-harness.js";
|
|
3
|
+
/**
|
|
4
|
+
* The full cross-backend contract for the operator part of a {@link SessionPolicyStore} row. Every case builds its store via
|
|
5
|
+
* `make()`; with no runner the cases run sequentially and the first failure throws.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sessionPolicyStoreContract(make: () => SessionPolicyStore, runAssertion?: ContractAssertionRunner): Promise<void>;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { SESSION_RULES_GRAMMAR, SessionPolicyError, readSessionRulesShape } from "../session-policy-store.js";
|
|
4
|
+
import { beginContract } from "./contract-harness.js";
|
|
5
|
+
const OWNER = "kit-owner";
|
|
6
|
+
const G = SESSION_RULES_GRAMMAR;
|
|
7
|
+
const A = "KitToolA";
|
|
8
|
+
const B = "KitToolB";
|
|
9
|
+
const fresh = () => `kit-2151-${randomUUID()}`;
|
|
10
|
+
const isLoosenRefusal = (e) => e instanceof SessionPolicyError && e.code === "loosen_forbidden";
|
|
11
|
+
async function partOf(store, sessionId) {
|
|
12
|
+
const row = await store.getRules(sessionId, OWNER);
|
|
13
|
+
assert.ok(row !== null, "the row the case wrote must read back");
|
|
14
|
+
const read = readSessionRulesShape(row);
|
|
15
|
+
assert.ok(!("fault" in read), `the stored row must read as rules: ${"fault" in read ? read.sentence : ""}`);
|
|
16
|
+
return read.rules.operatorRules;
|
|
17
|
+
}
|
|
18
|
+
export async function sessionPolicyStoreContract(make, runAssertion) {
|
|
19
|
+
const { run, settle } = beginContract(runAssertion);
|
|
20
|
+
run("a non-operator write over no row stamps an EMPTY operator part — the owner takes its own deny back", async () => {
|
|
21
|
+
const store = make();
|
|
22
|
+
const sid = fresh();
|
|
23
|
+
await store.putRules(sid, OWNER, { toolDeny: [A] });
|
|
24
|
+
assert.deepEqual(await partOf(store, sid), { grammar: G });
|
|
25
|
+
const back = await store.putRules(sid, OWNER, {});
|
|
26
|
+
assert.equal(back.toolDeny, undefined);
|
|
27
|
+
assert.deepEqual(await partOf(store, sid), { grammar: G });
|
|
28
|
+
});
|
|
29
|
+
run("an operator write's whole value is the operator's: no part on the row, and the owner's drop of its entry is refused with nothing written", async () => {
|
|
30
|
+
const store = make();
|
|
31
|
+
const sid = fresh();
|
|
32
|
+
const stored = await store.putRules(sid, OWNER, { toolDeny: [A] }, { operator: true });
|
|
33
|
+
assert.equal(await partOf(store, sid), undefined);
|
|
34
|
+
await assert.rejects(store.putRules(sid, OWNER, {}), isLoosenRefusal);
|
|
35
|
+
const row = await store.getRules(sid, OWNER);
|
|
36
|
+
assert.equal(row?.rev, stored.rev);
|
|
37
|
+
assert.deepEqual(row?.toolDeny, [A]);
|
|
38
|
+
});
|
|
39
|
+
run("an owner write over an operator row carries the operator's buckets as its part — above them it adds and takes back, below them it is refused", async () => {
|
|
40
|
+
const store = make();
|
|
41
|
+
const sid = fresh();
|
|
42
|
+
await store.putRules(sid, OWNER, { toolDeny: [A] }, { operator: true });
|
|
43
|
+
await store.putRules(sid, OWNER, { toolDeny: [A, B] });
|
|
44
|
+
assert.deepEqual(await partOf(store, sid), { toolDeny: [A], grammar: G });
|
|
45
|
+
await store.putRules(sid, OWNER, { toolDeny: [A] });
|
|
46
|
+
assert.deepEqual((await store.getRules(sid, OWNER))?.toolDeny, [A]);
|
|
47
|
+
await assert.rejects(store.putRules(sid, OWNER, {}), isLoosenRefusal);
|
|
48
|
+
assert.deepEqual(await partOf(store, sid), { toolDeny: [A], grammar: G });
|
|
49
|
+
});
|
|
50
|
+
run("the same entry from both writers: once an operator write carries it, the owner's drop is refused", async () => {
|
|
51
|
+
const store = make();
|
|
52
|
+
const sid = fresh();
|
|
53
|
+
await store.putRules(sid, OWNER, { toolDeny: [A] });
|
|
54
|
+
await store.putRules(sid, OWNER, { toolDeny: [A] }, { operator: true });
|
|
55
|
+
await assert.rejects(store.putRules(sid, OWNER, {}), isLoosenRefusal);
|
|
56
|
+
assert.deepEqual((await store.getRules(sid, OWNER))?.toolDeny, [A]);
|
|
57
|
+
});
|
|
58
|
+
run("a part handed in with the value is no authority: the stored part is the judge's", async () => {
|
|
59
|
+
const store = make();
|
|
60
|
+
const sid = fresh();
|
|
61
|
+
await store.putRules(sid, OWNER, { toolDeny: [A] }, { operator: true });
|
|
62
|
+
const forged = { toolDeny: [A, B], operatorRules: { grammar: G } };
|
|
63
|
+
await store.putRules(sid, OWNER, forged);
|
|
64
|
+
assert.deepEqual(await partOf(store, sid), { toolDeny: [A], grammar: G });
|
|
65
|
+
await assert.rejects(store.putRules(sid, OWNER, {}), isLoosenRefusal);
|
|
66
|
+
});
|
|
67
|
+
run("allowDirs: the owner's own confinement is the owner's to remove; an operator's is not", async () => {
|
|
68
|
+
const store = make();
|
|
69
|
+
const own = fresh();
|
|
70
|
+
await store.putRules(own, OWNER, { allowDirs: ["/work/src"] });
|
|
71
|
+
await store.putRules(own, OWNER, {});
|
|
72
|
+
assert.equal((await store.getRules(own, OWNER))?.allowDirs, undefined);
|
|
73
|
+
const operators = fresh();
|
|
74
|
+
await store.putRules(operators, OWNER, { allowDirs: ["/work"] }, { operator: true });
|
|
75
|
+
await store.putRules(operators, OWNER, { allowDirs: ["/work/src"] });
|
|
76
|
+
await store.putRules(operators, OWNER, { allowDirs: ["/work"] });
|
|
77
|
+
await assert.rejects(store.putRules(operators, OWNER, {}), isLoosenRefusal);
|
|
78
|
+
assert.deepEqual((await store.getRules(operators, OWNER))?.allowDirs, ["/work"]);
|
|
79
|
+
});
|
|
80
|
+
run("a stored row replayed by a non-operator onto a fresh or erased key keeps who may take what back (an import's replay)", async () => {
|
|
81
|
+
const store = make();
|
|
82
|
+
const exported = async (sid) => {
|
|
83
|
+
const records = store.listBySession !== undefined ? await store.listBySession(sid) : [{ principal: OWNER, rules: (await store.getRules(sid, OWNER)) }];
|
|
84
|
+
assert.equal(records.length, 1);
|
|
85
|
+
return records[0].rules;
|
|
86
|
+
};
|
|
87
|
+
const opSid = fresh();
|
|
88
|
+
await store.putRules(opSid, OWNER, { toolDeny: [A] }, { operator: true });
|
|
89
|
+
const opRow = await exported(opSid);
|
|
90
|
+
const replayed = fresh();
|
|
91
|
+
await store.putRules(replayed, OWNER, opRow);
|
|
92
|
+
await assert.rejects(store.putRules(replayed, OWNER, {}), isLoosenRefusal);
|
|
93
|
+
const mixedSid = fresh();
|
|
94
|
+
await store.putRules(mixedSid, OWNER, { toolDeny: [A] }, { operator: true });
|
|
95
|
+
await store.putRules(mixedSid, OWNER, { toolDeny: [A, B] });
|
|
96
|
+
const mixed = await exported(mixedSid);
|
|
97
|
+
const target = fresh();
|
|
98
|
+
await store.putRules(target, OWNER, mixed);
|
|
99
|
+
assert.deepEqual(await partOf(store, target), { toolDeny: [A], grammar: G });
|
|
100
|
+
await store.putRules(target, OWNER, { toolDeny: [A] });
|
|
101
|
+
await assert.rejects(store.putRules(target, OWNER, {}), isLoosenRefusal);
|
|
102
|
+
if (store.deleteBySession !== undefined) {
|
|
103
|
+
await store.deleteBySession(replayed);
|
|
104
|
+
await store.putRules(replayed, OWNER, opRow);
|
|
105
|
+
await assert.rejects(store.putRules(replayed, OWNER, {}), isLoosenRefusal);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
run("a value built on the no-row record (`{ rev: 0 }`, what a read of an unwritten key answers) is written now: the owner takes back what it added through it", async () => {
|
|
109
|
+
const store = make();
|
|
110
|
+
const sid = fresh();
|
|
111
|
+
await store.putRules(sid, OWNER, { rev: 0, toolDeny: [A] });
|
|
112
|
+
assert.deepEqual(await partOf(store, sid), { grammar: G });
|
|
113
|
+
const back = await store.putRules(sid, OWNER, {});
|
|
114
|
+
assert.equal(back.toolDeny, undefined);
|
|
115
|
+
});
|
|
116
|
+
run("the part rides the session's enumeration (an export carries who may take what back)", async () => {
|
|
117
|
+
const store = make();
|
|
118
|
+
if (store.listBySession === undefined)
|
|
119
|
+
return;
|
|
120
|
+
const sid = fresh();
|
|
121
|
+
await store.putRules(sid, OWNER, { toolDeny: [A] }, { operator: true });
|
|
122
|
+
await store.putRules(sid, OWNER, { toolDeny: [A, B] });
|
|
123
|
+
const records = await store.listBySession(sid);
|
|
124
|
+
assert.equal(records.length, 1);
|
|
125
|
+
const read = readSessionRulesShape(records[0].rules);
|
|
126
|
+
assert.ok(!("fault" in read));
|
|
127
|
+
assert.deepEqual(read.rules.operatorRules, { toolDeny: [A], grammar: G });
|
|
128
|
+
});
|
|
129
|
+
await settle();
|
|
130
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { strict as assert } from "node:assert";
|
|
2
|
+
import { TtlSessionStore } from "../session-store.js";
|
|
2
3
|
import { beginContract } from "./contract-harness.js";
|
|
3
4
|
const SESSION_ENTRY = (id, parentId) => ({
|
|
4
5
|
id,
|
|
@@ -72,5 +73,129 @@ export async function sessionRepoContract(make, runAssertion) {
|
|
|
72
73
|
assert.deepEqual(after.map((e) => e.parentId), [null, "e1"]);
|
|
73
74
|
assert.equal((await repo.exportEntries(A)).length, 2, "importing into C must not disturb the donor session");
|
|
74
75
|
});
|
|
76
|
+
run("the owner cell round-trips: create({id, owner}) records it and open() reads it back; a create naming no owner records null", async () => {
|
|
77
|
+
const repo = make();
|
|
78
|
+
const owned = await repo.create({ id: A, owner: "kit-alice" });
|
|
79
|
+
assert.equal((await owned.getMetadata()).owner, "kit-alice");
|
|
80
|
+
assert.equal((await (await repo.open({ id: A, createdAt: "" })).getMetadata()).owner, "kit-alice", "the owner is in the row, not in the handle");
|
|
81
|
+
const B = "0198fa00-0000-7000-8000-00000000000b";
|
|
82
|
+
await repo.create({ id: B });
|
|
83
|
+
assert.equal((await (await repo.open({ id: B, createdAt: "" })).getMetadata()).owner ?? null, null, "no owner named ⇒ anonymous");
|
|
84
|
+
});
|
|
85
|
+
run("create({id, owner: Y}) onto an existing row owned by X reopens it untouched: the row keeps X, and the session it returns carries X", async () => {
|
|
86
|
+
const repo = make();
|
|
87
|
+
const first = await repo.create({ id: A, owner: "kit-x" });
|
|
88
|
+
const token = (await first.getMetadata()).incarnation;
|
|
89
|
+
const again = await repo.create({ id: A, owner: "kit-y" });
|
|
90
|
+
assert.equal((await again.getMetadata()).owner, "kit-x", "a caller whose create met an existing row reads that row's owner, never its own");
|
|
91
|
+
assert.equal((await again.getMetadata()).incarnation, token, "and the same occupant");
|
|
92
|
+
assert.equal((await (await repo.open({ id: A, createdAt: "" })).getMetadata()).owner, "kit-x");
|
|
93
|
+
});
|
|
94
|
+
run("fork records the owner its options name in the fork row, else (absent or null) the source row's; importEntries onto an existing row keeps its metadata whole, onto an absent id records the importer", async () => {
|
|
95
|
+
const repo = make();
|
|
96
|
+
const src = await repo.create({ id: A, owner: "kit-alice" });
|
|
97
|
+
await src.getStorage().appendEntry(SESSION_ENTRY("e1", null));
|
|
98
|
+
const B = "0198fa00-0000-7000-8000-00000000000b";
|
|
99
|
+
const C = "0198fa00-0000-7000-8000-00000000000c";
|
|
100
|
+
const D = "0198fa00-0000-7000-8000-00000000000d";
|
|
101
|
+
assert.equal((await (await repo.fork({ id: A, createdAt: "" }, { id: B, owner: "kit-bob" })).getMetadata()).owner, "kit-bob");
|
|
102
|
+
assert.equal((await (await repo.fork({ id: A, createdAt: "" }, { id: C })).getMetadata()).owner, "kit-alice", "a fork whose options name no owner records the source row's — a copy of an owned history is never anonymous");
|
|
103
|
+
assert.equal((await (await repo.fork({ id: A, createdAt: "" }, { id: D, owner: null })).getMetadata()).owner, "kit-alice", "an explicit null never downgrades the copy either");
|
|
104
|
+
if (repo.importEntries === undefined)
|
|
105
|
+
return;
|
|
106
|
+
const token = (await src.getMetadata()).incarnation;
|
|
107
|
+
await repo.importEntries(A, "kit-carol", [SESSION_ENTRY("e1", null)]);
|
|
108
|
+
const imported = await (await repo.open({ id: A, createdAt: "" })).getMetadata();
|
|
109
|
+
assert.equal(imported.owner, "kit-alice", "the owner is recorded only by the write that creates the row: an import never re-stamps it");
|
|
110
|
+
assert.equal(imported.incarnation, token, "the log is replaced, not the occupant");
|
|
111
|
+
await repo.importEntries(A, undefined, []);
|
|
112
|
+
assert.equal((await (await repo.open({ id: A, createdAt: "" })).getMetadata()).owner, "kit-alice", "an import with no principal never makes an owned row anonymous");
|
|
113
|
+
const cells = (m) => [m.createdAt, m.forkedFrom, m.forkedFromIncarnation, m.incarnation, m.owner, JSON.stringify(m.placement)];
|
|
114
|
+
const forkRow = await (await repo.open({ id: B, createdAt: "" })).getMetadata();
|
|
115
|
+
await repo.importEntries(B, "kit-carol", [SESSION_ENTRY("e1", null)]);
|
|
116
|
+
assert.deepEqual(cells(await (await repo.open({ id: B, createdAt: "" })).getMetadata()), cells(forkRow), "the log is replaced, never the row: a fork row keeps its creation time and its lineage");
|
|
117
|
+
const E = "0198fa00-0000-7000-8000-00000000000e";
|
|
118
|
+
await repo.importEntries(E, "kit-carol", [SESSION_ENTRY("e1", null)]);
|
|
119
|
+
assert.equal((await (await repo.open({ id: E, createdAt: "" })).getMetadata()).owner, "kit-carol", "an import onto an absent id is a new row of the importer");
|
|
120
|
+
});
|
|
121
|
+
run("the metadata a repo hands out is not the row: writing to it (from open() or list()) changes no identity cell the row answers", async () => {
|
|
122
|
+
const repo = make();
|
|
123
|
+
const sess = await repo.create({ id: A, owner: "kit-alice" });
|
|
124
|
+
const token = (await sess.getMetadata()).incarnation;
|
|
125
|
+
for (const meta of [await sess.getMetadata(), await (await repo.open({ id: A, createdAt: "" })).getMetadata(), ...(await repo.list()).filter((m) => m.id === A)]) {
|
|
126
|
+
try {
|
|
127
|
+
Object.assign(meta, { owner: "kit-mallory", incarnation: "kit-forged" });
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const again = await (await repo.open({ id: A, createdAt: "" })).getMetadata();
|
|
133
|
+
assert.equal(again.owner, "kit-alice", "the owner changes only through a write of the row");
|
|
134
|
+
assert.equal(again.incarnation, token);
|
|
135
|
+
if (repo.headOf !== undefined)
|
|
136
|
+
assert.deepEqual(await repo.headOf(A), { owner: "kit-alice", incarnation: token, leafId: null });
|
|
137
|
+
});
|
|
138
|
+
run("headOf (where the repo answers it): no row ⇒ undefined; a row ⇒ its owner, occupant and active leaf, as open() reads them", async () => {
|
|
139
|
+
const repo = make();
|
|
140
|
+
if (repo.headOf === undefined)
|
|
141
|
+
return;
|
|
142
|
+
assert.equal(await repo.headOf(A), undefined);
|
|
143
|
+
const sess = await repo.create({ id: A, owner: "kit-alice" });
|
|
144
|
+
await sess.getStorage().appendEntry(SESSION_ENTRY("e1", null));
|
|
145
|
+
await sess.getStorage().appendEntry(SESSION_ENTRY("e2", "e1"));
|
|
146
|
+
await sess.getStorage().setLeafId("e1");
|
|
147
|
+
const opened = await repo.open({ id: A, createdAt: "" });
|
|
148
|
+
assert.deepEqual(await repo.headOf(A), { owner: "kit-alice", incarnation: (await opened.getMetadata()).incarnation, leafId: await opened.getStorage().getLeafId() });
|
|
149
|
+
await repo.delete({ id: A, createdAt: "" });
|
|
150
|
+
assert.equal(await repo.headOf(A), undefined, "a deleted row has no head");
|
|
151
|
+
});
|
|
152
|
+
run("the store over a repo: a row replaced behind a cached session (deleted, then created again naming another owner) — the store's next acquire serves the new row, never the cached transcript", async () => {
|
|
153
|
+
const repo = make();
|
|
154
|
+
const store = new TtlSessionStore({ repo });
|
|
155
|
+
try {
|
|
156
|
+
if (store.headOf === undefined)
|
|
157
|
+
return;
|
|
158
|
+
await store.register(A, "kit-alice");
|
|
159
|
+
await (await store.acquire(A)).session.getStorage().appendEntry(SESSION_ENTRY("alice-only", null));
|
|
160
|
+
await repo.delete({ id: A, createdAt: "" });
|
|
161
|
+
await repo.create({ id: A, owner: "kit-bob" });
|
|
162
|
+
const served = (await store.acquire(A)).session;
|
|
163
|
+
assert.equal((await served.getMetadata()).owner, "kit-bob", "the session handed out is the row the owner is read from");
|
|
164
|
+
assert.deepEqual((await served.getStorage().getEntries()).map((e) => e.id), [], "the previous owner's transcript is not served under the new owner");
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
store.dispose();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
run("the store over a repo answers headOf exactly when the repo does: present ⇒ the repo's own answer; absent ⇒ no headOf on the store, which still works", async () => {
|
|
171
|
+
const repo = make();
|
|
172
|
+
const store = new TtlSessionStore({ repo });
|
|
173
|
+
try {
|
|
174
|
+
assert.equal(typeof store.headOf, typeof repo.headOf === "function" ? "function" : "undefined");
|
|
175
|
+
const { sessionId } = await store.acquire(A);
|
|
176
|
+
if (store.headOf !== undefined)
|
|
177
|
+
assert.deepEqual(await store.headOf(sessionId), await repo.headOf(sessionId), "the store's head is the repo's, verbatim");
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
store.dispose();
|
|
181
|
+
}
|
|
182
|
+
const bare = {
|
|
183
|
+
create: (o) => repo.create(o),
|
|
184
|
+
open: (m) => repo.open(m),
|
|
185
|
+
list: () => repo.list(),
|
|
186
|
+
delete: (m) => repo.delete(m),
|
|
187
|
+
fork: (s, o) => repo.fork(s, o),
|
|
188
|
+
};
|
|
189
|
+
const over = new TtlSessionStore({ repo: bare });
|
|
190
|
+
try {
|
|
191
|
+
assert.equal(over.headOf, undefined, "no head read on the repo ⇒ none on the store (an authorizer takes its fail-closed arm)");
|
|
192
|
+
const B = "0198fa00-0000-7000-8000-00000000000b";
|
|
193
|
+
await over.acquire(B);
|
|
194
|
+
await over.register(B, "kit-alice");
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
over.dispose();
|
|
198
|
+
}
|
|
199
|
+
});
|
|
75
200
|
await settle();
|
|
76
201
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type SessionStore } from "../session.js";
|
|
2
2
|
import { type ContractAssertionRunner } from "./contract-harness.js";
|
|
3
|
-
import { type SessionStoreIncarnationContractHooks } from "./session-store-incarnation-contract.js";
|
|
4
3
|
/**
|
|
5
4
|
* The cross-backend contract for {@link SessionStore} as the interface states it TODAY — what a store must
|
|
6
5
|
* do so that the engine, a deployment's authorizer and a shell read the same answers off it whether the
|
|
@@ -18,15 +17,13 @@ import { type SessionStoreIncarnationContractHooks } from "./session-store-incar
|
|
|
18
17
|
* What the kit cannot see from outside, it does not claim: idle eviction and `pin` (the interface has no
|
|
19
18
|
* clock seam), what `release` does to an ordinary session's live set (`size` is best-effort and a durable
|
|
20
19
|
* `list` may still show the history), a durable backend's crash recovery, and a delete that fails half-way.
|
|
21
|
-
* One stated obligation is not a case yet because the bundled store does not keep it: an ownership row
|
|
22
|
-
* outliving its session across a restart (`TtlSessionStore` holds owners in process memory).
|
|
23
20
|
*
|
|
24
21
|
* Semantics since @sema-agent/core 7.55.0.
|
|
25
22
|
*/
|
|
26
23
|
export interface SessionStoreContractHooks {
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
deleteSession:
|
|
24
|
+
/** Delete `sessionId`'s session ROW through the backend's own deletion path (history and all), so that a later
|
|
25
|
+
* `acquire(sessionId)` creates a new session under the same id. */
|
|
26
|
+
deleteSession: (store: SessionStore, sessionId: string) => Promise<void>;
|
|
30
27
|
/** A SECOND, independent store instance over the same rows as `store` — what a restarted process
|
|
31
28
|
* constructs. After calling it the kit no longer uses `store`, so a backend that holds an exclusive lock
|
|
32
29
|
* may release `store`'s first (the kit still disposes `store` at the end of the case: that dispose must
|
|
@@ -40,7 +37,7 @@ export interface SessionStoreContractHooks {
|
|
|
40
37
|
/** Optional faces (plus the replica hook, and `requireExisting` — absent = the store rejects the option, its other
|
|
41
38
|
* legal mode) a run verified or found absent; reported per judging case, after its assertions held. */
|
|
42
39
|
onOptionalMember?: (report: {
|
|
43
|
-
member: "requireExisting" | "forget" | "fork" | "list" | "noteTaskRun" | "listPlaced" | "placements" | "
|
|
40
|
+
member: "requireExisting" | "forget" | "fork" | "list" | "noteTaskRun" | "listPlaced" | "placements" | "headOf" | "ownership" | "replica";
|
|
44
41
|
status: "verified" | "absent";
|
|
45
42
|
}) => void;
|
|
46
43
|
}
|