@sema-agent/server 7.103.2 → 7.105.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 +378 -2
- package/README.md +6 -1
- package/README.zh-CN.md +5 -1
- package/USAGE.md +320 -16
- package/deploy/sema-up/README.md +57 -7
- package/deploy/sema-up/cluster-up.sh +18 -4
- package/deploy/sema-up/kube-up.sh +16 -0
- package/deploy/sema-up/lib/dataroot.sh +142 -0
- package/deploy/sema-up/lib/flagshape.sh +69 -0
- package/deploy/sema-up/lib/lanes.py +14 -4
- package/deploy/sema-up/lib/shell-access.sh +41 -0
- package/deploy/sema-up/preflight.sh +14 -1
- package/deploy/sema-up/sema-up.sh +134 -37
- package/deploy/sema-up/smoke.sh +5 -0
- package/dist/auto-mode-face.js +3 -4
- package/dist/boot/budget-tracing.js +1 -1
- package/dist/boot/config-center.d.ts +6 -2
- package/dist/boot/config-center.js +5 -59
- package/dist/boot/execution-env.d.ts +11 -0
- package/dist/boot/execution-env.js +14 -0
- package/dist/boot/execution-lane.d.ts +25 -0
- package/dist/boot/execution-lane.js +95 -0
- package/dist/boot/memory-consolidation.d.ts +185 -9
- package/dist/boot/memory-consolidation.js +192 -32
- package/dist/boot/resolve-spec.d.ts +2 -0
- package/dist/boot/resolve-spec.js +28 -24
- package/dist/boot/runner-deps.d.ts +6 -2
- package/dist/boot/runner-deps.js +6 -1
- package/dist/boot/session-faces.d.ts +5 -0
- package/dist/boot/session-faces.js +18 -2
- package/dist/boot/session-incarnation-seat.d.ts +41 -0
- package/dist/boot/session-incarnation-seat.js +69 -0
- package/dist/boot/stage-01-config.js +2 -2
- package/dist/boot/stage-02-registry-pull.js +3 -1
- package/dist/boot/stage-04-stores.d.ts +2 -0
- package/dist/boot/stage-04-stores.js +3 -1
- package/dist/boot/stage-05-execution-env.d.ts +4 -0
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.d.ts +4 -1
- package/dist/boot/stage-06-runners.js +7 -14
- package/dist/boot/stage-07-capability-layer.d.ts +4 -1
- package/dist/boot/stage-08-reapers.d.ts +4 -1
- package/dist/boot/stage-09-leader.d.ts +6 -1
- package/dist/boot/stage-09-leader.js +2 -1
- package/dist/boot/stage-10-http-server.d.ts +6 -1
- package/dist/boot/stage-10-http-server.js +4 -1
- package/dist/brain.d.ts +1 -1
- package/dist/config-catalog.d.ts +16 -1
- package/dist/config-catalog.js +9 -9
- package/dist/config-center/restart-signal.d.ts +4 -4
- package/dist/config-invariants.d.ts +7 -1
- package/dist/config-invariants.js +37 -6
- package/dist/config-provider.d.ts +42 -10
- package/dist/config-provider.js +18 -16
- package/dist/config-types.d.ts +37 -16
- package/dist/config-types.js +1 -0
- package/dist/config.d.ts +28 -30
- package/dist/config.js +95 -77
- package/dist/env-facts.d.ts +13 -3
- package/dist/env-facts.js +15 -3
- package/dist/execution-lane-caps.d.ts +15 -0
- package/dist/execution-lane-caps.js +8 -0
- package/dist/file-history-capture.d.ts +42 -35
- package/dist/file-history-capture.js +15 -10
- package/dist/file-history-disclosure.d.ts +7 -31
- package/dist/file-history-disclosure.js +12 -0
- package/dist/fleet/fleet-bus.d.ts +5 -1
- package/dist/http/admission.d.ts +12 -5
- package/dist/http/admission.js +5 -1
- package/dist/http/dispatch.d.ts +18 -0
- package/dist/http/dispatch.js +19 -6
- package/dist/http/principal-gate.d.ts +22 -0
- package/dist/http/principal-gate.js +20 -0
- package/dist/http/resume-legs.d.ts +1 -1
- package/dist/http/resume-legs.js +9 -5
- package/dist/http/route-table.d.ts +15 -4
- package/dist/http/route-table.js +6 -5
- package/dist/http/routes/agents-roster.d.ts +7 -0
- package/dist/http/routes/agents-roster.js +6 -2
- package/dist/http/routes/approvals-assistant.d.ts +48 -19
- package/dist/http/routes/approvals-assistant.js +117 -50
- package/dist/http/routes/capabilities.js +3 -2
- package/dist/http/routes/config-catalog.js +1 -0
- package/dist/http/routes/fleet.d.ts +3 -2
- package/dist/http/routes/images.js +0 -1
- package/dist/http/routes/memory-consolidation.d.ts +3 -2
- package/dist/http/routes/memory-policy.js +3 -3
- package/dist/http/routes/rules.js +2 -2
- package/dist/http/routes/runs.js +29 -10
- package/dist/http/routes/session-sync.js +14 -15
- package/dist/http/routes/sessions-list.js +3 -2
- package/dist/http/routes/sessions.js +49 -25
- package/dist/http/routes/shared-memory.js +2 -2
- package/dist/http/routes/side-query.js +8 -1
- package/dist/http/routes/tasks.js +22 -19
- package/dist/http/routes/trace-usage.d.ts +2 -0
- package/dist/http/routes/trace-usage.js +8 -13
- package/dist/http/send.d.ts +12 -0
- package/dist/http/send.js +12 -1
- package/dist/http/server.d.ts +30 -8
- package/dist/http/server.js +11 -5
- package/dist/http/sse-lifecycle.d.ts +1 -1
- package/dist/http/sse-log.d.ts +12 -9
- package/dist/http/sse-log.js +1 -8
- package/dist/http/sync-submit-body.d.ts +43 -0
- package/dist/http/sync-submit-body.js +4 -0
- package/dist/http/wire-types.d.ts +23 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/leader/merge.d.ts +8 -3
- package/dist/memory-incident-sink.js +2 -1
- package/dist/model-select.d.ts +9 -0
- package/dist/model-select.js +5 -1
- package/dist/observability/corrupt-read-seat.d.ts +151 -16
- package/dist/observability/corrupt-read-seat.js +158 -15
- package/dist/observability/fail-open.d.ts +17 -1
- package/dist/observability/fail-open.js +17 -1
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal.d.ts +60 -10
- package/dist/observability/run-terminal.js +119 -9
- package/dist/parked-decide.d.ts +8 -3
- package/dist/parked-decide.js +56 -20
- package/dist/parked-revive-surface.d.ts +14 -7
- package/dist/parked-revive-surface.js +5 -1
- package/dist/plugins/approval-ask-store-file.d.ts +4 -1
- package/dist/plugins/approval-ask-store-file.js +9 -12
- package/dist/plugins/approval-ask-store-sql.d.ts +4 -0
- package/dist/plugins/approval-ask-store-sql.js +4 -1
- package/dist/plugins/approval-exemption-store.d.ts +21 -1
- package/dist/plugins/approval-exemption-store.js +36 -7
- package/dist/plugins/approval-nonce-store.d.ts +16 -2
- package/dist/plugins/approval-nonce-store.js +17 -7
- package/dist/plugins/caching-session-store.d.ts +5 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/file-resume-anchor-store.d.ts +7 -2
- package/dist/plugins/file-resume-anchor-store.js +19 -5
- package/dist/plugins/file-run-store.d.ts +21 -8
- package/dist/plugins/file-run-store.js +60 -38
- package/dist/plugins/image-bake-store-sql.d.ts +0 -2
- package/dist/plugins/image-bake-store-sql.js +0 -5
- package/dist/plugins/local-session-store.d.ts +22 -0
- package/dist/plugins/local-session-store.js +74 -11
- package/dist/plugins/memory-run-store.d.ts +7 -5
- package/dist/plugins/memory-run-store.js +11 -4
- package/dist/plugins/one-time-migrations.d.ts +38 -1
- package/dist/plugins/one-time-migrations.js +143 -15
- package/dist/plugins/permission-rule-store-file.d.ts +9 -2
- package/dist/plugins/permission-rule-store-file.js +37 -51
- package/dist/plugins/pg-session-storage.d.ts +3 -0
- package/dist/plugins/pg-session-storage.js +19 -10
- package/dist/plugins/retention-store-sql.js +1 -0
- package/dist/plugins/run-store-sql.d.ts +15 -6
- package/dist/plugins/run-store-sql.js +15 -13
- package/dist/plugins/schema-indexes.js +1 -0
- package/dist/plugins/send-file-ledger.d.ts +5 -1
- package/dist/plugins/send-file-ledger.js +11 -6
- package/dist/plugins/session-event-payload.d.ts +14 -4
- package/dist/plugins/session-event-payload.js +5 -2
- package/dist/plugins/session-policy-store-sql.js +18 -5
- package/dist/plugins/store-backend.d.ts +13 -7
- package/dist/plugins/store-backend.js +10 -6
- package/dist/plugins/store-contracts.d.ts +7 -2
- package/dist/plugins/tidb-pool.js +12 -5
- package/dist/plugins/tidb-session-store.d.ts +6 -1
- package/dist/plugins/tidb-session-store.js +17 -10
- package/dist/project-identity.d.ts +4 -6
- package/dist/read-face-posture.d.ts +21 -0
- package/dist/read-face-posture.js +9 -0
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +72 -3
- package/dist/request-value-closure.js +12 -4
- package/dist/roster-bound-http-status.d.ts +6 -0
- package/dist/roster-bound-http-status.js +6 -0
- package/dist/rules-consent.js +0 -11
- package/dist/run-local.js +22 -27
- package/dist/runs.d.ts +22 -2
- package/dist/runs.js +29 -7
- package/dist/sealed-key.d.ts +31 -4
- package/dist/sealed-key.js +9 -0
- package/dist/security.d.ts +5 -1
- package/dist/session-children-retire.d.ts +92 -0
- package/dist/session-children-retire.js +85 -0
- package/dist/session-error-wire.d.ts +58 -0
- package/dist/session-error-wire.js +38 -0
- package/dist/session-incarnation.d.ts +61 -0
- package/dist/session-incarnation.js +43 -0
- package/dist/session-policy-wire.d.ts +98 -12
- package/dist/session-policy-wire.js +71 -8
- package/dist/session-records-skipped-notice.d.ts +20 -0
- package/dist/session-records-skipped-notice.js +27 -0
- package/dist/tool-approval.d.ts +65 -0
- package/dist/tool-approval.js +36 -9
- package/dist/trace/core-keyset-guard.d.ts +8 -7
- package/dist/trace/engine-notice-wire.d.ts +3 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/project.d.ts +11 -6
- package/dist/trace/project.js +12 -3
- package/dist/trace/redact.d.ts +7 -0
- package/dist/trace/redact.js +153 -61
- package/dist/trace/wire-projection-faces.d.ts +10 -3
- package/dist/trace/wire-projection-faces.js +8 -2
- package/package.json +2 -2
- package/dist/plugins/memory-session-policy-store.d.ts +0 -46
- package/dist/plugins/memory-session-policy-store.js +0 -52
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { readJsonlRecords } from "@sema-agent/core";
|
|
2
|
+
import { recordFailOpen } from "./fail-open.js";
|
|
3
|
+
export function createCorruptReadSeat(warn, event, note, leg) {
|
|
4
|
+
return (info) => {
|
|
5
|
+
leg?.(info);
|
|
6
|
+
return warn(event, {
|
|
7
|
+
kind: info.kind,
|
|
8
|
+
path: info.path,
|
|
9
|
+
reason: info.reason,
|
|
10
|
+
...corruptReadLevelFields(info),
|
|
11
|
+
...(info.sessionId !== undefined ? { sessionId: info.sessionId } : {}),
|
|
12
|
+
...(info.principal !== undefined ? { principal: info.principal } : {}),
|
|
13
|
+
...(note !== undefined ? { note } : {}),
|
|
14
|
+
});
|
|
15
|
+
};
|
|
11
16
|
}
|
|
12
17
|
function corruptReadLevelFields(info) {
|
|
13
18
|
switch (info.kind) {
|
|
@@ -29,19 +34,157 @@ export function buildDurableAbsenceNote(thirdFace) {
|
|
|
29
34
|
"layer (deployment policy / hooks / shell gate still applied); session repo = a listing silently omitted " +
|
|
30
35
|
`rows; ${thirdFace}. Inspect the named path before deciding.`);
|
|
31
36
|
}
|
|
32
|
-
|
|
37
|
+
const RULE_LANE_REMEDY = "the permission-rule lane is fail-closed until a person inspects the file (moving it aside restarts the lane with an empty log)";
|
|
38
|
+
export const JSONL_REPLAY_SEATS = {
|
|
39
|
+
"approval-nonce": {
|
|
40
|
+
direction: "fail-closed",
|
|
41
|
+
event: "approval_nonce_corrupt_read",
|
|
42
|
+
face: "approval-nonce ledger",
|
|
43
|
+
note: "S-582 fail-closed: an unreadable consumption record may be a SPENT plan_review nonce — replaying the rest would accept that proof again. " +
|
|
44
|
+
"The ledger refuses every consume/reap until a person inspects the file (direct-door plan_review answers 5xx meanwhile; /decide is unaffected).",
|
|
45
|
+
remedy: "direct-door plan_review is refused until a person inspects the file. Moving it aside forgets every consumption in it — each of those proofs " +
|
|
46
|
+
"could be replayed until it expires (PRINCIPAL_JWT_MAX_TTL_SEC plus skew after it was recorded) — so move it aside only once that window has passed, then restart",
|
|
47
|
+
},
|
|
48
|
+
"approval-exemption": {
|
|
49
|
+
direction: "fail-closed",
|
|
50
|
+
event: "approval_exemption_corrupt_read",
|
|
51
|
+
face: "approval-exemption log",
|
|
52
|
+
note: "S-582 fail-closed: an unreadable record may be a REVOKE tombstone — replaying the rest would resurrect a revoked \"don't ask again\". " +
|
|
53
|
+
"That session's exemptions are refused (every tool asks) until a person inspects the file; other sessions are unaffected.",
|
|
54
|
+
remedy: "this session's exemptions are refused (the tool asks every time) until a person inspects the file; deleting the session, or moving the file " +
|
|
55
|
+
"aside and restarting, drops this session's exemptions (they will be asked for again)",
|
|
56
|
+
},
|
|
57
|
+
"permission-rule-approvals": {
|
|
58
|
+
direction: "fail-closed",
|
|
59
|
+
event: "permission_rule_approvals_corrupt_read",
|
|
60
|
+
face: "permission-rule approval log",
|
|
61
|
+
note: "S-582 fail-closed: an unreadable snapshot would roll a rule-approval record (and its rev) back to an earlier state. The face refuses every read and write until a person inspects the file.",
|
|
62
|
+
remedy: RULE_LANE_REMEDY,
|
|
63
|
+
},
|
|
64
|
+
"permission-rule-tickets": {
|
|
65
|
+
direction: "fail-closed",
|
|
66
|
+
event: "permission_rule_tickets_corrupt_read",
|
|
67
|
+
face: "permission-rule import-ticket log",
|
|
68
|
+
note: "S-582 fail-closed: an unreadable consume/release line would resurrect a claimed import ticket as unclaimed (one human consent redeemed twice). The face refuses every read and write until a person inspects the file.",
|
|
69
|
+
remedy: RULE_LANE_REMEDY,
|
|
70
|
+
},
|
|
71
|
+
"approval-ask-journal": {
|
|
72
|
+
direction: "fail-open",
|
|
73
|
+
event: "approval_ask_journal_corrupt_read",
|
|
74
|
+
tag: "server.approval-ask-journal.corrupt-record-skipped",
|
|
75
|
+
note: "F: a skipped ask-journal record loses an ask transition's PROGRESS, never a gate (every write is CAS-guarded); the original is quarantined next to the journal.",
|
|
76
|
+
},
|
|
77
|
+
"run-store": {
|
|
78
|
+
direction: "fail-open",
|
|
79
|
+
event: "run_store_corrupt_read",
|
|
80
|
+
tag: "server.run-store.corrupt-record-skipped",
|
|
81
|
+
note: "F: a skipped run.json reads that run as ABSENT ('no such run'); a skipped events.jsonl line is one trace event missing from replay; " +
|
|
82
|
+
"a skipped claim record sends that claim down the torn-claim path (adopted by its live row, else quarantined).",
|
|
83
|
+
},
|
|
84
|
+
"resume-anchor": {
|
|
85
|
+
direction: "fail-open",
|
|
86
|
+
event: "resume_anchor_corrupt_read",
|
|
87
|
+
tag: "server.resume-anchor.corrupt-record-skipped",
|
|
88
|
+
note: "F: a skipped anchor means that eventId no longer resolves (resumeAt / rewindFilesTo answer 404 unknown_event for it; the next put re-records it).",
|
|
89
|
+
},
|
|
90
|
+
"send-file-ledger": {
|
|
91
|
+
direction: "fail-open",
|
|
92
|
+
event: "send_file_ledger_corrupt_read",
|
|
93
|
+
tag: "server.send-file-ledger.corrupt-record-skipped",
|
|
94
|
+
note: "F: a skipped ledger row means that issued link is missing from its tenant's listing (the stored object itself is untouched).",
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
function corruptRefusalSentence(seat, path, reason) {
|
|
98
|
+
const entry = JSONL_REPLAY_SEATS[seat];
|
|
99
|
+
return `refusing to use a corrupt ${entry.face} (${path}): ${reason} — ${entry.remedy}`;
|
|
100
|
+
}
|
|
101
|
+
export class JsonlStoreCorruptError extends Error {
|
|
102
|
+
kind = "corrupt";
|
|
103
|
+
seat;
|
|
104
|
+
path;
|
|
105
|
+
reason;
|
|
106
|
+
constructor(seat, path, reason) {
|
|
107
|
+
super(corruptRefusalSentence(seat, path, reason));
|
|
108
|
+
this.name = "JsonlStoreCorruptError";
|
|
109
|
+
this.seat = seat;
|
|
110
|
+
this.path = path;
|
|
111
|
+
this.reason = reason;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export class JsonlStoreGate {
|
|
115
|
+
seat;
|
|
116
|
+
path;
|
|
117
|
+
reason;
|
|
118
|
+
constructor(seat, path) {
|
|
119
|
+
this.seat = seat;
|
|
120
|
+
this.path = path;
|
|
121
|
+
}
|
|
122
|
+
trip(reason) {
|
|
123
|
+
this.reason ??= reason;
|
|
124
|
+
}
|
|
125
|
+
get tripped() {
|
|
126
|
+
return this.reason !== undefined;
|
|
127
|
+
}
|
|
128
|
+
assertUsable() {
|
|
129
|
+
if (this.reason !== undefined)
|
|
130
|
+
throw new JsonlStoreCorruptError(this.seat, this.path, this.reason);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const warnToConsole = (event, fields) => console.warn(event, fields);
|
|
134
|
+
const isThenable = (v) => typeof v === "object" && v !== null && typeof v.then === "function";
|
|
135
|
+
function guardDisclosure(disclose, event) {
|
|
136
|
+
const reportSinkFailure = (info, err) => warnToConsole(`${event}_sink_failed`, { kind: info.kind, path: info.path, reason: info.reason, sinkError: err instanceof Error ? err.name : typeof err });
|
|
33
137
|
return (info) => {
|
|
138
|
+
let out;
|
|
139
|
+
try {
|
|
140
|
+
out = disclose(info);
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
reportSinkFailure(info, err);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (isThenable(out))
|
|
147
|
+
out.then(undefined, (err) => reportSinkFailure(info, err));
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function replayJsonl(path, seat, shape, warn, onSkipped) {
|
|
151
|
+
const entry = JSONL_REPLAY_SEATS[seat];
|
|
152
|
+
const disclose = guardDisclosure(createCorruptReadSeat(warn ?? warnToConsole, entry.event, entry.note), entry.event);
|
|
153
|
+
const judge = { what: shape.what, accepts: (record) => shape.read(record) !== undefined };
|
|
154
|
+
const kept = readJsonlRecords(path, (info) => {
|
|
34
155
|
switch (info.kind) {
|
|
35
156
|
case "record":
|
|
36
|
-
|
|
37
|
-
|
|
157
|
+
onSkipped(info);
|
|
158
|
+
break;
|
|
38
159
|
case "replay":
|
|
39
|
-
|
|
160
|
+
break;
|
|
40
161
|
default: {
|
|
41
162
|
const unreachable = info;
|
|
42
163
|
return unreachable;
|
|
43
164
|
}
|
|
44
165
|
}
|
|
45
|
-
|
|
166
|
+
disclose(info);
|
|
167
|
+
}, judge);
|
|
168
|
+
return kept.map((record) => {
|
|
169
|
+
const value = shape.read(record);
|
|
170
|
+
if (value === undefined) {
|
|
171
|
+
throw new Error(`JSONL replay seat ${seat}: the shape reader accepted a record of ${path} and then refused it — a shape reader must be a pure function of the record`);
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
export function replayJsonlGuarded(path, seat, shape, warn) {
|
|
177
|
+
const gate = new JsonlStoreGate(seat, path);
|
|
178
|
+
const records = replayJsonl(path, seat, shape, warn, (info) => gate.trip(info.reason));
|
|
179
|
+
return { records, gate };
|
|
180
|
+
}
|
|
181
|
+
export function replayJsonlSkipping(path, seat, shape, warn) {
|
|
182
|
+
const { tag } = JSONL_REPLAY_SEATS[seat];
|
|
183
|
+
let skipped = 0;
|
|
184
|
+
const records = replayJsonl(path, seat, shape, warn, (info) => {
|
|
185
|
+
skipped += 1;
|
|
186
|
+
recordFailOpen(tag, `path=${info.path} reason=${info.reason}`);
|
|
187
|
+
});
|
|
188
|
+
return { records, skipped };
|
|
46
189
|
}
|
|
47
190
|
//# sourceMappingURL=corrupt-read-seat.js.map
|
|
@@ -259,7 +259,11 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
259
259
|
};
|
|
260
260
|
readonly "server.run-terminal.redact-threw": {
|
|
261
261
|
readonly cls: "F";
|
|
262
|
-
readonly note: "三个发射座(errorMessage 终态文案 / 诊断文本 diag / 工件 artifact,probe 前缀判别;普查 56 行;补丁头 patch-head 那座随 7.83.0 / S-113 的 redactPatchHead 一并退役):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(RangeError
|
|
262
|
+
readonly note: "三个发射座(errorMessage 终态文案 / 诊断文本 diag / 工件 artifact,probe 前缀判别;普查 56 行;补丁头 patch-head 那座随 7.83.0 / S-113 的 redactPatchHead 一并退役):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(RangeError:关键字/前缀臂那一形由 S-219 的 atLeast 铸点闭掉;JSON 秘密字段臂的转义对循环与 PEM 栅栏臂的标签交替循环由 S-265 换成手写线性扫描器闭掉 —— 已知形全部闭掉,本臂留作结构性兜底,计数恒零是证据、不归零即新形)⇒ 落固定占位 «redacted:oversized»——绝不落原文(安全轴 fail-closed),也绝不阻断终态翻转与 claim 释放(可用性)。方向刻意:一条超大错误体不得把 run 卡在 running。";
|
|
263
|
+
};
|
|
264
|
+
readonly "server.run-terminal.provider-echo-shaped": {
|
|
265
|
+
readonly cls: "F";
|
|
266
|
+
readonly note: "S-283(7.105.0,`observability/run-terminal.ts` 的 `admitProviderEcho`,唯一判决口;S-889 起动作与 core #1204 / 7.34.1 源头闸同一条):模型服务商回声、引擎逐字转交的不透明 id(`apiFailure.requestId` / `responseModel`,以及账本孪生 `turn_end.responseModel`、子代通告 `task_notification.apiFailure.requestId` 与 `POST /v1/side-query` 200 体的 `responseModel`;铸点 + 账本汇点两口)越出形状(字符串、长度 1–128、每个码元是可见 ASCII 0x21–0x7E;空串与非串值都在外)⇒ **缺席**(键删掉 / 不铸)。方向是 **fail-closed**(越界字节不上 wire / 不落库 / 不进日志),不截断、不过脱敏器(不透明 id,脱敏会改坏它 —— 与 S-270 structuredOutput 同方向)。放行的最坏后果 = 这枚 id / 模型名在 wire 上缺席(`apiFailure` 留 `{}` = 「provider 故障、无可补充标签」,core 契约原句),运维拿不到它去服务商那边对单;detail 只带座位 / 原长或类型,**不带值**。计数非零 = 这台部署接的端点(或中间网关)在回显越界字节,运维按座位去查那一跳;读面不改写存量行,存量越界行每次回放各计一次。";
|
|
263
267
|
};
|
|
264
268
|
readonly "server.run-terminal.redact-capped": {
|
|
265
269
|
readonly cls: "F";
|
|
@@ -301,6 +305,18 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
301
305
|
readonly cls: "F";
|
|
302
306
|
readonly note: "S-609(`routes/approvals-assistant.ts` 的 `GET /v1/assistant/inbox`):某一行的续跑上下文(checkpoint_ctx)读出来是**坏体**(店的拒绝契约 `StoreCorruptError`;local = 档不是 JSON / 信封不合形,SQL = 列是合法 JSON 但不是一只对象)⇒ **这一行**的 `objective` 落 `null`,列表其余照常。放行的最坏后果 = inbox 上这一张卡不显示它属于哪个任务(纯展示位;这一格不参与任何决裁、续跑或门判 —— 续跑腿与 plan_review 门对同一行各自走读口、坏体一律响亮 500)。不放行的代价是整只 `summaries.map` 抛出 ⇒ 一行坏体打掉整个用户的 inbox。故 F 类;必须留痕,因为修前那句 `.catch(() => null)` 让「坏体」与「本来就没有」同形无痕(detail = 读口转述的 `where`,人拿它去修那一行)。缺席(行不在 / 店抖动)**不**记本 tag:那是读口的刻意保守,不是失败开放。";
|
|
303
307
|
};
|
|
308
|
+
readonly "server.run-store.corrupt-record-skipped": {
|
|
309
|
+
readonly cls: "F";
|
|
310
|
+
readonly note: "S-582(`plugins/file-run-store.ts`,local 车道的 File 形 run 店;三个盘面同一只座):盘上一条记录读不出形 —— 一行 `42` / 缺本店必填键的对象 / 中间的半截 JSON(撕尾不走本 tag,那是崩溃的正常落盘形)⇒ 跳过它继续。三个盘面各自的最坏后果:`run.json` 坏 ⇒ 那条 run 读成**缺席**(`GET /v1/runs/:id` 404,listRuns 少一行;它若占着 claim,EEXIST 路按「行不在」判残骸接管);`events.jsonl` 一行坏 ⇒ 回放少一个事件(其余照读、按 seq 去重不变);claim 记录坏 ⇒ 走既有的撕裂 claim 路(有非终局行就收编,否则隔离出 activeDir)。都是**可用性**损失,不开任何门 ⇒ F。修前三面全零披露(店自己的 `typeof … continue` 与 core 的静默跳过);计数让「这台机器的 run 账在腐」在遥测里显形。⚠️ `RUN_STORE_STRICT_HYDRATE=on` 的射程是「文件读不出」,不含「文件里有坏行」—— strict 档下坏行照走本 tag(未改 strict 语义,见普查表第 96 行)。";
|
|
311
|
+
};
|
|
312
|
+
readonly "server.resume-anchor.corrupt-record-skipped": {
|
|
313
|
+
readonly cls: "F";
|
|
314
|
+
readonly note: "S-582(`plugins/file-resume-anchor-store.ts`,local 车道的 E18 续跑锚日志):一条锚记录读不出形(`42` / 缺 eventId·entryId / 中间半截 JSON)⇒ 跳过它继续重放。最坏后果 = 那个 eventId 不再解析得出(`resumeAt` / `rewindFilesTo` 对它答 404 `resume_at.unknown_event` / `rewind_files_to.unknown_event` —— `boot/resolve-spec.ts` 的解析腿;下一次 put 会把它重记)。锚是**定位**事实,不是授权事实(属主守卫在行上另有 `owner` 列)⇒ F。修前零披露。";
|
|
315
|
+
};
|
|
316
|
+
readonly "server.send-file-ledger.corrupt-record-skipped": {
|
|
317
|
+
readonly cls: "F";
|
|
318
|
+
readonly note: "S-582(`plugins/send-file-ledger.ts`,local 车道的发文件账本):一条账行读不出形(`42` / 缺 id·scope·objectKey 三元组 / 中间半截 JSON)⇒ 跳过它继续。最坏后果 = 那条已签发的链接在它租户的列表里**看不见**(对象本身没动;账本本来就不存 URL)。治理面少一行是可见性损失,不是放行 ⇒ F;但它恰是治理面要看的那一类事实,所以必须留痕 —— 修前零披露。";
|
|
319
|
+
};
|
|
304
320
|
};
|
|
305
321
|
/** 词表键推导的闭集类型——未登记的 tag 传不进 {@link recordFailOpen}(编译期拒)。 */
|
|
306
322
|
export type FailOpenTag = keyof typeof FAIL_OPEN_TAGS;
|
|
@@ -250,7 +250,11 @@ export const FAIL_OPEN_TAGS = {
|
|
|
250
250
|
},
|
|
251
251
|
"server.run-terminal.redact-threw": {
|
|
252
252
|
cls: "F",
|
|
253
|
-
note: "三个发射座(errorMessage 终态文案 / 诊断文本 diag / 工件 artifact,probe 前缀判别;普查 56 行;补丁头 patch-head 那座随 7.83.0 / S-113 的 redactPatchHead 一并退役):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(RangeError
|
|
253
|
+
note: "三个发射座(errorMessage 终态文案 / 诊断文本 diag / 工件 artifact,probe 前缀判别;普查 56 行;补丁头 patch-head 那座随 7.83.0 / S-113 的 redactPatchHead 一并退役):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(RangeError:关键字/前缀臂那一形由 S-219 的 atLeast 铸点闭掉;JSON 秘密字段臂的转义对循环与 PEM 栅栏臂的标签交替循环由 S-265 换成手写线性扫描器闭掉 —— 已知形全部闭掉,本臂留作结构性兜底,计数恒零是证据、不归零即新形)⇒ 落固定占位 «redacted:oversized»——绝不落原文(安全轴 fail-closed),也绝不阻断终态翻转与 claim 释放(可用性)。方向刻意:一条超大错误体不得把 run 卡在 running。",
|
|
254
|
+
},
|
|
255
|
+
"server.run-terminal.provider-echo-shaped": {
|
|
256
|
+
cls: "F",
|
|
257
|
+
note: "S-283(7.105.0,`observability/run-terminal.ts` 的 `admitProviderEcho`,唯一判决口;S-889 起动作与 core #1204 / 7.34.1 源头闸同一条):模型服务商回声、引擎逐字转交的不透明 id(`apiFailure.requestId` / `responseModel`,以及账本孪生 `turn_end.responseModel`、子代通告 `task_notification.apiFailure.requestId` 与 `POST /v1/side-query` 200 体的 `responseModel`;铸点 + 账本汇点两口)越出形状(字符串、长度 1–128、每个码元是可见 ASCII 0x21–0x7E;空串与非串值都在外)⇒ **缺席**(键删掉 / 不铸)。方向是 **fail-closed**(越界字节不上 wire / 不落库 / 不进日志),不截断、不过脱敏器(不透明 id,脱敏会改坏它 —— 与 S-270 structuredOutput 同方向)。放行的最坏后果 = 这枚 id / 模型名在 wire 上缺席(`apiFailure` 留 `{}` = 「provider 故障、无可补充标签」,core 契约原句),运维拿不到它去服务商那边对单;detail 只带座位 / 原长或类型,**不带值**。计数非零 = 这台部署接的端点(或中间网关)在回显越界字节,运维按座位去查那一跳;读面不改写存量行,存量越界行每次回放各计一次。",
|
|
254
258
|
},
|
|
255
259
|
"server.run-terminal.redact-capped": { cls: "F", note: "S-107:终态自由文本(errorMessage/blockedReason/remoteEnvFailures[].message/error 列/done·failed 行)长度 > REDACT_INPUT_CAP(64KB)⇒ 整段换 «redacted:oversized»(fail-closed 取证丢弃,不截断不扫);计数=多少条终态文案被整段丢弃" },
|
|
256
260
|
"server.run-terminal.artifact-capped": { cls: "F", note: "S-107:leader_run 行上的工件字节(salvaged[].patch / candidatePatch.patch / merge.candidatePatch / merge.rej)长度 > ARTIFACT_INPUT_CAP(256KB)⇒ 整份换 «redacted:oversized-artifact»(fail-closed 取证丢弃;原字节仍在 worker 分支/merge 咽喉);计数=多少份工件被整份丢弃" },
|
|
@@ -274,6 +278,18 @@ export const FAIL_OPEN_TAGS = {
|
|
|
274
278
|
cls: "F",
|
|
275
279
|
note: "S-609(`routes/approvals-assistant.ts` 的 `GET /v1/assistant/inbox`):某一行的续跑上下文(checkpoint_ctx)读出来是**坏体**(店的拒绝契约 `StoreCorruptError`;local = 档不是 JSON / 信封不合形,SQL = 列是合法 JSON 但不是一只对象)⇒ **这一行**的 `objective` 落 `null`,列表其余照常。放行的最坏后果 = inbox 上这一张卡不显示它属于哪个任务(纯展示位;这一格不参与任何决裁、续跑或门判 —— 续跑腿与 plan_review 门对同一行各自走读口、坏体一律响亮 500)。不放行的代价是整只 `summaries.map` 抛出 ⇒ 一行坏体打掉整个用户的 inbox。故 F 类;必须留痕,因为修前那句 `.catch(() => null)` 让「坏体」与「本来就没有」同形无痕(detail = 读口转述的 `where`,人拿它去修那一行)。缺席(行不在 / 店抖动)**不**记本 tag:那是读口的刻意保守,不是失败开放。",
|
|
276
280
|
},
|
|
281
|
+
"server.run-store.corrupt-record-skipped": {
|
|
282
|
+
cls: "F",
|
|
283
|
+
note: "S-582(`plugins/file-run-store.ts`,local 车道的 File 形 run 店;三个盘面同一只座):盘上一条记录读不出形 —— 一行 `42` / 缺本店必填键的对象 / 中间的半截 JSON(撕尾不走本 tag,那是崩溃的正常落盘形)⇒ 跳过它继续。三个盘面各自的最坏后果:`run.json` 坏 ⇒ 那条 run 读成**缺席**(`GET /v1/runs/:id` 404,listRuns 少一行;它若占着 claim,EEXIST 路按「行不在」判残骸接管);`events.jsonl` 一行坏 ⇒ 回放少一个事件(其余照读、按 seq 去重不变);claim 记录坏 ⇒ 走既有的撕裂 claim 路(有非终局行就收编,否则隔离出 activeDir)。都是**可用性**损失,不开任何门 ⇒ F。修前三面全零披露(店自己的 `typeof … continue` 与 core 的静默跳过);计数让「这台机器的 run 账在腐」在遥测里显形。⚠️ `RUN_STORE_STRICT_HYDRATE=on` 的射程是「文件读不出」,不含「文件里有坏行」—— strict 档下坏行照走本 tag(未改 strict 语义,见普查表第 96 行)。",
|
|
284
|
+
},
|
|
285
|
+
"server.resume-anchor.corrupt-record-skipped": {
|
|
286
|
+
cls: "F",
|
|
287
|
+
note: "S-582(`plugins/file-resume-anchor-store.ts`,local 车道的 E18 续跑锚日志):一条锚记录读不出形(`42` / 缺 eventId·entryId / 中间半截 JSON)⇒ 跳过它继续重放。最坏后果 = 那个 eventId 不再解析得出(`resumeAt` / `rewindFilesTo` 对它答 404 `resume_at.unknown_event` / `rewind_files_to.unknown_event` —— `boot/resolve-spec.ts` 的解析腿;下一次 put 会把它重记)。锚是**定位**事实,不是授权事实(属主守卫在行上另有 `owner` 列)⇒ F。修前零披露。",
|
|
288
|
+
},
|
|
289
|
+
"server.send-file-ledger.corrupt-record-skipped": {
|
|
290
|
+
cls: "F",
|
|
291
|
+
note: "S-582(`plugins/send-file-ledger.ts`,local 车道的发文件账本):一条账行读不出形(`42` / 缺 id·scope·objectKey 三元组 / 中间半截 JSON)⇒ 跳过它继续。最坏后果 = 那条已签发的链接在它租户的列表里**看不见**(对象本身没动;账本本来就不存 URL)。治理面少一行是可见性损失,不是放行 ⇒ F;但它恰是治理面要看的那一类事实,所以必须留痕 —— 修前零披露。",
|
|
292
|
+
},
|
|
277
293
|
};
|
|
278
294
|
export function failOpenTagForDroppedFrame(frameType) {
|
|
279
295
|
switch (frameType) {
|
|
@@ -241,7 +241,7 @@ export function createMetrics() {
|
|
|
241
241
|
m.counter("task_model_not_allowlisted_resume_total", "Resumed legs whose model/compactionModel is outside the @-mention allowlist (allowed to keep running by design — the allowlist gates fresh picks only)");
|
|
242
242
|
m.counter("task_model_unavailable_fallback_total", "Resumed legs whose explicit model pick names a catalog entry that is unavailable on this deployment (no brain mounted for its provider / entry fails its schema) — the leg runs on the default model; fresh submits get 400 request.model_unavailable");
|
|
243
243
|
m.counter("prompt_constitution_total", "Prompt assembly constitution mode per task (core 1.243 提示词主权): steady state = core; provider-assembled/replaced/legacy warrant eyes");
|
|
244
|
-
m.counter("rewind_snapshot_failed_total", "Per-turn rewind file snapshots that failed, by the engine's FileHistoryResult.error.code (snapshot_failed/aborted/…; `unknown` = the
|
|
244
|
+
m.counter("rewind_snapshot_failed_total", "Per-turn rewind file snapshots that failed, by the engine's FileHistoryResult.error.code (snapshot_failed/aborted/…; `unknown` = the text carried no word of that closed set, e.g. the boundary-timeout arm). COARSE on purpose — whether a failure is permanent (the same file will fail every turn until the deployment changes) is counted by file_history_permanent_failure_total instead, from the typed error object rather than from prose");
|
|
245
245
|
m.counter("verifications_total", "Tasks run through the verification gate (1.44), by final verdict");
|
|
246
246
|
m.counter("cascade_total", "Tasks run through the model cascade (1.45), by outcome (passed/exhausted)");
|
|
247
247
|
m.counter("degenerate_total", "Degenerate-output failures (1.59), by salvage class (a/b/unknown)");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RunRecord } from "../plugins/store-contracts.js";
|
|
1
|
+
import { type PersistedTaskResult, type RunRecord } from "../plugins/store-contracts.js";
|
|
2
2
|
/** 一次终局写口交给观测面的原料(store 侧装配,**未脱敏** —— 脱敏在本模块内做,见 {@link notifyRunTerminal})。 */
|
|
3
3
|
export interface RunTerminalRecord {
|
|
4
4
|
/** 这条 run 的账本 id = `GET /v1/runs/:id` 的 id(本仓 run 行主键名为 taskId,同一个值)。 */
|
|
@@ -78,10 +78,12 @@ export declare function notifyRunTerminal(rec: RunTerminalRecord): void;
|
|
|
78
78
|
/** S-105(test [ref] 旁生):终态**持久化**的 `errorMessage` 与日志副本同一脱敏口——provider 错误体(403 体带回显请求头/
|
|
79
79
|
* 订阅链接)里的密钥形不得原文落 run 行再由 `GET /v1/runs/:id` 直出。三 run store 的 `setTerminal` 写入前同调此函数;
|
|
80
80
|
* `undefined` 透传(缺席仍缺席),不截断(日志上限是日志的事,行上保留全文供取证)。 */
|
|
81
|
-
/** r4 最终轮 [medium]:`redactSecrets`
|
|
82
|
-
*
|
|
81
|
+
/** r4 最终轮 [medium]:`redactSecrets` 曾会抛 `RangeError`(关键字/前缀臂那一形由 S-219 的 `atLeast` 铸点闭掉;JSON 秘密字段臂的
|
|
82
|
+
* 转义对循环与 PEM 栅栏臂的标签交替循环由 S-265 换成手写线性扫描器闭掉 —— 已知形全部闭掉);S-105 把脱敏
|
|
83
83
|
* 挪到 `setTerminal` **入口**后,这个抛会先于 CAS/claim 释放 ⇒ run 卡 `running`、会话 claim 悬挂到 reaper。这里 **fail-closed
|
|
84
|
-
* 不抛**:脱敏失败 ⇒ 落固定占位 `«redacted:oversized»`(绝不落原文,也绝不阻断终态翻转),并走既有 fail-open 遥测 tag。
|
|
84
|
+
* 不抛**:脱敏失败 ⇒ 落固定占位 `«redacted:oversized»`(绝不落原文,也绝不阻断终态翻转),并走既有 fail-open 遥测 tag。
|
|
85
|
+
* 🔴 S-265 之后这条 throw 臂**照留**(结构性兜底,不依赖「脱敏器今天抛不抛」):它守的是「终态翻转不被一次脱敏异常拦下」,
|
|
86
|
+
* 而脱敏器是一张会长新臂的表 —— 计数 `server.run-terminal.redact-threw` 恒零是证据,不归零就是新形,运维按 detail 回溯。 */
|
|
85
87
|
export declare const OVERSIZED_REDACTION_PLACEHOLDER = "\u00ABredacted:oversized\u00BB";
|
|
86
88
|
/** S-107 窄范围重扫 [medium]:`redactSecrets` 对 PEM 起始栅栏密集的多 MB 文案**不抛但慢**(实测 1MB 1.4s / 8MB 7.9s 同步阻塞
|
|
87
89
|
* 事件循环),而终态座每次写要扫两遍(appendEvent + setTerminal 入口)、每次读再扫一遍。超过本上限的自由文本**整段**换占位
|
|
@@ -111,20 +113,26 @@ export declare function redactErrorMessage(raw: string | undefined): string | un
|
|
|
111
113
|
* ⚠️ **命中那一次仍然给剥后文本**(S-103 的「先扫描后渲染」不动摇):把零宽字符塞进密钥中间的那条文本,
|
|
112
114
|
* 渲染的必须是剥后那份,否则消费端把残片拼回原钥。
|
|
113
115
|
* ② **扫描器抛 ⇒ 登记过的占位(fail-closed),不把整条 run 拖下水**(codex r1 [high],亲核实测后修)。
|
|
114
|
-
* `redactSecrets` 的 JSON
|
|
116
|
+
* 立案时 `redactSecrets` 的 JSON 秘密字段臂含多字符循环体(`(?:\\.[^"\\]*)*`),实测
|
|
115
117
|
* `{"password":"<400 万个 \\n 转义对>"}`(8MB)抛 `RangeError`。`result` 接进来之前这条路不存在;接进来之后,
|
|
116
118
|
* 裸调用会让异常穿过 `redactTerminalResult` → 三孪生 `setTerminal`,**一条跑成功的 run 落成 failed + result:null**
|
|
117
119
|
* (实测读数)。⇒ 与本文件既有的 {@link redactErrorMessage} / `redactDiagnosticText` / `redactArtifactText`
|
|
118
120
|
* 三座**同一条**处置:抛就换占位、计数、留痕([ref] 的 loud,不是静默 fail-open)。丢的是这一条病态文本,
|
|
119
|
-
* 不是这条 run。根因(那条正则)
|
|
121
|
+
* 不是这条 run。根因(那条正则)由 S-265(7.105.0)换成手写线性扫描器闭掉(同批闭掉同类第二例 PEM 栅栏标签循环);
|
|
122
|
+
* 本处置**照留** —— 理由同 {@link OVERSIZED_REDACTION_PLACEHOLDER} 顶注末句。
|
|
120
123
|
* ③ **不设体量上限**(裁定):`result` 住在 `result` JSON/JSONB 列(亲核 `plugins/tidb-pool.ts` 与
|
|
121
124
|
* `plugins/pg-pool.ts` 的 DDL),数 MB 终稿是正常形 —— {@link redactErrorMessage} 的两道上限是为
|
|
122
125
|
* `task_run.error` 那一列 `TEXT(65 535 字节)` 定的,套到这里等于用静默的数据损坏换一次脱敏。
|
|
123
126
|
*/
|
|
124
127
|
export declare function redactModelText(raw: string): string;
|
|
125
|
-
/**
|
|
126
|
-
* `
|
|
127
|
-
export declare
|
|
128
|
+
/** 回声座上的值留不留:形内 ⇒ `true`(原样留);`undefined` ⇒ `false`(缺席本身,不计数);其余越界 ⇒ `false` + 计数。
|
|
129
|
+
* 调用方按 `false` 删键 / 不铸键 —— 本函数是全部回声座(结果面 / 账本孪生 / 子代通告 / side-query 200 体)的**唯一**判决口。 */
|
|
130
|
+
export declare function admitProviderEcho(value: unknown, seat: string): value is string;
|
|
131
|
+
/** {@link redactTerminalResult} 会碰的全部顶层键 = 现行形里**不是** `"verbatim"` 的行(`"redacted"` ∪ S-283 的
|
|
132
|
+
* `"provider-echo"`)∪ 旧形平面键;`memoRedactTerminalResult` 的记忆键由它派生(两处同源,见 `RESULT_KEY_TREATMENT` 顶注)。
|
|
133
|
+
* 🔴 S-283 起改名(原 `REDACTABLE_RESULT_LEAVES`):回声键不脱敏但**会被改**(越界 ⇒ 删键),漏进记忆键集 = 读面记忆腿对存量行
|
|
134
|
+
* O(1) 原样直返,读面纵深对那几片恒不生效([ref] 立案时 `editedFiles` 那一格的同一个病)。 */
|
|
135
|
+
export declare const TREATED_RESULT_LEAVES: readonly string[];
|
|
128
136
|
export declare function redactTerminalResult<T extends {
|
|
129
137
|
terminal?: unknown;
|
|
130
138
|
/** 旧形(≤7.63.0)的三个顶层座;形上取 `unknown` —— 读盘面对盘上字节不作假设。 */
|
|
@@ -144,7 +152,25 @@ export declare function redactTerminalResult<T extends {
|
|
|
144
152
|
editedFiles?: unknown;
|
|
145
153
|
/** S-394:形上同样 `unknown` —— 本函数是读盘面,`stats` 的树形逐层现判(见 {@link redactStats})。 */
|
|
146
154
|
stats?: unknown;
|
|
155
|
+
/** S-283:provider 回声三键(形上 `unknown`,读盘面纪律同上;处置见 {@link PROVIDER_ECHO_SHAPERS})。 */
|
|
156
|
+
apiFailure?: unknown;
|
|
157
|
+
strandedHumanAnswers?: unknown;
|
|
158
|
+
responseModel?: unknown;
|
|
147
159
|
} | null | undefined>(result: T): T;
|
|
160
|
+
/**
|
|
161
|
+
* 🔴 **S-173 —— run 行 `result` 列的唯一入口**:先过出口封口({@link sealResultForExit}:摘恢复凭据 + 折域外码),
|
|
162
|
+
* 再过 {@link redactTerminalResult}。三家 run 店(SQL 两方言 / file / memory)的**三个**写口 —— `setTerminal` 与
|
|
163
|
+
* park 两口 `setSuspended` / `setNeedsReview` —— 入口各调一次,列上的字节因此只有一条来路。
|
|
164
|
+
*
|
|
165
|
+
* 为什么封口也挪到店的入口(而不只靠调用方):S-173 之前这一列只收终局结果,而终局结果上凭据只可能藏在
|
|
166
|
+
* `failed.nestedPause` 一处、且每个调用点都已先封口。park 两口收的是 **`paused` 因由** —— 那一臂按 core 的
|
|
167
|
+
* 类型**必带** `token`(引擎内契约)。凭据挂在「每个调用点都记得先封口」上 = 漏一处就是一次凭据落盘、且
|
|
168
|
+
* 不会红([ref] 安全轴:fail-closed,不是约定)。两件事都**幂等**(封过口的再封是同形、脱过的再脱是同一引用),
|
|
169
|
+
* 所以调用点已封过口的腿零行为变化。
|
|
170
|
+
*
|
|
171
|
+
* `null` 原样(= 这条腿没有结果可落:引擎没给终局就收流 / 断连竞态 park / 重开 re-park)。
|
|
172
|
+
*/
|
|
173
|
+
export declare function sealRunRowResult(result: PersistedTaskResult | null): PersistedTaskResult | null;
|
|
148
174
|
/** S-107(第五轮合并重扫 wf_f00c00bc 两根 [high]):同一个 `TaskResult` 除 run 行外还被逐字追加进 run_events 的 `done` 行
|
|
149
175
|
* (bg/verify/cascade/stream/resume 五腿),`failed` 行带原文 `errorMessage`,`GET /v1/runs/:id/events` 原样回放。
|
|
150
176
|
* 三孪生 `appendEvent` 入口 + 事件读面(旧行纵深)同调本函数:只认 `done.result`(整块走 `redactTerminalResult`)与
|
|
@@ -188,7 +214,7 @@ export declare function redactLeaderResultWithFlag(result: unknown): {
|
|
|
188
214
|
changed: boolean;
|
|
189
215
|
};
|
|
190
216
|
export declare function memoRedactedRead<T>(seat: string, raw: T, redact: (v: T) => T): T;
|
|
191
|
-
/** run 终态 result
|
|
217
|
+
/** run 终态 result 的读面记忆只按**本函数会改字节的那几叶**({@link TREATED_RESULT_LEAVES}:脱敏叶 ∪ S-283 回声叶 ∪ 旧形平面键,属主是
|
|
192
218
|
* `RESULT_KEY_TREATMENT`)做键与留体——
|
|
193
219
|
* 第十六轮重扫:按整块 result 摘要每次轮询要 stringify 一份可能数 MB 的 result,且大 result 的脏行永远存不进去。叶全缺席
|
|
194
220
|
* ⇒ O(1) 直返(`redactTerminalResult` 本就同引用)。
|
|
@@ -201,6 +227,30 @@ export declare function memoRedactedRead<T>(seat: string, raw: T, redact: (v: T)
|
|
|
201
227
|
* `editedFiles` 那一格的病)。读数:8MB result 的一次摘要 ~10ms 量级,远小于一次全扫;干净行仍只摘要
|
|
202
228
|
* 一次即进 clean 集,脏行超过 {@link READ_MEMO_DIRTY_MAX_ENTRY_BYTES} 时退化为每次重扫(既有形)。 */
|
|
203
229
|
export declare function memoRedactTerminalResult<T extends Parameters<typeof redactTerminalResult>[0]>(seat: string, result: T): T;
|
|
230
|
+
/**
|
|
231
|
+
* 🔴 **账本行的读面口 —— 全部耐久读腿共用这一只**(S-269,7.105.0)。
|
|
232
|
+
*
|
|
233
|
+
* 脱敏的**属主**仍是 {@link redactLedgerEventData}(写读共用的唯一汇点,规则一张:哪些行型有自由文本座、怎么脱);
|
|
234
|
+
* 本函数**不是第二只脱敏器**,只在汇点外面加一层**按型记忆**(读面每次轮询 / 每个观看者 / 每次重连都重放同一批
|
|
235
|
+
* 不可变的行):
|
|
236
|
+
* · `done` 行 —— 只按 `result` 的可脱敏叶记忆({@link memoRedactTerminalResult};整块 result 可能数 MB);
|
|
237
|
+
* · `failed` 行与模型自由文本两行(`text` / `reasoning`,词表属主 `trace/ledger-events.ts`)—— 按内容摘要记忆({@link memoRedactedRead});
|
|
238
|
+
* · 其余行型 —— 直调汇点(park 行只有一个小 gate,扫一次的代价远低于摘要 + 记忆表位;其余型汇点逐字透传)。
|
|
239
|
+
*
|
|
240
|
+
* **三条耐久读腿同调**:`GET /v1/runs/:id/events`(账本裸回放)、`GET /v1/tasks/:id/turns`(`projectEvents`)、
|
|
241
|
+
* trace SSE `GET /v1/tasks/:id/stream`(`mapTraceEvent`)。此前只有第一条在 `formatEvent` 里手写了这段分派,后两条
|
|
242
|
+
* **直吃原行** —— 新行在三孪生 `appendEvent` 落库前已脱,所以只打在**存量行**上:7.75.1(B-106)之前 `text` 行的写口
|
|
243
|
+
* 不脱,那批行在两条 trace 腿上原字节回放(S-269,车EC U-2)。分派收进本函数、三条腿同调 ⇒ 读面纵深一条规则。
|
|
244
|
+
*
|
|
245
|
+
* **性能取舍(读实现后定)**:汇点对已脱行在**输出**上幂等(`redactModelText` 认不出凭据形就回同一引用),但**代价不是
|
|
246
|
+
* 空过** —— 每次都整段重扫(`redactSecrets` 全池一遍)。所以读面走记忆:同一行第二次起只付一次 sha256 摘要 + 集合查找
|
|
247
|
+
* (车QO 本机实测,8.6MB 散文 min-of-5:整段重扫 112ms vs 摘要〔含 `JSON.stringify`〕31ms,约 3.6×);记忆表有界(干净 8192 条 / 脏 512 条且总字节封顶,见 {@link memoRedactedRead})。
|
|
248
|
+
* **没**选「行上打已脱标记 ⇒ 跳过」:那要在三孪生写口加一个持久字段,而需要读面纵深的恰恰是**没有**标记的存量行 ——
|
|
249
|
+
* 标记只能省新行的扫描;记忆对新旧行一视同仁,且不新开一张持久面。
|
|
250
|
+
* trace 两腿对 `done` 行只投 `seq`(不投 `result`),它的摘要是**白付**的那一份;**刻意**不按「这条腿投哪些键」裁掉 ——
|
|
251
|
+
* 那等于在读腿里手抄一份汇点的行型表([ref]),汇点将来加一种带自由文本的行型时读腿静默漏。
|
|
252
|
+
*/
|
|
253
|
+
export declare function redactLedgerEventForRead(type: string, data: unknown): unknown;
|
|
204
254
|
export declare function diffScanShadowCountForTest(): number;
|
|
205
255
|
export declare function resetDiffScanShadowCountForTest(): void;
|
|
206
256
|
export declare function resetReadRedactionMemoForTest(): void;
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { redactSecrets, normalizeForRedaction } from "../trace/redact.js";
|
|
3
3
|
import { isModelTextLedgerEventType, isParkLedgerEventType } from "../trace/ledger-events.js";
|
|
4
4
|
import { isUnsuccessfulRunStatus } from "../plugins/store-contracts.js";
|
|
5
|
+
import { sealResultForExit } from "../terminal.js";
|
|
5
6
|
import { errorText } from "./err-text.js";
|
|
6
7
|
import { recordFailOpen } from "./fail-open.js";
|
|
7
8
|
const MESSAGE_CAP = 1024;
|
|
@@ -96,28 +97,72 @@ export function redactModelText(raw) {
|
|
|
96
97
|
return OVERSIZED_REDACTION_PLACEHOLDER;
|
|
97
98
|
}
|
|
98
99
|
}
|
|
100
|
+
const PROVIDER_ECHO_SHAPE = /^[\x21-\x7E]{1,128}$/;
|
|
101
|
+
function isProviderEchoShape(value) {
|
|
102
|
+
return PROVIDER_ECHO_SHAPE.test(value);
|
|
103
|
+
}
|
|
104
|
+
export function admitProviderEcho(value, seat) {
|
|
105
|
+
if (value === undefined)
|
|
106
|
+
return false;
|
|
107
|
+
if (typeof value === "string" && isProviderEchoShape(value))
|
|
108
|
+
return true;
|
|
109
|
+
const what = typeof value === "string" ? `len=${value.length}` : `type=${value === null ? "null" : typeof value}`;
|
|
110
|
+
recordFailOpen("server.run-terminal.provider-echo-shaped", `seat=${seat} ${what}`);
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
function shapeEchoMembers(node, table, seat) {
|
|
114
|
+
if (node === null || typeof node !== "object" || Array.isArray(node))
|
|
115
|
+
return node;
|
|
116
|
+
const n = node;
|
|
117
|
+
let out;
|
|
118
|
+
for (const [key, treatment] of Object.entries(table)) {
|
|
119
|
+
const v = n[key];
|
|
120
|
+
if (v === undefined)
|
|
121
|
+
continue;
|
|
122
|
+
switch (treatment) {
|
|
123
|
+
case "verbatim":
|
|
124
|
+
case "provider-join-key":
|
|
125
|
+
break;
|
|
126
|
+
case "provider-echo":
|
|
127
|
+
if (!admitProviderEcho(v, `${seat}.${key}`))
|
|
128
|
+
delete (out ??= { ...n })[key];
|
|
129
|
+
break;
|
|
130
|
+
default: {
|
|
131
|
+
const unhandled = treatment;
|
|
132
|
+
throw new Error(`unhandled echo-member treatment: ${String(unhandled)}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return out ?? node;
|
|
137
|
+
}
|
|
138
|
+
function shapeEchoRows(rows, table, seat) {
|
|
139
|
+
if (!Array.isArray(rows))
|
|
140
|
+
return rows;
|
|
141
|
+
const next = rows.map((row) => shapeEchoMembers(row, table, seat));
|
|
142
|
+
return next.every((x, i) => x === rows[i]) ? rows : next;
|
|
143
|
+
}
|
|
99
144
|
const MAX_EDITED_FILES = 1000;
|
|
100
145
|
const RESULT_KEY_TREATMENT = {
|
|
101
146
|
terminal: "redacted",
|
|
102
147
|
remoteEnvFailures: "redacted",
|
|
103
148
|
editedFiles: "redacted",
|
|
149
|
+
responseModel: "provider-echo",
|
|
150
|
+
apiFailure: "provider-echo",
|
|
151
|
+
strandedHumanAnswers: "provider-echo",
|
|
104
152
|
taskId: "verbatim",
|
|
105
153
|
runId: "verbatim",
|
|
106
154
|
sessionId: "verbatim",
|
|
107
155
|
model: "verbatim",
|
|
108
|
-
responseModel: "verbatim",
|
|
109
156
|
compaction: "verbatim",
|
|
110
157
|
effectiveReasoning: "verbatim",
|
|
111
158
|
result: "redacted",
|
|
112
159
|
salvagedOutput: "redacted",
|
|
113
|
-
apiFailure: "verbatim",
|
|
114
160
|
retryAfterMs: "verbatim",
|
|
115
161
|
degraded: "verbatim",
|
|
116
162
|
structuredOutput: "verbatim",
|
|
117
163
|
rewindNotes: "redacted",
|
|
118
164
|
haltedOnUserRejection: "verbatim",
|
|
119
165
|
haltedByUser: "verbatim",
|
|
120
|
-
strandedHumanAnswers: "verbatim",
|
|
121
166
|
pendingApproval: "verbatim",
|
|
122
167
|
approvalWaitedMs: "verbatim",
|
|
123
168
|
effectiveReadFace: "verbatim",
|
|
@@ -126,9 +171,28 @@ const RESULT_KEY_TREATMENT = {
|
|
|
126
171
|
effectiveMemoryScopes: "verbatim",
|
|
127
172
|
stats: "redacted",
|
|
128
173
|
};
|
|
174
|
+
const API_FAILURE_TREATMENT = {
|
|
175
|
+
status: "verbatim",
|
|
176
|
+
requestId: "provider-echo",
|
|
177
|
+
};
|
|
178
|
+
const STRANDED_ANSWER_TREATMENT = {
|
|
179
|
+
deliveryId: "verbatim",
|
|
180
|
+
toolCallId: "provider-join-key",
|
|
181
|
+
};
|
|
182
|
+
const PROVIDER_ECHO_SHAPERS = {
|
|
183
|
+
responseModel: (v) => (admitProviderEcho(v, "responseModel") ? v : undefined),
|
|
184
|
+
apiFailure: (v) => shapeEchoMembers(v, API_FAILURE_TREATMENT, "apiFailure"),
|
|
185
|
+
strandedHumanAnswers: (v) => shapeEchoRows(v, STRANDED_ANSWER_TREATMENT, "strandedHumanAnswers[]"),
|
|
186
|
+
};
|
|
187
|
+
{
|
|
188
|
+
const judged = Object.entries(RESULT_KEY_TREATMENT).filter(([, t]) => t === "provider-echo").map(([k]) => k).sort();
|
|
189
|
+
const wired = Object.keys(PROVIDER_ECHO_SHAPERS).sort();
|
|
190
|
+
if (judged.join(",") !== wired.join(","))
|
|
191
|
+
throw new Error(`run-terminal: provider-echo classification [${judged.join(",")}] ≠ shaper table [${wired.join(",")}]`);
|
|
192
|
+
}
|
|
129
193
|
const LEGACY_RESULT_LEAVES = ["errorMessage", "blockedReason", "checkpointGate"];
|
|
130
|
-
export const
|
|
131
|
-
...Object.entries(RESULT_KEY_TREATMENT).filter(([, t]) => t
|
|
194
|
+
export const TREATED_RESULT_LEAVES = [
|
|
195
|
+
...Object.entries(RESULT_KEY_TREATMENT).filter(([, t]) => t !== "verbatim").map(([k]) => k),
|
|
132
196
|
...LEGACY_RESULT_LEAVES,
|
|
133
197
|
];
|
|
134
198
|
export function redactTerminalResult(result) {
|
|
@@ -234,8 +298,25 @@ export function redactTerminalResult(result) {
|
|
|
234
298
|
changed = true;
|
|
235
299
|
}
|
|
236
300
|
}
|
|
301
|
+
const rr = result;
|
|
302
|
+
for (const [key, shape] of Object.entries(PROVIDER_ECHO_SHAPERS)) {
|
|
303
|
+
const v = rr[key];
|
|
304
|
+
if (v === undefined || shape === undefined)
|
|
305
|
+
continue;
|
|
306
|
+
const next = shape(v);
|
|
307
|
+
if (next === v)
|
|
308
|
+
continue;
|
|
309
|
+
if (next === undefined)
|
|
310
|
+
delete out[key];
|
|
311
|
+
else
|
|
312
|
+
out[key] = next;
|
|
313
|
+
changed = true;
|
|
314
|
+
}
|
|
237
315
|
return changed ? out : result;
|
|
238
316
|
}
|
|
317
|
+
export function sealRunRowResult(result) {
|
|
318
|
+
return result === null ? null : redactTerminalResult(sealResultForExit(result));
|
|
319
|
+
}
|
|
239
320
|
export function redactLedgerEventData(type, data) {
|
|
240
321
|
if (data === null || typeof data !== "object" || Array.isArray(data))
|
|
241
322
|
return data;
|
|
@@ -263,8 +344,18 @@ export function redactLedgerEventData(type, data) {
|
|
|
263
344
|
const next = redactCheckpointGate(gate);
|
|
264
345
|
return next === gate ? data : { ...data, gate: next };
|
|
265
346
|
}
|
|
347
|
+
if (type === "turn_end")
|
|
348
|
+
return shapeEchoMembers(data, TURN_END_ECHO_TREATMENT, "turn_end");
|
|
349
|
+
if (type === "task_notification") {
|
|
350
|
+
const af = data.apiFailure;
|
|
351
|
+
if (af === undefined)
|
|
352
|
+
return data;
|
|
353
|
+
const next = shapeEchoMembers(af, API_FAILURE_TREATMENT, "task_notification.apiFailure");
|
|
354
|
+
return next === af ? data : { ...data, apiFailure: next };
|
|
355
|
+
}
|
|
266
356
|
return data;
|
|
267
357
|
}
|
|
358
|
+
const TURN_END_ECHO_TREATMENT = { responseModel: "provider-echo" };
|
|
268
359
|
export function redactWorkerReportError(report) {
|
|
269
360
|
const gate = redactCheckpointGate(report.checkpointGate);
|
|
270
361
|
const error = report.error === undefined ? undefined : (redactErrorMessage(report.error) ?? "");
|
|
@@ -385,7 +476,7 @@ const STATS_GATE_ROW_TREATMENT = {
|
|
|
385
476
|
decision: "verbatim",
|
|
386
477
|
toolName: "verbatim",
|
|
387
478
|
toolArg: "model-text",
|
|
388
|
-
toolCallId: "
|
|
479
|
+
toolCallId: "provider-join-key",
|
|
389
480
|
};
|
|
390
481
|
const STATS_REPETITION_EVENT_TREATMENT = {
|
|
391
482
|
turn: "verbatim",
|
|
@@ -416,6 +507,8 @@ function redactStatsNode(node, table) {
|
|
|
416
507
|
switch (treatment) {
|
|
417
508
|
case "verbatim":
|
|
418
509
|
break;
|
|
510
|
+
case "provider-join-key":
|
|
511
|
+
break;
|
|
419
512
|
case "model-text":
|
|
420
513
|
if (typeof v === "string")
|
|
421
514
|
next = redactModelText(v);
|
|
@@ -670,15 +763,32 @@ export function memoRedactTerminalResult(seat, result) {
|
|
|
670
763
|
if (result === undefined || result === null)
|
|
671
764
|
return result;
|
|
672
765
|
const r = result;
|
|
673
|
-
if (
|
|
766
|
+
if (TREATED_RESULT_LEAVES.every((k) => r[k] === undefined))
|
|
674
767
|
return result;
|
|
675
768
|
const leaves = {};
|
|
676
|
-
for (const k of
|
|
769
|
+
for (const k of TREATED_RESULT_LEAVES)
|
|
677
770
|
leaves[k] = r[k];
|
|
678
771
|
const redacted = memoRedactedRead(seat, leaves, (l) => redactTerminalResult(l));
|
|
679
772
|
if (redacted === leaves)
|
|
680
773
|
return result;
|
|
681
|
-
|
|
774
|
+
const merged = { ...result, ...redacted };
|
|
775
|
+
for (const k of TREATED_RESULT_LEAVES)
|
|
776
|
+
if (!Object.hasOwn(redacted, k))
|
|
777
|
+
delete merged[k];
|
|
778
|
+
return merged;
|
|
779
|
+
}
|
|
780
|
+
export function redactLedgerEventForRead(type, data) {
|
|
781
|
+
if (type === "done" && data !== null && typeof data === "object") {
|
|
782
|
+
const result = data.result;
|
|
783
|
+
if (result && typeof result === "object") {
|
|
784
|
+
return { ...data, result: memoRedactTerminalResult("ev.done.result", result) };
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (type === "failed")
|
|
788
|
+
return memoRedactedRead("ev.failed", data, (d) => redactLedgerEventData(type, d));
|
|
789
|
+
if (isModelTextLedgerEventType(type))
|
|
790
|
+
return memoRedactedRead("ev.model-text", data, (d) => redactLedgerEventData(type, d));
|
|
791
|
+
return redactLedgerEventData(type, data);
|
|
682
792
|
}
|
|
683
793
|
let shadowScans = 0;
|
|
684
794
|
export function diffScanShadowCountForTest() { return shadowScans; }
|