@sema-agent/server 7.151.0 → 7.152.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 +266 -0
- package/README.md +15 -3
- package/README.zh-CN.md +10 -2
- package/USAGE.md +93 -64
- package/deploy/sema-up/README.md +5 -4
- package/deploy/sema-up/cluster-up.sh +6 -6
- package/deploy/sema-up/kube-up.sh +5 -5
- package/deploy/sema-up/lib/flagshape.sh +36 -8
- package/deploy/sema-up/machines.example.yaml +3 -1
- package/deploy/sema-up/sema-up.sh +22 -16
- package/dist/approval-ask-audit-store.d.ts +1 -1
- package/dist/approval-ask-machine.d.ts +4 -10
- package/dist/approval-ask-retention.d.ts +1 -1
- package/dist/approval-card.d.ts +10 -12
- package/dist/approval-gate-currency.d.ts +8 -8
- package/dist/approval-gate-currency.js +3 -4
- package/dist/approval-hmac.d.ts +17 -1
- package/dist/approval-hmac.js +7 -1
- package/dist/approval-queue-kind.d.ts +1 -1
- package/dist/approval.d.ts +2 -2
- package/dist/approval.js +2 -2
- package/dist/bind-host.d.ts +1 -3
- package/dist/bind-host.js +2 -3
- package/dist/boot/coordinators.d.ts +1 -1
- package/dist/boot/coordinators.js +9 -10
- package/dist/boot/device-lane.js +2 -0
- package/dist/boot/execution-env.d.ts +1 -0
- package/dist/boot/execution-env.js +31 -5
- package/dist/boot/leader.d.ts +31 -30
- package/dist/boot/leader.js +38 -28
- package/dist/boot/leg-path-adjudication.d.ts +40 -0
- package/dist/boot/leg-path-adjudication.js +18 -0
- package/dist/boot/parked-revive-gate.js +3 -15
- package/dist/boot/reapers.d.ts +1 -1
- package/dist/boot/reapers.js +1 -1
- package/dist/boot/resolve-spec.js +12 -26
- package/dist/boot/runner-deps.d.ts +39 -35
- package/dist/boot/runner-deps.js +92 -89
- package/dist/boot/shutdown.d.ts +6 -0
- package/dist/boot/shutdown.js +10 -4
- package/dist/boot/stage-02-web-admin-pull.js +4 -5
- package/dist/boot/stage-05-execution-env.js +2 -1
- package/dist/boot/stage-06-runners.d.ts +2 -0
- package/dist/boot/stage-06-runners.js +10 -4
- package/dist/boot/stage-07-capability-layer.d.ts +1 -0
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-08-reapers.d.ts +1 -0
- package/dist/boot/stage-09-leader.d.ts +1 -0
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +8 -0
- package/dist/boot/stage-10-http-server.js +11 -15
- package/dist/boot/stores.js +7 -4
- package/dist/boot/web-admin.js +2 -7
- package/dist/capabilities/approvals-push.d.ts +3 -3
- package/dist/capabilities/scenarios.d.ts +28 -13
- package/dist/capabilities/scenarios.js +2 -2
- package/dist/capability-facts.d.ts +22 -20
- package/dist/capability-facts.js +18 -34
- package/dist/card-rule-offers.d.ts +32 -0
- package/dist/card-rule-offers.js +130 -0
- package/dist/config-catalog.js +12 -11
- package/dist/config-dir-trust.js +4 -2
- package/dist/config-invariants.d.ts +18 -7
- package/dist/config-invariants.js +51 -8
- package/dist/config-lkg.d.ts +5 -19
- package/dist/config-lkg.js +8 -62
- package/dist/config-provider.d.ts +1 -1
- package/dist/config-root-migration.d.ts +2 -2
- package/dist/config-types.d.ts +28 -38
- package/dist/config.d.ts +7 -5
- package/dist/config.js +55 -16
- package/dist/control-socket.d.ts +51 -0
- package/dist/control-socket.js +115 -0
- package/dist/deployment-governance.d.ts +2 -1
- package/dist/device-ws-hub.d.ts +7 -0
- package/dist/device-ws-hub.js +7 -1
- package/dist/elicitation.js +2 -2
- package/dist/entry-problems.d.ts +40 -3
- package/dist/entry-problems.js +3 -1
- package/dist/execution-env-refusal.d.ts +8 -2
- package/dist/execution-env-refusal.js +3 -0
- package/dist/fleet/engine-signal.d.ts +12 -29
- package/dist/fleet/engine-signal.js +1 -7
- package/dist/fleet-client.js +2 -5
- package/dist/gate-settlement.d.ts +1 -8
- package/dist/gate-settlement.js +0 -4
- package/dist/history-anchor.js +1 -1
- package/dist/hitl-delivery.d.ts +2 -3
- package/dist/hitl-delivery.js +0 -2
- package/dist/hooks/hook-llm.js +4 -2
- package/dist/hooks/hook-runner.d.ts +3 -2
- package/dist/hooks/hook-runner.js +16 -1
- package/dist/host-decision.d.ts +11 -11
- package/dist/http/active-run-conflict.d.ts +35 -2
- package/dist/http/active-run-conflict.js +48 -19
- package/dist/http/approval-sweeps.d.ts +1 -1
- package/dist/http/approval-sweeps.js +5 -2
- package/dist/http/checkpoint-ctx.d.ts +2 -0
- package/dist/http/checkpoint-ctx.js +1 -0
- package/dist/http/dispatch.d.ts +6 -2
- package/dist/http/dispatch.js +25 -5
- package/dist/http/durable-run-leg.js +0 -1
- package/dist/http/health-reader.d.ts +16 -9
- package/dist/http/health-reader.js +2 -1
- package/dist/http/health-self-report.js +1 -1
- package/dist/http/owner-access.d.ts +2 -3
- package/dist/http/owner-access.js +3 -4
- package/dist/http/principal-gate.d.ts +49 -21
- package/dist/http/principal-gate.js +61 -11
- package/dist/http/request-channel.d.ts +23 -0
- package/dist/http/request-channel.js +9 -0
- package/dist/http/request-guards.js +17 -2
- package/dist/http/resume-ctx-read.js +20 -3
- package/dist/http/resume-legs.d.ts +2 -2
- package/dist/http/resume-legs.js +111 -41
- package/dist/http/route-ctx.d.ts +73 -7
- package/dist/http/route-ctx.js +17 -0
- package/dist/http/route-table.d.ts +4 -0
- package/dist/http/route-table.js +4 -0
- package/dist/http/routes/admin-config-refresh.js +3 -3
- package/dist/http/routes/admin-drain.d.ts +3 -0
- package/dist/http/routes/admin-drain.js +7 -7
- package/dist/http/routes/admin-memory-optout.js +3 -3
- package/dist/http/routes/adoption.js +3 -5
- package/dist/http/routes/approvals-assistant.d.ts +2 -1
- package/dist/http/routes/approvals-assistant.js +95 -48
- package/dist/http/routes/capabilities.d.ts +4 -0
- package/dist/http/routes/capabilities.js +30 -5
- package/dist/http/routes/config-catalog.js +2 -2
- package/dist/http/routes/devices.js +1 -1
- package/dist/http/routes/diagnostics.d.ts +3 -3
- package/dist/http/routes/diagnostics.js +19 -7
- package/dist/http/routes/images.js +10 -10
- package/dist/http/routes/memory-bundle.js +3 -3
- package/dist/http/routes/memory-compliance.js +3 -3
- package/dist/http/routes/memory-consolidation.js +2 -2
- package/dist/http/routes/memory-origin.js +3 -3
- package/dist/http/routes/memory-policy.js +2 -2
- package/dist/http/routes/observability.js +3 -3
- package/dist/http/routes/retention-ops.js +3 -3
- package/dist/http/routes/rules.js +5 -5
- package/dist/http/routes/run-permission-mode.d.ts +2 -1
- package/dist/http/routes/run-permission-mode.js +2 -2
- package/dist/http/routes/runs.d.ts +4 -9
- package/dist/http/routes/runs.js +73 -33
- package/dist/http/routes/session-sync.d.ts +18 -6
- package/dist/http/routes/session-sync.js +38 -36
- package/dist/http/routes/sessions.js +20 -9
- package/dist/http/routes/tasks.js +28 -37
- package/dist/http/routes/workflows.js +1 -1
- package/dist/http/server.d.ts +9 -3
- package/dist/http/server.js +15 -3
- package/dist/http/submit-ctx.d.ts +1 -1
- package/dist/http/submit-ctx.js +2 -0
- package/dist/human-facing-args.d.ts +33 -0
- package/dist/human-facing-args.js +33 -0
- package/dist/index.d.ts +1 -1
- package/dist/k8s-api-credentials.d.ts +29 -0
- package/dist/k8s-api-credentials.js +25 -0
- package/dist/lane-home-probe.d.ts +1 -1
- package/dist/lane-home-probe.js +2 -1
- package/dist/leader/diffup.d.ts +19 -0
- package/dist/leader/diffup.js +7 -0
- package/dist/leader/leader.d.ts +114 -10
- package/dist/leader/leader.js +69 -5
- package/dist/leader/repair-wire.js +2 -2
- package/dist/leader/run-entry.d.ts +2 -1
- package/dist/leader/run-entry.js +1 -1
- package/dist/leader/wire.d.ts +42 -132
- package/dist/leader/wire.js +81 -67
- package/dist/memory-scope.d.ts +20 -9
- package/dist/memory-scope.js +15 -3
- package/dist/model-entry-headers.d.ts +9 -10
- package/dist/model-entry-headers.js +0 -4
- package/dist/model-entry-refusal.d.ts +7 -4
- package/dist/model-entry-refusal.js +11 -5
- package/dist/model-plane-face.d.ts +1 -1
- package/dist/model-plane-face.js +3 -1
- package/dist/model-route-endpoint.d.ts +13 -7
- package/dist/model-route-endpoint.js +14 -11
- package/dist/model-select.d.ts +22 -0
- package/dist/model-select.js +9 -0
- package/dist/model-unreachable-notice.d.ts +11 -0
- package/dist/model-unreachable-notice.js +4 -4
- package/dist/object-store-buckets.d.ts +640 -6
- package/dist/object-store-buckets.js +166 -0
- package/dist/observability/fail-open.d.ts +20 -16
- package/dist/observability/fail-open.js +20 -16
- package/dist/observability/logger.js +3 -1
- package/dist/operator-roster.d.ts +68 -0
- package/dist/operator-roster.js +32 -0
- package/dist/orchestration/workflow-park-decision.d.ts +1 -1
- package/dist/outbound-dispatcher.d.ts +71 -8
- package/dist/outbound-dispatcher.js +48 -7
- package/dist/owner-faces.d.ts +1 -1
- package/dist/parked-decide.d.ts +2 -2
- package/dist/permission-mode-admission.d.ts +59 -26
- package/dist/permission-mode-admission.js +37 -10
- package/dist/plugins/approval-ask-store-file.d.ts +4 -0
- package/dist/plugins/approval-ask-store-file.js +3 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +4 -0
- package/dist/plugins/approval-ask-store-memory.js +7 -1
- package/dist/plugins/approval-ask-store-sql.d.ts +26 -2
- package/dist/plugins/approval-ask-store-sql.js +12 -1
- package/dist/plugins/blob-backend.d.ts +83 -7
- package/dist/plugins/blob-backend.js +57 -6
- package/dist/plugins/checkpoint-index-columns.d.ts +20 -16
- package/dist/plugins/checkpoint-index-columns.js +3 -6
- package/dist/plugins/checkpoint-store-face.d.ts +119 -61
- package/dist/plugins/checkpoint-store-face.js +134 -55
- package/dist/plugins/landed-attendants.d.ts +2 -2
- package/dist/plugins/local-checkpoint-store.d.ts +13 -3
- package/dist/plugins/local-checkpoint-store.js +38 -49
- package/dist/plugins/one-time-migrations.d.ts +51 -21
- package/dist/plugins/one-time-migrations.js +67 -45
- package/dist/plugins/pg-pool.js +11 -9
- package/dist/plugins/remote-env-host.d.ts +29 -10
- package/dist/plugins/remote-env-host.js +99 -19
- package/dist/plugins/remote-env-k8s.d.ts +68 -2
- package/dist/plugins/remote-env-k8s.js +107 -24
- package/dist/plugins/replica-registration.d.ts +1 -1
- package/dist/plugins/replica-registration.js +1 -1
- package/dist/plugins/s3-presign.d.ts +23 -0
- package/dist/plugins/s3-presign.js +37 -3
- package/dist/plugins/scheduler-support.d.ts +2 -2
- package/dist/plugins/scheduler-support.js +0 -3
- package/dist/plugins/schema-indexes.js +2 -0
- package/dist/plugins/send-file-ledger.d.ts +2 -2
- package/dist/plugins/send-file-ledger.js +2 -2
- package/dist/plugins/send-user-file.d.ts +3 -4
- package/dist/plugins/send-user-file.js +56 -52
- package/dist/plugins/session-blob-claim-token.d.ts +15 -0
- package/dist/plugins/session-blob-claim-token.js +20 -0
- package/dist/plugins/session-blob-namespace.d.ts +46 -0
- package/dist/plugins/session-blob-namespace.js +116 -0
- package/dist/plugins/snapshot-bucket-presence.d.ts +25 -2
- package/dist/plugins/snapshot-bucket-presence.js +8 -2
- package/dist/plugins/sql-json-column.d.ts +1 -1
- package/dist/plugins/store-backend.d.ts +44 -2
- package/dist/plugins/store-backend.js +75 -53
- package/dist/plugins/task-attachment-store.d.ts +2 -1
- package/dist/plugins/task-attachment-store.js +5 -1
- package/dist/plugins/tidb-pool.js +19 -24
- package/dist/plugins/workspace-custody.d.ts +4 -3
- package/dist/question.d.ts +2 -1
- package/dist/question.js +5 -13
- package/dist/request-form.d.ts +6 -1
- package/dist/request-form.js +4 -1
- package/dist/rule-issue-reading.d.ts +1 -1
- package/dist/rules-consent.d.ts +1 -1
- package/dist/run-local.js +8 -9
- package/dist/runs.d.ts +17 -8
- package/dist/runs.js +15 -2
- package/dist/runtime-governance.d.ts +2 -2
- package/dist/sema-config-dir.d.ts +2 -1
- package/dist/session-sync.d.ts +27 -11
- package/dist/session-sync.js +15 -7
- package/dist/stores-sql/blob-index-sql.d.ts +30 -9
- package/dist/stores-sql/blob-index-sql.js +40 -21
- package/dist/stores-sql/checkpoint-store-sql.d.ts +54 -27
- package/dist/stores-sql/checkpoint-store-sql.js +70 -146
- package/dist/stores-sql/file-history-store-sql.d.ts +64 -16
- package/dist/stores-sql/file-history-store-sql.js +100 -28
- package/dist/stores-sql/retention-store-sql.js +3 -9
- package/dist/task-settings.d.ts +6 -5
- package/dist/task-settings.js +3 -3
- package/dist/tool-approval.d.ts +342 -518
- package/dist/tool-approval.js +498 -1207
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/ledger-events.d.ts +3 -3
- package/dist/trace/project.d.ts +2 -3
- package/dist/trace/redact.js +3 -2
- package/dist/trace/self-minted-wire.d.ts +287 -157
- package/dist/trace/self-minted-wire.js +93 -48
- package/dist/web-admin-client/apply-effective.js +2 -2
- package/dist/web-admin-client/read-warnings.d.ts +2 -2
- package/dist/web-admin-client/read-warnings.js +1 -1
- package/dist/web-admin-client/types.d.ts +1 -1
- package/dist/workspace-space.d.ts +10 -4
- package/dist/workspace-space.js +4 -1
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +1 -1
- package/package.json +6 -2
- package/scripts/embed-minimal.mjs +3 -3
- package/wire-changes/server@7.152.0.json +900 -0
- package/dist/config-d-migration.d.ts +0 -37
- package/dist/config-d-migration.js +0 -60
|
@@ -5,6 +5,7 @@ import { readOutboundWiring } from "../../outbound-dispatcher.js";
|
|
|
5
5
|
import { resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
6
6
|
import { sendJson, sendError } from "../send.js";
|
|
7
7
|
import { gatedPerson, explicitOperatorOk, isLoopbackAddress, sendOperatorPrincipalsRequired, verifiedIdentitySource } from "../principal-gate.js";
|
|
8
|
+
import { requestChannelOf } from "../request-channel.js";
|
|
8
9
|
import * as facts from "../../capability-facts.js";
|
|
9
10
|
import { assertNever } from "../route-ctx.js";
|
|
10
11
|
import { STORE_NOT_DURABLE_CODE, StoreNotDurableError, describeStoreRoot, projectStoreDurabilityWiring, unwiredStorePosture } from "../../store-posture.js";
|
|
@@ -12,7 +13,6 @@ import { describeLocalStoreDeclaration } from "../../local-store-declaration.js"
|
|
|
12
13
|
export function buildServerWiringGates(input) {
|
|
13
14
|
const gate = resolveStreamApprovalGate({
|
|
14
15
|
toolApprovalEnabled: input.toolApprovalEnabled,
|
|
15
|
-
streamApprovalEnabled: input.streamApprovalEnabled,
|
|
16
16
|
backend: input.backend,
|
|
17
17
|
checkpointStore: input.checkpointStore,
|
|
18
18
|
});
|
|
@@ -126,17 +126,30 @@ export function assertStaticWiringConsistent(facts, logger) {
|
|
|
126
126
|
return audit;
|
|
127
127
|
}
|
|
128
128
|
export function remoteWithoutVerifiedIdentity(req, config) {
|
|
129
|
-
return !
|
|
129
|
+
return !requestFromThisHost(req) && verifiedIdentitySource(req, config) === undefined;
|
|
130
|
+
}
|
|
131
|
+
function requestFromThisHost(req) {
|
|
132
|
+
const channel = requestChannelOf(req);
|
|
133
|
+
switch (channel) {
|
|
134
|
+
case "tcp":
|
|
135
|
+
return isLoopbackAddress(req.socket.remoteAddress);
|
|
136
|
+
case "control-socket":
|
|
137
|
+
return true;
|
|
138
|
+
default: {
|
|
139
|
+
const unknownChannel = channel;
|
|
140
|
+
throw new Error(`diagnostics: request channel ${String(unknownChannel)} has no locality reading`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
130
143
|
}
|
|
131
144
|
export async function handleDiagnostics(req, res, match, ctx) {
|
|
132
145
|
const { deps } = ctx;
|
|
133
146
|
switch (match.decl.id) {
|
|
134
147
|
case "diagnostics-wiring": {
|
|
135
|
-
if (!facts.operatorLane(deps).available) {
|
|
136
|
-
sendOperatorPrincipalsRequired(res);
|
|
148
|
+
if (!facts.operatorLane(deps, "operate").available) {
|
|
149
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
137
150
|
return;
|
|
138
151
|
}
|
|
139
|
-
if (!explicitOperatorOk(gatedPerson(req, deps.config), deps.config)) {
|
|
152
|
+
if (!explicitOperatorOk(gatedPerson(req, deps.config), deps.config, "operate")) {
|
|
140
153
|
sendError(res, 403, "auth.operator_only", "wiring diagnostics are operator-only");
|
|
141
154
|
return;
|
|
142
155
|
}
|
|
@@ -156,8 +169,7 @@ export async function handleDiagnostics(req, res, match, ctx) {
|
|
|
156
169
|
configApply: deps.configApplyState?.() ?? null,
|
|
157
170
|
mcpRevocations: deps.mcpRevocationState?.() ?? null,
|
|
158
171
|
serverGates: buildServerWiringGates({
|
|
159
|
-
toolApprovalEnabled: deps.
|
|
160
|
-
streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
|
|
172
|
+
toolApprovalEnabled: deps.config.toolApprovalEnabled === true,
|
|
161
173
|
backend: deps.backend,
|
|
162
174
|
checkpointStore: deps.checkpointStore,
|
|
163
175
|
durableApproval: deps.config.durableApproval === true,
|
|
@@ -49,13 +49,13 @@ export async function handleImages(req, res, match, ctx) {
|
|
|
49
49
|
const bakes = deps.imageBakes;
|
|
50
50
|
const idx = deps.imageIndex;
|
|
51
51
|
const principal = gatedPerson(req, deps.config);
|
|
52
|
-
const isExplicitOperator = explicitOperatorOk(principal, deps.config);
|
|
53
|
-
const operatorLane = facts.operatorLane(deps);
|
|
52
|
+
const isExplicitOperator = explicitOperatorOk(principal, deps.config, "operate");
|
|
53
|
+
const operatorLane = facts.operatorLane(deps, "operate");
|
|
54
54
|
const cfg = deps.config.imageBakes;
|
|
55
55
|
const isRunner = source !== undefined && source !== null && source === cfg.runnerPrincipal;
|
|
56
56
|
if (match.decl.id === "image-bake-submit") {
|
|
57
57
|
if (!operatorLane.available) {
|
|
58
|
-
sendOperatorPrincipalsRequired(res);
|
|
58
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
if (!isExplicitOperator) {
|
|
@@ -130,7 +130,7 @@ export async function handleImages(req, res, match, ctx) {
|
|
|
130
130
|
}
|
|
131
131
|
if (match.decl.id === "image-bake-events") {
|
|
132
132
|
if (!operatorLane.available) {
|
|
133
|
-
sendOperatorPrincipalsRequired(res);
|
|
133
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
134
134
|
return;
|
|
135
135
|
}
|
|
136
136
|
if (!isExplicitOperator) {
|
|
@@ -152,7 +152,7 @@ export async function handleImages(req, res, match, ctx) {
|
|
|
152
152
|
}
|
|
153
153
|
if (match.decl.id === "image-bake-cancel") {
|
|
154
154
|
if (!operatorLane.available) {
|
|
155
|
-
sendOperatorPrincipalsRequired(res);
|
|
155
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
156
156
|
return;
|
|
157
157
|
}
|
|
158
158
|
if (!isExplicitOperator) {
|
|
@@ -264,7 +264,7 @@ export async function handleImages(req, res, match, ctx) {
|
|
|
264
264
|
}
|
|
265
265
|
if (match.decl.id === "image-bake-detail") {
|
|
266
266
|
if (!operatorLane.available) {
|
|
267
|
-
sendOperatorPrincipalsRequired(res);
|
|
267
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
268
268
|
return;
|
|
269
269
|
}
|
|
270
270
|
if (!isExplicitOperator) {
|
|
@@ -290,7 +290,7 @@ export async function handleImages(req, res, match, ctx) {
|
|
|
290
290
|
if (pool.available && isIndexRouteId(match.decl.id)) {
|
|
291
291
|
const idx = pool.imageIndex;
|
|
292
292
|
const principal = gatedPerson(req, deps.config);
|
|
293
|
-
const operator = explicitOperatorOk(principal, deps.config);
|
|
293
|
+
const operator = explicitOperatorOk(principal, deps.config, "operate");
|
|
294
294
|
const viewer = { operator, tenantId: principal ?? null };
|
|
295
295
|
const q = new URLSearchParams(match.search);
|
|
296
296
|
if (match.decl.id === "images-index") {
|
|
@@ -340,11 +340,11 @@ export async function handleImages(req, res, match, ctx) {
|
|
|
340
340
|
return;
|
|
341
341
|
}
|
|
342
342
|
if (match.decl.id === "image-register") {
|
|
343
|
-
if (!facts.operatorLane(deps).available) {
|
|
344
|
-
sendOperatorPrincipalsRequired(res);
|
|
343
|
+
if (!facts.operatorLane(deps, "operate").available) {
|
|
344
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
345
345
|
return;
|
|
346
346
|
}
|
|
347
|
-
if (!explicitOperatorOk(principal, deps.config)) {
|
|
347
|
+
if (!explicitOperatorOk(principal, deps.config, "operate")) {
|
|
348
348
|
sendError(res, 403, "auth.operator_only", "operator only");
|
|
349
349
|
return;
|
|
350
350
|
}
|
|
@@ -73,10 +73,10 @@ export async function handleMemoryBundle(req, res, match, ctx) {
|
|
|
73
73
|
}
|
|
74
74
|
const fact = facts.memoryBundle(deps);
|
|
75
75
|
if (!fact.available && fact.why === "operator_principals_empty") {
|
|
76
|
-
sendOperatorPrincipalsRequired(res);
|
|
76
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
|
-
if (!explicitOperatorOk(principal, deps.config)) {
|
|
79
|
+
if (!explicitOperatorOk(principal, deps.config, "operate")) {
|
|
80
80
|
sendError(res, 403, "auth.operator_only", "memory governance bundles are an operator-only deployment action");
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
@@ -86,7 +86,7 @@ export async function handleMemoryBundle(req, res, match, ctx) {
|
|
|
86
86
|
sendError(res, 501, "capability.memory_engine_required", "memory governance bundles are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend implements the v2-c bundle composite (exportSnapshotOf / importBundleCommit); the file memory backend does, the pg/mysql memory backends do not");
|
|
87
87
|
return;
|
|
88
88
|
case "operator_principals_empty":
|
|
89
|
-
sendOperatorPrincipalsRequired(res);
|
|
89
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
90
90
|
return;
|
|
91
91
|
default:
|
|
92
92
|
return assertNever(fact.why);
|
|
@@ -29,10 +29,10 @@ export async function handleMemoryCompliance(req, res, match, ctx) {
|
|
|
29
29
|
}
|
|
30
30
|
const fact = facts.memoryCompliance(deps);
|
|
31
31
|
if (!fact.available && fact.why === "operator_principals_empty") {
|
|
32
|
-
sendOperatorPrincipalsRequired(res);
|
|
32
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
|
-
if (!explicitOperatorOk(principal, deps.config)) {
|
|
35
|
+
if (!explicitOperatorOk(principal, deps.config, "operate")) {
|
|
36
36
|
sendError(res, 403, "auth.operator_only", "memory provenance and erasure are operator-only deployment actions");
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
@@ -42,7 +42,7 @@ export async function handleMemoryCompliance(req, res, match, ctx) {
|
|
|
42
42
|
sendError(res, 501, "capability.memory_engine_required", "memory provenance and erasure are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/mysql memory backends do not (their control plane would land on a replica's local disk)");
|
|
43
43
|
return;
|
|
44
44
|
case "operator_principals_empty":
|
|
45
|
-
sendOperatorPrincipalsRequired(res);
|
|
45
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
46
46
|
return;
|
|
47
47
|
default:
|
|
48
48
|
return assertNever(fact.why);
|
|
@@ -85,14 +85,14 @@ export async function handleMemoryConsolidation(req, res, match, ctx) {
|
|
|
85
85
|
sendError(res, 501, "feature.memory_consolidation_disabled", "memory consolidation is switched off on this deployment (MEMORY_CONSOLIDATION_DRIVER is off, or its default derived off because no cheap model is configured); turn it on to use these endpoints");
|
|
86
86
|
return;
|
|
87
87
|
case "operator_principals_empty":
|
|
88
|
-
sendOperatorPrincipalsRequired(res);
|
|
88
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
89
89
|
return;
|
|
90
90
|
default:
|
|
91
91
|
return assertNever(fact.why);
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
const faces = fact.valve;
|
|
95
|
-
if (!explicitOperatorOk(principal, deps.config)) {
|
|
95
|
+
if (!explicitOperatorOk(principal, deps.config, "operate")) {
|
|
96
96
|
sendError(res, 403, "auth.operator_only", "memory consolidation is operator-only: the caller principal is not in OPERATOR_PRINCIPALS");
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
@@ -118,10 +118,10 @@ export async function handleMemoryOrigin(req, res, match, ctx) {
|
|
|
118
118
|
}
|
|
119
119
|
const fact = facts.memoryOrigin(deps);
|
|
120
120
|
if (!fact.available && fact.why === "operator_principals_empty") {
|
|
121
|
-
sendOperatorPrincipalsRequired(res);
|
|
121
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
|
-
if (!explicitOperatorOk(principal, deps.config)) {
|
|
124
|
+
if (!explicitOperatorOk(principal, deps.config, "operate")) {
|
|
125
125
|
sendError(res, 403, "auth.operator_only", "memory origin marking and clearance are operator-only deployment actions");
|
|
126
126
|
return;
|
|
127
127
|
}
|
|
@@ -131,7 +131,7 @@ export async function handleMemoryOrigin(req, res, match, ctx) {
|
|
|
131
131
|
sendError(res, 501, "capability.memory_engine_required", "memory origin marking and clearance are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the clearance ledger and its write-ahead custody rows live in that plane, and on a backend without it they would land on one replica's local disk");
|
|
132
132
|
return;
|
|
133
133
|
case "operator_principals_empty":
|
|
134
|
-
sendOperatorPrincipalsRequired(res);
|
|
134
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
135
135
|
return;
|
|
136
136
|
default:
|
|
137
137
|
return assertNever(fact.why);
|
|
@@ -9,7 +9,7 @@ import { gatedPerson, explicitOperatorOk, sendPrincipalRefusal, sendUnauthorized
|
|
|
9
9
|
import { assertNever } from "../route-ctx.js";
|
|
10
10
|
import { sentMemoryFaceFault } from "../memory-face-fault-reply.js";
|
|
11
11
|
function layerFaceAudience(deps, principal, face, scope) {
|
|
12
|
-
if (explicitOperatorOk(principal, deps.config))
|
|
12
|
+
if (explicitOperatorOk(principal, deps.config, "operate"))
|
|
13
13
|
return { kind: "operator" };
|
|
14
14
|
return { kind: "layer_owner", face, withheld: (coreMessage) => deps.logger?.warn?.(face === "export" ? "memory_export_refused" : "memory_sync_refused", { scope, err: coreMessage }) };
|
|
15
15
|
}
|
|
@@ -44,7 +44,7 @@ function directoryDecidesThisFace(mode) {
|
|
|
44
44
|
async function gateMemoryScope(deps, person, scope, need) {
|
|
45
45
|
if (scope === formatUserScope(person) || parseProjectLayerKey(scope)?.owner === person)
|
|
46
46
|
return { kind: "allow" };
|
|
47
|
-
if (explicitOperatorOk(person, deps.config))
|
|
47
|
+
if (explicitOperatorOk(person, deps.config, "operate"))
|
|
48
48
|
return { kind: "allow" };
|
|
49
49
|
const directory = deps.orgMemoryDirectory;
|
|
50
50
|
if (!scope.startsWith(ORG_PREFIX) || directory === undefined || !directoryDecidesThisFace(deps.config.memoryOrgAdmissionMode)) {
|
|
@@ -9,10 +9,10 @@ export async function handleObservability(req, res, match, ctx) {
|
|
|
9
9
|
case "outcomes": {
|
|
10
10
|
const fact = facts.outcomeLedger(deps);
|
|
11
11
|
if (!fact.available && fact.why === "operator_principals_empty") {
|
|
12
|
-
sendOperatorPrincipalsRequired(res);
|
|
12
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
|
-
if (!explicitOperatorOk(gatedPerson(req, deps.config), deps.config)) {
|
|
15
|
+
if (!explicitOperatorOk(gatedPerson(req, deps.config), deps.config, "operate")) {
|
|
16
16
|
sendError(res, 403, "auth.operator_only", "the outcomes view is operator-only");
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
@@ -22,7 +22,7 @@ export async function handleObservability(req, res, match, ctx) {
|
|
|
22
22
|
sendError(res, 501, "capability.outcome_ledger_required", "no queryable outcome ledger on this worker (File sink — read the JSONL dataset directly)");
|
|
23
23
|
return;
|
|
24
24
|
case "operator_principals_empty":
|
|
25
|
-
sendOperatorPrincipalsRequired(res);
|
|
25
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
26
26
|
return;
|
|
27
27
|
default:
|
|
28
28
|
return assertNever(fact.why);
|
|
@@ -33,11 +33,11 @@ export async function handleRetention(req, res, match, ctx) {
|
|
|
33
33
|
sendPrincipalRefusal(res, req, deps.config);
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
-
if (!facts.operatorLane(deps).available) {
|
|
37
|
-
sendOperatorPrincipalsRequired(res);
|
|
36
|
+
if (!facts.operatorLane(deps, "operate").available) {
|
|
37
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
|
-
if (!explicitOperatorOk(principal, deps.config)) {
|
|
40
|
+
if (!explicitOperatorOk(principal, deps.config, "operate")) {
|
|
41
41
|
sendError(res, 403, "auth.operator_only", OPERATOR_ONLY_MESSAGE);
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
@@ -242,9 +242,9 @@ function sendImportReceipt(res, outcome) {
|
|
|
242
242
|
function resolveTargetPrincipal(requested, caller, config) {
|
|
243
243
|
if (requested === undefined || requested === caller)
|
|
244
244
|
return { ok: true, principal: caller };
|
|
245
|
-
if (!facts.operatorLane({ config }).available)
|
|
245
|
+
if (!facts.operatorLane({ config }, "operate").available)
|
|
246
246
|
return { ok: false, why: "operator_principals_empty" };
|
|
247
|
-
if (!explicitOperatorOk(caller, config))
|
|
247
|
+
if (!explicitOperatorOk(caller, config, "operate"))
|
|
248
248
|
return { ok: false, why: "not_operator" };
|
|
249
249
|
if (principalShapeProblem(requested) !== undefined)
|
|
250
250
|
return { ok: false, why: "not_a_principal" };
|
|
@@ -259,7 +259,7 @@ async function handleRuleList(res, search, caller, lane, config) {
|
|
|
259
259
|
if (!target.ok) {
|
|
260
260
|
switch (target.why) {
|
|
261
261
|
case "operator_principals_empty":
|
|
262
|
-
sendOperatorPrincipalsRequired(res);
|
|
262
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
263
263
|
return;
|
|
264
264
|
case "not_operator":
|
|
265
265
|
sendError(res, 403, "auth.operator_only", "reading another principal's rules is an operator-only action");
|
|
@@ -382,7 +382,7 @@ async function handleRuleWrite(res, rawBody, caller, lane, config) {
|
|
|
382
382
|
if (!target.ok) {
|
|
383
383
|
switch (target.why) {
|
|
384
384
|
case "operator_principals_empty":
|
|
385
|
-
sendOperatorPrincipalsRequired(res);
|
|
385
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
386
386
|
return;
|
|
387
387
|
case "not_operator":
|
|
388
388
|
sendError(res, 403, "auth.operator_only", "writing another principal's rules is an operator-only action");
|
|
@@ -434,7 +434,7 @@ async function handleRuleRevoke(res, rawBody, caller, lane, config) {
|
|
|
434
434
|
if (!target.ok) {
|
|
435
435
|
switch (target.why) {
|
|
436
436
|
case "operator_principals_empty":
|
|
437
|
-
sendOperatorPrincipalsRequired(res);
|
|
437
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
438
438
|
return;
|
|
439
439
|
case "not_operator":
|
|
440
440
|
sendError(res, 403, "auth.operator_only", "revoking another principal's rules is an operator-only action");
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
* 拒 ⇒ 422 `request.host_posture_refused`(与提交口同码同句)。
|
|
16
16
|
*
|
|
17
17
|
* ── 回写:准入的词写回存储体(run 模式词的唯一来源)────────────────────────────────────────────────────────
|
|
18
|
-
* 续跑腿(停驻后续跑 / 重启后续跑)按存储体顶层 `permissionMode` 重建 spec;不写回 =
|
|
18
|
+
* 续跑腿(停驻后续跑 / 重启后续跑)按存储体顶层 `permissionMode` 重建 spec;不写回 = 下一腿回到切档前的词。词与写据经改写口一笔换(写据 `switch`:
|
|
19
|
+
* 切档写下的词不是任何地板的出口,运维事后改严时续跑口照钳 —— `permission-mode-admission.ts` 顶注「写据」)。写回走**认领栅栏**
|
|
19
20
|
* (checkpoint 店的 `rewriteCtxWhileClaimed`:只在**接下切档的那条腿**此刻仍持有这条 run 时写,只改既有行)—— 这条 run 终局放掉认领、同会话新 run
|
|
20
21
|
* 已写下它自己的存储体之后,或这条 run 停驻、续跑的新腿(同一个 taskId 回到 running、认领仍在)已写下它续跑用的体之后,迟到的回写零写,
|
|
21
22
|
* 不会拿旧腿的体盖掉新腿的。腿身份 = {@link switchLegHolds}(栅栏里问)。同一条腿上的切档在本进程按活流对象串行(引擎受理序 = 落盘序;活流只在本副本)。
|
|
@@ -5,7 +5,7 @@ import { admitRunById, enter } from "../owner-access.js";
|
|
|
5
5
|
import { assertNever } from "../route-ctx.js";
|
|
6
6
|
import * as facts from "../../capability-facts.js";
|
|
7
7
|
import { PERMISSION_MODE_WORDS, coercePermissionModeStrict, parseTaskSettings } from "../../task-settings.js";
|
|
8
|
-
import { admitPermissionModeWord } from "../../permission-mode-admission.js";
|
|
8
|
+
import { admitPermissionModeWord, withStoredPermissionMode } from "../../permission-mode-admission.js";
|
|
9
9
|
import { readResumeCtx, resumeCtxCorruptAnswer, resumeCtxUnreadableAnswer, resumeCtxUnsealableAnswer } from "../resume-ctx-read.js";
|
|
10
10
|
import { sealCheckpointCtx } from "../checkpoint-ctx.js";
|
|
11
11
|
import { serviceVersion } from "../../service-version.js";
|
|
@@ -114,7 +114,7 @@ export async function handlePermissionMode(req, res, match, ctx) {
|
|
|
114
114
|
}
|
|
115
115
|
let persistMiss;
|
|
116
116
|
try {
|
|
117
|
-
const next = sealCheckpointCtx({ ...stored,
|
|
117
|
+
const next = sealCheckpointCtx({ ...withStoredPermissionMode(stored, admitted, "switch"), writerVersion: serviceVersion() }, deps.checkpointCtxSealing, sessionId);
|
|
118
118
|
const rewrite = await checkpointStore.rewriteCtxWhileClaimed(sessionId, next, { fence: runStore, taskId, legHolds: (fenced) => switchLegHolds(fenced, deps.instanceId, ctx.registry.steerableRuns.get(taskId), live) });
|
|
119
119
|
persistMiss = rewrite === "written" ? undefined : rewrite;
|
|
120
120
|
}
|
|
@@ -129,20 +129,15 @@ export declare const RUN_VERBS_ROUTES: readonly [{
|
|
|
129
129
|
readonly label: "/v1/questions/:id/respond";
|
|
130
130
|
readonly methods: readonly ["POST"];
|
|
131
131
|
}, {
|
|
132
|
-
readonly id: "
|
|
132
|
+
readonly id: "task-ask-decision";
|
|
133
133
|
readonly pattern: RegExp;
|
|
134
|
-
readonly label: "/v1/
|
|
134
|
+
readonly label: "/v1/tasks/:id/asks/:askId/decision";
|
|
135
135
|
readonly methods: readonly ["POST"];
|
|
136
136
|
}, {
|
|
137
|
-
readonly id: "
|
|
137
|
+
readonly id: "task-ask-read";
|
|
138
138
|
readonly pattern: RegExp;
|
|
139
|
-
readonly label: "/v1/
|
|
139
|
+
readonly label: "/v1/tasks/:id/asks/:askId";
|
|
140
140
|
readonly methods: readonly ["GET"];
|
|
141
|
-
}, {
|
|
142
|
-
readonly id: "task-ask-decision";
|
|
143
|
-
readonly pattern: RegExp;
|
|
144
|
-
readonly label: "/v1/tasks/:id/asks/:askId/decision";
|
|
145
|
-
readonly methods: readonly ["POST"];
|
|
146
141
|
}];
|
|
147
142
|
export type RunVerbsRouteId = RouteIdsOf<typeof RUN_VERBS_ROUTES>;
|
|
148
143
|
//# sourceMappingURL=runs.d.ts.map
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -15,9 +15,9 @@ import { defaultSubagentTailBus } from "../../fleet/subagent-tail-bus.js";
|
|
|
15
15
|
import { idemCallerOf, idemKeyOf, inflightKeyOf, submissionIdemOf } from "../idempotency.js";
|
|
16
16
|
import { streamSseLog } from "../sse-log.js";
|
|
17
17
|
import { bindSseLifecycle } from "../sse-lifecycle.js";
|
|
18
|
-
import { buildApprovalPreamble, buildApprovalPreambleSseFrames, readClosedWordSeat
|
|
18
|
+
import { buildApprovalPreamble, buildApprovalPreambleSseFrames, readClosedWordSeat } from "../../approval-card.js";
|
|
19
19
|
import { heldAskOf, readDecider, retiredDecisionKeyFacts, DECIDED_BY_NOT_IN_PROOF, POLICY_CANNOT_ALLOW_PERSON_ASK } from "../../host-decision.js";
|
|
20
|
-
import { ASK_DECISION_CONSUMER_ABSENT, approvalPreambleReplays, decidedRowFacts, outcomeOfDecidedRow, parkSessionOf, presentedApprovalFrameFamily, resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
20
|
+
import { ASK_DECISION_CONSUMER_ABSENT, ASK_NOT_FOUND_UNDER_TASK, approvalPreambleReplays, decidedRowFacts, outcomeOfDecidedRow, parkSessionOf, presentedApprovalFrameFamily, readPersistRuleSlot, readReadRootGrantSlot, resolveStreamApprovalGate, READ_ROOT_GRANT_LIVE_ONLY, READ_ROOT_GRANT_NOT_ACCEPTED } from "../../tool-approval.js";
|
|
21
21
|
import { MAX_DECISION_NOTE_CHARS, noteLandedOnRow, noteRecordedReceipt } from "../../approval-ask-machine.js";
|
|
22
22
|
import { askRowEndingCause } from "../../approval-deny-reasons.js";
|
|
23
23
|
import { normalizeRunEventType, screenedStoredEventData } from "../../trace/project.js";
|
|
@@ -28,7 +28,7 @@ import { INJECTION_TIERS } from "../../trace/injection-tier.js";
|
|
|
28
28
|
import { headerStr, gatedSubject, explicitOperatorOk, sendPrincipalRefusal, runIdentityOf } from "../principal-gate.js";
|
|
29
29
|
import { ActorAssertionWire } from "@sema-agent/settings-schema";
|
|
30
30
|
import * as facts from "../../capability-facts.js";
|
|
31
|
-
import { assertNever } from "../route-ctx.js";
|
|
31
|
+
import { assertNever, readRememberSlot } from "../route-ctx.js";
|
|
32
32
|
import { readRunFace } from "../run-read-face.js";
|
|
33
33
|
import { replayOfExistingRun, replayOfKey } from "../submit-replay.js";
|
|
34
34
|
import { mintAndDriveDurableRun } from "../durable-run-leg.js";
|
|
@@ -49,6 +49,7 @@ const STEER_PRIORITY_UNION = STEER_PRIORITY_WORDS.join(" | ");
|
|
|
49
49
|
const STEER_PRIORITY_CHOICES = STEER_PRIORITY_WORDS.length <= 2 ? STEER_PRIORITY_WORDS.join(" or ") : `${STEER_PRIORITY_WORDS.slice(0, -1).join(", ")}, or ${STEER_PRIORITY_WORDS[STEER_PRIORITY_WORDS.length - 1]}`;
|
|
50
50
|
const ASK_PARKING_RETRY_AFTER_SEC = 2;
|
|
51
51
|
const ASK_ACTOR_LABEL_MAX = 256;
|
|
52
|
+
const ASK_LEDGER_DISABLED = "the durable stream-approval ask ledger is not enabled on this worker (needs a store backend (DB_BACKEND) and DURABLE_APPROVAL)";
|
|
52
53
|
const AskDecisionActorSchema = ActorAssertionWire.strip();
|
|
53
54
|
function mirrorRetryAfter(res, body) {
|
|
54
55
|
const retryAfterSec = body !== null && typeof body === "object" ? body.retryAfterSec : undefined;
|
|
@@ -69,6 +70,9 @@ const AskDecisionBodySchema = z
|
|
|
69
70
|
actor: z.object({ label: z.string().max(ASK_ACTOR_LABEL_MAX) }).strict().optional(),
|
|
70
71
|
decidedBy: z.unknown().optional(),
|
|
71
72
|
decidedVia: z.unknown().optional(),
|
|
73
|
+
remember: z.unknown().optional(),
|
|
74
|
+
persistRule: z.unknown().optional(),
|
|
75
|
+
readRootGrant: z.unknown().optional(),
|
|
72
76
|
})
|
|
73
77
|
.strict();
|
|
74
78
|
const ASK_DECISION_CONFIRM_TIMEOUT_MS = 2_000;
|
|
@@ -116,18 +120,19 @@ async function streamRunEvents(req, res, deps, runStore, entry, taskId) {
|
|
|
116
120
|
preamble: async (signal) => {
|
|
117
121
|
const gate = resolveStreamApprovalGate({
|
|
118
122
|
toolApprovalEnabled: Boolean(deps.toolApproval),
|
|
119
|
-
streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
|
|
120
123
|
backend: deps.backend,
|
|
121
124
|
checkpointStore: deps.checkpointStore,
|
|
122
125
|
});
|
|
123
126
|
if (!gate.active)
|
|
124
127
|
return [];
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
const presented = presentedApprovalFrameFamily(gate.active);
|
|
129
|
+
if (presented === undefined ||
|
|
130
|
+
!approvalPreambleReplays({
|
|
131
|
+
streamApprovalOn: gate.active,
|
|
132
|
+
windowMs: deps.config.streamApproval?.windowMs,
|
|
133
|
+
windowMarginMs: deps.config.streamAskWindowMarginMs,
|
|
134
|
+
declaredFamilies: new Set([presented]),
|
|
135
|
+
})) {
|
|
131
136
|
return [];
|
|
132
137
|
}
|
|
133
138
|
const store = gate.askStore;
|
|
@@ -1174,12 +1179,12 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1174
1179
|
sendJson(res, status, respBody);
|
|
1175
1180
|
return;
|
|
1176
1181
|
}
|
|
1177
|
-
if (match.decl.id === "
|
|
1182
|
+
if (match.decl.id === "task-ask-read") {
|
|
1178
1183
|
if (rateLimited(req, res))
|
|
1179
1184
|
return;
|
|
1180
|
-
const
|
|
1181
|
-
if (!
|
|
1182
|
-
sendError(res, 501, "feature.
|
|
1185
|
+
const streamFact = facts.streamApproval(deps);
|
|
1186
|
+
if (!streamFact.available) {
|
|
1187
|
+
sendError(res, 501, "feature.approval_ask_disabled", ASK_LEDGER_DISABLED);
|
|
1183
1188
|
return;
|
|
1184
1189
|
}
|
|
1185
1190
|
const principal = gatedSubject(req, deps.config);
|
|
@@ -1187,15 +1192,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1187
1192
|
sendPrincipalRefusal(res, req, deps.config);
|
|
1188
1193
|
return;
|
|
1189
1194
|
}
|
|
1190
|
-
const
|
|
1191
|
-
if (match.decl.id === "tool-approval-read") {
|
|
1192
|
-
const { status, body: respBody } = await approvalFact.coordinator.readApproval(id, principal);
|
|
1193
|
-
sendJson(res, status, respBody);
|
|
1194
|
-
return;
|
|
1195
|
-
}
|
|
1196
|
-
const body = await readJson(req);
|
|
1197
|
-
const { status, body: respBody } = await approvalFact.coordinator.respond(id, principal, body, req, { readRootGrantAccepted: facts.readRootGrant(deps).available });
|
|
1198
|
-
mirrorRetryAfter(res, respBody);
|
|
1195
|
+
const { status, body: respBody } = await streamFact.coordinator.readAsk(match.params.taskId, match.params.askId, principal);
|
|
1199
1196
|
sendJson(res, status, respBody);
|
|
1200
1197
|
return;
|
|
1201
1198
|
}
|
|
@@ -1204,7 +1201,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1204
1201
|
return;
|
|
1205
1202
|
const streamFact = facts.streamApproval(deps);
|
|
1206
1203
|
if (!streamFact.available) {
|
|
1207
|
-
sendError(res, 501, "feature.approval_ask_disabled",
|
|
1204
|
+
sendError(res, 501, "feature.approval_ask_disabled", ASK_LEDGER_DISABLED);
|
|
1208
1205
|
return;
|
|
1209
1206
|
}
|
|
1210
1207
|
const askStore = streamFact.askStore;
|
|
@@ -1222,7 +1219,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1222
1219
|
sendError(res, 400, "request.body_shape", retired.error, retired.extra);
|
|
1223
1220
|
return;
|
|
1224
1221
|
}
|
|
1225
|
-
sendError(res, 400, "request.body_shape", "invalid ask decision body — expected decision=approve|deny plus optional updatedInput / note / idempotencyKey / actor.label / decidedBy / decidedVia");
|
|
1222
|
+
sendError(res, 400, "request.body_shape", "invalid ask decision body — expected decision=approve|deny plus optional updatedInput / note / idempotencyKey / actor.label / decidedBy / decidedVia / remember / persistRule / readRootGrant");
|
|
1226
1223
|
return;
|
|
1227
1224
|
}
|
|
1228
1225
|
const body = parsedBody.data;
|
|
@@ -1242,16 +1239,38 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1242
1239
|
}
|
|
1243
1240
|
}
|
|
1244
1241
|
const decider = decidedByReading.decider;
|
|
1242
|
+
const rememberSlot = readRememberSlot(body.remember, body.decision);
|
|
1243
|
+
if (rememberSlot.kind === "refused") {
|
|
1244
|
+
sendError(res, 400, rememberSlot.refusal.errorCode, rememberSlot.refusal.message, rememberSlot.refusal.facts);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
const remember = rememberSlot.kind === "session" ? "session" : undefined;
|
|
1248
|
+
const ruleSlot = readPersistRuleSlot(body.persistRule, body.decision === "approve" ? "allow" : "deny");
|
|
1249
|
+
if (!ruleSlot.ok) {
|
|
1250
|
+
sendError(res, 400, "request.body_shape", ruleSlot.error);
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
const persistRule = ruleSlot.slot;
|
|
1254
|
+
const grantSlot = readReadRootGrantSlot(body.readRootGrant, body.decision === "approve" && remember === undefined);
|
|
1255
|
+
if (!grantSlot.ok) {
|
|
1256
|
+
sendError(res, 400, "request.body_shape", grantSlot.error);
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
if (grantSlot.grant !== undefined && !facts.readRootGrant(deps).available) {
|
|
1260
|
+
sendError(res, 400, "request.body_shape", READ_ROOT_GRANT_NOT_ACCEPTED);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
const readRootGrant = grantSlot.grant;
|
|
1245
1264
|
const hasUpdatedInput = typeof rawBody === "object" && rawBody !== null && "updatedInput" in rawBody;
|
|
1246
1265
|
const taskId = match.params.taskId;
|
|
1247
1266
|
const askId = match.params.askId;
|
|
1248
|
-
const askNotFound = () => sendError(res, 404, "not_found.ask",
|
|
1267
|
+
const askNotFound = () => sendError(res, 404, "not_found.ask", ASK_NOT_FOUND_UNDER_TASK);
|
|
1249
1268
|
const row0 = await askStore.getAsk(askId);
|
|
1250
1269
|
if (!row0 || row0.taskId !== taskId) {
|
|
1251
1270
|
askNotFound();
|
|
1252
1271
|
return;
|
|
1253
1272
|
}
|
|
1254
|
-
const callerIsOperator = explicitOperatorOk(principal === undefined ? undefined : personOf(principal), deps.config);
|
|
1273
|
+
const callerIsOperator = explicitOperatorOk(principal === undefined ? undefined : personOf(principal), deps.config, "operate");
|
|
1255
1274
|
const askAdmission = keyAdmission("ask.decide", { subject: principal, operator: callerIsOperator }, ownerCellOf(row0.owner));
|
|
1256
1275
|
if (askAdmission === undefined) {
|
|
1257
1276
|
askNotFound();
|
|
@@ -1304,7 +1323,8 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1304
1323
|
...noteReceipt(row),
|
|
1305
1324
|
});
|
|
1306
1325
|
};
|
|
1307
|
-
const
|
|
1326
|
+
const heldLeg = coordinator.holdLiveLeg(askId);
|
|
1327
|
+
const policyMayNotApprove = (row) => body.decision === "approve" && policyCannotAnswer(heldAskOf(coordinator.askDemandsAPerson(row, heldLeg)), decider.decidedBy);
|
|
1308
1328
|
const refusePolicyApprove = () => sendError(res, 400, "request.field_conflict", POLICY_CANNOT_ALLOW_PERSON_ASK, fieldConflictFacts("policy_person_ask", ["decidedBy", "decision"]));
|
|
1309
1329
|
const conflictWithFirst = (row) => {
|
|
1310
1330
|
const first = projectDecisionActor(row.decisionActor);
|
|
@@ -1319,6 +1339,8 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1319
1339
|
const answer = await coordinator.decideParkedAsk(askStore, row, {
|
|
1320
1340
|
write: { decision: body.decision, decider, ...(body.note !== undefined ? { note: body.note } : {}), actor, ...(body.idempotencyKey !== undefined ? { idempotencyKey: body.idempotencyKey } : {}) },
|
|
1321
1341
|
...(body.decision === "approve" && hasUpdatedInput ? { updatedInput: body.updatedInput } : {}),
|
|
1342
|
+
...(remember !== undefined ? { remember } : {}),
|
|
1343
|
+
...(persistRule !== undefined ? { persistRule } : {}),
|
|
1322
1344
|
...(principal !== undefined ? { principal } : {}),
|
|
1323
1345
|
operator: callerIsOperator,
|
|
1324
1346
|
policyMayNotApprove: policyMayNotApprove(row),
|
|
@@ -1366,6 +1388,8 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1366
1388
|
accepted: "parked_redeemed",
|
|
1367
1389
|
...decisionNoteEcho(recorded),
|
|
1368
1390
|
...noteReceipt(recorded),
|
|
1391
|
+
...(answer.rememberApplied !== undefined ? { rememberApplied: answer.rememberApplied } : {}),
|
|
1392
|
+
...(answer.rule ?? {}),
|
|
1369
1393
|
});
|
|
1370
1394
|
return;
|
|
1371
1395
|
}
|
|
@@ -1388,6 +1412,10 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1388
1412
|
return;
|
|
1389
1413
|
}
|
|
1390
1414
|
case "PARKED":
|
|
1415
|
+
if (readRootGrant !== undefined) {
|
|
1416
|
+
sendError(res, 400, "request.body_shape", READ_ROOT_GRANT_LIVE_ONLY);
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1391
1419
|
await answerParked(row);
|
|
1392
1420
|
return;
|
|
1393
1421
|
case "DENIED":
|
|
@@ -1440,8 +1468,13 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1440
1468
|
refusePolicyApprove();
|
|
1441
1469
|
return;
|
|
1442
1470
|
}
|
|
1471
|
+
const ruleTextRefusal = persistRule !== undefined ? coordinator.precheckEditedRuleOfAskRow(row0, row0.owner, persistRule, hasUpdatedInput) : undefined;
|
|
1472
|
+
if (ruleTextRefusal !== undefined) {
|
|
1473
|
+
sendError(res, 400, "request.body_shape", ruleTextRefusal.message, ruleTextRefusal.ruleRejectCode !== undefined ? { ruleRejectCode: ruleTextRefusal.ruleRejectCode } : undefined);
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1443
1476
|
let outcome;
|
|
1444
|
-
const releaseEditMark = body.decision === "approve" && hasUpdatedInput ? deps.toolApproval?.markEditedDecisionInFlight(askId) : undefined;
|
|
1477
|
+
const releaseEditMark = body.decision === "approve" && (hasUpdatedInput || readRootGrant !== undefined) ? deps.toolApproval?.markEditedDecisionInFlight(askId) : undefined;
|
|
1445
1478
|
try {
|
|
1446
1479
|
outcome = await coordinator.writeAskDecision(askStore, askId, { kind: "live", batchId: row0.batchId }, row0.owner, {
|
|
1447
1480
|
decision: body.decision,
|
|
@@ -1473,7 +1506,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1473
1506
|
confirmed.decision === body.decision &&
|
|
1474
1507
|
confirmed.idempotencyKey === body.idempotencyKey &&
|
|
1475
1508
|
!confirmed.provisional) {
|
|
1476
|
-
const settledNow = deps.toolApproval.notifyExternalDecision(askId, outcomeOfDecidedRow(confirmed)).settled;
|
|
1509
|
+
const settledNow = deps.toolApproval.notifyExternalDecision(askId, outcomeOfDecidedRow(confirmed), readRootGrant).settled;
|
|
1477
1510
|
deps.logger?.warn?.("ask_decision_edit_settled_after_ambiguous_commit", { taskId, askId, settled: settledNow });
|
|
1478
1511
|
}
|
|
1479
1512
|
}
|
|
@@ -1520,7 +1553,13 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1520
1553
|
sendError(res, 500, "internal.error", "the ask row was rewritten between commit and read-back — the decision that stands is not the one this request submitted; re-read the ask before acting on it");
|
|
1521
1554
|
return;
|
|
1522
1555
|
}
|
|
1523
|
-
const
|
|
1556
|
+
const live = await coordinator.settleLiveDecision(outcome.row, heldLeg, row0.owner, principal, {
|
|
1557
|
+
...(remember !== undefined ? { remember } : {}),
|
|
1558
|
+
...(readRootGrant !== undefined ? { readRootGrant } : {}),
|
|
1559
|
+
...(persistRule !== undefined ? { persistRule } : {}),
|
|
1560
|
+
inputWasEdited: hasUpdatedInput,
|
|
1561
|
+
});
|
|
1562
|
+
const settled = live.settled;
|
|
1524
1563
|
const forwarded = outcome.row.decision === "approve" && outcome.row.updatedInput !== undefined && settled > 0;
|
|
1525
1564
|
sendJson(res, 200, {
|
|
1526
1565
|
askId: outcome.row.askId,
|
|
@@ -1532,6 +1571,8 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1532
1571
|
...(settled === 0 ? { consumerState: ASK_DECISION_CONSUMER_ABSENT } : {}),
|
|
1533
1572
|
...decisionNoteEcho(outcome.row),
|
|
1534
1573
|
...noteReceipt(outcome.row),
|
|
1574
|
+
...(live.rememberApplied !== undefined ? { rememberApplied: live.rememberApplied } : {}),
|
|
1575
|
+
...(live.rule ?? {}),
|
|
1535
1576
|
});
|
|
1536
1577
|
return;
|
|
1537
1578
|
}
|
|
@@ -1620,8 +1661,7 @@ export const RUN_VERBS_ROUTES = [
|
|
|
1620
1661
|
{ id: "run-task-stop", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/tasks\/(?<tid>[^/]+)\/stop$/, label: "/v1/runs/:id/tasks/:tid/*", methods: ["POST"] },
|
|
1621
1662
|
{ id: "elicitation-respond", pattern: /^\/v1\/elicitations\/(?<id>[^/]+)\/respond$/, label: "/v1/elicitations/:id/respond", methods: ["POST"] },
|
|
1622
1663
|
{ id: "question-respond", pattern: /^\/v1\/questions\/(?<id>[^/]+)\/respond$/, label: "/v1/questions/:id/respond", methods: ["POST"] },
|
|
1623
|
-
{ id: "tool-approval-respond", pattern: /^\/v1\/tool-approvals\/(?<id>[^/]+)\/respond$/, label: "/v1/tool-approvals/:id/respond", methods: ["POST"] },
|
|
1624
|
-
{ id: "tool-approval-read", pattern: /^\/v1\/tool-approvals\/(?<id>[^/]+)$/, label: "/v1/tool-approvals/:id", methods: ["GET"] },
|
|
1625
1664
|
{ id: "task-ask-decision", pattern: /^\/v1\/tasks\/(?<taskId>[^/]+)\/asks\/(?<askId>[^/]+)\/decision$/, label: "/v1/tasks/:id/asks/:askId/decision", methods: ["POST"] },
|
|
1665
|
+
{ id: "task-ask-read", pattern: /^\/v1\/tasks\/(?<taskId>[^/]+)\/asks\/(?<askId>[^/]+)$/, label: "/v1/tasks/:id/asks/:askId", methods: ["GET"] },
|
|
1626
1666
|
];
|
|
1627
1667
|
//# sourceMappingURL=runs.js.map
|