@sema-agent/core 5.40.0 → 5.42.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/CHANGELOG.md +101 -3
- package/dist/core/auto-mode-prompt.js +1 -1
- package/dist/core/checkpoint-store.d.ts +15 -0
- package/dist/core/checkpoint-store.js +5 -1
- package/dist/core/memory-engine/file-backend.js +13 -2
- package/dist/core/memory-engine/layout.d.ts +15 -0
- package/dist/core/memory-engine/layout.js +17 -8
- package/dist/core/park-selfcheck.js +3 -1
- package/dist/core/runner/prepare-config-doors.js +20 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +192 -0
- package/dist/core/runner/prepare-hands-readface.js +283 -0
- package/dist/core/runner/prepare-task.d.ts +1 -5
- package/dist/core/runner/prepare-task.js +16 -229
- package/dist/core/runner/runtask.js +6 -1
- package/dist/core/types.d.ts +20 -0
- package/dist/core/types.js +20 -0
- package/dist/engine/harness/agent-harness.d.ts +17 -0
- package/dist/engine/harness/agent-harness.js +19 -1
- package/dist/engine/harness/types.d.ts +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/tools/fs/fs-search-tools.d.ts +8 -0
- package/dist/tools/fs/fs-search-tools.js +67 -58
- package/dist/tools/fs/search.d.ts +31 -9
- package/dist/tools/fs/search.js +110 -73
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,108 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 5.
|
|
3
|
+
## 5.42.0 — 2026-08-18
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### Added
|
|
6
|
+
- `checkpointId` — a NON-SECRET stable checkpoint identity minted beside the secret resume
|
|
7
|
+
token at every suspend site (`mintCheckpointId` exported; `cp_` + uuid, independently minted —
|
|
8
|
+
not a token digest, so token-less projection surfaces still carry it). Rides `Checkpoint` and
|
|
9
|
+
`CheckpointSummary` (single-source projection); the park self-check faithfulness probe covers it
|
|
10
|
+
(a field-whitelisting backend that drops it reports `row_not_faithful`). Identification and
|
|
11
|
+
capability are separate axes; `token` semantics untouched (still secret, still the capability).
|
|
12
|
+
Legacy rows read compatibly (`checkpointId` absent; no backfill).
|
|
13
|
+
- `task.user_followup_undrained` — stranded user FOLLOW-UPS at agent_end now announce under their
|
|
14
|
+
own EngineNotice code (one notice per family; `detail: { followUp, taskId? }`). **Code-family
|
|
15
|
+
change vs 5.41.0 (the only release with the conflated shape)**: a followUp-only stranding no
|
|
16
|
+
longer reports under `task.user_steer_undrained`, and the steer notice's `detail` no longer
|
|
17
|
+
carries a `followUp` count. Settled-frame keys unchanged. Mint extracted pure
|
|
18
|
+
(`undrainedUserInputNotices`) and pinned.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- Grep's ripgrep legs parse `rg --null` records (ripgrep terminates its own path field with NUL)
|
|
22
|
+
instead of cutting lines at a guessed `:digits:` separator. The `:digits:`/`-digits-` boundary
|
|
23
|
+
mis-parse family is gone with the guessing: deny-guarded runs whose match TEXT contains a
|
|
24
|
+
cluster (e.g. `credentials:12:`) keep the rg fast path instead of over-tripping into a full-tree
|
|
25
|
+
JS rescan; the deny tripwire still judges every record (deny-hit abandonment unchanged) and the
|
|
26
|
+
partial legs drop a killed run's cut tail whole BEFORE judging (a half filename can no longer be
|
|
27
|
+
delivered as a result row or trip a pointless rescan).
|
|
28
|
+
- The JS fallback scanner's unlimited form (`head_limit: 0`) is byte-bounded at the same figure as
|
|
29
|
+
the ripgrep exec pipe (`JS_GREP_OUTPUT_MAX_BYTES = MAX_EXEC_OUTPUT_BYTES`), head-preserving, with
|
|
30
|
+
an explicit truncation note when the ceiling is hit (previously unbounded accumulation in
|
|
31
|
+
rg-less environments).
|
|
32
|
+
- design/238 B-5: the hands/read-face phase (P10) of `prepareTask` moved verbatim to
|
|
33
|
+
`prepare-hands-readface.ts` behind a dual seam (async hands mount / synchronous hands-less
|
|
34
|
+
resolver, dispatched on `handsEnabled`) — pure restructuring, zero behavior change; the
|
|
35
|
+
hands-less leg's synchronous-to-mount property is now pinned (an async wrapper there would open
|
|
36
|
+
a microtask window the original inline code never had).
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- The Grep tool's structured details stop lying on byte-truncated and fenced-partial texts
|
|
40
|
+
(merged-code rescan): a byte-truncated run carries `truncated: true` and omits
|
|
41
|
+
`totalLines`/`numMatches` (the served count no longer poses as the total); a fenced partial's
|
|
42
|
+
bare `No matches.` line can no longer surface as a fabricated result row. The details assembly
|
|
43
|
+
is exported pure (`grepDetailFields`).
|
|
44
|
+
- `ControlPlaneCorruptError` gains a structured `facet` discriminant (`"lock-ownership"`) at its
|
|
45
|
+
three ownership mint sites; the governance-export fold branches on it instead of message text.
|
|
46
|
+
|
|
47
|
+
- 5.41.0's stranded-user-input notice conflated both families under the steer code ([the -2 item
|
|
48
|
+
of the 5.41 review]); split per family as above.
|
|
49
|
+
|
|
50
|
+
## 5.41.0 — 2026-08-18
|
|
51
|
+
|
|
52
|
+
No BREAKING changes. Three behavioral narrowings disclosed below (garbage values on the three
|
|
53
|
+
tool-face seats now refuse loudly; the strict sidecar family's journal read and lock fence both
|
|
54
|
+
join the fail-closed law).
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- backlog #259 — user steers stranded at agent_end are ANNOUNCED, never silently dropped behind
|
|
59
|
+
their "queued" receipts: the terminal sweep hands ENGINE notes back for per-session redelivery,
|
|
60
|
+
but USER inputs have no redelivery semantics (a steer aimed at a finished run must not fire at
|
|
61
|
+
the next one) — their remnant counts now ride a dedicated harness sink, the `settled` frame
|
|
62
|
+
(additive `undrainedSteerCount`/`undrainedFollowUpCount`), and a new `EngineNotice` family
|
|
63
|
+
`task.user_steer_undrained`. The stranding window is a narrow race past the loop's final queue
|
|
64
|
+
check, which is exactly why it gets a loud terminal account.
|
|
65
|
+
- backlog #251 — the mixed-version window of the design/186 ledger v2 is machine-detected:
|
|
66
|
+
recovering a v1-format journal over a v2 disk ledger (a shape only a pre-v2 process mints)
|
|
67
|
+
announces the old-writer-alive warning on the external-findings channel; recovery is unchanged.
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
|
|
71
|
+
- backlog #297 (ruled: narrow) — the three tool-face seats (`excludeTools` / `deferTools` /
|
|
72
|
+
`alwaysLoadTools`) get the fourth seat's value door: a garbage value (a bare string, a truthy
|
|
73
|
+
non-iterable, a non-string entry) refuses `config.tool_face_control` instead of stringifying
|
|
74
|
+
into a never-matching list, throwing an uncoded TypeError, or a falsy value silently reading as
|
|
75
|
+
absent. **Narrowing**: a deployment whose misspelled seat previously "worked" now hears it.
|
|
76
|
+
- backlog #299 (ruled: fix alone) — the auto-mode classifier's action block bounds tool args at
|
|
77
|
+
48,000 chars (its own subject-level cap, deliberately above the delegation-review lanes'
|
|
78
|
+
12,000-per-field sampled payloads so their coverage notes ride through uncut): it was the one
|
|
79
|
+
unbounded string in the classifier's prompt, so a single long tool argument (a SendMessage body
|
|
80
|
+
needs nothing crafted, just length) could blow the classifier's context and trip the session
|
|
81
|
+
breaker out of auto mode.
|
|
82
|
+
- backlog #307 (also filed as #256) — the journal-aware strict sidecar read joins the ENOENT-only
|
|
83
|
+
absence law: an EACCES/EIO journal read refused `ControlPlaneCorruptError` instead of silently
|
|
84
|
+
serving the pre-transaction state while a committed next state sat unreadable beside it — the
|
|
85
|
+
one fail-open left in the strict-read family. **Narrowing**: a deployment whose journal reads
|
|
86
|
+
were failing now hears it instead of silently reading stale.
|
|
87
|
+
- backlog #255 — the sidecar lock's commit fence splits by family: the fail-closed LEDGER family
|
|
88
|
+
(challenge/lineage locked updates and the rebuild path) aborts on an UNPROVABLE owner
|
|
89
|
+
(absent/unreadable owner file) — "cannot disprove a steal" is not a fence; the calibration
|
|
90
|
+
family keeps its documented pre-token leniency, and a FOREIGN token aborts both as before.
|
|
91
|
+
**Narrowing**: within the ledger family only.
|
|
92
|
+
- CHANGELOG erratum for 5.40.0 (recorded in that entry): the header counts THREE narrowings
|
|
93
|
+
(the defer alignment was the missing third) and the release date corrects to 08-17.
|
|
94
|
+
|
|
95
|
+
## 5.40.0 — 2026-08-17
|
|
96
|
+
|
|
97
|
+
No BREAKING changes. Three behavioral narrowings disclosed below (the requestedCwd × workspace-restore
|
|
6
98
|
combination now refuses loudly; Grep's ripgrep leg withholds alias-spelled deny-listed paths it
|
|
7
|
-
previously returned
|
|
99
|
+
previously returned; `deferMode: "auto"` never sweeps engine built-ins — a small-window deployment
|
|
100
|
+
whose built-ins were previously deferred now carries their schemas inline, trading context bytes for
|
|
101
|
+
the first-use activation error the sweep caused).
|
|
102
|
+
|
|
103
|
+
<!-- Post-release erratum (2026-08-17, server review [4351]): the header originally said "Two
|
|
104
|
+
behavioral narrowings" and dated the entry 2026-08-18 (pre-written); the defer narrowing was listed
|
|
105
|
+
under Fixed but missing from this header, and the release actually shipped late on 08-17. -->
|
|
8
106
|
|
|
9
107
|
### Added
|
|
10
108
|
|
|
@@ -102,5 +102,5 @@ export function renderAutoModeWindow(messages, options) {
|
|
|
102
102
|
export function renderAutoModeAction(input) {
|
|
103
103
|
const ask = input.askMessage ? `\npermission gate: ${input.askMessage}` : "";
|
|
104
104
|
return (`\n## New action to classify (the agent's most recent action — evaluate THIS)\n\n` +
|
|
105
|
-
`[tool_call] ${input.req.toolName} ${JSON.stringify(input.req.args ?? {})}${ask}\n`);
|
|
105
|
+
`[tool_call] ${input.req.toolName} ${excerpt(JSON.stringify(input.req.args ?? {}), 48_000)}${ask}\n`);
|
|
106
106
|
}
|
|
@@ -40,6 +40,15 @@ export type CheckpointToken = string & {
|
|
|
40
40
|
};
|
|
41
41
|
/** Mint a CSPRNG 128-bit checkpoint token (token-as-auth, §6/Q5): unguessable, never logged/in-URL. */
|
|
42
42
|
export declare function mintCheckpointToken(): CheckpointToken;
|
|
43
|
+
/**
|
|
44
|
+
* Mint the NON-SECRET stable checkpoint identity — identification and capability are separate
|
|
45
|
+
* axes (settledBy/approvalId precedent): `checkpointId` is safe to log, render, and correlate across
|
|
46
|
+
* frames (tool_approval cards, replay, resume, snapshots); {@link Checkpoint.token} stays the secret
|
|
47
|
+
* resume capability and never rides a display surface. Minted independently (NOT a token digest —
|
|
48
|
+
* a digest would make the id derivable only where the token is held, and the projection surfaces
|
|
49
|
+
* that need it most are exactly the token-less ones). Legacy rows have no id (absent, additive).
|
|
50
|
+
*/
|
|
51
|
+
export declare function mintCheckpointId(): string;
|
|
43
52
|
/**
|
|
44
53
|
* Who resumes a checkpoint, and how the resume `outcome` is interpreted. The `kind` is the discriminant
|
|
45
54
|
* that {@link ResumeOutcome} must match at the resume entry (council #3: prevents a `task_done` gate
|
|
@@ -1327,6 +1336,10 @@ export interface ResolveExpectation {
|
|
|
1327
1336
|
* machine (pending → resolved | expired) that makes resume idempotent (§5). */
|
|
1328
1337
|
export interface Checkpoint {
|
|
1329
1338
|
token: CheckpointToken;
|
|
1339
|
+
/** Non-secret stable identity ({@link mintCheckpointId}) — the display/correlation twin of
|
|
1340
|
+
* the secret `token` (identification vs capability, separate axes). Present on every row minted
|
|
1341
|
+
* since its introduction; absent ⇒ legacy row (read compatibly, no backfill). */
|
|
1342
|
+
checkpointId?: string;
|
|
1330
1343
|
/**
|
|
1331
1344
|
* Schema version of this checkpoint (design/49 v1.5). Every live suspend stamps the version of the
|
|
1332
1345
|
* FEATURE it carries (RESOURCE / BINDING / TOKEN / ORG_ADMISSION); {@link CURRENT_CHECKPOINT_VERSION}
|
|
@@ -1490,6 +1503,8 @@ export interface Checkpoint {
|
|
|
1490
1503
|
export interface CheckpointSummary {
|
|
1491
1504
|
/** The resume capability token ({@link Checkpoint.token}). */
|
|
1492
1505
|
token: CheckpointToken;
|
|
1506
|
+
/** Non-secret stable identity ({@link Checkpoint.checkpointId}) — absent on legacy rows. */
|
|
1507
|
+
checkpointId?: string;
|
|
1493
1508
|
/** The suspended task / session id ({@link Checkpoint.sessionId}). */
|
|
1494
1509
|
sessionId: string;
|
|
1495
1510
|
/** The multi-tenant scope this checkpoint lives in ({@link Checkpoint.scope}) — always equal to the query's. */
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
1
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
2
2
|
import { uuidv7 } from "../internal/harness.js";
|
|
3
3
|
import { inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
4
4
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
5
5
|
export function mintCheckpointToken() {
|
|
6
6
|
return randomBytes(16).toString("hex");
|
|
7
7
|
}
|
|
8
|
+
export function mintCheckpointId() {
|
|
9
|
+
return `cp_${randomUUID()}`;
|
|
10
|
+
}
|
|
8
11
|
export const PROBE_REASON_MAX = 200;
|
|
9
12
|
export const PROBE_CAUSE_PATH_MAX = 200;
|
|
10
13
|
const PROBE_CAUSE_MAX_SHOWN = 8;
|
|
@@ -234,6 +237,7 @@ export function summarizeCheckpoint(cp) {
|
|
|
234
237
|
}
|
|
235
238
|
return {
|
|
236
239
|
token: cp.token,
|
|
240
|
+
...(cp.checkpointId !== undefined ? { checkpointId: cp.checkpointId } : {}),
|
|
237
241
|
sessionId: cp.sessionId,
|
|
238
242
|
scope: cp.scope,
|
|
239
243
|
gateKind: cp.gate.kind,
|
|
@@ -1166,6 +1166,17 @@ export class FileMemoryEngineBackend {
|
|
|
1166
1166
|
throw new ControlPlaneCorruptError(`transaction journal v1 ledger snapshot entry ${JSON.stringify(id)} is not a string: ${jp}`);
|
|
1167
1167
|
snapshotRows[id] = { rev };
|
|
1168
1168
|
}
|
|
1169
|
+
try {
|
|
1170
|
+
const diskRaw = readControlFileOrAbsent(join(this.controlPlaneRoot, LEDGER_FILE), "committed-rev ledger");
|
|
1171
|
+
const diskParsed = diskRaw !== undefined ? JSON.parse(diskRaw) : undefined;
|
|
1172
|
+
if (diskParsed !== null && typeof diskParsed === "object" && "v" in diskParsed && diskParsed.v === LEDGER_SCHEMA_VERSION) {
|
|
1173
|
+
this.enqueueExternalItems([
|
|
1174
|
+
"memory ledger: recovering a v1-format journal over a v2 ledger — a pre-v2 process wrote AFTER this store migrated (mixed-version deployment window). Retire old readers/writers before further writes; see the v2 rollback runbook.",
|
|
1175
|
+
]);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
catch {
|
|
1179
|
+
}
|
|
1169
1180
|
}
|
|
1170
1181
|
for (const op of journal.ops) {
|
|
1171
1182
|
const invalid = journalOpInvalid(op, this.directoryRoot);
|
|
@@ -2160,7 +2171,7 @@ export class FileMemoryEngineBackend {
|
|
|
2160
2171
|
held = undefined;
|
|
2161
2172
|
}
|
|
2162
2173
|
if (held !== token) {
|
|
2163
|
-
throw new ControlPlaneCorruptError("txn lock ownership lost before commit (holder stalled past the stale deadline and was stolen from?) — transaction aborted, nothing was committed");
|
|
2174
|
+
throw new ControlPlaneCorruptError("txn lock ownership lost before commit (holder stalled past the stale deadline and was stolen from?) — transaction aborted, nothing was committed", { facet: "lock-ownership" });
|
|
2164
2175
|
}
|
|
2165
2176
|
}
|
|
2166
2177
|
async applyPatchesLocked(patches, lockToken) {
|
|
@@ -3230,7 +3241,7 @@ export class FileMemoryEngineBackend {
|
|
|
3230
3241
|
}
|
|
3231
3242
|
catch (err) {
|
|
3232
3243
|
if (err.code === FileMemoryEngineBackend.EXPORT_INCOMPLETE || err instanceof ControlPlaneCorruptError) {
|
|
3233
|
-
if (err instanceof ControlPlaneCorruptError &&
|
|
3244
|
+
if (err instanceof ControlPlaneCorruptError && err.facet !== "lock-ownership")
|
|
3234
3245
|
throw err;
|
|
3235
3246
|
return { custody: [], unbound: [], complete: false, unsliceableCustody: 0, reason: err instanceof Error ? err.message : String(err) };
|
|
3236
3247
|
}
|
|
@@ -11,8 +11,13 @@ export declare const CONTROL_PLANE_DIR = ".engine";
|
|
|
11
11
|
* silently degraded to an empty ledger / unclaimed root — that would let a corrupted (or truncated)
|
|
12
12
|
* sidecar erase CAS baselines and re-open the root claim. */
|
|
13
13
|
export declare class ControlPlaneCorruptError extends Error {
|
|
14
|
+
/** Structured discriminant for callers that fold ONE corruption family and rethrow the rest —
|
|
15
|
+
* branching on message text is banned (§B8). Only `"lock-ownership"` is minted today (the
|
|
16
|
+
* recoverable-as-incomplete family); absent = every other corruption (a definite state, throw). */
|
|
17
|
+
readonly facet?: "lock-ownership";
|
|
14
18
|
constructor(message: string, opts?: {
|
|
15
19
|
cause?: unknown;
|
|
20
|
+
facet?: "lock-ownership";
|
|
16
21
|
});
|
|
17
22
|
}
|
|
18
23
|
/**
|
|
@@ -248,6 +253,16 @@ export interface SidecarLockOptions {
|
|
|
248
253
|
* volume, revoked permission) makes mkdir fail forever while the lock dir it would stat never
|
|
249
254
|
* exists — spun this synchronous loop at full speed and never reached its own cap. */
|
|
250
255
|
export declare function acquireSidecarLock(lockDir: string, opts?: SidecarLockOptions): string;
|
|
256
|
+
/** 证伪式复审 L2-analog: re-verify the owner token at the COMMIT POINT — a stolen-from holder
|
|
257
|
+
* aborts instead of writing over the stealer. A FOREIGN token always aborts (positive proof the
|
|
258
|
+
* lock is someone else's now). What an ABSENT/UNREADABLE owner file means splits by caller family
|
|
259
|
+
* (backlog #255): the fail-closed LEDGER family (`"strict"` — challenges/lineage via
|
|
260
|
+
* lockedStrictUpdate) treats unprovable ownership as lost — those sidecars promise their commit
|
|
261
|
+
* point is fenced, and "cannot disprove" is not a fence; the fail-open calibration family
|
|
262
|
+
* (`"lenient"`, the pre-#255 shape — e.g. the scope registry, whose sidecars self-heal at the next
|
|
263
|
+
* locked update) keeps the leniency, because its token writes are best-effort and an absent owner
|
|
264
|
+
* file there is the documented pre-token degraded shape, not evidence of a steal. */
|
|
265
|
+
export declare function assertSidecarLockOwnership(lockDir: string, token: string, what: string, mode?: "strict" | "lenient"): void;
|
|
251
266
|
interface AnnouncementsRecord {
|
|
252
267
|
/** How many announcements were dropped by the bounded-queue fold (disclosed at render). */
|
|
253
268
|
folded: number;
|
|
@@ -7,9 +7,12 @@ const SCOPES_FILE = "scopes.json";
|
|
|
7
7
|
export const CURSORS_FILE = "cursors.json";
|
|
8
8
|
export const CONTROL_PLANE_DIR = ".engine";
|
|
9
9
|
export class ControlPlaneCorruptError extends Error {
|
|
10
|
+
facet;
|
|
10
11
|
constructor(message, opts) {
|
|
11
|
-
super(message, opts);
|
|
12
|
+
super(message, opts?.cause !== undefined ? { cause: opts.cause } : undefined);
|
|
12
13
|
this.name = "ControlPlaneCorruptError";
|
|
14
|
+
if (opts?.facet !== undefined)
|
|
15
|
+
this.facet = opts.facet;
|
|
13
16
|
}
|
|
14
17
|
}
|
|
15
18
|
function slugifyPath(absPath) {
|
|
@@ -598,16 +601,19 @@ function lockedJournaledUpdate(controlDir, fileName, mutate) {
|
|
|
598
601
|
releaseSidecarLock(lock, token);
|
|
599
602
|
}
|
|
600
603
|
}
|
|
601
|
-
function assertSidecarLockOwnership(lockDir, token, what) {
|
|
604
|
+
export function assertSidecarLockOwnership(lockDir, token, what, mode = "lenient") {
|
|
602
605
|
let held;
|
|
603
606
|
try {
|
|
604
607
|
held = readFileSync(join(lockDir, "owner"), "utf8");
|
|
605
608
|
}
|
|
606
|
-
catch {
|
|
609
|
+
catch (err) {
|
|
610
|
+
if (mode === "strict" && err.code !== undefined) {
|
|
611
|
+
throw new ControlPlaneCorruptError(`${what}: sidecar lock ownership UNPROVABLE at commit (owner file ${err.code === "ENOENT" ? "absent" : `unreadable: ${err.code}`}) — a fail-closed ledger must not commit on a fence it cannot prove; update aborted`, { cause: err, facet: "lock-ownership" });
|
|
612
|
+
}
|
|
607
613
|
return;
|
|
608
614
|
}
|
|
609
615
|
if (held !== token) {
|
|
610
|
-
throw new ControlPlaneCorruptError(`${what}: sidecar lock ownership lost before commit (holder stalled past ${LOCK_STALE_MS}ms and was stolen from) — update aborted
|
|
616
|
+
throw new ControlPlaneCorruptError(`${what}: sidecar lock ownership lost before commit (holder stalled past ${LOCK_STALE_MS}ms and was stolen from) — update aborted`, { facet: "lock-ownership" });
|
|
611
617
|
}
|
|
612
618
|
}
|
|
613
619
|
function releaseSidecarLock(lockDir, token) {
|
|
@@ -897,7 +903,7 @@ function lockedStrictUpdate(controlDir, fileName, what, coerce, fn) {
|
|
|
897
903
|
const { next, result } = fn(current);
|
|
898
904
|
if (next !== undefined) {
|
|
899
905
|
const data = `${JSON.stringify(next, null, 2)}\n`;
|
|
900
|
-
assertSidecarLockOwnership(lock, token, what);
|
|
906
|
+
assertSidecarLockOwnership(lock, token, what, "strict");
|
|
901
907
|
atomicWriteFileSync(journal, data);
|
|
902
908
|
atomicWriteFileSync(file, data);
|
|
903
909
|
rmSync(journal, { force: true });
|
|
@@ -932,7 +938,10 @@ function readStrictSidecar(controlDir, fileName, what) {
|
|
|
932
938
|
try {
|
|
933
939
|
journalRaw = readFileSync(journal, "utf8");
|
|
934
940
|
}
|
|
935
|
-
catch {
|
|
941
|
+
catch (err) {
|
|
942
|
+
if (err.code !== "ENOENT") {
|
|
943
|
+
throw new ControlPlaneCorruptError(`control-plane journal unreadable ((${err.code ?? "io error"})) — a committed next state may exist that cannot be proven; repair the read fault rather than serving the pre-transaction state: ${journal}`, { cause: err });
|
|
944
|
+
}
|
|
936
945
|
journalRaw = undefined;
|
|
937
946
|
}
|
|
938
947
|
if (journalRaw !== undefined) {
|
|
@@ -1421,7 +1430,7 @@ export function rebuildStrictControlPlaneLedger(controlDir, ledger, now) {
|
|
|
1421
1430
|
}
|
|
1422
1431
|
const at = now();
|
|
1423
1432
|
const quarantinedTo = [];
|
|
1424
|
-
assertSidecarLockOwnership(lock, token, what);
|
|
1433
|
+
assertSidecarLockOwnership(lock, token, what, "strict");
|
|
1425
1434
|
for (const path of [file, journal]) {
|
|
1426
1435
|
if (!existsSync(path))
|
|
1427
1436
|
continue;
|
|
@@ -1439,7 +1448,7 @@ export function rebuildStrictControlPlaneLedger(controlDir, ledger, now) {
|
|
|
1439
1448
|
}
|
|
1440
1449
|
quarantinedTo.push(dest);
|
|
1441
1450
|
}
|
|
1442
|
-
assertSidecarLockOwnership(lock, token, what);
|
|
1451
|
+
assertSidecarLockOwnership(lock, token, what, "strict");
|
|
1443
1452
|
atomicWriteFileSync(file, `${JSON.stringify(empty, null, 2)}\n`);
|
|
1444
1453
|
rmSync(journal, { force: true });
|
|
1445
1454
|
return { ledger, quarantinedTo, at };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { boundInputHashOf, canonicalize } from "./canonical-json.js";
|
|
2
|
-
import { MAX_SUPPORTED_CHECKPOINT_VERSION, mintCheckpointToken, resolveCheckpointStore } from "./checkpoint-store.js";
|
|
2
|
+
import { MAX_SUPPORTED_CHECKPOINT_VERSION, mintCheckpointId, mintCheckpointToken, resolveCheckpointStore } from "./checkpoint-store.js";
|
|
3
3
|
import { resolveDeclaredDurability } from "./wiring-manifest.js";
|
|
4
4
|
export const PARK_SELFCHECK_SCOPE_PREFIX = "sema:park-selfcheck:";
|
|
5
5
|
export const PARK_SELFCHECK_STEP_TIMEOUT_MS = 10_000;
|
|
@@ -59,6 +59,7 @@ export function durableParkGapFor(deps, spec = {}) {
|
|
|
59
59
|
function syntheticCheckpoint(scope) {
|
|
60
60
|
return {
|
|
61
61
|
token: mintCheckpointToken(),
|
|
62
|
+
checkpointId: mintCheckpointId(),
|
|
62
63
|
scope,
|
|
63
64
|
sessionId: `${scope}:session`,
|
|
64
65
|
leafId: `${scope}:leaf`,
|
|
@@ -89,6 +90,7 @@ function syntheticCheckpoint(scope) {
|
|
|
89
90
|
function faithfulnessDelta(sent, got) {
|
|
90
91
|
const project = (cp) => ({
|
|
91
92
|
token: cp.token,
|
|
93
|
+
checkpointId: cp.checkpointId,
|
|
92
94
|
scope: cp.scope,
|
|
93
95
|
sessionId: cp.sessionId,
|
|
94
96
|
leafId: cp.leafId,
|
|
@@ -87,6 +87,26 @@ export function prepareConfigDoors(input) {
|
|
|
87
87
|
const { deps, sessions, resume, internals } = input;
|
|
88
88
|
let spec = input.spec;
|
|
89
89
|
assertRestoreGatedToolsValue(spec.restoreGatedTools);
|
|
90
|
+
const assertToolNameListValue = (value, seat) => {
|
|
91
|
+
if (value === undefined)
|
|
92
|
+
return;
|
|
93
|
+
if (!Array.isArray(value)) {
|
|
94
|
+
const e = new Error(`TaskSpec.${seat} must be an array of tool names or absent (got ${value === null ? "null" : typeof value}) — a garbage list must refuse loudly, never be read as "no ${seat}".`);
|
|
95
|
+
e.code = "config.tool_face_control";
|
|
96
|
+
throw e;
|
|
97
|
+
}
|
|
98
|
+
const entries = value;
|
|
99
|
+
for (const entry of entries) {
|
|
100
|
+
if (typeof entry !== "string") {
|
|
101
|
+
const e = new Error(`TaskSpec.${seat} entries must be strings (got ${entry === null ? "null" : typeof entry}).`);
|
|
102
|
+
e.code = "config.tool_face_control";
|
|
103
|
+
throw e;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
assertToolNameListValue(spec.excludeTools, "excludeTools");
|
|
108
|
+
assertToolNameListValue(spec.deferTools, "deferTools");
|
|
109
|
+
assertToolNameListValue(spec.alwaysLoadTools, "alwaysLoadTools");
|
|
90
110
|
const toolFaceSnapshot = {
|
|
91
111
|
exclude: spec.excludeTools ? Object.freeze([...spec.excludeTools]) : undefined,
|
|
92
112
|
defer: spec.deferTools ? Object.freeze([...spec.deferTools]) : undefined,
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { type BeforeWriteHook, type CwdRef, type ReadDenyMatcher, type ReadFace } from "../../tools/fs/index.js";
|
|
2
|
+
import { type ReadFileState } from "../../tools/fs/safety.js";
|
|
3
|
+
import type { RunnerDeps, TaskSpec } from "../types.js";
|
|
4
|
+
import type { Model } from "../../internal/llm.js";
|
|
5
|
+
import type { AgentTool, ExecutionEnv, WorkspaceState } from "../../internal/harness.js";
|
|
6
|
+
import type { StoredSession } from "../session.js";
|
|
7
|
+
import type { PrepareConfigDoorsResult } from "./prepare-config-doors.js";
|
|
8
|
+
import type { PrepareSafetyScanResult } from "./prepare-safety-scan.js";
|
|
9
|
+
import type { PrepareResume, RunInternals } from "./prepare-task.js";
|
|
10
|
+
/** Test seam (mirrors `__resetMalformedNoticeSeatAnnouncement`): never called by production code.
|
|
11
|
+
* Deliberately asymmetric — it resets only the console latch: the WeakSet arm needs no seam
|
|
12
|
+
* because a test resets it by minting a fresh sink function (identity IS the ledger key), while
|
|
13
|
+
* the console arm's key is the process itself, which only this seam can refresh. Moved WITH the
|
|
14
|
+
* phase (design/238 B-5); prepare-task.ts keeps a re-export so the existing import sites stand. */
|
|
15
|
+
export declare function __resetReadFaceClampAnnouncement(): void;
|
|
16
|
+
export interface PrepareHandsReadFaceInput {
|
|
17
|
+
/** owned (value) — `ownedEnv !== undefined || deps.executionEnv !== undefined`, computed in the
|
|
18
|
+
* driver: the phase's own branch condition (hands mount vs the hands-less resolver arm). */
|
|
19
|
+
handsEnabled: boolean;
|
|
20
|
+
/** borrowed-readonly (the ownership-ref PROTOCOL stays with the driver — D-8 案① posture, same
|
|
21
|
+
* as B-4): the effective env (`ownedEnv ?? deps.executionEnv ?? stub`). This phase drives it
|
|
22
|
+
* (canonicalPath/joinPath/writeFileExclusive probe/instanceof screens) and closes tool bands
|
|
23
|
+
* over it, but NEVER rebinds or destroys it. */
|
|
24
|
+
executionEnv: ExecutionEnv;
|
|
25
|
+
/** borrowed-readonly — B-4's settled root (T6 FINAL semantics); the containment root every face
|
|
26
|
+
* in this phase canonicalizes from. */
|
|
27
|
+
taskRootFinal: string;
|
|
28
|
+
/** borrowed-readonly — B-4's checkpoint-path rebaser (T7): identity unless a divergent restore
|
|
29
|
+
* armed it; applied to the readFileState seed keys and the handsCwd seed. */
|
|
30
|
+
rebaseRestoredPath: (p: string) => string;
|
|
31
|
+
/** borrowed-readonly — the durable-resume seat this phase reads: `seed` only (readFileState /
|
|
32
|
+
* handsCwd / readFace section). Presence also gates the leg forks (T11: replay never runs on a
|
|
33
|
+
* resume; the workspace_state rung is skipped; the face fold goes stricter-wins). */
|
|
34
|
+
resume: Pick<PrepareResume, "seed"> | undefined;
|
|
35
|
+
/** borrowed-readonly — the acquired session (B-3 output), as a Pick (相 API 规则件 R-4: receiver
|
|
36
|
+
* preserved): `getWorkspaceState` feeds the design/155 continuation rung; `buildContext` feeds
|
|
37
|
+
* the transcript replay. */
|
|
38
|
+
session: Pick<StoredSession, "getWorkspaceState" | "buildContext">;
|
|
39
|
+
/** borrowed-readonly — the acquired session's id, for error/notice tags and the band's session
|
|
40
|
+
* registration. */
|
|
41
|
+
sessionId: string;
|
|
42
|
+
/** borrowed-readonly — the host task id: trace frames + the band's task-registry owner. */
|
|
43
|
+
hostTaskId: string;
|
|
44
|
+
/** borrowed-readonly — the registry scope (`internals?.registryScope ?? spec.principal ??
|
|
45
|
+
* "default"`, computed in the driver); the band's task-registry scope. */
|
|
46
|
+
taskScope: string;
|
|
47
|
+
/** borrowed-readonly — the D-6 spec-time full-shell reachability precomputation; an input to the
|
|
48
|
+
* face resolver on BOTH arms (the driver's post-exclusion roster assertion pins it to the
|
|
49
|
+
* mounted truth — that assertion is driver-side, after this phase). */
|
|
50
|
+
fullShellReachable: boolean;
|
|
51
|
+
/** borrowed-readonly — the MAX-RANK fold of spec.shellGate with the inherited chain's doctrine
|
|
52
|
+
* (computed in the driver's inherited-gate block); the shellGate fold's doctrine value. */
|
|
53
|
+
effectiveShellGate: "off" | "classify" | "always";
|
|
54
|
+
/** borrowed-readonly — B-1's frozen task-start face snapshot; only `.exclude` is read here (the
|
|
55
|
+
* Monitor-mount honesty bit for the gh rate-limit hint, #181-F6). */
|
|
56
|
+
toolFaceSnapshot: PrepareConfigDoorsResult["toolFaceSnapshot"];
|
|
57
|
+
/** borrowed-readonly — B-1's resolved main model; read ONLY for its pdf capability profile
|
|
58
|
+
* (the Read tool's document-degradation fork). */
|
|
59
|
+
model: Model;
|
|
60
|
+
/** borrowed-readonly — the task-spec seats this phase reads, as a Pick over the SAME spec object
|
|
61
|
+
* (R-4). `tools` feeds only the band shadow check; `envFacts` only the advertised scratchpad. */
|
|
62
|
+
spec: Pick<TaskSpec, "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "handsReadOnly" | "readDenyPatterns" | "readFace" | "oneShot" | "sessionId" | "tools">;
|
|
63
|
+
/** borrowed-readonly — the deployment seats this phase reads, as a Pick over the SAME deps
|
|
64
|
+
* object (R-4: `deps.onError?.()` keeps its receiver; a new deps read must widen this Pick).
|
|
65
|
+
* `readDenyBuiltinTiers`/`readDenyBuiltinExclude` are SNAPSHOTTED before the first await that
|
|
66
|
+
* precedes the toolkit compile (#245/#244 — the caller retains these arrays by reference). */
|
|
67
|
+
deps: Pick<RunnerDeps, "onError" | "onNotice" | "tracer" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "readFace" | "permissionRuleOrg" | "hands">;
|
|
68
|
+
/** borrowed-readonly — the trusted spawn-side seats the band forwards: the finished-command
|
|
69
|
+
* notification lane and the mid-flight detach hub. */
|
|
70
|
+
internals: Pick<RunInternals, "onTaskNotification" | "detachHub"> | undefined;
|
|
71
|
+
/** borrowed (ref cell; the REF itself is never replaced) — design/138 S2-C late-bound memory
|
|
72
|
+
* write gate: the band's beforeWrite closure reads `.current` call-time; prepareMemory (driver,
|
|
73
|
+
* after this phase) installs the gate. Declared in the driver because the SAME ref feeds
|
|
74
|
+
* prepareMemory. */
|
|
75
|
+
memoryWriteGateRef: {
|
|
76
|
+
current?: BeforeWriteHook;
|
|
77
|
+
};
|
|
78
|
+
/** borrowed-readonly — the driver's per-tool offload wrapper (design/158 posture + policy); the
|
|
79
|
+
* band rides through it exactly as caller/MCP tools do. */
|
|
80
|
+
firstPartyOffload: (tool: AgentTool) => AgentTool;
|
|
81
|
+
/** borrowed-mutable — the SHARED mount array (§4.4: later refs alias it; identity is the
|
|
82
|
+
* contract, never map/copy). This phase pushes the hands band IN PLACE (after caller tools /
|
|
83
|
+
* built-ins / MCP / A2A, before the background-task mounts — T22 order). Mutation owners after
|
|
84
|
+
* this phase: P11 background/Monitor/worktree pushes, the driver's excludeTools splice, the
|
|
85
|
+
* refresh seam's runtime splice/push. */
|
|
86
|
+
tools: AgentTool[];
|
|
87
|
+
/** borrowed-readonly HERE (census divergence #5 vs the design row): this phase only reads
|
|
88
|
+
* `.has("Bash")`/`.has("Monitor")` for the provenance bits — its mutation owners are B-2/P8/P9
|
|
89
|
+
* (+refresh seam)/P11/P22, per the B-2 Result's mutation-owner table. */
|
|
90
|
+
egressTools: ReadonlySet<string>;
|
|
91
|
+
/** borrowed-mutable — the shellGate fold's tier write-back (`.get` for the provenance bit,
|
|
92
|
+
* monotonic `.set` — tighten-only). Same object B-2 returned; owner table lives on the B-2
|
|
93
|
+
* Result. */
|
|
94
|
+
irreversibilityTier: PrepareSafetyScanResult["irreversibilityTier"];
|
|
95
|
+
/** borrowed-mutable — the shellGate fold `.add`s Bash/Monitor here. Same-object contract as
|
|
96
|
+
* above. */
|
|
97
|
+
irreversibleTools: PrepareSafetyScanResult["irreversibleTools"];
|
|
98
|
+
/** borrowed-mutable — the classify doctrine's probe installs (`.set`, only where the doctrine
|
|
99
|
+
* owns the seat). Same-object contract as above. */
|
|
100
|
+
reversibilityProbes: PrepareSafetyScanResult["reversibilityProbes"];
|
|
101
|
+
}
|
|
102
|
+
/** The phase's outputs (相 API 规则件 four-class form) — ALL settled before the return; the driver
|
|
103
|
+
* binds them as fresh consts (R-5) except the inverted-closure trio and the two shellGated bits,
|
|
104
|
+
* which write back into the driver's pre-existing `let`s (D-3 hold / driver-owned seats — see the
|
|
105
|
+
* module header). */
|
|
106
|
+
export interface PrepareHandsReadFaceResult {
|
|
107
|
+
/** owned — the per-task read-before-write state, exposed for the suspend closure's checkpoint
|
|
108
|
+
* serialization (§15.2 net-add #8) and the compaction faces. Undefined ⇔ hands-less. The MAP
|
|
109
|
+
* keeps being mutated by tool executes / compaction at run time — object identity carried. */
|
|
110
|
+
readFileStateForCheckpoint: ReadFileState | undefined;
|
|
111
|
+
/** owned — parity-204 (CC seededFromContext): seeds declared context files through the SAME
|
|
112
|
+
* resolveKey containment as a real Read. CREATED here; CALLED by the driver's memory-seed and
|
|
113
|
+
* loadProjectMemory blocks (T10 — the forever-cross-phase edge; this module does not claim the
|
|
114
|
+
* call order). Undefined on a hands-less task (nothing to seed). Reads the phase-local
|
|
115
|
+
* `resolvedReadFace` call-time — settled before this function returns. */
|
|
116
|
+
seedContextFiles: ((files: ReadonlyArray<{
|
|
117
|
+
path: string;
|
|
118
|
+
content: string;
|
|
119
|
+
}>) => Promise<void>) | undefined;
|
|
120
|
+
/** owned — design/99 §E13: the per-task logical cwd ref (observable `cd`), minted when a real
|
|
121
|
+
* writable shell mounts. The driver stamps it into checkpoints, threads it into worktree tools
|
|
122
|
+
* and gate cwd reads; tools mutate `.current` at run time. Undefined when no shell / readOnly. */
|
|
123
|
+
handsCwdRef: CwdRef | undefined;
|
|
124
|
+
/** owned — design/155: the settle-side snapshot base (what the cwd/worktree refs were RESTORED
|
|
125
|
+
* to), so runtask's settle write diffs against it. The driver's G4 worktree mount writes
|
|
126
|
+
* `.restoredWorktreeDir` after this phase — object identity carried. */
|
|
127
|
+
workspaceStateSettle: {
|
|
128
|
+
rootCanonical: string;
|
|
129
|
+
restoredHandsCwd?: string;
|
|
130
|
+
restoredWorktreeDir?: string;
|
|
131
|
+
baselineUnknown?: boolean;
|
|
132
|
+
} | undefined;
|
|
133
|
+
/** owned — design/155: the branch-nearest `workspace_state` entry, fetched ONCE here and shared
|
|
134
|
+
* with the driver's worktree seed (the G4 mount reads `.activeWorktree` through it). */
|
|
135
|
+
wsSnapshot: WorkspaceState | undefined;
|
|
136
|
+
/** owned — the wsSnapshot path rebaser (identity when no snapshot / same root); the G4 worktree
|
|
137
|
+
* seed applies it to the snapshot's worktree paths. */
|
|
138
|
+
rebaseWsPath: (p: string) => string;
|
|
139
|
+
/** owned — real shell + write hands + background-capable env: gates the driver's TaskOutput/
|
|
140
|
+
* TaskStop/Monitor/SendMessage mounts (P11) and the Monitor arm of the shellGate fold here. */
|
|
141
|
+
backgroundTaskToolsActive: boolean;
|
|
142
|
+
/** write-back (D-3 hold) — the run's RESOLVED read face where hands mount; undefined ⇔ no hands
|
|
143
|
+
* (the hands-less value lives in `handsLessResolvedFace`). The driver assigns this onto its
|
|
144
|
+
* outer `let` for the call-time closures (ctx enrichment / checkpoint mint / envFacts). */
|
|
145
|
+
resolvedReadFace: ReadFace | undefined;
|
|
146
|
+
/** write-back (D-3 hold) — the NORMALIZED deny-set additions in force (deployment ∪ task ∪
|
|
147
|
+
* resume-seed; zero-builtin compile — the persisted/carrier form, never the judged union's
|
|
148
|
+
* post-dedup remainder). Driver assigns onto its outer `let`. */
|
|
149
|
+
readDenyAdditionsNormalized: ReadonlyArray<{
|
|
150
|
+
pattern: string;
|
|
151
|
+
caseSensitive: boolean;
|
|
152
|
+
}>;
|
|
153
|
+
/** write-back (D-3 hold) — the hands-LESS legs' resolved face (#242: the read posture is a
|
|
154
|
+
* property of the RUN, not the mount). Driver assigns onto its outer `let`. */
|
|
155
|
+
handsLessResolvedFace: ReadFace | undefined;
|
|
156
|
+
/** owned — the canonicalized write-capable extra roots (additionalDirectories + the advertised
|
|
157
|
+
* scratchpad), deduped; the driver's envFacts advertisement and attachment readers share the
|
|
158
|
+
* ARRAY identity. Settled here (no mutation after return). */
|
|
159
|
+
additionalRootsCanonical: string[];
|
|
160
|
+
/** owned — the read-only arm of the fence widening (2026-08-05 both-arms ruling); same identity
|
|
161
|
+
* contract as above. */
|
|
162
|
+
additionalReadRootsCanonical: string[];
|
|
163
|
+
/** owned — RB-159: the canonical containment root, published so the working-file ATTACHMENT
|
|
164
|
+
* reader applies the same containment the hands tools do. Undefined ⇔ hands-less. */
|
|
165
|
+
attachmentRootCanonical: string | undefined;
|
|
166
|
+
/** owned — design/199 件B: the ONE compiled deny judge per prepare (built-ins ∪ deployment ∪
|
|
167
|
+
* task additions). Undefined ⇔ hands-less (its consumers only exist with hands). */
|
|
168
|
+
readDenyMatcher: ReadDenyMatcher | undefined;
|
|
169
|
+
/** write-back — "the shellGate doctrine is the SOLE POSITIVE source of Bash's tier" provenance
|
|
170
|
+
* bit; the driver's `let` (risk grading + the persisted-rule mandate boundary read it
|
|
171
|
+
* call-time). Written ONLY by this phase; false when the fold did not run. */
|
|
172
|
+
shellGatedBash: boolean;
|
|
173
|
+
/** write-back — the Monitor twin of the bit above. */
|
|
174
|
+
shellGatedMonitor: boolean;
|
|
175
|
+
}
|
|
176
|
+
/** The hands-LESS resolver arm (#242) — SYNCHRONOUS by contract. The original P10 else-arm ran
|
|
177
|
+
* with ZERO awaits, so on a hands-less leg the stretch from the driver's A2A warning callback
|
|
178
|
+
* (deployment `onError`, which can queue a microtask) through the P11 SendMessage/delegation
|
|
179
|
+
* mounts was uninterruptible. An `async` phase call would open exactly that microtask window (the
|
|
180
|
+
* T1 hazard, here on the hands-less leg — a queued `spec.tools` mutation could suppress the
|
|
181
|
+
* SendMessage mount or plant a trusted-revive delegation entry). So this leg is its OWN plain sync
|
|
182
|
+
* seam the driver calls WITHOUT `await` (dispatching on the `handsEnabled` boolean it already
|
|
183
|
+
* owns — NOT on a `Promise` identity check, which a host that replaces `globalThis.Promise` could
|
|
184
|
+
* defeat; r3): the leg is byte-for-byte as synchronous as before the extraction.
|
|
185
|
+
* (Adversarial review 2026-08-18, verified against the a945be28 baseline: awaits 3981–4243 are all
|
|
186
|
+
* inside the hands branch; the else arm reached 4440/4463 with none.) */
|
|
187
|
+
export declare function resolveHandsLessReadFace(input: PrepareHandsReadFaceInput): PrepareHandsReadFaceResult;
|
|
188
|
+
/** The hands-MOUNT branch — the async half of the phase (see the module header for the contract).
|
|
189
|
+
* The driver awaits this ONLY on the hands leg (`handsEnabled === true`); the hands-less leg goes
|
|
190
|
+
* through the synchronous {@link resolveHandsLessReadFace} instead. Two seams, one boolean
|
|
191
|
+
* dispatch — no `Promise`-identity guard (r3). */
|
|
192
|
+
export declare function prepareHandsMount(input: PrepareHandsReadFaceInput): Promise<PrepareHandsReadFaceResult>;
|