@sema-agent/server 7.100.0 → 7.101.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 +22 -1
- package/README.md +12 -1
- package/README.zh-CN.md +9 -1
- package/USAGE.md +80 -9
- package/dist/approval-card.d.ts +24 -12
- package/dist/approval-card.js +4 -3
- package/dist/approval-policy-settlement.d.ts +31 -0
- package/dist/approval-policy-settlement.js +16 -0
- package/dist/approval-reconciler.d.ts +8 -1
- package/dist/approval-reconciler.js +5 -4
- package/dist/boot/config-center.js +86 -20
- package/dist/boot/execution-env.js +20 -2
- package/dist/boot/parked-revive-gate.js +1 -0
- package/dist/boot/reapers.js +1 -0
- package/dist/boot/resolve-spec.js +25 -8
- package/dist/boot/runner-deps.js +9 -3
- package/dist/boot/stage-02-registry-pull.js +9 -0
- package/dist/boot/stores.js +4 -0
- package/dist/config-catalog.js +5 -3
- package/dist/config-center/apply-effective.d.ts +25 -0
- package/dist/config-center/apply-effective.js +144 -69
- package/dist/config-center/apply-ledger.d.ts +12 -2
- package/dist/config-center/apply-ledger.js +4 -1
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/http-client.d.ts +4 -1
- package/dist/config-center/http-client.js +15 -10
- package/dist/config-center/read-warnings.d.ts +115 -6
- package/dist/config-center/read-warnings.js +115 -5
- package/dist/config-center/types.d.ts +6 -0
- package/dist/config-invariants.d.ts +2 -2
- package/dist/config-invariants.js +15 -0
- package/dist/config-provider.d.ts +4 -0
- package/dist/config-provider.js +64 -9
- package/dist/config-types.d.ts +37 -8
- package/dist/config.js +87 -22
- package/dist/deployment-governance.d.ts +45 -12
- package/dist/deployment-governance.js +37 -9
- package/dist/env-name-allowlist-knobs.d.ts +37 -0
- package/dist/env-name-allowlist-knobs.js +24 -0
- package/dist/fleet-client.d.ts +2 -1
- package/dist/hooks/hook-llm.js +13 -4
- package/dist/host-lsp-manager.d.ts +14 -2
- package/dist/host-lsp-manager.js +7 -6
- package/dist/http/admission.js +22 -7
- package/dist/http/memory-store-refusal-reply.d.ts +24 -0
- package/dist/http/memory-store-refusal-reply.js +10 -0
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/capabilities.js +3 -3
- package/dist/http/routes/memory-bundle.js +3 -0
- package/dist/http/routes/memory-compliance.js +3 -5
- package/dist/http/routes/memory-origin.js +3 -5
- package/dist/http/routes/sessions.js +7 -6
- package/dist/http/routes/side-query.js +10 -4
- package/dist/instruction-source-notice.d.ts +97 -0
- package/dist/instruction-source-notice.js +78 -0
- package/dist/memory-layer-legacy-lock.d.ts +14 -0
- package/dist/memory-layer-legacy-lock.js +74 -0
- package/dist/memory-operator-faces.js +1 -17
- package/dist/memory-store-refusal.d.ts +37 -0
- package/dist/memory-store-refusal.js +14 -0
- package/dist/model-entry-refusal.d.ts +130 -0
- package/dist/model-entry-refusal.js +157 -0
- package/dist/model-provider.d.ts +9 -3
- package/dist/model-route-endpoint.d.ts +1 -1
- package/dist/model-select.d.ts +49 -4
- package/dist/model-select.js +48 -17
- package/dist/observability/fail-open.d.ts +9 -1
- package/dist/observability/fail-open.js +9 -1
- package/dist/observability/metrics.js +1 -0
- package/dist/observability/secret-env-scrub.d.ts +27 -4
- package/dist/observability/secret-env-scrub.js +22 -7
- package/dist/plugins/caching-session-store.d.ts +6 -0
- package/dist/plugins/caching-session-store.js +6 -0
- package/dist/plugins/local-session-store.d.ts +20 -25
- package/dist/plugins/local-session-store.js +111 -64
- package/dist/plugins/pg-session-storage.d.ts +7 -0
- package/dist/plugins/pg-session-storage.js +19 -0
- package/dist/plugins/remote-env-host.d.ts +3 -1
- package/dist/plugins/remote-env-host.js +3 -6
- package/dist/plugins/tidb-pool.js +5 -0
- package/dist/plugins/tidb-session-store.d.ts +13 -0
- package/dist/plugins/tidb-session-store.js +14 -0
- package/dist/plugins/web-search.d.ts +2 -1
- package/dist/plugins/web-search.js +43 -24
- package/dist/project-memory.d.ts +10 -0
- package/dist/project-memory.js +28 -14
- package/dist/run-local.d.ts +17 -15
- package/dist/run-local.js +115 -15
- package/dist/sealed-key.d.ts +5 -2
- package/dist/server-secret-env.d.ts +23 -1
- package/dist/server-secret-env.js +9 -0
- package/dist/tool-approval.d.ts +65 -31
- package/dist/tool-approval.js +96 -49
- package/dist/trace/core-keyset-guard.d.ts +27 -5
- package/dist/trace/engine-notice-wire.d.ts +27 -1
- package/dist/trace/engine-notice-wire.js +8 -0
- package/dist/trace/wire-projection-faces.d.ts +2 -2
- package/dist/trace/wire-projection-faces.js +2 -2
- package/package.json +2 -2
- package/dist/memory-layer-preflight.d.ts +0 -46
- package/dist/memory-layer-preflight.js +0 -121
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core 7.31.1 #1094 `memory.target_outside_store` 的 **operator 面 HTTP 映射** —— 唯一一处字面量(判据在
|
|
3
|
+
* `src/memory-store-refusal.ts`)。挂在每一个触 store 树的 operator 记忆口的已分类收尾上:
|
|
4
|
+
* `routes/memory-compliance.ts`(出处 / 抹除)· `routes/memory-origin.ts`(外源列表 / 清标;**不含**只读控制面的清标账列表 ——
|
|
5
|
+
* core 的 wire 表逐字:只碰控制面的面不判,给它挂一条永远收不到的码路径正是附录 A 活触发门要防的形)·
|
|
6
|
+
* `routes/memory-bundle.ts`(导出 / 导入)。会话所有者的自助抹除口**不走这里**(`routes/sessions.ts`:同码、固定文案,见那里的注)。
|
|
7
|
+
* `POST /v1/admin/memory/consolidation/run` 也不走这里:那口把 core 的**任何**有码拒一律收成 409 `memory.consolidation_refused`
|
|
8
|
+
* (码进文案,附录 A 该行),本码已在那条臂上,不再给它第二个码。
|
|
9
|
+
*
|
|
10
|
+
* ## 为什么 409
|
|
11
|
+
* core 的读法:operator 要处置的拒(去掉 store 里那只通到外面的目录链),不是可重试的 IO 故障,也不是对调用方的裁决。与
|
|
12
|
+
* `memory.erasure_census_incomplete` 同一类(**当前 store 状态**下这次做不了;状态修好后重发即改判)⇒ 409,不 500(服务器没坏)、
|
|
13
|
+
* 不 4xx 请求族(请求没写错)。抹除面:链先于请求在场 ⇒ core 在提交点之前拒,零删除、零 journal(真 core 格钉在
|
|
14
|
+
* `test/memory-compliance-http.test.ts`);链在提交点之后才换上 ⇒ 已删的几条算数、journal 留盘 —— core 原句会带
|
|
15
|
+
* `after the commit point; the journal is kept`,本层原样转发、不嗅它分类(codex 一审 C1)。
|
|
16
|
+
*
|
|
17
|
+
* ## 文案原样
|
|
18
|
+
* core 的诊断句点名动作、路径与解析到哪 —— 那是运维找到那只链的唯一材料,与本族其它 core 码同一条「码与文案都不改写」纪律。
|
|
19
|
+
* 只对 operator 面这么做:这几口都过了 operator 门,宿主路径对这个受众不是泄露。
|
|
20
|
+
*/
|
|
21
|
+
import type { ServerResponse } from "node:http";
|
|
22
|
+
/** `err` 是 `memory.target_outside_store` ⇒ 409 带码 + core 原句,返回 true(已应答);否则 false,一个字节都不写。 */
|
|
23
|
+
export declare function sentMemoryStoreRefusal(res: ServerResponse, err: unknown): boolean;
|
|
24
|
+
//# sourceMappingURL=memory-store-refusal-reply.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { targetOutsideStoreOf } from "../memory-store-refusal.js";
|
|
2
|
+
import { sendError } from "./send.js";
|
|
3
|
+
export function sentMemoryStoreRefusal(res, err) {
|
|
4
|
+
const refusal = targetOutsideStoreOf(err);
|
|
5
|
+
if (refusal === undefined)
|
|
6
|
+
return false;
|
|
7
|
+
sendError(res, 409, "memory.target_outside_store", refusal.message);
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=memory-store-refusal-reply.js.map
|
|
@@ -288,6 +288,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
288
288
|
...(s.deadline !== undefined ? { deadline: s.deadline } : {}),
|
|
289
289
|
...(s.hasBidiControls === true ? { hasBidiControls: true } : {}),
|
|
290
290
|
...(s.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
291
|
+
...(s.mandated === true ? { mandated: true } : {}),
|
|
291
292
|
...(s.denialLimitFallback !== undefined ? { denialLimitFallback: s.denialLimitFallback } : {}),
|
|
292
293
|
...(s.origin !== undefined ? { origin: s.origin } : {}),
|
|
293
294
|
...(s.ruleStoreUnreadable !== undefined ? { ruleStoreUnreadable: s.ruleStoreUnreadable } : {}),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expandTiers, reasoningEffortLevelsOf, routePairingStatus } from "@sema-agent/core";
|
|
2
|
-
import { isModelAllowlisted } from "../../model-select.js";
|
|
2
|
+
import { isModelAllowlisted, selectableCatalog } from "../../model-select.js";
|
|
3
3
|
import { resolveModelApiKey } from "../../key-resolver.js";
|
|
4
4
|
import { isSealedKeyPoison } from "../../sealed-key.js";
|
|
5
5
|
import { cwdHonored, deviceCwdHonored } from "../../task-cwd.js";
|
|
@@ -228,7 +228,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
228
228
|
return;
|
|
229
229
|
}
|
|
230
230
|
case "models": {
|
|
231
|
-
const atCatalog =
|
|
231
|
+
const atCatalog = selectableCatalog(deps.config.models, deps.config.tiers, deps.config.modelRefusals);
|
|
232
232
|
const atAllowlist = deps.config.atModelAllowlist ?? [];
|
|
233
233
|
const models = Object.entries(deps.config.models)
|
|
234
234
|
.filter(([name]) => name !== "default")
|
|
@@ -242,7 +242,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
242
242
|
...(m.maxTokens ? { maxOutputTokens: m.maxTokens } : {}),
|
|
243
243
|
...((levels) => (levels !== undefined ? { supportedEffortLevels: [...levels] } : {}))(reasoningEffortLevelsOf(m)),
|
|
244
244
|
...(m.compat !== undefined ? { compat: m.compat } : {}),
|
|
245
|
-
...(atAllowlist.length > 0 ? { atMentionable: isModelAllowlisted(name, atCatalog, atAllowlist) } : {}),
|
|
245
|
+
...(atAllowlist.length > 0 ? { atMentionable: isModelAllowlisted(name, atCatalog, atAllowlist, deps.config.modelRefusals) } : {}),
|
|
246
246
|
routePairing: routePairingOf(deps, name, m),
|
|
247
247
|
}));
|
|
248
248
|
const nonAlias = Object.entries(deps.config.models).filter(([n]) => n !== "default");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { sendJson, sendError } from "../send.js";
|
|
3
|
+
import { sentMemoryStoreRefusal } from "../memory-store-refusal-reply.js";
|
|
3
4
|
import { buildOriginFencedBundle, readOriginAware } from "../../memory-origin-fence.js";
|
|
4
5
|
import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
5
6
|
import { assertNever } from "../route-ctx.js";
|
|
@@ -35,6 +36,8 @@ function sentClassifiedFailure(res, ctx, face, err) {
|
|
|
35
36
|
const code = codeOf(err);
|
|
36
37
|
if (code !== undefined && sentCoreCode(res, code, err instanceof Error ? err.message : String(err)))
|
|
37
38
|
return true;
|
|
39
|
+
if (sentMemoryStoreRefusal(res, err))
|
|
40
|
+
return true;
|
|
38
41
|
ctx.deps.logger?.warn?.(`memory_bundle_${face}_failed`, { err: String(err) });
|
|
39
42
|
return false;
|
|
40
43
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { ControlPlaneCorruptError } from "@sema-agent/core";
|
|
3
3
|
import { erasureInputSmugglesPrototypeKey } from "../../memory-operator-faces.js";
|
|
4
|
-
import {
|
|
4
|
+
import { sentMemoryStoreRefusal } from "../memory-store-refusal-reply.js";
|
|
5
5
|
import { sendJson, sendError } from "../send.js";
|
|
6
6
|
import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
7
7
|
import { assertNever } from "../route-ctx.js";
|
|
@@ -36,13 +36,11 @@ function codeOf(err) {
|
|
|
36
36
|
return typeof code === "string" ? code : undefined;
|
|
37
37
|
}
|
|
38
38
|
function sentClassifiedFailure(res, ctx, face, err) {
|
|
39
|
-
if (face === "erase" && err instanceof MemoryLayerUnwritableError) {
|
|
40
|
-
sendError(res, 409, "memory.layer_unwritable", `memory layer directories are not writable on disk: ${err.dirList} — refused before the engine was called: nothing was written and no journal was left. Restore the owner write bit with \`chmod u+w ${err.chmodTargets}\` and retry. This is a known issue of the memory engine's mount: it leaves a layer that a session reads but does not write read-only on disk (directories 0555, entries 0444) and does not restore it, so a later project session can lock it again`);
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
39
|
const coreCode = codeOf(err);
|
|
44
40
|
if (coreCode !== undefined && sentCoreCode(res, coreCode, err instanceof Error ? err.message : String(err)))
|
|
45
41
|
return true;
|
|
42
|
+
if (sentMemoryStoreRefusal(res, err))
|
|
43
|
+
return true;
|
|
46
44
|
if (err instanceof ControlPlaneCorruptError) {
|
|
47
45
|
sendError(res, 500, "internal.memory_control_plane_corrupt", err.message);
|
|
48
46
|
return true;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { ControlPlaneCorruptError } from "@sema-agent/core";
|
|
3
3
|
import { sendJson, sendError } from "../send.js";
|
|
4
|
+
import { sentMemoryStoreRefusal } from "../memory-store-refusal-reply.js";
|
|
4
5
|
import { buildOriginFence, readOriginAware } from "../../memory-origin-fence.js";
|
|
5
|
-
import { MemoryLayerUnwritableError } from "../../memory-layer-preflight.js";
|
|
6
6
|
import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
7
7
|
import { assertNever } from "../route-ctx.js";
|
|
8
8
|
export const MEMORY_ORIGIN_EXTERNAL_PATH = "/v1/memory/origin/external";
|
|
@@ -48,15 +48,13 @@ function codeOf(err) {
|
|
|
48
48
|
return typeof code === "string" ? code : undefined;
|
|
49
49
|
}
|
|
50
50
|
function sentClassifiedFailure(res, ctx, face, err) {
|
|
51
|
-
if (face === "clear" && err instanceof MemoryLayerUnwritableError) {
|
|
52
|
-
sendError(res, 409, "memory.layer_unwritable", `memory layer directories are not writable on disk: ${err.dirList} — refused before the engine was called: nothing was written and no journal was left. Restore the owner write bit with \`chmod u+w ${err.chmodTargets}\` and retry. This is a known issue of the memory engine's mount: it leaves a layer that a session reads but does not write read-only on disk (directories 0555, entries 0444) and does not restore it, so a later project session can lock it again`);
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
51
|
if (face === "clear") {
|
|
56
52
|
const coreCode = codeOf(err);
|
|
57
53
|
if (coreCode !== undefined && sentClearCode(res, coreCode, err instanceof Error ? err.message : String(err)))
|
|
58
54
|
return true;
|
|
59
55
|
}
|
|
56
|
+
if (face !== "clearances" && sentMemoryStoreRefusal(res, err))
|
|
57
|
+
return true;
|
|
60
58
|
if (err instanceof ControlPlaneCorruptError) {
|
|
61
59
|
sendError(res, 500, "internal.memory_control_plane_corrupt", err.message);
|
|
62
60
|
return true;
|
|
@@ -4,12 +4,12 @@ import { isUuidV7, isUuidShape, legacyUnownedSessionOk } from "../../security.js
|
|
|
4
4
|
import { windowMessages, truncateMessageBlobs } from "../../audit.js";
|
|
5
5
|
import { redactSecrets } from "../../trace/redact.js";
|
|
6
6
|
import { recordFailOpen } from "../../observability/fail-open.js";
|
|
7
|
+
import { targetOutsideStoreOf } from "../../memory-store-refusal.js";
|
|
7
8
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_PERMISSION_RULES, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
|
|
8
9
|
import { readSessionPermissionRules, describeSessionPermissionRulesProblem } from "../../session-policy-wire.js";
|
|
9
10
|
import { sendJson, sendError, sendSessionPolicyError, sseHeaders } from "../send.js";
|
|
10
11
|
import { gatedPrincipal, explicitOperatorOk, runOwnerReadable, sendPrincipalRefusal } from "../principal-gate.js";
|
|
11
12
|
import { assertNever } from "../route-ctx.js";
|
|
12
|
-
import { MemoryLayerUnwritableError } from "../../memory-layer-preflight.js";
|
|
13
13
|
const MCP_STATUS_TTL_MS = 5_000;
|
|
14
14
|
const MCP_MATERIALIZE_TIMEOUT_MS = 10_000;
|
|
15
15
|
const A2A_STATUS_TTL_MS = 5_000;
|
|
@@ -500,16 +500,17 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
500
500
|
sendJson(res, 200, await faces.erase({ requestId: raw.requestId, select: { sessionId }, ...(raw.allowUnevidenced !== undefined ? { allowUnevidenced: raw.allowUnevidenced } : {}) }));
|
|
501
501
|
}
|
|
502
502
|
catch (err) {
|
|
503
|
-
if (err instanceof MemoryLayerUnwritableError) {
|
|
504
|
-
deps.logger?.warn?.("session_memory_erase_layer_unwritable", { sessionId, dirs: err.dirs });
|
|
505
|
-
sendError(res, 409, "memory.layer_unwritable", "memory erasure refused: a memory layer directory on this server is not writable on disk, so the erase was stopped before anything was written (nothing was deleted, no journal was left). Ask whoever operates this deployment to restore the owner write bit on the memory layer directories (a known issue of the memory engine's mount), then retry with the same requestId");
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
503
|
const code = err.code;
|
|
509
504
|
if (code === "config.memory_erasure_request") {
|
|
510
505
|
sendError(res, 400, "config.memory_erasure_request", err instanceof Error ? err.message : "invalid memory erasure request");
|
|
511
506
|
return;
|
|
512
507
|
}
|
|
508
|
+
const outside = targetOutsideStoreOf(err);
|
|
509
|
+
if (outside !== undefined) {
|
|
510
|
+
deps.logger?.warn?.("session_memory_erase_refused", { sessionId, err: outside.message });
|
|
511
|
+
sendError(res, 409, "memory.target_outside_store", "session memory erasure refused — a directory in this deployment's memory store leads outside it (a link), so this erasure did not complete (a refusal before its commit point changed nothing; one after it keeps the deletions already made and a journal that finishes the rest once the link is removed); ask the operator to remove the link, then resend with the same requestId");
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
513
514
|
deps.logger?.warn?.("session_memory_erase_failed", { sessionId, err: String(err) });
|
|
514
515
|
sendError(res, 500, "internal.error", "session memory erasure failed");
|
|
515
516
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { expandTiers, isThinkingLevel } from "@sema-agent/core";
|
|
2
2
|
import { HttpError } from "../../security.js";
|
|
3
|
-
import { isModelAllowlisted } from "../../model-select.js";
|
|
3
|
+
import { isModelAllowlisted, matchCatalogModel, refusedCatalogEntry, selectableCatalog } from "../../model-select.js";
|
|
4
|
+
import { modelUnavailableMessage } from "../../model-entry-refusal.js";
|
|
4
5
|
import { redactSecrets } from "../../trace/redact.js";
|
|
5
6
|
import { sendJson, sendError, httpErrorCode, msg } from "../send.js";
|
|
6
7
|
import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
|
|
@@ -68,9 +69,14 @@ export async function handleSideQuery(req, res, match, ctx) {
|
|
|
68
69
|
return;
|
|
69
70
|
}
|
|
70
71
|
if (typeof body.model === "string" && body.model.length > 0) {
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
72
|
+
const catalog = selectableCatalog(deps.config.models ?? {}, deps.config.tiers, deps.config.modelRefusals);
|
|
73
|
+
const unavailable = matchCatalogModel(body.model, catalog, deps.config.modelRefusals) === undefined ? refusedCatalogEntry(body.model, deps.config.modelRefusals) : undefined;
|
|
74
|
+
if (unavailable !== undefined) {
|
|
75
|
+
const message = modelUnavailableMessage("model", body.model.slice(0, 120), unavailable.name, unavailable.refusal);
|
|
76
|
+
sendError(res, 400, "request.model_unavailable", message);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!isModelAllowlisted(body.model, catalog, deps.config.atModelAllowlist, deps.config.modelRefusals)) {
|
|
74
80
|
sendError(res, 400, "request.model_not_allowed", `model is not in this deployment's @-mention allowlist: "${body.model.slice(0, 120)}" — the allowlist is configured by the operator (config-center models.atModelAllowlist)`);
|
|
75
81
|
return;
|
|
76
82
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-690 —— 项目指令文件**换源**通告(`engine_notice` 码 `instructions.source_changed`)的唯一铸点与基线属主口。
|
|
3
|
+
*
|
|
4
|
+
* ## 这件事为什么归本仓
|
|
5
|
+
* 选哪一份指令文件是**部署的内容半场**(`project-memory.ts` 的 `INSTRUCTION_FILE_CHAIN`;core 的 `loadProjectMemory`
|
|
6
|
+
* 契约逐字「core … NEVER hardcodes a filename」),所以「这一 run 读的是哪一份、上一 run 读的是哪一份」也只有本仓
|
|
7
|
+
* 答得出。core 的 run 内变更车道(`steering_injected`,`source: "instructions_change"`)只跟**本 run 自己声明的**
|
|
8
|
+
* 来源比,进程重启(= 升级)后没有任何东西记得上一 run 注入的是哪一份 —— 于是两文件并存的仓升级后静默换源
|
|
9
|
+
* (车MN 收车档 §1.5,判据 G6)。
|
|
10
|
+
*
|
|
11
|
+
* ## 基线 = 会话的服务端元数据
|
|
12
|
+
* 基线是「同一 session 上一次 run 选中的链成员相对路径」(不是指纹:指纹密钥每进程随机,跨进程不可比)。它住在会话店的
|
|
13
|
+
* 服务端元数据面 —— SQL 两孪生是 `session_meta.instruction_source` 一列,local 文件店是一只 sidecar —— 所以重启 /
|
|
14
|
+
* 跨副本都读得到、随会话删除一起消失,且不进模型上下文、不进 wire 会话面。不接这张面的店(core 的内存 dev 店、
|
|
15
|
+
* `run-local` 的 core 文件店)⇒ 席缺席 ⇒ 一帧不发(诚实缺席:那两形的会话本来就活不过进程,或没有会话流可投)。
|
|
16
|
+
*
|
|
17
|
+
* ## 铸点(唯一)
|
|
18
|
+
* {@link announceInstructionSource} 是本码**唯一**的铸点(`@contract instructions.source_changed.single_mint`)。
|
|
19
|
+
* 调用点只有一处:`makeLoadProjectMemory` 在每次**走了指令链**的 prepare 载入完成之后(core 每个 run 恰调一次 `loadProjectMemory`)
|
|
20
|
+
* ⇒ 一 run 至多一帧。走链之前就判「这次没有项目可读」的 prepare(无手场景、cwd 不是本机绝对目录)是**不适用**,不是
|
|
21
|
+
* 「来源 = 无」—— 不进本铸点,基线保持上一次真读的那份(O6,7.101.0 合并复审:无手孪生 Runner 与主 Runner 共用同一只
|
|
22
|
+
* loader 与会话店,把不适用折成 `null` 会让「默认 → 无手 → 默认」同 session 两跳无端发两帧)。分工照 `task-mcp.ts` `mintInjectionDropNotices` 的先例:本模块铸通告,`emit` 由装配方给
|
|
23
|
+
* (部署的 `onNotice` 转发体 —— 先日志后分流,白名单 / 归属 / 消毒 / 隔离四件判据一个不绕)。
|
|
24
|
+
*
|
|
25
|
+
* ## 不做的事
|
|
26
|
+
* 不区分「升级换源」与「用户改了文件」(跨进程指纹不可比;载荷只说前后两个来源)。detail 没有 `runId`:铸点在 core 的
|
|
27
|
+
* `loadProjectMemory` 席内,该席的 ctx 不带 runId(core 7.30.1 键集 = cwd / handsEnabled / isSubagent / agentName /
|
|
28
|
+
* sessionId / phase)—— 与 `mcp.injection_dropped`「装配期铸、无 runId」同形,run 归属由投递腿承载(这条 run 的 SSE /
|
|
29
|
+
* 这条 run 的账本)。向 core 要 ctx 加 runId 已登记为未闭环项。
|
|
30
|
+
*/
|
|
31
|
+
import type { EngineNotice } from "@sema-agent/core";
|
|
32
|
+
import type { ServerNoticeCode } from "./trace/engine-notice-wire.js";
|
|
33
|
+
export declare const INSTRUCTION_SOURCE_CHANGED: ServerNoticeCode;
|
|
34
|
+
/** 基线读数。`recorded: false` = 这个会话还没有任何 run 记过基线(首 run ⇒ 只写不发);`source: null` = 记过,且那一次
|
|
35
|
+
* 没有项目指令文件被选中。两态必须可判别:把「没记过」读成「记过没有」会让新会话首 run 发一帧 `null → CLAUDE.md`。 */
|
|
36
|
+
export type InstructionSourceBaseline = {
|
|
37
|
+
readonly recorded: false;
|
|
38
|
+
} | {
|
|
39
|
+
readonly recorded: true;
|
|
40
|
+
readonly source: string | null;
|
|
41
|
+
};
|
|
42
|
+
/** 基线属主口(会话店的服务端元数据面)。 */
|
|
43
|
+
export interface InstructionSourceStore {
|
|
44
|
+
readInstructionSource(sessionId: string): Promise<InstructionSourceBaseline>;
|
|
45
|
+
/**
|
|
46
|
+
* 覆写基线 —— **CAS**:只在店里此刻的基线仍等于 `expected`(调用方读到的那一份)时写,否则什么都不做(不抛)。
|
|
47
|
+
* 为什么是 CAS(codex che-NM r1 [high]):铸点对基线读写有期限,超期的写会**晚到**;不带期望值的覆写会让一个
|
|
48
|
+
* 基于过期读数的旧 run 盖掉更新的 run 写下的基线,下一 run 于是比错。会话行不存在 ⇒ 同样不写。
|
|
49
|
+
* 写**没有提交**必须抛(SQL 的写本来就如此;local sidecar 回滚内存再抛)—— 「内存已前进而盘没跟上」会让同来源的
|
|
50
|
+
* 后续 run 在相等比较上短路、再也不重写,重启后比的是一份过期的盘(codex che-NM r1 [medium])。
|
|
51
|
+
*/
|
|
52
|
+
recordInstructionSource(sessionId: string, expected: InstructionSourceBaseline, source: string | null): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
/** 一只装配好的席:基线店 + 通告出口。 */
|
|
55
|
+
export interface InstructionSourceSeat {
|
|
56
|
+
readonly store: InstructionSourceStore;
|
|
57
|
+
readonly emit: (notice: EngineNotice) => void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 基线的**存储编码**(SQL 列与 local sidecar 同一只):「没有项目指令文件」存成空串,链成员存它的相对路径;
|
|
61
|
+
* 「没记过」是 SQL `NULL` / sidecar 里缺键。空串不会与任何文件名撞(没有叫空名的文件),所以不需要保留词。
|
|
62
|
+
*/
|
|
63
|
+
export declare function encodeInstructionSource(source: string | null): string;
|
|
64
|
+
/** 两份基线读数是否同一(CAS 的判等;三店与测试夹具同用这一只)。 */
|
|
65
|
+
export declare function sameInstructionSourceBaseline(a: InstructionSourceBaseline, b: InstructionSourceBaseline): boolean;
|
|
66
|
+
/** {@link encodeInstructionSource} 的逆。非串非空的值 = 这一格被本仓以外的东西写坏了 ⇒ 抛(调用方按基线读不出处置)。 */
|
|
67
|
+
export declare function decodeInstructionSource(raw: unknown): InstructionSourceBaseline;
|
|
68
|
+
/**
|
|
69
|
+
* 会话店是否提供基线面 —— **两个方法都在**才算(只有一半的店让读写不同源,比整张缺席更坏)。结构判而不是按类名判
|
|
70
|
+
* (包装店 `CachingSessionStore` 逐方法转发,判据看的是能力不是名字)。
|
|
71
|
+
*/
|
|
72
|
+
export declare function instructionSourceStoreOf(sessionStore: unknown): InstructionSourceStore | undefined;
|
|
73
|
+
/** 装配点的唯一口(server boot 与 run-local 两处同走):店没有基线面或没有通告出口 ⇒ 席缺席(一帧不发)。 */
|
|
74
|
+
export declare function createInstructionSourceSeat(sessionStore: unknown, emit: ((notice: EngineNotice) => void) | undefined): InstructionSourceSeat | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* 基线每一次读 / 写的期限(codex che-NM r1 [high])。铸点跑在 prepare 里、loader 等它交出答案才返回,而两支 SQL 孪生的
|
|
77
|
+
* 查询默认没有超时(MySQL 无、PG `statement_timeout` 缺省关)—— 一次卡住的行锁 / 连接就会把整个 prepare 挂住,而修前
|
|
78
|
+
* loader 根本不碰库。两层同一个数:① 这里放手(超期 ⇒ 按读 / 写失败处置,留痕放行);② 两支 SQL 孪生的基线查询带**每查询
|
|
79
|
+
* 超时**(codex che-NM r2 [high];S-9 `write-behind-counter.ts` 先例:mysql2 `{sql, timeout}` / pg `query_timeout`,pg-pool
|
|
80
|
+
* 超时即销毁那条连接)—— 只放手不截断,卡住的查询会一直占着池里的连接。超期后仍在飞的写由 CAS 挡住,不会盖掉更新的基线。
|
|
81
|
+
* 数值与 core 给 `probeInstructionSources` 的 2s 天花板同量级:一行 PK 读写在健康库上是毫秒级。
|
|
82
|
+
*/
|
|
83
|
+
export declare const BASELINE_IO_DEADLINE_MS = 2000;
|
|
84
|
+
/**
|
|
85
|
+
* **唯一铸点**:本次 prepare 选中的来源 `current`(链成员相对路径;没有 = `null`)与基线比。
|
|
86
|
+
* · 没有基线 ⇒ 只写不发(新会话首 run);
|
|
87
|
+
* · 相同 ⇒ 什么都不做(不写);
|
|
88
|
+
* · 不同 ⇒ 铸一帧、再以读到的基线为期望值 CAS 覆写。
|
|
89
|
+
* 基线读 / 写各有 {@link BASELINE_IO_DEADLINE_MS} 的期限。
|
|
90
|
+
*
|
|
91
|
+
* 失败方向(F 类,`server.instruction-source.announce-degraded`,detail 带 `op=read|write|emit`):通告是**披露**面
|
|
92
|
+
* 不是执法面,所以基线读 / 写 / 出口失败一律留痕后放行,**绝不**抛回调用方 —— 抛回去会让 core 把这次 prepare 的项目记忆整块丢掉(loader 抛 ⇒ 不注入),
|
|
93
|
+
* 为一条通告赔上模型要读的指令本身。读失败 ⇒ 本 run 不比也不写;写失败 ⇒ 本 run 的帧照发(它对本 run 为真),代价是
|
|
94
|
+
* 下一 run 可能再说一遍同一次换源;出口抛(转发体今天结构上不抛,守的是「本函数不抛」这条契约)⇒ 本 run 缺这一帧。
|
|
95
|
+
*/
|
|
96
|
+
export declare function announceInstructionSource(seat: InstructionSourceSeat, sessionId: string, current: string | null): Promise<void>;
|
|
97
|
+
//# sourceMappingURL=instruction-source-notice.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { recordFailOpen } from "./observability/fail-open.js";
|
|
2
|
+
export const INSTRUCTION_SOURCE_CHANGED = "instructions.source_changed";
|
|
3
|
+
export function encodeInstructionSource(source) {
|
|
4
|
+
return source ?? "";
|
|
5
|
+
}
|
|
6
|
+
export function sameInstructionSourceBaseline(a, b) {
|
|
7
|
+
if (!a.recorded || !b.recorded)
|
|
8
|
+
return a.recorded === b.recorded;
|
|
9
|
+
return a.source === b.source;
|
|
10
|
+
}
|
|
11
|
+
export function decodeInstructionSource(raw) {
|
|
12
|
+
if (raw === null || raw === undefined)
|
|
13
|
+
return { recorded: false };
|
|
14
|
+
if (typeof raw !== "string")
|
|
15
|
+
throw new TypeError(`instruction_source holds a ${typeof raw}, not a string`);
|
|
16
|
+
return { recorded: true, source: raw === "" ? null : raw };
|
|
17
|
+
}
|
|
18
|
+
export function instructionSourceStoreOf(sessionStore) {
|
|
19
|
+
const s = sessionStore;
|
|
20
|
+
if (typeof s?.readInstructionSource !== "function" || typeof s.recordInstructionSource !== "function")
|
|
21
|
+
return undefined;
|
|
22
|
+
return { readInstructionSource: s.readInstructionSource.bind(s), recordInstructionSource: s.recordInstructionSource.bind(s) };
|
|
23
|
+
}
|
|
24
|
+
export function createInstructionSourceSeat(sessionStore, emit) {
|
|
25
|
+
const store = instructionSourceStoreOf(sessionStore);
|
|
26
|
+
return store && emit ? { store, emit } : undefined;
|
|
27
|
+
}
|
|
28
|
+
const describeSource = (source) => source ?? "no project instruction file";
|
|
29
|
+
export const BASELINE_IO_DEADLINE_MS = 2_000;
|
|
30
|
+
class BaselineDeadlineExceeded extends Error {
|
|
31
|
+
}
|
|
32
|
+
function withinDeadline(op) {
|
|
33
|
+
const running = Promise.resolve().then(op);
|
|
34
|
+
running.catch(() => undefined);
|
|
35
|
+
let timer;
|
|
36
|
+
const deadline = new Promise((_, reject) => {
|
|
37
|
+
timer = setTimeout(() => reject(new BaselineDeadlineExceeded(`no answer within ${BASELINE_IO_DEADLINE_MS}ms`)), BASELINE_IO_DEADLINE_MS);
|
|
38
|
+
timer.unref?.();
|
|
39
|
+
});
|
|
40
|
+
return Promise.race([running, deadline]).finally(() => clearTimeout(timer));
|
|
41
|
+
}
|
|
42
|
+
export async function announceInstructionSource(seat, sessionId, current) {
|
|
43
|
+
let baseline;
|
|
44
|
+
try {
|
|
45
|
+
baseline = await withinDeadline(() => seat.store.readInstructionSource(sessionId));
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
degraded("read", sessionId, e);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (baseline.recorded && baseline.source === current)
|
|
52
|
+
return;
|
|
53
|
+
if (baseline.recorded) {
|
|
54
|
+
const previous = baseline.source;
|
|
55
|
+
try {
|
|
56
|
+
seat.emit({
|
|
57
|
+
code: INSTRUCTION_SOURCE_CHANGED,
|
|
58
|
+
message: `Project instructions for this run come from ${describeSource(current)}; the previous run of this session read ${describeSource(previous)}.`,
|
|
59
|
+
detail: { previous, current, sessionId },
|
|
60
|
+
sessionId,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
degraded("emit", sessionId, e);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
await withinDeadline(() => seat.store.recordInstructionSource(sessionId, baseline, current));
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
degraded("write", sessionId, e);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function degraded(op, sessionId, e) {
|
|
75
|
+
const why = e instanceof BaselineDeadlineExceeded ? `timeout=${BASELINE_IO_DEADLINE_MS}ms` : `err=${e instanceof Error ? e.message : String(e)}`;
|
|
76
|
+
recordFailOpen("server.instruction-source.announce-degraded", `op=${op} session=${sessionId} ${why}`);
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=instruction-source-notice.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { EngineNotice } from "@sema-agent/core";
|
|
2
|
+
import type { ServerNoticeCode } from "./trace/engine-notice-wire.js";
|
|
3
|
+
export declare const MEMORY_LAYER_LOCKED_LEGACY: ServerNoticeCode;
|
|
4
|
+
/** 探测读数:锁住的层名(排序)+ 看不全的目录条数。挂载面不存在(新数据根)⇒ 两者皆空。 */
|
|
5
|
+
export interface LockedLegacyLayerProbe {
|
|
6
|
+
readonly layers: readonly string[];
|
|
7
|
+
readonly unreadable: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function probeLockedLegacyMemoryLayers(memoryDir: string): LockedLegacyLayerProbe;
|
|
10
|
+
/** 锁住的层 ⇒ 一条通告(纯数据);没有 ⇒ `undefined`。detail 两键:`layers`(层名)与 `remedy`(同一句 `chmod -R u+w <memoryDir>`)。 */
|
|
11
|
+
export declare function buildMemoryLayerLockedLegacyNotice(memoryDir: string, layers: readonly string[]): EngineNotice | undefined;
|
|
12
|
+
/** boot 期的唯一调用口:探测一次、有锁住的层就经 `emit` 发一条;看不全的子树走 F 类留痕。不抛(不拒启)。 */
|
|
13
|
+
export declare function announceLockedLegacyMemoryLayers(memoryDir: string, emit: (notice: EngineNotice) => void): void;
|
|
14
|
+
//# sourceMappingURL=memory-layer-legacy-lock.d.ts.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { lstatSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { recordFailOpen } from "./observability/fail-open.js";
|
|
4
|
+
export const MEMORY_LAYER_LOCKED_LEGACY = "memory.layer_locked_legacy";
|
|
5
|
+
const OWNER_WRITE = 0o200;
|
|
6
|
+
function shellQuoted(s) {
|
|
7
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
8
|
+
}
|
|
9
|
+
const isMissing = (err) => err?.code === "ENOENT";
|
|
10
|
+
function treeHasLockedDir(dir, gaps) {
|
|
11
|
+
let st;
|
|
12
|
+
try {
|
|
13
|
+
st = lstatSync(dir);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
if (!isMissing(err))
|
|
17
|
+
gaps.n += 1;
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (!st.isDirectory())
|
|
21
|
+
return false;
|
|
22
|
+
if ((st.mode & OWNER_WRITE) === 0)
|
|
23
|
+
return true;
|
|
24
|
+
let entries;
|
|
25
|
+
try {
|
|
26
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
if (!isMissing(err))
|
|
30
|
+
gaps.n += 1;
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
for (const e of entries) {
|
|
34
|
+
if (e.isDirectory() && treeHasLockedDir(join(dir, e.name), gaps))
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
export function probeLockedLegacyMemoryLayers(memoryDir) {
|
|
40
|
+
const gaps = { n: 0 };
|
|
41
|
+
let top;
|
|
42
|
+
try {
|
|
43
|
+
top = readdirSync(memoryDir, { withFileTypes: true });
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
return { layers: [], unreadable: isMissing(err) ? 0 : 1 };
|
|
47
|
+
}
|
|
48
|
+
const layers = top.filter((e) => e.isDirectory() && treeHasLockedDir(join(memoryDir, e.name), gaps)).map((e) => e.name);
|
|
49
|
+
return { layers: layers.sort(), unreadable: gaps.n };
|
|
50
|
+
}
|
|
51
|
+
export function buildMemoryLayerLockedLegacyNotice(memoryDir, layers) {
|
|
52
|
+
if (layers.length === 0)
|
|
53
|
+
return undefined;
|
|
54
|
+
const remedy = `chmod -R u+w ${shellQuoted(memoryDir)}`;
|
|
55
|
+
const one = layers.length === 1;
|
|
56
|
+
return {
|
|
57
|
+
code: MEMORY_LAYER_LOCKED_LEGACY,
|
|
58
|
+
message: `memory layer${one ? "" : "s"} ${layers.map((l) => JSON.stringify(l)).join(", ")} ${one ? "still carries" : "still carry"} the read-only permission bits ` +
|
|
59
|
+
"an engine from 7.30.x or earlier (a server replica at 7.100.0 or earlier) sets on every layer it mounts read-only — deleting an " +
|
|
60
|
+
"entry there (memory erase / origin clear) fails for a server that does not run as root: the request answers 500, its journal " +
|
|
61
|
+
`stays on disk and this replica's next start fails. Run \`${remedy}\` once no such engine still runs on this store (an older ` +
|
|
62
|
+
"replica sets the bits again whenever it mounts a layer read-only). Starting anyway.",
|
|
63
|
+
detail: { layers: [...layers], remedy },
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function announceLockedLegacyMemoryLayers(memoryDir, emit) {
|
|
67
|
+
const probe = probeLockedLegacyMemoryLayers(memoryDir);
|
|
68
|
+
if (probe.unreadable > 0)
|
|
69
|
+
recordFailOpen("server.memory.locked-layer-probe-partial", `unreadable=${probe.unreadable}`);
|
|
70
|
+
const notice = buildMemoryLayerLockedLegacyNotice(memoryDir, probe.layers);
|
|
71
|
+
if (notice !== undefined)
|
|
72
|
+
emit(notice);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=memory-layer-legacy-lock.js.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { MemoryEngine, } from "@sema-agent/core";
|
|
2
2
|
import { memoryMountRootOf } from "./memory-scope.js";
|
|
3
|
-
import { memoryLayerWriteRefusal } from "./memory-layer-preflight.js";
|
|
4
3
|
export function backendOwnsControlPlane(backend) {
|
|
5
4
|
const b = backend;
|
|
6
5
|
return typeof b?.controlPlaneRoot === "string" && b.controlPlaneRoot !== "";
|
|
@@ -41,7 +40,6 @@ function erasureEnvelopeRefusal(input, allowedScopes) {
|
|
|
41
40
|
export function createMemoryComplianceFaces(store, opts) {
|
|
42
41
|
if (!backendOwnsControlPlane(store.backend))
|
|
43
42
|
return undefined;
|
|
44
|
-
const mountRoot = memoryMountRootOf(store.backend, store.root);
|
|
45
43
|
const engine = new MemoryEngine({
|
|
46
44
|
backend: store.backend,
|
|
47
45
|
memoryDir: memoryMountRootOf(store.backend, store.root),
|
|
@@ -59,14 +57,6 @@ export function createMemoryComplianceFaces(store, opts) {
|
|
|
59
57
|
const refusal = erasureEnvelopeRefusal(input, opts?.allowedScopes);
|
|
60
58
|
if (refusal !== undefined)
|
|
61
59
|
return Promise.reject(refusal);
|
|
62
|
-
try {
|
|
63
|
-
const locked = memoryLayerWriteRefusal(mountRoot);
|
|
64
|
-
if (locked !== undefined)
|
|
65
|
-
return Promise.reject(locked);
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
return Promise.reject(err);
|
|
69
|
-
}
|
|
70
60
|
return engine.eraseMemoryEntries(input);
|
|
71
61
|
},
|
|
72
62
|
};
|
|
@@ -74,7 +64,6 @@ export function createMemoryComplianceFaces(store, opts) {
|
|
|
74
64
|
export function createMemoryOriginFaces(store, opts) {
|
|
75
65
|
if (!backendOwnsControlPlane(store.backend))
|
|
76
66
|
return undefined;
|
|
77
|
-
const mountRoot = memoryMountRootOf(store.backend, store.root);
|
|
78
67
|
const engine = new MemoryEngine({
|
|
79
68
|
backend: store.backend,
|
|
80
69
|
memoryDir: memoryMountRootOf(store.backend, store.root),
|
|
@@ -84,12 +73,7 @@ export function createMemoryOriginFaces(store, opts) {
|
|
|
84
73
|
return {
|
|
85
74
|
listExternal: (scopes) => engine.listExternalOriginEntries(scopes),
|
|
86
75
|
listClearances: () => engine.listOriginClearances(),
|
|
87
|
-
clear:
|
|
88
|
-
const locked = memoryLayerWriteRefusal(mountRoot);
|
|
89
|
-
if (locked !== undefined)
|
|
90
|
-
throw locked;
|
|
91
|
-
return engine.clearEntryOrigin(entryId, input);
|
|
92
|
-
},
|
|
76
|
+
clear: (entryId, input) => engine.clearEntryOrigin(entryId, input),
|
|
93
77
|
};
|
|
94
78
|
}
|
|
95
79
|
export function createSessionMemoryStatusFace(store, opts) {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core 7.31.1 #1094 —— **`memory.target_outside_store`** 在本仓的唯一读法属主(纯叶:零 http / 零 boot 依赖)。
|
|
3
|
+
*
|
|
4
|
+
* ## 这个码说的是什么(core `docs/wire-consumption/7.31.1.md` 逐 face 表,tag v7.31.1 亲读)
|
|
5
|
+
* 记忆 store 里的一个目录经软链通到了 store **外**(或 store 根在 backend 打开 / 引擎首次挂载之后被改指)⇒ 引擎对落点在外的
|
|
6
|
+
* 删 / 写 / 重放 / 自身写一律**不做**,抛普通 `Error` + `.code`(文案以码领头,点名动作、路径、解析到哪)。core 的读法原话:
|
|
7
|
+
* operator 要处置的拒(去掉那只目录链),**不是**可重试的 IO 故障,也**不是**对调用方的裁决;链去掉之前重发恒拒。
|
|
8
|
+
*
|
|
9
|
+
* ## 两条到达车道,两个读者(本模块各给一只,别处不许再写第二份判据)
|
|
10
|
+
* ① **抛出面**(抹除 / 出处 / 外源列表 / 清标 / bundle 导出导入 —— 凡触 store 树的 backend 面):`.code` 就是码
|
|
11
|
+
* ⇒ {@link targetOutsideStoreOf}。HTTP 映射在 `http/memory-store-refusal-reply.ts`(409,一处字面量)。
|
|
12
|
+
* ② **报告面**(harvest `incident`):core 契约写明 report 车道上码**只在文本里** —— `sidecar_corrupt` 的 detail 恰两种形
|
|
13
|
+
* `memory commit refused: memory.target_outside_store: …` / `memory control plane refused: memory.target_outside_store: …`
|
|
14
|
+
* (core `docs/wire-consumption/7.31.1.md` 的 harvest 行逐字)⇒ {@link targetOutsideStoreInIncident} 只认**这两个 lead 紧跟码**
|
|
15
|
+
* 的位置:lead 与码都是 core 铸的字面,路径与引用数据全在码之后。🔴 不做全文子串找(codex 一审 C2:一条真控制面损坏若在
|
|
16
|
+
* 它引用的坏数据里恰好带着这串字,子串找会把它标成目录链 —— 两者运维动作相反)。不读散文、不猜语义;结构化码位
|
|
17
|
+
* (`HarvestReport.incident` / `.warnings` 带码)是向 core 索取的欠账。
|
|
18
|
+
* ⚠️ 同一拒的**还原类**(缺失文件还原 / 只读层还原 / `MEMORY.md` 写)只进 `HarvestReport.warnings`(可以 `ok:true`、无 incident),
|
|
19
|
+
* 那几行的前缀是**条目路径**(模型可写)—— 本仓今天不读 warnings 车道(既有面,不是本批回归),也不在模型可写的文本上
|
|
20
|
+
* 找码;这一半候 core 的结构化码位(登记在案,codex 一审 C3)。
|
|
21
|
+
*
|
|
22
|
+
* ## 不是这个码的三形(core 同表「different surface」,本仓**不**自拼码,如实走各自既有臂)
|
|
23
|
+
* · journal 重放目标目录成链 ⇒ `ControlPlaneCorruptError`(无 `.code`,KL-1094-b)⇒ 路由 typed 500 `internal.memory_control_plane_corrupt`;
|
|
24
|
+
* · 引擎原地写撞硬链(nlink>1)⇒ 文本 `has N names (a hard link)`、无码(KL-1094-g / #1105)⇒ 走该写点既有的失败车道(harvest
|
|
25
|
+
* warnings / 挂载失败),抛出面够不着;
|
|
26
|
+
* · 解析故障(`EACCES` / `ELOOP` / `ENOTDIR`)⇒ 以 errno 本身抛 ⇒ 路由未分类 500 + 运维 warn(`String(err)` 带 core 补救句)。
|
|
27
|
+
*/
|
|
28
|
+
/** core 的码(`src/core/memory-engine/layout.ts` `TARGET_OUTSIDE_STORE`;core 不在包根导出它,本常量是它在本仓的唯一写点)。 */
|
|
29
|
+
export declare const TARGET_OUTSIDE_STORE = "memory.target_outside_store";
|
|
30
|
+
/** 抛出面:`err` 是这一拒 ⇒ 原样交回(已窄化成 `Error`,调用方要它的 `message`);否则 `undefined`。形 = core 的 `Error` + `.code`。 */
|
|
31
|
+
export declare function targetOutsideStoreOf(err: unknown): Error | undefined;
|
|
32
|
+
/** 报告面:harvest `incident` 是这一拒 ⇒ 码;否则 `undefined`(真控制面损坏的 `sidecar_corrupt` 走这一臂 —— 不被说成链)。 */
|
|
33
|
+
export declare function targetOutsideStoreInIncident(incident: {
|
|
34
|
+
readonly kind: string;
|
|
35
|
+
readonly detail: string;
|
|
36
|
+
}): typeof TARGET_OUTSIDE_STORE | undefined;
|
|
37
|
+
//# sourceMappingURL=memory-store-refusal.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const TARGET_OUTSIDE_STORE = "memory.target_outside_store";
|
|
2
|
+
export function targetOutsideStoreOf(err) {
|
|
3
|
+
if (!(err instanceof Error))
|
|
4
|
+
return undefined;
|
|
5
|
+
const { code } = err;
|
|
6
|
+
return code === TARGET_OUTSIDE_STORE ? err : undefined;
|
|
7
|
+
}
|
|
8
|
+
const INCIDENT_PREFIXES = [`memory commit refused: ${TARGET_OUTSIDE_STORE}: `, `memory control plane refused: ${TARGET_OUTSIDE_STORE}: `];
|
|
9
|
+
export function targetOutsideStoreInIncident(incident) {
|
|
10
|
+
if (incident.kind !== "sidecar_corrupt")
|
|
11
|
+
return undefined;
|
|
12
|
+
return INCIDENT_PREFIXES.some((p) => incident.detail.startsWith(p)) ? TARGET_OUTSIDE_STORE : undefined;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=memory-store-refusal.js.map
|