@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
package/dist/http/route-table.js
CHANGED
|
@@ -122,6 +122,7 @@ const SHAPED_LABEL_DECLS = ROUTE_DECLARATIONS.filter((d) => !("path" in d) && ty
|
|
|
122
122
|
const BILLABLE_DECLS = ROUTE_DECLARATIONS.filter((d) => d.billable !== undefined);
|
|
123
123
|
const METHOD_DISPATCHED_DECLS = ROUTE_DECLARATIONS.filter((d) => d.methodDispatched === true);
|
|
124
124
|
const SUBSCRIPTION_DECLS = ROUTE_DECLARATIONS.filter(isDispatchDecl).filter((d) => d.subscription === true);
|
|
125
|
+
const POWER_DECLS = ROUTE_DECLARATIONS.filter(isDispatchDecl).filter((d) => d.power !== undefined);
|
|
125
126
|
const CREDENTIAL_GATED_BY_METHOD = (() => {
|
|
126
127
|
const m = new Map();
|
|
127
128
|
for (const d of ROUTE_DECLARATIONS)
|
|
@@ -156,6 +157,9 @@ export function isCredentialGatedRewrite(method, url) {
|
|
|
156
157
|
export function isSubscriptionRequest(method, url) {
|
|
157
158
|
return SUBSCRIPTION_DECLS.some((d) => d.methods.includes(method) && matchDecl(d, url) !== null);
|
|
158
159
|
}
|
|
160
|
+
export function declaredPowerOf(method, url) {
|
|
161
|
+
return POWER_DECLS.find((d) => d.methods.includes(method) && matchDecl(d, url) !== null)?.power;
|
|
162
|
+
}
|
|
159
163
|
export function isMethodDispatchedSubmitPath(url) {
|
|
160
164
|
return METHOD_DISPATCHED_DECLS.some((d) => declMatches(d, url));
|
|
161
165
|
}
|
|
@@ -13,11 +13,11 @@ export async function handleAdminConfigRefresh(req, res, match, ctx) {
|
|
|
13
13
|
sendPrincipalRefusal(res, req, deps.config);
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
|
-
if (!facts.operatorLane(deps).available) {
|
|
17
|
-
sendOperatorPrincipalsRequired(res);
|
|
16
|
+
if (!facts.operatorLane(deps, "operate").available) {
|
|
17
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
-
if (!explicitOperatorOk(principal, deps.config)) {
|
|
20
|
+
if (!explicitOperatorOk(principal, deps.config, "operate")) {
|
|
21
21
|
sendError(res, 403, "auth.operator_only", OPERATOR_ONLY_MESSAGE);
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* 进入排空经 `drainState.enter`(收尾链装的唯一入口)。
|
|
7
7
|
* · 门序 = operator 面的既有口径(`routes/retention-ops.ts` 逐字):身份 → 授权 → 验型;billable=false,**刻意不吃排空 503**
|
|
8
8
|
* (排空中的重放必须能拿到 `already_draining` 回执,吃了就自锁)。
|
|
9
|
+
* · 授权 = 持 `drain` 权力(声明行的 `power` 列,本口的门与分派层的圈禁判读同一格):全运维档(`OPERATOR_PRINCIPALS`,持每一只权力)∪ 排空档
|
|
10
|
+
* (`OPERATOR_DRAIN_PRINCIPALS`,服务主体只持这一只 —— 它的身份只在本口算数,别的口 403 `auth.principal_confined`,见 `operator-roster.ts`)。
|
|
9
11
|
* · 判序:体坏(含动作号不是标识符形 —— settings-schema 11 `DRAIN_ACTION_ID`)⇒ 400 `request.field_invalid`(零状态变化)→ 没有收尾链(嵌入宿主 / 测试夹具)⇒ 501 `capability.drain_state_required`
|
|
10
12
|
* → 身份不符 ⇒ 409 `conflict.fleet_identity`(零状态变化;拒句点名不符的字段,不回显值)→ 入口:已在排空 / 硬停已在进行 ⇒ 200 回执
|
|
11
13
|
* (这一发的期限不会被采用);这一发会进入排空而 `deadlineAt` 离进入那一拍超过 `FLEET_LEASE_OUTAGE_MAX_MS`(`config.fleetLeaseOutageMaxMs`,
|
|
@@ -24,6 +26,7 @@ export declare const ADMIN_DRAIN_ROUTES: readonly [{
|
|
|
24
26
|
readonly id: "admin-drain";
|
|
25
27
|
readonly path: "/v1/admin/drain";
|
|
26
28
|
readonly methods: readonly ["POST"];
|
|
29
|
+
readonly power: "drain";
|
|
27
30
|
}];
|
|
28
31
|
type AdminDrainRouteId = RouteIdsOf<typeof ADMIN_DRAIN_ROUTES>;
|
|
29
32
|
export {};
|
|
@@ -7,7 +7,7 @@ import { drainIdentityMismatches } from "../../fleet/drain-entry.js";
|
|
|
7
7
|
import { assertNever } from "../route-ctx.js";
|
|
8
8
|
import { WEB_ADMIN_ENV } from "@sema-agent/settings-schema";
|
|
9
9
|
export const ADMIN_DRAIN_PATH = "/v1/admin/drain";
|
|
10
|
-
const OPERATOR_ONLY_MESSAGE = "drain declaration is
|
|
10
|
+
const OPERATOR_ONLY_MESSAGE = "drain declaration is for this deployment's operators and drain principals only: the caller principal is in neither OPERATOR_PRINCIPALS nor OPERATOR_DRAIN_PRINCIPALS";
|
|
11
11
|
export async function handleAdminDrain(req, res, match, ctx) {
|
|
12
12
|
const { deps } = ctx;
|
|
13
13
|
switch (match.decl.id) {
|
|
@@ -17,11 +17,12 @@ export async function handleAdminDrain(req, res, match, ctx) {
|
|
|
17
17
|
sendPrincipalRefusal(res, req, deps.config);
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const power = ADMIN_DRAIN_DECL.power;
|
|
21
|
+
if (!facts.operatorLane(deps, power).available) {
|
|
22
|
+
sendOperatorPrincipalsRequired(res, power);
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
24
|
-
if (!explicitOperatorOk(principal, deps.config)) {
|
|
25
|
+
if (!explicitOperatorOk(principal, deps.config, power)) {
|
|
25
26
|
sendError(res, 403, "auth.operator_only", OPERATOR_ONLY_MESSAGE);
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
@@ -59,7 +60,6 @@ export async function handleAdminDrain(req, res, match, ctx) {
|
|
|
59
60
|
return assertNever(match.decl.id);
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
];
|
|
63
|
+
const ADMIN_DRAIN_DECL = { id: "admin-drain", path: ADMIN_DRAIN_PATH, methods: ["POST"], power: "drain" };
|
|
64
|
+
export const ADMIN_DRAIN_ROUTES = [ADMIN_DRAIN_DECL];
|
|
65
65
|
//# sourceMappingURL=admin-drain.js.map
|
|
@@ -19,10 +19,10 @@ export async function handleMemoryOptOut(req, res, match, ctx) {
|
|
|
19
19
|
}
|
|
20
20
|
const fact = facts.memoryOptOutGrant(deps);
|
|
21
21
|
if (!fact.available && fact.why === "operator_principals_empty") {
|
|
22
|
-
sendOperatorPrincipalsRequired(res);
|
|
22
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
|
-
if (!explicitOperatorOk(principal, deps.config)) {
|
|
25
|
+
if (!explicitOperatorOk(principal, deps.config, "operate")) {
|
|
26
26
|
sendError(res, 403, "auth.operator_only", "memory opt-out grant administration is operator-only: the caller principal is not in OPERATOR_PRINCIPALS");
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
@@ -33,7 +33,7 @@ export async function handleMemoryOptOut(req, res, match, ctx) {
|
|
|
33
33
|
sendError(res, 501, "capability.memory_optout_grant_required", "this deployment has no memory opt-out grant table: the per-principal memory-capture opt-out authorization store lives on a SQL store backend (DB_BACKEND=mysql|pg) and the local backend deliberately keeps no companion store for forgetting — wire a SQL backend to administer opt-out grants");
|
|
34
34
|
return;
|
|
35
35
|
case "operator_principals_empty":
|
|
36
|
-
sendOperatorPrincipalsRequired(res);
|
|
36
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
37
37
|
return;
|
|
38
38
|
default:
|
|
39
39
|
return assertNever(fact.why);
|
|
@@ -69,10 +69,10 @@ async function operatorLane(req, res, id, params, ctx) {
|
|
|
69
69
|
}
|
|
70
70
|
const fact = facts.adoption(deps);
|
|
71
71
|
if (!fact.available && fact.why === "operator_principals_empty") {
|
|
72
|
-
sendOperatorPrincipalsRequired(res);
|
|
72
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
|
-
if (!explicitOperatorOk(principal, deps.config)) {
|
|
75
|
+
if (!explicitOperatorOk(principal, deps.config, "operate")) {
|
|
76
76
|
sendError(res, 403, "auth.operator_only", "adoption is an operator-only deployment action");
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
@@ -82,7 +82,7 @@ async function operatorLane(req, res, id, params, ctx) {
|
|
|
82
82
|
sendAdoptionStoreRequired(res);
|
|
83
83
|
return;
|
|
84
84
|
case "operator_principals_empty":
|
|
85
|
-
sendOperatorPrincipalsRequired(res);
|
|
85
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
86
86
|
return;
|
|
87
87
|
default:
|
|
88
88
|
return assertNever(fact.why);
|
|
@@ -129,8 +129,6 @@ async function operatorLane(req, res, id, params, ctx) {
|
|
|
129
129
|
return;
|
|
130
130
|
}
|
|
131
131
|
const out = await runner.start(fromPrincipal, toPrincipal);
|
|
132
|
-
if (out.kind === "receipt")
|
|
133
|
-
await deps.toolApproval?.followRowOwners();
|
|
134
132
|
sendOutcome(res, out);
|
|
135
133
|
return;
|
|
136
134
|
}
|
|
@@ -118,6 +118,7 @@ export type PendingRowForWire = PendingRow & {
|
|
|
118
118
|
governanceForced?: true;
|
|
119
119
|
rememberRefused?: true;
|
|
120
120
|
agent?: SessionAgent;
|
|
121
|
+
askId?: string;
|
|
121
122
|
};
|
|
122
123
|
/**
|
|
123
124
|
* 🔴 [ref]-2 / [ref]②:两条 durable 读面(`GET /v1/approvals` + `/v1/approvals/stream`)的**唯一**
|
|
@@ -153,7 +154,7 @@ export declare function streamApprovals(req: IncomingMessage, res: ServerRespons
|
|
|
153
154
|
* 缺席,流的字节与 [ref] 之前逐字相同(能力缺席 = 零变化,不是空数组)。**位置在 `pollMs` 之前**
|
|
154
155
|
* 与 `governance` 同一条理由:`pollMs` 只有测试传,而本参数是每个生产调用点都必须表态的那一个。
|
|
155
156
|
*/
|
|
156
|
-
live: Pick<ToolApprovalCoordinator, "listLivePending"> | undefined,
|
|
157
|
+
live: Pick<ToolApprovalCoordinator, "listLivePending" | "readParkedAskOfGate"> | undefined,
|
|
157
158
|
/**
|
|
158
159
|
* 🔴 [ref]:`pending` 车道的**第二个半场** —— 被扣的跨会话来信(`peer_inbound` 行),与 `GET /v1/approvals` 的 `pending[]`
|
|
159
160
|
* **同一只**闭包({@link peerInboundRowsFor},已绑调用方自己的信箱 scope)。与停驻行同一条 delta 车道、同一对事件名
|
|
@@ -3,9 +3,9 @@ import { skillCardFacts } from "../../approval-card.js";
|
|
|
3
3
|
import { hostDecisionFacts, readDecider, retiredDecisionKeyFacts, DECIDED_BY_NOT_IN_PROOF } from "../../host-decision.js";
|
|
4
4
|
import { sameExecutionRecord } from "@sema-agent/core";
|
|
5
5
|
import { findParkedAgentForCheckpoint } from "../../parked-decide.js";
|
|
6
|
-
import { DECIDED_REPLAY_SCAN_CAP, pendingTriageOrder } from "../../plugins/checkpoint-store-face.js";
|
|
6
|
+
import { DECIDED_REPLAY_SCAN_CAP, displayRuleOffers, pendingTriageOrder } from "../../plugins/checkpoint-store-face.js";
|
|
7
7
|
import { approvalPayloadFingerprint } from "../../plugins/approval-payload-fingerprint.js";
|
|
8
|
-
import { carriesDeploymentListQuestion, standingAnswerDurablyRecordable } from "../../tool-approval.js";
|
|
8
|
+
import { cardRuleOffersOfAskRow, carriesDeploymentListQuestion, readPersistRuleSlot, standingAnswerDurablyRecordable } from "../../tool-approval.js";
|
|
9
9
|
import { APPROVAL_GATE_KINDS } from "../../approval-content-kind.js";
|
|
10
10
|
import { isParkedRunStatus } from "../../plugins/store-contracts.js";
|
|
11
11
|
import { principalFrom, scopeCellOf, CHECKPOINT_PUBLIC_SCOPE, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../../security.js";
|
|
@@ -13,7 +13,7 @@ import { keyAdmits } from "../../owner-faces.js";
|
|
|
13
13
|
import { personOf } from "../../owner-brands.js";
|
|
14
14
|
import { editedPlanBinding, verifyDirectDoorProof } from "../../principal-jwt.js";
|
|
15
15
|
import { isWellFormedUnicode } from "../../principal-shape.js";
|
|
16
|
-
import { MAX_APPROVAL_REASON_CHARS, projectQuestionAnswer, questionAnswerFromProjection } from "../../approval-hmac.js";
|
|
16
|
+
import { MAX_APPROVAL_REASON_CHARS, persistRuleFromProjection, projectPersistRule, projectQuestionAnswer, questionAnswerFromProjection } from "../../approval-hmac.js";
|
|
17
17
|
import { PLAN_APPROVE_PERMISSION_MODES_AFTER, isPlanApprovePermissionModeAfter, planReviewDecisionExitsPlan, storedBodyPermissionMode } from "../../task-settings.js";
|
|
18
18
|
import { MAX_APPROVAL_NONCE_CHARS } from "../../plugins/approval-nonce-store.js";
|
|
19
19
|
import { redactedPreview, redactSecrets } from "../../trace/redact.js";
|
|
@@ -22,12 +22,13 @@ import { buildRunName } from "../../fleet/fleet-bus.js";
|
|
|
22
22
|
import { sleep } from "../sse-log.js";
|
|
23
23
|
import { bindSseLifecycle } from "../sse-lifecycle.js";
|
|
24
24
|
import { sendJson, sendError, sseHeaders, logRequestError, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_MS } from "../send.js";
|
|
25
|
-
import {
|
|
25
|
+
import { createLogger } from "../../observability/logger.js";
|
|
26
|
+
import { gatedSubject, explicitOperatorOk, confinedTierOf, sendPrincipalConfined, sendPrincipalRefusal } from "../principal-gate.js";
|
|
26
27
|
import { admitRunById, admitSessionById, callerEntry, callerOf, callerOfDecision, deciderSubjectOf, enter, latestRunIdOf, listRunsOfScope, protocolEntry, readSessionAgent, readVisibleRun, refuse, runFactsOf, withCaller } from "../owner-access.js";
|
|
27
28
|
import { governanceOriginOf } from "../../runtime-governance.js";
|
|
28
29
|
import { deploymentListAsks } from "../../approval.js";
|
|
29
30
|
import { grantBindingOf } from "../../standing-answer-grant.js";
|
|
30
|
-
import { sendResumeOutcome } from "../route-ctx.js";
|
|
31
|
+
import { readRememberSlot, ruleReceiptOf, sendResumeOutcome } from "../route-ctx.js";
|
|
31
32
|
import { assertNever } from "../route-ctx.js";
|
|
32
33
|
import * as facts from "../../capability-facts.js";
|
|
33
34
|
import { storeFaultClassOf } from "../../plugins/store-fault.js";
|
|
@@ -42,10 +43,10 @@ import { defaultPeerInboundFace, parsePeerInboundId } from "../../peer-inbound-f
|
|
|
42
43
|
import { engineScopeOfOwner } from "../../engine-identity.js";
|
|
43
44
|
import { RESUME_RESTATABLE_KEYS, readResumeRestatement } from "../resume-restatement.js";
|
|
44
45
|
const DECIDE_BODY_DECLARED_KEYS = [
|
|
45
|
-
"answer", "boundCallId", "boundInputHash", "checkpointToken", "decidedBy", "decidedVia", "decision", "feedback", "reason", "remember", "updatedInput",
|
|
46
|
+
"answer", "boundCallId", "boundInputHash", "checkpointToken", "decidedBy", "decidedVia", "decision", "feedback", "persistRule", "reason", "remember", "updatedInput",
|
|
46
47
|
...RESUME_RESTATABLE_KEYS,
|
|
47
48
|
];
|
|
48
|
-
const DECIDE_PROOF_COVERED_KEYS = ["answer", "boundCallId", "boundInputHash", "decision", "reason"];
|
|
49
|
+
const DECIDE_PROOF_COVERED_KEYS = ["answer", "boundCallId", "boundInputHash", "decision", "persistRule", "reason"];
|
|
49
50
|
const DECIDE_NOT_IN_PROOF_KEYS = ["checkpointToken", "decidedBy", "decidedVia", "feedback", "remember", "updatedInput", ...RESUME_RESTATABLE_KEYS];
|
|
50
51
|
function readDecisionDoorRestatement(res, body, directDoorActive) {
|
|
51
52
|
if (directDoorActive && RESUME_RESTATABLE_KEYS.some((key) => body[key] !== undefined)) {
|
|
@@ -68,6 +69,7 @@ const PROOF_SLOT_OF = {
|
|
|
68
69
|
decision: "decision",
|
|
69
70
|
editedPlan: "boundInputHash",
|
|
70
71
|
permissionModeAfter: "permissionModeAfter",
|
|
72
|
+
persistRule: "persistRule",
|
|
71
73
|
reason: "reason",
|
|
72
74
|
};
|
|
73
75
|
function buildApprovalEnvelope(sessionId, covered, reading) {
|
|
@@ -312,7 +314,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
312
314
|
}
|
|
313
315
|
const cs = park.checkpointStore;
|
|
314
316
|
const principal = gatedSubject(req, deps.config);
|
|
315
|
-
const operator = explicitOperatorOk(principal === undefined ? undefined : personOf(principal), deps.config);
|
|
317
|
+
const operator = explicitOperatorOk(principal === undefined ? undefined : personOf(principal), deps.config, "operate");
|
|
316
318
|
const ownScope = principal ?? CHECKPOINT_PUBLIC_SCOPE;
|
|
317
319
|
if (match.decl.id === "approvals-stream") {
|
|
318
320
|
const ownerParam = operator ? (new URLSearchParams(match.search).get("owner") ?? undefined) : undefined;
|
|
@@ -333,7 +335,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
333
335
|
sendApprovalsReadFailure(req, res, deps.logger, err);
|
|
334
336
|
return;
|
|
335
337
|
}
|
|
336
|
-
const pending = [...(await projectDurableQueue(durableRows, deps.config, deps.sessionStorage)), ...peerInboundRowsFor(defaultPeerInboundFace, principal, ownerParam)()];
|
|
338
|
+
const pending = [...(await projectDurableQueue(durableRows, deps.config, deps.sessionStorage, deps.toolApproval)), ...peerInboundRowsFor(defaultPeerInboundFace, principal, ownerParam)()];
|
|
337
339
|
const livePending = deps.toolApproval ? await deps.toolApproval.listLivePending(scope) : undefined;
|
|
338
340
|
sendJson(res, 200, {
|
|
339
341
|
pending,
|
|
@@ -677,19 +679,16 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
677
679
|
return;
|
|
678
680
|
}
|
|
679
681
|
const storeNowMs = await store.nowMs();
|
|
680
|
-
const
|
|
681
|
-
const proof = verifyDirectDoorProof({ jwt: hdr(PRINCIPAL_TOKEN_HEADER), mac: hdr(APPROVAL_MAC_HEADER), kid: hdr(APPROVAL_MAC_KID_HEADER) }, buildApprovalEnvelope(run.sessionId, PLAN_REVIEW_PROOF_COVERED_KEYS, {
|
|
682
|
+
const proof = admitDirectDoorDecider(req, res, buildApprovalEnvelope(run.sessionId, PLAN_REVIEW_PROOF_COVERED_KEYS, {
|
|
682
683
|
boundCallId: planNonce ?? null,
|
|
683
684
|
checkpointId: sighting?.value,
|
|
684
685
|
decision,
|
|
685
686
|
editedPlan: decision === "edit" ? editedPlanBinding(body.editedPlan) : null,
|
|
686
687
|
permissionModeAfter: isPlanApprovePermissionModeAfter(body.permissionModeAfter) ? body.permissionModeAfter : undefined,
|
|
687
688
|
reason: typeof body.reason === "string" ? body.reason : null,
|
|
688
|
-
}), deps.config, { actionBinding: false, nowSec: Math.floor(storeNowMs / 1000) });
|
|
689
|
-
if (
|
|
690
|
-
sendError(res, proof.status, proof.errorCode, proof.error);
|
|
689
|
+
}), deps.config, match.decl.power, { actionBinding: false, nowSec: Math.floor(storeNowMs / 1000) });
|
|
690
|
+
if (proof === undefined)
|
|
691
691
|
return;
|
|
692
|
-
}
|
|
693
692
|
deciderPrincipal = proof.principal;
|
|
694
693
|
singleUse = proof.singleUse === undefined ? undefined : { proof: proof.singleUse, store };
|
|
695
694
|
}
|
|
@@ -740,7 +739,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
740
739
|
const doorKind = approvalQueueKindOfDoorId(sessionId);
|
|
741
740
|
switch (doorKind) {
|
|
742
741
|
case "peer_inbound":
|
|
743
|
-
await decidePeerInbound(req, res, sessionId, { readJson, config: deps.config, logger: deps.logger, principal, face: defaultPeerInboundFace });
|
|
742
|
+
await decidePeerInbound(req, res, sessionId, { readJson, config: deps.config, logger: deps.logger, principal, face: defaultPeerInboundFace, power: match.decl.power });
|
|
744
743
|
return;
|
|
745
744
|
case "checkpoint":
|
|
746
745
|
break;
|
|
@@ -772,15 +771,12 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
772
771
|
return;
|
|
773
772
|
}
|
|
774
773
|
let remember = false;
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
sendError(res, 400, "request.field_conflict", "remember requires decision 'approve'", fieldConflictFacts("remember_decision", ["remember", "decision"]));
|
|
782
|
-
return;
|
|
783
|
-
}
|
|
774
|
+
const rememberSlot = readRememberSlot(body.remember, decision);
|
|
775
|
+
if (rememberSlot.kind === "refused") {
|
|
776
|
+
sendError(res, 400, rememberSlot.refusal.errorCode, rememberSlot.refusal.message, rememberSlot.refusal.facts);
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
if (rememberSlot.kind === "session") {
|
|
784
780
|
if (deps.config.directDoorActive) {
|
|
785
781
|
sendError(res, 400, "remember_not_in_proof", "remember is not supported on a direct-door worker (not covered by the decision proof)");
|
|
786
782
|
return;
|
|
@@ -791,6 +787,12 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
791
787
|
}
|
|
792
788
|
remember = true;
|
|
793
789
|
}
|
|
790
|
+
const ruleSlot = readPersistRuleSlot(body.persistRule, decision === "approve" ? "allow" : "deny");
|
|
791
|
+
if (!ruleSlot.ok) {
|
|
792
|
+
sendError(res, 400, "request.body_shape", ruleSlot.error);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const ruleProjection = ruleSlot.slot !== undefined ? projectPersistRule(ruleSlot.slot) : undefined;
|
|
794
796
|
let answer;
|
|
795
797
|
let answerProjection;
|
|
796
798
|
if (body.answer !== undefined) {
|
|
@@ -864,24 +866,19 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
864
866
|
};
|
|
865
867
|
let deciderPrincipal = principal === undefined ? undefined : personOf(principal);
|
|
866
868
|
if (deps.config.directDoorActive) {
|
|
867
|
-
const
|
|
868
|
-
const h = req.headers[n];
|
|
869
|
-
return Array.isArray(h) ? h[0] : h;
|
|
870
|
-
};
|
|
871
|
-
const proof = verifyDirectDoorProof({ jwt: hdr(PRINCIPAL_TOKEN_HEADER), mac: hdr(APPROVAL_MAC_HEADER), kid: hdr(APPROVAL_MAC_KID_HEADER) }, buildApprovalEnvelope(sessionId, DECIDE_PROOF_COVERED_KEYS, {
|
|
869
|
+
const proof = admitDirectDoorDecider(req, res, buildApprovalEnvelope(sessionId, DECIDE_PROOF_COVERED_KEYS, {
|
|
872
870
|
answer: answerProjection,
|
|
873
871
|
boundCallId: binding.boundCallId,
|
|
874
872
|
boundInputHash: binding.boundInputHash,
|
|
875
873
|
decision,
|
|
874
|
+
persistRule: ruleProjection,
|
|
876
875
|
reason: typeof body.reason === "string" ? body.reason : null,
|
|
877
|
-
}), deps.config);
|
|
878
|
-
if (
|
|
879
|
-
sendError(res, proof.status, proof.errorCode, proof.error);
|
|
876
|
+
}), deps.config, match.decl.power);
|
|
877
|
+
if (proof === undefined)
|
|
880
878
|
return;
|
|
881
|
-
}
|
|
882
879
|
deciderPrincipal = proof.principal;
|
|
883
880
|
}
|
|
884
|
-
const deciderCaller = { subject: deciderSubjectOf(principal, deciderPrincipal), operator: explicitOperatorOk(deciderPrincipal, deps.config) };
|
|
881
|
+
const deciderCaller = { subject: deciderSubjectOf(principal, deciderPrincipal), operator: explicitOperatorOk(deciderPrincipal, deps.config, "operate") };
|
|
885
882
|
if (!deciderCaller.operator) {
|
|
886
883
|
const cpScope = await cs
|
|
887
884
|
.peekPendingScope(sessionId, { gateKinds: APPROVAL_GATE_KINDS })
|
|
@@ -891,7 +888,8 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
891
888
|
return;
|
|
892
889
|
}
|
|
893
890
|
}
|
|
894
|
-
const
|
|
891
|
+
const persistRule = ruleProjection !== undefined ? { slot: persistRuleFromProjection(ruleProjection), inputWasEdited: binding.updatedInput !== undefined } : undefined;
|
|
892
|
+
const out = await resumeCheckpoint(sessionId, decision, body.reason ?? undefined, hostDecisionFacts(decider, deciderPrincipal), req, answer, binding, remember ? { grantedBy: deciderPrincipal ?? null, laneCannotRecord: "refuse" } : undefined, true, deciderCaller, restated.restatement, persistRule);
|
|
895
893
|
const replay = await decideIdempotentReplay(cs, {
|
|
896
894
|
sessionId,
|
|
897
895
|
out,
|
|
@@ -910,12 +908,31 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
910
908
|
sendResumeOutcome(res, replay, deps.logger);
|
|
911
909
|
return;
|
|
912
910
|
}
|
|
913
|
-
|
|
911
|
+
const rule = out.status >= 200 && out.status < 300 ? ruleReceiptOf(out, persistRule) : undefined;
|
|
912
|
+
const extra = { ...(remember ? { rememberApplied: out.rememberApplied === true } : {}), ...(rule ?? {}) };
|
|
913
|
+
sendResumeOutcome(res, out, deps.logger, Object.keys(extra).length > 0 ? extra : undefined);
|
|
914
914
|
return;
|
|
915
915
|
}
|
|
916
916
|
}
|
|
917
917
|
return assertNever(match.decl.id);
|
|
918
918
|
}
|
|
919
|
+
function admitDirectDoorDecider(req, res, envelope, config, power, opts) {
|
|
920
|
+
const hdr = (n) => {
|
|
921
|
+
const h = req.headers[n];
|
|
922
|
+
return Array.isArray(h) ? h[0] : h;
|
|
923
|
+
};
|
|
924
|
+
const proof = verifyDirectDoorProof({ jwt: hdr(PRINCIPAL_TOKEN_HEADER), mac: hdr(APPROVAL_MAC_HEADER), kid: hdr(APPROVAL_MAC_KID_HEADER) }, envelope, config, opts);
|
|
925
|
+
if (!proof.ok) {
|
|
926
|
+
sendError(res, proof.status, proof.errorCode, proof.error);
|
|
927
|
+
return undefined;
|
|
928
|
+
}
|
|
929
|
+
const confined = confinedTierOf(proof.principal, config, power);
|
|
930
|
+
if (confined !== undefined) {
|
|
931
|
+
sendPrincipalConfined(res, confined);
|
|
932
|
+
return undefined;
|
|
933
|
+
}
|
|
934
|
+
return proof;
|
|
935
|
+
}
|
|
919
936
|
async function decidePeerInbound(req, res, id, env) {
|
|
920
937
|
const body = (await env.readJson(req));
|
|
921
938
|
const decision = body?.decision === "approve" ? "approve" : body?.decision === "deny" ? "deny" : undefined;
|
|
@@ -938,15 +955,9 @@ async function decidePeerInbound(req, res, id, env) {
|
|
|
938
955
|
}
|
|
939
956
|
let decider = env.principal;
|
|
940
957
|
if (env.config.directDoorActive) {
|
|
941
|
-
const
|
|
942
|
-
|
|
943
|
-
return Array.isArray(h) ? h[0] : h;
|
|
944
|
-
};
|
|
945
|
-
const proof = verifyDirectDoorProof({ jwt: hdr(PRINCIPAL_TOKEN_HEADER), mac: hdr(APPROVAL_MAC_HEADER), kid: hdr(APPROVAL_MAC_KID_HEADER) }, buildApprovalEnvelope(id, DECIDE_PROOF_COVERED_KEYS, { answer: undefined, boundCallId: undefined, boundInputHash: undefined, decision, reason: null }), env.config);
|
|
946
|
-
if (!proof.ok) {
|
|
947
|
-
sendError(res, proof.status, proof.errorCode, proof.error);
|
|
958
|
+
const proof = admitDirectDoorDecider(req, res, buildApprovalEnvelope(id, DECIDE_PROOF_COVERED_KEYS, { answer: undefined, boundCallId: undefined, boundInputHash: undefined, decision, persistRule: undefined, reason: null }), env.config, env.power);
|
|
959
|
+
if (proof === undefined)
|
|
948
960
|
return;
|
|
949
|
-
}
|
|
950
961
|
decider = proof.principal;
|
|
951
962
|
}
|
|
952
963
|
const coord = parsePeerInboundId(id);
|
|
@@ -1035,9 +1046,45 @@ function projectPendingForWire(row, deployment, agent) {
|
|
|
1035
1046
|
});
|
|
1036
1047
|
return { kind: "checkpoint", ...redacted, ...(governanceForced ? { governanceForced } : {}), ...(rememberRefused ? { rememberRefused: true } : {}), ...(agent !== undefined ? { agent } : {}) };
|
|
1037
1048
|
}
|
|
1038
|
-
async function projectDurableQueue(rows, deployment, sessions) {
|
|
1049
|
+
async function projectDurableQueue(rows, deployment, sessions, cards) {
|
|
1039
1050
|
const agents = await sessionAgentsOf(rows.map((r) => r.sessionId), sessions);
|
|
1040
|
-
|
|
1051
|
+
const carded = await Promise.all(rows.map((r) => withCardRecord(r, cards)));
|
|
1052
|
+
return carded.map((r) => projectPendingForWire(r, deployment, agents.get(r.sessionId)));
|
|
1053
|
+
}
|
|
1054
|
+
const queueLogger = createLogger();
|
|
1055
|
+
async function withCardRecord(row, cards) {
|
|
1056
|
+
if (cards === undefined || row.toolCallId === null || row.boundInputHash === undefined)
|
|
1057
|
+
return row;
|
|
1058
|
+
const asked = await cards.readParkedAskOfGate(row.sessionId, { boundCallId: row.toolCallId, boundInputHash: row.boundInputHash });
|
|
1059
|
+
const withOffers = (ruleOffers) => {
|
|
1060
|
+
const next = { ...row };
|
|
1061
|
+
if (ruleOffers === undefined)
|
|
1062
|
+
delete next.ruleOffers;
|
|
1063
|
+
else
|
|
1064
|
+
next.ruleOffers = ruleOffers;
|
|
1065
|
+
return next;
|
|
1066
|
+
};
|
|
1067
|
+
const withAskIdentity = (next, ask) => {
|
|
1068
|
+
if (next.taskId !== null && next.taskId !== ask.taskId) {
|
|
1069
|
+
queueLogger.warn("approval queue row: the parked leg's task differs from the ask's task — the row keeps its checkpoint identity", { sessionId: row.sessionId, askId: ask.askId });
|
|
1070
|
+
return next;
|
|
1071
|
+
}
|
|
1072
|
+
return { ...next, taskId: ask.taskId, askId: ask.askId };
|
|
1073
|
+
};
|
|
1074
|
+
switch (asked.kind) {
|
|
1075
|
+
case "none":
|
|
1076
|
+
return row;
|
|
1077
|
+
case "unreadable":
|
|
1078
|
+
return withOffers(undefined);
|
|
1079
|
+
case "row": {
|
|
1080
|
+
const offers = cardRuleOffersOfAskRow(asked.row);
|
|
1081
|
+
return withAskIdentity(withOffers(offers === undefined ? undefined : displayRuleOffers(offers, { countRedactions: false })), asked.row);
|
|
1082
|
+
}
|
|
1083
|
+
default: {
|
|
1084
|
+
const unhandled = asked;
|
|
1085
|
+
throw new Error(`queue projection: unhandled parked-ask reading ${JSON.stringify(unhandled)}`);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1041
1088
|
}
|
|
1042
1089
|
async function sessionAgentsOf(sessionIds, sessions) {
|
|
1043
1090
|
const out = new Map();
|
|
@@ -1122,7 +1169,7 @@ export async function streamApprovals(req, res, cs, live, peerInbound, sessions,
|
|
|
1122
1169
|
while (!closed) {
|
|
1123
1170
|
let snapshot;
|
|
1124
1171
|
try {
|
|
1125
|
-
const pending = [...(await projectDurableQueue(await cs.listPending(scope), governance, sessions)), ...(peerInbound?.() ?? [])];
|
|
1172
|
+
const pending = [...(await projectDurableQueue(await cs.listPending(scope), governance, sessions, live)), ...(peerInbound?.() ?? [])];
|
|
1126
1173
|
snapshot = { pending, livePending: live ? await live.listLivePending(scope) : [] };
|
|
1127
1174
|
}
|
|
1128
1175
|
catch (err) {
|
|
@@ -38,6 +38,10 @@ export declare const CAPABILITIES_ROUTES: readonly [{
|
|
|
38
38
|
readonly id: "capability-mcp";
|
|
39
39
|
readonly path: "/v1/capabilities/mcp";
|
|
40
40
|
readonly methods: readonly ["GET"];
|
|
41
|
+
}, {
|
|
42
|
+
readonly id: "capability-scenario-effective";
|
|
43
|
+
readonly path: "/v1/capabilities/scenario";
|
|
44
|
+
readonly methods: readonly ["GET"];
|
|
41
45
|
}, {
|
|
42
46
|
readonly id: "capability-mcp-probe";
|
|
43
47
|
readonly path: "/v1/capabilities/mcp/probe";
|
|
@@ -36,7 +36,8 @@ import { centerEntitlementSourceWired } from "../../runtime-caps-resolver.js";
|
|
|
36
36
|
import * as facts from "../../capability-facts.js";
|
|
37
37
|
import { judgeAutoModeArming } from "../../auto-mode-face.js";
|
|
38
38
|
import { coercePermissionModeStrict, PERMISSION_MODE_WORDS, taskHooksHonored } from "../../task-settings.js";
|
|
39
|
-
import { gatedPerson, sendPrincipalRefusal } from "../principal-gate.js";
|
|
39
|
+
import { gatedPerson, gatedSubject, sendPrincipalRefusal } from "../principal-gate.js";
|
|
40
|
+
import { scenarioRequestedBySubject } from "../../request-form.js";
|
|
40
41
|
import { readAdoptionImportCapability } from "../../adoption/import.js";
|
|
41
42
|
import { headReadPresent } from "../../session-incarnation.js";
|
|
42
43
|
import { callerTrust } from "../caller-trust.js";
|
|
@@ -57,6 +58,12 @@ function retentionAdvertisement(config) {
|
|
|
57
58
|
export function createCapabilitiesLocal() {
|
|
58
59
|
return { mcpProbe: createMcpProbeLocal() };
|
|
59
60
|
}
|
|
61
|
+
const QUERY_SCENARIO_REPEATED = Symbol("scenario query given more than once");
|
|
62
|
+
const SCENARIO_QUERY_REPEATED_REFUSAL = "scenario must be given at most once (omit it to read this deployment's default scenario)";
|
|
63
|
+
function scenarioQueryOf(search) {
|
|
64
|
+
const given = new URLSearchParams(search).getAll("scenario");
|
|
65
|
+
return given.length > 1 ? QUERY_SCENARIO_REPEATED : given[0];
|
|
66
|
+
}
|
|
60
67
|
export async function handleCapabilities(req, res, match, ctx) {
|
|
61
68
|
const { deps } = ctx;
|
|
62
69
|
switch (match.decl.id) {
|
|
@@ -140,6 +147,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
140
147
|
permissionRulesRevoke: facts.ruleLane(deps).available,
|
|
141
148
|
respondFreeFormRules: true,
|
|
142
149
|
respondBatchRuleOffers: true,
|
|
150
|
+
approvalDecideRuleSlot: true,
|
|
143
151
|
oneShot: true,
|
|
144
152
|
policy: true,
|
|
145
153
|
usage: Boolean(deps.costQuota),
|
|
@@ -213,6 +221,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
213
221
|
retention: retentionAdvertisement(deps.config),
|
|
214
222
|
peerLane: Boolean(deps.peerDirectory),
|
|
215
223
|
scenarioSubject: true,
|
|
224
|
+
effectiveScenario: true,
|
|
216
225
|
sessionList: facts.sessionList(deps).available,
|
|
217
226
|
sessionSearch: facts.sessionList(deps).available,
|
|
218
227
|
sessionKinds: facts.sessionKinds(deps).available ? [...SESSION_KINDS] : [],
|
|
@@ -263,15 +272,30 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
263
272
|
sendPrincipalRefusal(res, req, deps.config);
|
|
264
273
|
return;
|
|
265
274
|
}
|
|
266
|
-
const
|
|
267
|
-
if (
|
|
268
|
-
sendError(res, 400, "request.field_invalid",
|
|
275
|
+
const named = scenarioQueryOf(match.search);
|
|
276
|
+
if (named === QUERY_SCENARIO_REPEATED) {
|
|
277
|
+
sendError(res, 400, "request.field_invalid", SCENARIO_QUERY_REPEATED_REFUSAL);
|
|
269
278
|
return;
|
|
270
279
|
}
|
|
271
|
-
const scenarioName = await deps.scenarioGate(principal,
|
|
280
|
+
const { scenario: scenarioName } = await deps.scenarioGate(principal, scenarioRequestedBySubject(gatedSubject(req, deps.config), named));
|
|
272
281
|
await serveMcpProbeFace(res, ctx, principal, { declaredBy: "deployment", specs: mcpForScenario(deps.config.mcpServers, scenarioName) ?? [] });
|
|
273
282
|
return;
|
|
274
283
|
}
|
|
284
|
+
case "capability-scenario-effective": {
|
|
285
|
+
const principal = gatedPerson(req, deps.config);
|
|
286
|
+
if (deps.config.requirePrincipal && !principal) {
|
|
287
|
+
sendPrincipalRefusal(res, req, deps.config);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const named = scenarioQueryOf(match.search);
|
|
291
|
+
if (named === QUERY_SCENARIO_REPEATED) {
|
|
292
|
+
sendError(res, 400, "request.field_invalid", SCENARIO_QUERY_REPEATED_REFUSAL);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const choice = await deps.scenarioGate(principal, scenarioRequestedBySubject(gatedSubject(req, deps.config), named));
|
|
296
|
+
sendJson(res, 200, { scenario: choice.scenario, source: choice.source });
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
275
299
|
case "capability-mcp-probe": {
|
|
276
300
|
const principal = gatedPerson(req, deps.config);
|
|
277
301
|
if (deps.config.requirePrincipal && !principal) {
|
|
@@ -401,6 +425,7 @@ export const CAPABILITIES_ROUTES = [
|
|
|
401
425
|
{ id: "capabilities", path: "/v1/capabilities", methods: ["GET"] },
|
|
402
426
|
{ id: "capability-scenario", pattern: /^\/v1\/capabilities\/scenarios\/(?<name>.*)$/, label: "/v1/capabilities/scenarios/:name", methods: ["GET"] },
|
|
403
427
|
{ id: "capability-mcp", path: "/v1/capabilities/mcp", methods: ["GET"] },
|
|
428
|
+
{ id: "capability-scenario-effective", path: "/v1/capabilities/scenario", methods: ["GET"] },
|
|
404
429
|
{ id: "capability-mcp-probe", path: "/v1/capabilities/mcp/probe", methods: ["POST"] },
|
|
405
430
|
];
|
|
406
431
|
//# sourceMappingURL=capabilities.js.map
|
|
@@ -23,13 +23,13 @@ export async function handleConfigCatalog(req, res, match, ctx) {
|
|
|
23
23
|
sendError(res, 501, "feature.config_catalog_disabled", "the config catalog is switched off on this deployment (CONFIG_CATALOG_ENABLED=false)");
|
|
24
24
|
return;
|
|
25
25
|
case "operator_principals_empty":
|
|
26
|
-
sendOperatorPrincipalsRequired(res);
|
|
26
|
+
sendOperatorPrincipalsRequired(res, "operate");
|
|
27
27
|
return;
|
|
28
28
|
default:
|
|
29
29
|
return assertNever(fact.why);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
if (!explicitOperatorOk(principal, deps.config)) {
|
|
32
|
+
if (!explicitOperatorOk(principal, deps.config, "operate")) {
|
|
33
33
|
sendError(res, 403, "auth.operator_only", OPERATOR_ONLY_MESSAGE);
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
@@ -144,7 +144,7 @@ export async function handleDevices(req, res, match, ctx) {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
const { store, hub, enrollment, runStore } = lane;
|
|
147
|
-
const operator = explicitOperatorOk(principal, deps.config);
|
|
147
|
+
const operator = explicitOperatorOk(principal, deps.config, "operate");
|
|
148
148
|
const domain = deviceOwnerDomainOf(req, deps.config);
|
|
149
149
|
if (match.decl.id === "devices-list") {
|
|
150
150
|
if (!operator && domain === undefined) {
|
|
@@ -42,7 +42,6 @@ export interface ServerWiringGates {
|
|
|
42
42
|
* 不再是装配现场的事实,而是别处算完可能已漂的复述)。 */
|
|
43
43
|
export interface ServerWiringGateInput {
|
|
44
44
|
toolApprovalEnabled: boolean;
|
|
45
|
-
streamApprovalEnabled: boolean;
|
|
46
45
|
/** 谓词自己的入参类型(结构形,不 import store 依赖树)—— 取型而非重述,两处不可能漂。 */
|
|
47
46
|
backend: StreamApprovalGateInput["backend"];
|
|
48
47
|
/** 谓词自己的入参类型(只收店的形,工厂方法过不了 tsc)—— 取型而非重述,与 `backend` 同理;在场性即事实,本面不读实例的内容。 */
|
|
@@ -116,8 +115,9 @@ export declare function assertStaticWiringConsistent(facts: StaticWiringFacts, l
|
|
|
116
115
|
* 字符串,端口误暴露到公网就是侦察面(装配拓扑 / 治理姿态 / 写保护名表)。配了 service 凭据的部署上,到得了这里的请求都过了全局凭据门、
|
|
117
116
|
* 必然带着验过的身份,本判不咬;不要求静态凭据在场(鉴权桥的登录令牌就是身份)。
|
|
118
117
|
*
|
|
119
|
-
*
|
|
120
|
-
* `remoteAddress`
|
|
118
|
+
* 「本机」按请求通道穷举(`request-channel.ts`;加一种通道不接 ⇒ 编译红):经本机控制 socket 来的请求 = 本机(能连上它的只有引擎的 OS 用户 —— 与 `/health` 把它认作
|
|
119
|
+
* 本机用户同读,同一只请求不被一面认作本机、另一面认作远程);经 TCP 来的请求判源 = TCP 对端地址(`req.socket.remoteAddress`),**非任何头**(XFF 类头在无反代形上是自造面;
|
|
120
|
+
* 有反代的部署必然配凭据)。`remoteAddress` 缺席(socket 已断等边缘)⇒ 判拒(fail-closed)。
|
|
121
121
|
*/
|
|
122
122
|
export declare function remoteWithoutVerifiedIdentity(req: IncomingMessage, config: Parameters<typeof verifiedIdentitySource>[1]): boolean;
|
|
123
123
|
export declare function handleDiagnostics(req: IncomingMessage, res: ServerResponse, match: RouteMatch<DiagnosticsRouteId>, ctx: RouteCtx): Promise<void>;
|