@sema-agent/server 7.105.0 → 7.107.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 +105 -0
- package/README.md +2 -2
- package/USAGE.md +135 -43
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +2 -0
- package/deploy/sema-up/cluster-up.sh +15 -2
- package/deploy/sema-up/kube-up.sh +8 -3
- package/deploy/sema-up/lib/flagshape.sh +29 -0
- package/deploy/sema-up/lib/parse-machines.py +4 -1
- package/deploy/sema-up/sema-up.sh +11 -9
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +3 -1
- package/dist/approval-card.d.ts +1 -1
- package/dist/approval.d.ts +6 -1
- package/dist/approval.js +4 -4
- package/dist/auth-bridge.d.ts +14 -0
- package/dist/auth-bridge.js +7 -0
- package/dist/auto-mode-face.d.ts +49 -40
- package/dist/auto-mode-face.js +6 -68
- package/dist/bench/s1/live-deps.d.ts +9 -2
- package/dist/bench/s1/live-deps.js +13 -8
- package/dist/boot/budget-tracing.js +3 -2
- package/dist/boot/config-center.d.ts +4 -0
- package/dist/boot/config-center.js +95 -37
- package/dist/boot/coordinators.d.ts +1 -0
- package/dist/boot/coordinators.js +3 -1
- package/dist/boot/device-lane.d.ts +2 -1
- package/dist/boot/execution-env.d.ts +11 -0
- package/dist/boot/execution-env.js +30 -6
- package/dist/boot/execution-lane.js +2 -3
- package/dist/boot/leader.d.ts +2 -1
- package/dist/boot/leader.js +2 -0
- package/dist/boot/parked-revive-gate.d.ts +10 -7
- package/dist/boot/parked-revive-gate.js +4 -11
- package/dist/boot/resolve-spec.d.ts +1 -4
- package/dist/boot/resolve-spec.js +44 -21
- package/dist/boot/runner-deps.d.ts +5 -8
- package/dist/boot/runner-deps.js +3 -9
- package/dist/boot/side-query-lane.d.ts +2 -2
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +2 -1
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.d.ts +3 -2
- package/dist/boot/stage-06-runners.js +2 -5
- package/dist/boot/stage-07-capability-layer.d.ts +2 -2
- package/dist/boot/stage-07-capability-layer.js +1 -2
- package/dist/boot/stage-08-reapers.d.ts +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -2
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +2 -2
- package/dist/boot/stage-10-http-server.js +11 -7
- package/dist/boot/stores.d.ts +1 -1
- package/dist/boot/stores.js +6 -4
- package/dist/brain.d.ts +24 -2
- package/dist/brain.js +29 -10
- package/dist/budget.js +3 -0
- package/dist/capabilities/center-wiring.d.ts +64 -0
- package/dist/capabilities/center-wiring.js +24 -0
- package/dist/capabilities/composite-members.d.ts +89 -0
- package/dist/capabilities/composite-members.js +20 -0
- package/dist/capabilities/execution-lane.d.ts +3 -0
- package/dist/capabilities/execution-lane.js +5 -2
- package/dist/config-catalog.d.ts +35 -5
- package/dist/config-catalog.js +92 -26
- package/dist/config-center/apply-effective.d.ts +1 -1
- package/dist/config-center/apply-effective.js +7 -8
- package/dist/config-center/apply-ledger.d.ts +17 -0
- package/dist/config-center/apply-ledger.js +5 -0
- package/dist/config-center/center-request.d.ts +17 -0
- package/dist/config-center/center-request.js +4 -0
- package/dist/config-center/pull-state.d.ts +29 -0
- package/dist/config-center/pull-state.js +2 -0
- package/dist/config-types.d.ts +41 -14
- package/dist/config-types.js +1 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.js +69 -16
- package/dist/cross-session-settings.d.ts +1 -1
- package/dist/deployment-governance.d.ts +12 -1
- package/dist/deployment-governance.js +2 -0
- package/dist/execution-lane-caps.d.ts +30 -2
- package/dist/execution-lane-caps.js +12 -0
- package/dist/fleet-client.d.ts +18 -13
- package/dist/fleet-client.js +32 -22
- package/dist/fleet-lease.d.ts +72 -47
- package/dist/fleet-lease.js +66 -14
- package/dist/governance-http-status.d.ts +0 -1
- package/dist/governance-http-status.js +0 -1
- package/dist/hosted-posture.d.ts +6 -17
- package/dist/hosted-posture.js +11 -6
- package/dist/http/admission.js +14 -9
- package/dist/http/approval-sweeps.js +13 -1
- package/dist/http/dispatch.js +1 -1
- package/dist/http/durable-run-leg.js +2 -0
- package/dist/http/request-guards.d.ts +25 -0
- package/dist/http/request-guards.js +25 -5
- package/dist/http/resume-legs.js +19 -11
- package/dist/http/route-ctx.d.ts +9 -0
- package/dist/http/routes/approvals-assistant.js +0 -1
- package/dist/http/routes/capabilities.js +11 -12
- package/dist/http/routes/config-catalog.js +2 -1
- package/dist/http/routes/diagnostics.js +2 -0
- package/dist/http/routes/tasks.js +4 -1
- package/dist/http/server.d.ts +20 -4
- package/dist/http/session-inherit-env.d.ts +37 -0
- package/dist/http/session-inherit-env.js +6 -0
- package/dist/http/wire-types.d.ts +19 -28
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/key-resolver.d.ts +74 -19
- package/dist/key-resolver.js +65 -7
- package/dist/leader/leader.d.ts +6 -0
- package/dist/leader/leader.js +2 -1
- package/dist/leader/repair-wire.js +2 -1
- package/dist/leader/wire.d.ts +9 -1
- package/dist/leader/wire.js +10 -6
- package/dist/observability/fail-open.d.ts +5 -1
- package/dist/observability/fail-open.js +5 -1
- package/dist/observability/metrics.js +2 -0
- package/dist/observability/permission-rule-events.d.ts +1 -1
- package/dist/observability/permission-rule-events.js +1 -1
- package/dist/observability/run-terminal.d.ts +40 -1
- package/dist/observability/run-terminal.js +2 -5
- package/dist/observability/secret-env-scrub.js +1 -1
- package/dist/observability/tool-trace.d.ts +2 -0
- package/dist/operator-ask.d.ts +29 -1
- package/dist/operator-ask.js +6 -0
- package/dist/parked-decide.d.ts +14 -1
- package/dist/parked-decide.js +8 -1
- package/dist/plugins/session-policy-store-sql.d.ts +38 -7
- package/dist/plugins/session-policy-store-sql.js +64 -22
- package/dist/plugins/sql-driver.d.ts +1 -1
- package/dist/plugins/store-backend.d.ts +12 -3
- package/dist/plugins/store-backend.js +16 -5
- package/dist/plugins/tidb-pool.js +1 -1
- package/dist/read-face-posture.d.ts +4 -4
- package/dist/request-key-closure.js +2 -2
- package/dist/request-value-closure.d.ts +17 -5
- package/dist/request-value-closure.js +12 -0
- package/dist/router/route-orchestration.d.ts +11 -0
- package/dist/router/route-orchestration.js +7 -6
- package/dist/run-local.js +4 -2
- package/dist/runs.d.ts +2 -1
- package/dist/runs.js +4 -4
- package/dist/runtime-caps-resolver.d.ts +4 -10
- package/dist/runtime-governance.d.ts +8 -11
- package/dist/runtime-governance.js +10 -41
- package/dist/sealed-key.d.ts +0 -40
- package/dist/sealed-key.js +0 -20
- package/dist/server-secret-env.d.ts +11 -12
- package/dist/session-policy-wire.d.ts +5 -3
- package/dist/session-titler.d.ts +11 -0
- package/dist/session-titler.js +6 -2
- package/dist/sql-engine-posture.d.ts +2 -2
- package/dist/store-posture.d.ts +53 -0
- package/dist/store-posture.js +32 -0
- package/dist/task-settings.d.ts +64 -62
- package/dist/task-settings.js +57 -27
- package/dist/tool-approval.d.ts +51 -22
- package/dist/tool-approval.js +44 -8
- package/dist/tool-declaration-http-status.d.ts +12 -0
- package/dist/tool-declaration-http-status.js +12 -0
- package/dist/trace/core-keyset-guard.d.ts +7 -7
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +5 -0
- package/dist/trace/project.d.ts +7 -5
- package/dist/trace/project.js +51 -3
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/unattended-permission-mode.d.ts +110 -0
- package/dist/unattended-permission-mode.js +65 -0
- package/package.json +3 -3
- package/dist/boot/session-shell-gate-registry.d.ts +0 -39
- package/dist/boot/session-shell-gate-registry.js +0 -21
- package/dist/roster-bound-http-status.d.ts +0 -6
- package/dist/roster-bound-http-status.js +0 -6
package/dist/operator-ask.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export function operatorAsk(rule, message) {
|
|
2
2
|
return { action: "ask", message, decisionReason: "rule", matchedAskRule: rule };
|
|
3
3
|
}
|
|
4
|
+
export function operatorAlwaysHumanAsk(rule, message) {
|
|
5
|
+
return withDeploymentAuthorship(operatorAsk(rule, message));
|
|
6
|
+
}
|
|
7
|
+
export function withDeploymentAuthorship(ask) {
|
|
8
|
+
return { ...ask, authoredBy: "deployment" };
|
|
9
|
+
}
|
|
4
10
|
//# sourceMappingURL=operator-ask.js.map
|
package/dist/parked-decide.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 行永 parked、生命周期分叉)。
|
|
10
10
|
*/
|
|
11
11
|
import type { HostDecision } from "./host-decision.js";
|
|
12
|
-
import type { BackgroundAgentRecord, BackgroundAgentStore, CheckpointStore, QuestionAnswer, ResumeOutcome, TaskRegistry, WorkflowRun, WorkflowRunStore } from "@sema-agent/core";
|
|
12
|
+
import type { BackgroundAgentRecord, BackgroundAgentStore, CheckpointStore, QuestionAnswer, ResumeOutcome, TaskRegistry, ToolExecuteContext, WorkflowRun, WorkflowRunStore } from "@sema-agent/core";
|
|
13
13
|
import type { ParkedReviveSurfaceFactory } from "./parked-revive-surface.js";
|
|
14
14
|
import type { RebuiltInheritedGate } from "./boot/parked-revive-gate.js";
|
|
15
15
|
import { type WorkflowRunParkKey, type WorkflowAgentSessionIndex } from "./orchestration/workflow-agent-session-index.js";
|
|
@@ -142,6 +142,19 @@ export interface ParkedDecideDeps {
|
|
|
142
142
|
rebuildInheritedGate?: (row: BackgroundAgentRecord, identity: {
|
|
143
143
|
principal: string | undefined;
|
|
144
144
|
}) => Promise<RebuiltInheritedGate | undefined>;
|
|
145
|
+
/**
|
|
146
|
+
* 🔴 S-877 四续 ⑪ —— 复活子代**自己的**审批席(`ToolExecuteContext.onAsk`:core 的委派工具把它转给子代,子代自己的引擎问 —— 模式写门 /
|
|
147
|
+
* 形状层 —— 在这里答)。本腿直接调 `execute`,不经 Runner,ctx 由本腿铸;不给 ⇒ 子代自己的问落 subRunner 的 `deps.onAsk`(协调器
|
|
148
|
+
* `ask()` 的无 ALS 上下文臂,没有会话身份 ⇒ 读不了会话授权 ⇒ 每条非只读命令 / 每次写都再 park,`remember:"session"` 对复活子代无效;
|
|
149
|
+
* 子代活着时同一问落父的席、带身份、读得到授权 —— 同一个子代两种答案)。生产 = 协调器的窗 0 席 `boundUnattendedAsk`(与 bg run 腿给父任务
|
|
150
|
+
* 装的同一只):身份 = 行的根会话(与 remember 的授权锚同键 {@link grantRootOf})+ 权威 principal;授权读不中 ⇒ 无人值守政策口(park),
|
|
151
|
+
* 不放宽。缺席(协调器未装的部署)⇒ ctx 不带座,逐字今天。
|
|
152
|
+
*/
|
|
153
|
+
approverSeatFor?: (identity: {
|
|
154
|
+
owner: string | null;
|
|
155
|
+
taskId: string;
|
|
156
|
+
sessionId: string;
|
|
157
|
+
}) => NonNullable<ToolExecuteContext["onAsk"]>;
|
|
145
158
|
warn?: (event: string, fields: Record<string, unknown>) => void;
|
|
146
159
|
}
|
|
147
160
|
export interface ParkedDecideRequest {
|
package/dist/parked-decide.js
CHANGED
|
@@ -62,6 +62,9 @@ export async function findParkedAgentForCheckpoint(agentStore, cp) {
|
|
|
62
62
|
}
|
|
63
63
|
return undefined;
|
|
64
64
|
}
|
|
65
|
+
function grantRootOf(row) {
|
|
66
|
+
return row.rootSessionId ?? row.parentSessionId;
|
|
67
|
+
}
|
|
65
68
|
function mintReviveArgs(row, knownAgentTypes) {
|
|
66
69
|
return {
|
|
67
70
|
prompt: row.description ?? "resume parked agent",
|
|
@@ -148,7 +151,7 @@ function sessionGone(taskId) {
|
|
|
148
151
|
}
|
|
149
152
|
export async function decideParkedAgent(deps, req, match) {
|
|
150
153
|
const row = match.row;
|
|
151
|
-
const rememberRoot = req.grantRemember !== undefined ? (row
|
|
154
|
+
const rememberRoot = req.grantRemember !== undefined ? grantRootOf(row) : undefined;
|
|
152
155
|
if (req.grantRemember !== undefined && rememberRoot === undefined) {
|
|
153
156
|
return { status: 400, body: { error: "remember cannot be applied: this parked agent row carries no root/parent session anchor for the exemption", errorCode: "decide.parked_remember_unsupported", taskId: match.handle } };
|
|
154
157
|
}
|
|
@@ -234,9 +237,13 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
234
237
|
}
|
|
235
238
|
}
|
|
236
239
|
const outcome = buildParkedResumeOutcome(req);
|
|
240
|
+
const seatRoot = grantRootOf(row);
|
|
237
241
|
const ctx = {
|
|
238
242
|
toolCallId: `drv-${ticket.claimId}`,
|
|
239
243
|
...(surface.mountsCallerTool !== undefined ? { mountsCallerTool: surface.mountsCallerTool } : {}),
|
|
244
|
+
...(deps.approverSeatFor !== undefined && seatRoot !== undefined
|
|
245
|
+
? { onAsk: deps.approverSeatFor({ owner: decodeCheckpointScope(req.scope) ?? null, taskId: match.handle, sessionId: seatRoot }) }
|
|
246
|
+
: {}),
|
|
240
247
|
reviveClaim: {
|
|
241
248
|
row: { ...ticket.row, parkClaimId: ticket.claimId },
|
|
242
249
|
rev: ticket.reservedRev,
|
|
@@ -3,6 +3,7 @@ import type { Pool as MySqlPool } from "mysql2/promise";
|
|
|
3
3
|
import type { Pool as PgPool } from "pg";
|
|
4
4
|
import { type JsonColumnShape } from "./sql-json-column.js";
|
|
5
5
|
import { type SqlDriver } from "./sql-driver.js";
|
|
6
|
+
import type { Logger } from "../observability/logger.js";
|
|
6
7
|
/** `session_policy.policy_key` 的**唯一铸造点**(core 复合键的 sha256)。
|
|
7
8
|
*
|
|
8
9
|
* 🔴 导出而不是让收编腿再抄一份(clay 宪法「schema 单一属主禁复制」):[ref] form b 的身份重绑要
|
|
@@ -32,20 +33,26 @@ export declare const policyKeyFor: (sessionId: string, principal: string | undef
|
|
|
32
33
|
* + 恰好解一次 `decodeJsonColumn` + 折码 `foldJsonColumn`);本店只留「形判据是哪一只」与「坏体折成哪个码」。
|
|
33
34
|
*/
|
|
34
35
|
export declare function readSessionRulesBody(body: unknown): JsonColumnShape<SessionPermissionRules>;
|
|
36
|
+
/** 本店唯一的日志口:抹除封键那一臂(S-891)的点名。缺席 ⇒ 退回 `console.warn`(不静默)。 */
|
|
37
|
+
export type SessionPolicyStoreLogger = Pick<Logger, "warn">;
|
|
35
38
|
/** Dual-dialect durable SessionPolicyStore. See the file header for the dialect-delta ledger. */
|
|
36
39
|
export declare class SqlSessionPolicyStore implements SessionPolicyStore {
|
|
37
40
|
private readonly db;
|
|
38
|
-
|
|
41
|
+
private readonly logger?;
|
|
42
|
+
constructor(db: SqlDriver, logger?: SessionPolicyStoreLogger | undefined);
|
|
39
43
|
/** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
|
|
40
44
|
private q;
|
|
41
45
|
/** 判据属主 = `sql-errors.ts`([ref] P1-①;本站点旧形已是并集形,收编后语义不变)。 */
|
|
42
46
|
private isDupKey;
|
|
43
|
-
/** Read the stored rules for (session, principal-owner); null when none (parity with InMemory).
|
|
47
|
+
/** Read the stored rules for (session, principal-owner); null when none (parity with InMemory).
|
|
48
|
+
* 🔴 S-891(codex r1 [high] ①):同一条语句带上这把键的**封键记号** —— 封着 ⇒ `corrupt`,不答「没有规则」(见 {@link sealedKeyError})。 */
|
|
44
49
|
getRules(sessionId: string, principal?: string): Promise<StoredSessionRules | null>;
|
|
45
50
|
/** 2c session-sync: every (principal, rules) record for a session across ALL principals — getRules is
|
|
46
51
|
* per-(session,principal), but a cross-backend session EXPORT must bundle the WHOLE session's policy (the importer
|
|
47
52
|
* replays each via putRules). principal is read from the DENORMALIZED column (the sha256 policy_key is one-way);
|
|
48
|
-
* NULL → undefined = the session-wide row (parity with core's InMemory `principal ?? undefined`). Rules carry rev.
|
|
53
|
+
* NULL → undefined = the session-wide row (parity with core's InMemory `principal ?? undefined`). Rules carry rev.
|
|
54
|
+
* 🔴 S-891:会话里有**封着的键** ⇒ 整只枚举 `corrupt`(与一行读不出形时整只枚举拒同一口径 —— 导出一份缺了那把键的规则,
|
|
55
|
+
* 导入方就会把「状态未知」当成「这个 principal 没有规则」)。 */
|
|
49
56
|
listBySession(sessionId: string): Promise<SessionRulesRecord[]>;
|
|
50
57
|
/** Operator/owner write: CAS + tighten-only (unless operator) + rev bump, atomic in one txn; first-write race retried. */
|
|
51
58
|
putRules(sessionId: string, principal: string | undefined, rules: SessionPermissionRules, opts?: PutRulesOptions): Promise<StoredSessionRules>;
|
|
@@ -91,7 +98,8 @@ export declare class SqlSessionPolicyStore implements SessionPolicyStore {
|
|
|
91
98
|
* against a CURRENT read (PG is pinned to READ COMMITTED, TiDB's DML is a current read), so a row for a
|
|
92
99
|
* DIFFERENT principal that a concurrent putRules commits BETWEEN ① and ③ would be deleted by a session-wide
|
|
93
100
|
* DELETE while ② never marked it — a silently reset rev line, exactly the defect this batch exists to close.
|
|
94
|
-
* Scoped to the key set, the invariant is exact and checkable: **every row this seam deletes leaves a mark
|
|
101
|
+
* Scoped to the key set, the invariant is exact and checkable: **every row this seam deletes leaves a mark** —
|
|
102
|
+
* a readable row's mark continues its rev line and ends its lineage; an unreadable row's mark SEALS its key (S-891, below).
|
|
95
103
|
* A row born after ① is simply not part of this erase (same as one born a microsecond after ③ — the caller
|
|
96
104
|
* that needs "and nothing came back" re-issues; the seam is idempotent).
|
|
97
105
|
* No rows ⇒ return early: an erase of a session that never had rules writes NO mark (no line, nothing to keep),
|
|
@@ -99,8 +107,31 @@ export declare class SqlSessionPolicyStore implements SessionPolicyStore {
|
|
|
99
107
|
* Why the mark is a separate table and not a tombstone row: see the DDL comment in tidb-pool.ts — a tombstone
|
|
100
108
|
* would keep `session_id` + the DENORMALIZED plaintext `principal` in the table after an E21 purge, i.e. the
|
|
101
109
|
* right-to-delete would degrade to buy rev ordering. The mark keeps only the one-way hash, the session id and
|
|
102
|
-
* an integer.
|
|
110
|
+
* an integer.
|
|
111
|
+
*
|
|
112
|
+
* 🔴 **S-891(7.106.0)—— 抹除恒删行;计数读不出的行留「封键记号」**。一行的计数读不出(三个读点的同一只判词
|
|
113
|
+
* {@link readRowCounters} 判坏:`rev` 不是 ≥ 1 的安全整数 / `gen` 不是 ≥ 0 的安全整数;或下一代的号 `gen + 1` 已出安全整数)
|
|
114
|
+
* ⇒ 这一行**照删**,它的记号元组写 `(键, 会话, {@link SEALED_MARK_REV}, 0, 时刻)`、走**同一句** `GREATEST` 合并:rev 落上界,
|
|
115
|
+
* 世代 = `GREATEST(旧记号.gen + 1, 0)`(有旧记号照抬,没有就是 0)。于是这把键的下一次 `putRules` 算出 `上界 + 1`,被写前那道
|
|
116
|
+
* **同一只** {@link isStoredCounter} 拒(`corrupt`,零写);读面同答 `corrupt`({@link sealedKeyError})—— **封键**,直到运营方
|
|
117
|
+
* 显式解封(USAGE §9.11:原地把记号的 `rev` 归 0、`gen` + 1;不删记号,删了会交回世代)。
|
|
118
|
+
* 两条都不许丢,所以是这个形:
|
|
119
|
+
* · **删得掉** —— 抹除是运营方清坏行的出路(E21 `DELETE /v1/sessions/:id`、2c overwrite-dst 都经这里)。7.105.0 的「拒,一条写
|
|
120
|
+
* 都不发」让一行坏计数把整个会话的删除半程停住、重试恒败;
|
|
121
|
+
* · **不许同世代** —— 修复车 QS 的「照删、不写记号」被 codex 真复现:这把键的血统没在本次抹除上结束,重建行从旧记号起号、
|
|
122
|
+
* 与持有**旧快照**的活读者同世代 ⇒ 读者把重建的宽松行读成延续(deny → allow)。读不出的号不能拿来结束血统,
|
|
123
|
+
* 那就不让这把键再有下一行,直到有人看过。
|
|
124
|
+
* 响亮面:提交**之后**一条 warn(`session_policy_erase_sealed_keys`,点名会话与被封的键 + 两个计数的原文;回滚了就什么都没封,
|
|
125
|
+
* 死锁重试也不重复报)。这**不是** fail-open(没有放行任何东西,方向是 fail-closed),所以不走 `recordFailOpen`;普查登记 P 类终态。
|
|
126
|
+
* 残余(如实):记号里**已有**的世代若被列外改到 BIGINT 上界,冲突臂的 `记号.gen + 1` 在库里溢出 ⇒ 这次抹除响亮失败
|
|
127
|
+
* (驱动错误;那只记号本来就读不回,`putRules` 同样拒)—— 抹除不读记号,这一形不在本判词射程内。 */
|
|
103
128
|
deleteBySession(sessionId: string): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* S-891 封键的响亮面:一次抹除至多一条 warn。日志座**抛** ⇒ 退回 `console.warn` 带原句 —— 走到这里删除已经提交,
|
|
131
|
+
* 一次日志故障不许把它报成失败(E21 会因此 500、overwrite-dst 会因此中止;codex 7.105.0 修复验证轮 [medium] 同形)。
|
|
132
|
+
* 缺日志座(直接 `new` 本店的工具 / 测试)⇒ `console.warn`,不静默。
|
|
133
|
+
*/
|
|
134
|
+
private discloseSealed;
|
|
104
135
|
/**
|
|
105
136
|
* The store's one read-check-write transaction. Its load-bearing read is line ~106's
|
|
106
137
|
* `SELECT rules, rev … FOR UPDATE` — a LOCKING read, therefore a CURRENT read on both engines, which is
|
|
@@ -114,10 +145,10 @@ export declare class SqlSessionPolicyStore implements SessionPolicyStore {
|
|
|
114
145
|
}
|
|
115
146
|
/** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
|
|
116
147
|
export declare class TiDBSessionPolicyStore extends SqlSessionPolicyStore {
|
|
117
|
-
constructor(pool: MySqlPool);
|
|
148
|
+
constructor(pool: MySqlPool, logger?: SessionPolicyStoreLogger);
|
|
118
149
|
}
|
|
119
150
|
/** PostgreSQL binding — historical class name + ctor shape preserved. */
|
|
120
151
|
export declare class PgSessionPolicyStore extends SqlSessionPolicyStore {
|
|
121
|
-
constructor(pool: PgPool);
|
|
152
|
+
constructor(pool: PgPool, logger?: SessionPolicyStoreLogger);
|
|
122
153
|
}
|
|
123
154
|
//# sourceMappingURL=session-policy-store-sql.d.ts.map
|
|
@@ -26,9 +26,15 @@ function isStoredCounter(n, floor) {
|
|
|
26
26
|
return Number.isSafeInteger(n) && n >= floor;
|
|
27
27
|
}
|
|
28
28
|
function rowToStored(r, where) {
|
|
29
|
+
const counters = readRowCounters(r, where);
|
|
30
|
+
if ("problem" in counters)
|
|
31
|
+
throw new SessionPolicyError("corrupt", counters.problem);
|
|
32
|
+
return { ...readRulesBody(r.rules, `session_policy.rules ${where}`), rev: counters.rev, gen: counters.gen };
|
|
33
|
+
}
|
|
34
|
+
function readRowCounters(r, where) {
|
|
29
35
|
const rev = Number(r.rev);
|
|
30
36
|
if (!isStoredCounter(rev, 1)) {
|
|
31
|
-
|
|
37
|
+
return { problem: `session rules row ${where} carries rev ${String(r.rev)} — a stored row's rev is always a whole number ≥ 1 within the safe-integer range (the stamp is floor+1), and rev 0 is the live reader's synthetic empty seed; refusing rather than serve it as rules` };
|
|
32
38
|
}
|
|
33
39
|
if (r.gen === undefined || r.gen === null) {
|
|
34
40
|
throw new Error(`session_policy row ${where} came back without a \`gen\` column — a read leg did not project it (the column is BIGINT NOT NULL on both dialects). ` +
|
|
@@ -36,14 +42,22 @@ function rowToStored(r, where) {
|
|
|
36
42
|
}
|
|
37
43
|
const gen = Number(r.gen);
|
|
38
44
|
if (!isStoredCounter(gen, 0)) {
|
|
39
|
-
|
|
45
|
+
return { problem: `session rules row ${where} carries lineage gen ${String(r.gen)} — a generation is a whole number ≥ 0 within the safe-integer range; refusing rather than let a reader judge lineage by a value nothing minted` };
|
|
40
46
|
}
|
|
41
|
-
return {
|
|
47
|
+
return { rev, gen };
|
|
48
|
+
}
|
|
49
|
+
const SEALED_MARK_REV = Number.MAX_SAFE_INTEGER;
|
|
50
|
+
const SEALED_EVENT = "session_policy_erase_sealed_keys";
|
|
51
|
+
const NO_SEALED_KEYS = [];
|
|
52
|
+
function sealedKeyError(sessionId, what) {
|
|
53
|
+
return new SessionPolicyError("corrupt", `session rules for session ${JSON.stringify(sessionId)}: ${what} is sealed — an erase removed a row whose counters were unreadable (session_policy_rev_mark.rev = ${SEALED_MARK_REV}), so its rule state is unknown; refusing rather than serve it as "no rules" until an operator unseals it (USAGE §9.11)`);
|
|
42
54
|
}
|
|
43
55
|
export class SqlSessionPolicyStore {
|
|
44
56
|
db;
|
|
45
|
-
|
|
57
|
+
logger;
|
|
58
|
+
constructor(db, logger) {
|
|
46
59
|
this.db = db;
|
|
60
|
+
this.logger = logger;
|
|
47
61
|
}
|
|
48
62
|
q(tidb, pg) {
|
|
49
63
|
return this.db.dialect === "tidb" ? tidb : pg;
|
|
@@ -52,13 +66,23 @@ export class SqlSessionPolicyStore {
|
|
|
52
66
|
return isDupKeyError(this.db.dialect, e);
|
|
53
67
|
}
|
|
54
68
|
async getRules(sessionId, principal) {
|
|
55
|
-
const
|
|
56
|
-
|
|
69
|
+
const key = policyKey(sessionId, principal);
|
|
70
|
+
const { rows } = await this.db.query(this.q(`SELECT 'row' AS src, ${jsonColumnText("tidb", "rules")}, rev, gen FROM session_policy WHERE policy_key = ?` +
|
|
71
|
+
" UNION ALL SELECT 'seal', NULL, rev, gen FROM session_policy_rev_mark WHERE policy_key = ? AND rev = ?", `SELECT 'row' AS src, ${jsonColumnText("pg", "rules")}, rev, gen FROM session_policy WHERE policy_key = $1` +
|
|
72
|
+
" UNION ALL SELECT 'seal', NULL, rev, gen FROM session_policy_rev_mark WHERE policy_key = $2 AND rev = $3"), [key, key, SEALED_MARK_REV]);
|
|
73
|
+
if (rows.some((r) => r.src === "seal"))
|
|
74
|
+
throw sealedKeyError(sessionId, `key ${key}`);
|
|
75
|
+
const row = rows.find((r) => r.src === "row");
|
|
76
|
+
if (row === undefined)
|
|
57
77
|
return null;
|
|
58
|
-
return rowToStored(
|
|
78
|
+
return rowToStored(row, `for session ${JSON.stringify(sessionId)}`);
|
|
59
79
|
}
|
|
60
80
|
async listBySession(sessionId) {
|
|
61
|
-
const { rows } = await this.db.query(this.q(`SELECT
|
|
81
|
+
const { rows } = await this.db.query(this.q(`SELECT 'row' AS src, principal, ${jsonColumnText("tidb", "rules")}, rev, gen FROM session_policy WHERE session_id = ?` +
|
|
82
|
+
" UNION ALL SELECT 'seal', NULL, NULL, rev, gen FROM session_policy_rev_mark WHERE session_id = ? AND rev = ?", `SELECT 'row' AS src, principal, ${jsonColumnText("pg", "rules")}, rev, gen FROM session_policy WHERE session_id = $1` +
|
|
83
|
+
" UNION ALL SELECT 'seal', NULL, NULL, rev, gen FROM session_policy_rev_mark WHERE session_id = $2 AND rev = $3"), [sessionId, sessionId, SEALED_MARK_REV]);
|
|
84
|
+
if (rows.some((r) => r.src === "seal"))
|
|
85
|
+
throw sealedKeyError(sessionId, "one of its keys");
|
|
62
86
|
return rows.map((r) => ({
|
|
63
87
|
principal: r.principal == null ? undefined : String(r.principal),
|
|
64
88
|
rules: rowToStored(r, `for session ${JSON.stringify(sessionId)} (enumeration)`),
|
|
@@ -120,22 +144,20 @@ export class SqlSessionPolicyStore {
|
|
|
120
144
|
}
|
|
121
145
|
}
|
|
122
146
|
async deleteBySession(sessionId) {
|
|
123
|
-
await this.retrying(() => this.tx(async (conn) => {
|
|
147
|
+
const sealed = await this.retrying(() => this.tx(async (conn) => {
|
|
124
148
|
const { rows } = await conn.query(this.q("SELECT policy_key, rev, gen FROM session_policy WHERE session_id = ? ORDER BY policy_key FOR UPDATE", "SELECT policy_key, rev, gen FROM session_policy WHERE session_id = $1 ORDER BY policy_key FOR UPDATE"), [sessionId]);
|
|
125
149
|
if (rows.length === 0)
|
|
126
|
-
return;
|
|
150
|
+
return NO_SEALED_KEYS;
|
|
127
151
|
const now = new Date();
|
|
128
152
|
const keys = rows.map((r) => String(r.policy_key));
|
|
129
153
|
const params = [];
|
|
130
|
-
|
|
131
|
-
const rev = Number(r.rev);
|
|
132
|
-
const nextGen = Number(r.gen ?? 0) + 1;
|
|
133
|
-
if (!isStoredCounter(rev, 1) || !isStoredCounter(nextGen, 0)) {
|
|
134
|
-
throw new SessionPolicyError("corrupt", `session rules row for session ${JSON.stringify(sessionId)} carries rev ${String(r.rev)} / gen ${String(r.gen)} — the erase would leave a mark outside the safe-integer range; refusing before writing it`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
154
|
+
const sealedKeys = [];
|
|
137
155
|
const tuples = rows.map((r) => {
|
|
138
|
-
|
|
156
|
+
const counters = readRowCounters(r, `for session ${JSON.stringify(sessionId)} (erase)`);
|
|
157
|
+
const readable = !("problem" in counters) && isStoredCounter(counters.gen + 1, 0);
|
|
158
|
+
if (!readable)
|
|
159
|
+
sealedKeys.push({ policyKey: String(r.policy_key), rev: String(r.rev), gen: String(r.gen) });
|
|
160
|
+
params.push(String(r.policy_key), sessionId, readable ? counters.rev : SEALED_MARK_REV, readable ? counters.gen + 1 : 0, now);
|
|
139
161
|
const base = params.length - 5;
|
|
140
162
|
return this.db.dialect === "tidb" ? "(?,?,?,?,?)" : `($${base + 1},$${base + 2},$${base + 3},$${base + 4},$${base + 5})`;
|
|
141
163
|
});
|
|
@@ -144,7 +166,27 @@ export class SqlSessionPolicyStore {
|
|
|
144
166
|
" ON CONFLICT (policy_key) DO UPDATE SET rev = GREATEST(session_policy_rev_mark.rev, EXCLUDED.rev), gen = GREATEST(session_policy_rev_mark.gen + 1, EXCLUDED.gen), erased_at = EXCLUDED.erased_at"), params);
|
|
145
167
|
const holes = keys.map((_, i) => (this.db.dialect === "tidb" ? "?" : `$${i + 1}`)).join(",");
|
|
146
168
|
await conn.query(`DELETE FROM session_policy WHERE policy_key IN (${holes})`, keys);
|
|
169
|
+
return sealedKeys;
|
|
147
170
|
}));
|
|
171
|
+
if (sealed.length > 0)
|
|
172
|
+
this.discloseSealed(sessionId, sealed);
|
|
173
|
+
}
|
|
174
|
+
discloseSealed(sessionId, keys) {
|
|
175
|
+
const fields = {
|
|
176
|
+
sessionId,
|
|
177
|
+
keys,
|
|
178
|
+
note: "these rows' counters were unreadable, so the erase could not end their lineage: each key now carries a sealed rev mark and answers every read and write with SessionPolicyError corrupt until an operator unseals its session_policy_rev_mark row in place (USAGE §9.11)",
|
|
179
|
+
};
|
|
180
|
+
if (this.logger === undefined) {
|
|
181
|
+
console.warn(`[session-policy] ${SEALED_EVENT}`, fields);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
this.logger.warn(SEALED_EVENT, fields);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
console.warn(`[session-policy] ${SEALED_EVENT} — the injected logger threw (${err instanceof Error ? err.message : String(err)})`, fields);
|
|
189
|
+
}
|
|
148
190
|
}
|
|
149
191
|
async tx(fn) {
|
|
150
192
|
const conn = await this.db.connect();
|
|
@@ -168,13 +210,13 @@ export class SqlSessionPolicyStore {
|
|
|
168
210
|
}
|
|
169
211
|
}
|
|
170
212
|
export class TiDBSessionPolicyStore extends SqlSessionPolicyStore {
|
|
171
|
-
constructor(pool) {
|
|
172
|
-
super(mysqlDriver(pool));
|
|
213
|
+
constructor(pool, logger) {
|
|
214
|
+
super(mysqlDriver(pool), logger);
|
|
173
215
|
}
|
|
174
216
|
}
|
|
175
217
|
export class PgSessionPolicyStore extends SqlSessionPolicyStore {
|
|
176
|
-
constructor(pool) {
|
|
177
|
-
super(pgDriver(pool));
|
|
218
|
+
constructor(pool, logger) {
|
|
219
|
+
super(pgDriver(pool), logger);
|
|
178
220
|
}
|
|
179
221
|
}
|
|
180
222
|
//# sourceMappingURL=session-policy-store-sql.js.map
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
* `INTRA_TXN_PLAIN_READS` 每条带理由);真值神谕:`test/sql-txn-semantics-integration.test.ts`(三引擎)。
|
|
60
60
|
*
|
|
61
61
|
* **④ 引擎事实不进店** —— `SqlDriver.facts` 是**运维读面**(`GET /v1/diagnostics/wiring` 的 `sqlEngine`
|
|
62
|
-
* 段与 `GET /v1/capabilities` 的 `sql` 位)的料。店里不许出现 `if (engine === …)`:引擎判别在全仓
|
|
62
|
+
* 段与 `GET /v1/capabilities` 的 `store.sql` 位(7.106.0 起;此前顶层 `sql`,S-866 ①))的料。店里不许出现 `if (engine === …)`:引擎判别在全仓
|
|
63
63
|
* 只有连接初始化这一处,机器门 G-A 守着。`SqlDialect`(SQL **文本**方言,两值)与 `engine`(运行期
|
|
64
64
|
* 引擎事实,三值)是两条轴,刻意不合成一个三值。
|
|
65
65
|
*
|
|
@@ -13,6 +13,7 @@ import { type ApprovalAskStore as ApprovalAskStoreType } from "./approval-ask-st
|
|
|
13
13
|
import { type MemoryOptOutGrantStore } from "./memory-optout-grant-store-sql.js";
|
|
14
14
|
import { type SendFileLedger } from "./send-file-ledger.js";
|
|
15
15
|
import { TiDBResumeAnchorStore, PgResumeAnchorStore } from "./resume-anchor-store-sql.js";
|
|
16
|
+
import { type SessionPolicyStoreLogger } from "./session-policy-store-sql.js";
|
|
16
17
|
import type { ServiceConfig } from "../config-types.js";
|
|
17
18
|
import { TiDBRunStore, PgRunStore } from "./run-store-sql.js";
|
|
18
19
|
import { TiDBCheckpointStore, PgCheckpointStore } from "./checkpoint-store-sql.js";
|
|
@@ -31,6 +32,7 @@ import { type AdoptionLogStore } from "./adoption-log-sql.js";
|
|
|
31
32
|
import { type SqlRetentionStore, type SqlRetentionStoreOptions } from "./retention-store-sql.js";
|
|
32
33
|
import { type RetentionLaneStore } from "./retention-lane-store-sql.js";
|
|
33
34
|
import type { PermissionRuleStoreBundle } from "../rules-consent.js";
|
|
35
|
+
import { type StoreDegradedReason } from "../store-posture.js";
|
|
34
36
|
/** boot 期的一条日志座(级别由**座位**决定,不由参数携带):`createStoreBackend` 的 `onWarn` / `onError`
|
|
35
37
|
* 两参与 `LocalBackend` 的同名字段共用此形。装配层把它们分别接到 `logger.warn` / `logger.error` 上。 */
|
|
36
38
|
export type BootLogSink = (msg: string, meta?: Record<string, unknown>) => void;
|
|
@@ -194,8 +196,9 @@ export interface StoreBackend {
|
|
|
194
196
|
* mapping from URLs). REQUIRED on all backends (works local — one JSONL, like approvalExemption). */
|
|
195
197
|
sendFileLedger(): SendFileLedger;
|
|
196
198
|
/** E6 durable SessionPolicyStore (operator-tightened per-session tool rules). REQUIRED on all backends — works local
|
|
197
|
-
* (local = core's FileSessionPolicyStore), like resumeAnchor. Backs core's RunnerDeps.sessionPolicyStore.
|
|
198
|
-
|
|
199
|
+
* (local = core's FileSessionPolicyStore), like resumeAnchor. Backs core's RunnerDeps.sessionPolicyStore.
|
|
200
|
+
* `logger`:SQL 孪生抹除封键那一臂(S-891)的点名口(缺席 ⇒ 店退回 `console.warn`);local(core 的 File 店)不收。 */
|
|
201
|
+
sessionPolicy(logger?: SessionPolicyStoreLogger): ServiceSessionPolicyStore;
|
|
199
202
|
/** [ref] per-edited-file rewind FileHistoryStore (trackEdit/annulTrack/snapshot/restore over the tracked set;
|
|
200
203
|
* also the 2c history-transfer store). REQUIRED on all backends (local = core's file-backed
|
|
201
204
|
* `FileFileHistoryStore` under the data root). Backs core's RunnerDeps.fileHistoryStore. */
|
|
@@ -394,6 +397,12 @@ export declare function openStoreBackendWithFallback(config: ServiceConfig, logg
|
|
|
394
397
|
error?: (msg: string, meta?: Record<string, unknown>) => void;
|
|
395
398
|
}): Promise<{
|
|
396
399
|
backend: StoreBackend | undefined;
|
|
397
|
-
|
|
400
|
+
degradedReason: StoreDegradedReason | undefined;
|
|
398
401
|
}>;
|
|
402
|
+
/**
|
|
403
|
+
* S-866 ⑤c / G4 —— SQL 族「显式要了持久而店打不开」的拒启句(唯一铸点)。点名 `DB_BACKEND=<引擎>`,给三条出路:修坐标 / 连通性;
|
|
404
|
+
* `SESSION_BACKEND=auto`(尽力而为:店不可达时以内存起服,什么都不持久);`DB_BACKEND=memory`(显式内存部署)。驱动原文只取 message
|
|
405
|
+
* (与修前 `fatalExit` 打出的同一句),凭据不在其中(驱动不回显口令)。
|
|
406
|
+
*/
|
|
407
|
+
export declare function buildSqlStoreOpenRefusal(engine: "mysql" | "pg", err: unknown): string;
|
|
399
408
|
//# sourceMappingURL=store-backend.d.ts.map
|
|
@@ -52,6 +52,7 @@ import { TiDBRetentionLaneStore, PgRetentionLaneStore } from "./retention-lane-s
|
|
|
52
52
|
import { createSqlPermissionRuleStores } from "./permission-rule-store-sql.js";
|
|
53
53
|
import { createFilePermissionRuleStores } from "./permission-rule-store-file.js";
|
|
54
54
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
55
|
+
import { storeDegradedReasonFor } from "../store-posture.js";
|
|
55
56
|
function historyBlobBackend(config, dialect, pool) {
|
|
56
57
|
const s = config.snapshotBlobStore;
|
|
57
58
|
if (!s)
|
|
@@ -122,7 +123,7 @@ class TiDBBackend {
|
|
|
122
123
|
approvalAsk() { return new TiDBApprovalAskStore(this.pool); }
|
|
123
124
|
permissionRule() { return createSqlPermissionRuleStores(mysqlDriver(this.pool)); }
|
|
124
125
|
sendFileLedger() { return new TiDBSendFileLedger(this.pool); }
|
|
125
|
-
sessionPolicy() { return new TiDBSessionPolicyStore(this.pool); }
|
|
126
|
+
sessionPolicy(logger) { return new TiDBSessionPolicyStore(this.pool, logger); }
|
|
126
127
|
fileHistory() { return new TiDBFileHistoryStore(this.pool, historyBlobBackend(this.config, "tidb", this.pool), this.config.fileHistory); }
|
|
127
128
|
memoryOptOutGrant() { return new TiDBMemoryOptOutGrantStore(this.pool); }
|
|
128
129
|
sessionCaptureRecords() { return () => (this.captureRecordsInst ??= new TiDBSessionCaptureRecordStore(this.pool)); }
|
|
@@ -170,7 +171,7 @@ class PgBackend {
|
|
|
170
171
|
approvalAsk() { return new PgApprovalAskStore(this.pool); }
|
|
171
172
|
permissionRule() { return createSqlPermissionRuleStores(pgDriver(this.pool)); }
|
|
172
173
|
sendFileLedger() { return new PgSendFileLedger(this.pool); }
|
|
173
|
-
sessionPolicy() { return new PgSessionPolicyStore(this.pool); }
|
|
174
|
+
sessionPolicy(logger) { return new PgSessionPolicyStore(this.pool, logger); }
|
|
174
175
|
fileHistory() { return new PgFileHistoryStore(this.pool, historyBlobBackend(this.config, "pg", this.pool), this.config.fileHistory); }
|
|
175
176
|
memoryOptOutGrant() { return new PgMemoryOptOutGrantStore(this.pool); }
|
|
176
177
|
sessionCaptureRecords() { return () => (this.captureRecordsInst ??= new PgSessionCaptureRecordStore(this.pool)); }
|
|
@@ -346,15 +347,25 @@ export async function openStoreBackendWithFallback(config, logger) {
|
|
|
346
347
|
throw err;
|
|
347
348
|
if (err instanceof StoreBootRefusalError)
|
|
348
349
|
throw err;
|
|
349
|
-
if (!mayFallback)
|
|
350
|
+
if (!mayFallback) {
|
|
351
|
+
if (config.dbBackend === "mysql" || config.dbBackend === "pg")
|
|
352
|
+
throw new Error(buildSqlStoreOpenRefusal(config.dbBackend, err), { cause: err });
|
|
350
353
|
throw err;
|
|
354
|
+
}
|
|
351
355
|
logger.warn("store_db_unreachable_fallback_memory", {
|
|
352
356
|
backend: backend?.kind ?? config.dbBackend,
|
|
353
357
|
error: err instanceof Error ? err.message : String(err),
|
|
354
358
|
});
|
|
355
359
|
await backend?.close().catch(() => undefined);
|
|
356
|
-
return { backend: undefined,
|
|
360
|
+
return { backend: undefined, degradedReason: storeDegradedReasonFor(config.dbBackend) };
|
|
357
361
|
}
|
|
358
|
-
return { backend,
|
|
362
|
+
return { backend, degradedReason: undefined };
|
|
363
|
+
}
|
|
364
|
+
export function buildSqlStoreOpenRefusal(engine, err) {
|
|
365
|
+
const coords = engine === "pg" ? "PG_*" : "MYSQL_*";
|
|
366
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
367
|
+
return (`DB_BACKEND=${engine}: cannot open the configured ${engine === "pg" ? "PostgreSQL" : "MySQL-protocol"} store (${detail}) — refusing to boot, because DB_BACKEND asks for a durable store and SESSION_BACKEND does not allow running without it. ` +
|
|
368
|
+
`Next: fix the ${coords} coordinates or the store's reachability; or set SESSION_BACKEND=auto to boot in memory while the store is unreachable (best effort — nothing persists, and /health reports storeDegraded); ` +
|
|
369
|
+
`or set DB_BACKEND=memory for an explicitly in-memory deployment.`);
|
|
359
370
|
}
|
|
360
371
|
//# sourceMappingURL=store-backend.js.map
|
|
@@ -390,7 +390,7 @@ export const SCHEMA_STATEMENTS = [
|
|
|
390
390
|
-- 被删会话(**明文**会话 id —— 所以它必须随会话一起走,见上)。记号的读写按 policy_key 单键;托管留存的整树
|
|
391
391
|
-- 清扫按本列删(S-546 A9,索引 idx_session_policy_rev_mark_session)。
|
|
392
392
|
session_id VARCHAR(64) NOT NULL,
|
|
393
|
-
-- 抹除时刻该键带过的最高 rev(GREATEST 合并:记号只升不降)。
|
|
393
|
+
-- 抹除时刻该键带过的最高 rev(GREATEST 合并:记号只升不降)。= 安全整数上界 ⇒ 封键(S-891:被抹行的计数读不出;运营方原地解封,见 USAGE §9.11)。
|
|
394
394
|
rev BIGINT NOT NULL,
|
|
395
395
|
-- 这把键的**下一行从第几代开始**(每次抹除 +1;与 core File 侧车 <stem>.hwm.json 的 __gen 同义)。
|
|
396
396
|
-- 血统必须比它终结的那一行活得久,理由与 rev 完全一样:留在行里就等于跟着行一起消失。
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
* 本模块**只**管 `face` 一档。内建读拒表(`readDenyPatterns` / `readDenyBuiltinTiers` /
|
|
26
26
|
* `readDenyBuiltinExclude`)一个字节不动 —— 放开容纳面不等于放开那几档 deny,两者在 core 里是**并列
|
|
27
27
|
* 机制**(有活行的 deny 表在 open 面上照样拦)。
|
|
28
|
-
* ⚠️
|
|
29
|
-
*
|
|
30
|
-
* `
|
|
31
|
-
*
|
|
28
|
+
* ⚠️ **但别把这句话读成「每一档都一直在岗」**:内建表的缺省选择是 core 的 `READ_DENY_DEFAULT_TIERS` —— core 7.34.0 R-2 起
|
|
29
|
+
* = `credentials` + 家目录锚定的 `agent-config` 两档(7.22.0–7.33.x 是空选择 OFF;7.106.0 合并复审 X10-L2 改真)。一台没写
|
|
30
|
+
* `READ_DENY_BUILTIN_TIERS` 的部署上只有这两档生效(shell 历史 / 浏览器 / 钱包要显式写,`none` = 一行都不生效)。
|
|
31
|
+
* 本模块的定界不变:并列的那一侧开着哪几档,是那张表自己的缺省说了算。core 的 `config.read_face_deployment_clamped`
|
|
32
32
|
* (只读挂载钳制)同样不受本件影响。
|
|
33
33
|
*/
|
|
34
34
|
import { type PostureSource } from "./posture-source.js";
|
|
@@ -19,10 +19,10 @@ export function keyClosureClause(names, label) {
|
|
|
19
19
|
}
|
|
20
20
|
const TASK_BODY_DECLARED_KEYS = [
|
|
21
21
|
"a2aPeers", "acceptPartialRestore", "additionalDirectories", "additionalReadDirectories", "agents",
|
|
22
|
-
"appendSystemPrompt", "
|
|
22
|
+
"appendSystemPrompt", "attachmentIds", "attachments", "capabilitiesNeeded", "cascade", "clientContext",
|
|
23
23
|
"compaction", "compactionModel", "council", "cwd", "debate", "deferTools", "deviceId", "enableFork",
|
|
24
24
|
"excludeAllTools", "excludeTools", "fileHistory",
|
|
25
|
-
"finalVerification", "forwardSubagentEvents", "images", "interactiveTools", "jobId", "limits", "maxCostUsd",
|
|
25
|
+
"finalVerification", "forwardSubagentEvents", "images", "inheritEnv", "interactiveTools", "jobId", "limits", "maxCostUsd",
|
|
26
26
|
"maxTokens", "mcpServers", "memory", "memoryCapture", "memoryWrite", "model", "objective", "oneShot", "outputRetries",
|
|
27
27
|
"outputSchema", "permissionMode", "projectId", "promptProfile", "reasoningEffort", "requireExistingSession",
|
|
28
28
|
"resilience", "restoreFiles", "resumeAt", "resumeAtMode", "retainBackgroundProcesses", "retainSubagentSessions",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* 一个敲错的隐私/收紧请求被静默丢掉(用户以为关了,其实没关),或者一个敲错的键把功能静默关掉。
|
|
11
11
|
*
|
|
12
12
|
* 🔴 **判别是「值域恰好是一个关断词」,不是「值域恰好是一个字面量」**(S-724 加 `fileHistory` 时定形):
|
|
13
|
-
* `approverPosture`
|
|
14
|
-
* (
|
|
13
|
+
* 当年的反例是 `approverPosture`(值域也只有一个词 `"bypass"`,但它是一句**姿态声明**、闭集属主在 core,本仓刻意零判据);
|
|
14
|
+
* 那个请求键已随 core 7.34.0 退役(S-877,[ref] 裁 ③),判别式的理由不变 —— 一个非关断的单字面量键不属于本表。
|
|
15
15
|
* 关断词全集 {@link OffSwitchWord} 是 core 的两种关断拼法,新增第三种拼法 = 加一个词(并逐键重判)。
|
|
16
16
|
*
|
|
17
17
|
* ## 为什么是一只判据函数,不是每处一抄
|
|
@@ -53,10 +53,14 @@ export declare const FILE_HISTORY_OFF_SPELLING_MESSAGE = "fileHistory must be th
|
|
|
53
53
|
/**
|
|
54
54
|
* S-724 b′(主会话裁,车PW codex r1 [high] 复现后)—— `fileHistory: "disabled"` 与还原**同体**的拒句(`request.field_conflict`)。
|
|
55
55
|
* 与 `tool-approval.ts` 的 `SETTLED_BY_DENY_ONLY` / `READ_ROOT_GRANT_ALLOW_ONLY` **同一族**:「只看体就能判的矛盾,在裁决之前响亮说」。
|
|
56
|
-
* 这一对是调用方自己写出的矛盾(关掉这条 run 的文件历史,又要从文件历史还原);core 也拒(`rewind.capture_disabled`)
|
|
57
|
-
*
|
|
58
|
-
* 且拒后不回滚 ——
|
|
56
|
+
* 这一对是调用方自己写出的矛盾(关掉这条 run 的文件历史,又要从文件历史还原);core 也拒(`rewind.capture_disabled`)。立门时(7.104.0)
|
|
57
|
+
* core 的拒落在 prepare 的**会话分叉之后**(`prepare-acquire-reconcile` 先按 `resumeAt` 把活动分支移过去,`prepare-file-history` 才判),
|
|
58
|
+
* 且拒后不回滚 —— 所以本层在任何副作用之前拒。**这不是替 core 再判一次「捕获关了能不能还原」**:判的是体里两个键
|
|
59
59
|
* 能不能共存,不看部署、不看店。
|
|
60
|
+
* ⚠️ S-865 / S-877(core 7.35.0 #1195):core 起把只看请求体就判得出的拒因(本码在内)挪到**会话 acquire 之前**的请求门
|
|
61
|
+
* (`runner/file-history-request.js`),被拒的请求会话日志逐字节不动 —— 本门当年防的那个副作用在引擎侧已不存在。本门的 400
|
|
62
|
+
* 照旧有效(core wire 单:「the at-accept 400 stays valid」),退不退(退则 fresh 腿改落 core 的 422 `rewind.capture_disabled`)
|
|
63
|
+
* 列 7.106.0 收车档候裁。
|
|
60
64
|
* 判据:{@link fileHistoryRestoreConflict}。**只在 fresh 受理门**(`http/admission.ts`);续跑腿不过这道门(RESUME 重放不过受理门,
|
|
61
65
|
* 同律 —— 且续跑腿重建时整族剥掉一次性历史动作,矛盾在盘上的体里本就留不下来)。wire 可见、字节冻结。
|
|
62
66
|
*/
|
|
@@ -96,5 +100,13 @@ export interface SingleMemberOffFieldIssue {
|
|
|
96
100
|
* 全部合法或全部缺席 ⇒ `null`。**两腿(fresh admission / resume 重放)共用这一只**。
|
|
97
101
|
*/
|
|
98
102
|
export declare function singleMemberOffFieldIssue(raw: unknown): SingleMemberOffFieldIssue | null;
|
|
103
|
+
/** `inheritEnv` 的受理词表(闭集;**顺序 = 拒句里的列举顺序**)。 */
|
|
104
|
+
export declare const INHERIT_ENV_WORDS: readonly ["all", "scrub"];
|
|
105
|
+
/** 一条请求能声明的继承策略(wire 形,见 `TaskRequestBody.inheritEnv`)。 */
|
|
106
|
+
export type TaskInheritEnv = (typeof INHERIT_ENV_WORDS)[number];
|
|
107
|
+
/** 词表外的拒句(wire 可见;点名两词与各自的意思 —— 拿错的人要在同一条回执里读到该发什么)。 */
|
|
108
|
+
export declare const INHERIT_ENV_VALUE_MESSAGE: string;
|
|
109
|
+
/** `inheritEnv` 在场且不在词表里 ⇒ 拒句;缺席或合法 ⇒ `null`。**两腿(fresh admission / resume 重放)共用这一只**。 */
|
|
110
|
+
export declare function inheritEnvFieldIssue(raw: unknown): string | null;
|
|
99
111
|
export {};
|
|
100
112
|
//# sourceMappingURL=request-value-closure.d.ts.map
|
|
@@ -23,4 +23,16 @@ export function singleMemberOffFieldIssue(raw) {
|
|
|
23
23
|
}
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
|
+
export const INHERIT_ENV_WORDS = ["all", "scrub"];
|
|
27
|
+
export const INHERIT_ENV_VALUE_MESSAGE = `inheritEnv must be one of ${INHERIT_ENV_WORDS.map((w) => JSON.stringify(w)).join(" | ")} when present ` +
|
|
28
|
+
`("all" = the model-driven host shell inherits this machine's whole environment, honored only on a single-user host-lane deployment; ` +
|
|
29
|
+
`"scrub" = the default scrub of credential-shaped variables, accepted on every lane; absent = "scrub") — a name list is not a request value`;
|
|
30
|
+
export function inheritEnvFieldIssue(raw) {
|
|
31
|
+
if (raw === null || raw === undefined || typeof raw !== "object" || Array.isArray(raw))
|
|
32
|
+
return null;
|
|
33
|
+
const value = raw.inheritEnv;
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
return null;
|
|
36
|
+
return INHERIT_ENV_WORDS.includes(value) ? null : INHERIT_ENV_VALUE_MESSAGE;
|
|
37
|
+
}
|
|
26
38
|
//# sourceMappingURL=request-value-closure.js.map
|
|
@@ -104,6 +104,17 @@ export declare function isIsolatedExecEnv(provider?: string, runtimeClass?: stri
|
|
|
104
104
|
* - `toolPolicy` — ALSO ask on the direct write hand tools `Edit`/`Write` (review HIGH: shellGate alone
|
|
105
105
|
* leaves a non-isolated SUP task able to mutate the real fs via write tools ungated). The worker's existing
|
|
106
106
|
* approval machinery (durable suspend / poll) enforces the `ask` — same precondition as `shellGate:"always"`.
|
|
107
|
+
*
|
|
108
|
+
* 🔴 S-897(7.106.0,[ref]):这些 ask 是**部署**(运维开的 `ROUTER_ENABLED` 路由)写下的门 ⇒ 盖 core 的部署出身位
|
|
109
|
+
* `authoredBy:"deployment"`,经**唯一**盖位函数(`operator-ask.ts` 的 `withDeploymentAuthorship`,经 `operatorAlwaysHumanAsk`
|
|
110
|
+
* 取用 —— 治理层 ask、`APPROVAL_NEVER_AUTO` / 预算耗尽 ask 同一只)。修前:写工具的 ask 只自报 `matchedAskRule`(非强制卡),
|
|
111
|
+
* shellGate always 的 shell ask 是 core 自铸的、没有出身位 ⇒ 卡上只剩单个强制词(`operator_always`;读边界探针命中时是
|
|
112
|
+
* `probe_mandate`,它把 always 藏在后面)⇒ bypass 下壳按那一个词放行(cli L-758 的规则面)。盖了之后 core 判 `mandated`、
|
|
113
|
+
* 不铸 `mandate` 词(表外的门)⇒ 活卡与 park 行两腿都读成「另有门在」,壳出卡。
|
|
114
|
+
* shell 那一问的出身只能由**策略的** ask 带进去(core 的 shell 座铸的 ask 没有出身位的写口)⇒ 策略对 shell 工具也问一次,
|
|
115
|
+
* core 把它与 shellGate always 折成同一只 ask(不是两问)。
|
|
116
|
+
* ⚠️ 连带(产品面,如实):写工具的 ask 从此也是强制卡 ⇒ 用户的「本会话放行」不再退役 SUP 路由的写门(shell 那一问修前就退不掉)。
|
|
117
|
+
* 不改路由判据本身(`routeServiceTask` 一字不动)。
|
|
107
118
|
*/
|
|
108
119
|
export declare function supPostureOverrides(): Partial<TaskSpec>;
|
|
109
120
|
//# sourceMappingURL=route-orchestration.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {} from "@sema-agent/core";
|
|
2
|
-
import {
|
|
1
|
+
import { COARSE_SHELL_TOOLS } from "@sema-agent/core";
|
|
2
|
+
import { operatorAlwaysHumanAsk } from "../operator-ask.js";
|
|
3
3
|
export function routeOrchestration(s) {
|
|
4
4
|
if (s.explicitMode)
|
|
5
5
|
return { mode: s.explicitMode, reason: `explicit:${s.explicitMode}` };
|
|
@@ -40,11 +40,12 @@ export function isIsolatedExecEnv(provider, runtimeClass) {
|
|
|
40
40
|
return false;
|
|
41
41
|
}
|
|
42
42
|
const WRITE_HAND_TOOLS = new Set(["Edit", "Write", "NotebookEdit"]);
|
|
43
|
+
const SHELL_TOOLS = new Set(COARSE_SHELL_TOOLS);
|
|
43
44
|
export function supPostureOverrides() {
|
|
44
|
-
const ask = (toolName) =>
|
|
45
|
-
const
|
|
46
|
-
check: (req) => (WRITE_HAND_TOOLS.has(req.toolName) ? ask(req.toolName) : { action: "allow" }),
|
|
45
|
+
const ask = (toolName) => operatorAlwaysHumanAsk(`supervisor routing(capability-danger:${toolName})`, `${toolName} gated by supervisor routing (capability-danger task)`);
|
|
46
|
+
const gate = {
|
|
47
|
+
check: (req) => (WRITE_HAND_TOOLS.has(req.toolName) || SHELL_TOOLS.has(req.toolName) ? ask(req.toolName) : { action: "allow" }),
|
|
47
48
|
};
|
|
48
|
-
return { shellGate: "always", toolPolicy:
|
|
49
|
+
return { shellGate: "always", toolPolicy: gate };
|
|
49
50
|
}
|
|
50
51
|
//# sourceMappingURL=route-orchestration.js.map
|
package/dist/run-local.js
CHANGED
|
@@ -9,6 +9,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { Runner, uuidv7, FileStorageBackend, NodeExecutionEnv, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore, defaultTaskRegistry } from "@sema-agent/core";
|
|
11
11
|
import { singleUserAutoAcceptBaselineApplies } from "./approval.js";
|
|
12
|
+
import { createUnattendedLeg, unattendedPermissionMode, withUnattendedLeg } from "./unattended-permission-mode.js";
|
|
12
13
|
import { createBrain } from "./brain.js";
|
|
13
14
|
import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, createApprovalBaselinePolicy, createDeploymentGovernanceInputs, } from "./deployment-governance.js";
|
|
14
15
|
import { applyRuntimeGovernance } from "./runtime-governance.js";
|
|
@@ -567,7 +568,8 @@ export async function runLocal(argv, deps = {}) {
|
|
|
567
568
|
...(maxCostUsd !== undefined ? { maxCostUsd } : {}),
|
|
568
569
|
...(maxTokens !== undefined ? { maxTokens } : {}),
|
|
569
570
|
};
|
|
570
|
-
const
|
|
571
|
+
const unattendedLeg = createUnattendedLeg(unattendedPermissionMode(config), runnerDeps);
|
|
572
|
+
const spec = withUnattendedLeg(applyLocalGovernance({
|
|
571
573
|
objective: mention.cleanedObjective,
|
|
572
574
|
sessionId,
|
|
573
575
|
backgroundScope: "session",
|
|
@@ -583,7 +585,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
583
585
|
...(mcp ? { mcp } : {}),
|
|
584
586
|
...(a2a ? { a2a } : {}),
|
|
585
587
|
...(Object.keys(limits).length > 0 ? { limits } : {}),
|
|
586
|
-
}, config, workspaceDir, memoryEngine?.root);
|
|
588
|
+
}, config, workspaceDir, memoryEngine?.root), unattendedLeg);
|
|
587
589
|
logger.info("run_local_start", { scenario: scenarioName, model: spec.model, sessionId, exec: executionLaneOf(config.remoteExec?.provider) });
|
|
588
590
|
const settleSessionBackground = async () => {
|
|
589
591
|
const reaped = defaultTaskRegistry.reapSessionBackground(sessionId, scope);
|
package/dist/runs.d.ts
CHANGED
|
@@ -150,7 +150,8 @@ export declare function isSessionConflictResult(result: {
|
|
|
150
150
|
* 逐码显式登记为债务,并由那道门看住不许陈腐。
|
|
151
151
|
*
|
|
152
152
|
* S-724(core 7.31.0 #1048 / #1050):`rewind.capture_disabled` → **422**(关断词与还原同体 = 自相矛盾;本服务的请求面上
|
|
153
|
-
* 走不到它 —— fresh 受理门先 400 `request.field_conflict`(core
|
|
153
|
+
* 走不到它 —— fresh 受理门先 400 `request.field_conflict`(立门理由:core 这一拒落在会话分叉之后、不回滚;S-865 / core 7.35.0
|
|
154
|
+
* #1195 起 core 在分叉之前拒);映射是分诊面完备性)、`rewind.store_unconfigured`
|
|
154
155
|
* → **409**(部署没有历史店;`FILE_HISTORY_CAPTURE=false` 自本版不再摘店 ⇒ 可达面只剩没有后端的部署形,那里
|
|
155
156
|
* `capabilities.rewindFiles` 恒报 false —— 三问在 CHANGELOG 7.104.0)。
|
|
156
157
|
*/
|