@sema-agent/core 5.41.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 CHANGED
@@ -1,5 +1,52 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.42.0 — 2026-08-18
4
+
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
+
3
50
  ## 5.41.0 — 2026-08-18
4
51
 
5
52
  No BREAKING changes. Three behavioral narrowings disclosed below (garbage values on the three
@@ -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,
@@ -2171,7 +2171,7 @@ export class FileMemoryEngineBackend {
2171
2171
  held = undefined;
2172
2172
  }
2173
2173
  if (held !== token) {
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");
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" });
2175
2175
  }
2176
2176
  }
2177
2177
  async applyPatchesLocked(patches, lockToken) {
@@ -3241,7 +3241,7 @@ export class FileMemoryEngineBackend {
3241
3241
  }
3242
3242
  catch (err) {
3243
3243
  if (err.code === FileMemoryEngineBackend.EXPORT_INCOMPLETE || err instanceof ControlPlaneCorruptError) {
3244
- if (err instanceof ControlPlaneCorruptError && !err.message.includes("ownership"))
3244
+ if (err instanceof ControlPlaneCorruptError && err.facet !== "lock-ownership")
3245
3245
  throw err;
3246
3246
  return { custody: [], unbound: [], complete: false, unsliceableCustody: 0, reason: err instanceof Error ? err.message : String(err) };
3247
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
  /**
@@ -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) {
@@ -605,12 +608,12 @@ export function assertSidecarLockOwnership(lockDir, token, what, mode = "lenient
605
608
  }
606
609
  catch (err) {
607
610
  if (mode === "strict" && err.code !== undefined) {
608
- 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 });
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" });
609
612
  }
610
613
  return;
611
614
  }
612
615
  if (held !== token) {
613
- 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" });
614
617
  }
615
618
  }
616
619
  function releaseSidecarLock(lockDir, token) {
@@ -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,
@@ -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>;