@sema-agent/server 7.150.0 → 7.151.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 +185 -0
- package/USAGE.md +53 -25
- package/deploy/sema-up/README.md +7 -1
- package/deploy/sema-up/chart/Chart.yaml +1 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +5 -0
- package/deploy/sema-up/chart/templates/_helpers.tpl +15 -16
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -3
- package/deploy/sema-up/chart/templates/server.yaml +5 -3
- package/deploy/sema-up/chart/values-full-single.yaml +3 -1
- package/deploy/sema-up/chart/values.yaml +8 -1
- package/deploy/sema-up/cluster-up.sh +1 -0
- package/deploy/sema-up/kube-up.sh +1 -1
- package/deploy/sema-up/lib/flagshape.sh +1 -1
- package/deploy/sema-up/lib/helm-values-cluster.py +2 -0
- package/deploy/sema-up/lib/helm-values-kube.py +2 -0
- package/dist/adoption/import.js +3 -3
- package/dist/approval-ask-machine.d.ts +1 -1
- package/dist/approval-card.d.ts +2 -2
- package/dist/approval-gate-currency.d.ts +1 -1
- package/dist/auth-bridge.d.ts +20 -0
- package/dist/auth-bridge.js +9 -0
- package/dist/background-session-reap.d.ts +1 -1
- package/dist/bake-runner/main.d.ts +4 -2
- package/dist/bind-host.d.ts +2 -3
- package/dist/bind-host.js +2 -4
- package/dist/boot/budget-tracing.js +1 -1
- package/dist/boot/engine-lease.d.ts +5 -0
- package/dist/boot/engine-lease.js +1 -0
- package/dist/boot/execution-env.d.ts +8 -14
- package/dist/boot/execution-env.js +54 -17
- package/dist/boot/execution-lane.js +32 -5
- package/dist/boot/late-binds.d.ts +8 -4
- package/dist/boot/late-binds.js +7 -1
- package/dist/boot/memory-consolidation.d.ts +3 -2
- package/dist/boot/memory-consolidation.js +6 -6
- package/dist/boot/reapers.d.ts +5 -0
- package/dist/boot/reapers.js +12 -1
- package/dist/boot/resolve-spec.d.ts +3 -3
- package/dist/boot/resolve-spec.js +31 -22
- package/dist/boot/runner-deps.d.ts +9 -2
- package/dist/boot/runner-deps.js +3 -3
- package/dist/boot/session-age-out.d.ts +36 -0
- package/dist/boot/session-age-out.js +101 -0
- package/dist/boot/session-faces.d.ts +5 -14
- package/dist/boot/session-faces.js +9 -5
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-05-execution-env.d.ts +5 -3
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.d.ts +7 -4
- package/dist/boot/stage-06-runners.js +15 -6
- package/dist/boot/stage-07-capability-layer.d.ts +7 -4
- package/dist/boot/stage-07-capability-layer.js +2 -1
- package/dist/boot/stage-08-reapers.d.ts +7 -4
- package/dist/boot/stage-08-reapers.js +4 -2
- package/dist/boot/stage-09-leader.d.ts +11 -5
- package/dist/boot/stage-09-leader.js +12 -3
- package/dist/boot/stage-10-http-server.d.ts +11 -5
- package/dist/boot/stage-10-http-server.js +3 -6
- package/dist/boot/web-admin.js +3 -1
- package/dist/boot/workflow-orchestration.d.ts +7 -2
- package/dist/boot/workflow-orchestration.js +24 -7
- package/dist/bounded-rotation.d.ts +29 -0
- package/dist/bounded-rotation.js +24 -0
- package/dist/capability-facts.d.ts +22 -13
- package/dist/capability-facts.js +28 -18
- package/dist/center-credential.d.ts +25 -6
- package/dist/center-credential.js +25 -5
- package/dist/config-catalog.d.ts +26 -7
- package/dist/config-catalog.js +14 -11
- package/dist/config-invariants.d.ts +2 -1
- package/dist/config-invariants.js +4 -4
- package/dist/config-types.d.ts +10 -4
- package/dist/config.d.ts +51 -2
- package/dist/config.js +35 -24
- package/dist/deployment-shape.d.ts +9 -1
- package/dist/deployment-shape.js +2 -1
- package/dist/device-protocol.d.ts +5 -4
- package/dist/device-protocol.js +1 -1
- package/dist/device-ws-hub.js +3 -2
- package/dist/device-ws-protocol.d.ts +63 -10
- package/dist/device-ws-protocol.js +56 -25
- package/dist/engine-identity.d.ts +19 -7
- package/dist/engine-identity.js +10 -2
- package/dist/entry-mode-clamp-notice.d.ts +2 -1
- package/dist/execution-env-refusal.d.ts +23 -0
- package/dist/execution-env-refusal.js +5 -0
- package/dist/execution-lane-caps.d.ts +3 -1
- package/dist/field-conflict.d.ts +1 -1
- package/dist/file-history-capture.d.ts +11 -4
- package/dist/file-history-capture.js +9 -6
- package/dist/file-history-disclosure.d.ts +7 -5
- package/dist/file-history-disclosure.js +7 -5
- package/dist/fleet/drain-entry.d.ts +4 -0
- package/dist/hooks/hook-runner.js +2 -2
- package/dist/host-lsp-manager.d.ts +2 -2
- package/dist/http/admission.js +17 -2
- package/dist/http/approval-sweeps.js +5 -5
- package/dist/http/checkpoint-ctx.d.ts +2 -0
- package/dist/http/checkpoint-ctx.js +1 -0
- package/dist/http/dispatch.d.ts +2 -1
- package/dist/http/dispatch.js +34 -27
- package/dist/http/durable-run-leg.js +16 -16
- package/dist/http/health-self-report.d.ts +9 -1
- package/dist/http/health-self-report.js +2 -0
- package/dist/http/owner-access.d.ts +31 -11
- package/dist/http/owner-access.js +37 -6
- package/dist/http/resume-ctx-read.js +25 -3
- package/dist/http/resume-legs.d.ts +2 -1
- package/dist/http/resume-legs.js +259 -79
- package/dist/http/route-ctx.d.ts +18 -8
- package/dist/http/route-table.d.ts +1 -1
- package/dist/http/routes/admin-memory-optout.d.ts +1 -1
- package/dist/http/routes/agents-roster.js +2 -3
- package/dist/http/routes/approvals-assistant.js +4 -3
- package/dist/http/routes/background.d.ts +3 -3
- package/dist/http/routes/capabilities.js +8 -5
- package/dist/http/routes/fleet.js +10 -8
- package/dist/http/routes/projects.d.ts +3 -2
- package/dist/http/routes/projects.js +4 -5
- package/dist/http/routes/runs.js +102 -36
- package/dist/http/routes/session-sync.d.ts +4 -3
- package/dist/http/routes/session-sync.js +172 -77
- package/dist/http/routes/sessions-list.js +13 -7
- package/dist/http/routes/sessions.js +10 -6
- package/dist/http/routes/tasks.js +30 -16
- package/dist/http/routes/trace-usage.js +2 -2
- package/dist/http/server.d.ts +21 -27
- package/dist/http/server.js +2 -2
- package/dist/http/submit-ctx.js +5 -4
- package/dist/http/wire-types.d.ts +14 -4
- package/dist/identity-source.d.ts +20 -3
- package/dist/identity-source.js +14 -0
- package/dist/index.d.ts +4 -0
- package/dist/lane-home-probe.d.ts +3 -7
- package/dist/lane-home-probe.js +0 -1
- package/dist/leader/leader.d.ts +2 -1
- package/dist/leg-facts.d.ts +13 -5
- package/dist/leg-facts.js +2 -1
- package/dist/memory-scope.d.ts +5 -4
- package/dist/memory-scope.js +4 -4
- package/dist/memory-sync-client.d.ts +30 -2
- package/dist/memory-sync-client.js +16 -9
- package/dist/observability/err-text.d.ts +7 -0
- package/dist/observability/err-text.js +5 -0
- package/dist/observability/fail-open.d.ts +7 -3
- package/dist/observability/fail-open.js +7 -3
- package/dist/observability/metrics.js +1 -0
- package/dist/orchestration/workflow-notify-journal.d.ts +18 -1
- package/dist/orchestration/workflow-notify-journal.js +12 -1
- package/dist/orchestration/workflow-park-decision.d.ts +70 -18
- package/dist/orchestration/workflow-park-decision.js +49 -10
- package/dist/orchestration/workflow-park-holder.d.ts +65 -0
- package/dist/orchestration/workflow-park-holder.js +98 -0
- package/dist/orchestration/workflow-record-fence.d.ts +36 -0
- package/dist/orchestration/workflow-record-fence.js +42 -0
- package/dist/owner-faces.d.ts +1 -1
- package/dist/parked-decide.d.ts +104 -53
- package/dist/parked-decide.js +153 -117
- package/dist/peer-directory.d.ts +2 -2
- package/dist/peer-inbound-face.d.ts +2 -6
- package/dist/peer-inbound-face.js +0 -3
- package/dist/permission-mode-admission.d.ts +41 -5
- package/dist/permission-mode-admission.js +29 -3
- package/dist/plugins/adoption-log-sql.d.ts +3 -1
- package/dist/plugins/adoption-log-sql.js +7 -1
- package/dist/plugins/approval-ask-store-file.d.ts +10 -2
- package/dist/plugins/approval-ask-store-file.js +8 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +6 -1
- package/dist/plugins/approval-ask-store-memory.js +28 -9
- package/dist/plugins/approval-ask-store-sql.d.ts +43 -2
- package/dist/plugins/approval-ask-store-sql.js +34 -5
- package/dist/plugins/approval-exemption-store.js +3 -0
- package/dist/plugins/approval-nonce-store.js +2 -0
- package/dist/plugins/breaker-state-sql.js +2 -0
- package/dist/plugins/caching-session-store.d.ts +9 -15
- package/dist/plugins/caching-session-store.js +6 -0
- package/dist/plugins/device-store-sql.js +6 -0
- package/dist/plugins/error-origin.d.ts +18 -0
- package/dist/plugins/error-origin.js +10 -0
- package/dist/plugins/execution-placement-sql.js +6 -6
- package/dist/plugins/image-bake-store-sql.js +6 -0
- package/dist/plugins/image-index-sql.js +4 -0
- package/dist/plugins/landed-attendants.d.ts +212 -0
- package/dist/plugins/landed-attendants.js +161 -0
- package/dist/plugins/leader-run-store-sql.js +4 -0
- package/dist/plugins/local-session-live-reference.d.ts +40 -0
- package/dist/plugins/local-session-live-reference.js +39 -0
- package/dist/plugins/local-session-store.d.ts +71 -33
- package/dist/plugins/local-session-store.js +129 -27
- package/dist/plugins/memory-engine-pg.js +4 -0
- package/dist/plugins/memory-engine-sql-apply.js +5 -2
- package/dist/plugins/memory-engine-tidb.js +4 -0
- package/dist/plugins/memory-optout-grant-store-sql.js +4 -0
- package/dist/plugins/memory-sync-store-pg.js +5 -0
- package/dist/plugins/memory-sync-store-tidb.js +5 -0
- package/dist/plugins/one-time-migrations.d.ts +57 -63
- package/dist/plugins/one-time-migrations.js +162 -90
- package/dist/plugins/org-delivery-quota-store.js +4 -1
- package/dist/plugins/outcome-ledger-sql.js +2 -0
- package/dist/plugins/permission-rule-store-sql.js +11 -4
- package/dist/plugins/pg-cost-quota.js +4 -0
- package/dist/plugins/pg-pool.js +24 -1
- package/dist/plugins/pg-rate-limiter.js +2 -0
- package/dist/plugins/posix-shell-fs.d.ts +6 -1
- package/dist/plugins/posix-shell-fs.js +4 -1
- package/dist/plugins/remote-env-adb.d.ts +19 -15
- package/dist/plugins/remote-env-adb.js +41 -15
- package/dist/plugins/remote-env-connect-seat.d.ts +105 -0
- package/dist/plugins/remote-env-connect-seat.js +72 -0
- package/dist/plugins/remote-env-device.d.ts +22 -5
- package/dist/plugins/remote-env-device.js +106 -8
- package/dist/plugins/remote-env-e2b.d.ts +15 -6
- package/dist/plugins/remote-env-e2b.js +42 -32
- package/dist/plugins/remote-env-file-error.js +6 -2
- package/dist/plugins/remote-env-host.d.ts +17 -0
- package/dist/plugins/remote-env-host.js +30 -38
- package/dist/plugins/remote-env-k8s.d.ts +41 -39
- package/dist/plugins/remote-env-k8s.js +53 -30
- package/dist/plugins/remote-env-local-docker.d.ts +13 -8
- package/dist/plugins/remote-env-local-docker.js +34 -41
- package/dist/plugins/remote-env-ssh.d.ts +18 -12
- package/dist/plugins/remote-env-ssh.js +60 -59
- package/dist/plugins/remote-shell.d.ts +98 -1
- package/dist/plugins/remote-shell.js +218 -0
- package/dist/plugins/resume-anchor-store-sql.d.ts +11 -2
- package/dist/plugins/resume-anchor-store-sql.js +12 -5
- package/dist/plugins/retention-lane-store-sql.js +4 -0
- package/dist/plugins/retention-sql.d.ts +20 -2
- package/dist/plugins/retention-sql.js +11 -0
- package/dist/plugins/run-store-sql.d.ts +21 -2
- package/dist/plugins/run-store-sql.js +31 -7
- package/dist/plugins/scenario-prefix-switch-m42.d.ts +1 -1
- package/dist/plugins/scenario-prefix-switch-m42.js +2 -2
- package/dist/plugins/schema-consistency.d.ts +4 -2
- package/dist/plugins/schema-consistency.js +19 -2
- package/dist/plugins/send-file-ledger.js +3 -0
- package/dist/plugins/serving-requirement.d.ts +26 -7
- package/dist/plugins/serving-requirement.js +11 -4
- package/dist/plugins/session-birth.d.ts +6 -2
- package/dist/plugins/session-capture-record-store-sql.js +2 -0
- package/dist/plugins/session-ext-table.d.ts +14 -1
- package/dist/plugins/session-ext-table.js +5 -1
- package/dist/plugins/session-row-sql.d.ts +49 -2
- package/dist/plugins/session-row-sql.js +56 -13
- package/dist/plugins/session-store-sql.d.ts +34 -18
- package/dist/plugins/session-store-sql.js +98 -17
- package/dist/plugins/shared-memory-store-sql.d.ts +1 -0
- package/dist/plugins/shared-memory-store-sql.js +6 -2
- package/dist/plugins/sql-driver.d.ts +2 -1
- package/dist/plugins/sql-driver.js +18 -3
- package/dist/plugins/sql-errors.d.ts +11 -6
- package/dist/plugins/sql-errors.js +5 -6
- package/dist/plugins/stop-then-start-sentence.d.ts +10 -4
- package/dist/plugins/stop-then-start-sentence.js +14 -10
- package/dist/plugins/stop-then-start.d.ts +109 -0
- package/dist/plugins/stop-then-start.js +104 -0
- package/dist/plugins/store-backend.d.ts +40 -13
- package/dist/plugins/store-backend.js +169 -27
- package/dist/plugins/store-boundary.d.ts +57 -0
- package/dist/plugins/store-boundary.js +139 -0
- package/dist/plugins/store-contracts.d.ts +17 -0
- package/dist/plugins/store-fault-sentence-m53.d.ts +7 -0
- package/dist/plugins/store-fault-sentence-m53.js +25 -0
- package/dist/plugins/store-fault.d.ts +26 -7
- package/dist/plugins/store-fault.js +18 -2
- package/dist/plugins/task-attachment-store.js +9 -0
- package/dist/plugins/task-list-store-sql.d.ts +2 -2
- package/dist/plugins/task-list-store-sql.js +10 -8
- package/dist/plugins/tidb-cost-quota.js +4 -0
- package/dist/plugins/tidb-pool.js +2 -1
- package/dist/plugins/tidb-rate-limiter.js +4 -0
- package/dist/plugins/user-input-ledger-sql.js +4 -0
- package/dist/plugins/workflow-run-store-sql.d.ts +28 -10
- package/dist/plugins/workflow-run-store-sql.js +64 -4
- package/dist/plugins/workspace-fill.d.ts +4 -7
- package/dist/plugins/workspace-fill.js +2 -1
- package/dist/plugins/write-behind-counter.js +5 -0
- package/dist/project-context.d.ts +0 -2
- package/dist/project-context.js +4 -8
- package/dist/project-registry.d.ts +46 -0
- package/dist/project-registry.js +44 -0
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +4 -0
- package/dist/request-value-closure.js +10 -0
- package/dist/run-acceptance.d.ts +3 -2
- package/dist/run-acceptance.js +4 -6
- package/dist/run-local.js +1 -0
- package/dist/runs.d.ts +37 -8
- package/dist/runs.js +53 -12
- package/dist/runtime-governance.d.ts +5 -4
- package/dist/security.d.ts +30 -32
- package/dist/security.js +41 -7
- package/dist/server-secret-env.d.ts +12 -10
- package/dist/server-secret-env.js +24 -15
- package/dist/session-bindings.d.ts +82 -0
- package/dist/session-bindings.js +18 -0
- package/dist/session-children-retire.d.ts +2 -2
- package/dist/session-children-retire.js +2 -6
- package/dist/session-deletion.d.ts +16 -1
- package/dist/session-error-wire.d.ts +3 -3
- package/dist/session-error-wire.js +3 -3
- package/dist/session-incarnation.d.ts +10 -0
- package/dist/session-incarnation.js +10 -1
- package/dist/session-kind.d.ts +57 -0
- package/dist/session-kind.js +22 -0
- package/dist/session-row.d.ts +21 -1
- package/dist/session-row.js +7 -1
- package/dist/session-sync-kernel.d.ts +24 -14
- package/dist/session-sync-kernel.js +3 -6
- package/dist/session-sync.d.ts +4 -98
- package/dist/session-sync.js +1 -71
- package/dist/session-titler.d.ts +10 -3
- package/dist/session-titler.js +19 -6
- package/dist/ssh-host-key.d.ts +5 -0
- package/dist/ssh-host-key.js +3 -0
- package/dist/stores-sql/file-history-store-sql.d.ts +52 -2
- package/dist/stores-sql/file-history-store-sql.js +108 -70
- package/dist/stores-sql/pg-session-storage.d.ts +34 -7
- package/dist/stores-sql/pg-session-storage.js +37 -13
- package/dist/stores-sql/retention-store-sql.d.ts +1 -9
- package/dist/stores-sql/retention-store-sql.js +2 -10
- package/dist/stores-sql/session-policy-store-sql.d.ts +30 -7
- package/dist/stores-sql/session-policy-store-sql.js +61 -44
- package/dist/stores-sql/session-write-hooks.d.ts +11 -0
- package/dist/stores-sql/tidb-session-store.d.ts +58 -7
- package/dist/stores-sql/tidb-session-store.js +37 -13
- package/dist/submission-exit.d.ts +3 -2
- package/dist/submission-exit.js +9 -1
- package/dist/task-cwd.d.ts +2 -2
- package/dist/task-settings.d.ts +6 -5
- package/dist/terminal.d.ts +33 -2
- package/dist/terminal.js +139 -9
- package/dist/tool-approval.d.ts +140 -6
- package/dist/tool-approval.js +165 -72
- package/dist/trace/self-minted-wire.d.ts +290 -27
- package/dist/trace/self-minted-wire.js +101 -13
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +78 -27
- package/package.json +2 -2
- package/wire-changes/server@7.151.0.json +762 -0
- package/dist/http/session-inherit-env.d.ts +0 -36
- package/dist/http/session-inherit-env.js +0 -6
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { once } from "node:events";
|
|
2
2
|
import { sessionLogDigest, sessionLogDigestsComparable, validateFileHistoryExport } from "@sema-agent/core";
|
|
3
3
|
import { StringDecoder } from "node:string_decoder";
|
|
4
|
-
import { FileHistoryUnreadableError, blobGetterOf, exportSessionManifest, exportedFileHistory
|
|
4
|
+
import { FileHistoryUnreadableError, blobGetterOf, exportSessionManifest, exportedFileHistory } from "../../session-sync.js";
|
|
5
|
+
import { fileHistoryImportFace } from "../../plugins/landed-attendants.js";
|
|
5
6
|
import * as facts from "../../capability-facts.js";
|
|
6
7
|
import { classifySyncRelationshipByIds, createStagedLogFeed, MalformedImportEntryError, newStagingToken, readImportAnchors, SyncConflictError } from "../../session-sync-kernel.js";
|
|
7
8
|
import { isUuidShape } from "../../security.js";
|
|
@@ -9,7 +10,7 @@ import { sendJson, sendError, sendSessionError, sendSessionPolicyError, logReque
|
|
|
9
10
|
import { StampedSessionArchiveError } from "../../session-error-wire.js";
|
|
10
11
|
import { buildDisplaySafeUrl } from "../../trace/redact.js";
|
|
11
12
|
import { storeFaultClassOf } from "../../plugins/store-fault.js";
|
|
12
|
-
import { enter, readSessionAgent, readSyncGate, refuse } from "../owner-access.js";
|
|
13
|
+
import { enter, readSessionAgent, readSyncGate, recheckSyncGate, refuse } from "../owner-access.js";
|
|
13
14
|
import { listFilterOf, ownerKeyOf, sameKey } from "../../owner-faces.js";
|
|
14
15
|
const SYNC_BLOB_MAX_BYTES = 64 * 1024 * 1024;
|
|
15
16
|
const SYNC_NDJSON_LINE_MAX_BYTES = 8 * 1024 * 1024;
|
|
@@ -17,6 +18,7 @@ import { SHA256_HEX_RE } from "../../digest-form.js";
|
|
|
17
18
|
import { assertNever } from "../route-ctx.js";
|
|
18
19
|
import { readSessionRulesRecords, describeSessionRulesRecordProblem } from "../../session-policy-wire.js";
|
|
19
20
|
import { readSessionPinned, readSessionTitle } from "../../session-row.js";
|
|
21
|
+
import { incarnationLogPrefix, SessionOccupantChangedError } from "../../session-incarnation.js";
|
|
20
22
|
import { defaultTaskRegistry } from "@sema-agent/core";
|
|
21
23
|
import { SessionActiveRunError, SessionForkInProgressError, SessionWriterPresentError } from "../../execution-placement.js";
|
|
22
24
|
import { probeSessionChildren, sessionTenantScope } from "../../session-children-retire.js";
|
|
@@ -121,6 +123,33 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
121
123
|
}
|
|
122
124
|
return true;
|
|
123
125
|
};
|
|
126
|
+
const stillAdmitted = async (port) => {
|
|
127
|
+
const admitted = port === "pull" ? gate.face : gate.occupant;
|
|
128
|
+
return admitted === undefined || (await recheckSyncGate(sessionStorage, sessionId, admitted, port));
|
|
129
|
+
};
|
|
130
|
+
const pullAnswer = async (send) => {
|
|
131
|
+
let same;
|
|
132
|
+
try {
|
|
133
|
+
same = await stillAdmitted("pull");
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
if (sendPullUnreadable(req, res, deps.logger, e))
|
|
137
|
+
return;
|
|
138
|
+
throw e;
|
|
139
|
+
}
|
|
140
|
+
if (!same) {
|
|
141
|
+
sendSessionAbsent("missing");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
send();
|
|
145
|
+
};
|
|
146
|
+
const pushAnswer = async (send) => {
|
|
147
|
+
if (!(await stillAdmitted("push"))) {
|
|
148
|
+
sendError(res, 409, "conflict", "sync conflict");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
send();
|
|
152
|
+
};
|
|
124
153
|
const replaySessionRow = async (row, face) => {
|
|
125
154
|
if (Object.keys(row).length === 0)
|
|
126
155
|
return true;
|
|
@@ -166,9 +195,28 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
166
195
|
}
|
|
167
196
|
};
|
|
168
197
|
const canReplayHistory = fileHistoryImportFace(historyStore) !== undefined;
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
198
|
+
const landAttendants = async (attendants, landed, plan) => {
|
|
199
|
+
if (plan.wipe && !(await attendants.wipe(sessionId, landed)).landed)
|
|
200
|
+
return { kind: "gone", leg: "wipe" };
|
|
201
|
+
if (plan.fileHistory !== null && canReplayHistory) {
|
|
202
|
+
const h = await attendants.replayHistory(sessionId, landed, plan.fileHistory, blobGetterOf(historyStore), { allowReplace: plan.allowReplace });
|
|
203
|
+
if (!h.landed)
|
|
204
|
+
return { kind: "gone", leg: "file_history" };
|
|
205
|
+
if (!h.value.ok)
|
|
206
|
+
return { kind: "history_failed", failure: h.value };
|
|
207
|
+
}
|
|
208
|
+
if (plan.policy.length > 0 && !(await attendants.replayPolicy(sessionId, landed, plan.policy, plan.operator)).landed)
|
|
209
|
+
return { kind: "gone", leg: "policy" };
|
|
210
|
+
if (plan.anchors.length > 0 && !(await attendants.putAnchors(sessionId, landed, plan.anchors, plan.owner)).landed)
|
|
211
|
+
return { kind: "gone", leg: "anchors" };
|
|
212
|
+
if (!(await replaySessionRow(plan.row, landed)))
|
|
213
|
+
return { kind: "gone", leg: "row" };
|
|
214
|
+
return { kind: "landed" };
|
|
215
|
+
};
|
|
216
|
+
const sendLandedOccupantGone = (arm, leg, landed) => {
|
|
217
|
+
deps.logger?.warn?.("session_sync_landed_occupant_gone", { sessionId, arm, leg, landed: incarnationLogPrefix(landed.incarnation) });
|
|
218
|
+
deps.metrics?.inc("session_sync_landed_occupant_gone_total", { arm, leg }, 1);
|
|
219
|
+
sendError(res, 409, "conflict", "sync conflict");
|
|
172
220
|
};
|
|
173
221
|
if (match.decl.id === "session-sync-manifest") {
|
|
174
222
|
if (!pullGateOk())
|
|
@@ -186,7 +234,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
186
234
|
sendSessionAbsent("missing");
|
|
187
235
|
return;
|
|
188
236
|
}
|
|
189
|
-
sendJson(res, 200, { manifest: out.manifest });
|
|
237
|
+
await pullAnswer(() => sendJson(res, 200, { manifest: out.manifest }));
|
|
190
238
|
return;
|
|
191
239
|
}
|
|
192
240
|
if (match.decl.id === "session-sync-entries") {
|
|
@@ -202,9 +250,11 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
202
250
|
}
|
|
203
251
|
afterSeq = n;
|
|
204
252
|
}
|
|
253
|
+
const recheck = async () => { if (!(await stillAdmitted("pull")))
|
|
254
|
+
throw new SessionOccupantChangedError(sessionId); };
|
|
205
255
|
let it;
|
|
206
256
|
try {
|
|
207
|
-
it = await sessionStorage.exportEntriesStream(sessionId, afterSeq === undefined ?
|
|
257
|
+
it = await sessionStorage.exportEntriesStream(sessionId, { ...(afterSeq === undefined ? {} : { afterSeq }), recheck });
|
|
208
258
|
}
|
|
209
259
|
catch (e) {
|
|
210
260
|
if (sendPullUnreadable(req, res, deps.logger, e))
|
|
@@ -215,6 +265,19 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
215
265
|
sendSessionAbsent("missing");
|
|
216
266
|
return;
|
|
217
267
|
}
|
|
268
|
+
let openSame;
|
|
269
|
+
try {
|
|
270
|
+
openSame = await stillAdmitted("pull");
|
|
271
|
+
}
|
|
272
|
+
catch (e) {
|
|
273
|
+
if (sendPullUnreadable(req, res, deps.logger, e))
|
|
274
|
+
return;
|
|
275
|
+
throw e;
|
|
276
|
+
}
|
|
277
|
+
if (!openSame) {
|
|
278
|
+
sendSessionAbsent("missing");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
218
281
|
res.writeHead(200, { "content-type": "application/x-ndjson", "transfer-encoding": "chunked" });
|
|
219
282
|
const write = async (line) => { if (!res.write(line))
|
|
220
283
|
await once(res, "drain"); };
|
|
@@ -229,7 +292,10 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
229
292
|
res.end();
|
|
230
293
|
}
|
|
231
294
|
catch (e) {
|
|
232
|
-
|
|
295
|
+
if (e instanceof SessionOccupantChangedError)
|
|
296
|
+
deps.logger?.info?.("session_sync_pull_occupant_changed", { sessionId, count });
|
|
297
|
+
else
|
|
298
|
+
deps.logger?.error?.("sync entries stream failed", { sessionId, count, error: msg(e) });
|
|
233
299
|
res.end();
|
|
234
300
|
}
|
|
235
301
|
return;
|
|
@@ -264,7 +330,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
264
330
|
}
|
|
265
331
|
const referenced = envelope !== null && envelope.tracked.some((t) => t.versions.some((v) => v.blobHash === hash));
|
|
266
332
|
if (!referenced) {
|
|
267
|
-
sendError(res, 404, "not_found.blob", "blob not found");
|
|
333
|
+
await pullAnswer(() => sendError(res, 404, "not_found.blob", "blob not found"));
|
|
268
334
|
return;
|
|
269
335
|
}
|
|
270
336
|
let bytes;
|
|
@@ -276,12 +342,15 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
276
342
|
return;
|
|
277
343
|
throw e;
|
|
278
344
|
}
|
|
279
|
-
|
|
280
|
-
|
|
345
|
+
const found = bytes;
|
|
346
|
+
if (!found) {
|
|
347
|
+
await pullAnswer(() => sendError(res, 404, "not_found.blob", "blob not found"));
|
|
281
348
|
return;
|
|
282
349
|
}
|
|
283
|
-
|
|
284
|
-
|
|
350
|
+
await pullAnswer(() => {
|
|
351
|
+
res.writeHead(200, { "content-type": "application/octet-stream" });
|
|
352
|
+
res.end(Buffer.from(found));
|
|
353
|
+
});
|
|
285
354
|
return;
|
|
286
355
|
}
|
|
287
356
|
if (match.decl.id === "session-sync-blob-put") {
|
|
@@ -386,10 +455,14 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
386
455
|
const entryIds = body.entryIds;
|
|
387
456
|
const policy = policyRead.records;
|
|
388
457
|
const anchors = anchorsRead.anchors;
|
|
389
|
-
const dstIds = (await sessionStorage.listEntryIds?.(sessionId)) ?? null;
|
|
458
|
+
const dstIds = gate.occupant === undefined ? null : ((await sessionStorage.listEntryIds?.(sessionId)) ?? null);
|
|
390
459
|
const relA = classifySyncRelationshipByIds(entryIds, dstIds);
|
|
391
460
|
const refusalA = await firstPhaseRefusal(sessionStorage, sessionId, relA.relation, resolution === "overwrite-dst");
|
|
392
461
|
if (refusalA !== undefined) {
|
|
462
|
+
if (!(await stillAdmitted("push"))) {
|
|
463
|
+
sendError(res, 409, "conflict", "sync conflict");
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
393
466
|
if (refusalA.kind === "stamped") {
|
|
394
467
|
if (sendSessionError(res, refusalA.error, "import"))
|
|
395
468
|
return;
|
|
@@ -405,7 +478,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
405
478
|
if (v.blobHash !== null)
|
|
406
479
|
distinct.add(v.blobHash);
|
|
407
480
|
if (await firstMissingBlob(facts.sessionSyncFileHistory(deps), [...distinct]) !== undefined) {
|
|
408
|
-
sendMissingBlob(res);
|
|
481
|
+
await pushAnswer(() => sendMissingBlob(res));
|
|
409
482
|
return;
|
|
410
483
|
}
|
|
411
484
|
}
|
|
@@ -432,10 +505,10 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
432
505
|
case "mirror":
|
|
433
506
|
break;
|
|
434
507
|
case "writer":
|
|
435
|
-
sendSessionWriter(res);
|
|
508
|
+
await pushAnswer(() => sendSessionWriter(res));
|
|
436
509
|
return;
|
|
437
510
|
case "forking":
|
|
438
|
-
sendError(res, 409, "conflict.fork_in_progress", new SessionForkInProgressError(sessionId).message);
|
|
511
|
+
await pushAnswer(() => sendError(res, 409, "conflict.fork_in_progress", new SessionForkInProgressError(sessionId).message));
|
|
439
512
|
return;
|
|
440
513
|
default: {
|
|
441
514
|
const unknownRole = placed.cell;
|
|
@@ -443,12 +516,13 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
443
516
|
}
|
|
444
517
|
}
|
|
445
518
|
}
|
|
446
|
-
const activeNow = await activityOf(gateFace);
|
|
519
|
+
const activeNow = gate.occupant === undefined ? undefined : await activityOf(gateFace);
|
|
447
520
|
if (activeNow !== undefined) {
|
|
448
|
-
sendActivity(activeNow, "open");
|
|
521
|
+
await pushAnswer(() => sendActivity(activeNow, "open"));
|
|
449
522
|
return;
|
|
450
523
|
}
|
|
451
524
|
if (relA.relation === "identical" && !digestSaysDiffer) {
|
|
525
|
+
const attendantsA = backend.landedAttendants();
|
|
452
526
|
const seat = sessionStorage.executionPlacement;
|
|
453
527
|
if (seat !== undefined) {
|
|
454
528
|
if (gateFace === undefined) {
|
|
@@ -474,35 +548,38 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
474
548
|
}
|
|
475
549
|
}
|
|
476
550
|
}
|
|
477
|
-
if (
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
551
|
+
if (gateFace === undefined) {
|
|
552
|
+
sendError(res, 409, "conflict", "sync conflict");
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
let landedA;
|
|
556
|
+
try {
|
|
557
|
+
landedA = await landAttendants(attendantsA, gateFace, { wipe: false, fileHistory, allowReplace: resolution === "overwrite-dst", policy, operator: operatorOk, anchors, owner: importingPrincipal, row });
|
|
558
|
+
}
|
|
559
|
+
catch (e) {
|
|
560
|
+
if (sendSessionPolicyError(res, e))
|
|
481
561
|
return;
|
|
482
|
-
|
|
562
|
+
throw e;
|
|
483
563
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
564
|
+
switch (landedA.kind) {
|
|
565
|
+
case "landed":
|
|
566
|
+
break;
|
|
567
|
+
case "gone":
|
|
568
|
+
sendLandedOccupantGone("identical", landedA.leg, gateFace);
|
|
569
|
+
return;
|
|
570
|
+
case "history_failed":
|
|
571
|
+
sendFileHistoryReplayFailure(res, landedA.failure);
|
|
572
|
+
return;
|
|
573
|
+
default: {
|
|
574
|
+
const unhandled = landedA;
|
|
575
|
+
throw new Error(`session sync: unknown landing answer ${JSON.stringify(unhandled)}`);
|
|
492
576
|
}
|
|
493
577
|
}
|
|
494
|
-
|
|
495
|
-
for (const a of anchors)
|
|
496
|
-
await deps.resumeAnchorStore.put(sessionId, a.eventId, a.entryId, importingPrincipal);
|
|
497
|
-
if (!(await replaySessionRow(row, gateFace))) {
|
|
498
|
-
sendError(res, 409, "conflict", "sync conflict");
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
sendJson(res, 200, {
|
|
578
|
+
await pushAnswer(() => sendJson(res, 200, {
|
|
502
579
|
relation: "identical",
|
|
503
580
|
basis: payloadVerified ? "entry-ids+digest" : "entry-ids",
|
|
504
581
|
payloadVerified,
|
|
505
|
-
});
|
|
582
|
+
}));
|
|
506
583
|
return;
|
|
507
584
|
}
|
|
508
585
|
const leaseKey = importLeaseKey(gate.occupant !== undefined ? gate.occupant.owner : importingPrincipal, sessionId);
|
|
@@ -514,8 +591,12 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
514
591
|
const heldLease = importLeases.get(leaseKey);
|
|
515
592
|
if (heldLease) {
|
|
516
593
|
const retryAfterSec = staleMs > 0 ? Math.max(1, Math.ceil((staleMs - (Date.now() - heldLease.at)) / 1000)) : undefined;
|
|
517
|
-
sendError(res, 409, "import_in_flight", "an import is already in flight for this session"
|
|
518
|
-
+ (retryAfterSec !== undefined ? ` (a stalled holder is taken over after ${staleSec}s of inactivity; retry in ~${retryAfterSec}s)` : ""), retryAfterSec !== undefined ? { retryAfterSec } : undefined);
|
|
594
|
+
await pushAnswer(() => sendError(res, 409, "import_in_flight", "an import is already in flight for this session"
|
|
595
|
+
+ (retryAfterSec !== undefined ? ` (a stalled holder is taken over after ${staleSec}s of inactivity; retry in ~${retryAfterSec}s)` : ""), retryAfterSec !== undefined ? { retryAfterSec } : undefined));
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (!(await stillAdmitted("push"))) {
|
|
599
|
+
sendError(res, 409, "conflict", "sync conflict");
|
|
519
600
|
return;
|
|
520
601
|
}
|
|
521
602
|
const token = newStagingToken();
|
|
@@ -524,7 +605,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
524
605
|
importLeases.set(leaseKey, { at: Date.now(), stagingId, sessionId });
|
|
525
606
|
pendingStagings.set(stagingId, {
|
|
526
607
|
handle, realSessionId: sessionId, owner: importingPrincipal, ...(resolution ? { resolution } : {}), operatorOk,
|
|
527
|
-
fileHistory, policy, anchors, row, declaredEntryIds: entryIds, openedBy: ownerKeyOf(scope.gateOwner),
|
|
608
|
+
fileHistory, policy, anchors, row, declaredEntryIds: entryIds, openedBy: ownerKeyOf(scope.gateOwner), expectedOccupant: gateFace, leaseKey,
|
|
528
609
|
});
|
|
529
610
|
sendJson(res, 200, { stagingId, relation: relA.relation });
|
|
530
611
|
return;
|
|
@@ -610,19 +691,22 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
610
691
|
return;
|
|
611
692
|
throw e;
|
|
612
693
|
}
|
|
613
|
-
let
|
|
694
|
+
let gateAtCommit;
|
|
614
695
|
let activeAtCommit;
|
|
696
|
+
let activeStillAdmitted = true;
|
|
615
697
|
try {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
activeAtCommit =
|
|
698
|
+
gateAtCommit = await judgeSessionGate();
|
|
699
|
+
const atCommit = gateAtCommit.occupant;
|
|
700
|
+
activeAtCommit = gateAtCommit.mayPush && atCommit !== undefined ? await activityOf(gateAtCommit.face) : undefined;
|
|
701
|
+
if (activeAtCommit !== undefined && atCommit !== undefined)
|
|
702
|
+
activeStillAdmitted = await recheckSyncGate(sessionStorage, sessionId, atCommit, "push");
|
|
619
703
|
}
|
|
620
704
|
catch (e) {
|
|
621
705
|
await pending.handle.abort().catch(() => undefined);
|
|
622
706
|
cleanupStaging(stagingId);
|
|
623
707
|
throw e;
|
|
624
708
|
}
|
|
625
|
-
if (!
|
|
709
|
+
if (!gateAtCommit.mayPush) {
|
|
626
710
|
await pending.handle.abort().catch(() => undefined);
|
|
627
711
|
cleanupStaging(stagingId);
|
|
628
712
|
sendSessionAbsent("not_visible");
|
|
@@ -631,37 +715,43 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
631
715
|
if (activeAtCommit !== undefined) {
|
|
632
716
|
await pending.handle.abort().catch(() => undefined);
|
|
633
717
|
cleanupStaging(stagingId);
|
|
718
|
+
if (!activeStillAdmitted) {
|
|
719
|
+
sendError(res, 409, "conflict", "sync conflict");
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
634
722
|
sendActivity(activeAtCommit, "streamed");
|
|
635
723
|
return;
|
|
636
724
|
}
|
|
637
725
|
try {
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
await wipeSessionAttendants({ fileHistoryStore: historyStore, sessionPolicyStore: deps.sessionPolicyStore, resumeAnchorStore: deps.resumeAnchorStore }, sessionId);
|
|
641
|
-
}
|
|
642
|
-
if (pending.fileHistory !== null && canReplayHistory) {
|
|
643
|
-
const failed = await replayHistory(pending.fileHistory, pending.resolution === "overwrite-dst");
|
|
644
|
-
if (failed !== undefined) {
|
|
645
|
-
sendFileHistoryReplayFailure(res, failed);
|
|
646
|
-
cleanupStaging(stagingId);
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
const policyStore = deps.sessionPolicyStore;
|
|
651
|
-
if (policyStore)
|
|
652
|
-
await replayPolicyRecords(policyStore, sessionId, pending.policy, pending.operatorOk);
|
|
653
|
-
const anchorStore = deps.resumeAnchorStore;
|
|
654
|
-
if (anchorStore)
|
|
655
|
-
for (const a of pending.anchors)
|
|
656
|
-
await anchorStore.put(sessionId, a.eventId, a.entryId, pending.owner);
|
|
657
|
-
const rowLanded = await replaySessionRow(pending.row, committed.face);
|
|
726
|
+
const attendantsB = backend.landedAttendants();
|
|
727
|
+
const committed = await pending.handle.commit(pending.owner, { expectedOccupant: pending.expectedOccupant, ...(pending.resolution ? { resolution: pending.resolution } : {}) });
|
|
658
728
|
sessionStorage.evict?.(sessionId);
|
|
729
|
+
const landedB = await landAttendants(attendantsB, committed.face, {
|
|
730
|
+
wipe: pending.resolution === "overwrite-dst" && (committed.relation === "fork" || committed.relation === "stale"),
|
|
731
|
+
fileHistory: pending.fileHistory,
|
|
732
|
+
allowReplace: pending.resolution === "overwrite-dst",
|
|
733
|
+
policy: pending.policy,
|
|
734
|
+
operator: pending.operatorOk,
|
|
735
|
+
anchors: pending.anchors,
|
|
736
|
+
owner: pending.owner,
|
|
737
|
+
row: pending.row,
|
|
738
|
+
});
|
|
659
739
|
cleanupStaging(stagingId);
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
740
|
+
switch (landedB.kind) {
|
|
741
|
+
case "landed":
|
|
742
|
+
sendJson(res, 200, { relation: committed.relation });
|
|
743
|
+
return;
|
|
744
|
+
case "gone":
|
|
745
|
+
sendLandedOccupantGone("commit", landedB.leg, committed.face);
|
|
746
|
+
return;
|
|
747
|
+
case "history_failed":
|
|
748
|
+
sendFileHistoryReplayFailure(res, landedB.failure);
|
|
749
|
+
return;
|
|
750
|
+
default: {
|
|
751
|
+
const unhandled = landedB;
|
|
752
|
+
throw new Error(`session sync: unknown landing answer ${JSON.stringify(unhandled)}`);
|
|
753
|
+
}
|
|
663
754
|
}
|
|
664
|
-
sendJson(res, 200, { relation: committed.relation });
|
|
665
755
|
}
|
|
666
756
|
catch (e) {
|
|
667
757
|
await pending.handle.abort().catch(() => undefined);
|
|
@@ -700,9 +790,17 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
700
790
|
sendError(res, 400, "request.field_invalid", "entryIds must be an array of strings");
|
|
701
791
|
return;
|
|
702
792
|
}
|
|
703
|
-
const dst = await sessionStorage.exportEntries(sessionId);
|
|
793
|
+
const dst = gate.occupant === undefined ? null : await sessionStorage.exportEntries(sessionId);
|
|
704
794
|
const relation = classifySyncRelationshipByIds(body.entryIds, dst ? dst.map((e) => e.id) : null);
|
|
795
|
+
if (gate.occupant === undefined) {
|
|
796
|
+
sendJson(res, 200, relation);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
705
799
|
const refusal = await firstPhaseRefusal(sessionStorage, sessionId, relation.relation, false);
|
|
800
|
+
if (!(await stillAdmitted("push"))) {
|
|
801
|
+
sendError(res, 409, "conflict", "sync conflict");
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
706
804
|
if (refusal?.kind === "stamped") {
|
|
707
805
|
if (sendSessionError(res, refusal.error, "import"))
|
|
708
806
|
return;
|
|
@@ -791,9 +889,6 @@ function sendSessionSyncUnavailable(res, why) {
|
|
|
791
889
|
case "session_import_staging_absent":
|
|
792
890
|
sendError(res, 501, "capability.session_store_required", "staged session import requires a durable session store");
|
|
793
891
|
return;
|
|
794
|
-
case "file_history_store_absent":
|
|
795
|
-
sendError(res, 501, "capability.snapshot_store_required", "session sync is not available on this deployment: no file-history store is wired, so there is no file history to export or import");
|
|
796
|
-
return;
|
|
797
892
|
case "policy_purge_absent":
|
|
798
893
|
sendError(res, 501, "capability.policy_purge_required", "session sync is not available on this deployment: the session-policy store has no deleteBySession purge verb, so an overwrite-dst import could not wipe the destination's stale policy rows. Every in-tree policy store carries it since core 1.423 — upgrade the policy store");
|
|
799
894
|
return;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { isUuidV7 } from "../../security.js";
|
|
2
2
|
import { sendJson, sendError } from "../send.js";
|
|
3
3
|
import { sendUnauthorized } from "../principal-gate.js";
|
|
4
|
-
import { routeListFilter } from "../owner-access.js";
|
|
4
|
+
import { listOwnerOf, listVisibleSessions, routeListFilter } from "../owner-access.js";
|
|
5
5
|
import { filterFingerprint } from "../cursor-fingerprint.js";
|
|
6
|
+
import { SESSION_KINDS, isSessionKind } from "../../session-kind.js";
|
|
6
7
|
import { sessionTempoOfRunStatus } from "../../plugins/store-contracts.js";
|
|
7
8
|
import { assertNever } from "../route-ctx.js";
|
|
8
9
|
import { PRINCIPAL_MAX_BYTES } from "../../principal-shape.js";
|
|
@@ -21,8 +22,7 @@ export async function handleSessionsList(req, res, match, ctx) {
|
|
|
21
22
|
sendError(res, 501, "capability.session_store_required", "session list requires a durable session store (DB_BACKEND=mysql|pg|local)");
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
await handleSessionList(res, lister, new URLSearchParams(match.search), gateOwner, includeUnowned);
|
|
25
|
+
await handleSessionList(res, listing.lister, new URLSearchParams(match.search), { fleetWide, gateOwner, includeUnowned });
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
case "sendfile-links": {
|
|
@@ -109,18 +109,24 @@ function decodeSessionCursor(s, expectF) {
|
|
|
109
109
|
return undefined;
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
async function handleSessionList(res, lister, query,
|
|
113
|
-
const
|
|
112
|
+
async function handleSessionList(res, lister, query, filter) {
|
|
113
|
+
const kindRaw = query.get("sessionKind");
|
|
114
|
+
if (kindRaw !== null && !isSessionKind(kindRaw)) {
|
|
115
|
+
sendError(res, 400, "request.query_invalid", `sessionKind must be one of ${SESSION_KINDS.map((w) => JSON.stringify(w)).join(" | ")} when present (absent = the default view: top-level sessions only)`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const sessionKind = kindRaw ?? undefined;
|
|
119
|
+
const owner = listOwnerOf(filter, query.get("owner") ?? undefined);
|
|
114
120
|
const limit = Math.min(100, Math.max(1, Number(query.get("limit") ?? 50) || 50));
|
|
115
121
|
const qRaw = (query.get("q") ?? "").trim();
|
|
116
122
|
const q = qRaw ? qRaw.slice(0, 256) : undefined;
|
|
117
|
-
const f = filterFingerprint([owner, includeUnowned, q]);
|
|
123
|
+
const f = filterFingerprint(sessionKind === undefined ? [owner, filter.includeUnowned, q] : [owner, filter.includeUnowned, q, sessionKind]);
|
|
118
124
|
const cursor = decodeSessionCursor(query.get("cursor"), f);
|
|
119
125
|
if (cursor === "mismatch") {
|
|
120
126
|
sendError(res, 400, "request.cursor_filter_mismatch", "cursor was issued under a different filter (owner/q) — restart from the first page without a cursor");
|
|
121
127
|
return;
|
|
122
128
|
}
|
|
123
|
-
const rows = await lister
|
|
129
|
+
const rows = await listVisibleSessions(lister, filter, query.get("owner") ?? undefined, { ...(cursor ? { cursor } : {}), limit: limit + 1, ...(q ? { q } : {}), ...(sessionKind !== undefined ? { sessionKind } : {}) });
|
|
124
130
|
const hasMore = rows.length > limit;
|
|
125
131
|
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
126
132
|
const last = page[page.length - 1];
|
|
@@ -15,7 +15,7 @@ import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeade
|
|
|
15
15
|
import { gatedPerson, gatedSubject, sendPrincipalRefusal } from "../principal-gate.js";
|
|
16
16
|
import { personOf } from "../../owner-brands.js";
|
|
17
17
|
import { leafOfAdmittedHead, sessionFaceOfHead } from "../../session-incarnation.js";
|
|
18
|
-
import { admitSessionById, admitSessionForDeletion, admitSessionRow, admitSessionView, admittedAsFleet, callerEntry, callerOf, deliverVerdict, enter, judgeSessionById, protocolEntry, readOwnerCell, readVisibleLatestRun, refuse,
|
|
18
|
+
import { admitSessionById, admitSessionForDeletion, admitSessionRow, admitSessionView, admittedAsFleet, callerEntry, callerOf, deliverVerdict, enter, judgeSessionById, protocolEntry, readOwnerCell, readVisibleLatestRun, refuse, sessionOccupantUnchanged } from "../owner-access.js";
|
|
19
19
|
import { assertNever } from "../route-ctx.js";
|
|
20
20
|
import * as facts from "../../capability-facts.js";
|
|
21
21
|
import { rosterAgentId } from "./agents-roster.js";
|
|
@@ -462,7 +462,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
462
462
|
}
|
|
463
463
|
ownerCheckPending = true;
|
|
464
464
|
ownerCheckStartedAt = Date.now();
|
|
465
|
-
void
|
|
465
|
+
void sessionOccupantUnchanged(evEntry, evFact.sessionStorage, evSession, evAdmitted.row).then((same) => {
|
|
466
466
|
ownerCheckPending = false;
|
|
467
467
|
if (!closed && !same)
|
|
468
468
|
onEnd();
|
|
@@ -592,7 +592,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
592
592
|
sendJson(res, 200, {
|
|
593
593
|
sessionId,
|
|
594
594
|
autoConsolidationArmed: deps.memoryAutoConsolidationArmed,
|
|
595
|
-
...(deps.memorySyncLeg !== undefined ? { memorySync: { parked: deps.memorySyncLeg.parked()
|
|
595
|
+
...(deps.memorySyncLeg !== undefined ? { memorySync: { parked: deps.memorySyncLeg.parked() } } : {}),
|
|
596
596
|
...(s.captureOptedOut !== undefined ? { captureOptedOut: s.captureOptedOut } : {}),
|
|
597
597
|
...(s.committedCount !== undefined ? { committedCount: s.committedCount } : {}),
|
|
598
598
|
...(s.foldedCount !== undefined ? { foldedCount: s.foldedCount } : {}),
|
|
@@ -905,7 +905,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
905
905
|
sendError(res, 501, "capability.session_store_required", "session policy requires a session-store backend");
|
|
906
906
|
return;
|
|
907
907
|
}
|
|
908
|
-
const store = policyFact.
|
|
908
|
+
const store = policyFact.backend.sessionPolicy();
|
|
909
909
|
const policyEntry = enter(req, res, deps, match.decl.id);
|
|
910
910
|
if (policyEntry === undefined)
|
|
911
911
|
return;
|
|
@@ -947,8 +947,12 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
947
947
|
}
|
|
948
948
|
const rules = read.rules;
|
|
949
949
|
try {
|
|
950
|
-
const stored = await
|
|
951
|
-
|
|
950
|
+
const stored = await policyFact.backend.landedAttendants().putPolicy(sessionId, sessionFaceOfHead(sessionId, policyAdmitted.row), target, rules, { ...(typeof body.expectedRev === "number" ? { expectedRev: body.expectedRev } : {}), operator });
|
|
951
|
+
if (!stored.landed) {
|
|
952
|
+
refuse(policyEntry, res, "missing");
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
sendJson(res, 200, { rules: stored.value });
|
|
952
956
|
}
|
|
953
957
|
catch (e) {
|
|
954
958
|
if (sendSessionPolicyError(res, e))
|