@sema-agent/server 7.120.0 → 7.121.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 +75 -0
- package/USAGE.md +67 -20
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +2 -2
- package/deploy/sema-up/chart/templates/server.yaml +2 -2
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +1 -1
- package/dist/boot/config-center.d.ts +5 -7
- package/dist/boot/config-center.js +33 -9
- package/dist/boot/engine-lease.d.ts +1 -1
- package/dist/boot/resolve-spec.js +1 -0
- package/dist/boot/shutdown.d.ts +9 -10
- package/dist/boot/shutdown.js +54 -16
- package/dist/boot/stage-09-leader.d.ts +2 -9
- package/dist/boot/stage-10-http-server.d.ts +1 -9
- package/dist/boot/stage-10-http-server.js +31 -8
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/config-catalog.js +11 -5
- package/dist/config-center/center-request.d.ts +16 -1
- package/dist/config-center/center-request.js +36 -5
- package/dist/config-types.d.ts +39 -3
- package/dist/config-types.js +6 -0
- package/dist/config.d.ts +33 -4
- package/dist/config.js +99 -7
- package/dist/fleet/drain-entry.d.ts +87 -0
- package/dist/fleet/drain-entry.js +35 -0
- package/dist/fleet/engine-signal.d.ts +144 -0
- package/dist/fleet/engine-signal.js +60 -0
- package/dist/fleet/serving-lease.d.ts +47 -0
- package/dist/fleet/serving-lease.js +235 -0
- package/dist/fleet-client.d.ts +35 -19
- package/dist/fleet-client.js +49 -25
- package/dist/http/approval-sweeps.js +3 -5
- package/dist/http/dispatch.js +18 -13
- package/dist/http/health-self-report.d.ts +60 -0
- package/dist/http/health-self-report.js +53 -0
- package/dist/http/principal-gate.d.ts +8 -1
- package/dist/http/principal-gate.js +1 -0
- package/dist/http/readiness.d.ts +25 -5
- package/dist/http/readiness.js +31 -6
- package/dist/http/resume-legs.js +1 -1
- package/dist/http/route-ctx.d.ts +6 -4
- package/dist/http/route-table.d.ts +3 -0
- package/dist/http/route-table.js +4 -1
- package/dist/http/routes/a2a-serve.d.ts +1 -1
- package/dist/http/routes/a2a-serve.js +5 -5
- package/dist/http/routes/admin-drain.d.ts +12 -31
- package/dist/http/routes/admin-drain.js +15 -9
- package/dist/http/routes/approvals-assistant.d.ts +3 -3
- package/dist/http/routes/approvals-assistant.js +3 -3
- package/dist/http/routes/capabilities.js +2 -1
- package/dist/http/routes/devices.js +3 -2
- package/dist/http/routes/leader.d.ts +1 -1
- package/dist/http/routes/leader.js +1 -1
- package/dist/http/routes/memory-consolidation.d.ts +1 -1
- package/dist/http/routes/memory-consolidation.js +1 -1
- package/dist/http/routes/notify-wake.d.ts +1 -1
- package/dist/http/routes/notify-wake.js +1 -1
- package/dist/http/routes/rules.js +4 -3
- package/dist/http/routes/runs.d.ts +2 -2
- package/dist/http/routes/runs.js +2 -2
- package/dist/http/routes/side-query.d.ts +1 -1
- package/dist/http/routes/side-query.js +1 -1
- package/dist/http/routes/tasks.d.ts +2 -2
- package/dist/http/routes/tasks.js +3 -3
- package/dist/http/server.d.ts +7 -21
- package/dist/http/server.js +20 -1
- package/dist/http/wire-types.d.ts +7 -1
- package/dist/model-reach-scope.d.ts +15 -3
- package/dist/model-reach-scope.js +3 -2
- package/dist/model-unreachable-notice.d.ts +2 -1
- package/dist/model-unreachable-notice.js +1 -0
- package/dist/observability/fail-open.d.ts +16 -0
- package/dist/observability/fail-open.js +16 -0
- package/dist/plugins/one-time-migrations.js +1 -1
- package/dist/plugins/permission-rule-store-sql.d.ts +2 -1
- package/dist/plugins/remote-env-device.js +0 -1
- package/dist/request-key-closure.js +1 -1
- package/dist/rules-consent.d.ts +6 -2
- package/dist/rules-consent.js +3 -0
- package/dist/runs.js +1 -1
- package/dist/tool-approval.d.ts +7 -0
- package/dist/tool-approval.js +4 -1
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/self-minted-wire.d.ts +64 -0
- package/dist/trace/self-minted-wire.js +30 -0
- package/package.json +2 -2
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { IDENTITY_SOURCES } from "./principal-gate.js";
|
|
2
|
+
import { operatorLane, sessionList } from "../capability-facts.js";
|
|
3
|
+
import { authBridgeIssuerOf } from "../auth-bridge.js";
|
|
4
|
+
import { centerLaneConfigured } from "../config-center/center-request.js";
|
|
5
|
+
import { buildDisplaySafeUrl } from "../trace/redact.js";
|
|
6
|
+
export const IDENTITY_ATTACH_WORDS = ["available", "unavailable"];
|
|
7
|
+
export const OPERATOR_PRESENCE_WORDS = ["present", "absent"];
|
|
8
|
+
export const SESSIONS_VISIBLE_VALUES = [true, false, "unknown"];
|
|
9
|
+
function carriesCallerIdentity(kind) {
|
|
10
|
+
switch (kind) {
|
|
11
|
+
case "service_token":
|
|
12
|
+
return false;
|
|
13
|
+
case "sso":
|
|
14
|
+
case "direct_door_jwt":
|
|
15
|
+
return true;
|
|
16
|
+
default: {
|
|
17
|
+
const unknownKind = kind;
|
|
18
|
+
throw new Error(`health self-report: unknown identity source ${String(unknownKind)}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function identitySourceWired(kind, a) {
|
|
23
|
+
switch (kind) {
|
|
24
|
+
case "service_token":
|
|
25
|
+
return a.credentialGateArmed;
|
|
26
|
+
case "sso":
|
|
27
|
+
return a.bridgeWired;
|
|
28
|
+
case "direct_door_jwt":
|
|
29
|
+
return a.directDoorActive;
|
|
30
|
+
default: {
|
|
31
|
+
const unknownKind = kind;
|
|
32
|
+
throw new Error(`health self-report: unknown identity source ${String(unknownKind)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function buildHealthSelfReport(deps, credentialGateArmed) {
|
|
37
|
+
const { config } = deps;
|
|
38
|
+
const bridgeWired = deps.registryJwtVerifier !== undefined;
|
|
39
|
+
const kinds = IDENTITY_SOURCES.filter((k) => identitySourceWired(k, { credentialGateArmed, bridgeWired, directDoorActive: config.directDoorActive }));
|
|
40
|
+
const issuer = authBridgeIssuerOf(config)?.issuer;
|
|
41
|
+
const webBaseUrl = config.configCenter?.webBaseUrl;
|
|
42
|
+
const sessionsVisible = centerLaneConfigured(config) && config.fleetAdvertiseAddress !== undefined && bridgeWired && sessionList(deps).available ? (config.requirePrincipal ? true : "unknown") : false;
|
|
43
|
+
return {
|
|
44
|
+
auth: { required: credentialGateArmed, kinds, identityAttach: kinds.some(carriesCallerIdentity) ? "available" : "unavailable" },
|
|
45
|
+
operator: operatorLane(deps).available ? "present" : "absent",
|
|
46
|
+
center: {
|
|
47
|
+
...(issuer !== undefined ? { issuer: buildDisplaySafeUrl(issuer) } : {}),
|
|
48
|
+
...(webBaseUrl !== undefined ? { webBaseUrl: buildDisplaySafeUrl(webBaseUrl) } : {}),
|
|
49
|
+
sessionsVisible,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=health-self-report.js.map
|
|
@@ -13,13 +13,20 @@ export declare function systemFor(req: IncomingMessage, config: ServiceConfig):
|
|
|
13
13
|
export declare function bearerPresentedButUnverified(req: IncomingMessage, config: ServiceConfig): boolean;
|
|
14
14
|
/** loopback 三形(IPv4 / IPv6 / IPv4-mapped)—— 一处定义,diagnostics 门与任何「本机自用」判据同吃。缺席 ⇒ false(fail-closed)。 */
|
|
15
15
|
export declare function isLoopbackAddress(addr: string | undefined): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* 验过的身份来源的**闭集**(序 = `/health.auth.kinds` 的出现序)。{@link verifiedIdentitySource} 的回值就是这张表的词;
|
|
18
|
+
* `/health` 的自报位(`http/health-self-report.ts`)读同一张表,答「这台部署的装配**能**回哪几个词」—— 加一个来源 = 加一个词,
|
|
19
|
+
* 两边各自的穷举 `switch` 不接就编译红。
|
|
20
|
+
*/
|
|
21
|
+
export declare const IDENTITY_SOURCES: readonly ["service_token", "sso", "direct_door_jwt"];
|
|
22
|
+
export type IdentitySource = (typeof IDENTITY_SOURCES)[number];
|
|
16
23
|
/**
|
|
17
24
|
* 这次请求的身份是不是**密码学验过**的(而不是调用方自述的 header):`sso` = registry JWT(入口预认证的签名真源)/
|
|
18
25
|
* `direct_door_jwt` = direct-door principal JWT 验过 / `service_token` = 出示了单一或 per-system service token。
|
|
19
26
|
* 缺席 = 只有自述头或什么都没有。一处判定(7.63.0 codex [high]+[medium]):off-box 读 operator 面的门用它——名单只在
|
|
20
27
|
* 有可验身份时才是身份,零凭据下它只是谁都能写的字符串;而 JWT 身份不依赖静态 token 的在场。
|
|
21
28
|
*/
|
|
22
|
-
export declare function verifiedIdentitySource(req: IncomingMessage, config: ServiceConfig):
|
|
29
|
+
export declare function verifiedIdentitySource(req: IncomingMessage, config: ServiceConfig): IdentitySource | undefined;
|
|
23
30
|
export declare function gatedPrincipal(req: IncomingMessage, config: ServiceConfig): string | undefined;
|
|
24
31
|
/**
|
|
25
32
|
* 「**出示了凭证但验不过** ≠ **没出示**」在 **direct-door principal JWT** 面上的形 —— 本仓
|
|
@@ -37,6 +37,7 @@ export function bearerPresentedButUnverified(req, config) {
|
|
|
37
37
|
export function isLoopbackAddress(addr) {
|
|
38
38
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
39
39
|
}
|
|
40
|
+
export const IDENTITY_SOURCES = ["service_token", "sso", "direct_door_jwt"];
|
|
40
41
|
export function verifiedIdentitySource(req, config) {
|
|
41
42
|
if (ssoVerifiedPrincipal(req))
|
|
42
43
|
return "sso";
|
package/dist/http/readiness.d.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* 模块形:纯叶(只值 import `send.ts`)—— `fleet-client.ts` / `boot/` 都会读它,不许长出回环。
|
|
23
23
|
*/
|
|
24
24
|
import type { ServerResponse } from "node:http";
|
|
25
|
+
import type { ServingLeaseView } from "../fleet/engine-signal.js";
|
|
25
26
|
/**
|
|
26
27
|
* 就绪原因闭集,**数组序 = 优先序**:`/health.readyReason` 取首条的句、计费门取首条的拒绝码。
|
|
27
28
|
* draining 在前 —— 一台正在下线的副本「去找替补」(Retry-After 15)比「稍后重试本机」(5)更对。
|
|
@@ -61,6 +62,26 @@ export declare function healthReadinessKeys(v: ReadinessVerdict): Record<string,
|
|
|
61
62
|
/** `/readyz` 两形:200 `{ready:true}` / 503 `{ready:false, reasons}` + `Retry-After: 5`。体只这两键(不带身份字段:
|
|
62
63
|
* 就绪探针给 k8s readinessProbe / Traefik / LB 摘流用,不是 `/health` 的替身;`drainReason` 等运维自由文本也不上这一面)。 */
|
|
63
64
|
export declare function sendReadyz(res: ServerResponse, v: ReadinessVerdict): void;
|
|
65
|
+
/**
|
|
66
|
+
* 编排器片 2 件 2 —— 计费面请求的**种类**(闭集;路由声明表 `billable` 列的取值,`http/route-ctx.ts`):
|
|
67
|
+
* · `submit` —— 起一轮**新**工作(新任务 / 新 run / leader / side-query / wake / 记忆整理 / A2A `message/send`);
|
|
68
|
+
* · `hitl-answer` —— 对一张已经在等人的卡作答(耐久 `/decide`、计划审阅、PARKED 停驻卡的赎回);
|
|
69
|
+
* · `resume` —— 续跑一条已经存在的 run(助手 resume、子代 resume)。
|
|
70
|
+
*/
|
|
71
|
+
export declare const BILLABLE_KINDS: readonly ["submit", "hitl-answer", "resume"];
|
|
72
|
+
export type BillableKind = (typeof BILLABLE_KINDS)[number];
|
|
73
|
+
/**
|
|
74
|
+
* 计费面的拒因(**数组序 = 优先序**):就绪原因({@link READINESS_REASONS})∪ `lease_expired`(服务租约过期且中心不可达满 `T_max`)。
|
|
75
|
+
* `lease_expired` 刻意不是就绪原因:它不翻 `/readyz`(中心不可达不是本实例的病,摘它出路由只会把取消 / HITL / 读面一起摘掉)。
|
|
76
|
+
*/
|
|
77
|
+
export declare const ADMISSION_REFUSALS: readonly ["draining", "model_roster_pending", "lease_expired"];
|
|
78
|
+
export type AdmissionRefusal = (typeof ADMISSION_REFUSALS)[number];
|
|
79
|
+
/**
|
|
80
|
+
* 计费面请求的**唯一**准入判(dispatch 的计费门、a2a `message/send`、PARKED 赎回席同调):按 {@link ADMISSION_REFUSALS} 序找第一条
|
|
81
|
+
* 成立且按 {@link ADMISSION_TABLE} 拒这一种的拒因;没有 ⇒ `undefined`(放行)。入参是**判决**而不是 deps:就绪判由调用点经唯一谓词
|
|
82
|
+
* {@link readinessOf} 现读(模块边界上那一只 —— 谓词单源钉注入它,任何读点自己读 `drainState` 都会当场红),租约读数取 `deps.servingLease`。
|
|
83
|
+
*/
|
|
84
|
+
export declare function admissionRefusalOf(verdict: ReadinessVerdict, lease: ServingLeaseView | undefined, kind: BillableKind): BillableRefusal | undefined;
|
|
64
85
|
/** 计费提交门的拒绝(首条 reason 定码)。`retryAfterSec` 由发送形(`sendBillableRefusal`)镜像成头;交回
|
|
65
86
|
* `{status, body}` 的赎回席只取体(它的路由刻意不带头,见 `approval-sweeps.ts` 该臂注)。 */
|
|
66
87
|
export interface BillableRefusal {
|
|
@@ -69,17 +90,16 @@ export interface BillableRefusal {
|
|
|
69
90
|
readonly error: string;
|
|
70
91
|
readonly errorCode: string;
|
|
71
92
|
readonly message: string;
|
|
72
|
-
readonly reason?: string;
|
|
73
93
|
};
|
|
74
|
-
readonly retryAfterSec: 15 | 5;
|
|
94
|
+
readonly retryAfterSec: 15 | 5 | 60;
|
|
75
95
|
}
|
|
76
96
|
/**
|
|
77
97
|
* 计费提交门的拒绝体 —— **唯一铸点**(`error` 字面 `"draining"` / `"model_roster_pending"` 与 `message` 是冻结的
|
|
78
98
|
* wire 契约:SDK / 壳的 pre-stream 重试按它们判型;冻结在 `test/api-error-text-freeze.test.ts` 的
|
|
79
|
-
* `FROZEN_RESULT_BODY` 与 `test/api-notice-text-freeze.test.ts` §2)
|
|
80
|
-
*
|
|
99
|
+
* `FROZEN_RESULT_BODY` 与 `test/api-notice-text-freeze.test.ts` §2)。编排器片 2:排空体的 `reason` 键随
|
|
100
|
+
* 停机因由声明口一起退役(排空口的体改成契约 `DrainRequest`,没有因由字段);`lease_expired` 一臂新增。
|
|
81
101
|
*/
|
|
82
|
-
export declare function billableRefusalOf(
|
|
102
|
+
export declare function billableRefusalOf(reason: AdmissionRefusal): BillableRefusal;
|
|
83
103
|
/** 发送形:`Retry-After` + 体(`dispatch.ts` 的 billable 门与 a2a `message/send` 同调)。走 `sendError` —— errorCode
|
|
84
104
|
* 是它的必填位置参(对抗复审 #3:修前这里是 `sendJson(res, r.status, r.body)`,与 leader 口的豁免行逐字同文,
|
|
85
105
|
* 在 error-code-key-gate 上借了别人的豁免过门);体键序与修前逐字同(`error, errorCode, message, reason?`)。 */
|
package/dist/http/readiness.js
CHANGED
|
@@ -33,16 +33,41 @@ export function sendReadyz(res, v) {
|
|
|
33
33
|
res.setHeader("retry-after", "5");
|
|
34
34
|
sendJson(res, 503, { ready: false, reasons: [...v.reasons] });
|
|
35
35
|
}
|
|
36
|
-
export
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
export const BILLABLE_KINDS = ["submit", "hitl-answer", "resume"];
|
|
37
|
+
export const ADMISSION_REFUSALS = [...READINESS_REASONS, "lease_expired"];
|
|
38
|
+
const ADMISSION_TABLE = {
|
|
39
|
+
draining: { submit: true, "hitl-answer": false, resume: false },
|
|
40
|
+
model_roster_pending: { submit: true, "hitl-answer": true, resume: true },
|
|
41
|
+
lease_expired: { submit: true, "hitl-answer": false, resume: false },
|
|
42
|
+
};
|
|
43
|
+
export function admissionRefusalOf(verdict, lease, kind) {
|
|
44
|
+
const holdsNow = (r) => {
|
|
45
|
+
switch (r) {
|
|
46
|
+
case "draining":
|
|
47
|
+
case "model_roster_pending":
|
|
48
|
+
return !verdict.ready && verdict.reasons.includes(r);
|
|
49
|
+
case "lease_expired":
|
|
50
|
+
return lease?.state === "expired" && lease.pastOutageMax;
|
|
51
|
+
default: {
|
|
52
|
+
const unhandled = r;
|
|
53
|
+
throw new Error(`admission refusal without a predicate arm: ${String(unhandled)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const refusal = ADMISSION_REFUSALS.find((r) => holdsNow(r) && ADMISSION_TABLE[r][kind]);
|
|
58
|
+
return refusal === undefined ? undefined : billableRefusalOf(refusal);
|
|
59
|
+
}
|
|
60
|
+
export function billableRefusalOf(reason) {
|
|
61
|
+
switch (reason) {
|
|
39
62
|
case "draining":
|
|
40
|
-
return { status: 503, body: { error: "draining", errorCode: "draining", message: "this instance is draining for shutdown/upgrade — retry against the replacement instance"
|
|
63
|
+
return { status: 503, body: { error: "draining", errorCode: "draining", message: "this instance is draining for shutdown/upgrade — retry against the replacement instance" }, retryAfterSec: 15 };
|
|
41
64
|
case "model_roster_pending":
|
|
42
65
|
return { status: 503, body: { error: "model_roster_pending", errorCode: "state.model_roster_pending", message: "this worker has no model yet (waiting for the first effective-config pull to land the roster) — retry shortly" }, retryAfterSec: 5 };
|
|
66
|
+
case "lease_expired":
|
|
67
|
+
return { status: 503, body: { error: "lease_expired", errorCode: "state.lease_expired", message: "this instance has not renewed its serving lease with the fleet center for longer than its outage limit — it takes no new work; cancel, approvals, resume and reads stay open here — retry new work against another instance" }, retryAfterSec: 60 };
|
|
43
68
|
default: {
|
|
44
|
-
const unhandled =
|
|
45
|
-
throw new Error(`
|
|
69
|
+
const unhandled = reason;
|
|
70
|
+
throw new Error(`admission refusal without a billable refusal: ${String(unhandled)}`);
|
|
46
71
|
}
|
|
47
72
|
}
|
|
48
73
|
}
|
package/dist/http/resume-legs.js
CHANGED
|
@@ -582,7 +582,7 @@ export function createResumeLegs(ctx) {
|
|
|
582
582
|
let unregisterEngineNotice;
|
|
583
583
|
try {
|
|
584
584
|
const instructionLeg = createInstructionSelectionLeg();
|
|
585
|
-
result = await instructionLeg.run(sessionId, () => withModelReachScope(sessionId, () => withPrincipal(principal, async () => {
|
|
585
|
+
result = await instructionLeg.run(sessionId, () => withModelReachScope({ sessionId, taskId }, () => withPrincipal(principal, async () => {
|
|
586
586
|
const resumeNotifiedKeys = new NotifiedKeys();
|
|
587
587
|
if (deps.runStore && taskId) {
|
|
588
588
|
const rs0 = deps.runStore;
|
package/dist/http/route-ctx.d.ts
CHANGED
|
@@ -569,10 +569,12 @@ export type RouteMethod = "GET" | "POST" | "PUT" | "DELETE";
|
|
|
569
569
|
* `satisfies` 让它与 {@link RouteMethod} 双向闭合:漏一个动词 / 多一个都是编译红。 */
|
|
570
570
|
export declare const ROUTE_METHOD_ORDER: readonly ["GET", "POST", "PUT", "DELETE"];
|
|
571
571
|
export interface RouteDeclFacts {
|
|
572
|
-
/** 「这条 url **烧模型**」—— drain / model-roster-pending / 无 service-token
|
|
573
|
-
* 没有这一列 = 不烧模型(
|
|
574
|
-
* 少一道门 ——
|
|
575
|
-
|
|
572
|
+
/** 「这条 url **烧模型**」—— drain / model-roster-pending / 租约过期 / 无 service-token 四道 503 的共同判别。
|
|
573
|
+
* 没有这一列 = 不烧模型(零默认值:要它在场必须显式写出来,漏写的方向是**少收一笔账单**而不是
|
|
574
|
+
* 少一道门 —— 反了才是灾难,所以门读的是在场这一侧)。
|
|
575
|
+
* 编排器片 2 件 2:值 = 这一种计费请求的**种类**(`submit` 新工作 / `hitl-answer` 对等人的卡作答 / `resume` 续跑;闭集属主
|
|
576
|
+
* `http/readiness.ts` 的 `BILLABLE_KINDS`)—— 排空与租约过期只拒 `submit`(放行闭集表 `ADMISSION_TABLE`),无凭证门不看种类。 */
|
|
577
|
+
readonly billable?: import("./readiness.js").BillableKind;
|
|
576
578
|
/** 「本条 url 在**没有任何 service credential** 的部署形下,这几个方法必须 fail-closed」。
|
|
577
579
|
* 判据(两个合取项)= 「授权的唯一输入是 principal 头」∧「持久改写,或爆炸半径跨租户的治理读」;
|
|
578
580
|
* 逐口论证在各 `routes/*.ts` 的头注里,声明行只记结论。 */
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
import type { FlatServiceDeps } from "./server.js";
|
|
55
55
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
56
56
|
import type { RouteDecl, RouteDispatchDecl, RouteMatch, RouteCtx, RouteMethod } from "./route-ctx.js";
|
|
57
|
+
import type { BillableKind } from "./readiness.js";
|
|
57
58
|
/** 一只域 handler 相对**全局服务凭据门**的位置。门前 = 未鉴权可达(每一只都要单独论证),门后 = 先过凭据。 */
|
|
58
59
|
/**
|
|
59
60
|
* 一行路由在全局服务凭据门的哪一侧、吃哪道门(闭集,三员;S-672 起由两员拆成三员):
|
|
@@ -133,6 +134,8 @@ export declare function matchDecl(d: RouteDispatchDecl, url: string): Readonly<R
|
|
|
133
134
|
export declare function routeLabel(_method: string, url: string): string;
|
|
134
135
|
/** POST endpoints that trigger BILLABLE work — the fail-closed auth guard must cover ALL of them. */
|
|
135
136
|
export declare function isBillableSubmitPath(url: string): boolean;
|
|
137
|
+
/** 编排器片 2 件 2:这条 url 是哪一**种**计费请求(`undefined` = 不在计费面)。声明序第一条命中的行定种类(今天每条 url 至多命中一行)。 */
|
|
138
|
+
export declare function billableKindOf(url: string): BillableKind | undefined;
|
|
136
139
|
/** [ref]([ref] 件5)—— **改写门**:不烧模型、但在没有任何 service credential 的部署形下必须
|
|
137
140
|
* 与 billable / bake 两族一样 fail-closed 的写门。与 `isBillableSubmitPath` **刻意分开**:这几扇门
|
|
138
141
|
* `billable=false`(零模型工作),塞进那张名单会让它们连带吃 drain 与 roster-pending 两道 503 ——
|
package/dist/http/route-table.js
CHANGED
|
@@ -109,7 +109,7 @@ export function matchDecl(d, url) {
|
|
|
109
109
|
const NO_PARAMS = Object.freeze({});
|
|
110
110
|
const LABEL_BY_PATH = new Map(ROUTE_DECLARATIONS.flatMap((d) => ("path" in d ? [[d.path, d.path]] : [])));
|
|
111
111
|
const SHAPED_LABEL_DECLS = ROUTE_DECLARATIONS.filter((d) => !("path" in d) && typeof d.label === "string");
|
|
112
|
-
const BILLABLE_DECLS = ROUTE_DECLARATIONS.filter((d) => d.billable
|
|
112
|
+
const BILLABLE_DECLS = ROUTE_DECLARATIONS.filter((d) => d.billable !== undefined);
|
|
113
113
|
const METHOD_DISPATCHED_DECLS = ROUTE_DECLARATIONS.filter((d) => d.methodDispatched === true);
|
|
114
114
|
const CREDENTIAL_GATED_BY_METHOD = (() => {
|
|
115
115
|
const m = new Map();
|
|
@@ -135,6 +135,9 @@ export function routeLabel(_method, url) {
|
|
|
135
135
|
export function isBillableSubmitPath(url) {
|
|
136
136
|
return BILLABLE_DECLS.some((d) => declMatches(d, url));
|
|
137
137
|
}
|
|
138
|
+
export function billableKindOf(url) {
|
|
139
|
+
return BILLABLE_DECLS.find((d) => declMatches(d, url))?.billable;
|
|
140
|
+
}
|
|
138
141
|
export function isCredentialGatedRewrite(method, url) {
|
|
139
142
|
const rows = CREDENTIAL_GATED_BY_METHOD.get(method.toUpperCase());
|
|
140
143
|
return rows !== undefined && rows.some((d) => declMatches(d, url));
|
|
@@ -79,7 +79,7 @@ export type A2aServeRouteId = RouteIdsOf<typeof A2A_SERVE_ROUTES>;
|
|
|
79
79
|
export declare const A2A_RPC_ROUTES: readonly [{
|
|
80
80
|
readonly id: "a2a-rpc";
|
|
81
81
|
readonly path: "/v1/a2a";
|
|
82
|
-
readonly billable:
|
|
82
|
+
readonly billable: "submit";
|
|
83
83
|
readonly methodDispatched: true;
|
|
84
84
|
readonly methods: readonly ["POST"];
|
|
85
85
|
}];
|
|
@@ -5,7 +5,7 @@ import { isParkedRunStatus, isTerminalRunStatus } from "../../plugins/store-cont
|
|
|
5
5
|
import { HttpError } from "../../security.js";
|
|
6
6
|
import { scopedIdempotencyKey } from "../idempotency.js";
|
|
7
7
|
import { sendJson, sendError } from "../send.js";
|
|
8
|
-
import {
|
|
8
|
+
import { admissionRefusalOf, readinessOf, sendBillableRefusal } from "../readiness.js";
|
|
9
9
|
import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
|
|
10
10
|
import { assertNever } from "../route-ctx.js";
|
|
11
11
|
import * as facts from "../../capability-facts.js";
|
|
@@ -168,10 +168,10 @@ function rpcErrorFromTypedFailure(err, skillId) {
|
|
|
168
168
|
}
|
|
169
169
|
function submitUnavailable(res, ctx) {
|
|
170
170
|
const { deps } = ctx;
|
|
171
|
-
const
|
|
172
|
-
if (
|
|
171
|
+
const refusal = admissionRefusalOf(readinessOf(deps), deps.servingLease?.(), "submit");
|
|
172
|
+
if (refusal === undefined)
|
|
173
173
|
return false;
|
|
174
|
-
sendBillableRefusal(res,
|
|
174
|
+
sendBillableRefusal(res, refusal);
|
|
175
175
|
return true;
|
|
176
176
|
}
|
|
177
177
|
function readBlocking(params) {
|
|
@@ -392,6 +392,6 @@ export const A2A_SERVE_ROUTES = [
|
|
|
392
392
|
{ id: "a2a-agent-card", path: "/.well-known/agent-card.json", methods: ["GET"] },
|
|
393
393
|
];
|
|
394
394
|
export const A2A_RPC_ROUTES = [
|
|
395
|
-
{ id: "a2a-rpc", path: "/v1/a2a", billable:
|
|
395
|
+
{ id: "a2a-rpc", path: "/v1/a2a", billable: "submit", methodDispatched: true, methods: ["POST"] },
|
|
396
396
|
];
|
|
397
397
|
//# sourceMappingURL=a2a-serve.js.map
|
|
@@ -1,40 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* 编排壳在发停机信号(SIGTERM)**之前**先调本口显式声明停机因由;server 记入 `drainState.reason`,
|
|
4
|
-
* draining 期间由 `/health`(`drainReason`)与 503 draining 体(`reason`)透出。未声明 = 两面键缺席
|
|
5
|
-
* (additive,不编数)——升级/缩容/驱逐在消费端因此可判别,而不是一律读成「实例没了」。
|
|
2
|
+
* `POST /v1/admin/drain` —— 排空口(编排器长远稿片 2 件 2;契约体 = settings-schema 8.1.0 `DrainRequest`,回执 = `DrainReceipt`)。
|
|
6
3
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* (
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* `/health` 在 `server.ts` 的鉴权门**之前**、无鉴权、缺省全网卡监听,USAGE 三处白纸黑字把它定为「无需
|
|
19
|
-
* 鉴权」—— 任何能连到这个端口的人都读得到这段**运维手打的自由文本**。
|
|
20
|
-
* **裁定:行为不改。** 判据三条:① `/health` 免鉴权是既成契约,而它正是这条信息的设计消费者(k8s
|
|
21
|
-
* probe / LB / 编排器带不了 token,`drainReason` 放这儿的全部理由就是给它们看);② 值的内容完全由
|
|
22
|
-
* operator 自选,且窗口只在 draining 期;③ 同一免鉴权体上早已有 `dataRoot` 绝对路径 / `pid` / `port` /
|
|
23
|
-
* `instanceId` / `configHash` / `storeProbe`,单独收紧本键不改变那面的暴露量级。
|
|
24
|
-
* (那条另立的兄弟件 —— `storeProbe.error` 把**原始异常 message 未截断**塞出去、DSN 片段可随之外泄 ——
|
|
25
|
-
* 已在 [ref] 修掉:免鉴权面上它改成闭集词,原文只走日志轴。两件的判据不同故不合并:本键的值由 operator
|
|
26
|
-
* 自选且窗口只在 draining 期,`storeProbe.error` 的值则来自不受控的驱动异常。)
|
|
27
|
-
* ⇒ 真正的缺口是**告诫缺席**:此前路由头注、400 文案、CHANGELOG、wire 契约四处都只说「透出」,没有一处
|
|
28
|
-
* 说「其中一面免鉴权」,操作员要拼出「我写的字是公开的」得自己合并两处文档去推理。本批把它写进**写口
|
|
29
|
-
* 现场**(400 文案 + 本注 + USAGE 端点条目),因为那是操作员唯一会读到的地方。
|
|
30
|
-
* 词表化(把自由文本换成受限枚举)= 行为变更,**不在本批**:它会让「rolling upgrade to 7.36.0」这类真正
|
|
31
|
-
* 有用的因由说不出来,取舍要属主拍。
|
|
4
|
+
* 一只入口的 HTTP 车道(Docker 车道 / 中心直调 / 人手形):体经契约读器 `readDrainRequest`(一处判形),身份四元经
|
|
5
|
+
* `fleet/drain-entry.ts` 的 `drainIdentityMismatches`(一处判身份;本引擎的 scope = `config.fleetScope`,env `FLEET_SCOPE` 的唯一读数),
|
|
6
|
+
* 进入排空经 `drainState.enter`(收尾链装的唯一入口)。
|
|
7
|
+
* · 门序 = operator 面的既有口径(`routes/retention-ops.ts` 逐字):身份 → 授权 → 验型;billable=false,**刻意不吃排空 503**
|
|
8
|
+
* (排空中的重放必须能拿到 `already_draining` 回执,吃了就自锁)。
|
|
9
|
+
* · 体坏 ⇒ 400 `request.field_invalid`(零状态变化);身份不符 ⇒ 409 `conflict.fleet_identity`(零状态变化;拒句点名不符的字段,不回显值);
|
|
10
|
+
* 没有收尾链(嵌入宿主 / 测试夹具)⇒ 501 `capability.drain_state_required`。
|
|
11
|
+
* · 首次 ⇒ 200 回执 `state:"draining"`;同一 `actionId`(或别的动作)再来 ⇒ 200 `already_draining`,回显第一次的 `since` / `deadlineAt`,不重置。
|
|
12
|
+
* · 人手形:`actionId` 自取(推荐 `manual:<uuid>`),身份照样四元比对 —— 非 fleet 成员不带 `expectedGeneration`(缺席对缺席);
|
|
13
|
+
* `instanceId` 在场才比(从本机 `/health` 读);不带 `scope` = 点名缺省 scope(多租引擎要带它自己的 `FLEET_SCOPE`)。
|
|
14
|
+
* 修前形(7.35.0 起的 `{reason}` 停机因由声明:只记一句话、不进排空)随契约体退役 —— 零别名;那一形的请求体读不成 `DrainRequest` ⇒ 400。
|
|
32
15
|
*/
|
|
33
16
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
34
17
|
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
35
18
|
export declare const ADMIN_DRAIN_PATH = "/v1/admin/drain";
|
|
36
|
-
/** 上限与 approvals 的 reason 族同量级(消费面同为人读短句;非同一常量——两面语义无关,独立演化)。 */
|
|
37
|
-
export declare const MAX_DRAIN_REASON_CHARS = 256;
|
|
38
19
|
export declare function handleAdminDrain(req: IncomingMessage, res: ServerResponse, match: RouteMatch<AdminDrainRouteId>, ctx: RouteCtx): Promise<void>;
|
|
39
20
|
export declare const ADMIN_DRAIN_ROUTES: readonly [{
|
|
40
21
|
readonly id: "admin-drain";
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { readDrainRequest } from "@sema-agent/settings-schema/fleet";
|
|
1
2
|
import { sendJson, sendError } from "../send.js";
|
|
2
3
|
import { gatedPrincipal, explicitOperatorOk, sendOperatorPrincipalsRequired, sendPrincipalRefusal } from "../principal-gate.js";
|
|
3
4
|
import * as facts from "../../capability-facts.js";
|
|
5
|
+
import { fleetScopeOf } from "../../config-types.js";
|
|
6
|
+
import { drainIdentityMismatches } from "../../fleet/drain-entry.js";
|
|
4
7
|
import { assertNever } from "../route-ctx.js";
|
|
5
8
|
export const ADMIN_DRAIN_PATH = "/v1/admin/drain";
|
|
6
|
-
export const MAX_DRAIN_REASON_CHARS = 256;
|
|
7
9
|
const OPERATOR_ONLY_MESSAGE = "drain declaration is operator-only: the caller principal is not in OPERATOR_PRINCIPALS";
|
|
8
10
|
export async function handleAdminDrain(req, res, match, ctx) {
|
|
9
11
|
const { deps } = ctx;
|
|
@@ -22,18 +24,22 @@ export async function handleAdminDrain(req, res, match, ctx) {
|
|
|
22
24
|
sendError(res, 403, "auth.operator_only", OPERATOR_ONLY_MESSAGE);
|
|
23
25
|
return;
|
|
24
26
|
}
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
sendError(res, 400, "request.field_invalid", `the drain reason is surfaced verbatim on the UNAUTHENTICATED /health endpoint (and in 503 draining bodies), so anyone who can reach this port can read it — do not put ticket ids, hostnames, or internal identifiers in it. body must be { reason: string }: a non-empty string of at most ${MAX_DRAIN_REASON_CHARS} characters`);
|
|
27
|
+
const request = readDrainRequest(await ctx.helpers.readJson(req));
|
|
28
|
+
if (request === undefined) {
|
|
29
|
+
sendError(res, 400, "request.field_invalid", "the drain request must be { actionId, worker, deadlineAt, scope?, expectedGeneration?, instanceId?, term? }: actionId a non-empty idempotency key, worker this engine's worker name, deadlineAt epoch milliseconds, expectedGeneration a canonical decimal string (\"7\") — nothing was changed");
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
const enter = deps.drainState?.enter;
|
|
33
|
+
if (enter === undefined) {
|
|
34
|
+
sendError(res, 501, "capability.drain_state_required", "this deployment has no drain entry wired (embedded in another process?) — there is no drain for this request to enter");
|
|
33
35
|
return;
|
|
34
36
|
}
|
|
35
|
-
deps.
|
|
36
|
-
|
|
37
|
+
const mismatched = drainIdentityMismatches(request, { scope: deps.config.fleetScope, worker: deps.config.configCenter?.worker, generation: deps.config.fleetGeneration, instanceId: deps.instanceId });
|
|
38
|
+
if (mismatched.length > 0) {
|
|
39
|
+
sendError(res, 409, "conflict.fleet_identity", `the drain request names a different instance than this engine (mismatched: ${mismatched.join(", ")}) — nothing was changed; compare with this engine's GET /health (worker comes from SEMA_REGISTRY_WORKER, generation from FLEET_GENERATION, scope from FLEET_SCOPE — unset, and a request without scope, mean "${fleetScopeOf(undefined)}")`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
sendJson(res, 200, enter({ actionId: request.actionId, deadlineAt: request.deadlineAt }));
|
|
37
43
|
return;
|
|
38
44
|
}
|
|
39
45
|
default:
|
|
@@ -183,17 +183,17 @@ export declare const APPROVALS_ASSISTANT_ROUTES: readonly [{
|
|
|
183
183
|
readonly id: "assistant-task-resume";
|
|
184
184
|
readonly pattern: RegExp;
|
|
185
185
|
readonly label: "/v1/assistant/tasks/:id/resume";
|
|
186
|
-
readonly billable:
|
|
186
|
+
readonly billable: "resume";
|
|
187
187
|
readonly methods: readonly ["POST"];
|
|
188
188
|
}, {
|
|
189
189
|
readonly id: "assistant-task-plan-review";
|
|
190
190
|
readonly pattern: RegExp;
|
|
191
191
|
readonly label: "/v1/assistant/tasks/:id/plan_review";
|
|
192
|
-
readonly billable:
|
|
192
|
+
readonly billable: "hitl-answer";
|
|
193
193
|
readonly methods: readonly ["POST"];
|
|
194
194
|
}, {
|
|
195
195
|
readonly prefix: "/v1/approvals";
|
|
196
|
-
readonly billable:
|
|
196
|
+
readonly billable: "hitl-answer";
|
|
197
197
|
}];
|
|
198
198
|
/** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
|
|
199
199
|
export type ApprovalsAssistantRouteId = RouteIdsOf<typeof APPROVALS_ASSISTANT_ROUTES>;
|
|
@@ -917,9 +917,9 @@ export const APPROVALS_ASSISTANT_ROUTES = [
|
|
|
917
917
|
{ id: "approval-exemptions-list", pattern: /^\/v1\/approvals\/(?<sessionId>[^/]+)\/exemptions(?:\/(?<tool>[^/]+))?$/, label: "/v1/approvals/:id/exemptions", methods: ["GET"] },
|
|
918
918
|
{ id: "approval-exemption-delete", pattern: /^\/v1\/approvals\/(?<sessionId>[^/]+)\/exemptions(?:\/(?<tool>[^/]+))?$/, label: "/v1/approvals/:id/exemptions", methods: ["DELETE"] },
|
|
919
919
|
{ id: "assistant-task-preempt", pattern: /^\/v1\/assistant\/tasks\/(?<taskId>[^/]+)\/preempt$/, label: "/v1/assistant/tasks/:id/preempt", methods: ["POST"] },
|
|
920
|
-
{ id: "assistant-task-resume", pattern: /^\/v1\/assistant\/tasks\/(?<taskId>[^/]+)\/resume$/, label: "/v1/assistant/tasks/:id/resume", billable:
|
|
921
|
-
{ id: "assistant-task-plan-review", pattern: /^\/v1\/assistant\/tasks\/(?<taskId>[^/]+)\/plan_review$/, label: "/v1/assistant/tasks/:id/plan_review", billable:
|
|
922
|
-
{ prefix: "/v1/approvals", billable:
|
|
920
|
+
{ id: "assistant-task-resume", pattern: /^\/v1\/assistant\/tasks\/(?<taskId>[^/]+)\/resume$/, label: "/v1/assistant/tasks/:id/resume", billable: "resume", methods: ["POST"] },
|
|
921
|
+
{ id: "assistant-task-plan-review", pattern: /^\/v1\/assistant\/tasks\/(?<taskId>[^/]+)\/plan_review$/, label: "/v1/assistant/tasks/:id/plan_review", billable: "hitl-answer", methods: ["POST"] },
|
|
922
|
+
{ prefix: "/v1/approvals", billable: "hitl-answer" },
|
|
923
923
|
];
|
|
924
924
|
const DECISION_DOOR_KEY_TABLE = deriveDecisionDoorKeyTable(DECISION_DOOR_FAMILY, APPROVALS_ASSISTANT_ROUTES);
|
|
925
925
|
//# sourceMappingURL=approvals-assistant.js.map
|
|
@@ -79,7 +79,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
79
79
|
: false,
|
|
80
80
|
sessionEvents: facts.sessionEventStream(deps).available,
|
|
81
81
|
fleet: facts.fleetStream(deps).available
|
|
82
|
-
? { stream: true, sessionScope: true, observe: true, resume: "snapshot", bgNotifyFailClosed: true, steerPriority: "engine", hookFailureNotice: true }
|
|
82
|
+
? { stream: true, sessionScope: true, observe: true, resume: "snapshot", bgNotifyFailClosed: true, steerPriority: "engine", hookFailureNotice: true, leaseClient: true, generationEcho: true }
|
|
83
83
|
: false,
|
|
84
84
|
workspace: facts.workspaceBrowse().available,
|
|
85
85
|
leader: facts.leaderLane(deps).available,
|
|
@@ -130,6 +130,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
130
130
|
readFace: deps.config.readFace ?? null,
|
|
131
131
|
writeProtection: projectWriteProtectionCapability(deps.writeProtection),
|
|
132
132
|
appendSystemPrompt: true,
|
|
133
|
+
outputStyle: true,
|
|
133
134
|
compactionModel: true,
|
|
134
135
|
subagentOutput: facts.subagentOutput(deps).available,
|
|
135
136
|
subagentStream: facts.subagentOutput(deps).available,
|
|
@@ -8,6 +8,7 @@ import { deviceOwnerDomainOf } from "../device-placement.js";
|
|
|
8
8
|
import { echoKeyName } from "../../request-key-closure.js";
|
|
9
9
|
const DEVICE_NOT_FOUND_MESSAGE = "device not found";
|
|
10
10
|
const NO_DEVICE_DOMAIN_MESSAGE = "device management needs a caller domain: this credential carries no verified tenant scope claim (registry auth-bridge JWT `scope`) and is not an explicit operator — connect through the registry auth bridge, or configure OPERATOR_PRINCIPALS";
|
|
11
|
+
const NO_ENROLL_DOMAIN_MESSAGE = "device enrollment needs a registry login that carries a tenant scope: this credential carries no verified tenant scope claim (registry auth-bridge JWT `scope`), and an operator principal carries no scope, so it cannot enroll a device or issue an enroll token — connect through the registry auth bridge with a tenant-scoped login";
|
|
11
12
|
const DEVICE_LANE_REQUIRED_MESSAGE = "device management requires the device execution lane (REMOTE_EXEC=device with a SQL store backend) — this deployment has no device lane wired";
|
|
12
13
|
const _deviceRouteCodesPinned = ["not_found.device", "device.rebind_rev_conflict", "device.rebind_active_run"];
|
|
13
14
|
void _deviceRouteCodesPinned;
|
|
@@ -164,7 +165,7 @@ export async function handleDevices(req, res, match, ctx) {
|
|
|
164
165
|
}
|
|
165
166
|
if (match.decl.id === "device-enroll-tokens") {
|
|
166
167
|
if (domain === undefined) {
|
|
167
|
-
sendError(res, 403, "auth.forbidden",
|
|
168
|
+
sendError(res, 403, "auth.forbidden", NO_ENROLL_DOMAIN_MESSAGE);
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
170
171
|
if ((await readClosedBody(req, res, readJson, [])) === null)
|
|
@@ -180,7 +181,7 @@ export async function handleDevices(req, res, match, ctx) {
|
|
|
180
181
|
}
|
|
181
182
|
if (match.decl.id === "device-enroll") {
|
|
182
183
|
if (domain === undefined) {
|
|
183
|
-
sendError(res, 403, "auth.forbidden",
|
|
184
|
+
sendError(res, 403, "auth.forbidden", NO_ENROLL_DOMAIN_MESSAGE);
|
|
184
185
|
return;
|
|
185
186
|
}
|
|
186
187
|
const raw = await readClosedBody(req, res, readJson, ENROLL_BODY_KEYS);
|
|
@@ -16,7 +16,7 @@ export declare function handleLeader(req: IncomingMessage, res: ServerResponse,
|
|
|
16
16
|
export declare const LEADER_ROUTES: readonly [{
|
|
17
17
|
readonly id: "leader-submit";
|
|
18
18
|
readonly path: "/v1/leader";
|
|
19
|
-
readonly billable:
|
|
19
|
+
readonly billable: "submit";
|
|
20
20
|
readonly methods: readonly ["POST"];
|
|
21
21
|
}, {
|
|
22
22
|
readonly id: "leader-detail";
|
|
@@ -51,7 +51,7 @@ export async function handleLeader(req, res, match, ctx) {
|
|
|
51
51
|
throw new Error(`leader endpoint did not claim its own declared route: ${match.decl.id}`);
|
|
52
52
|
}
|
|
53
53
|
export const LEADER_ROUTES = [
|
|
54
|
-
{ id: "leader-submit", path: LEADER_SUBMIT_PATH, billable:
|
|
54
|
+
{ id: "leader-submit", path: LEADER_SUBMIT_PATH, billable: "submit", methods: ["POST"] },
|
|
55
55
|
{ id: "leader-detail", pattern: /^\/v1\/leader\/(?<id>[^/]+)$/, label: "/v1/leader/:id", methods: ["GET"] },
|
|
56
56
|
];
|
|
57
57
|
//# sourceMappingURL=leader.js.map
|
|
@@ -58,7 +58,7 @@ export declare function handleMemoryConsolidation(req: IncomingMessage, res: Ser
|
|
|
58
58
|
export declare const MEMORY_CONSOLIDATION_ROUTES: readonly [{
|
|
59
59
|
readonly id: "memory-consolidation-run";
|
|
60
60
|
readonly path: "/v1/admin/memory/consolidation/run";
|
|
61
|
-
readonly billable:
|
|
61
|
+
readonly billable: "submit";
|
|
62
62
|
readonly methods: readonly ["POST"];
|
|
63
63
|
}, {
|
|
64
64
|
readonly id: "memory-consolidation-status";
|
|
@@ -163,7 +163,7 @@ export async function handleMemoryConsolidation(req, res, match, ctx) {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
export const MEMORY_CONSOLIDATION_ROUTES = [
|
|
166
|
-
{ id: "memory-consolidation-run", path: MEMORY_CONSOLIDATION_RUN_PATH, billable:
|
|
166
|
+
{ id: "memory-consolidation-run", path: MEMORY_CONSOLIDATION_RUN_PATH, billable: "submit", methods: ["POST"] },
|
|
167
167
|
{ id: "memory-consolidation-status", path: MEMORY_CONSOLIDATION_STATUS_PATH, methods: ["GET"] },
|
|
168
168
|
];
|
|
169
169
|
//# sourceMappingURL=memory-consolidation.js.map
|
|
@@ -22,7 +22,7 @@ export declare const NOTIFY_WAKE_ROUTES: readonly [{
|
|
|
22
22
|
readonly id: "session-wake";
|
|
23
23
|
readonly pattern: RegExp;
|
|
24
24
|
readonly label: "/v1/sessions/:id/$verb";
|
|
25
|
-
readonly billable:
|
|
25
|
+
readonly billable: "submit";
|
|
26
26
|
readonly methods: readonly ["POST"];
|
|
27
27
|
}, {
|
|
28
28
|
readonly id: "session-notify";
|
|
@@ -143,7 +143,7 @@ export async function handleNotifyWake(req, res, match, ctx) {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
export const NOTIFY_WAKE_ROUTES = [
|
|
146
|
-
{ id: "session-wake", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/wake$/, label: "/v1/sessions/:id/$verb", billable:
|
|
146
|
+
{ id: "session-wake", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/wake$/, label: "/v1/sessions/:id/$verb", billable: "submit", methods: ["POST"] },
|
|
147
147
|
{ id: "session-notify", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/notify$/, label: "/v1/sessions/:id/$verb", methods: ["POST"] },
|
|
148
148
|
];
|
|
149
149
|
//# sourceMappingURL=notify-wake.js.map
|
|
@@ -4,7 +4,7 @@ import { gatedPrincipal, explicitOperatorOk, sendOperatorPrincipalsRequired, sen
|
|
|
4
4
|
import { assertNever } from "../route-ctx.js";
|
|
5
5
|
import * as facts from "../../capability-facts.js";
|
|
6
6
|
import { createLogger } from "../../observability/logger.js";
|
|
7
|
-
import { RULE_IMPORT_RETRY_AFTER_SEC, RULE_SCOPE_REFUSAL_SENTENCE, MAX_RULE_SCOPE_CHARS, parseRuleScope, serializeRuleScope } from "../../rules-consent.js";
|
|
7
|
+
import { RULE_IMPORT_RETRY_AFTER_SEC, RULE_SCOPE_REFUSAL_SENTENCE, MAX_RULE_SCOPE_CHARS, buildCcImportLayerRootRefusal, parseRuleScope, serializeRuleScope } from "../../rules-consent.js";
|
|
8
8
|
import { CONTROL_AND_BIDI_CHARS } from "../../text-bidi.js";
|
|
9
9
|
import { HttpError } from "../../security.js";
|
|
10
10
|
import { MAX_RULE_TEXT_CHARS, RULE_BEHAVIORS, isValidDurableScope } from "@sema-agent/core";
|
|
@@ -173,8 +173,9 @@ export async function handleRules(req, res, match, ctx) {
|
|
|
173
173
|
sendError(res, 400, "request.body_shape", "body must be {layers:[{layer,path,root,content}]}");
|
|
174
174
|
return;
|
|
175
175
|
}
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
const refusedLayer = parsed.data.layers.findIndex(ccImportRootRefused);
|
|
177
|
+
if (refusedLayer >= 0) {
|
|
178
|
+
sendError(res, 400, "request.body_shape", buildCcImportLayerRootRefusal(refusedLayer));
|
|
178
179
|
return;
|
|
179
180
|
}
|
|
180
181
|
const prepared = await lane.prepareImport(principal, parsed.data.layers);
|
|
@@ -40,7 +40,7 @@ idemKey: string | undefined): Promise<{
|
|
|
40
40
|
export declare const RUNS_ROUTES: readonly [{
|
|
41
41
|
readonly id: "runs-submit";
|
|
42
42
|
readonly path: "/v1/runs";
|
|
43
|
-
readonly billable:
|
|
43
|
+
readonly billable: "submit";
|
|
44
44
|
readonly methods: readonly ["POST"];
|
|
45
45
|
}, {
|
|
46
46
|
readonly id: "run-steer";
|
|
@@ -99,7 +99,7 @@ export declare const RUN_VERBS_ROUTES: readonly [{
|
|
|
99
99
|
readonly id: "run-subagent-resume";
|
|
100
100
|
readonly pattern: RegExp;
|
|
101
101
|
readonly label: "/v1/runs/:id/subagents/:sub/*";
|
|
102
|
-
readonly billable:
|
|
102
|
+
readonly billable: "resume";
|
|
103
103
|
readonly methods: readonly ["POST"];
|
|
104
104
|
}, {
|
|
105
105
|
readonly id: "run-task-output";
|