@sema-agent/server 7.142.0 → 7.144.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/MIGRATION.md +608 -0
- package/USAGE.md +63 -36
- package/dist/adoption/plan.js +22 -0
- package/dist/approval-ask-audit-store.d.ts +7 -5
- package/dist/approval-ask-audit-store.js +12 -6
- package/dist/approval-ask-machine.d.ts +15 -7
- package/dist/approval-ask-machine.js +10 -4
- package/dist/approval-card.d.ts +155 -39
- package/dist/approval-card.js +111 -31
- package/dist/approval-queue-kind.d.ts +27 -0
- package/dist/approval-queue-kind.js +6 -0
- package/dist/approval-reconciler.d.ts +8 -8
- package/dist/approval-reconciler.js +3 -3
- package/dist/audit.js +4 -2
- package/dist/boot/budget-tracing.d.ts +1 -1
- package/dist/boot/execution-env.js +1 -1
- package/dist/boot/governance-seams.d.ts +26 -41
- package/dist/boot/governance-seams.js +61 -28
- package/dist/boot/late-binds.d.ts +7 -4
- package/dist/boot/late-binds.js +3 -1
- package/dist/boot/leader.js +2 -4
- package/dist/boot/memory-consolidation.d.ts +0 -8
- package/dist/boot/memory-consolidation.js +3 -3
- package/dist/boot/reapers.js +5 -4
- package/dist/boot/resolve-spec.d.ts +1 -1
- package/dist/boot/resolve-spec.js +8 -8
- package/dist/boot/runner-deps.d.ts +14 -6
- package/dist/boot/runner-deps.js +6 -17
- package/dist/boot/shutdown.d.ts +9 -0
- package/dist/boot/shutdown.js +18 -2
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-02-web-admin-pull.js +3 -0
- package/dist/boot/stage-03-store-backend.d.ts +4 -1
- package/dist/boot/stage-03-store-backend.js +1 -2
- package/dist/boot/stage-04-stores.d.ts +5 -2
- package/dist/boot/stage-04-stores.js +3 -5
- package/dist/boot/stage-05-execution-env.d.ts +5 -2
- package/dist/boot/stage-06-runners.d.ts +5 -2
- package/dist/boot/stage-06-runners.js +1 -3
- package/dist/boot/stage-07-capability-layer.d.ts +5 -2
- package/dist/boot/stage-07-capability-layer.js +6 -2
- package/dist/boot/stage-08-reapers.d.ts +5 -2
- package/dist/boot/stage-09-leader.d.ts +5 -2
- package/dist/boot/stage-10-http-server.d.ts +5 -2
- package/dist/boot/stage-10-http-server.js +4 -1
- package/dist/boot/stores.d.ts +5 -2
- package/dist/boot/stores.js +7 -7
- package/dist/boot/web-admin.d.ts +5 -1
- package/dist/boot/web-admin.js +31 -19
- package/dist/capabilities/hands-lane.d.ts +9 -39
- package/dist/capabilities/hands-lane.js +8 -13
- package/dist/capabilities/mcp-probe.d.ts +3 -2
- package/dist/capabilities/scenarios.d.ts +23 -17
- package/dist/capabilities/scenarios.js +2 -2
- package/dist/capabilities/skills.d.ts +1 -17
- package/dist/capabilities/skills.js +0 -8
- package/dist/capability-facts.d.ts +8 -8
- package/dist/capability-facts.js +4 -4
- package/dist/card-void.d.ts +6 -9
- package/dist/config-catalog.js +1 -1
- package/dist/config-d-migration.d.ts +2 -0
- package/dist/config-dir-trust.js +58 -29
- package/dist/config-invariants.d.ts +7 -1
- package/dist/config-invariants.js +7 -0
- package/dist/config-provider.d.ts +10 -0
- package/dist/config-provider.js +15 -4
- package/dist/config-root-migration.d.ts +13 -0
- package/dist/config-root-migration.js +99 -0
- package/dist/config-types.d.ts +5 -5
- package/dist/config.d.ts +4 -3
- package/dist/config.js +1 -1
- package/dist/delegated-session-owner.d.ts +35 -151
- package/dist/delegated-session-owner.js +1 -83
- package/dist/device-ws-protocol.d.ts +1 -0
- package/dist/device-ws-protocol.js +2 -1
- package/dist/entry-mode-clamp-notice.d.ts +1 -1
- package/dist/env-model-table-gate.d.ts +53 -0
- package/dist/env-model-table-gate.js +53 -0
- package/dist/execution-placement.d.ts +7 -4
- package/dist/fleet/fleet-bus.d.ts +28 -52
- package/dist/fleet/fleet-bus.js +35 -100
- package/dist/fleet/fleet-reconciler.d.ts +4 -4
- package/dist/fleet/fleet-reconciler.js +1 -1
- package/dist/fleet/subagent-tail-bus.d.ts +14 -11
- package/dist/fleet/subagent-tail-bus.js +15 -28
- package/dist/hooks/classic-stations.d.ts +3 -1
- package/dist/hooks/classic-stations.js +2 -0
- package/dist/hooks/hook-runner.js +28 -7
- package/dist/host-decision.d.ts +54 -20
- package/dist/host-decision.js +42 -14
- package/dist/http/admission.d.ts +1 -1
- package/dist/http/admission.js +7 -3
- package/dist/http/approval-sweeps.js +11 -8
- package/dist/http/caller-trust.d.ts +1 -1
- package/dist/http/durable-run-leg.js +2 -2
- package/dist/http/engine-refusal.d.ts +16 -0
- package/dist/http/engine-refusal.js +17 -0
- package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
- package/dist/http/memory-compliance-failure-reply.js +4 -6
- package/dist/http/owner-access.d.ts +11 -0
- package/dist/http/owner-access.js +13 -0
- package/dist/http/principal-gate.d.ts +1 -1
- package/dist/http/refusal-body.d.ts +7 -3
- package/dist/http/refusal-body.js +1 -0
- package/dist/http/resume-legs.d.ts +21 -7
- package/dist/http/resume-legs.js +82 -53
- package/dist/http/resume-restatement.d.ts +39 -0
- package/dist/http/resume-restatement.js +25 -0
- package/dist/http/route-ctx.d.ts +35 -10
- package/dist/http/route-ctx.js +1 -1
- package/dist/http/routes/approvals-assistant.d.ts +9 -1
- package/dist/http/routes/approvals-assistant.js +143 -31
- package/dist/http/routes/capabilities.js +11 -2
- package/dist/http/routes/memory-policy.d.ts +2 -2
- package/dist/http/routes/memory-policy.js +1 -1
- package/dist/http/routes/notify-wake.js +23 -25
- package/dist/http/routes/rules.js +48 -13
- package/dist/http/routes/runs.js +9 -8
- package/dist/http/routes/session-memory-sync.d.ts +34 -6
- package/dist/http/routes/session-memory-sync.js +27 -10
- package/dist/http/routes/session-sync.js +13 -20
- package/dist/http/routes/sessions.d.ts +2 -2
- package/dist/http/routes/sessions.js +22 -23
- package/dist/http/routes/tasks.js +27 -49
- package/dist/http/server.d.ts +13 -7
- package/dist/http/server.js +2 -2
- package/dist/http/stored-body-replay.js +15 -4
- package/dist/http/wire-types.d.ts +17 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lane-home-probe.d.ts +15 -0
- package/dist/lane-home-probe.js +4 -0
- package/dist/leader/leader.js +5 -2
- package/dist/leader/wire.d.ts +7 -1
- package/dist/leader/wire.js +6 -5
- package/dist/leg-facts.d.ts +37 -0
- package/dist/leg-facts.js +15 -0
- package/dist/memory-bundle-engine.d.ts +1 -4
- package/dist/memory-bundle-engine.js +2 -2
- package/dist/memory-operator-faces.d.ts +30 -66
- package/dist/memory-operator-faces.js +17 -88
- package/dist/memory-scope-stores.d.ts +38 -40
- package/dist/memory-scope-stores.js +51 -36
- package/dist/memory-scope.d.ts +0 -9
- package/dist/memory-scope.js +0 -9
- package/dist/memory-sync-client.js +14 -3
- package/dist/memory-sync.d.ts +20 -4
- package/dist/memory-sync.js +23 -4
- package/dist/model-plane-face.d.ts +13 -7
- package/dist/model-plane-face.js +2 -1
- package/dist/observability/corrupt-read-seat.d.ts +5 -9
- package/dist/observability/corrupt-read-seat.js +1 -2
- package/dist/observability/fail-open.d.ts +30 -10
- package/dist/observability/fail-open.js +27 -10
- package/dist/observability/run-terminal.d.ts +1 -1
- package/dist/observability/run-terminal.js +6 -2
- package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
- package/dist/orchestration/workflow-completion-inbox.js +0 -12
- package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
- package/dist/orchestration/workflow-park-decision.d.ts +8 -3
- package/dist/orchestration/workflow-park-decision.js +5 -3
- package/dist/org-delivery-gate.d.ts +11 -0
- package/dist/org-delivery-gate.js +19 -1
- package/dist/parked-decide.d.ts +13 -9
- package/dist/parked-decide.js +7 -7
- package/dist/parked-revive-surface.d.ts +6 -7
- package/dist/parked-revive-surface.js +1 -1
- package/dist/peer-inbound-face.d.ts +122 -0
- package/dist/peer-inbound-face.js +123 -0
- package/dist/permission-rule-list.d.ts +13 -9
- package/dist/permission-rule-list.js +7 -3
- package/dist/plugins/approval-ask-store-file.js +18 -6
- package/dist/plugins/approval-ask-store-memory.js +8 -6
- package/dist/plugins/approval-ask-store-sql.d.ts +28 -13
- package/dist/plugins/approval-ask-store-sql.js +30 -11
- package/dist/plugins/caching-session-store.d.ts +0 -3
- package/dist/plugins/caching-session-store.js +0 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +45 -38
- package/dist/plugins/checkpoint-store-sql.js +108 -45
- package/dist/plugins/file-history-store-sql.d.ts +24 -18
- package/dist/plugins/file-history-store-sql.js +24 -19
- package/dist/plugins/file-run-store.d.ts +29 -0
- package/dist/plugins/file-run-store.js +97 -8
- package/dist/plugins/fork-routing-session-store.d.ts +8 -1
- package/dist/plugins/fork-routing-session-store.js +6 -0
- package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
- package/dist/plugins/imported-layer-root-retirement.js +96 -0
- package/dist/plugins/local-checkpoint-store.d.ts +6 -8
- package/dist/plugins/local-checkpoint-store.js +5 -4
- package/dist/plugins/local-session-store.d.ts +69 -30
- package/dist/plugins/local-session-store.js +133 -57
- package/dist/plugins/local-session-window.d.ts +4 -2
- package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
- package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
- package/dist/plugins/mailbox-store-sql.d.ts +12 -8
- package/dist/plugins/mailbox-store-sql.js +76 -7
- package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
- package/dist/plugins/memory-sync-store-pg.js +21 -0
- package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
- package/dist/plugins/memory-sync-store-tidb.js +13 -1
- package/dist/plugins/one-time-migrations.d.ts +44 -14
- package/dist/plugins/one-time-migrations.js +118 -15
- package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
- package/dist/plugins/org-delivery-quota-store.js +2 -12
- package/dist/plugins/park-commit-watch.d.ts +1 -1
- package/dist/plugins/permission-rule-store-file.d.ts +10 -1
- package/dist/plugins/permission-rule-store-file.js +15 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
- package/dist/plugins/permission-rule-store-sql.js +35 -2
- package/dist/plugins/pg-session-storage.d.ts +1 -9
- package/dist/plugins/pg-session-storage.js +2 -19
- package/dist/plugins/remote-env-device.d.ts +8 -11
- package/dist/plugins/remote-env-device.js +6 -3
- package/dist/plugins/remote-env-host.d.ts +8 -4
- package/dist/plugins/remote-env-host.js +7 -9
- package/dist/plugins/remote-env-k8s.d.ts +37 -24
- package/dist/plugins/remote-env-k8s.js +50 -17
- package/dist/plugins/remote-shell.js +12 -2
- package/dist/plugins/retention-store-sql.d.ts +2 -2
- package/dist/plugins/sandbox-read-floor.d.ts +5 -7
- package/dist/plugins/sandbox-read-floor.js +2 -7
- package/dist/plugins/schema-consistency.d.ts +5 -1
- package/dist/plugins/session-fork.d.ts +1 -1
- package/dist/plugins/session-ownership.d.ts +9 -0
- package/dist/plugins/session-ownership.js +6 -0
- package/dist/plugins/session-store.d.ts +13 -16
- package/dist/plugins/session-store.js +9 -10
- package/dist/plugins/sql-errors.d.ts +10 -1
- package/dist/plugins/sql-errors.js +13 -0
- package/dist/plugins/sql-json-column.d.ts +24 -2
- package/dist/plugins/sql-json-column.js +38 -3
- package/dist/plugins/store-backend.d.ts +23 -28
- package/dist/plugins/store-backend.js +12 -3
- package/dist/plugins/store-contracts.d.ts +29 -5
- package/dist/plugins/store-contracts.js +4 -1
- package/dist/plugins/task-attachment-store.d.ts +2 -2
- package/dist/plugins/task-attachment-store.js +37 -52
- package/dist/plugins/tidb-session-store.d.ts +2 -14
- package/dist/plugins/tidb-session-store.js +2 -20
- package/dist/plugins/tool-result-store-sql.d.ts +9 -8
- package/dist/plugins/tool-result-store-sql.js +23 -2
- package/dist/plugins/wiring-section-codec.d.ts +32 -0
- package/dist/plugins/wiring-section-codec.js +55 -0
- package/dist/plugins/workspace-fill.d.ts +1 -1
- package/dist/rule-entry-list.d.ts +3 -2
- package/dist/rule-entry-list.js +5 -1
- package/dist/rules-consent.d.ts +28 -5
- package/dist/rules-consent.js +40 -28
- package/dist/run-local.js +11 -6
- package/dist/runs.d.ts +12 -8
- package/dist/runs.js +10 -7
- package/dist/runtime-governance.d.ts +1 -1
- package/dist/security.d.ts +0 -4
- package/dist/server-secret-env.d.ts +4 -0
- package/dist/session-error-wire.d.ts +34 -23
- package/dist/session-error-wire.js +8 -3
- package/dist/session-incarnation.d.ts +3 -3
- package/dist/session-notify-door.d.ts +106 -0
- package/dist/session-notify-door.js +97 -0
- package/dist/session-sync.d.ts +9 -14
- package/dist/session-sync.js +5 -5
- package/dist/session-workspace.d.ts +3 -13
- package/dist/session-workspace.js +0 -9
- package/dist/standing-answer-grant.d.ts +5 -0
- package/dist/standing-answer-grant.js +10 -0
- package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
- package/dist/stores-sql/session-policy-store-sql.js +3 -13
- package/dist/task-mcp.d.ts +1 -1
- package/dist/task-settings.d.ts +11 -8
- package/dist/task-settings.js +31 -11
- package/dist/tool-approval.d.ts +55 -35
- package/dist/tool-approval.js +112 -74
- package/dist/trace/core-keyset-guard.d.ts +12 -9
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +4 -2
- package/dist/trace/exit-seat.d.ts +7 -1
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/ledger-sink.d.ts +2 -2
- package/dist/trace/ledger-sink.js +12 -4
- package/dist/trace/model-segment.d.ts +74 -25
- package/dist/trace/model-segment.js +141 -32
- package/dist/trace/project.d.ts +15 -6
- package/dist/trace/project.js +12 -4
- package/dist/trace/redact.d.ts +72 -9
- package/dist/trace/redact.js +1509 -471
- package/dist/trace/self-minted-wire.d.ts +598 -47
- package/dist/trace/self-minted-wire.js +190 -20
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/verifier-pause-card.d.ts +33 -5
- package/dist/verifier-pause-card.js +15 -10
- package/dist/web-admin-client/apply-effective.d.ts +12 -16
- package/dist/web-admin-client/apply-effective.js +90 -10
- package/dist/workspace-space.d.ts +19 -3
- package/dist/workspace-space.js +8 -4
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
- package/package.json +3 -2
- package/wire-changes/server@7.143.0.json +1167 -0
- package/wire-changes/server@7.144.0.json +291 -0
- package/dist/session-records-skipped-notice.d.ts +0 -21
- package/dist/session-records-skipped-notice.js +0 -27
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { FileSessionRepo, type AcquiredSession, type PlacedSessionRow, type SessionHead, type SessionPlacementRecord, type SessionStore, type SessionRepo, type SessionMetadata, type SessionTreeEntry, type StoreDurability } from "@sema-agent/core";
|
|
2
|
-
import { type OwnerlessClaim } from "../delegated-session-owner.js";
|
|
3
2
|
import { type PlacementAcquireOpts } from "./session-placement.js";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
import { StoreBootRefusalError, type SessionRunClaimSource, type SessionRunRollupSource, type SessionSummary } from "./store-contracts.js";
|
|
@@ -30,11 +29,39 @@ export declare const SESSION_META_CELL_OBJECT: z.ZodObject<{
|
|
|
30
29
|
createdAt: z.ZodOptional<z.ZodString>;
|
|
31
30
|
forkedFrom: z.ZodOptional<z.ZodString>;
|
|
32
31
|
}, z.core.$strict>;
|
|
32
|
+
/** 严格边档清册里一只边档的对账读数(拒句的逐文件一段;{@link LocalDataRootSchemaMismatchError.findings})。 */
|
|
33
|
+
export interface LocalSidecarMismatch {
|
|
34
|
+
/** 文件的绝对路径。 */
|
|
35
|
+
readonly path: string;
|
|
36
|
+
/** 角色(`<文件名> (the local <角色> sidecar)`)。 */
|
|
37
|
+
readonly label: string;
|
|
38
|
+
/** 读不出的种别:`not-json` / `malformed`(形),或 I/O 的 errno(`EACCES` …;没有 errno ⇒ `unreadable`)。 */
|
|
39
|
+
readonly problem: string;
|
|
40
|
+
/** 格里本构建不认得的键(非空 ⇒ 这只边档是更新的引擎写下的:数据根比本构建新)。 */
|
|
41
|
+
readonly unknownFields: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* **local 数据根的「本构建读不出」拒**(起服对账一族的 local 腿;机读码与 SQL 列册对账同一个 `config.schema_mismatch`,拒句同形)。
|
|
45
|
+
* 一只严格边档用它那一只严格读者读不出 ⇒ 这一只拒,文件原地不动、一个字节不改。两处触发、一只类:
|
|
46
|
+
* · `boot` —— 起服对账({@link LocalSessionStore.assertStrictSidecarsReadable},唯一调用点 `LocalBackend.ensureSchema()`)把清册里每一只读不出的合成一只:拒启;
|
|
47
|
+
* · `serving` —— 起服之后这只边档第一次被碰时才读不出(嵌入方跳过了 `ensureSchema()`;或起服后别的写者把它写成本构建读不出的形,在替换之前那一读里看见):
|
|
48
|
+
* 读不出的是**形**(`not-json` / `malformed`,确定的判词)⇒ 这只边档在本进程里从此每一读每一写都交这一只(重启才重判);碰它的请求失败(把它交给 HTTP 全局兜底的路由:
|
|
49
|
+
* 500 `internal.error` + 一行 error 级 `request_error` 带本码;自己接住故障的路由按各自的 500 臂答)。I/O 故障(非确定)不粘。
|
|
50
|
+
* 属 {@link StoreBootRefusalError} 族:起服降级臂不吞它(缺省推导的 local 不会被吃成内存后端)。拒句零原文:只点名文件、种别与不认得的键名,不带任何值。
|
|
51
|
+
*/
|
|
52
|
+
export declare class LocalDataRootSchemaMismatchError extends StoreBootRefusalError {
|
|
53
|
+
readonly findings: readonly LocalSidecarMismatch[];
|
|
54
|
+
readonly when: "boot" | "serving";
|
|
55
|
+
/** 机读码(与 SQL 列册对账 / 退役 schema 的拒启同一个)。 */
|
|
56
|
+
readonly code = "config.schema_mismatch";
|
|
57
|
+
constructor(findings: readonly LocalSidecarMismatch[], when: "boot" | "serving");
|
|
58
|
+
}
|
|
33
59
|
/**
|
|
34
60
|
* local 车道会话存档的**唯一 repo 出口**:本仓里每一只读 `<root>/sessions/*.jsonl` 的 `FileSessionRepo` 都从这里取(`LocalSessionStore` 的底座;会话视图窗不再自开 repo,
|
|
35
61
|
* 它的元数据与日志都由店交出 —— `local-session-window.ts`),所以坏记录的披露座只有这一份接线。座 = F 类 `server.session-repo.corrupt-record-skipped`(会话存档里读不出的记录丢的是可用性:
|
|
36
|
-
* 历史少一截 / 列表少一行,不开任何门;计数单位 = 一条被跳过的记录,逐命中探针行,warn 每进程一次)
|
|
37
|
-
*
|
|
62
|
+
* 历史少一截 / 列表少一行,不开任何门;计数单位 = 一条被跳过的记录,逐命中探针行,warn 每进程一次),外加一条作用域腿:一次性迁移 M33 的整档读作用域({@link noteReplayInArchiveRewriteRead});
|
|
63
|
+
* 作用域外那条腿是空操作。用户面的丢失账不经这只座:core 的 `FileSessionRepo` 把回放的计数记在它交出的存储上(`getReplayLoss`),引擎在 run 里读它说
|
|
64
|
+
* `session.lineage_rerooted`(core 7.62.0 [ref];本仓 7.104.0–7.143.0 在这里另铸 `session.records_skipped`,7.144.0 退役 —— 一个事实一个来源)。
|
|
38
65
|
*/
|
|
39
66
|
export declare function createLocalSessionRepo(root: string): FileSessionRepo;
|
|
40
67
|
/** 快照目录名(数据根下;本店自有的目录与格式)。常设拒启臂 {@link assertNoArchiveSnapshots} 读它,所以它活得比 M33 的码久。 */
|
|
@@ -101,11 +128,6 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
101
128
|
olderThanMs?: number;
|
|
102
129
|
scope?: string;
|
|
103
130
|
}) => Promise<PlacedSessionRow[]>;
|
|
104
|
-
/**
|
|
105
|
-
* 委派继承律的认领口(`delegated-session-owner.ts`;停驻挂点对宿主无主子代行的认领):这只会话的那一位住客(判断时读到的化身)仍无主、且是 placed 行 ⇒ 写 `owner`,
|
|
106
|
-
* 返回是否写成。与 `placementOf` 同在或同缺(`delegatedOwnerHostOf`)。随一次性迁移 M23 的窗同删(登记表 M23 行)。
|
|
107
|
-
*/
|
|
108
|
-
readonly claimOwnerless?: OwnerlessClaim;
|
|
109
131
|
/** placement 的唯一读法(repo 的 `placementOf`;repo 不记 placement ⇒ 恒答缺席 —— 那种 repo 里没有 placed 行)。 */
|
|
110
132
|
private readonly recordedPlacementOf;
|
|
111
133
|
/** In-flight acquisitions keyed by id, so concurrent acquire(sameId) in one process share one (mirrors TiDBSessionStore). */
|
|
@@ -170,7 +192,12 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
170
192
|
* 盘上有会话而边档里没有它那一格 ⇒ 第一次读补铸一格只有化身的(属主没有事实可写)并落盘。严格读(读不出 ⇒ 整只拒,见 `SidecarMap.strict`)。
|
|
171
193
|
*/
|
|
172
194
|
private readonly meta;
|
|
173
|
-
/**
|
|
195
|
+
/**
|
|
196
|
+
* [ref] —— 本店的**严格边档清册**(今天一只:会话元数据)。唯一来源 = 构造里 `sidecar(…, strict)` 那一参:工厂造出严格读的边档时同一手登进来,
|
|
197
|
+
* 没有第二张手写名单。读者只有起服对账 {@link assertStrictSidecarsReadable}。
|
|
198
|
+
*/
|
|
199
|
+
private readonly strictSidecars;
|
|
200
|
+
/** 认领行(`session_owner_claimed`,一次性迁移 M10 的认领臂,窗口见 `plugins/one-time-migrations.ts` 登记表)的出口(本店今天只有这一行经它;委派孤行 warn 的铸点已不在 `src/`)。 */
|
|
174
201
|
private readonly log;
|
|
175
202
|
/**
|
|
176
203
|
* 按会话的写锁(表见 {@link SESSION_WRITE_LOCKS_BY_ROOT})—— 会话行两位的**每一笔**写(用户的行格写原语:判 → 边档草稿 → 落盘 → 发布;自动标题器的
|
|
@@ -204,12 +231,18 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
204
231
|
* @param dataDir the SAME data dir the file-backed repo persists under (LocalBackend passes `fileBackend.root`).
|
|
205
232
|
* When given, lastActivity is persisted to `<dataDir>/session-last-activity.json` so it survives a
|
|
206
233
|
* restart (the file-backed deployment); omit it for the in-memory deployment (nothing survives).
|
|
207
|
-
* @param opts.log the `session_owner_claimed`
|
|
208
|
-
* LOG_LEVEL; absent ⇒ an info-level JSON-line logger — a claim
|
|
234
|
+
* @param opts.log the `session_owner_claimed` line's sink (LocalBackend passes a logger at the deployment's
|
|
235
|
+
* LOG_LEVEL; absent ⇒ an info-level JSON-line logger — a claim is never silent).
|
|
209
236
|
* @param opts.durability / opts.placements the deployment's declaration ({@link LocalSessionStoreDeclaration}; see the two fields). */
|
|
210
237
|
constructor(repo: HeadReadingSessionRepo & Partial<PlacementRecordingRepo>, runs: SessionRunRollupSource & SessionRunClaimSource, dataDir?: string, opts?: {
|
|
211
238
|
log?: Pick<Logger, "info" | "warn">;
|
|
212
239
|
} & LocalSessionStoreDeclaration);
|
|
240
|
+
/**
|
|
241
|
+
* [ref] —— **起服对账的 local 腿**(SQL 列册对账的对位;唯一调用点 `LocalBackend.ensureSchema()`,排在 M33 快照消化之前 —— 消化会写会话元数据格):
|
|
242
|
+
* 严格边档清册里的每一只读一次盘(`SidecarMap.reconcile`:冷 ⇒ 载入那一读;已载入 ⇒ 重读只判);读不出的合成**一只** {@link LocalDataRootSchemaMismatchError}
|
|
243
|
+
* (`boot` 形,逐文件一段)⇒ 拒启,开始服务之前。每一只只会以那一只拒失败(严格读的拒臂把读不出的每一形都收成它);别的故障原样上抛(响亮,不归并)。内存部署(没有数据根)⇒ 零读。
|
|
244
|
+
*/
|
|
245
|
+
assertStrictSidecarsReadable(): Promise<void>;
|
|
213
246
|
/**
|
|
214
247
|
* 这一格此刻是不是 `forking`(fork 创建的格 {@link isForkCreatedCell} ∧ 存档不在)。存档在不在只问 repo 的唯一探针 {@link probeArchive} 的「不在」—— **只问有没有**:存档在而用不了(会话头读不出 / 截断)
|
|
215
248
|
* 是「在」,内容读不读得出归真正读内容的那一读(门之后、体之前读一次叶子,读不出 ⇒ `corrupt_session`)。所以带键的会话与不带键的会话在执行席上同形,坏档照样能被换装修好
|
|
@@ -258,10 +291,10 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
258
291
|
private withSessionWriteLock;
|
|
259
292
|
/**
|
|
260
293
|
* 盘上这只会话的存档此刻是什么状态(repo 的**唯一探针**;`open` 读整只存档,所以只在边档里没有那一格、或要判 `forking` 时才走到这里)。三种答案:
|
|
261
|
-
* 不在(`not_found`)· 在而读得出 · 读不出({@link UnusableArchive}:`corrupt_session`
|
|
294
|
+
* 不在(`not_found`)· 在而读得出 · 读不出({@link UnusableArchive}:`corrupt_session` = 在而内容用不了;`storage` = 读的时候抛了别的故障,I/O 级,
|
|
262
295
|
* 说不出在不在 —— 词的读法见 {@link archiveFaultWord})。探针**不吞也不改**那只故障:{@link UnusableArchive.fault} 就是抛出来的那一只,每个读者各自决定上抛还是点名 ——
|
|
263
296
|
* 要对这份存档动手的({@link existsInRepo};{@link metaOf};{@link readExecutionPlacement})一律上抛它;一次性迁移 M32 的回填({@link executionPlacementBackfill})经
|
|
264
|
-
* {@link metaRead} / {@link forkingRead} 收下它、点名跳过,不上抛。只问有没有的 {@link forkingRead} 把 `corrupt_session`
|
|
297
|
+
* {@link metaRead} / {@link forkingRead} 收下它、点名跳过,不上抛。只问有没有的 {@link forkingRead} 把 `corrupt_session` 读作「在」(存档找到了),`storage` 不替它答。
|
|
265
298
|
*/
|
|
266
299
|
private probeArchive;
|
|
267
300
|
/**
|
|
@@ -270,8 +303,10 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
270
303
|
*/
|
|
271
304
|
private placementProbe;
|
|
272
305
|
/**
|
|
273
|
-
* 改写面与执行席的读法({@link placementProbe} 的一形):placed ⇒ 那份元组;普通 ⇒ `undefined`;行头读不出(`corrupt_session`
|
|
306
|
+
* 改写面与执行席的读法({@link placementProbe} 的一形):placed ⇒ 那份元组;普通 ⇒ `undefined`;行头读不出(`corrupt_session`)⇒ `undefined` —— 没有可读的
|
|
274
307
|
* placement 记录,这一行按普通行答,内容读不读得出归真正读内容的那一读(坏档照样换得掉,与 forking 探针同一条规矩);说不出(`storage`)⇒ 抛那只故障。
|
|
308
|
+
* core 7.61.0 起 repo 的探针按行头的元组自己读、不整头解码:placed 行的属主格坏了探针照答元组(本臂不再接得住 placed 行)—— 探针拒只剩「行头记录整个读不出」,
|
|
309
|
+
* 那时没有任何元组可守(`test/local-session-placement.test.ts` 的 [ref] 三格)。
|
|
275
310
|
*/
|
|
276
311
|
private placedRecordOf;
|
|
277
312
|
/** {@link placedRecordOf} 的是非形。 */
|
|
@@ -288,7 +323,8 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
288
323
|
private metaOf;
|
|
289
324
|
/**
|
|
290
325
|
* {@link metaOf} 的本体(三态,不抛「用不了」):边档里有那一格 ⇒ 读它;没有 ⇒ 问**唯一探针** {@link probeArchive} 一次 —— 不在 ⇒ 无此会话;在而读得出 ⇒ 补铸({@link cellOfPresent});
|
|
291
|
-
* 读不出 ⇒ {@link UnusableArchive}(
|
|
326
|
+
* 读不出 ⇒ {@link UnusableArchive}(**不补铸**:不替一份读不出的存档铸化身;`corrupt_session` 的故障换成具名拒 {@link SessionOwnerUnreadableError} ——
|
|
327
|
+
* 格缺 ∧ 存档用不了 = 这只会话没有可读的属主,见 {@link ownerUnreadableArchive})。要动存档的读者({@link metaOf})把它上抛;不动存档、只要「有没有」的读者
|
|
292
328
|
* ({@link executionPlacementBackfill})原样收下。
|
|
293
329
|
*/
|
|
294
330
|
private metaRead;
|
|
@@ -304,7 +340,8 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
304
340
|
* 叶子出自会话存档(本店的**内容**;core `SessionRepo.headOf`:File repo 读日志首记录与尾、不重放 —— 存档首行的属主 / 令牌本店不读)。
|
|
305
341
|
* 存档读不出(会话头坏了、I/O 故障)⇒ 头照答行上的属主与住客,叶子那一格带着那只故障(`UnreadableLeaf`,读叶子的人收到它):只判归属的读者
|
|
306
342
|
* 不因坏存档改判,坏存档只让读内容的那一位看见(SQL 孪生的头不碰 `session_event`,同一条律;规则在 `session-incarnation.ts` 头注)。
|
|
307
|
-
* 行本身读不出(边档里还没有这一格、要从存档推出这只会话在不在,而存档读不出 —— {@link metaOf} 的探针)⇒
|
|
343
|
+
* 行本身读不出(边档里还没有这一格、要从存档推出这只会话在不在,而存档读不出 —— {@link metaOf} 的探针)⇒ 整只拒:会话头坏 / 日志结构走不通 ⇒ 具名拒
|
|
344
|
+
* {@link SessionOwnerUnreadableError}(没有可读的属主;码与句子是原故障的),说不出在不在的 I/O 级故障 ⇒ 原故障。
|
|
308
345
|
* 两样住在两只文件里 ⇒ 先读那一格、读叶子、再同步复读那一格:格没了(读间被删)或换了主 / 换了住客 ⇒ `undefined`(调用方按删处理),
|
|
309
346
|
* 所以一只头从不把这一代的属主配上另一代的叶子([ref] 六轮复审同一条)。`undefined` = 无此会话。
|
|
310
347
|
* 化身那一格经 `headFromRow` 的 `absent` 读法(边档对象形恒有令牌;读不出的边档整只严格拒在 `metaOf` 之前)。
|
|
@@ -447,17 +484,16 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
447
484
|
/** 这次 acquire 要落的 placement:只有声明了分区的店才收(未声明 ⇒ `undefined`,会话就是普通会话 —— core `SessionStore.placements` 缺席的读法)。 */
|
|
448
485
|
private declaredPlacement;
|
|
449
486
|
private load;
|
|
450
|
-
/** 为这只会话打开它自己的存档,锁内一形(调用方持这只会话的写锁,见 {@link load})
|
|
451
|
-
*
|
|
452
|
-
* 与作用域见 `session-records-skipped-notice.ts`);读不出(`corrupt_session`)照旧原样上抛,不铸。存档只读这一次:住客那一格缺(更早版本
|
|
487
|
+
/** 为这只会话打开它自己的存档,锁内一形(调用方持这只会话的写锁,见 {@link load})。回放跳过了记录 ⇒ 交出的存储带着那份丢失账(core 文件店的
|
|
488
|
+
* `getReplayLoss`,经 {@link occupiedBy} 的视图原样转发),引擎在 run 里说 `session.lineage_rerooted`;读不出(`corrupt_session`)原样上抛。存档只读这一次:住客那一格缺(更早版本
|
|
453
489
|
* 写下的会话)时,这次打开就是补铸的证明({@link handOut})。GUARD(open 腿):打开一只已存在的会话**不写属主** —— 记下的属主(含「登记为
|
|
454
490
|
* 无主」)一个字节不动,没被记下的也不在这里替它记成无主:打开不是一个属主事实(7.119.0 前这一腿把「不知道」记成进程内存里的 null,重启即失)。 */
|
|
455
491
|
private openForItselfHeld;
|
|
456
492
|
/**
|
|
457
493
|
* 带 placement 的按 id 首建(锁内一形,调用方持这只会话的写锁、已判过存档不在且不是 fork 创建的格)。写序 = **判别位先落**:先建存档(引擎文件 repo 把元组写进行头、
|
|
458
|
-
* 首写不可变),再写会话元数据那一格(
|
|
459
|
-
* `
|
|
460
|
-
*
|
|
494
|
+
* 首写不可变),再写会话元数据那一格(化身新铸;属主 = `placement.owner` 原样 —— core 7.62.0 [ref] 盒义务 7:委派树的属主是引擎判的,店只记、不推断;
|
|
495
|
+
* 唯一读法 `placedSessionOwnerOf`,在建存档之前判,坏值 ⇒ 响亮拒、一个字节不写;SQL 孪生同调)。崩在两笔之间 = 存档在而格缺 = 一只 placed 行、属主从没被记下
|
|
496
|
+
* (读作无主;本店没有回填臂 —— M23 的扫描与回写是 SQL 语句,只在两只 SQL 店跑 —— 这一行在具名树的停驻挂点被否决、响亮,匿名树 `null === null` 照放;SQL 孪生的同形由 M23 在窗内回填)。
|
|
461
497
|
* 格已在而存档不在(登记先落了格、存档还没建 —— 一只普通会话的行)⇒ `placement_refused`(SQL 孪生:登记过的行在,带 placement 的建读回缺席元组,同一判)。
|
|
462
498
|
* 建后读回准入(镜像方向):别的进程(同一数据根上的 run-local 腿)在这一刻抢先建了同 id 的存档 ⇒ repo 返回的是那一只 ⇒ 元组缺席 / 异组 ⇒ 响亮拒。
|
|
463
499
|
*/
|
|
@@ -484,11 +520,6 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
484
520
|
* 整段在这只会话的写锁里(与删除 / 换装 / 登记同一把)。
|
|
485
521
|
*/
|
|
486
522
|
release(sessionId: string): Promise<void>;
|
|
487
|
-
/**
|
|
488
|
-
* 委派继承律的认领口(锁内 CAS):placed 行、那一格的化身 = `incarnation`、属主无主(记下的 `null` 或从没被记下)⇒ 写 `owner`、返回 true;任何一条不符 ⇒ false、零写
|
|
489
|
-
* (SQL 孪生的 `CLAIM_OWNERLESS_PLACED_SQL` 同四个条件)。placement 读不出 ⇒ 上抛(不写)。
|
|
490
|
-
*/
|
|
491
|
-
private claimOwnerlessPlaced;
|
|
492
523
|
/**
|
|
493
524
|
* P0.5 variant-2 (E16, clay 2026-06-26) — enumerate the caller's sessions (CC /resume picker) from the SESSION
|
|
494
525
|
* abstraction (`repo.list()`), keyset-paginated newest-first by last activity. Session IDENTITY comes from the
|
|
@@ -617,7 +648,7 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
617
648
|
* ③ 内容,按写口的动词(唯一读法在下面这一只 switch;这里不读那一格、不判属主,只换**内容**;交给引擎仓的属主恒缺席 —— 规则一):
|
|
618
649
|
* · `create` / `judged`(导入):原地换锁里读到的那一份存档的日志(没有存档 ⇒ 导入新建一只,匿名),以锁里读到的存档头为 `expect.row`(**行代号**:
|
|
619
650
|
* 读与写之间存档换了一代 ⇒ 引擎仓拒 `conflict`;它判不出同一代里的追加)。匿名导入只换匿名存档的日志 —— 仍带属主戳的存档(更早的版本写下、一次性迁移 M33
|
|
620
|
-
* 改写不了的,或跳版升上来的)
|
|
651
|
+
* 改写不了的,或跳版升上来的)到不了这里:暂存提交在一切「谁赢」的判之前就以具名拒答了它({@link assertArchiveAnonymousHeld},唯一的一读);`create` 落在没有存档的名上,`replace` 先清存档。
|
|
621
652
|
* 读存档、校验、存档判与写在引擎仓里是一步,抛在哪一处内容都一个字节没动 ⇒ 撤回 ②({@link withdrawImportCell}:行原样 —— 净零写,SQL 孪生的事务回滚同义)再原样上抛;
|
|
622
653
|
* · `replace`(接口句「先清既有对话日志,再导入」):先清掉内容存档(不带 `expect` —— 带了就得先读它的头,读不出的存档就清不掉),再导入一只新存档。
|
|
623
654
|
* 清不可撤:清过之后导入再抛(I/O 故障),内容就没了,行留 ② 落下的 mirror(只读方向,下次推送补齐)—— 已发版本的 replace 同形(删后导入)。
|
|
@@ -663,10 +694,18 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
663
694
|
readStagedEntries(stagingId: string): Promise<SessionTreeEntry[]>;
|
|
664
695
|
/** Drop a finished/aborted local staging handle from the registry (called by the handle on commit/abort). */
|
|
665
696
|
forgetStaging(stagingId: string): void;
|
|
697
|
+
/**
|
|
698
|
+
* 带戳存档的唯一一判(调用方持这只会话的写锁;只读、零写):这份存档的头仍记着属主戳(更早的版本导入 / 换装写下的,一次性迁移 M33 改写不了的,或跳版升上来的)⇒
|
|
699
|
+
* {@link StampedSessionArchiveError}。引擎导入律不让匿名导入替换带戳存档,本店不重写、不绕 —— 只让拒带对的出路(fork 的源另有一读,同一只具名拒,见 {@link forkTo})。
|
|
700
|
+
* 这是「这份存档的日志根本换不了」的判,所以排在一切「谁赢」的判之前({@link commitStagedImport}):换不了的存档上谈不上同步冲突或载荷分叉,
|
|
701
|
+
* 那两条拒的出路(带同意重来)都是死路 —— 唯一的出路在这一句里。
|
|
702
|
+
*/
|
|
703
|
+
private assertArchiveAnonymousHeld;
|
|
666
704
|
/**
|
|
667
705
|
* {@link LocalStagingHandle.commit} 的整段 —— **一次持锁**(这只会话的写锁,{@link withSessionWriteLock}):属主 CAS(此刻的属主格 = 这次导入的门判过的那一格,
|
|
668
|
-
* 否则抛 {@link StagedCommitOwnerChangedError},一个字节不写;在判目的端日志之前判,SQL 孪生同序)→ 提交时重新分类(目的端此刻的日志 vs staged)
|
|
669
|
-
*
|
|
706
|
+
* 否则抛 {@link StagedCommitOwnerChangedError},一个字节不写;在判目的端日志之前判,SQL 孪生同序)→ 分区判 → 提交时重新分类(目的端此刻的日志 vs staged)→
|
|
707
|
+
* 这一提交要换日志(不是「id 与内容都相同」那一支)⇒ 带戳存档判({@link assertArchiveAnonymousHeld};只有 local 店有带戳存档,SQL 孪生没有这一步)→
|
|
708
|
+
* 拒未经同意的 fork / stale / 载荷分叉(带戳判在它们之前:带戳存档上日志换不了,同步冲突与载荷分叉的出路 —— 带同意重来 —— 无从谈起)→ 本处写者拒 → 换装(导入写序 ①②③,落点 `judged` = 上面这一判;化身与 fork 键按换装律沿用)或 identical 且内容相同时日志不动、
|
|
670
709
|
* 行照样落(② 那一格:重戳属主 + mirror)→ 两支同记新近(推送即活动)→ **在同一次持锁里**
|
|
671
710
|
* 读出落定的会话面交回。锁放下之前谁也换不掉这一位住客,所以交回的面就是这次提交落下的那一位(SQL 孪生在提交事务里读同一行,同义);
|
|
672
711
|
* 提交之后按住客条件写的重放(会话行两位)只认这只面,不再重读。改属主的每一条写腿(登记 / 删除 / 导入 / 换装)都在同一把锁里,所以锁内这一读到锁放下都成立。
|
|
@@ -3,17 +3,17 @@ import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readdirSync, rea
|
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
4
|
import { isDeepStrictEqual } from "node:util";
|
|
5
5
|
import { dirname, join, resolve } from "node:path";
|
|
6
|
-
import { assertFirstOwnerWritable, forkOwnerOf, importerOwnerOf } from "./session-ownership.js";
|
|
7
|
-
import { StampedSessionArchiveError, sessionUnreadableOf } from "../session-error-wire.js";
|
|
6
|
+
import { assertFirstOwnerWritable, forkOwnerOf, importerOwnerOf, placedSessionOwnerOf } from "./session-ownership.js";
|
|
7
|
+
import { SessionOwnerUnreadableError, StampedSessionArchiveError, sessionUnreadableOf } from "../session-error-wire.js";
|
|
8
8
|
import { FileSessionRepo, SessionError, StoredSession, admitSessionPlacement, atomicWriteFile, sessionLogMarkOf, settledForkOptions, uuidv7, validateEntriesForImport } from "@sema-agent/core";
|
|
9
|
-
import { createPlacedSessionRow, sourceFacesVia } from "../delegated-session-owner.js";
|
|
10
9
|
import { placementCapabilityOf } from "./session-placement.js";
|
|
11
10
|
import { z } from "zod";
|
|
12
11
|
import { contentForkRelation, fastForwardSharedContentDiverged, identicalIdsAlsoIdenticalContent } from "../session-sync-content.js";
|
|
13
12
|
import { StoreBootRefusalError } from "./store-contracts.js";
|
|
14
13
|
import { judgeSessionRowWrite, sessionRowTagKey, settleSessionRowWrite, } from "../session-row.js";
|
|
15
14
|
import { quarantineUnreadableSidecar } from "./local-sidecar-quarantine.js";
|
|
16
|
-
import { createZodColumnReader, decodeJsonColumn, readStringRecordColumn, SqlJsonColumnError } from "./sql-json-column.js";
|
|
15
|
+
import { createZodColumnReader, decodeJsonColumn, isJsonColumnBodyProblem, readStringRecordColumn, SqlJsonColumnError } from "./sql-json-column.js";
|
|
16
|
+
import { SCHEMA_MISMATCH_ERROR_CODE } from "./schema-consistency.js";
|
|
17
17
|
import { ONE_TIME_MIGRATIONS, migrationWindowOpen } from "./one-time-migrations.js";
|
|
18
18
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
19
19
|
import { createLogger } from "../observability/logger.js";
|
|
@@ -24,7 +24,6 @@ import { SessionForkInProgressError, SessionWriterPresentError, WRITABLE, assert
|
|
|
24
24
|
import { classifyForkTarget, forkByFreshId } from "./session-fork.js";
|
|
25
25
|
import { decodeInstructionSource, encodeInstructionSource, sameInstructionSourceBaseline } from "../instruction-source-notice.js";
|
|
26
26
|
import { decodeSessionWorkspaceCell, encodeSessionWorkspaceCell } from "./session-workspace-cell.js";
|
|
27
|
-
import { noteReplayInSessionOpenScope, openSessionForItself } from "../session-records-skipped-notice.js";
|
|
28
27
|
import { createCorruptReadFailOpenSeat } from "../observability/corrupt-read-seat.js";
|
|
29
28
|
import { buildObjectivePreview, isAttachmentFrame } from "../user-authored-text.js";
|
|
30
29
|
import { classifySyncRelationshipByIds, ownerCellIs, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, } from "../session-sync-kernel.js";
|
|
@@ -35,8 +34,8 @@ function archiveFaultWord(code) {
|
|
|
35
34
|
case "not_found":
|
|
36
35
|
return "absent";
|
|
37
36
|
case "corrupt_session":
|
|
38
|
-
case "invalid_session":
|
|
39
37
|
return code;
|
|
38
|
+
case "invalid_session":
|
|
40
39
|
case "invalid_entry":
|
|
41
40
|
case "invalid_fork_target":
|
|
42
41
|
case "storage":
|
|
@@ -54,6 +53,12 @@ function unusableArchiveOf(word, fault) {
|
|
|
54
53
|
const code = fault instanceof SessionError ? undefined : fault?.code;
|
|
55
54
|
return { state: "unusable", word, fault, ...(word === "storage" && typeof code === "string" ? { errno: code } : {}) };
|
|
56
55
|
}
|
|
56
|
+
function ownerUnreadableArchive(probe) {
|
|
57
|
+
const { fault } = probe;
|
|
58
|
+
if (probe.word === "storage" || !(fault instanceof SessionError))
|
|
59
|
+
return probe;
|
|
60
|
+
return { ...probe, fault: new SessionOwnerUnreadableError(fault) };
|
|
61
|
+
}
|
|
57
62
|
function importRowRefused(sessionId, landing) {
|
|
58
63
|
return new SessionError("conflict", landing === "create"
|
|
59
64
|
? `session ${sessionId} already exists — importEntries only creates a session (replaceEntries rewrites one); nothing was written`
|
|
@@ -148,6 +153,32 @@ function ownerClaimWindowOpen() {
|
|
|
148
153
|
}
|
|
149
154
|
const SIDECAR_MAP_STATE_BY_PATH = new Map();
|
|
150
155
|
const SESSION_WRITE_LOCKS_BY_ROOT = new Map();
|
|
156
|
+
const quotedFieldNames = (names) => `${names.slice(0, 8).map((name) => JSON.stringify(name.slice(0, 64))).join(", ")}${names.length > 8 ? `, … (${names.length} in all)` : ""}`;
|
|
157
|
+
function describeLocalDataRootMismatch(findings, when) {
|
|
158
|
+
const lines = [
|
|
159
|
+
when === "boot"
|
|
160
|
+
? `${SCHEMA_MISMATCH_ERROR_CODE}: the local data root does not match this build's shape — refusing to start.`
|
|
161
|
+
: `${SCHEMA_MISMATCH_ERROR_CODE}: the local data root does not match this build's shape — refusing to read or write the file(s) named below until this process is restarted.`,
|
|
162
|
+
];
|
|
163
|
+
for (const f of findings) {
|
|
164
|
+
lines.push(f.unknownFields.length > 0
|
|
165
|
+
? ` ${f.label} at ${f.path}: UNKNOWN field(s) ${quotedFieldNames(f.unknownFields)} — the data root is NEWER than this build (a newer server engine wrote it).`
|
|
166
|
+
: ` ${f.label} at ${f.path}: unreadable (${f.problem}) — this build cannot read it, and an unreadable file is treated as a mismatch, not as empty.`);
|
|
167
|
+
}
|
|
168
|
+
lines.push("This build never reads such a file as empty and never writes over it: either would silently change what the file records.", "", " Ways out (these three and no others):", " (1) the data root is NEWER than this build (unknown fields): deploy the build that wrote it;", " (2) roll back: restore the data-root backup taken before the upgrade, then start this build — also the way out when the file's bytes are damaged (not-json / malformed);", " (3) the file could not be read at all (an I/O error such as EACCES): repair access to it (permissions, mount, disk) and restart — nothing in the file needs changing.", "The check behind this refusal only reads: the file was left in place and nothing in the data root was changed.");
|
|
169
|
+
return lines.join("\n");
|
|
170
|
+
}
|
|
171
|
+
export class LocalDataRootSchemaMismatchError extends StoreBootRefusalError {
|
|
172
|
+
findings;
|
|
173
|
+
when;
|
|
174
|
+
code = SCHEMA_MISMATCH_ERROR_CODE;
|
|
175
|
+
constructor(findings, when) {
|
|
176
|
+
super(describeLocalDataRootMismatch(findings, when));
|
|
177
|
+
this.findings = findings;
|
|
178
|
+
this.when = when;
|
|
179
|
+
this.name = "LocalDataRootSchemaMismatchError";
|
|
180
|
+
}
|
|
181
|
+
}
|
|
151
182
|
class SidecarMap {
|
|
152
183
|
path;
|
|
153
184
|
label;
|
|
@@ -174,32 +205,68 @@ class SidecarMap {
|
|
|
174
205
|
if (!path)
|
|
175
206
|
return Promise.resolve();
|
|
176
207
|
const st = this.state;
|
|
208
|
+
if (st.refused !== undefined)
|
|
209
|
+
return Promise.reject(st.refused);
|
|
177
210
|
if (!st.loaded) {
|
|
178
211
|
st.loaded = (async () => {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
212
|
+
const disk = await this.readDisk(path);
|
|
213
|
+
if (disk.state === "absent")
|
|
214
|
+
return;
|
|
215
|
+
if (disk.state === "read") {
|
|
216
|
+
for (const [id, v] of Object.entries(disk.entries)) {
|
|
182
217
|
if (!st.entries.has(id))
|
|
183
218
|
st.entries.set(id, v);
|
|
184
219
|
}
|
|
220
|
+
return;
|
|
185
221
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (!this.strict) {
|
|
190
|
-
this.onUnreadable(path, e);
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
const kind = e instanceof SqlJsonColumnError ? e.problem : (e?.code ?? "unreadable");
|
|
194
|
-
throw new Error(`${this.label} is unreadable (${kind}) — refusing to read or write session owners and incarnations until it is repaired: ` +
|
|
195
|
-
`reading it as empty would turn every session ownerless, and a replacement incarnation token for an existing session would ` +
|
|
196
|
-
`make it a different occupant (its memory opt-out would stop binding it); a write would overwrite the unreadable bytes. ` +
|
|
197
|
-
`The file is left in place at ${path}.`);
|
|
222
|
+
if (!this.strict) {
|
|
223
|
+
this.onUnreadable(path, disk.fault);
|
|
224
|
+
return;
|
|
198
225
|
}
|
|
226
|
+
st.loaded = undefined;
|
|
227
|
+
throw this.refuse(path, disk);
|
|
199
228
|
})();
|
|
200
229
|
}
|
|
201
230
|
return st.loaded;
|
|
202
231
|
}
|
|
232
|
+
async reconcile() {
|
|
233
|
+
const path = this.path;
|
|
234
|
+
if (!path)
|
|
235
|
+
return;
|
|
236
|
+
const st = this.state;
|
|
237
|
+
if (st.refused !== undefined)
|
|
238
|
+
throw st.refused;
|
|
239
|
+
if (!st.loaded)
|
|
240
|
+
return this.ensureLoaded();
|
|
241
|
+
await st.loaded;
|
|
242
|
+
const disk = await this.readDisk(path);
|
|
243
|
+
if (disk.state === "unreadable")
|
|
244
|
+
throw this.refuse(path, disk);
|
|
245
|
+
}
|
|
246
|
+
async readDisk(path) {
|
|
247
|
+
let text;
|
|
248
|
+
try {
|
|
249
|
+
text = await readFile(path, "utf8");
|
|
250
|
+
}
|
|
251
|
+
catch (e) {
|
|
252
|
+
return e?.code === "ENOENT" ? { state: "absent" } : { state: "unreadable", fault: e };
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
return { state: "read", entries: decodeJsonColumn(text, this.read, this.label) };
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
258
|
+
return { state: "unreadable", fault: e };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
refuse(path, disk) {
|
|
262
|
+
const shape = isJsonColumnBodyProblem(disk.fault);
|
|
263
|
+
const problem = disk.fault instanceof SqlJsonColumnError ? disk.fault.problem : (disk.fault?.code ?? "unreadable");
|
|
264
|
+
const unknownFields = disk.fault instanceof SqlJsonColumnError ? disk.fault.unknownKeys : [];
|
|
265
|
+
const refusal = new LocalDataRootSchemaMismatchError([{ path, label: this.label, problem, unknownFields }], "serving");
|
|
266
|
+
if (shape)
|
|
267
|
+
this.state.refused = refusal;
|
|
268
|
+
return refusal;
|
|
269
|
+
}
|
|
203
270
|
persist() {
|
|
204
271
|
return this.commit(() => true);
|
|
205
272
|
}
|
|
@@ -216,11 +283,22 @@ class SidecarMap {
|
|
|
216
283
|
const path = this.path;
|
|
217
284
|
const st = this.state;
|
|
218
285
|
const link = st.writeChain.then(async () => {
|
|
286
|
+
if (st.refused !== undefined)
|
|
287
|
+
throw st.refused;
|
|
219
288
|
const base = new Map(st.entries);
|
|
220
289
|
const draft = new Map(base);
|
|
221
290
|
if (!change(draft))
|
|
222
291
|
return true;
|
|
223
292
|
if (path) {
|
|
293
|
+
if (this.strict) {
|
|
294
|
+
const disk = await this.readDisk(path);
|
|
295
|
+
if (disk.state === "unreadable") {
|
|
296
|
+
if (isJsonColumnBodyProblem(disk.fault))
|
|
297
|
+
throw this.refuse(path, disk);
|
|
298
|
+
this.onWriteFailed(disk.fault);
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
224
302
|
try {
|
|
225
303
|
const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
226
304
|
await writeFile(tmp, JSON.stringify(Object.fromEntries(draft)), "utf8");
|
|
@@ -241,7 +319,7 @@ class SidecarMap {
|
|
|
241
319
|
}
|
|
242
320
|
return true;
|
|
243
321
|
});
|
|
244
|
-
st.writeChain = link.then(() => undefined);
|
|
322
|
+
st.writeChain = link.then(() => undefined, () => undefined);
|
|
245
323
|
return link;
|
|
246
324
|
}
|
|
247
325
|
}
|
|
@@ -255,10 +333,7 @@ function noteReplayInArchiveRewriteRead(info) {
|
|
|
255
333
|
}
|
|
256
334
|
export function createLocalSessionRepo(root) {
|
|
257
335
|
return new FileSessionRepo(root, {
|
|
258
|
-
onCorruptRead: createCorruptReadFailOpenSeat("server.session-repo.corrupt-record-skipped",
|
|
259
|
-
noteReplayInSessionOpenScope(info);
|
|
260
|
-
noteReplayInArchiveRewriteRead(info);
|
|
261
|
-
}),
|
|
336
|
+
onCorruptRead: createCorruptReadFailOpenSeat("server.session-repo.corrupt-record-skipped", noteReplayInArchiveRewriteRead),
|
|
262
337
|
});
|
|
263
338
|
}
|
|
264
339
|
function occupiedBy(session, row) {
|
|
@@ -351,7 +426,6 @@ export class LocalSessionStore {
|
|
|
351
426
|
placements;
|
|
352
427
|
placementOf;
|
|
353
428
|
listPlaced;
|
|
354
|
-
claimOwnerless;
|
|
355
429
|
recordedPlacementOf;
|
|
356
430
|
pending = new Map();
|
|
357
431
|
rowsDropped = new Set();
|
|
@@ -369,6 +443,7 @@ export class LocalSessionStore {
|
|
|
369
443
|
instructionSources;
|
|
370
444
|
workspaces;
|
|
371
445
|
meta;
|
|
446
|
+
strictSidecars = [];
|
|
372
447
|
log;
|
|
373
448
|
sessionWriteLocks;
|
|
374
449
|
executionPlacement;
|
|
@@ -389,7 +464,6 @@ export class LocalSessionStore {
|
|
|
389
464
|
this.recordedPlacementOf = (sessionId) => repoPlacementOf.call(repo, sessionId);
|
|
390
465
|
this.placementOf = this.recordedPlacementOf;
|
|
391
466
|
this.listPlaced = (kind, o) => repoListPlaced.call(repo, kind, o);
|
|
392
|
-
this.claimOwnerless = (sessionId, incarnation, owner) => this.claimOwnerlessPlaced(sessionId, incarnation, owner);
|
|
393
467
|
}
|
|
394
468
|
else {
|
|
395
469
|
this.recordedPlacementOf = async () => undefined;
|
|
@@ -409,7 +483,12 @@ export class LocalSessionStore {
|
|
|
409
483
|
}
|
|
410
484
|
this.durability = opts.durability;
|
|
411
485
|
}
|
|
412
|
-
const sidecar = (file, which, read, strict = false) =>
|
|
486
|
+
const sidecar = (file, which, read, strict = false) => {
|
|
487
|
+
const map = new SidecarMap(dataDir ? resolve(join(dataDir, file)) : undefined, `${file} (the local ${which} sidecar)`, read, (path, e) => this.refuseSidecar(path, `LocalSessionStore ${which} sidecar`, e, which), (e) => this.warnSidecar("write", e, which), strict);
|
|
488
|
+
if (strict)
|
|
489
|
+
this.strictSidecars.push(map);
|
|
490
|
+
return map;
|
|
491
|
+
};
|
|
413
492
|
this.titles = sidecar("session-titles.json", "title", readStringRecordColumn);
|
|
414
493
|
this.pinned = sidecar("session-pinned.json", "pinned", readPinnedSidecar);
|
|
415
494
|
this.instructionSources = sidecar("session-instruction-sources.json", "instruction-source", readStringRecordColumn);
|
|
@@ -430,6 +509,19 @@ export class LocalSessionStore {
|
|
|
430
509
|
place: (sessionId, occupant, cell) => this.placeExecution(sessionId, occupant, cell),
|
|
431
510
|
};
|
|
432
511
|
}
|
|
512
|
+
async assertStrictSidecarsReadable() {
|
|
513
|
+
const loads = await Promise.allSettled(this.strictSidecars.map((map) => map.reconcile()));
|
|
514
|
+
const findings = [];
|
|
515
|
+
for (const load of loads) {
|
|
516
|
+
if (load.status === "fulfilled")
|
|
517
|
+
continue;
|
|
518
|
+
if (!(load.reason instanceof LocalDataRootSchemaMismatchError))
|
|
519
|
+
throw load.reason;
|
|
520
|
+
findings.push(...load.reason.findings);
|
|
521
|
+
}
|
|
522
|
+
if (findings.length > 0)
|
|
523
|
+
throw new LocalDataRootSchemaMismatchError(findings, "boot");
|
|
524
|
+
}
|
|
433
525
|
async forkingRead(sessionId, meta) {
|
|
434
526
|
if (!isForkCreatedCell(meta))
|
|
435
527
|
return false;
|
|
@@ -597,7 +689,7 @@ export class LocalSessionStore {
|
|
|
597
689
|
return { state: "found", meta: readSessionMetaCell(cell) };
|
|
598
690
|
const probe = await this.probeArchive(sessionId);
|
|
599
691
|
if (probe.state === "unusable")
|
|
600
|
-
return probe;
|
|
692
|
+
return ownerUnreadableArchive(probe);
|
|
601
693
|
if (probe.state === "absent")
|
|
602
694
|
return { state: "absent" };
|
|
603
695
|
const meta = await this.cellOfPresent(sessionId);
|
|
@@ -922,7 +1014,7 @@ export class LocalSessionStore {
|
|
|
922
1014
|
}
|
|
923
1015
|
async openForItselfHeld(sessionId, opts) {
|
|
924
1016
|
await this.meta.ensureLoaded();
|
|
925
|
-
const session = await
|
|
1017
|
+
const session = await this.repo.open({ id: sessionId, createdAt: "" });
|
|
926
1018
|
admitSessionPlacement(sessionId, (await session.getMetadata()).placement, opts ?? {}, placementCapabilityOf(this));
|
|
927
1019
|
return this.handOut(sessionId, session);
|
|
928
1020
|
}
|
|
@@ -930,15 +1022,9 @@ export class LocalSessionStore {
|
|
|
930
1022
|
if (this.metaCellOf(sessionId) !== undefined) {
|
|
931
1023
|
throw new SessionError("placement_refused", `session ${sessionId} is already registered as an ordinary session — refusing to create a subagent transcript on its id`);
|
|
932
1024
|
}
|
|
933
|
-
|
|
934
|
-
await
|
|
935
|
-
|
|
936
|
-
insert: async (owner) => {
|
|
937
|
-
session = await this.repo.create({ id: sessionId, placement });
|
|
938
|
-
await this.writeMeta(sessionId, (before) => (before === undefined ? { incarnation: mintSessionIncarnation(), owner, createdAt: rowBornNow() } : undefined));
|
|
939
|
-
},
|
|
940
|
-
readOwner: async () => (await this.headOf(sessionId))?.owner,
|
|
941
|
-
}, this.log);
|
|
1025
|
+
const owner = placedSessionOwnerOf(sessionId, placement);
|
|
1026
|
+
const session = await this.repo.create({ id: sessionId, placement });
|
|
1027
|
+
await this.writeMeta(sessionId, (before) => (before === undefined ? { incarnation: mintSessionIncarnation(), owner, createdAt: rowBornNow() } : undefined));
|
|
942
1028
|
admitSessionPlacement(sessionId, (await session.getMetadata()).placement, opts, placementCapabilityOf(this));
|
|
943
1029
|
return this.handOut(sessionId, session);
|
|
944
1030
|
}
|
|
@@ -980,20 +1066,6 @@ export class LocalSessionStore {
|
|
|
980
1066
|
await this.repo.delete({ id: sessionId, createdAt: "" });
|
|
981
1067
|
});
|
|
982
1068
|
}
|
|
983
|
-
async claimOwnerlessPlaced(sessionId, incarnation, owner) {
|
|
984
|
-
return this.withSessionWriteLock(sessionId, async () => {
|
|
985
|
-
if ((await this.placedRecordOrThrow(sessionId)) === undefined)
|
|
986
|
-
return false;
|
|
987
|
-
return this.writeMeta(sessionId, (before) => {
|
|
988
|
-
if (before === undefined)
|
|
989
|
-
return undefined;
|
|
990
|
-
const cell = readSessionMetaCell(before);
|
|
991
|
-
if (cell.incarnation !== incarnation || (cell.owner ?? null) !== null)
|
|
992
|
-
return undefined;
|
|
993
|
-
return cellWith(before, { owner });
|
|
994
|
-
});
|
|
995
|
-
});
|
|
996
|
-
}
|
|
997
1069
|
async listSessions(opts) {
|
|
998
1070
|
await this.loadRowSidecars();
|
|
999
1071
|
const rows = [];
|
|
@@ -1189,8 +1261,6 @@ export class LocalSessionStore {
|
|
|
1189
1261
|
case "judged":
|
|
1190
1262
|
try {
|
|
1191
1263
|
const archive = (await this.archiveHeadOf(sessionId)) ?? null;
|
|
1192
|
-
if ((archive?.owner ?? null) !== null)
|
|
1193
|
-
throw new StampedSessionArchiveError(sessionId);
|
|
1194
1264
|
await this.repo.importEntries(sessionId, undefined, entries, { expect: { row: archive } });
|
|
1195
1265
|
}
|
|
1196
1266
|
catch (err) {
|
|
@@ -1268,6 +1338,10 @@ export class LocalSessionStore {
|
|
|
1268
1338
|
forgetStaging(stagingId) {
|
|
1269
1339
|
this.stagings.delete(stagingId);
|
|
1270
1340
|
}
|
|
1341
|
+
async assertArchiveAnonymousHeld(sessionId) {
|
|
1342
|
+
if (((await this.archiveHeadOf(sessionId))?.owner ?? null) !== null)
|
|
1343
|
+
throw new StampedSessionArchiveError(sessionId);
|
|
1344
|
+
}
|
|
1271
1345
|
async commitStagedImport(sessionId, owner, staged, opts) {
|
|
1272
1346
|
return this.withImportLock(sessionId, owner, async (importer) => {
|
|
1273
1347
|
const current = await this.metaOf(sessionId);
|
|
@@ -1278,6 +1352,9 @@ export class LocalSessionStore {
|
|
|
1278
1352
|
await this.assertNotPlacedHeld(sessionId);
|
|
1279
1353
|
const dstEntries = await this.exportEntries(sessionId);
|
|
1280
1354
|
const rel = classifySyncRelationshipByIds(staged.map((e) => e.id), dstEntries === null ? null : dstEntries.map((e) => e.id));
|
|
1355
|
+
const contentEqual = rel.relation === "identical" && identicalIdsAlsoIdenticalContent(staged, dstEntries);
|
|
1356
|
+
if (!contentEqual)
|
|
1357
|
+
await this.assertArchiveAnonymousHeld(sessionId);
|
|
1281
1358
|
if ((rel.relation === "fork" || rel.relation === "stale") && opts.resolution !== "overwrite-dst") {
|
|
1282
1359
|
throw new SyncConflictError(sessionId, rel);
|
|
1283
1360
|
}
|
|
@@ -1287,7 +1364,6 @@ export class LocalSessionStore {
|
|
|
1287
1364
|
if (diverged)
|
|
1288
1365
|
throw new SyncConflictError(sessionId, contentForkRelation(diverged, rel.newEntryIds));
|
|
1289
1366
|
}
|
|
1290
|
-
const contentEqual = rel.relation === "identical" && identicalIdsAlsoIdenticalContent(staged, dstEntries);
|
|
1291
1367
|
if (current?.execution?.role === "writer")
|
|
1292
1368
|
throw new SessionWriterPresentError(sessionId, current.execution.lane);
|
|
1293
1369
|
if (rel.relation !== "identical" || !contentEqual) {
|
|
@@ -24,12 +24,14 @@
|
|
|
24
24
|
*
|
|
25
25
|
* ## Cost: one linear walk, the engine's
|
|
26
26
|
* The branch is core's own `getPathToRoot` over the whole log (the walk every engine read of a local session makes, with its stop
|
|
27
|
-
* rules: an entry without a parent ends it, a parent the log does not hold is `invalid_session
|
|
27
|
+
* rules: an entry without a parent ends it, a parent the log does not hold is `corrupt_session` — `invalid_session` up to core 7.61.x —, an unknown
|
|
28
|
+
* leaf is `not_found`). A missing parent whose replay dropped records is the one break a RUN does not refuse since core 7.62.0 ([ref]: it re-roots
|
|
29
|
+
* and says `session.lineage_rerooted`); this view is not a run and takes the walk's refusal as is.
|
|
28
30
|
* Since core 7.54.0 ([ref]) that walk is linear; before, it built the path with `unshift` (quadratic — seconds on a long session, on
|
|
29
31
|
* the event loop) and this module walked the `parentId` chain itself. The second walker is retired: the view reads the branch the
|
|
30
32
|
* engine reads, by the engine's reading. Every later read of the window goes through the same walk, bounded by the floor.
|
|
31
33
|
* A parent CYCLE (a corrupted log; the engine never writes one) is refused by that same walk: since core 7.54.1 it carries one
|
|
32
|
-
* `ParentChainGuard`, and the entry it stands on twice ends it with `invalid_session` naming that entry — `wake` refuses a looped log
|
|
34
|
+
* `ParentChainGuard`, and the entry it stands on twice ends it with `corrupt_session` (`invalid_session` up to core 7.61.x) naming that entry — `wake` refuses a looped log
|
|
33
35
|
* the way it refuses one whose parent is missing. Core 7.54.0's walk had no guard (a looped log spun it forever on the event loop,
|
|
34
36
|
* and the view's wake with it). The retired walker's own cycle refusal went with it: the engine's guard is the one refusal now, shared
|
|
35
37
|
* by `getPathToRoot` and the run's resume ancestor scan (the two walks core 7.54.1 guards; this module has no walker of its own left).
|