@sema-agent/server 7.45.0 → 7.46.0-rc.2
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/USAGE.md +38 -0
- package/dist/approval-card.d.ts +106 -40
- package/dist/approval-card.js +45 -8
- package/dist/boot/coordinators.d.ts +1 -1
- package/dist/boot/memory-consolidation.d.ts +191 -0
- package/dist/boot/memory-consolidation.js +132 -0
- package/dist/boot/runner-deps.d.ts +1 -1
- package/dist/config-types.d.ts +48 -2
- package/dist/config-types.js +1 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.js +53 -3
- package/dist/http/routes/capabilities.js +4 -0
- package/dist/http/routes/memory-compliance.d.ts +102 -0
- package/dist/http/routes/memory-compliance.js +113 -0
- package/dist/http/routes/memory-consolidation.d.ts +60 -0
- package/dist/http/routes/memory-consolidation.js +155 -0
- package/dist/http/routes/memory-origin.d.ts +124 -0
- package/dist/http/routes/memory-origin.js +193 -0
- package/dist/http/routes/rules.js +1 -1
- package/dist/http/routes/side-query.js +1 -0
- package/dist/http/server.d.ts +71 -2
- package/dist/http/server.js +27 -2
- package/dist/main.js +55 -2
- package/dist/memory-operator-faces.d.ts +211 -0
- package/dist/memory-operator-faces.js +76 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +42 -28
- package/dist/plugins/checkpoint-store-sql.js +31 -17
- package/dist/plugins/local-checkpoint-store.js +3 -3
- package/dist/plugins/permission-rule-store-file.d.ts +2 -2
- package/dist/plugins/permission-rule-store-file.js +41 -4
- package/dist/plugins/permission-rule-store-sql.d.ts +2 -2
- package/dist/plugins/permission-rule-store-sql.js +59 -18
- package/dist/plugins/store-backend.d.ts +1 -1
- package/dist/plugins/tidb-pool.js +3 -3
- package/dist/rules-consent.d.ts +30 -3
- package/dist/rules-consent.js +50 -7
- package/dist/task-cwd.d.ts +1 -1
- package/dist/tool-approval.d.ts +43 -10
- package/dist/tool-approval.js +105 -27
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/package.json +3 -3
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ControlPlaneCorruptError } from "@sema-agent/core";
|
|
3
|
+
import { sendJson, sendError } from "../send.js";
|
|
4
|
+
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
5
|
+
export const MEMORY_CONSOLIDATION_STATUS_PATH = "/v1/admin/memory/consolidation";
|
|
6
|
+
export const MEMORY_CONSOLIDATION_RUN_PATH = "/v1/admin/memory/consolidation/run";
|
|
7
|
+
const RunRequestSchema = z.object({ scope: z.string().optional() }).strict();
|
|
8
|
+
function buildRunSummary(r) {
|
|
9
|
+
return {
|
|
10
|
+
runId: r.runId,
|
|
11
|
+
scope: r.scope,
|
|
12
|
+
resumed: r.resumed,
|
|
13
|
+
outcome: r.outcome,
|
|
14
|
+
converged: r.converged,
|
|
15
|
+
cycles: r.cycles.length,
|
|
16
|
+
cyclesDone: r.cyclesDone,
|
|
17
|
+
productsCommitted: r.productsCommitted,
|
|
18
|
+
entriesSuperseded: r.entriesSuperseded,
|
|
19
|
+
plans: r.planIds.length,
|
|
20
|
+
model: r.model,
|
|
21
|
+
contractVersion: r.contractVersion,
|
|
22
|
+
usage: r.usage,
|
|
23
|
+
repairs: r.repairs,
|
|
24
|
+
writeFailures: r.writeFailures.length,
|
|
25
|
+
residueProducts: r.residue.length,
|
|
26
|
+
...(r.planArchive !== undefined ? { planArchive: r.planArchive } : {}),
|
|
27
|
+
notices: r.notices.map((n) => ({ noticeCode: n.code, message: n.message })),
|
|
28
|
+
...(r.announceFailures !== undefined && r.announceFailures.length > 0 ? { announceFailures: r.announceFailures.length } : {}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function buildRunRowSummary(row) {
|
|
32
|
+
return {
|
|
33
|
+
runId: row.runId,
|
|
34
|
+
scope: row.scope,
|
|
35
|
+
state: row.state,
|
|
36
|
+
...(row.outcome !== undefined ? { outcome: row.outcome } : {}),
|
|
37
|
+
startedAt: row.startedAt,
|
|
38
|
+
...(row.settledAt !== undefined ? { settledAt: row.settledAt } : {}),
|
|
39
|
+
attempt: row.attempt,
|
|
40
|
+
cyclesDone: row.cyclesDone,
|
|
41
|
+
entriesSuperseded: row.entriesSuperseded,
|
|
42
|
+
plans: row.planIds.length,
|
|
43
|
+
model: row.model,
|
|
44
|
+
contractVersion: row.contractVersion,
|
|
45
|
+
usage: row.usage,
|
|
46
|
+
repairs: row.repairs,
|
|
47
|
+
writeFailures: row.writeFailures.length,
|
|
48
|
+
residueProducts: row.residue?.length ?? 0,
|
|
49
|
+
...(row.planArchive !== undefined ? { planArchive: row.planArchive } : {}),
|
|
50
|
+
...(row.nextEligibleAt !== undefined ? { nextEligibleAt: row.nextEligibleAt } : {}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function decideScope(given, configured) {
|
|
54
|
+
if (given !== undefined) {
|
|
55
|
+
const trimmed = given.trim();
|
|
56
|
+
if (trimmed === "")
|
|
57
|
+
return { refusal: "blank" };
|
|
58
|
+
if (configured.length > 0 && !configured.includes(trimmed))
|
|
59
|
+
return { refusal: "off-roster" };
|
|
60
|
+
return { scope: trimmed };
|
|
61
|
+
}
|
|
62
|
+
if (configured.length === 1)
|
|
63
|
+
return { scope: configured[0] };
|
|
64
|
+
return { refusal: configured.length === 0 ? "none-configured" : "ambiguous" };
|
|
65
|
+
}
|
|
66
|
+
function codeOf(err) {
|
|
67
|
+
if (!(err instanceof Error))
|
|
68
|
+
return undefined;
|
|
69
|
+
const { code } = err;
|
|
70
|
+
return typeof code === "string" ? code : undefined;
|
|
71
|
+
}
|
|
72
|
+
export async function handleMemoryConsolidation(req, res, url, ctx) {
|
|
73
|
+
const miss = { fell: false };
|
|
74
|
+
await handleMemoryConsolidationBody(req, res, url, ctx, miss);
|
|
75
|
+
return !miss.fell;
|
|
76
|
+
}
|
|
77
|
+
async function handleMemoryConsolidationBody(req, res, url, ctx, miss) {
|
|
78
|
+
const { deps } = ctx;
|
|
79
|
+
const path = url.split("?")[0] ?? url;
|
|
80
|
+
const isRun = req.method === "POST" && path === MEMORY_CONSOLIDATION_RUN_PATH;
|
|
81
|
+
const isStatus = req.method === "GET" && path === MEMORY_CONSOLIDATION_STATUS_PATH;
|
|
82
|
+
const faces = deps.memoryConsolidation;
|
|
83
|
+
if ((!isRun && !isStatus) || faces === undefined) {
|
|
84
|
+
miss.fell = true;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
88
|
+
if (deps.config.requirePrincipal && !principal) {
|
|
89
|
+
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
|
|
93
|
+
sendError(res, 403, "auth.operator_only", "memory consolidation is operator-only: the caller principal is not in OPERATOR_PRINCIPALS (an empty list means NO one is an operator — configure it to enable these endpoints)");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (isStatus) {
|
|
97
|
+
const q = new URL(req.url ?? "", "http://x").searchParams.get("scope") ?? undefined;
|
|
98
|
+
const decided = decideScope(q, faces.scopes);
|
|
99
|
+
const scope = "scope" in decided ? decided.scope : undefined;
|
|
100
|
+
const row = scope !== undefined ? faces.lastRun(scope) : undefined;
|
|
101
|
+
sendJson(res, 200, {
|
|
102
|
+
enabled: true,
|
|
103
|
+
seat: faces.seat,
|
|
104
|
+
model: faces.model,
|
|
105
|
+
scopes: faces.scopes,
|
|
106
|
+
...(scope !== undefined ? { scope } : {}),
|
|
107
|
+
...(row !== undefined ? { lastRun: buildRunRowSummary(row) } : {}),
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const parsed = RunRequestSchema.safeParse(await ctx.helpers.readJson(req));
|
|
112
|
+
if (!parsed.success) {
|
|
113
|
+
sendError(res, 400, "request.body_shape", "invalid memory consolidation request — expected an object with an optional `scope` string");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const decided = decideScope(parsed.data.scope, faces.scopes);
|
|
117
|
+
if ("refusal" in decided) {
|
|
118
|
+
switch (decided.refusal) {
|
|
119
|
+
case "blank":
|
|
120
|
+
sendError(res, 400, "request.body_shape", "invalid memory consolidation request — `scope` must be a non-empty string naming the memory scope to fold");
|
|
121
|
+
return;
|
|
122
|
+
case "none-configured":
|
|
123
|
+
sendError(res, 400, "request.body_shape", "invalid memory consolidation request — `scope` is required: this deployment configured no MEMORY_CONSOLIDATION_SCOPES, so there is no single library to default to, and one consolidation run reads a WHOLE scope (~1e5 prompt tokens). Send a `scope`, or set MEMORY_CONSOLIDATION_SCOPES to the one scope this worker folds");
|
|
124
|
+
return;
|
|
125
|
+
case "off-roster":
|
|
126
|
+
sendError(res, 400, "request.body_shape", `invalid memory consolidation request — this worker declares the scopes it folds in MEMORY_CONSOLIDATION_SCOPES (${faces.scopes.join(", ")}) and the requested scope is not one of them; a stale or mistyped scope would spend a whole-library distillation on a different library and supersede its entries. Send one of the declared scopes, add this one to MEMORY_CONSOLIDATION_SCOPES, or unset that knob to fold any scope on request`);
|
|
127
|
+
return;
|
|
128
|
+
case "ambiguous":
|
|
129
|
+
sendError(res, 400, "request.body_shape", `invalid memory consolidation request — a scope is required: MEMORY_CONSOLIDATION_SCOPES lists more than one scope (${faces.scopes.join(", ")}) and the server never picks which library to spend a whole-library distillation on`);
|
|
130
|
+
return;
|
|
131
|
+
default: {
|
|
132
|
+
const unreachable = decided.refusal;
|
|
133
|
+
throw new Error(`unhandled consolidation scope refusal: ${String(unreachable)}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const r = await faces.run(decided.scope);
|
|
139
|
+
sendJson(res, 200, buildRunSummary(r));
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
const coreCode = codeOf(err);
|
|
143
|
+
if (coreCode !== undefined) {
|
|
144
|
+
sendError(res, 409, "memory.consolidation_refused", `memory consolidation refused (${coreCode}): ${err instanceof Error ? err.message : String(err)}`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (err instanceof ControlPlaneCorruptError) {
|
|
148
|
+
sendError(res, 500, "internal.memory_control_plane_corrupt", err.message);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
ctx.deps.logger?.warn?.("memory_consolidation_run_failed", { scope: decided.scope, err: String(err) });
|
|
152
|
+
sendError(res, 500, "internal.error", "memory consolidation run failed");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=memory-consolidation.js.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/316 件② —— **外源标记人面的 operator 三口**(core 5.59.0 `MemoryEngine.listExternalOriginEntries` /
|
|
3
|
+
* `listOriginClearances` / `clearEntryOrigin`,design/336 §13-4①②)。
|
|
4
|
+
*
|
|
5
|
+
* · `GET /v1/memory/origin/external?scopes=a,b` —— 点名 scope 里每一条带外源标记的条目 + 出处账;
|
|
6
|
+
* · `GET /v1/memory/origin/clearances` —— 清标审计账(**白名单投影**,见下);
|
|
7
|
+
* · `POST /v1/memory/origin/entries/:entryId/clear` `{requestId, reason}` —— 审计化的 UN-MARK 阀门。
|
|
8
|
+
*
|
|
9
|
+
* ── 为什么是 operator-only ─────────────────────────────────────────────────────────────────────
|
|
10
|
+
* 与件③(memory-compliance.ts)、#264 的 bundle 两口同族:
|
|
11
|
+
* · 前两口是**跨租户治理读**:一次调用取回任意 scope 的条目 + 每条的完整出处账(贡献会话 id、污染
|
|
12
|
+
* 理由、托管链事件),按定义超出任何单个 principal 的自助边界;
|
|
13
|
+
* · clear 是**治理写面**,而且是记忆域里语义最重的一次表态 —— 它把「这条记忆来自外部、用前先核」
|
|
14
|
+
* 这句披露**替宿主担保掉**。core 的原话是 the host's explicit vouching starts the entry's unmarked
|
|
15
|
+
* life:签字的人必须是部署的操作员,不是任何一个租户。
|
|
16
|
+
* 缺 principal 的形照 sibling operator 端点(adoption / retention-ops / memory-bundle / memory-compliance)401。
|
|
17
|
+
*
|
|
18
|
+
* ── 门序(逐字同 routes/memory-compliance.ts)─────────────────────────────────────────────────
|
|
19
|
+
* 身份(401)→ 授权(403)→ 能力(501)→ 验型(400)。**授权在能力之前**:一个够不着任何东西的调用方
|
|
20
|
+
* 不该从「这个部署有没有记忆引擎」上读出部署形态。
|
|
21
|
+
*
|
|
22
|
+
* ── 能力面:501 的判据与件③ **同一条**(同码同文案族,不同族名)────────────────────────────────
|
|
23
|
+
* 三口都真的读引擎控制面,而且 clear 的写前托管行是崩溃窗内条目字节的**唯一一份** —— 判在挂载期
|
|
24
|
+
* (`createMemoryOriginFaces` 返 undefined ⇒ 三口整个不挂),全文见 `src/memory-operator-faces.ts` 头注。
|
|
25
|
+
* 码复用 `capability.memory_engine_required`(消费端分支相同:换部署形态),文案自成一句 —— 与 bundle /
|
|
26
|
+
* compliance 两族刻意不同字,因为要查的旋钮不是同一个(那两族自己的头注就吃过复用文案的亏)。
|
|
27
|
+
*
|
|
28
|
+
* ── `scopes` 为什么必须由调用方给(F-13 的诚实形)──────────────────────────────────────────────
|
|
29
|
+
* 🔴 本仓**今天没有任何 scope 枚举读面**。core 5.59.0 长出了 `listMemoryScopes`(#437③:File 腿在场、
|
|
30
|
+
* SQL 腿是 core 候升件,且带「backend 是否支持枚举」判别位——不支持**永不读作零**),但本仓尚未接线。
|
|
31
|
+
* 于是 v1 的形是:scope 名**显式提供**,缺参 400 并**指路来源**。
|
|
32
|
+
* 为什么不给一个默认名单(比如「本部署配过的那些」):污染审计的价值全在**发现链闭合**,而不完备是
|
|
33
|
+
* **静默**的 —— 空数组与「这些 scope 干净」在 wire 上同形。一个猜出来的名单会让 operator 读到一份
|
|
34
|
+
* 自称干净、其实只查了一半的账。所以本口的答案永远只对**它被点名的那些 scope**成立,这句话写进 400
|
|
35
|
+
* 文案、写进附录 A、也写进端点文档:**never read an empty answer as "the store is clean"**。
|
|
36
|
+
*
|
|
37
|
+
* ── `GET …/clearances` 为什么是**白名单**而不是透传(F-7)───────────────────────────────────────
|
|
38
|
+
* 与件③ 的两个账(透传)**刻意相反**,判据仍是那一条「新键静默上 wire 危险吗」:
|
|
39
|
+
* · `OriginClearanceRow.entryText` = 被清标条目的**完整正文托管字节**。core 逐字:a crash between the
|
|
40
|
+
* tombstone batch and the re-record batch leaves this as the only copy —— 它是**崩溃恢复席**,而恢复
|
|
41
|
+
* 的手段是**再调一次 `clearEntryOrigin`**(引擎从行里自己重放),不是让人把字节读出来贴回去。
|
|
42
|
+
* 把整条记忆的正文放上一个审计列表面,等于给每一次「看看清标记录」都附赠一份全文导出;
|
|
43
|
+
* · 而这份账**新长一个键**时,静默上 wire 恰恰是危险的那一侧(它是托管面,不是事实陈述面)。
|
|
44
|
+
* ⇒ 显式白名单 + **编译期差集门**(`test/wire-whitelist-exhaustiveness.test.ts` 的 `KnownExcluded`
|
|
45
|
+
* = `entryText`,逐字写理由):core 加一个键 ⇒ TS2344 ⇒ 由人处置,而不是悄悄上 wire 或悄悄漏掉。
|
|
46
|
+
* 🔴 白名单是**深**的,不是只做一层(codex 对抗复审 [high] 抓的第一版病):嵌套的 `origin` 也逐键投影
|
|
47
|
+
* —— 一层白名单 + 一个整只透传的嵌套对象,等于给这条排除留了一个后门,全文见 {@link buildOriginWire}。
|
|
48
|
+
* 🔴 **剥了不等于不说**:投影补一个 `custodyBytes`(托管字节数)。这是 #196 absence-reports-not-silent-green
|
|
49
|
+
* 在本口的落法 —— operator 判「这个 pending 行还揣着一条记忆吗」只需要这一位;悄悄把整个托管概念从
|
|
50
|
+
* 审计面上抹掉,会让一个**必须由人处置**的崩溃恢复席看上去不存在。
|
|
51
|
+
*
|
|
52
|
+
* ── 拒因族:**闭集逐码 switch**(#123 码族)────────────────────────────────────────────────────
|
|
53
|
+
* 见 {@link sentClearCode} 的头注(逐码状态与理由、以及 core 真形与 design/316 稿面的**一处偏差**)。
|
|
54
|
+
*
|
|
55
|
+
* 计费/lane:三口都是**部署级治理动作**,零模型工作 ⇒ `billable=false`(与 adoption / retention-ops /
|
|
56
|
+
* memory-bundle / memory-compliance 同族,申明在 test/billable-route-declaration.test.ts)。
|
|
57
|
+
*
|
|
58
|
+
* ── 改写门(`isCredentialGatedRewrite`):**三口全进** ──────────────────────────────────────────
|
|
59
|
+
* 该门自 #277 起的判据是「持久改写 **或** 授权唯一输入是 principal 头的跨租户治理读」:
|
|
60
|
+
* · `POST …/clear` 落在**前**一项:它把一条条目的外源披露**永久摘掉**(墓碑 + 同 id 重录),伪造一个
|
|
61
|
+
* 在册 operator 头即可让任意租户的一条外来记忆看上去是本店自产的 —— 而记忆是模型每次挂载都读的面;
|
|
62
|
+
* · `GET …/external` 落在**后**一项,而且是这一族里爆炸半径最大的读:一次调用取回**任意 scope**的
|
|
63
|
+
* 条目 + 每条的完整出处账(贡献会话 id、污染理由、仓内 ingest 路径与内容哈希、托管链事件)。判据与
|
|
64
|
+
* `POST /v1/memory/export`(#277 收进门的那条纯读)逐字同构:它没有属主门兜底,operator 头过了就直接答。
|
|
65
|
+
* · `GET …/clearances` —— 🔴 **本车第一版把它留在门外,codex 对抗复审 [high] 推翻,亲验后采信**。
|
|
66
|
+
* 那一版的理由是「投影后不含条目正文,爆炸半径差一个量级」,而这个理由**用错了基准**:本门的判据
|
|
67
|
+
* 从来不是「含不含正文」,而是「授权的**唯一**输入是 principal 头」∧「没有属主门兜底」。按那条判据,
|
|
68
|
+
* 一口**整部署**的清标账(每个租户的 scope+slug、entryId、rev、operator 手写的自由文本 `reason`、
|
|
69
|
+
* requestId、时间线)比已经在门里的**单条** `GET …/:entryId/provenance` 爆炸半径更大,不是更小;而
|
|
70
|
+
* `reason` 是自由文本 —— 它完全可能把条目内容摘一句进去,连「不含正文」这个前提本身都不牢。
|
|
71
|
+
* 与 `POST /v1/memory/export` / `GET …/provenance` 两次改判是同一条论证:把它留在门外只为守住一句
|
|
72
|
+
* 自己写的措辞,是把措辞看得比它要保护的东西更重。
|
|
73
|
+
* ⚠️ 三条都是**逐条式**入门,不是 `/v1/memory/origin/` 前缀吃(写口那一臂才是前缀,方向宁紧):这个
|
|
74
|
+
* 前缀下将来长出的读口未必都够判据,逐口论证过才入门 —— 这正是本门「闭」的那一半。代价与补偿如实
|
|
75
|
+
* 成文:无凭证部署上三口都答 503 `auth.service_token_required`,补偿是**既有旋钮零新增**(配
|
|
76
|
+
* `SERVICE_AUTH_TOKEN` 走正路,或本地开发显式 `ALLOW_UNAUTHED_WRITES=true`)。
|
|
77
|
+
*
|
|
78
|
+
* 分层:本模块不值 import `server.ts`(那条边闭合运行时装载环),只 `import type`。
|
|
79
|
+
*/
|
|
80
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
81
|
+
import { type OriginClearanceRow } from "@sema-agent/core";
|
|
82
|
+
import type { RouteCtx } from "../route-ctx.js";
|
|
83
|
+
export declare const MEMORY_ORIGIN_EXTERNAL_PATH = "/v1/memory/origin/external";
|
|
84
|
+
export declare const MEMORY_ORIGIN_CLEARANCES_PATH = "/v1/memory/origin/clearances";
|
|
85
|
+
/** 带参写口 ⇒ 名册门走 `ROUTE_LABEL_PATTERNS`(server.ts),不是 LITERALS。 */
|
|
86
|
+
export declare const MEMORY_ORIGIN_CLEAR_RE: RegExp;
|
|
87
|
+
/**
|
|
88
|
+
* 一条清标行 → wire 的**显式白名单**(F-7)。全文理由见文件头「为什么是白名单」。
|
|
89
|
+
*
|
|
90
|
+
* 被剥的 core 键**只有一个**:`entryText`(完整正文托管字节),而且**剥了要说** —— `custodyBytes` 是它
|
|
91
|
+
* 留在审计面上的那句披露(0 = 这行没揣着字节;>0 = 崩溃恢复席里还有一条记忆的正文,pending 行要人处置)。
|
|
92
|
+
* 嵌套的 `origin` 不是「被剥」而是**被深投影**(三键之外的成员剥值留名,见 {@link buildOriginWire})。
|
|
93
|
+
* 🔴 **不要**给这个函数标 `: Record<string, unknown>` 返回型:那会让 `keyof ReturnType<…>` 塌成 `string`,
|
|
94
|
+
* 编译期差集门当场变成恒真(vacuous)—— `buildImportReportWire` 的头注里踩过这个坑,而本车在 `origin`
|
|
95
|
+
* 那只投影上**又踩了一次**(标了具体返回型,门读标注不读字面量),所以这条钉现在有两处案底。
|
|
96
|
+
*/
|
|
97
|
+
export declare function buildClearanceRowWire(r: OriginClearanceRow): {
|
|
98
|
+
events: {
|
|
99
|
+
detail?: string | undefined;
|
|
100
|
+
eventId: string;
|
|
101
|
+
at: number;
|
|
102
|
+
to: "failed" | "done";
|
|
103
|
+
requestId: string;
|
|
104
|
+
}[];
|
|
105
|
+
custodyBytes: number;
|
|
106
|
+
tombstonedAt?: number | undefined;
|
|
107
|
+
requestId: string;
|
|
108
|
+
reason: string;
|
|
109
|
+
at: number;
|
|
110
|
+
status: "pending" | "failed" | "done";
|
|
111
|
+
originUnknownKeys?: string[] | undefined;
|
|
112
|
+
clearanceId: string;
|
|
113
|
+
entryId: string;
|
|
114
|
+
scope: string;
|
|
115
|
+
slug: string;
|
|
116
|
+
baseRev: string;
|
|
117
|
+
origin: {
|
|
118
|
+
at: number;
|
|
119
|
+
cause?: "static" | "unattributed" | "observed" | "derived" | undefined;
|
|
120
|
+
taint: "external";
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
export declare function handleMemoryOrigin(req: IncomingMessage, res: ServerResponse, url: string, ctx: RouteCtx): Promise<boolean>;
|
|
124
|
+
//# sourceMappingURL=memory-origin.d.ts.map
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ControlPlaneCorruptError } from "@sema-agent/core";
|
|
3
|
+
import { sendJson, sendError } from "../send.js";
|
|
4
|
+
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
5
|
+
export const MEMORY_ORIGIN_EXTERNAL_PATH = "/v1/memory/origin/external";
|
|
6
|
+
export const MEMORY_ORIGIN_CLEARANCES_PATH = "/v1/memory/origin/clearances";
|
|
7
|
+
export const MEMORY_ORIGIN_CLEAR_RE = /^\/v1\/memory\/origin\/entries\/([^/]+)\/clear$/;
|
|
8
|
+
const ClearRequestSchema = z.object({
|
|
9
|
+
requestId: z.string().min(1),
|
|
10
|
+
reason: z.string(),
|
|
11
|
+
});
|
|
12
|
+
function sentClearCode(res, coreCode, message) {
|
|
13
|
+
switch (coreCode) {
|
|
14
|
+
case "memory.origin_clear_unattributed":
|
|
15
|
+
sendError(res, 422, "memory.origin_clear_unattributed", message);
|
|
16
|
+
return true;
|
|
17
|
+
case "memory.origin_clear_invalid":
|
|
18
|
+
sendError(res, 422, "memory.origin_clear_invalid", message);
|
|
19
|
+
return true;
|
|
20
|
+
case "memory.origin_clear_unknown":
|
|
21
|
+
sendError(res, 409, "memory.origin_clear_unknown", message);
|
|
22
|
+
return true;
|
|
23
|
+
case "memory.origin_clear_not_marked":
|
|
24
|
+
sendError(res, 409, "memory.origin_clear_not_marked", message);
|
|
25
|
+
return true;
|
|
26
|
+
case "memory.origin_clear_challenged":
|
|
27
|
+
sendError(res, 409, "memory.origin_clear_challenged", message);
|
|
28
|
+
return true;
|
|
29
|
+
case "memory.origin_clear_conflict":
|
|
30
|
+
sendError(res, 409, "memory.origin_clear_conflict", message);
|
|
31
|
+
return true;
|
|
32
|
+
case "memory.origin_clear_failed":
|
|
33
|
+
sendError(res, 409, "memory.origin_clear_failed", message);
|
|
34
|
+
return true;
|
|
35
|
+
case "memory.origin_clear_pending":
|
|
36
|
+
sendError(res, 409, "memory.origin_clear_pending", message);
|
|
37
|
+
return true;
|
|
38
|
+
default:
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function codeOf(err) {
|
|
43
|
+
if (!(err instanceof Error))
|
|
44
|
+
return undefined;
|
|
45
|
+
const { code } = err;
|
|
46
|
+
return typeof code === "string" ? code : undefined;
|
|
47
|
+
}
|
|
48
|
+
function sentClassifiedFailure(res, ctx, face, err) {
|
|
49
|
+
if (face === "clear") {
|
|
50
|
+
const coreCode = codeOf(err);
|
|
51
|
+
if (coreCode !== undefined && sentClearCode(res, coreCode, err instanceof Error ? err.message : String(err)))
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
if (err instanceof ControlPlaneCorruptError) {
|
|
55
|
+
sendError(res, 500, "internal.memory_control_plane_corrupt", err.message);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
ctx.deps.logger?.warn?.(`memory_origin_${face}_failed`, { err: String(err) });
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
function buildClearanceEventWire(e) {
|
|
62
|
+
return {
|
|
63
|
+
eventId: e.eventId,
|
|
64
|
+
at: e.at,
|
|
65
|
+
to: e.to,
|
|
66
|
+
requestId: e.requestId,
|
|
67
|
+
...(e.detail !== undefined ? { detail: e.detail } : {}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function buildOriginWire(o) {
|
|
71
|
+
return { taint: o.taint, ...(o.cause !== undefined ? { cause: o.cause } : {}), at: o.at };
|
|
72
|
+
}
|
|
73
|
+
function originUnknownKeysOf(o) {
|
|
74
|
+
const known = new Set(["taint", "cause", "at"]);
|
|
75
|
+
const extra = Object.keys(o).filter((k) => !known.has(k)).sort();
|
|
76
|
+
return extra.length > 0 ? extra : undefined;
|
|
77
|
+
}
|
|
78
|
+
export function buildClearanceRowWire(r) {
|
|
79
|
+
const originUnknownKeys = originUnknownKeysOf(r.origin);
|
|
80
|
+
return {
|
|
81
|
+
clearanceId: r.clearanceId,
|
|
82
|
+
entryId: r.entryId,
|
|
83
|
+
scope: r.scope,
|
|
84
|
+
slug: r.slug,
|
|
85
|
+
baseRev: r.baseRev,
|
|
86
|
+
origin: buildOriginWire(r.origin),
|
|
87
|
+
...(originUnknownKeys !== undefined ? { originUnknownKeys } : {}),
|
|
88
|
+
requestId: r.requestId,
|
|
89
|
+
reason: r.reason,
|
|
90
|
+
at: r.at,
|
|
91
|
+
status: r.status,
|
|
92
|
+
...(r.tombstonedAt !== undefined ? { tombstonedAt: r.tombstonedAt } : {}),
|
|
93
|
+
events: r.events.map(buildClearanceEventWire),
|
|
94
|
+
custodyBytes: Buffer.byteLength(r.entryText, "utf8"),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function parseScopes(raw) {
|
|
98
|
+
const out = [];
|
|
99
|
+
for (const one of raw) {
|
|
100
|
+
for (const seg of one.split(",")) {
|
|
101
|
+
const s = seg.trim();
|
|
102
|
+
if (s !== "" && !out.includes(s))
|
|
103
|
+
out.push(s);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return out.length > 0 ? out : null;
|
|
107
|
+
}
|
|
108
|
+
export async function handleMemoryOrigin(req, res, url, ctx) {
|
|
109
|
+
const miss = { fell: false };
|
|
110
|
+
await handleMemoryOriginBody(req, res, url, ctx, miss);
|
|
111
|
+
return !miss.fell;
|
|
112
|
+
}
|
|
113
|
+
async function handleMemoryOriginBody(req, res, url, ctx, miss) {
|
|
114
|
+
const { deps } = ctx;
|
|
115
|
+
const path = url.split("?")[0] ?? url;
|
|
116
|
+
const isExternal = req.method === "GET" && path === MEMORY_ORIGIN_EXTERNAL_PATH;
|
|
117
|
+
const isClearances = req.method === "GET" && path === MEMORY_ORIGIN_CLEARANCES_PATH;
|
|
118
|
+
const clearMatch = req.method === "POST" ? MEMORY_ORIGIN_CLEAR_RE.exec(path) : null;
|
|
119
|
+
if (!isExternal && !isClearances && clearMatch === null) {
|
|
120
|
+
miss.fell = true;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
124
|
+
if (deps.config.requirePrincipal && !principal) {
|
|
125
|
+
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
|
|
129
|
+
sendError(res, 403, "auth.operator_only", "memory origin marking and clearance are operator-only deployment actions");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const faces = deps.memoryOriginFace;
|
|
133
|
+
if (faces === undefined) {
|
|
134
|
+
sendError(res, 501, "capability.memory_engine_required", "memory origin marking and clearance are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the clearance ledger and its write-ahead custody rows live in that plane, and on a backend without it they would land on one replica's local disk");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (isExternal) {
|
|
138
|
+
const scopes = parseScopes(new URL(req.url ?? "", "http://x").searchParams.getAll("scopes"));
|
|
139
|
+
if (scopes === null) {
|
|
140
|
+
sendError(res, 400, "request.query_invalid", "`scopes` is required and must name at least one memory scope (comma-separated, e.g. `?scopes=user:alice,org:acme`) — this deployment has no scope-enumeration read face yet, so name the same scope keys you use on `GET /v1/memory/export?scope=`; an answer here covers ONLY the scopes you named and an empty result must never be read as \"the store is clean\"");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
sendJson(res, 200, { scopes, entries: await faces.listExternal(scopes) });
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
if (!sentClassifiedFailure(res, ctx, "external", err))
|
|
148
|
+
sendError(res, 500, "internal.error", "memory external-origin listing failed");
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (isClearances) {
|
|
153
|
+
try {
|
|
154
|
+
sendJson(res, 200, { clearances: faces.listClearances().map(buildClearanceRowWire) });
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
if (!sentClassifiedFailure(res, ctx, "clearances", err))
|
|
158
|
+
sendError(res, 500, "internal.error", "memory origin clearance listing failed");
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const entryId = ctx.helpers.safeDecode(clearMatch[1]);
|
|
163
|
+
if (entryId === null) {
|
|
164
|
+
sendError(res, 400, "request.id_invalid", "invalid memory entry id");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const raw = await ctx.helpers.readJson(req);
|
|
168
|
+
const parsed = ClearRequestSchema.safeParse(raw);
|
|
169
|
+
if (!parsed.success) {
|
|
170
|
+
if (parsed.error.issues.some((i) => i.path.length > 0 && i.path[0] === "requestId")) {
|
|
171
|
+
sendError(res, 400, "request.request_id_required", "memory origin clearance requires a non-empty `requestId` string — it is the audit attribution of WHO cleared this marker and the server never mints one for you");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
sendError(res, 400, "request.body_shape", "invalid memory origin clearance request — expected an object with a non-empty `requestId` and a `reason` string (whether an EMPTY reason is acceptable is the engine's judgment, not this layer's)");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const receipt = await faces.clear(entryId, { requestId: parsed.data.requestId, reason: parsed.data.reason });
|
|
179
|
+
ctx.deps.logger?.info?.("memory_origin_cleared", {
|
|
180
|
+
principal: principal ?? null,
|
|
181
|
+
entryId,
|
|
182
|
+
clearanceId: receipt.clearanceId,
|
|
183
|
+
claimedRequestId: parsed.data.requestId,
|
|
184
|
+
landedSlug: receipt.landedSlug,
|
|
185
|
+
});
|
|
186
|
+
sendJson(res, 200, receipt);
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
if (!sentClassifiedFailure(res, ctx, "clear", err))
|
|
190
|
+
sendError(res, 500, "internal.error", "memory origin clearance failed");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=memory-origin.js.map
|
|
@@ -105,7 +105,7 @@ async function handleRulesBody(req, res, url, ctx, miss) {
|
|
|
105
105
|
sendError(res, 413, "request.payload_too_large", "too many allow entries across the submitted settings layers");
|
|
106
106
|
return;
|
|
107
107
|
}
|
|
108
|
-
sendJson(res, 200, { preview: prepared.preview, ticket: prepared.ticket, expiresAtMs: prepared.expiresAtMs });
|
|
108
|
+
sendJson(res, 200, { preview: prepared.preview, ...(prepared.ticket !== undefined ? { ticket: prepared.ticket, expiresAtMs: prepared.expiresAtMs } : {}) });
|
|
109
109
|
return;
|
|
110
110
|
}
|
|
111
111
|
const parsed = CcImportRedeemBodySchema.safeParse(rawBody);
|
package/dist/http/server.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import type { IncomingMessage } from "node:http";
|
|
3
|
-
import { type Runner, type TaskSpec, type TaskResult, type WorkflowRunStore, type MemoryEntry, type MemoryExportBundle, type MemoryImportReport, type SideQuerySpec, type SideQueryResult, type Brain } from "@sema-agent/core";
|
|
3
|
+
import { type Runner, type TaskSpec, type TaskResult, type WorkflowRunStore, type MemoryEntry, type MemoryExportBundle, type MemoryImportReport, type EntryProvenanceAccount, type MemoryErasureAttestation, type MemoryEntryOrigin, type OriginClearanceRow, type ConsolidationRunReceipt, type ConsolidationDriverRunRow, type SideQuerySpec, type SideQueryResult, type Brain } from "@sema-agent/core";
|
|
4
4
|
import type { TaskRequestBody } from "./wire-types.js";
|
|
5
5
|
import type { ServiceConfig } from "../config-types.js";
|
|
6
6
|
import { type RestartSignal, type SessionMirrorRuling } from "../config-center/facade.js";
|
|
@@ -26,6 +26,7 @@ import type { QuotaTracker } from "../observability/cost-quota.js";
|
|
|
26
26
|
import type { ModelUsageTracker, PromptManifestTracker } from "../budget.js";
|
|
27
27
|
import { scopedIdempotencyKey } from "./idempotency.js";
|
|
28
28
|
export { scopedIdempotencyKey };
|
|
29
|
+
import type { ConsolidationSeatLabel } from "../boot/memory-consolidation.js";
|
|
29
30
|
import { streamApprovals, isQuestionAnswer } from "./routes/approvals-assistant.js";
|
|
30
31
|
export { streamApprovals, isQuestionAnswer };
|
|
31
32
|
import { cascadeConfig } from "./run-meta.js";
|
|
@@ -176,7 +177,7 @@ export interface ServiceStoreDeps {
|
|
|
176
177
|
backend?: StoreBackend;
|
|
177
178
|
/** #154 车二:**持久化权限规则的同意车道**(core design/179 三步协议的宿主半场)。
|
|
178
179
|
* 在场 ⇔ 规则店真装配(`backend.permissionRule()` 有值)⇒ `POST /v1/rules/cc-import/{prepare,redeem}`
|
|
179
|
-
* 可用、ask 帧才投 `
|
|
180
|
+
* 可用、ask 帧才投 `ruleOffers`;缺席 ⇒ 两口 501、帧上零候选(发一格无处可兑的「不再询问」
|
|
180
181
|
* = wire 谎言,比缺席更坏)。 */
|
|
181
182
|
ruleConsent?: RuleConsentLane;
|
|
182
183
|
}
|
|
@@ -324,6 +325,70 @@ export interface ServiceSeamDeps {
|
|
|
324
325
|
memoryBundleImport?: (bundle: unknown, opts?: {
|
|
325
326
|
expectedScopes?: readonly string[];
|
|
326
327
|
}) => Promise<MemoryImportReport>;
|
|
328
|
+
/** design/316 件③(core 5.57.0 `MemoryEngine.provenanceOf` / `eraseMemoryEntries`):出处 / 抹除合规面。
|
|
329
|
+
* 启用 `GET /v1/memory/entries/:entryId/provenance` 与 `POST /v1/memory/erase`(两口 operator-only)。
|
|
330
|
+
* main.ts 把它接到 `createMemoryComplianceFaces` 的产物上。
|
|
331
|
+
* 🔴 缺席有两个成因,与 {@link memoryBundleExport} **同码不同判据**(同码是因为消费端分支相同:换部署
|
|
332
|
+
* 形态;判据不同是因为要查的旋钮不同):①记忆引擎没接线;②引擎接了但后端**不自带控制面归属**
|
|
333
|
+
* (`controlPlaneRoot`)——本仓两只 SQL 记忆孪生即此形,core 那时会把控制面落到副本本地盘上,与
|
|
334
|
+
* stateless replicas 正面冲突。②在**挂载期**判掉(工厂返 undefined),理由全文见
|
|
335
|
+
* src/memory-operator-faces.ts 头注。
|
|
336
|
+
* 🔴 `erase` 的入参是 `unknown` 而不是 `EraseMemoryEntriesInput`:HTTP 层拿到的是一个**未校验**的对象,
|
|
337
|
+
* 三选一选择子的整体判决是 core 的单一属主面(`erasureRequestInvalid`)。写成具体类型就得在路由里断言
|
|
338
|
+
* 一次「它是合法输入」——那句断言是假的,而且正好把类型检查关灯。 */
|
|
339
|
+
memoryCompliance?: {
|
|
340
|
+
provenanceOf: (entryId: string) => Promise<EntryProvenanceAccount>;
|
|
341
|
+
erase: (input: unknown, opts?: {
|
|
342
|
+
allowedScopes?: readonly string[];
|
|
343
|
+
}) => Promise<MemoryErasureAttestation>;
|
|
344
|
+
};
|
|
345
|
+
/** design/316 件②(core 5.59.0 `MemoryEngine.listExternalOriginEntries` / `listOriginClearances` /
|
|
346
|
+
* `clearEntryOrigin`):**外源标记人面**。启用 `GET /v1/memory/origin/external`、
|
|
347
|
+
* `GET /v1/memory/origin/clearances`、`POST /v1/memory/origin/entries/:entryId/clear`(三口 operator-only)。
|
|
348
|
+
* main.ts 把它接到 `createMemoryOriginFaces` 的产物上。
|
|
349
|
+
* 🔴 缺席的两个成因与 {@link memoryCompliance} **逐字同一条**(同码 `capability.memory_engine_required`,
|
|
350
|
+
* 消费端分支相同:换部署形态):①记忆引擎没接线;②引擎接了但后端**不自带控制面归属**
|
|
351
|
+
* (`controlPlaneRoot`)。②在**挂载期**判掉(工厂返 undefined)。这一族的理由比合规两口更硬:清标的
|
|
352
|
+
* 写前托管行(`OriginClearanceRow.entryText`)在墓碑已落、重录未成的崩溃窗内是那条记忆**唯一的一份**
|
|
353
|
+
* ——落在副本本地盘上 = pod 重建即永久丢失。全文见 src/memory-operator-faces.ts 头注。
|
|
354
|
+
* 🔴 `listClearances` 是**同步**的(core 的读面就是一次严格 sidecar 读),且账本损坏时**抛**
|
|
355
|
+
* `ControlPlaneCorruptError` —— 那是 fail-closed 信号,不是空数组。 */
|
|
356
|
+
memoryOriginFace?: {
|
|
357
|
+
listExternal: (scopes: readonly string[]) => Promise<Array<{
|
|
358
|
+
id: string;
|
|
359
|
+
slug: string;
|
|
360
|
+
scope: string;
|
|
361
|
+
origin: MemoryEntryOrigin;
|
|
362
|
+
provenance: EntryProvenanceAccount;
|
|
363
|
+
}>>;
|
|
364
|
+
listClearances: () => OriginClearanceRow[];
|
|
365
|
+
clear: (entryId: string, input: {
|
|
366
|
+
requestId: string;
|
|
367
|
+
reason: string;
|
|
368
|
+
}) => Promise<{
|
|
369
|
+
entryId: string;
|
|
370
|
+
clearanceId: string;
|
|
371
|
+
origin: MemoryEntryOrigin;
|
|
372
|
+
landedSlug: string;
|
|
373
|
+
}>;
|
|
374
|
+
};
|
|
375
|
+
/** design/378(core 5.58.0 `runMemoryConsolidationDriver`):记忆 **consolidation 阀门**的操作面。
|
|
376
|
+
* 启用 `POST /v1/admin/memory/consolidation/run` 与 `GET /v1/admin/memory/consolidation`(两口 operator-only)。
|
|
377
|
+
* main.ts 把它接到 `createMemoryConsolidationFaces` 的产物上。
|
|
378
|
+
* 🔴 缺席的**唯一**成因是「阀门没开」(`MEMORY_CONSOLIDATION_DRIVER` 不是 `on` —— 出厂缺省):
|
|
379
|
+
* 阀门开着却结构上跑不了的三种形(引擎没接线 / 后端没有控制面归属 / `MEMORY_PROVENANCE=off`)在
|
|
380
|
+
* **启动期**就被拒了(`boot/memory-consolidation.ts`),那些机器根本起不来。⇒ 缺席形是 **404 族**
|
|
381
|
+
* (整域不挂),刻意**不是** 501:501 说「换部署形态」,这里的动作是「把旋钮打开」。全文见
|
|
382
|
+
* `routes/memory-consolidation.ts` 头注。
|
|
383
|
+
* 🔴 `seat` 的词表单一真源 = `boot/memory-consolidation.ts` 的 {@link ConsolidationSeatLabel}
|
|
384
|
+
* (type-only import,tsc 擦除、非装载边)——在这里再写一遍字面量联合就是第二份会漂的词表。 */
|
|
385
|
+
memoryConsolidation?: {
|
|
386
|
+
readonly seat: ConsolidationSeatLabel;
|
|
387
|
+
readonly model: string;
|
|
388
|
+
readonly scopes: readonly string[];
|
|
389
|
+
run(scope: string): Promise<ConsolidationRunReceipt>;
|
|
390
|
+
lastRun(scope: string): ConsolidationDriverRunRow | undefined;
|
|
391
|
+
};
|
|
327
392
|
/** design/170 件A §7 —— org 记忆授权目录,`boot/org-memory.ts` 装配的**同一个实例**(准入 seam 与
|
|
328
393
|
* 本面共享它的 TTL 缓存/退避窗/gen 高水位)。memory-policy 面的 `org:` 属主门用它回答成员性:
|
|
329
394
|
* 读面要该 scope 在授权表里、写面另要条目 `write === true`。缺席=本部署没有目录源 ⇒ `org:` 键
|
|
@@ -610,6 +675,10 @@ export declare function createHttpServer(rawDeps: ServiceDeps): http.Server & {
|
|
|
610
675
|
* 「单 URL 多方法」这一种成员,加一条要连同它的域内补门一起过评审。
|
|
611
676
|
*/
|
|
612
677
|
export declare function isMethodDispatchedSubmitPath(url: string): boolean;
|
|
678
|
+
/** 「这台部署配了任何一把 service 凭证吗」—— fail-closed 写门族的真值源。**单一属主**:`handle()` 的
|
|
679
|
+
* 凭证门、#329 的 parked 赎回席、以及 design/378 的 consolidation 阀门启动期告警读同一句(手抄迟早
|
|
680
|
+
* 只改一处,而这条判据决定的是「无凭证部署上写口开不开」)。导出仅为最后一个消费点(main.ts)。 */
|
|
681
|
+
export declare function hasServiceAuth(config: ServiceConfig): boolean;
|
|
613
682
|
export declare function isBillableSubmitPath(url: string): boolean;
|
|
614
683
|
/**
|
|
615
684
|
* A-010.23(#209 件5)—— **改写门**:不烧模型、但在**没有任何 service credential** 的部署形下必须与
|