@sema-agent/server 7.128.0 → 7.129.1
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 +140 -0
- package/USAGE.md +114 -18
- package/dist/approval-card.d.ts +53 -26
- package/dist/approval-card.js +40 -11
- package/dist/approval-crash-convergence.js +4 -4
- package/dist/bake-runner/main.js +2 -1
- package/dist/boot/execution-env.js +1 -0
- package/dist/boot/memory-consolidation.d.ts +46 -16
- package/dist/boot/memory-consolidation.js +15 -8
- package/dist/boot/side-query-lane.d.ts +7 -20
- package/dist/boot/side-query-lane.js +0 -24
- package/dist/boot/stage-01-config.js +3 -0
- package/dist/boot/stage-06-runners.js +1 -1
- package/dist/boot/stores.js +2 -2
- package/dist/boot/web-admin.js +17 -10
- package/dist/boot/webfetch-summarize-lane.d.ts +10 -7
- package/dist/boot/webfetch-summarize-lane.js +2 -3
- package/dist/brain.js +6 -9
- package/dist/capabilities/builtin-tools.d.ts +2 -2
- package/dist/capabilities/engine-capabilities.d.ts +14 -0
- package/dist/capabilities/engine-capabilities.js +12 -0
- package/dist/capability-facts.d.ts +3 -1
- package/dist/capability-facts.js +1 -0
- package/dist/config-catalog.js +5 -1
- package/dist/config-types.d.ts +7 -3
- package/dist/config.js +7 -1
- package/dist/egress-redirect.d.ts +12 -0
- package/dist/egress-redirect.js +7 -0
- package/dist/entry-mode-clamp-notice.d.ts +30 -0
- package/dist/entry-mode-clamp-notice.js +13 -0
- package/dist/exec-bounds.d.ts +69 -0
- package/dist/exec-bounds.js +37 -0
- package/dist/fleet/fleet-bus.d.ts +7 -0
- package/dist/fleet/fleet-bus.js +1 -0
- package/dist/hooks/hook-llm.js +6 -2
- package/dist/host-lsp-manager.d.ts +3 -3
- package/dist/http/admission.js +5 -2
- package/dist/http/resume-legs.js +46 -27
- package/dist/http/route-ctx.d.ts +1 -1
- package/dist/http/routes/approvals-assistant.js +6 -4
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/session-sync.js +3 -4
- package/dist/http/routes/sessions.js +4 -17
- package/dist/http/send.d.ts +1 -1
- package/dist/http/send.js +4 -3
- package/dist/http/wire-types.d.ts +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/observability/fail-open.d.ts +7 -3
- package/dist/observability/fail-open.js +7 -3
- package/dist/observability/secret-env-scrub.d.ts +37 -3
- package/dist/observability/secret-env-scrub.js +2 -2
- package/dist/parked-decide.js +5 -2
- package/dist/plugins/memory-embedder.js +5 -0
- package/dist/plugins/one-time-migrations.d.ts +180 -9
- package/dist/plugins/one-time-migrations.js +315 -3
- package/dist/plugins/pg-pool.js +3 -1
- package/dist/plugins/remote-env-e2b.d.ts +31 -9
- package/dist/plugins/remote-env-e2b.js +77 -26
- package/dist/plugins/remote-env-host.d.ts +17 -20
- package/dist/plugins/remote-env-host.js +20 -30
- package/dist/plugins/remote-env-k8s.d.ts +12 -4
- package/dist/plugins/remote-env-k8s.js +22 -31
- package/dist/plugins/session-policy-store-sql.d.ts +16 -4
- package/dist/plugins/session-policy-store-sql.js +17 -20
- package/dist/plugins/session-rules-column.d.ts +16 -0
- package/dist/plugins/session-rules-column.js +6 -0
- package/dist/plugins/tidb-pool.js +4 -1
- package/dist/read-face-posture.d.ts +2 -1
- package/dist/request-value-closure.js +1 -1
- package/dist/retired-card-words.d.ts +17 -0
- package/dist/retired-card-words.js +49 -0
- package/dist/rule-entry-list.js +2 -0
- package/dist/run-local.js +10 -3
- package/dist/server-secret-env.d.ts +77 -85
- package/dist/server-secret-env.js +40 -26
- package/dist/session-policy-wire.d.ts +61 -124
- package/dist/session-policy-wire.js +59 -121
- package/dist/session-sync.d.ts +8 -6
- package/dist/session-sync.js +6 -5
- package/dist/spec-fields.d.ts +3 -2
- package/dist/standing-answer-grant.d.ts +17 -9
- package/dist/standing-answer-grant.js +11 -8
- package/dist/task-settings.d.ts +6 -1
- package/dist/task-settings.js +1 -1
- package/dist/tool-approval.d.ts +14 -7
- package/dist/tool-approval.js +23 -18
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -0
- package/dist/trace/project.d.ts +7 -4
- package/dist/trace/project.js +6 -75
- package/dist/trace/tool-output-media.d.ts +2 -1
- package/dist/trace/tool-output-media.js +3 -25
- package/dist/web-admin-client/apply-effective.js +28 -11
- package/dist/web-admin-client/facade.d.ts +1 -1
- package/dist/web-admin-client/facade.js +1 -1
- package/dist/web-admin-client/read-face.d.ts +73 -20
- package/dist/web-admin-client/read-face.js +76 -36
- package/dist/web-admin-client/skills-mcp.d.ts +1 -1
- package/dist/web-admin-client/skills-mcp.js +1 -1
- package/dist/web-admin-client/types.d.ts +3 -3
- package/package.json +3 -3
- package/dist/mcp-tool-axes.d.ts +0 -62
- package/dist/mcp-tool-axes.js +0 -47
package/dist/http/admission.js
CHANGED
|
@@ -3,6 +3,7 @@ import { isAtMentionable, matchCatalogModel, refusedCatalogEntry, selectableCata
|
|
|
3
3
|
import { modelUnavailableMessage } from "../model-entry-refusal.js";
|
|
4
4
|
import { HttpError, sessionIdShapeIssue } from "../security.js";
|
|
5
5
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, PERMISSION_MODE_WORDS, parseTaskSettings, settingsPermissionBucketIssue, skillListingBudgetFractionOk, skillListingMaxDescOk, stampEntryPermissionMode, taskHooksHonored, taskSettingsKeyIssue } from "../task-settings.js";
|
|
6
|
+
import { buildEntryModeClampNotice } from "../entry-mode-clamp-notice.js";
|
|
6
7
|
import { autonomyModeFloor } from "../runtime-governance.js";
|
|
7
8
|
import { taskBodyKeyIssue } from "../request-key-closure.js";
|
|
8
9
|
import { FILE_HISTORY_DISABLED_NO_RESTORE, fileHistoryRestoreConflict, inheritEnvFieldIssue, singleMemberOffFieldIssue } from "../request-value-closure.js";
|
|
@@ -629,7 +630,7 @@ export function createAdmission(ctx) {
|
|
|
629
630
|
sendError(res, 400, "request.precondition_unmet", "cascade requested but no ladder configured — set MODEL_CASCADE_LADDER (catalog model names, cheapest→strongest)");
|
|
630
631
|
return null;
|
|
631
632
|
}
|
|
632
|
-
const admitted = stampEntryPermissionMode(body, autonomyModeFloor(deps.config.autonomy));
|
|
633
|
+
const { body: admitted, entry: entryMode } = stampEntryPermissionMode(body, autonomyModeFloor(deps.config.autonomy));
|
|
633
634
|
try {
|
|
634
635
|
const auth = deps.authorize ? await deps.authorize({ req, body: admitted }) : undefined;
|
|
635
636
|
const engineNotices = [];
|
|
@@ -644,7 +645,9 @@ export function createAdmission(ctx) {
|
|
|
644
645
|
});
|
|
645
646
|
const model = modelIdOf(spec.model);
|
|
646
647
|
const acceptance = createRunAcceptance({ sessionTitler: deps.sessionTitler }, { ...(spec.sessionId ? { sessionId: spec.sessionId } : {}), objective: spec.objective, ...(model !== undefined ? { model } : {}) });
|
|
647
|
-
|
|
648
|
+
const clampNotice = buildEntryModeClampNotice(entryMode, spec.sessionId);
|
|
649
|
+
const pendingNotices = clampNotice !== undefined ? [clampNotice, ...engineNotices] : engineNotices;
|
|
650
|
+
return { spec, runner: runnerFor(spec), auth, verify, cascade, jobId: admitted.jobId, body: admitted, acceptance, ...(bindingIntent === undefined ? {} : { bindingIntent }), ...(pendingNotices.length > 0 ? { engineNotices: pendingNotices } : {}) };
|
|
648
651
|
}
|
|
649
652
|
catch (err) {
|
|
650
653
|
if (err instanceof HttpError) {
|
package/dist/http/resume-legs.js
CHANGED
|
@@ -49,9 +49,8 @@ import { buildCheckpointVersionKeys, resumeBlockedByPolicyAnswer, storedBodyRepl
|
|
|
49
49
|
import { storedBodyReplayRefusal } from "./stored-body-replay.js";
|
|
50
50
|
import { httpErrorCode } from "./send.js";
|
|
51
51
|
import { createRunAcceptance } from "../run-acceptance.js";
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
return code === "resume.env_failed" || code === "resume.tool_unavailable" || code === "resume.tool_contract_mismatch";
|
|
52
|
+
function legMayHaveReopenedCheckpoint(r) {
|
|
53
|
+
return r.terminal.kind === "failed" && nestedPauseOf(r.terminal) === undefined;
|
|
55
54
|
}
|
|
56
55
|
function nestedParkStatusOf(r) {
|
|
57
56
|
const paused = nestedPauseOf(r.terminal);
|
|
@@ -476,6 +475,7 @@ export function createResumeLegs(ctx) {
|
|
|
476
475
|
async function driveResumeLeg(args, onAccepted) {
|
|
477
476
|
const resumeLegStartedAt = Date.now();
|
|
478
477
|
const { runner: legRunner, token, sessionId, principal, fleetScope, taskConfig, resumeObjective, outcome, verifyRounds, resumeCtx } = args;
|
|
478
|
+
const reparkStatus = outcome.gate === "plan_review" ? "needs_review" : "suspended";
|
|
479
479
|
const commitResumeCtx = () => deps.checkpointStore.putCtx(resumeCtx.sessionId, { ...resumeCtx.ctx, writerVersion: serviceVersion() }).catch((e) => {
|
|
480
480
|
deps.metrics?.inc("resume_ctx_not_persisted_total");
|
|
481
481
|
deps.logger?.warn?.("resume_ctx_not_persisted", { sessionId: resumeCtx.sessionId, gate: outcome.gate, error: errorText(e), note: "this continuation runs with the body it was given; the durable rebuild input still holds the PREVIOUS body, so the NEXT continuation of this session rebuilds from that one (never wider than this leg's)" });
|
|
@@ -562,6 +562,32 @@ export function createResumeLegs(ctx) {
|
|
|
562
562
|
return true;
|
|
563
563
|
}
|
|
564
564
|
};
|
|
565
|
+
const settleCardForCancel = async () => {
|
|
566
|
+
const cs2 = deps.checkpointStore;
|
|
567
|
+
if (!cs2)
|
|
568
|
+
return;
|
|
569
|
+
try {
|
|
570
|
+
const live = await cs2.get(token);
|
|
571
|
+
if (live?.status === "pending" && !(await cs2.expire(token, live.scope))) {
|
|
572
|
+
deps.logger?.warn?.("resume_cancel_checkpoint_settle_lost", { taskId, sessionId });
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
catch (cancelSettleErr) {
|
|
576
|
+
deps.logger?.warn?.("resume_cancel_checkpoint_settle_failed", {
|
|
577
|
+
taskId, sessionId, err: cancelSettleErr instanceof Error ? cancelSettleErr.message : String(cancelSettleErr),
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
const reopenedAfterLeg = async (leg, cancelledAtDone) => {
|
|
582
|
+
if (freshRunClaimed || !legMayHaveReopenedCheckpoint(leg))
|
|
583
|
+
return false;
|
|
584
|
+
const pending = cancelledAtDone ? false : await confirmCheckpointReopened();
|
|
585
|
+
if (cancelledAtDone || cancelCtrl.cause() !== undefined) {
|
|
586
|
+
await settleCardForCancel();
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
return pending;
|
|
590
|
+
};
|
|
565
591
|
let resumeHeartbeat;
|
|
566
592
|
let resumeManifestLease = 0;
|
|
567
593
|
let resumeStreamRef;
|
|
@@ -685,7 +711,7 @@ export function createResumeLegs(ctx) {
|
|
|
685
711
|
const vrNested = nestedPauseOf(vr.terminal);
|
|
686
712
|
const reParkStatus = nestedParkStatusOf(vr);
|
|
687
713
|
const reSuspended = vrNested !== undefined;
|
|
688
|
-
const vrReopened =
|
|
714
|
+
const vrReopened = await reopenedAfterLeg(vr, vrCause !== undefined);
|
|
689
715
|
if (vrReopened)
|
|
690
716
|
reopenConfirmedPark = true;
|
|
691
717
|
if (taskId && deps.runStore) {
|
|
@@ -696,7 +722,7 @@ export function createResumeLegs(ctx) {
|
|
|
696
722
|
if (reSuspended || vrReopened) {
|
|
697
723
|
await appendModelUsageDelta(ap, deps.modelUsage, taskId);
|
|
698
724
|
const vrParkCallId = vrNested !== undefined ? await parkToolCallId({ terminal: vrNested }, deps.checkpointStore) : undefined;
|
|
699
|
-
await ap(reParkStatus ??
|
|
725
|
+
await ap(reParkStatus ?? reparkStatus, { gate: vrNested?.gate ?? null, ...(vrParkCallId !== undefined ? { toolCallId: vrParkCallId } : {}), ...(vrReopened ? { reopened: failureCodeOf(vr.terminal) ?? null } : {}) });
|
|
700
726
|
}
|
|
701
727
|
else {
|
|
702
728
|
safe = await attachModelUsage(safe, { append: ap, getEvents: (id, a) => rs2.getEvents(id, a), modelUsage: deps.modelUsage, taskId });
|
|
@@ -819,8 +845,12 @@ export function createResumeLegs(ctx) {
|
|
|
819
845
|
resumeStreamRef = stream;
|
|
820
846
|
if (taskId)
|
|
821
847
|
steerableRuns.set(taskId, stream);
|
|
822
|
-
if (!taskId || !rs)
|
|
823
|
-
|
|
848
|
+
if (!taskId || !rs) {
|
|
849
|
+
const rNoRow = await stream.result();
|
|
850
|
+
if (await reopenedAfterLeg(rNoRow, cancelCtrl.cause() !== undefined))
|
|
851
|
+
reopenConfirmedPark = true;
|
|
852
|
+
return sealResultForExit(rNoRow);
|
|
853
|
+
}
|
|
824
854
|
onAccepted?.(acceptedResumingReceipt(taskId, sessionId));
|
|
825
855
|
let causeAtDone;
|
|
826
856
|
for await (const ev of stream) {
|
|
@@ -951,10 +981,10 @@ export function createResumeLegs(ctx) {
|
|
|
951
981
|
await appendModelUsageDelta(append, deps.modelUsage, taskId);
|
|
952
982
|
return r;
|
|
953
983
|
}
|
|
954
|
-
if (
|
|
984
|
+
if (await reopenedAfterLeg(r, causeAtDone !== undefined)) {
|
|
955
985
|
reopenConfirmedPark = true;
|
|
956
986
|
try {
|
|
957
|
-
await append(
|
|
987
|
+
await append(reparkStatus, { gate: null, reopened: failureCodeOf(r.terminal) ?? null });
|
|
958
988
|
await appendModelUsageDelta(append, deps.modelUsage, taskId);
|
|
959
989
|
}
|
|
960
990
|
catch {
|
|
@@ -1000,20 +1030,7 @@ export function createResumeLegs(ctx) {
|
|
|
1000
1030
|
const refusalCause = e.code === "checkpoint.invalid_outcome" ? decisionRefusalCauseOf(e, outcome.gate) : undefined;
|
|
1001
1031
|
const disposition = resumeRefusalDisposition(e);
|
|
1002
1032
|
if (disposition === "cancelled") {
|
|
1003
|
-
|
|
1004
|
-
if (cs2) {
|
|
1005
|
-
try {
|
|
1006
|
-
const live = await cs2.get(token);
|
|
1007
|
-
if (live?.status === "pending" && !(await cs2.expire(token, live.scope))) {
|
|
1008
|
-
deps.logger?.warn?.("resume_cancel_checkpoint_settle_lost", { taskId, sessionId });
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
catch (cancelSettleErr) {
|
|
1012
|
-
deps.logger?.warn?.("resume_cancel_checkpoint_settle_failed", {
|
|
1013
|
-
taskId, sessionId, err: cancelSettleErr instanceof Error ? cancelSettleErr.message : String(cancelSettleErr),
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1033
|
+
await settleCardForCancel();
|
|
1017
1034
|
if (claimedRow && taskId && deps.runStore) {
|
|
1018
1035
|
const handleCause = cancelCtrl.cause();
|
|
1019
1036
|
if (handleCause === undefined)
|
|
@@ -1035,8 +1052,8 @@ export function createResumeLegs(ctx) {
|
|
|
1035
1052
|
settleFleet("failed");
|
|
1036
1053
|
}
|
|
1037
1054
|
else if (retriable) {
|
|
1038
|
-
await (
|
|
1039
|
-
settleFleet(
|
|
1055
|
+
await (reparkStatus === "needs_review" ? deps.runStore.setNeedsReview(taskId, null) : deps.runStore.setSuspended(taskId, null)).catch(() => undefined);
|
|
1056
|
+
settleFleet(reparkStatus);
|
|
1040
1057
|
}
|
|
1041
1058
|
else {
|
|
1042
1059
|
await deps.runStore.setTerminal(taskId, "failed", null, e.message).catch(() => undefined);
|
|
@@ -1099,14 +1116,16 @@ export function createResumeLegs(ctx) {
|
|
|
1099
1116
|
const sealed = sealResultForExit(result);
|
|
1100
1117
|
const resumePlane = outwardPlane(sealed.terminal);
|
|
1101
1118
|
if (taskId && deps.runStore) {
|
|
1102
|
-
if (
|
|
1119
|
+
if (checkpointReopened && reparkStatus === "needs_review")
|
|
1120
|
+
await deps.runStore.setNeedsReview(taskId, null);
|
|
1121
|
+
else if (resumePlane.status === "suspended" || checkpointReopened)
|
|
1103
1122
|
await deps.runStore.setSuspended(taskId, sealed.terminal.kind === "paused" ? sealed : null);
|
|
1104
1123
|
else if (resumePlane.status === "needs_review")
|
|
1105
1124
|
await deps.runStore.setNeedsReview(taskId, sealed);
|
|
1106
1125
|
else
|
|
1107
1126
|
await deps.runStore.setTerminal(taskId, resumePlane.status, sealed, resumePlane.errorMessage ?? null);
|
|
1108
1127
|
}
|
|
1109
|
-
settleFleet(checkpointReopened ?
|
|
1128
|
+
settleFleet(checkpointReopened ? reparkStatus : resumePlane.status, fleetRunResiduals(sealed));
|
|
1110
1129
|
return {
|
|
1111
1130
|
status: 200,
|
|
1112
1131
|
decisionCommitted: true,
|
package/dist/http/route-ctx.d.ts
CHANGED
|
@@ -128,7 +128,7 @@ export interface PreparedTaskSubmission {
|
|
|
128
128
|
cascade?: boolean;
|
|
129
129
|
jobId?: string;
|
|
130
130
|
body: TaskRequestBody;
|
|
131
|
-
/** L-167:**装配期**铸下的、这条会话的用户面 `EngineNotice`(
|
|
131
|
+
/** L-167:**装配期**铸下的、这条会话的用户面 `EngineNotice`(今天两族:请求腿 MCP 注入被丢弃;入口地板钳制 `permission.entry_mode_clamped`)。
|
|
132
132
|
* 装配跑在这条腿的通告口开张之前,所以铸点交不出去 —— 由腿在 `registerEngineNoticeLeg` 那一拍
|
|
133
133
|
* 经 `pending` 一起投(投递单点仍是那一只注册助手,不新开第二条机制)。缺席 = 本次装配没铸下任何通告。 */
|
|
134
134
|
engineNotices?: readonly EngineNotice[];
|
|
@@ -18,7 +18,7 @@ import { sendJson, sendError, sseHeaders, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_
|
|
|
18
18
|
import { gatedPrincipal, explicitOperatorOk, isOperator, sendPrincipalRefusal } from "../principal-gate.js";
|
|
19
19
|
import { governanceOriginOf } from "../../runtime-governance.js";
|
|
20
20
|
import { deploymentListAsks } from "../../approval.js";
|
|
21
|
-
import {
|
|
21
|
+
import { callGrantBindingOf, questionGrantBindingOf } from "../../standing-answer-grant.js";
|
|
22
22
|
import { sendResumeOutcome } from "../route-ctx.js";
|
|
23
23
|
import { assertNever } from "../route-ctx.js";
|
|
24
24
|
import * as facts from "../../capability-facts.js";
|
|
@@ -321,7 +321,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
321
321
|
return;
|
|
322
322
|
}
|
|
323
323
|
const rows = await deps.approvalExemptionStore.list(exSessionId);
|
|
324
|
-
sendJson(res, 200, { exemptions: rows.filter((r) =>
|
|
324
|
+
sendJson(res, 200, { exemptions: rows.filter((r) => callGrantBindingOf({ toolName: r.toolName }).kind === "none") });
|
|
325
325
|
return;
|
|
326
326
|
}
|
|
327
327
|
if (exToolName === undefined) {
|
|
@@ -360,6 +360,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
360
360
|
...(s.boundGrantDigest !== undefined ? { boundGrantDigest: s.boundGrantDigest } : {}),
|
|
361
361
|
...(s.boundGrants !== undefined ? { boundGrants: s.boundGrants } : {}),
|
|
362
362
|
...(s.classifierNoRuling !== undefined ? { classifierNoRuling: s.classifierNoRuling } : {}),
|
|
363
|
+
...(s.boundGrantAbsence !== undefined ? { boundGrantAbsence: s.boundGrantAbsence } : {}),
|
|
363
364
|
objective,
|
|
364
365
|
input,
|
|
365
366
|
};
|
|
@@ -840,13 +841,14 @@ function projectPendingForWire(row, deployment) {
|
|
|
840
841
|
const governance = deployment;
|
|
841
842
|
const redacted = redactPendingDisclosures(row);
|
|
842
843
|
const governanceForced = governanceOriginOf({ riskDescriptor: row.riskDescriptor ?? undefined }, governance);
|
|
844
|
+
const deploymentListQuestion = carriesDeploymentListQuestion(row.toolName !== null && deployment !== undefined && deploymentListAsks(deployment, { toolName: row.toolName }), row.classes);
|
|
843
845
|
const rememberRefused = !standingAnswerDurablyRecordable({
|
|
844
846
|
governanceForced: governanceForced === true,
|
|
845
847
|
requiresRealApproval: row.requiresRealApproval === true,
|
|
846
848
|
mandated: row.mandated === true || row.ruleOffersAbsence === "mandated",
|
|
847
|
-
grantBinding:
|
|
849
|
+
grantBinding: questionGrantBindingOf(row, deploymentListQuestion),
|
|
848
850
|
classes: row.classes,
|
|
849
|
-
deploymentListQuestion
|
|
851
|
+
deploymentListQuestion,
|
|
850
852
|
});
|
|
851
853
|
return { ...redacted, ...(governanceForced ? { governanceForced } : {}), ...(rememberRefused ? { rememberRefused: true } : {}) };
|
|
852
854
|
}
|
|
@@ -73,6 +73,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
73
73
|
const rulesLaneForCaller = facts.ruleLaneForCaller(deps, gatedPrincipal(req, deps.config)).available;
|
|
74
74
|
sendJson(res, 200, gateCompositeMembers({
|
|
75
75
|
...deps.capabilities,
|
|
76
|
+
...facts.engineCapabilityBits(),
|
|
76
77
|
asyncRuns: facts.durableRuns(deps).available,
|
|
77
78
|
artifacts: facts.durableRuns(deps).available,
|
|
78
79
|
approvals: facts.checkpointPark(deps).available,
|
|
@@ -14,7 +14,7 @@ const SYNC_NDJSON_LINE_MAX_BYTES = 8 * 1024 * 1024;
|
|
|
14
14
|
const SYNC_IMPORT_BATCH = 500;
|
|
15
15
|
import { SHA256_HEX_RE } from "../../digest-form.js";
|
|
16
16
|
import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
|
|
17
|
-
import { readSessionRulesRecords, describeSessionRulesRecordProblem
|
|
17
|
+
import { readSessionRulesRecords, describeSessionRulesRecordProblem } from "../../session-policy-wire.js";
|
|
18
18
|
import { readSessionPinned, readSessionTitle } from "../../session-row.js";
|
|
19
19
|
export function createSessionSyncLocal() {
|
|
20
20
|
const pendingStagings = new Map();
|
|
@@ -232,9 +232,8 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
232
232
|
}
|
|
233
233
|
const policyRead = readSessionRulesRecords(body.policy);
|
|
234
234
|
if (policyRead.problem !== undefined) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
sendError(res, 400, "rules.legacy_tool_name", `policy: ${describeSessionRulesRecordProblem(policyRead.problem)}`, { ...legacy });
|
|
235
|
+
if (policyRead.problem.kind === "rules") {
|
|
236
|
+
sendSessionPolicyError(res, policyRead.problem.refusal, `policy: [${policyRead.problem.index}].rules: `);
|
|
238
237
|
return;
|
|
239
238
|
}
|
|
240
239
|
sendError(res, 400, "request.field_invalid", `policy: ${describeSessionRulesRecordProblem(policyRead.problem)}`);
|
|
@@ -7,7 +7,7 @@ import { recordFailOpen } from "../../observability/fail-open.js";
|
|
|
7
7
|
import { MCP_DIAL_DEADLINE_MS, raceDeadline } from "../mcp-dial-deadline.js";
|
|
8
8
|
import { sentMemoryComplianceFailure } from "../memory-compliance-failure-reply.js";
|
|
9
9
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
|
|
10
|
-
import { readWritableSessionPermissionRules
|
|
10
|
+
import { readWritableSessionPermissionRules } from "../../session-policy-wire.js";
|
|
11
11
|
import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeaders } from "../send.js";
|
|
12
12
|
import { gatedPrincipal, explicitOperatorOk, sessionOwnerReadable, sendPrincipalRefusal, sendUnauthorized } from "../principal-gate.js";
|
|
13
13
|
import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
|
|
@@ -819,22 +819,9 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
819
819
|
return;
|
|
820
820
|
}
|
|
821
821
|
const read = readWritableSessionPermissionRules(body.rules);
|
|
822
|
-
if (read.
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
sendError(res, 400, "request.body_shape", describeSessionPermissionRulesProblem(read.problem));
|
|
826
|
-
return;
|
|
827
|
-
case "field-not-rule-list":
|
|
828
|
-
sendError(res, 400, "request.field_invalid", describeSessionPermissionRulesProblem(read.problem));
|
|
829
|
-
return;
|
|
830
|
-
case "legacy-tool-name":
|
|
831
|
-
sendError(res, 400, "rules.legacy_tool_name", describeSessionPermissionRulesProblem(read.problem), { ...read.problem.refusal });
|
|
832
|
-
return;
|
|
833
|
-
default: {
|
|
834
|
-
const unhandled = read.problem;
|
|
835
|
-
throw new Error(`unhandled SessionPermissionRules problem: ${JSON.stringify(unhandled)}`);
|
|
836
|
-
}
|
|
837
|
-
}
|
|
822
|
+
if (read.refusal !== undefined) {
|
|
823
|
+
sendSessionPolicyError(res, read.refusal);
|
|
824
|
+
return;
|
|
838
825
|
}
|
|
839
826
|
if (body.expectedRev !== undefined && (typeof body.expectedRev !== "number" || !Number.isInteger(body.expectedRev) || body.expectedRev < 0)) {
|
|
840
827
|
sendError(res, 400, "request.field_invalid", "expectedRev must be a non-negative integer");
|
package/dist/http/send.d.ts
CHANGED
|
@@ -84,7 +84,7 @@ export declare function httpErrorCode(status: number, code?: string): string;
|
|
|
84
84
|
* 域外通用臂 ⇒ `500 internal.error "internal error"`:码丢,连带**修复路径**也丢(同一行 `PUT` 却答
|
|
85
85
|
* `500 corrupt` 并点名那一列)。一只腿之后,新站点只会忘记调它,不会再各自发明一份映射。
|
|
86
86
|
*/
|
|
87
|
-
export declare function sendSessionPolicyError(res: ServerResponse, e: unknown): boolean;
|
|
87
|
+
export declare function sendSessionPolicyError(res: ServerResponse, e: unknown, sentencePrefix?: string): boolean;
|
|
88
88
|
/**
|
|
89
89
|
* S-612 —— core `SessionError` → 应答,经 {@link sessionErrorHttp} 那**一只**映射(`src/session-error-wire.ts`)。
|
|
90
90
|
* 回 `true` = 已应答,调用点 `return`;`false` = 不是 `SessionError`,或这个词在这一面上是登记过的留白(站点的既有
|
package/dist/http/send.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SessionError, SessionPolicyError } from "@sema-agent/core";
|
|
2
|
-
import { sessionPolicyErrorHttp } from "../session-policy-wire.js";
|
|
2
|
+
import { sessionPolicyErrorFacts, sessionPolicyErrorHttp, sessionPolicyErrorSentence } from "../session-policy-wire.js";
|
|
3
3
|
import { sessionErrorHttp } from "../session-error-wire.js";
|
|
4
4
|
import { redactErrorMessage } from "../observability/run-terminal.js";
|
|
5
5
|
export const SSE_RESPONSE_MARK = Symbol("sema.http.sse-response");
|
|
@@ -48,11 +48,12 @@ export function httpErrorCode(status, code) {
|
|
|
48
48
|
default: return status >= 500 ? "internal.error" : "request.rejected";
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
export function sendSessionPolicyError(res, e) {
|
|
51
|
+
export function sendSessionPolicyError(res, e, sentencePrefix = "") {
|
|
52
52
|
if (!(e instanceof SessionPolicyError))
|
|
53
53
|
return false;
|
|
54
54
|
const h = sessionPolicyErrorHttp(e.code);
|
|
55
|
-
|
|
55
|
+
const facts = sessionPolicyErrorFacts(e);
|
|
56
|
+
sendError(res, h.status, h.errorCode, redactErrorMessage(`${sentencePrefix}${sessionPolicyErrorSentence(e)}`) ?? "session policy error", facts === undefined ? undefined : { ...facts });
|
|
56
57
|
return true;
|
|
57
58
|
}
|
|
58
59
|
export function sendSessionError(res, e, face) {
|
|
@@ -312,7 +312,8 @@ export interface TaskRequestBody {
|
|
|
312
312
|
excludeTools?: string[];
|
|
313
313
|
/** S-1448(core 7.49.0 #1623,[ref]):引擎的**可用性选择器** —— 这一 run 只用列出的工具(wire 名、别名、`Tool(content)` 整只、
|
|
314
314
|
* `mcp__srv` / `mcp__srv__*` / 工具段通配;`"*"` = 本层不设名单;`[]` = 什么都不给)。宿主**原样透传**进 `TaskSpec.allowTools`:
|
|
315
|
-
* 不算补集、不过滤、不与 `settings.permissions.allow`(免问)
|
|
315
|
+
* 不算补集、不过滤、不与 `settings.permissions.allow`(免问)混词;没有任何工具答应的名字照跑(原样保留,今天什么都不准,之后挂上同名工具即生效;引擎告知运维一次),
|
|
316
|
+
* 任何工具名都不可能有的拼法由引擎开跑前拒(`config.tool_face_control`,同步腿 400)。
|
|
316
317
|
* `excludeTools` 是同一只运算的减项(deny 赢)。值形:字符串数组(受理门 400 `request.field_invalid`,与 `excludeTools` 同一只门);
|
|
317
318
|
* 装着的引擎不认该座 ⇒ 400 `request.field_invalid` 点名所需引擎版本(不静默丢)。无租户门(收窄)。 */
|
|
318
319
|
allowTools?: string[];
|
|
@@ -415,7 +416,7 @@ export interface TaskRequestBody {
|
|
|
415
416
|
/**
|
|
416
417
|
* S-895(7.106.0,core [ref] [ref] ②)—— 模型驱动的 host shell **继承多少用户环境**,由调用方按请求声明。
|
|
417
418
|
* 闭集两词(词表外 ⇒ 400 `request.field_invalid`,两腿同判,判据属主 `request-value-closure.ts` 的 `inheritEnvFieldIssue`):
|
|
418
|
-
* · `"all"` ——
|
|
419
|
+
* · `"all"` —— 整份用户环境**减本服务自有凭据名**(配置目录 secret 名 / 名单型旋钮名 / 模型条目 `apiKeyEnv`,起服向引擎声明一次,判在引擎;7.129.0 起,CC 交互形);只在**单用户**部署兑现,射程 = 写容纳面那一只
|
|
419
420
|
* `hostPostureDeclarationsHonored`(`hosted-posture.ts`)⇒ 托管部署(`REQUIRE_PRINCIPAL=true`)响亮拒
|
|
420
421
|
* 422 `request.host_posture_refused`;
|
|
421
422
|
* · `"scrub"` —— 缺省清洗(剥凭据形变量),**任何车道、任何部署**都受理(它就是缺省;7.106.0 合并复审 X7)。
|
package/dist/index.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export { createAuthorizer, principalFrom, HttpError, type AuthContext, type Owne
|
|
|
24
24
|
export { memoryScopeFor, memoryEngineBackendFor } from "./memory-scope.js";
|
|
25
25
|
export type { TaskRequestBody } from "./http/wire-types.js";
|
|
26
26
|
export { createHttpServer, type ServiceDeps, type RequestAuth, type FlatServiceDeps, type ServiceDepGroups, type ServiceCoreDeps, type ServiceStoreDeps, type ServiceCoordinatorDeps, type ServiceSeamDeps, type ServiceObservabilityDeps, type ServiceGovernanceDeps, type ServiceDeploymentDeps, type ServiceKnobDeps, } from "./http/server.js";
|
|
27
|
-
export { createSideQueryLane,
|
|
27
|
+
export { createSideQueryLane, type KeyResolver, type SideQueryLane, type SideQueryLaneCtx } from "./boot/side-query-lane.js";
|
|
28
28
|
export { createKeyResolver, resolveModelApiKey, type ModelKeyRef } from "./key-resolver.js";
|
|
29
29
|
export { isModelKeyPoison, ModelKeyPoisonedError, type ModelKeyPoison, type ModelKeyPoisonReason } from "./key-resolver.js";
|
|
30
30
|
export { CATALOG_ENTRY_HEADER_LANES, CATALOG_ENTRY_HEADER_NAMES, type CatalogEntryHeaderName } from "./model-entry-headers.js";
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ export { ensureTiDBTaskListSchema, ensurePgTaskListSchema } from "./plugins/task
|
|
|
21
21
|
export { createAuthorizer, principalFrom, HttpError, } from "./security.js";
|
|
22
22
|
export { memoryScopeFor, memoryEngineBackendFor } from "./memory-scope.js";
|
|
23
23
|
export { createHttpServer, } from "./http/server.js";
|
|
24
|
-
export { createSideQueryLane
|
|
24
|
+
export { createSideQueryLane } from "./boot/side-query-lane.js";
|
|
25
25
|
export { createKeyResolver, resolveModelApiKey } from "./key-resolver.js";
|
|
26
26
|
export { isModelKeyPoison, ModelKeyPoisonedError } from "./key-resolver.js";
|
|
27
27
|
export { CATALOG_ENTRY_HEADER_LANES, CATALOG_ENTRY_HEADER_NAMES } from "./model-entry-headers.js";
|
|
@@ -43,9 +43,9 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
43
43
|
readonly cls: "F";
|
|
44
44
|
readonly note: "core 7.50.0 媒体块提货(`trace/tool-output-media.ts` 的 `liveToolEndForBacklog`,三处活面写口共用的那一只判据 —— 同步流出口 `sseData`、子代 tail 路由写环、tail 总线每订阅者队列):本帧媒体字节(`output` 媒体块的 `data` 与 `structured` 媒体卡的 `file.base64` 一起计)+ 这条连接 / 这个订阅者还没送出去的字节 > 上界(64 MiB)⇒ 本帧媒体降成持久面那只形(媒体块换描述符 `{ type, mimeType?, bytes, truncated: true }`,帧级 `truncated` + `totalChars`;媒体卡去掉 `file.base64`)。每降一帧记一次,只在这只函数里记(三处写口不各记)。放行的最坏后果 = 一个读得慢的观众在 SSE 上看不到这张图,只见「这里有一张多大的什么图、没送达」(账本面本就恒是描述符),不是门、不是凭据、不改任何裁决;反方向(不降)让一条停读的连接随帧数线性压住几百 MiB。帧上它与引擎自己的媒体预算降级同形不可分,所以计数是运维唯一能看出「多少观众丢了图」的位;计数持续非零 = 有消费端读流太慢(或网络瓶颈),去看那几条连接。detail = `backlog=<待发字节> media=<本帧媒体字节>`。";
|
|
45
45
|
};
|
|
46
|
-
readonly "server.memory-consolidation.operator-valve-
|
|
46
|
+
readonly "server.memory-consolidation.operator-valve-layers-unfed": {
|
|
47
47
|
readonly cls: "P-DEBT";
|
|
48
|
-
readonly note: "core 7.
|
|
48
|
+
readonly note: "core 7.51.0 #1718 提货(`boot/memory-consolidation.ts` `createOperatorValveWriteJudge`):运维手动整理阀门的写判官座已是引擎的那一只构造器,但三层没喂 —— 人的持久规则层(阀门没有「库属于谁」的来源,无 principal / 不声明 local-owner)、部署的调用方策略层(`APPROVAL_REQUIRE` / `APPROVAL_DENY` / `AUTONOMY` / 敏感路径守卫集是每任务现合成的活配置、无部署级单例;敏感路径层另需引擎豁免根入参,宿主构造器输入没有这一位)、项目根(项目层 scope 键是任务根的单向散列,反推不出)。每被问一次(早问库目录一次 + 每个产物一次)记一次。放行的最坏后果 = 运维手动整理时,一条点名记忆库路径的人的持久规则或部署写策略(例如 `APPROVAL_DENY=Write`)拦不住这一轮折叠(与 7.128.x 逐字同;只是判官不再是代答);自动整理腿走引擎腿自己的判官,不在本 tag 射程。收口 = 三层各自有了来源(人:「读谁的桶」裁定;策略:一份设计 + 向 core 请托引擎豁免根入参;根:无来源则永久缺席并改写本债)那一版,本 tag 删。";
|
|
49
49
|
};
|
|
50
50
|
readonly "server.workflow-park-index.sync-failed": {
|
|
51
51
|
readonly cls: "P-DEBT";
|
|
@@ -261,7 +261,7 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
261
261
|
};
|
|
262
262
|
readonly "server.resume.reopen-confirm-unreadable": {
|
|
263
263
|
readonly cls: "F";
|
|
264
|
-
readonly note: "S-447(#60 M2 自查项,`http/resume-legs.ts` 的 `confirmCheckpointReopened`)
|
|
264
|
+
readonly note: "S-447(#60 M2 自查项,`http/resume-legs.ts` 的 `confirmCheckpointReopened`):续跑腿交回一条没 park 的失败结果后(7.129.0 起每一条都问,不再只在三个重开码上问)按 **token** 复核「这张 checkpoint 是不是又 pending 了」,这一读抛(库拖/版本太新/店不可达)。**方向本来就是 fail-closed**:读不出 ⇒ `return true` ⇒ 「不写任何终局、重新 park」(`:1206-1212` 头注已把取舍与有界代价写死:错 park 由 `reapSuspended` 兑,错终局会孤儿化一张活 park 到 TTL)—— 所以这条腿不是 P 类,缺的只是**留痕**。放行的最坏后果 = 多一次多余的 re-park(一张已经被决掉的卡重新挂到 reaper 窗里),人面上是「这条续跑没动」而不是谁被放行了。必须登记的理由是 **uniformity**:同文件三只同形读(`:249` `server.approvals.stale-echoed-token-unreadable` · `:288` `server.approvals.stale-current-pending-unreadable` · 本腿)中只有它是黑的,于是「店一直读不出所以每条续跑都在白白 re-park」与「一切正常」在遥测上同形。计数非零 = 这台机器的 checkpoint 店在拖,运维要去看库。";
|
|
265
265
|
};
|
|
266
266
|
readonly "server.approvals.stale-current-pending-unreadable": {
|
|
267
267
|
readonly cls: "F";
|
|
@@ -327,6 +327,10 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
327
327
|
readonly cls: "F";
|
|
328
328
|
readonly note: "S-249(`approval-card.ts` 的边界窄读族;今天四个成员:`readRuleOffersAbsence`、S-934 ② 起的 `readAskClassPair`(core 7.37.0 #1214 的类对 `askClass` / `classes`,活卡帧 / 呈卡帧的卡 / 运维队列行同一只,判法 = core 屏 `screenAskClassMembers`(S-955 ⑥ / core 7.38.0 #1249,本仓零自写对判),两键同在同缺,半对 / 集外 / 首员不符 / 重复员 = 屏拒 = 读不出;detail 前缀 `askClass=` / `classes=`),与 S-721 起的 `readClosedWordSeat` —— 一座一只 core 成员筛,座是参数:`mandate`(core 7.32.0 #1092,活卡帧 / 活挂起行 / 运维队列行同一只,闭集筛 = core `isPersistedRuleMandate`)与 S-755 起的 `origin`(运维队列行读 park 行孪生座,闭集筛 = core `isAskOrigin`,与 inbox 行 core 投影处同一只筛)、S-1170 起的 `classifierNoRuling`(core 7.43.0 #1353 的成因词,活卡帧读 `AskRequest.classifierNoRuling`,闭集筛 = core `isAutoModeFallbackCause`)与 `differs`(core 7.44.0 #1374,载体是 `runs.ts` 的 409 `steering.duplicate_input_id` 体读的引擎拒错 `detail.differs`,闭集筛 = core `STEER_REPLAY_FACETS` 的非空子集;集外成员 / 空 / 非数组 ⇒ 不铸 `detail`,码仍是契约);detail 前缀 `ruleOffersAbsence=` / `mandate=` / `origin=` / `classifierNoRuling=` / `differs=` 判别是哪一座;第四个成员是 `approval-content-kind.ts` 的 `readApprovalCard` —— 卡词 `approvalCard` 自己的闭集窄读,活 ask / LOCAL 店停驻门 / SQL 店读出的门子树三个载体共用一只,闭集 = core `TOOL_APPROVAL_CARDS`,detail 前缀 `approvalCard=`):活卡帧 / `card_json` / (S-700 起)运维队列行(`parkRowFacts` 读寄存行上的 park 孪生座;队列是热读面 ⇒ 同一行每读一次计一次)的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40);数 / 布尔等原始值原样,对象 / 数组 / 函数 / null 只报形名(`<object>` / `<array>` / `<function>` / `<null>`,S-700 codex r1:格式化从不调用值自己的转换方法 —— 持久层里一格 `{\"toString\":null}` 曾让诊断本身抛出、打掉整只队列)。";
|
|
329
329
|
};
|
|
330
|
+
readonly "server.approval-card.retired-card-word-read": {
|
|
331
|
+
readonly cls: "F";
|
|
332
|
+
readonly note: "S-1562(core 7.51.0 #1515 / #1702;一次性迁移 M18 的**读侧半场**,`approval-card.ts` 的 `readStoredCardEnvelope` / `currentParkArm`,窗与删法见 `plugins/one-time-migrations.ts` 登记表 M18 —— 窗关随 M18 删本 tag):存下的审批卡(`approval_ask.card_json`)或停驻行(`pendingAction` 的 `tool_approval` 臂)上带着引擎 7.51.0 退役的词 —— `ruleOffersAbsence: \"shadowed\"` 或类对里的 `governance_unavailable` —— 读时按 M18 那一只改写读(前者删键 = 合法缺席,后者换成 `rule_unreadable`、类集去重按引擎序)。每改写一次记一次;detail = 载体(`card_json` / `parked row`)与座名,不带值。放行的最坏后果 = 无:这一读与存卡的起服改写对同一份字节给同一个答案(一只函数);它不是兜底,它登记在这里是为了**响亮** —— 载体 `card_json` 计数非零 = 有起服改写够不着的卡(滚动升级窗里 7.128.x 副本新写的卡、local 文件 ask 账);载体 `parked row` 计数非零 = 读到了带旧词的停驻行(引擎写的文档,本服务从不改写它 —— 行摘要覆盖类对 `askClass` / `classes`,改写 = 续跑判 `damaged`;所以这一格在窗内随每次读到那样的行而涨,直到那些行被决掉 / 收割),运维据它判断旧副本是否都已换下、窗关删码前是否还有这种行在飞。反方向(不改写)= 修前:整张存卡读不出(断连重放跳过、幂等重入走 park、回决口按要人)、停驻行的 `shadowed` 读成「读不出 ⇒ 按强制」(拿不到「本会话放行」)。";
|
|
333
|
+
};
|
|
330
334
|
readonly "server.pg-pool.idle-client-error": {
|
|
331
335
|
readonly cls: "F";
|
|
332
336
|
readonly note: "S-55(test [5864] 独立复现):pg 连接池里一条连接被外部终止(pg_terminate_backend / 云端故障切换 / LB 空闲回收)或自身网络错误。**两态一 tag,detail 判别**(`state=idle` / `state=checked-out`;先例=park 墓碑行的两臂 detail 判别):①idle 态 —— node-postgres 语义(pg-pool@3.14.0 makeIdleListener):池在 emit 'error' **之前**已 `_remove` 掉该 client,坏连接自然淘汰,下次 acquire 发新连接,在飞查询零影响;修前 Pool 上无 'error' 监听 ⇒ EventEmitter 把事件转 throw ⇒ uncaughtException ⇒ 整进程 exit 1。②checked-out 态(codex R1-[high] 补,同批修)—— pg-pool 借出时摘 idle 监听(index.js),`pool.connect()` 显式持有的连接(事务)在 checkout 期死时 client 直接 emit 'error' 同样打死进程;守卫只观察:中毒 client 的后续查询由 pg 以 `_queryable=false` 响亮拒,归还时池淘汰;同一次 checkout 的死可能双发事件(FATAL 消息 + socket end),warn 逐枚、计数按 checkout 去重。放行的最坏后果 = 下一次 acquire 多付一次建连,故 F 类。必须留痕:「连接不断被外部杀」(故障切换风暴 / 回收策略过激 / 有人在库上清连接)与「一切正常」在服务面同形;每次事件另有逐次结构化 warn(`pg_pool_idle_client_error` / `pg_pool_checked_out_client_error`,含 code/severity),本计数让频率曲线可读。`pool.query()` 快路不在洞内(pg-pool 自带 checkout 期 once('error') 守卫)。mysql2 孪生(tidb-pool)**不同病**:PoolConnection 构造器恒挂 once('error') 库内吞并淘汰、借出期同在(坐标成文在 createTidbPool 头注),故无同族 tag。";
|
|
@@ -24,9 +24,9 @@ export const FAIL_OPEN_TAGS = {
|
|
|
24
24
|
cls: "F",
|
|
25
25
|
note: "core 7.50.0 媒体块提货(`trace/tool-output-media.ts` 的 `liveToolEndForBacklog`,三处活面写口共用的那一只判据 —— 同步流出口 `sseData`、子代 tail 路由写环、tail 总线每订阅者队列):本帧媒体字节(`output` 媒体块的 `data` 与 `structured` 媒体卡的 `file.base64` 一起计)+ 这条连接 / 这个订阅者还没送出去的字节 > 上界(64 MiB)⇒ 本帧媒体降成持久面那只形(媒体块换描述符 `{ type, mimeType?, bytes, truncated: true }`,帧级 `truncated` + `totalChars`;媒体卡去掉 `file.base64`)。每降一帧记一次,只在这只函数里记(三处写口不各记)。放行的最坏后果 = 一个读得慢的观众在 SSE 上看不到这张图,只见「这里有一张多大的什么图、没送达」(账本面本就恒是描述符),不是门、不是凭据、不改任何裁决;反方向(不降)让一条停读的连接随帧数线性压住几百 MiB。帧上它与引擎自己的媒体预算降级同形不可分,所以计数是运维唯一能看出「多少观众丢了图」的位;计数持续非零 = 有消费端读流太慢(或网络瓶颈),去看那几条连接。detail = `backlog=<待发字节> media=<本帧媒体字节>`。",
|
|
26
26
|
},
|
|
27
|
-
"server.memory-consolidation.operator-valve-
|
|
27
|
+
"server.memory-consolidation.operator-valve-layers-unfed": {
|
|
28
28
|
cls: "P-DEBT",
|
|
29
|
-
note: "core 7.
|
|
29
|
+
note: "core 7.51.0 #1718 提货(`boot/memory-consolidation.ts` `createOperatorValveWriteJudge`):运维手动整理阀门的写判官座已是引擎的那一只构造器,但三层没喂 —— 人的持久规则层(阀门没有「库属于谁」的来源,无 principal / 不声明 local-owner)、部署的调用方策略层(`APPROVAL_REQUIRE` / `APPROVAL_DENY` / `AUTONOMY` / 敏感路径守卫集是每任务现合成的活配置、无部署级单例;敏感路径层另需引擎豁免根入参,宿主构造器输入没有这一位)、项目根(项目层 scope 键是任务根的单向散列,反推不出)。每被问一次(早问库目录一次 + 每个产物一次)记一次。放行的最坏后果 = 运维手动整理时,一条点名记忆库路径的人的持久规则或部署写策略(例如 `APPROVAL_DENY=Write`)拦不住这一轮折叠(与 7.128.x 逐字同;只是判官不再是代答);自动整理腿走引擎腿自己的判官,不在本 tag 射程。收口 = 三层各自有了来源(人:「读谁的桶」裁定;策略:一份设计 + 向 core 请托引擎豁免根入参;根:无来源则永久缺席并改写本债)那一版,本 tag 删。",
|
|
30
30
|
},
|
|
31
31
|
"server.workflow-park-index.sync-failed": {
|
|
32
32
|
cls: "P-DEBT",
|
|
@@ -242,7 +242,7 @@ export const FAIL_OPEN_TAGS = {
|
|
|
242
242
|
},
|
|
243
243
|
"server.resume.reopen-confirm-unreadable": {
|
|
244
244
|
cls: "F",
|
|
245
|
-
note: "S-447(#60 M2 自查项,`http/resume-legs.ts` 的 `confirmCheckpointReopened`)
|
|
245
|
+
note: "S-447(#60 M2 自查项,`http/resume-legs.ts` 的 `confirmCheckpointReopened`):续跑腿交回一条没 park 的失败结果后(7.129.0 起每一条都问,不再只在三个重开码上问)按 **token** 复核「这张 checkpoint 是不是又 pending 了」,这一读抛(库拖/版本太新/店不可达)。**方向本来就是 fail-closed**:读不出 ⇒ `return true` ⇒ 「不写任何终局、重新 park」(`:1206-1212` 头注已把取舍与有界代价写死:错 park 由 `reapSuspended` 兑,错终局会孤儿化一张活 park 到 TTL)—— 所以这条腿不是 P 类,缺的只是**留痕**。放行的最坏后果 = 多一次多余的 re-park(一张已经被决掉的卡重新挂到 reaper 窗里),人面上是「这条续跑没动」而不是谁被放行了。必须登记的理由是 **uniformity**:同文件三只同形读(`:249` `server.approvals.stale-echoed-token-unreadable` · `:288` `server.approvals.stale-current-pending-unreadable` · 本腿)中只有它是黑的,于是「店一直读不出所以每条续跑都在白白 re-park」与「一切正常」在遥测上同形。计数非零 = 这台机器的 checkpoint 店在拖,运维要去看库。",
|
|
246
246
|
},
|
|
247
247
|
"server.approvals.stale-current-pending-unreadable": {
|
|
248
248
|
cls: "F",
|
|
@@ -287,6 +287,10 @@ export const FAIL_OPEN_TAGS = {
|
|
|
287
287
|
cls: "F",
|
|
288
288
|
note: "S-249(`approval-card.ts` 的边界窄读族;今天四个成员:`readRuleOffersAbsence`、S-934 ② 起的 `readAskClassPair`(core 7.37.0 #1214 的类对 `askClass` / `classes`,活卡帧 / 呈卡帧的卡 / 运维队列行同一只,判法 = core 屏 `screenAskClassMembers`(S-955 ⑥ / core 7.38.0 #1249,本仓零自写对判),两键同在同缺,半对 / 集外 / 首员不符 / 重复员 = 屏拒 = 读不出;detail 前缀 `askClass=` / `classes=`),与 S-721 起的 `readClosedWordSeat` —— 一座一只 core 成员筛,座是参数:`mandate`(core 7.32.0 #1092,活卡帧 / 活挂起行 / 运维队列行同一只,闭集筛 = core `isPersistedRuleMandate`)与 S-755 起的 `origin`(运维队列行读 park 行孪生座,闭集筛 = core `isAskOrigin`,与 inbox 行 core 投影处同一只筛)、S-1170 起的 `classifierNoRuling`(core 7.43.0 #1353 的成因词,活卡帧读 `AskRequest.classifierNoRuling`,闭集筛 = core `isAutoModeFallbackCause`)与 `differs`(core 7.44.0 #1374,载体是 `runs.ts` 的 409 `steering.duplicate_input_id` 体读的引擎拒错 `detail.differs`,闭集筛 = core `STEER_REPLAY_FACETS` 的非空子集;集外成员 / 空 / 非数组 ⇒ 不铸 `detail`,码仍是契约);detail 前缀 `ruleOffersAbsence=` / `mandate=` / `origin=` / `classifierNoRuling=` / `differs=` 判别是哪一座;第四个成员是 `approval-content-kind.ts` 的 `readApprovalCard` —— 卡词 `approvalCard` 自己的闭集窄读,活 ask / LOCAL 店停驻门 / SQL 店读出的门子树三个载体共用一只,闭集 = core `TOOL_APPROVAL_CARDS`,detail 前缀 `approvalCard=`):活卡帧 / `card_json` / (S-700 起)运维队列行(`parkRowFacts` 读寄存行上的 park 孪生座;队列是热读面 ⇒ 同一行每读一次计一次)的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40);数 / 布尔等原始值原样,对象 / 数组 / 函数 / null 只报形名(`<object>` / `<array>` / `<function>` / `<null>`,S-700 codex r1:格式化从不调用值自己的转换方法 —— 持久层里一格 `{\"toString\":null}` 曾让诊断本身抛出、打掉整只队列)。",
|
|
289
289
|
},
|
|
290
|
+
"server.approval-card.retired-card-word-read": {
|
|
291
|
+
cls: "F",
|
|
292
|
+
note: "S-1562(core 7.51.0 #1515 / #1702;一次性迁移 M18 的**读侧半场**,`approval-card.ts` 的 `readStoredCardEnvelope` / `currentParkArm`,窗与删法见 `plugins/one-time-migrations.ts` 登记表 M18 —— 窗关随 M18 删本 tag):存下的审批卡(`approval_ask.card_json`)或停驻行(`pendingAction` 的 `tool_approval` 臂)上带着引擎 7.51.0 退役的词 —— `ruleOffersAbsence: \"shadowed\"` 或类对里的 `governance_unavailable` —— 读时按 M18 那一只改写读(前者删键 = 合法缺席,后者换成 `rule_unreadable`、类集去重按引擎序)。每改写一次记一次;detail = 载体(`card_json` / `parked row`)与座名,不带值。放行的最坏后果 = 无:这一读与存卡的起服改写对同一份字节给同一个答案(一只函数);它不是兜底,它登记在这里是为了**响亮** —— 载体 `card_json` 计数非零 = 有起服改写够不着的卡(滚动升级窗里 7.128.x 副本新写的卡、local 文件 ask 账);载体 `parked row` 计数非零 = 读到了带旧词的停驻行(引擎写的文档,本服务从不改写它 —— 行摘要覆盖类对 `askClass` / `classes`,改写 = 续跑判 `damaged`;所以这一格在窗内随每次读到那样的行而涨,直到那些行被决掉 / 收割),运维据它判断旧副本是否都已换下、窗关删码前是否还有这种行在飞。反方向(不改写)= 修前:整张存卡读不出(断连重放跳过、幂等重入走 park、回决口按要人)、停驻行的 `shadowed` 读成「读不出 ⇒ 按强制」(拿不到「本会话放行」)。",
|
|
293
|
+
},
|
|
290
294
|
"server.pg-pool.idle-client-error": {
|
|
291
295
|
cls: "F",
|
|
292
296
|
note: "S-55(test [5864] 独立复现):pg 连接池里一条连接被外部终止(pg_terminate_backend / 云端故障切换 / LB 空闲回收)或自身网络错误。**两态一 tag,detail 判别**(`state=idle` / `state=checked-out`;先例=park 墓碑行的两臂 detail 判别):①idle 态 —— node-postgres 语义(pg-pool@3.14.0 makeIdleListener):池在 emit 'error' **之前**已 `_remove` 掉该 client,坏连接自然淘汰,下次 acquire 发新连接,在飞查询零影响;修前 Pool 上无 'error' 监听 ⇒ EventEmitter 把事件转 throw ⇒ uncaughtException ⇒ 整进程 exit 1。②checked-out 态(codex R1-[high] 补,同批修)—— pg-pool 借出时摘 idle 监听(index.js),`pool.connect()` 显式持有的连接(事务)在 checkout 期死时 client 直接 emit 'error' 同样打死进程;守卫只观察:中毒 client 的后续查询由 pg 以 `_queryable=false` 响亮拒,归还时池淘汰;同一次 checkout 的死可能双发事件(FATAL 消息 + socket end),warn 逐枚、计数按 checkout 去重。放行的最坏后果 = 下一次 acquire 多付一次建连,故 F 类。必须留痕:「连接不断被外部杀」(故障切换风暴 / 回收策略过激 / 有人在库上清连接)与「一切正常」在服务面同形;每次事件另有逐次结构化 warn(`pg_pool_idle_client_error` / `pg_pool_checked_out_client_error`,含 code/severity),本计数让频率曲线可读。`pool.query()` 快路不在洞内(pg-pool 自带 checkout 期 once('error') 守卫)。mysql2 孪生(tidb-pool)**不同病**:PoolConnection 构造器恒挂 once('error') 库内吞并淘汰、借出期同在(坐标成文在 createTidbPool 头注),故无同族 tag。",
|
|
@@ -1,4 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* [ref]([ref])—— **密钥形 env 剥除的留痕面**(core D1 `scrubSecretEnv` 的可观测半场)。
|
|
3
|
+
*
|
|
4
|
+
* ## 它解决的问题
|
|
5
|
+
* `scrubSecretEnv` 是本仓两条 shell/子进程腿的**安全默认**:模型可驱动的 shell(`inheritEnv:"scrub"`)与
|
|
6
|
+
* git 子进程(钩子/凭证助手)拿到的 env 里,`*_KEY`/`*_TOKEN`/`*_SECRET`/`*_PASSWORD`… 一律先丢掉。
|
|
7
|
+
* 两处都以**单参形**调用,于是「这一次到底丢了哪些 key、按哪条规则(core 7.35.0 起五词:exact-name / prefix-rule / namespace-rule / name-rule /
|
|
8
|
+
* value-rule)、置信度多少」
|
|
9
|
+
* **零留痕** —— 部署方既证明不了保护生效过,也发现不了它悄悄不生效了(词表漂移 / 一个新的密钥命名法从
|
|
10
|
+
* 规则的缝里穿过去 / 某条腿被改成 `inheritEnv:"all"`)。core 早就把收集口开好了(`findings?:
|
|
11
|
+
* SecretEnvFinding[]`,d.ts 原话:the VALUE is never carried — this record is meant to be loggable),
|
|
12
|
+
* 缺的只是本仓这半场。本模块就是那半场,**不是**一个新的执法面:它一个字节也不改变剥除行为。
|
|
13
|
+
*
|
|
14
|
+
* ## 形(照 `fail-open.ts` 的仓内先例)
|
|
15
|
+
* 1. **计数(逐次)** —— `secret_env_scrubbed_total{site,kind,confidence}`;
|
|
16
|
+
* 2. **结构化披露(每 site×key 一次)** —— 首次在某条腿上丢掉某个 key 时一条 `info`,之后同 key 只走计数。
|
|
17
|
+
* 有部署级出路的现场(S-699:`server.lsp.spawn-env`;7.103.0 起 + `server.bake-runner.spawn-env`)升 `warn`,并带当前放行名单与出路句
|
|
18
|
+
* —— 规则见 `SecretEnvScrubSiteSpec`。
|
|
19
|
+
* 不是「每 site 一次」:一个**新**的密钥形 key 后来才出现(部署加了一个凭证)时,那才是最该看见的一行。
|
|
20
|
+
* findings 全部是 core 的 `SecretEnvFinding`(7.129.0 / core 7.51.1 起只有引擎一只写手,经 `server-secret-env.ts` 的唯一组合):形状臂的五词
|
|
21
|
+
* `exact-name` / `prefix-rule`(`OTEL_*`)/ `namespace-rule`(`SEMA_` 命名空间)/ `name-rule`(名字形状)/ `value-rule`(值里带凭据),加
|
|
22
|
+
* `own-credential`(本进程起服声明的自有凭据名 —— 配置目录 secret 名 ∪ 名单型旋钮名 —— 被形状臂漏下、由声明臂扣的;`all` 词下也扣)。
|
|
23
|
+
* `kind` 原样进标签、不做穷举 switch,core 换词即自成新桶;本仓 7.99.0–7.128.x 自铸的 `config-catalog` / `name-list-knob` 两桶随目录臂退役,
|
|
24
|
+
* 它们的读数今天在 `own-credential` 桶 —— 那一桶就是 core 形状规则的缺口读数。git 的 env 配置族与代理变量一族自 core 7.35.0 起是**豁免族**
|
|
25
|
+
* (名与值都不读、原样交出),不产 finding;此前(7.33.2–7.33.8)被扣的成员记 core 的 `family-rule`,本仓 7.103.0 的 `git-config-group` 桶与 7.103.1
|
|
26
|
+
* 中间态的 `secret_env_git_config_pairs_withheld_total` / warn `git_config_pair_withheld` 更早随本仓那一层退役(车PG)。
|
|
27
|
+
* 值永不进任何一件(core 的 finding 本身就不携带值)。
|
|
28
|
+
*
|
|
29
|
+
* ## 为什么是进程级自由函数而不是注入
|
|
30
|
+
* 与 `recordFailOpen` 同因:调用点都在**没有 logger/metrics 句柄**的深处(`plugins/remote-env-host.ts` 的 `mergeEnv` 全文零观测通道;
|
|
31
|
+
* git 腿是自由函数 / 闭包)。7.103.0 起各腿不再直接调本函数 —— 只经 `server-secret-env.ts` 的唯一组合 `hostSubprocessEnv`。
|
|
32
|
+
* 装配层用 {@link installSecretEnvScrubRecorder} 把句柄接上,装配之前命中的次数不会丢(计数照常累加,
|
|
33
|
+
* install 时一次性补进 metrics),披露行则在下一次命中时补发(未成功披露的 key 不进 `disclosed` 集)。
|
|
34
|
+
*/
|
|
35
|
+
import { type SecretEnvFinding } from "@sema-agent/core";
|
|
2
36
|
import type { Logger } from "./logger.js";
|
|
3
37
|
import type { Metrics } from "./metrics.js";
|
|
4
38
|
/**
|
|
@@ -16,7 +50,7 @@ export interface SecretEnvScrubSiteSpec {
|
|
|
16
50
|
/** 登记过的剥除现场。闭集:未登记的 site 传不进 {@link recordSecretEnvScrub}(编译期拒,同 FAIL_OPEN_TAGS 纪律)。 */
|
|
17
51
|
export declare const SECRET_ENV_SCRUB_SITES: {
|
|
18
52
|
readonly "server.remote-env-host.inherit-scrub": {
|
|
19
|
-
readonly note: "模型可驱动的 host shell 继承 env(`inheritEnv
|
|
53
|
+
readonly note: "模型可驱动的 host shell 继承 env(`inheritEnv` 三词任一,缺省 `\"scrub\"`;`\"all\"` 词下扣的只有本服务自己的凭据名)——这条腿上的一次泄漏等于把编排器的脑钥/仓库令牌交给模型选的命令";
|
|
20
54
|
readonly remedy: null;
|
|
21
55
|
};
|
|
22
56
|
readonly "server.project-memory.git-subprocess": {
|
|
@@ -52,7 +86,7 @@ interface Sink {
|
|
|
52
86
|
* `findings` 只许装**没有按原样交给子进程**的键(剥掉 / 换成 core 占位半条)—— 点名放回的键取回源值,不是剥除(不计数、不披露)。
|
|
53
87
|
* `allowed` = 这一次生效的点名放行名单(S-699),只在有出路的现场(`remedy` 在场)写进披露行;没有放行面的腿传 `[]`。
|
|
54
88
|
*/
|
|
55
|
-
export declare function recordSecretEnvScrub(site: SecretEnvScrubSite, findings: readonly
|
|
89
|
+
export declare function recordSecretEnvScrub(site: SecretEnvScrubSite, findings: readonly SecretEnvFinding[], allowed: readonly string[]): void;
|
|
56
90
|
/** 装配层把 logger/metrics 接上(main.ts,与 `installFailOpenRecorder` 同一处)。装配前累计的次数在此补进 metrics。 */
|
|
57
91
|
export declare function installSecretEnvScrubRecorder(deps: Sink): void;
|
|
58
92
|
/** 已留驻的「现场×键」条数(测试/诊断读面)。恒 ≤ {@link DISCLOSED_CAP}。 */
|
|
@@ -2,7 +2,7 @@ import { summarizeRedactions } from "@sema-agent/core";
|
|
|
2
2
|
import { describeAllowlistPostures } from "../env-name-allowlist-knobs.js";
|
|
3
3
|
export const SECRET_ENV_SCRUB_SITES = {
|
|
4
4
|
"server.remote-env-host.inherit-scrub": {
|
|
5
|
-
note: "模型可驱动的 host shell 继承 env(`inheritEnv
|
|
5
|
+
note: "模型可驱动的 host shell 继承 env(`inheritEnv` 三词任一,缺省 `\"scrub\"`;`\"all\"` 词下扣的只有本服务自己的凭据名)——这条腿上的一次泄漏等于把编排器的脑钥/仓库令牌交给模型选的命令",
|
|
6
6
|
remedy: null,
|
|
7
7
|
},
|
|
8
8
|
"server.project-memory.git-subprocess": {
|
|
@@ -27,7 +27,7 @@ export const SECRET_ENV_SCRUB_SITES = {
|
|
|
27
27
|
},
|
|
28
28
|
};
|
|
29
29
|
const SCRUB_COUNTER = "secret_env_scrubbed_total";
|
|
30
|
-
const SCRUB_COUNTER_HELP = "secret env entries not handed on as written (dropped) to a spawned child process, by site/kind/confidence (core
|
|
30
|
+
const SCRUB_COUNTER_HELP = "secret env entries not handed on as written (dropped) to a spawned child process, by site/kind/confidence (core inheritedHostEnv: the scrub's rules + this deployment's declared own credentials, kind own-credential)";
|
|
31
31
|
const DISCLOSED_CAP = 512;
|
|
32
32
|
const SEP = "=";
|
|
33
33
|
let sink = {};
|
package/dist/parked-decide.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readClosedWordSeat } from "./approval-card.js";
|
|
1
2
|
import { routeWorkflowPark } from "./orchestration/workflow-agent-session-index.js";
|
|
2
3
|
import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
|
|
3
4
|
import { backgroundScopesForCheckpointScope, decodeCheckpointScope } from "./security.js";
|
|
@@ -313,8 +314,9 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
313
314
|
if (isErr) {
|
|
314
315
|
const disp = await rollback("execute_rejected");
|
|
315
316
|
const content = receipt.content;
|
|
316
|
-
const
|
|
317
|
-
if (
|
|
317
|
+
const receiptDetails = receipt.details;
|
|
318
|
+
if (receiptDetails?.error === "parked_resume.startup_failed") {
|
|
319
|
+
const cause = readClosedWordSeat(receiptDetails, "cause");
|
|
318
320
|
return {
|
|
319
321
|
status: 422,
|
|
320
322
|
body: {
|
|
@@ -322,6 +324,7 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
322
324
|
errorCode: "parked_resume.startup_failed",
|
|
323
325
|
taskId: match.handle,
|
|
324
326
|
rollback: disp,
|
|
327
|
+
...(cause !== undefined ? { cause } : {}),
|
|
325
328
|
},
|
|
326
329
|
};
|
|
327
330
|
}
|