@sema-agent/core 5.20.0 → 5.21.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 +9 -0
- package/dist/agents/agent-definition.d.ts +7 -0
- package/dist/agents/agent-transcript-tool.d.ts +30 -0
- package/dist/agents/builtin-agents.d.ts +60 -0
- package/dist/agents/cascade.d.ts +107 -0
- package/dist/agents/cumulative-stats.d.ts +56 -0
- package/dist/agents/observer.d.ts +162 -0
- package/dist/agents/peer-admission.d.ts +95 -0
- package/dist/agents/repair-loop.d.ts +226 -0
- package/dist/agents/retain-ledger.d.ts +141 -0
- package/dist/agents/roster-store.d.ts +105 -0
- package/dist/agents/send-message-tool.d.ts +92 -0
- package/dist/agents/session-util.d.ts +5 -0
- package/dist/agents/subagent-steps.d.ts +66 -0
- package/dist/agents/subagent.d.ts +600 -0
- package/dist/agents/suspend-guard.d.ts +29 -0
- package/dist/agents/teacher.d.ts +75 -0
- package/dist/agents/team.d.ts +120 -1
- package/dist/agents/tool-filter.d.ts +34 -0
- package/dist/agents/verify.d.ts +198 -0
- package/dist/bench/metrics.d.ts +455 -0
- package/dist/brain/anthropic.d.ts +30 -0
- package/dist/brain/circuit-breaker.d.ts +33 -0
- package/dist/brain/context-overflow.d.ts +60 -3
- package/dist/brain/degrading.d.ts +67 -0
- package/dist/brain/errors.d.ts +42 -0
- package/dist/brain/failover.d.ts +15 -0
- package/dist/brain/media-degrade.d.ts +39 -0
- package/dist/brain/model-presets.d.ts +31 -0
- package/dist/brain/open-responses.d.ts +19 -0
- package/dist/brain/openai.d.ts +46 -0
- package/dist/brain/reasoning.d.ts +106 -1
- package/dist/brain/repetition.d.ts +83 -0
- package/dist/brain/request-params.d.ts +56 -0
- package/dist/brain/retry.d.ts +46 -0
- package/dist/brain/routing.d.ts +12 -0
- package/dist/brain/status-sink.d.ts +30 -1
- package/dist/brain/stream-engine.d.ts +147 -0
- package/dist/brain/stream-shared.d.ts +34 -0
- package/dist/brain/terminal-cause.d.ts +31 -0
- package/dist/brain/timeout.d.ts +107 -0
- package/dist/brain/tool-call-id.d.ts +20 -0
- package/dist/brain/tool-call-repair.d.ts +13 -0
- package/dist/config/catalog.d.ts +47 -0
- package/dist/config/defaults.d.ts +33 -0
- package/dist/core/a2a-task-state.d.ts +53 -0
- package/dist/core/a2a.d.ts +51 -0
- package/dist/core/arg-summary.d.ts +62 -0
- package/dist/core/ask-question.d.ts +272 -2
- package/dist/core/auto-compaction.d.ts +467 -0
- package/dist/core/auto-compaction.js +20 -6
- package/dist/core/auto-mode-prompt-assets.d.ts +8 -0
- package/dist/core/auto-mode-prompt.d.ts +27 -0
- package/dist/core/auto-mode.d.ts +54 -3
- package/dist/core/auto-promote.d.ts +100 -0
- package/dist/core/background-agent-store.d.ts +292 -0
- package/dist/core/background-shell.d.ts +110 -0
- package/dist/core/cache-break-detector.d.ts +34 -0
- package/dist/core/canonical-json.d.ts +57 -0
- package/dist/core/checkpoint-store.d.ts +1574 -16
- package/dist/core/compliance.d.ts +30 -0
- package/dist/core/consolidate-scope.d.ts +75 -0
- package/dist/core/context-edit.d.ts +99 -0
- package/dist/core/context-guard.d.ts +46 -0
- package/dist/core/exec-gate.d.ts +44 -0
- package/dist/core/exec-output-tail.d.ts +61 -0
- package/dist/core/file-snapshot-store.d.ts +104 -0
- package/dist/core/fs-write-gate-policy.d.ts +36 -0
- package/dist/core/git-worktree-env.d.ts +31 -0
- package/dist/core/governance-codes.d.ts +19 -0
- package/dist/core/hooks.d.ts +608 -1
- package/dist/core/human-input-projection.d.ts +37 -0
- package/dist/core/human-input-projection.js +13 -0
- package/dist/core/image-downsample.d.ts +74 -0
- package/dist/core/locked-config.d.ts +37 -0
- package/dist/core/lsp-diagnostics.d.ts +77 -0
- package/dist/core/lsp-protocol.d.ts +29 -0
- package/dist/core/lsp-session.d.ts +60 -1
- package/dist/core/lsp.d.ts +150 -1
- package/dist/core/mailbox-store.d.ts +57 -0
- package/dist/core/mcp.d.ts +381 -0
- package/dist/core/media-byte-cap.d.ts +21 -0
- package/dist/core/memory-admission.d.ts +71 -0
- package/dist/core/memory-engine/content-origin.d.ts +64 -0
- package/dist/core/memory-engine/data-plane.d.ts +43 -0
- package/dist/core/memory-engine/dual-root.d.ts +24 -0
- package/dist/core/memory-engine/engine.d.ts +230 -0
- package/dist/core/memory-engine/engine.js +103 -35
- package/dist/core/memory-engine/file-backend.d.ts +155 -0
- package/dist/core/memory-engine/frontmatter.d.ts +16 -0
- package/dist/core/memory-engine/layout.d.ts +217 -0
- package/dist/core/memory-engine/memory-backend-contract.d.ts +12 -0
- package/dist/core/memory-engine/migrate.d.ts +9 -0
- package/dist/core/memory-engine/scan.d.ts +12 -0
- package/dist/core/memory-engine/scope-contract.d.ts +61 -1
- package/dist/core/memory-engine/sync-client.d.ts +87 -0
- package/dist/core/memory-engine/sync.d.ts +60 -0
- package/dist/core/memory-engine/tools.d.ts +41 -0
- package/dist/core/memory-engine/types.d.ts +188 -0
- package/dist/core/memory-recall.d.ts +141 -0
- package/dist/core/memory-vector.d.ts +20 -0
- package/dist/core/memory.d.ts +458 -0
- package/dist/core/message-utils.d.ts +6 -0
- package/dist/core/oracle-isolation.d.ts +69 -0
- package/dist/core/permission-rule-consent.d.ts +138 -0
- package/dist/core/permission-rule-model.d.ts +122 -0
- package/dist/core/permission-rule-store.d.ts +119 -3
- package/dist/core/permission-rules.d.ts +87 -1
- package/dist/core/present-plan-tool.d.ts +20 -0
- package/dist/core/pricing.d.ts +26 -0
- package/dist/core/property-harness.d.ts +86 -0
- package/dist/core/protocol-naming.d.ts +38 -0
- package/dist/core/protocol-table.d.ts +61 -0
- package/dist/core/push-queue.d.ts +1 -0
- package/dist/core/remote-env.d.ts +383 -1
- package/dist/core/retention-policy.d.ts +33 -0
- package/dist/core/retention.d.ts +51 -0
- package/dist/core/roles.d.ts +59 -0
- package/dist/core/runner/active-skill-scope.d.ts +34 -0
- package/dist/core/runner/assemble-result.d.ts +134 -0
- package/dist/core/runner/compaction-call-options.d.ts +97 -1
- package/dist/core/runner/grounding-signal.d.ts +10 -0
- package/dist/core/runner/image.d.ts +17 -0
- package/dist/core/runner/image.js +29 -15
- package/dist/core/runner/memory-consolidation.d.ts +60 -0
- package/dist/core/runner/prepare-memory.d.ts +59 -0
- package/dist/core/runner/prepare-task.d.ts +1011 -2
- package/dist/core/runner/prepare-task.js +12 -11
- package/dist/core/runner/prompt-suggestions.d.ts +7 -0
- package/dist/core/runner/runtask.d.ts +304 -3
- package/dist/core/runner/session-file-state-replay.d.ts +49 -0
- package/dist/core/runner/session-rule-policy.d.ts +58 -0
- package/dist/core/runner/strict-output-schema.d.ts +18 -3
- package/dist/core/runner/synthetic-tools.d.ts +113 -0
- package/dist/core/runner/teardown-bounded.d.ts +34 -0
- package/dist/core/runner/tool-disclosure.d.ts +227 -0
- package/dist/core/runner/tool-output-projection.d.ts +6 -0
- package/dist/core/runner/turn-attachments.d.ts +634 -0
- package/dist/core/runner/usage-accounting.d.ts +32 -0
- package/dist/core/runtime.d.ts +9 -0
- package/dist/core/safe-notify.d.ts +64 -0
- package/dist/core/safety-axis-vocab.d.ts +23 -0
- package/dist/core/safety-merge-corpus.d.ts +37 -0
- package/dist/core/scheduler.d.ts +121 -0
- package/dist/core/secret-env.d.ts +32 -0
- package/dist/core/select-model.d.ts +15 -0
- package/dist/core/sensitive-path-policy.d.ts +42 -0
- package/dist/core/session-policy-store.d.ts +94 -0
- package/dist/core/session-reconcile.d.ts +80 -0
- package/dist/core/session-store.d.ts +85 -0
- package/dist/core/session.d.ts +153 -0
- package/dist/core/shared-memory/contract.d.ts +22 -0
- package/dist/core/shared-memory/normalize.d.ts +123 -2
- package/dist/core/shared-memory/tools.d.ts +14 -0
- package/dist/core/shared-memory/types.d.ts +105 -0
- package/dist/core/shutdown-debug.d.ts +6 -0
- package/dist/core/side-query.d.ts +38 -0
- package/dist/core/side-query.js +6 -1
- package/dist/core/skill-tool-specifier.d.ts +72 -0
- package/dist/core/skills-directory.d.ts +100 -1
- package/dist/core/spec-contract.d.ts +89 -0
- package/dist/core/store-contracts/background-agent-store-contract.d.ts +31 -0
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +26 -0
- package/dist/core/store-contracts/contract-harness.d.ts +20 -0
- package/dist/core/store-contracts/contract-kit-version.d.ts +27 -0
- package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +8 -0
- package/dist/core/store-contracts/mailbox-store-contract.d.ts +42 -0
- package/dist/core/store-contracts/tool-result-store-contract.d.ts +13 -0
- package/dist/core/strategy-store.d.ts +37 -0
- package/dist/core/stub-env.d.ts +7 -0
- package/dist/core/surrogate-safe-slice.d.ts +35 -0
- package/dist/core/task-notification.d.ts +202 -0
- package/dist/core/task-outcome.d.ts +53 -0
- package/dist/core/task-registry-agent.d.ts +337 -1
- package/dist/core/task-registry-monitor.d.ts +12 -0
- package/dist/core/task-registry-shared.d.ts +540 -0
- package/dist/core/task-registry.d.ts +343 -0
- package/dist/core/task-registry.js +13 -2
- package/dist/core/task-tool-shape.d.ts +44 -0
- package/dist/core/tighten-task-spec.d.ts +21 -0
- package/dist/core/tool-detach.d.ts +21 -0
- package/dist/core/tool-errors.d.ts +131 -0
- package/dist/core/tool-errors.js +4 -0
- package/dist/core/tool-name-aliases.d.ts +27 -0
- package/dist/core/tool-policy.d.ts +555 -0
- package/dist/core/tool-result-budget.d.ts +32 -0
- package/dist/core/tool-result-store.d.ts +174 -1
- package/dist/core/tools.d.ts +45 -0
- package/dist/core/trace.d.ts +323 -0
- package/dist/core/types.d.ts +3859 -2
- package/dist/core/untrusted-egress.d.ts +8 -0
- package/dist/core/untrusted-text.d.ts +156 -0
- package/dist/core/usage-window-store.d.ts +95 -0
- package/dist/core/version.d.ts +1 -0
- package/dist/core/warm-resume.d.ts +17 -0
- package/dist/core/wiring-manifest.d.ts +169 -0
- package/dist/core/with-retry.d.ts +24 -0
- package/dist/core/workflow-journal-store.d.ts +160 -0
- package/dist/core/workflow-run-store-contract.d.ts +25 -0
- package/dist/core/workflow-run-store.d.ts +119 -0
- package/dist/engine/compaction/compaction.d.ts +256 -1
- package/dist/engine/compaction/utils.d.ts +94 -0
- package/dist/engine/execution-env/kill-tree.d.ts +29 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +94 -0
- package/dist/engine/harness/agent-harness.d.ts +116 -0
- package/dist/engine/harness/agent-harness.js +3 -14
- package/dist/engine/harness/messages.d.ts +15 -0
- package/dist/engine/harness/types.d.ts +464 -2
- package/dist/engine/llm/diagnostics.d.ts +4 -0
- package/dist/engine/llm/event-stream.d.ts +3 -0
- package/dist/engine/llm/index.d.ts +7 -0
- package/dist/engine/llm/types.d.ts +500 -3
- package/dist/engine/llm/validation.d.ts +3 -0
- package/dist/engine/loop/agent-loop.d.ts +87 -2
- package/dist/engine/loop/runtime-deps.d.ts +7 -0
- package/dist/engine/loop/types.d.ts +424 -0
- package/dist/engine/lsp/frame-decoder.d.ts +13 -0
- package/dist/engine/lsp/node-lsp-manager.d.ts +107 -0
- package/dist/engine/lsp/stdio-lsp-transport.d.ts +7 -0
- package/dist/engine/session/import-validate.d.ts +27 -0
- package/dist/engine/session/log-digest.d.ts +93 -0
- package/dist/engine/session/memory-repo.d.ts +6 -0
- package/dist/engine/session/memory-storage.d.ts +2 -0
- package/dist/engine/session/session.d.ts +75 -0
- package/dist/engine/session/storage-base.d.ts +8 -0
- package/dist/fixtures/index.d.ts +36 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.js +0 -1
- package/dist/internal/harness-types.d.ts +6 -0
- package/dist/internal/harness.d.ts +11 -0
- package/dist/internal/llm.d.ts +6 -0
- package/dist/orchestration/builtin-workflows.d.ts +53 -0
- package/dist/orchestration/dev-vm-script-runner.d.ts +19 -0
- package/dist/orchestration/goal.d.ts +57 -0
- package/dist/orchestration/goal.js +3 -0
- package/dist/orchestration/run-spec.d.ts +42 -0
- package/dist/orchestration/run-workflow-tool.d.ts +169 -0
- package/dist/orchestration/workflow-governance.d.ts +61 -0
- package/dist/orchestration/workflow-meta.d.ts +28 -0
- package/dist/orchestration/workflow-observe.d.ts +60 -0
- package/dist/orchestration/workflow-primitives.d.ts +23 -1
- package/dist/orchestration/workflow-sandbox-conformance.d.ts +38 -0
- package/dist/orchestration/workflow-script-runner.d.ts +88 -0
- package/dist/orchestration/workflow-script-store.d.ts +98 -0
- package/dist/orchestration/workflow-size-guideline.d.ts +67 -0
- package/dist/orchestration/workflow-types.d.ts +169 -2
- package/dist/orchestration/workflow.d.ts +358 -0
- package/dist/prompt-assembly/artifact-store.d.ts +33 -0
- package/dist/prompt-assembly/artifact.d.ts +25 -0
- package/dist/prompt-assembly/assemble.d.ts +20 -0
- package/dist/prompt-assembly/composer.d.ts +29 -0
- package/dist/prompt-assembly/epoch.d.ts +55 -1
- package/dist/prompt-assembly/event-registry.d.ts +35 -0
- package/dist/prompt-assembly/explain.d.ts +12 -0
- package/dist/prompt-assembly/tool-catalog.d.ts +38 -0
- package/dist/prompt-assembly/turn-snapshot.d.ts +40 -0
- package/dist/prompt-assembly/types.d.ts +115 -0
- package/dist/prompts/coordinator.d.ts +27 -0
- package/dist/prompts/default.d.ts +539 -0
- package/dist/prompts/simple-sections.d.ts +45 -0
- package/dist/prompts/supervisor.d.ts +66 -0
- package/dist/scenarios/env.d.ts +28 -0
- package/dist/scenarios/full-body.d.ts +50 -0
- package/dist/scenarios/scenario-registry.d.ts +60 -0
- package/dist/scenarios/teacher-quickstart.d.ts +27 -0
- package/dist/server/http.d.ts +17 -0
- package/dist/stores/cc/lockfile.d.ts +6 -0
- package/dist/stores/cc/mailbox-store.d.ts +8 -0
- package/dist/stores/cc/roster-adapter.d.ts +4 -0
- package/dist/stores/cc/sidecar-transcript.d.ts +66 -0
- package/dist/stores/file/background-agent-store.d.ts +24 -0
- package/dist/stores/file/checkpoint-store.d.ts +38 -0
- package/dist/stores/file/file-snapshot-store.d.ts +25 -0
- package/dist/stores/file/fs-atomic.d.ts +155 -0
- package/dist/stores/file/index.d.ts +89 -0
- package/dist/stores/file/mailbox-store.d.ts +36 -0
- package/dist/stores/file/memory-store.d.ts +82 -0
- package/dist/stores/file/permission-rule-store.d.ts +79 -0
- package/dist/stores/file/session-policy-store.d.ts +28 -0
- package/dist/stores/file/session-store.d.ts +40 -0
- package/dist/stores/file/shared-ledger.d.ts +83 -0
- package/dist/stores/file/tool-result-store.d.ts +11 -0
- package/dist/stores/file/usage-window-store.d.ts +18 -0
- package/dist/stores/file/workflow-journal-store.d.ts +100 -0
- package/dist/stores/file/workflow-run-store.d.ts +11 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +282 -0
- package/dist/tools/fs/encoding.d.ts +60 -0
- package/dist/tools/fs/fs-bash.d.ts +133 -0
- package/dist/tools/fs/fs-pdf.d.ts +28 -0
- package/dist/tools/fs/fs-search-tools.d.ts +5 -0
- package/dist/tools/fs/fs-shared.d.ts +360 -0
- package/dist/tools/fs/fs-write.d.ts +16 -0
- package/dist/tools/fs/gh-rate-limit.d.ts +22 -0
- package/dist/tools/fs/index.d.ts +79 -0
- package/dist/tools/fs/notebook.d.ts +56 -0
- package/dist/tools/fs/pdf.d.ts +70 -0
- package/dist/tools/fs/pdf.js +8 -2
- package/dist/tools/fs/repo-map.d.ts +10 -0
- package/dist/tools/fs/safety.d.ts +376 -0
- package/dist/tools/fs/search.d.ts +131 -0
- package/dist/tools/loop-tick.d.ts +24 -0
- package/dist/tools/monitor.d.ts +55 -0
- package/dist/tools/scheduler-tools.d.ts +51 -0
- package/dist/tools/sql-adapters.d.ts +18 -0
- package/dist/tools/sql.d.ts +25 -0
- package/dist/tools/task-list.d.ts +77 -0
- package/dist/tools/todo.d.ts +8 -0
- package/dist/tools/web.d.ts +184 -0
- package/dist/tools/worktree.d.ts +81 -0
- package/package.json +2 -2
- package/dist/tools/gitea-issue.d.ts +0 -13
- package/dist/tools/gitea-issue.js +0 -75
|
@@ -2,38 +2,120 @@ import { type Embedder, type MemoryNoteHeader, type MemoryNoteRecord, type Memor
|
|
|
2
2
|
export declare class FileMemoryStore implements MemoryStore {
|
|
3
3
|
private readonly root;
|
|
4
4
|
private readonly tmpDir;
|
|
5
|
+
/** Optional injected embedder (design/81 Slice 5). When present, note bodies are embedded (in the background,
|
|
6
|
+
* after the durable note write) into a rebuildable sidecar index and `searchScored` ranks by cosine distance
|
|
7
|
+
* (portable / in-process, mirrors PgMemoryStore's jsonb mode). Absent ⇒ lexical, byte-identical to before. */
|
|
5
8
|
private readonly embedder?;
|
|
9
|
+
/** ms budget for a background embed; a slower/stuck endpoint yields no vector (row stays lexical), never
|
|
10
|
+
* blocking the durable note write (which already completed). */
|
|
6
11
|
private readonly embedTimeoutMs;
|
|
12
|
+
/** In-flight background embeds (so tests can flush + `close` can drain). */
|
|
7
13
|
private readonly pending;
|
|
14
|
+
/**
|
|
15
|
+
* RB-146 (2026-07-25, 按面收口): scope state is shared per PHYSICAL directory, like every other file
|
|
16
|
+
* backend. This was the last store on this surface still caching replayed state on the INSTANCE: a second
|
|
17
|
+
* `FileMemoryStore` over one directory replayed its own view on first touch and then served it forever,
|
|
18
|
+
* so a note durably committed through one instance was invisible to the other — the same shape as RB-73
|
|
19
|
+
* (session), RB-55 (agent), RB-59 (run) and RB-134 (checkpoint). Refcounted, and the last holder closes
|
|
20
|
+
* the logs; the `closed` latch below already existed for the fd-recycle guard.
|
|
21
|
+
*/
|
|
8
22
|
private get scopes();
|
|
9
23
|
private readonly sharedScopes;
|
|
10
24
|
private readonly scopeKey;
|
|
25
|
+
/** Set by {@link close}: a background embed that resolves AFTER close must NOT writeSync to the (now closed)
|
|
26
|
+
* vlog fd — a stray write on a recycled fd could corrupt an unrelated file. */
|
|
11
27
|
private closed;
|
|
12
28
|
constructor(root: string, opts?: {
|
|
13
29
|
embedder?: Embedder;
|
|
14
30
|
embedTimeoutMs?: number;
|
|
15
31
|
});
|
|
32
|
+
/** design/81 Slice 5 rung: `portable` (in-process cosine over the sidecar) when an embedder is injected,
|
|
33
|
+
* else `lexical`. File never reaches `native` (no backend index). */
|
|
16
34
|
get vectorMode(): MemoryVectorMode;
|
|
35
|
+
/** True when an embedder was injected. Exposed so a consumer/test can confirm it was accepted. */
|
|
17
36
|
get hasEmbedder(): boolean;
|
|
37
|
+
/** Await all in-flight background embeds (tests / `close`). */
|
|
18
38
|
flushEmbeds(): Promise<void>;
|
|
39
|
+
/** Open (replay) a scope's notes + sidecar vectors once and cache it. */
|
|
19
40
|
private scopeState;
|
|
41
|
+
/**
|
|
42
|
+
* RB-267 — record that `scope` holds live descriptors, and evict the coldest scopes past
|
|
43
|
+
* {@link MAX_OPEN_MEMORY_SCOPES}. Eviction releases the handles ONLY (`closeForSwap`, the RB-167 lazy
|
|
44
|
+
* reopen); the replayed `ScopeState` stays in the map, so nothing about the authority — the entry
|
|
45
|
+
* list, the cursor, the vector cache — is rebuilt or lost, and a later write to an evicted scope
|
|
46
|
+
* reopens its log transparently.
|
|
47
|
+
*/
|
|
20
48
|
private touchOpenLogs;
|
|
49
|
+
/** RB-267 — the notes log for a WRITE. Every durable append goes through here so the LRU sees the
|
|
50
|
+
* access: a write is also the only thing that can (re)open a descriptor (`AppendLog.append` reopens
|
|
51
|
+
* a swap-closed log lazily), so touching here is what keeps "has an open handle ⇒ tracked" true.
|
|
52
|
+
* Reads never open anything and deliberately do not touch. */
|
|
21
53
|
private writeLog;
|
|
54
|
+
/** RB-267 — the sidecar vector log, opened on FIRST WRITE. Only {@link queueEmbed} writes vectors, so
|
|
55
|
+
* a store with no embedder never opens this file at all (it used to take a descriptor per scope for
|
|
56
|
+
* a file it would never touch). Replay reads the path directly and needs no handle. */
|
|
22
57
|
private vectorLog;
|
|
58
|
+
/** Queue a background embed of `body` → sidecar vector for `id` (best-effort, timeout-bounded, non-blocking).
|
|
59
|
+
* Only when an embedder is injected; a failure/timeout leaves the row lexical (fail-open). The durable note
|
|
60
|
+
* write has ALREADY happened before this is called, so this never gates durability. */
|
|
23
61
|
private queueEmbed;
|
|
62
|
+
/** Rewrite the derived `MEMORY.md` projection atomically (CC markdown parity). */
|
|
24
63
|
private writeProjection;
|
|
25
64
|
read(scope: string): string | null;
|
|
26
65
|
append(scope: string, note: string): string | void;
|
|
27
66
|
appendStructured(scope: string, note: StructuredNoteInput): string;
|
|
67
|
+
/** Append + fsync the note event BEFORE updating the in-memory list, then refresh the projection. */
|
|
28
68
|
private commit;
|
|
29
69
|
clear(scope: string): void;
|
|
70
|
+
/** design/84 Seam B: read the cached periodic-consolidation cursor (replayed from the last `op:cursor`). */
|
|
30
71
|
getConsolidationCursor(scope: string): string | undefined;
|
|
72
|
+
/** design/84 Seam B: persist the periodic-consolidation cursor via a durable `op:cursor` append-log event,
|
|
73
|
+
* then update the cached state (durable FIRST, like `commit`). */
|
|
31
74
|
setConsolidationCursor(scope: string, cursor: string): void;
|
|
75
|
+
/**
|
|
76
|
+
* Keyword search backing the `recall` tool. design/81 Slice 2b — uses the SHARED synonym/stem-aware
|
|
77
|
+
* {@link import("../../core/memory.js").lexicalSearchMatch} (the SAME matcher as
|
|
78
|
+
* {@link import("../../core/memory.js").InMemoryMemoryStore.search}, ZERO model/native/embedder dep) so
|
|
79
|
+
* "auth token" matches a note saying "credentials" / "bearer", and the `recall` tool returns IDENTICAL
|
|
80
|
+
* results across the File and InMemory backends. A bullet matches a query term iff it contains ANY of that
|
|
81
|
+
* term's expansions, and must match EVERY query term (AND across terms, OR within a term's synonyms). With
|
|
82
|
+
* no synonym/stem hit this is byte-identical to the plain substring filter.
|
|
83
|
+
*/
|
|
32
84
|
search(scope: string, query: string, limit?: number): string[];
|
|
85
|
+
/** Manifest (design/65 §2.2): a header per note, body excluded — in-memory parity. */
|
|
33
86
|
listStructuredNotes(scope: string): MemoryNoteHeader[];
|
|
34
87
|
getByIds(scope: string, ids: string[]): MemoryNoteRecord[];
|
|
88
|
+
/**
|
|
89
|
+
* Id-addressable similarity retrieval (design/81 Slice 2 — the consolidation candidate source). The
|
|
90
|
+
* `score` is a cosine **DISTANCE** ∈ [0,2], **0 = identical** ({@link ScoredMemory} contract): it is the
|
|
91
|
+
* lexical `1 - Jaccard(terms)` ∈ [0,1] ⊂ [0,2] stand-in (byte-identical to
|
|
92
|
+
* {@link InMemoryMemoryStore.searchScored} / the lexical {@link import("../pg.js").PgMemoryStore} fallback),
|
|
93
|
+
* so the `{0.05, 0.3}` consolidation band needs no retune. An injected embedder is ACCEPTED but vector
|
|
94
|
+
* ranking is the DEFERRED Slice 5 — so this method ALWAYS uses the lexical distance for now (an injected
|
|
95
|
+
* embedder is harmless, NOT a throw). Excludes the query's own id (a note must not self-match — see below)
|
|
96
|
+
* and zero-overlap entries; returns the `limit` nearest, ascending by distance.
|
|
97
|
+
*
|
|
98
|
+
* **Self-id exclusion:** byte-identical to {@link InMemoryMemoryStore.searchScored} / the lexical
|
|
99
|
+
* {@link import("../pg.js").PgMemoryStore} fallback, the SOLE consumer (consolidation) excludes the
|
|
100
|
+
* query note's own id (and its sibling this-task ids) by filtering the returned `ScoredMemory[]` on its
|
|
101
|
+
* `thisTaskIds` set — keeping the store's ranking pure and the cross-backend contract identical.
|
|
102
|
+
*/
|
|
35
103
|
searchScored(scope: string, query: string, limit?: number): ScoredMemory[] | Promise<ScoredMemory[]>;
|
|
104
|
+
/**
|
|
105
|
+
* Consolidation UPDATE (design/81 Slice 2): replace a note's body by id, keeping the id stable. Appends a
|
|
106
|
+
* durable `update` event FIRST, then mutates the in-memory replay + refreshes the projection — same
|
|
107
|
+
* durable-before-visible order as {@link commit}. An auto-derived description is re-derived from the new
|
|
108
|
+
* body (so the manifest doesn't drift); an explicit one is preserved (design/65 [79], in-memory parity).
|
|
109
|
+
* Unknown id → no-op (defensive). NB: no `sanitizeUntrustedText` here — the fence lives at the
|
|
110
|
+
* inlets/recall, NOT in `update` (design/81 §1; in-memory `update` only trims).
|
|
111
|
+
*/
|
|
36
112
|
update(scope: string, id: string, text: string): void;
|
|
113
|
+
/**
|
|
114
|
+
* Consolidation DELETE (design/81 Slice 2): remove a note by id (id never reused afterward). Appends a
|
|
115
|
+
* durable `delete` tombstone FIRST, then drops it from the in-memory replay + refreshes the projection.
|
|
116
|
+
* Unknown id → no-op.
|
|
117
|
+
*/
|
|
37
118
|
delete(scope: string, id: string): void;
|
|
119
|
+
/** Release all open scope handles (best-effort). */
|
|
38
120
|
close(): void;
|
|
39
121
|
}
|
|
@@ -1,6 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/179 §5/§8 — the file backend for persisted allow rules.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this store builds its own symlink refusal
|
|
5
|
+
*
|
|
6
|
+
* The other file stores canonicalize with `realpath`, which RESOLVES a symlink rather than refusing one —
|
|
7
|
+
* the right answer for a data directory, the wrong one for a file that decides what runs without asking.
|
|
8
|
+
* So the refusal is assembled here from the pieces this repository already uses elsewhere: the directory
|
|
9
|
+
* is `lstat`ed and refused if it is a symlink, not a directory, or owned by another user; the temp file is
|
|
10
|
+
* opened `O_EXCL | O_NOFOLLOW` so no planted link is ever traversed; the publish is the standard
|
|
11
|
+
* write-temp → fsync → rename → fsync-dir sequence; and the READ side `lstat`s too, because `readFileSync`
|
|
12
|
+
* follows a link happily.
|
|
13
|
+
*
|
|
14
|
+
* ## One writer, enforced — reading is unrestricted
|
|
15
|
+
*
|
|
16
|
+
* Atomic rename keeps a file from tearing; it does not make "read the revision, decide, write" atomic. Two
|
|
17
|
+
* processes can each read revision N, and the second rename discards the first one's add — or its
|
|
18
|
+
* TOMBSTONE, leaving a rule somebody deleted standing.
|
|
19
|
+
*
|
|
20
|
+
* Three attempts at solving that inside the file established what does NOT work. Declaring single-process
|
|
21
|
+
* use does not hold when the provider is independently constructible. A hand-rolled advisory lock brings
|
|
22
|
+
* its own ownership bugs: pruning an abandoned lock races the next holder for the same path, and expiring
|
|
23
|
+
* by age kills a live-but-slow writer. Write-then-read-back does not close it either — two writers can
|
|
24
|
+
* each rename and each verify before the other renames, so both report success and one write is gone.
|
|
25
|
+
* That last one is the instructive failure: no scheme that reasons only about the FILE can serialize
|
|
26
|
+
* transactions, because the window opens before the first byte is written.
|
|
27
|
+
*
|
|
28
|
+
* So the boundary is enforced where it can be. Obtaining the WRITE face acquires the same process-level
|
|
29
|
+
* lock the rest of this file-store family uses — write-then-link acquisition, no expiry by age, and a
|
|
30
|
+
* prune only for a definitively dead owner — and a second holder, in this process or another, is refused
|
|
31
|
+
* loudly instead of interleaving. Inside the one process that holds it, transactions are serialized and
|
|
32
|
+
* the existing revision check is then a true compare-and-set.
|
|
33
|
+
*
|
|
34
|
+
* READING takes nothing. The gate reads on every shell call and must never be able to fail on a lock, so
|
|
35
|
+
* the lock rides on the write face alone. A deployment that genuinely needs concurrent writers wants a
|
|
36
|
+
* backend with real atomic compare-and-set, which is what the store seam exists for.
|
|
37
|
+
*
|
|
38
|
+
* ## Corruption is a whole-file refusal
|
|
39
|
+
*
|
|
40
|
+
* A file that does not parse, or whose checksum does not match its contents, yields ZERO rules and a loud
|
|
41
|
+
* disclosure — never a partial load. Half a rule set is a set nobody reviewed. Note what the checksum is
|
|
42
|
+
* and is not: it detects corruption, and it does not detect an editor, because an editor recomputes it.
|
|
43
|
+
* For this backend the trust is host = user, exactly as for the settings file it mirrors.
|
|
44
|
+
*/
|
|
1
45
|
import type { PermissionRuleStore, PermissionRuleStoreProvider, PermissionRuleWriter, StoredAllowRules, WritablePermissionRuleStore } from "../../core/permission-rule-store.js";
|
|
2
46
|
import { PERMISSION_RULE_WRITER } from "../../core/permission-rule-store.js";
|
|
3
47
|
import type { StoreDurability, StoreFidelity } from "../../core/checkpoint-store.js";
|
|
48
|
+
/** One principal's rule file. */
|
|
4
49
|
declare class FilePermissionRuleStore implements WritablePermissionRuleStore {
|
|
5
50
|
private readonly dir;
|
|
6
51
|
private readonly file;
|
|
@@ -9,24 +54,58 @@ declare class FilePermissionRuleStore implements WritablePermissionRuleStore {
|
|
|
9
54
|
readonly durability: StoreDurability;
|
|
10
55
|
readonly fidelity: StoreFidelity;
|
|
11
56
|
constructor(dir: string, file: string, acquireWriteLock: () => void, onError?: ((message: string) => void) | undefined);
|
|
57
|
+
/** Read and validate. Any refusal answers with an empty set AND says so — never a silent empty store. */
|
|
12
58
|
private read;
|
|
13
59
|
private disclose;
|
|
14
60
|
private write;
|
|
15
61
|
list(): Promise<StoredAllowRules>;
|
|
62
|
+
/**
|
|
63
|
+
* The current file, for a WRITE.
|
|
64
|
+
*
|
|
65
|
+
* Only genuine absence initializes a fresh one. An unreadable file — bad JSON, a failed checksum, an
|
|
66
|
+
* unexpected shape, a planted symlink — makes every write throw, and the original bytes are left exactly
|
|
67
|
+
* where they are. Treating corruption as emptiness here is what turns a recoverable incident into an
|
|
68
|
+
* unrecoverable one: the next redemption would write a fresh file over the damaged one, discarding every
|
|
69
|
+
* standing rule, every tombstone, the revision and the replica identity, along with the evidence of what
|
|
70
|
+
* went wrong. Reads already answer zero rules for the same condition, so the lane stays fail-closed
|
|
71
|
+
* while a person looks at the file.
|
|
72
|
+
*/
|
|
16
73
|
private current;
|
|
74
|
+
/**
|
|
75
|
+
* Publish `next` and confirm it is readable and is the version just written.
|
|
76
|
+
*
|
|
77
|
+
* This is an integrity check, not concurrency control — a write lock is what makes the transaction
|
|
78
|
+
* atomic (see the module doc). What it catches is the write that did not survive its own round trip:
|
|
79
|
+
* a filesystem that reordered, a partial device failure, or a hand edit landing at exactly the wrong
|
|
80
|
+
* moment. Failing here is loud rather than a reported conflict, because under the lock there is no
|
|
81
|
+
* legitimate writer to have lost a race to.
|
|
82
|
+
*/
|
|
17
83
|
private writeAndVerify;
|
|
84
|
+
/** Serializes this process's transactions. With the write lock held, this is the whole of the
|
|
85
|
+
* serialization the revision check needs. */
|
|
18
86
|
private writeChain;
|
|
19
87
|
private serialize;
|
|
88
|
+
/** Acquiring the write face takes the process lock; reading never does. */
|
|
20
89
|
get [PERMISSION_RULE_WRITER](): PermissionRuleWriter;
|
|
21
90
|
private readonly writer;
|
|
22
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* The file-backed provider. One file per principal, named by a digest of the principal so no identity
|
|
94
|
+
* string ever becomes a path component.
|
|
95
|
+
*
|
|
96
|
+
* An absent principal resolves to a store that reads zero rules and refuses to write — an unauthenticated
|
|
97
|
+
* task does not share anyone's bucket.
|
|
98
|
+
*/
|
|
23
99
|
export declare class FilePermissionRuleStoreProvider implements PermissionRuleStoreProvider {
|
|
24
100
|
private readonly dir;
|
|
25
101
|
private readonly onError?;
|
|
26
102
|
private lock;
|
|
27
103
|
constructor(dir: string, onError?: ((message: string) => void) | undefined);
|
|
104
|
+
/** Take the writer lock, once, on first use of a write face. Reading never calls this. */
|
|
28
105
|
private acquireWriteLock;
|
|
29
106
|
forPrincipal(principal: string | undefined): PermissionRuleStore;
|
|
107
|
+
/** Release the writer lock. A long-lived deployment holds it for its lifetime; a test or a short-lived
|
|
108
|
+
* tool releases so the next holder is not told a live process owns the directory. */
|
|
30
109
|
dispose(): void;
|
|
31
110
|
}
|
|
32
111
|
export type { FilePermissionRuleStore };
|
|
@@ -1,19 +1,47 @@
|
|
|
1
1
|
import { type PutRulesOptions, type SessionPermissionRules, type SessionPolicyStore, type SessionRulesRecord, type StoredSessionRules } from "../../core/session-policy-store.js";
|
|
2
|
+
/**
|
|
3
|
+
* design/99 §E6 — file-backed {@link SessionPolicyStore} for the local (TOC) backend: ONE JSON file per
|
|
4
|
+
* `(sessionId, principal)`, semantics **byte-for-byte identical to `InMemorySessionPolicyStore`** (the
|
|
5
|
+
* cross-backend equivalence contract). CAS-rev OCC + tighten-only reuse the SAME pure helpers
|
|
6
|
+
* (`loosenReasons`/`normalizeRules`/`stripRev`) as core — no re-implemented rule logic that could drift.
|
|
7
|
+
*
|
|
8
|
+
* Atomicity: the file backend is single-process (the `FileStorageBackend` boot lock guarantees ONE writer per
|
|
9
|
+
* data dir), and `getRules`/`putRules` read-check-write SYNCHRONOUSLY (no await between the rev read and the
|
|
10
|
+
* atomic write), so the read-modify-write is atomic in the one event loop — exactly the InMemory store's premise.
|
|
11
|
+
* Cross-process CORRECT concurrency is the Pg/TiDB backend's job (a CAS WHERE clause), by design.
|
|
12
|
+
*/
|
|
13
|
+
/** Coordinates of one corrupt-treated-as-absent policy read (the {@link FileSessionPolicyStoreOptions.onCorruptRead}
|
|
14
|
+
* payload). Named rather than inline so the backend option that forwards it names the SAME shape. */
|
|
2
15
|
export interface SessionPolicyCorruptReadInfo {
|
|
16
|
+
/** The session whose read observed the corrupt row. On the enumeration face this is the session being
|
|
17
|
+
* ENUMERATED — a corrupt row's own `__sid` is by definition unreadable, so it cannot be attributed. */
|
|
3
18
|
sessionId: string;
|
|
19
|
+
/** Present only when the read was keyed by a principal (the enumeration face has none). */
|
|
4
20
|
principal?: string;
|
|
21
|
+
/** Absolute path of the offending file. */
|
|
5
22
|
path: string;
|
|
23
|
+
/** Which corruption grade was observed (unparseable / bad shape / unreadable). */
|
|
6
24
|
reason: string;
|
|
7
25
|
}
|
|
8
26
|
export interface FileSessionPolicyStoreOptions {
|
|
27
|
+
/** Disclosure sink for the corrupt-read fail-open (see {@link FileSessionPolicyStore.discloseCorrupt}).
|
|
28
|
+
* Never fires on plain ENOENT. Swallow-guarded: a throwing sink cannot re-introduce the fault. */
|
|
9
29
|
onCorruptRead?: (info: SessionPolicyCorruptReadInfo) => void;
|
|
10
30
|
}
|
|
11
31
|
export declare class FileSessionPolicyStore implements SessionPolicyStore {
|
|
12
32
|
private readonly dir;
|
|
13
33
|
private readonly onCorruptRead;
|
|
14
34
|
constructor(root: string, opts?: FileSessionPolicyStoreOptions);
|
|
35
|
+
/** The one delivery point for {@link onCorruptRead}. Swallow-guarded here so no caller has to remember. */
|
|
15
36
|
private disclose;
|
|
37
|
+
/** RB-482 top item (baggage class-4 ruling): corrupt-read fail-open is a DOCUMENTED compromise (a throwing
|
|
38
|
+
* getRules fails the whole task; a throwing putRules self-locks the repair write) — but it must not
|
|
39
|
+
* be a SILENT one: a deployment whose policy file was corrupted was getting an apparently normal
|
|
40
|
+
* UNCONSTRAINED task with zero signal. Every corrupt-treated-as-absent branch discloses here
|
|
41
|
+
* (swallow-guarded; never on a plain ENOENT, which really is absence). */
|
|
16
42
|
private discloseCorrupt;
|
|
43
|
+
/** `(sessionId, principal)` → a safe, INJECTIVE filename (sanitizeScope appends the full sha256 of the raw
|
|
44
|
+
* composite key, so distinct keys never collide on disk). */
|
|
17
45
|
private pathFor;
|
|
18
46
|
private read;
|
|
19
47
|
getRules(sessionId: string, principal?: string): Promise<StoredSessionRules | null>;
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import type { Session, SessionForkOptions, SessionMetadata, SessionRepo, SessionTreeEntry } from "../../internal/harness.js";
|
|
2
2
|
export interface FileSessionRepoOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Disclosure sink for a durable read this repo treats as ABSENT (ruled 2026-08-03). Same name and
|
|
5
|
+
* same `{path, reason}` shape as the sibling file stores' seat — "unreadable read as missing" and
|
|
6
|
+
* "corrupt read as missing" are one failure grammar, so they get one seat name.
|
|
7
|
+
*
|
|
8
|
+
* Fires for: an unreadable sessions DIRECTORY (the listing degrades to "no sessions"), a session log
|
|
9
|
+
* that could not be replayed during a listing (skipped from the result), and a corrupt interior JSONL
|
|
10
|
+
* record dropped on replay (turns silently missing from an otherwise healthy session).
|
|
11
|
+
*
|
|
12
|
+
* Never fires on plain ENOENT / `not_found` — that is honest absence, and the torn TAIL stays silent
|
|
13
|
+
* too (dying mid-write is the append log's documented crash-recovery shape, not corruption).
|
|
14
|
+
* Swallow-guarded: a throwing sink cannot re-introduce the fault the fail-open avoids.
|
|
15
|
+
*/
|
|
3
16
|
onCorruptRead?: (info: {
|
|
4
17
|
path: string;
|
|
5
18
|
reason: string;
|
|
@@ -9,12 +22,30 @@ export declare class FileSessionRepo implements SessionRepo {
|
|
|
9
22
|
private readonly dir;
|
|
10
23
|
private readonly tmpDir;
|
|
11
24
|
private readonly onCorruptRead;
|
|
25
|
+
/** RB-437 — the shared authorities THIS repo has joined, weakly (canonical key → the joined storage).
|
|
26
|
+
* `dispose()` walks it and releases each holder share; the weak value is what makes the identity
|
|
27
|
+
* check on the way out possible without pinning a storage the table itself only holds weakly. */
|
|
12
28
|
private readonly joined;
|
|
13
29
|
constructor(root: string, opts?: FileSessionRepoOptions);
|
|
30
|
+
/** The one delivery point for {@link FileSessionRepoOptions.onCorruptRead}; swallow-guarded here so
|
|
31
|
+
* no call site has to remember. */
|
|
14
32
|
private disclose;
|
|
15
33
|
private pathFor;
|
|
34
|
+
/** Read + torn-tail-recover a session file into (meta, entries); a missing file → not_found. */
|
|
16
35
|
private read;
|
|
17
36
|
private storage;
|
|
37
|
+
/**
|
|
38
|
+
* RB-437 — release every shared authority this repo joined (LAST holder ⇒ evict + close the append fd).
|
|
39
|
+
*
|
|
40
|
+
* This is the lifecycle-tied half of the eviction rule the four file-REPLACING operations already
|
|
41
|
+
* cover: after it, a NEW repo/backend over the same root replays each session from disk instead of
|
|
42
|
+
* serving this process's pre-dispose in-memory leaf, and a Session handle held across the teardown
|
|
43
|
+
* is refused with `code=log_closed` rather than writing onto a data root the process has released.
|
|
44
|
+
*
|
|
45
|
+
* Idempotent; safe to call on a repo that never opened anything. A repo whose authority was replaced
|
|
46
|
+
* meanwhile (delete/fork/import/create-over-absent) releases NOTHING for that key — the identity check
|
|
47
|
+
* means a stale holder can never revoke the rebuilt entry.
|
|
48
|
+
*/
|
|
18
49
|
dispose(): Promise<void>;
|
|
19
50
|
create(options?: {
|
|
20
51
|
id?: string;
|
|
@@ -23,7 +54,16 @@ export declare class FileSessionRepo implements SessionRepo {
|
|
|
23
54
|
list(): Promise<SessionMetadata[]>;
|
|
24
55
|
delete(metadata: SessionMetadata): Promise<void>;
|
|
25
56
|
fork(sourceMetadata: SessionMetadata, options?: SessionForkOptions): Promise<Session>;
|
|
57
|
+
/** 2c session-sync ([275], the FILE half of [266]②): the FULL oldest-first log for export. The file backend
|
|
58
|
+
* has NO F3 floor — `read` returns EVERY persisted entry (`id`/`parentId` verbatim) — so this is the complete
|
|
59
|
+
* log, BYPASSing any bounded tail (matches `InMemorySessionRepo.exportEntries`). Missing file → not_found. */
|
|
26
60
|
exportEntries(sessionId: string): Promise<SessionTreeEntry[]>;
|
|
61
|
+
/** 2c session-sync ([275]): import a verbatim log into `sessionId`. The single fail-closed invariant gate runs
|
|
62
|
+
* FIRST ({@link validateEntriesForImport}); `owner` is a durable multi-tenant concept the local file backend
|
|
63
|
+
* has no field for (accepted, unused — same as `InMemorySessionRepo`). Writes the WHOLE file (meta + entries)
|
|
64
|
+
* atomically via tmp→fsync→rename — the rename is the commit point, so a crash leaves either nothing or an
|
|
65
|
+
* orphan temp, never a partially-imported session (mirrors `fork`). An existing session is REPLACED (the
|
|
66
|
+
* orchestrator's importSession owns the already-exists policy, same as InMemory's overwrite). */
|
|
27
67
|
importEntries(sessionId: string, owner: string | undefined, entries: SessionTreeEntry[], options?: {
|
|
28
68
|
preserveActorAssertions?: boolean;
|
|
29
69
|
}): Promise<void>;
|
|
@@ -1,35 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/157 A9 — the SHARED substrate under the three snapshot+ledger file stores
|
|
3
|
+
* ({@link import("./background-agent-store.js").FileBackgroundAgentStore},
|
|
4
|
+
* {@link import("./checkpoint-store.js").FileCheckpointStore},
|
|
5
|
+
* {@link import("./workflow-run-store.js").FileWorkflowRunStore}).
|
|
6
|
+
*
|
|
7
|
+
* Each of them carried its own copy of the SAME three mechanisms: a per-directory refcounted
|
|
8
|
+
* authority table, the snapshot+ledger replay/commit skeleton, and `compact()`'s
|
|
9
|
+
* close → rewrite → reopen swap. What stays in the stores is what genuinely differs — the ledger
|
|
10
|
+
* EVENT model (`applyEvent`) and the domain predicates (CAS / reap / query projections). The stores
|
|
11
|
+
* COMPOSE this (they hold a {@link LedgerCore}); nothing here is inherited or subclassed.
|
|
12
|
+
*
|
|
13
|
+
* ── CLASS-FIX LEDGER ────────────────────────────────────────────────────────────────────────────
|
|
14
|
+
* Every entry below was first fixed on ONE file and then had to be re-fixed on the siblings, some of
|
|
15
|
+
* them twice. They now live here once, and the old addresses are named so the考据 is not lost with
|
|
16
|
+
* the code that carried it:
|
|
17
|
+
*
|
|
18
|
+
* · **RB-55 / RB-59 / RB-134 — one authority per DIRECTORY, not per instance.** Two stores over one
|
|
19
|
+
* directory each replayed a PRIVATE view, so two concurrent same-rev CASes BOTH won: a double
|
|
20
|
+
* claim (background-agent, RB-55), a double-won run update (workflow-run, RB-59 — the 1.408.0
|
|
21
|
+
* sweep covered the agent store and not this one, whose cross-instance test only exercised `put`),
|
|
22
|
+
* and a human approval consumed twice (checkpoint, RB-134, in the file whose own header calls
|
|
23
|
+
* itself "THE LOAD-BEARING file store"). Now: {@link SharedLedgerTable}.
|
|
24
|
+
* · **RB-144 — the table KEY is `canonicalStoreKey` (realpath + case fold).** Three stores were
|
|
25
|
+
* still keying on a lexical `resolve()` while two of them cited the mailbox as the precedent they
|
|
26
|
+
* copied. Now one call site, in {@link SharedLedgerTable.acquire}.
|
|
27
|
+
* · **RB-145 — `close()` is IDEMPOTENT per INSTANCE; the refcount belongs to the DIRECTORY.** A
|
|
28
|
+
* second `close()` on the same object used to decrement again and tear the shared log out from
|
|
29
|
+
* under a live sibling — `log_closed` on its every later write. The per-instance latch stays in
|
|
30
|
+
* each store (it is per-object state); the refcount, the identity-checked eviction and the fd
|
|
31
|
+
* teardown are {@link LedgerCore.release}.
|
|
32
|
+
* · **RB-149 / RB-167 — the compaction swap must be `closeForSwap()`, never `close()`.** `close()`
|
|
33
|
+
* means "finished", so a failed rewrite left the log permanently unusable — one transient I/O
|
|
34
|
+
* fault bricking every instance on that directory. The mailbox's `finally { new AppendLog(path) }`
|
|
35
|
+
* compensation was the closest of the four and still wrong twice: it does not cover an unwritable
|
|
36
|
+
* FILE (the reopen throws too), and its reopen error REPLACES the fault that caused the swap. The
|
|
37
|
+
* answer is AppendLog's lazy reopen — {@link LedgerCore.compact}.
|
|
38
|
+
* · **RB-150 / RB-167 — compaction is HOUSEKEEPING.** It runs after the operation is durable AND
|
|
39
|
+
* applied, so its failure must never be reported as the operation's failure: a committed
|
|
40
|
+
* once-only CAS surfaced as "this did not happen" is the worst possible direction (the caller
|
|
41
|
+
* retries, hits the new rev, and fails again — one success reported as two failures). The swallow
|
|
42
|
+
* is safe ONLY because of the lazy reopen above — {@link LedgerCore.commit}.
|
|
43
|
+
*
|
|
44
|
+
* DELIBERATELY NOT FOLDED IN: `mailbox-store.ts` (refcount is per-BOX with a per-instance dedup, and
|
|
45
|
+
* its compaction rewrites the LIVE log as a replay-equivalent event stream instead of a separate
|
|
46
|
+
* snapshot) and `workflow-journal-store.ts` (one ledger per RUN, no snapshot, no row map). Their
|
|
47
|
+
* tables are the same SHAPE, not the same MECHANISM; both keep their own, with a pointer to this
|
|
48
|
+
* ledger for the shared half of the history.
|
|
49
|
+
*/
|
|
50
|
+
/** The paths one ledger authority owns. `dir` is also what the authority is keyed on. */
|
|
1
51
|
export interface LedgerPaths {
|
|
2
52
|
readonly dir: string;
|
|
53
|
+
/** Scratch dir for {@link atomicWriteFile} (same filesystem as the targets → rename is atomic). */
|
|
3
54
|
readonly tmpDir: string;
|
|
4
55
|
readonly ledgerPath: string;
|
|
5
56
|
readonly snapshotPath: string;
|
|
6
57
|
}
|
|
58
|
+
/** The per-store half: how a SNAPSHOT row is keyed, and how one LEDGER event folds into the map. */
|
|
7
59
|
export interface LedgerModel<Row, Ev> {
|
|
8
60
|
keyOf(row: Row): string;
|
|
9
61
|
apply(rows: Map<string, Row>, ev: Ev): void;
|
|
10
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* One directory's authority: the replayed row map, the per-key async mutex, the append log and the
|
|
65
|
+
* event counter. Created and owned by {@link SharedLedgerTable}; every store instance over the
|
|
66
|
+
* directory holds a reference to the SAME object.
|
|
67
|
+
*/
|
|
11
68
|
export declare class LedgerCore<Row, Ev> {
|
|
12
69
|
private readonly key;
|
|
13
70
|
private readonly paths;
|
|
14
71
|
private readonly model;
|
|
15
72
|
private readonly table;
|
|
73
|
+
/** The authoritative replayed state — domain queries read it directly. */
|
|
16
74
|
readonly rows: Map<string, Row>;
|
|
75
|
+
/** How many store instances hold this authority (RB-145: it belongs to the directory). */
|
|
17
76
|
refs: number;
|
|
18
77
|
private readonly locks;
|
|
19
78
|
private events;
|
|
20
79
|
private handle;
|
|
21
80
|
constructor(key: string, paths: LedgerPaths, model: LedgerModel<Row, Ev>, table: Map<string, LedgerCore<Row, Ev>>);
|
|
81
|
+
/** Rebuild the authority: the snapshot (the compacted base) first, then the live ledger's events in
|
|
82
|
+
* order (last-writer-wins), then open the append handle. Only the FIRST instance runs it. */
|
|
22
83
|
bootstrap(): void;
|
|
84
|
+
/**
|
|
85
|
+
* Serialize an op behind any in-flight op on the same key — the in-process per-key async mutex (the
|
|
86
|
+
* SAME `Map<key, Promise>` chain shape as `TtlSessionStore.pending`). Each call appends a new tail;
|
|
87
|
+
* the next caller awaits THIS op before running. A prior op's rejection never blocks the chain (we
|
|
88
|
+
* await it `.catch`-guarded). The entry is pruned once it is the settled tail with nothing queued.
|
|
89
|
+
*/
|
|
23
90
|
withLock<T>(key: string, fn: () => Promise<T> | T): Promise<T>;
|
|
91
|
+
/**
|
|
92
|
+
* Commit one ledger event: fsync'd append FIRST, in-memory flip AFTER — so a crash can never leave
|
|
93
|
+
* memory and disk disagreeing. Then the best-effort housekeeping compaction (RB-150/RB-167).
|
|
94
|
+
*/
|
|
24
95
|
commit(ev: Ev, fsync: boolean, compactEvery: number): void;
|
|
96
|
+
/** Rewrite the snapshot from the authoritative map (atomic) and truncate the live ledger. After
|
|
97
|
+
* this a replay reads the whole state from the snapshot alone — the round-trip is identical. */
|
|
25
98
|
compact(): void;
|
|
99
|
+
/** Drop one instance's share. The LAST holder evicts the authority — identity-checked, so a stale
|
|
100
|
+
* holder can never revoke a REBUILT entry — and closes the append fd. */
|
|
26
101
|
release(): void;
|
|
102
|
+
/** Test/inspection: events appended since the last compaction. */
|
|
27
103
|
get pendingEvents(): number;
|
|
28
104
|
private get log();
|
|
29
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* The module-level authority table one store family keeps: ONE entry per canonical directory, with
|
|
108
|
+
* eviction (RB-134 — a table without invalidation is RB-73's mistake again: after a `close()` the
|
|
109
|
+
* next store over that root would join a table whose log was already closed and every write would
|
|
110
|
+
* throw `log_closed`, instead of replaying the on-disk truth into a fresh authority).
|
|
111
|
+
*/
|
|
30
112
|
export declare class SharedLedgerTable<Row, Ev> {
|
|
31
113
|
private readonly model;
|
|
32
114
|
private readonly live;
|
|
33
115
|
constructor(model: LedgerModel<Row, Ev>);
|
|
116
|
+
/** Join the live authority for this directory, or create + replay a fresh one. */
|
|
34
117
|
acquire(paths: LedgerPaths): LedgerCore<Row, Ev>;
|
|
35
118
|
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { ToolResultSlice, ToolResultStore } from "../../core/tool-result-store.js";
|
|
2
|
+
/**
|
|
3
|
+
* design/80 §2.5 — file {@link ToolResultStore}: write-once-idempotent, ONE file per ref.
|
|
4
|
+
*
|
|
5
|
+
* `ref = tr_<sessionId>_<toolCallId>` (engine-minted, already globally unique). `put` is an
|
|
6
|
+
* `O_CREAT|O_EXCL` create (`"wx"`) → on `EEXIST` it SILENTLY no-ops (write-once-idempotent; never
|
|
7
|
+
* re-writes — a re-write risks a non-identical preview and breaks the prompt cache, §2.5 / the in-memory
|
|
8
|
+
* store's `if (!map.has(ref))`). Content is fsync'd before return. `get` slices the file by `{offset,
|
|
9
|
+
* limit}` over CHARS to match `ToolResultSlice.totalChars` semantics; an unknown ref → `undefined` (the
|
|
10
|
+
* model is told it's gone — degrades, never crashes). `withToolResultOffload` joins text with `\n` and
|
|
11
|
+
* stores the joined string verbatim, so the char-offset slice here is exactly the in-memory store's slice.
|
|
12
|
+
*/
|
|
2
13
|
export declare class FileToolResultStore implements ToolResultStore {
|
|
3
14
|
private readonly dir;
|
|
4
15
|
constructor(root: string);
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import { type UsageWindow, type UsageWindowReading, type UsageWindowStore } from "../../core/usage-window-store.js";
|
|
2
|
+
/**
|
|
3
|
+
* design/164 件五 — file-backed {@link UsageWindowStore}: ONE JSON record per governed key, semantics
|
|
4
|
+
* identical to `InMemoryUsageWindowStore` BY CONSTRUCTION (both are shells around the same pure
|
|
5
|
+
* `chargeUsageRecord` / `readUsageRecord`; neither re-implements window arithmetic).
|
|
6
|
+
*
|
|
7
|
+
* Atomicity: the file backend is single-process (the `FileStorageBackend` boot lock guarantees ONE writer
|
|
8
|
+
* per data dir) and `charge` reads-modifies-writes SYNCHRONOUSLY (no await between the read and the
|
|
9
|
+
* atomic replace), so the update is atomic within the one event loop — the in-memory store's premise.
|
|
10
|
+
* Cross-process governance belongs to a database backend with a real transaction.
|
|
11
|
+
*
|
|
12
|
+
* **Corrupt records FAIL CLOSED** — deliberately the OPPOSITE of `FileSessionPolicyStore`'s fail-open. A
|
|
13
|
+
* policy file is a DENY list, where absence is the safe direction; a usage window is a CEILING, where
|
|
14
|
+
* absence is the unsafe direction: treating an unreadable ledger as "no usage yet" would hand the key an
|
|
15
|
+
* unlimited, silent allowance — precisely what the operator wired this to prevent. A corrupt record
|
|
16
|
+
* therefore refuses with `usage_window.store_corrupt` until it is repaired or removed.
|
|
17
|
+
*/
|
|
2
18
|
export declare class FileUsageWindowStore implements UsageWindowStore {
|
|
3
19
|
private readonly dir;
|
|
4
20
|
constructor(root: string);
|
|
21
|
+
/** key → a safe, INJECTIVE filename (`sanitizeScope` appends the full sha256 of the raw key, so two
|
|
22
|
+
* distinct principals can never share a ledger file). */
|
|
5
23
|
private pathFor;
|
|
6
24
|
private loadRecord;
|
|
7
25
|
charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[]): Promise<void>;
|