@sema-agent/core 5.28.0 → 5.29.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +36 -3
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/observer.d.ts +3 -3
  4. package/dist/agents/peer-admission.d.ts +1 -1
  5. package/dist/agents/teacher.d.ts +1 -1
  6. package/dist/agents/verify.d.ts +6 -6
  7. package/dist/bench/metrics.d.ts +1 -1
  8. package/dist/brain/retry.d.ts +1 -1
  9. package/dist/core/arg-summary.d.ts +1 -1
  10. package/dist/core/ask-class.d.ts +2 -2
  11. package/dist/core/ask-question.d.ts +1 -1
  12. package/dist/core/ask-question.js +14 -1
  13. package/dist/core/background-shell.d.ts +5 -5
  14. package/dist/core/checkpoint-store.d.ts +14 -14
  15. package/dist/core/file-snapshot-store.d.ts +1 -1
  16. package/dist/core/hooks.d.ts +3 -3
  17. package/dist/core/human-input-projection.d.ts +2 -2
  18. package/dist/core/memory-admission.d.ts +2 -2
  19. package/dist/core/memory-engine/engine.d.ts +1 -1
  20. package/dist/core/memory-engine/file-backend.d.ts +2 -2
  21. package/dist/core/memory-engine/header-hints.d.ts +1 -1
  22. package/dist/core/memory-engine/layout.d.ts +2 -2
  23. package/dist/core/memory-recall.d.ts +1 -1
  24. package/dist/core/memory.d.ts +2 -2
  25. package/dist/core/oracle-isolation.d.ts +2 -2
  26. package/dist/core/permission-rule-consent.d.ts +1 -1
  27. package/dist/core/permission-rule-model.d.ts +1 -1
  28. package/dist/core/permission-rule-org.d.ts +1 -1
  29. package/dist/core/permission-rule-store.d.ts +1 -1
  30. package/dist/core/runner/active-skill-scope.d.ts +1 -1
  31. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  32. package/dist/core/runner/prepare-task.d.ts +8 -3
  33. package/dist/core/runner/prepare-task.js +14 -4
  34. package/dist/core/runner/runtask.d.ts +1 -1
  35. package/dist/core/runner/runtask.js +5 -5
  36. package/dist/core/runner/session-rule-policy.d.ts +2 -2
  37. package/dist/core/runner/teardown-bounded.d.ts +1 -1
  38. package/dist/core/runner/tool-disclosure.d.ts +1 -1
  39. package/dist/core/scheduler.d.ts +4 -4
  40. package/dist/core/session-policy-store.d.ts +3 -3
  41. package/dist/core/shared-memory/normalize.d.ts +1 -1
  42. package/dist/core/skill-tool-specifier.d.ts +1 -1
  43. package/dist/core/task-notification.d.ts +2 -2
  44. package/dist/core/task-registry-agent.d.ts +9 -9
  45. package/dist/core/task-registry-shared.d.ts +4 -4
  46. package/dist/core/tool-errors.d.ts +2 -2
  47. package/dist/core/tool-policy.d.ts +45 -12
  48. package/dist/core/tool-policy.js +37 -4
  49. package/dist/core/tool-result-store.d.ts +2 -2
  50. package/dist/core/trace.d.ts +1 -1
  51. package/dist/core/types.d.ts +13 -13
  52. package/dist/core/untrusted-text.d.ts +6 -0
  53. package/dist/core/untrusted-text.js +1 -0
  54. package/dist/engine/compaction/compaction.d.ts +1 -1
  55. package/dist/engine/harness/messages.d.ts +1 -1
  56. package/dist/engine/harness/types.d.ts +2 -2
  57. package/dist/engine/llm/types.d.ts +1 -1
  58. package/dist/engine/loop/types.d.ts +2 -2
  59. package/dist/engine/session/import-validate.d.ts +1 -1
  60. package/dist/engine/session/log-digest.d.ts +1 -1
  61. package/dist/fixtures/index.d.ts +18 -2
  62. package/dist/fixtures/index.js +11 -0
  63. package/dist/orchestration/goal.d.ts +1 -1
  64. package/dist/orchestration/workflow-types.d.ts +5 -4
  65. package/dist/orchestration/workflow.js +11 -3
  66. package/dist/prompt-assembly/artifact.d.ts +1 -1
  67. package/dist/prompts/default.d.ts +1 -1
  68. package/dist/prompts/default.js +1 -1
  69. package/dist/scenarios/scenario-registry.d.ts +3 -3
  70. package/dist/stores/file/tool-result-store.d.ts +2 -2
  71. package/dist/tools/fs/bash-readonly-classifier.d.ts +2 -2
  72. package/dist/tools/fs/fs-bash.d.ts +1 -1
  73. package/dist/tools/fs/fs-search-tools.d.ts +1 -1
  74. package/dist/tools/fs/fs-shared.d.ts +1 -1
  75. package/dist/tools/fs/safety.d.ts +1 -1
  76. package/dist/tools/scheduler-tools.d.ts +1 -1
  77. package/dist/tools/task-list.d.ts +1 -1
  78. package/dist/tools/web.d.ts +3 -3
  79. package/package.json +1 -1
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * The floor is `parseLeadingCommandName` — the one simple-command parser already shared by the
20
20
  * read-only classifier, the reversibility probe, the coarse command-name policy and the skill tool
21
- * specifier. A second tokenizer would drift, and drift on a loosening face shows up as a bypass rather
21
+ * specifier. A second tokenizer would drift, and drift on a loosening face shows up as a circumvention rather
22
22
  * than as a test failure.
23
23
  *
24
24
  * ## Normalization order is load-bearing
@@ -203,7 +203,7 @@ export interface EffectivePermissionRule {
203
203
  /**
204
204
  * design/182 §7.3 — the introspection face: every personal rule with its effective status under the org
205
205
  * overlay. An org deny does not delete a personal rule — the personal rule is the user's approval
206
- * record, and the org's power is to OVERRIDE it, not to forge its removal — so a shadowed rule reports
206
+ * record, and the org's power is to OVERRIDE it, not to self-declare its removal — so a shadowed rule reports
207
207
  * `shadowed-by-org` and comes back by itself when the org deny is withdrawn. A rule whose every add is
208
208
  * tombstoned reports `removed` (its tombstone identity is still visible in the store).
209
209
  *
@@ -159,7 +159,7 @@ export interface RuleSyncJoinDelta {
159
159
  * What a write may say. Authorization discriminates on the DELTA SHAPE, not on a full snapshot: only the
160
160
  * add arm can introduce a dot, and the delete arm carries a tombstone and no adds. A backend additionally
161
161
  * REFUSES at runtime any delete that would introduce a new add dot — structure and runtime check together,
162
- * so "pick the delete arm and smuggle an add" is neither expressible nor accepted. The `sync-join` arm's
162
+ * so "pick the delete arm and carry across an add" is neither expressible nor accepted. The `sync-join` arm's
163
163
  * safety does not rest on hiding the type (ruled 2026-08-10: the backend contract IS exported for
164
164
  * out-of-repo store twins) — it rests on every inbound record inside it passing the single validator
165
165
  * again AT THE BACKEND: the fifth door of design/179 §4's validator list closes here, not at the
@@ -34,7 +34,7 @@ export declare class ActiveSkillScope {
34
34
  * - DENY any tool not in the INTERSECTION of every active frame's `allowTools`.
35
35
  * - For a WRITE tool with an `allowPaths` constraint on ANY active frame, DENY a write whose
36
36
  * canonicalized target is outside every listed prefix (reuses {@link canonicalizeTarget}, so a
37
- * symlink can't smuggle a write outside the allowed paths).
37
+ * symlink can't carry across a write outside the allowed paths).
38
38
  * - A frame whose manifest could not be resolved (`kind:"unresolved"`) → DENY-ALL (fail-closed: no
39
39
  * allowlist means nothing is allowed — NOT the bare task policy).
40
40
  * When NO frame is active, the policy returns `allow` for everything (it only ever subtracts), leaving
@@ -27,7 +27,7 @@ export declare const DEFAULT_CONSOLIDATION_MAX_NOTES = 50;
27
27
  export declare const DEFAULT_CONSOLIDATION_TIMEOUT_SEC = 30;
28
28
  /** Normalize a note body for the EXACT-restatement check in the `<lo` near-dup cheap path. Case- + whitespace-
29
29
  * insensitive ONLY (lower-case, collapse whitespace runs, trim); it PRESERVES every symbol/punctuation so it can
30
- * NOT false-equate semantically-distinct facts. (service [365] adversarial: the earlier `[^\p{L}\p{N}]`-stripping
30
+ * NOT false-equate semantically-distinct facts. (service [365] falsification-style: the earlier `[^\p{L}\p{N}]`-stripping
31
31
  * normalization made "C#"≡"C", "delta +5"≡"delta -5", "100%"≡"100" — and a textual-CONTAINMENT test on top of it
32
32
  * also mis-judged "rate limit 1000" ⊇ "rate limit 100" (word boundary). A fire-on-every-note, no-LLM path must not
33
33
  * make a semantic-subset call, so the cheap path now folds ONLY an exact restatement; every real near-dup defers
@@ -864,7 +864,7 @@ export interface InheritedGate {
864
864
  * Org-memory admission freeze (ruled 2026-08-05): the spawning chain's FROZEN admitted org-scope
865
865
  * set — every org memory scope the parent actually mounted (deployment-origin + admitted request).
866
866
  * A delegated child's REQUEST-origin org scopes must be a subset (intersection ≠ request ⇒ the
867
- * child's prepare refuses, `memory.admission_denied`); deployment-origin scopes bypass the freeze
867
+ * child's prepare refuses, `memory.admission_denied`); deployment-origin scopes circumvent the freeze
868
868
  * (operator authority is deployment-wide). ALWAYS emitted by chain assembly (empty array = parent
869
869
  * mounted no org plane), so an ABSENT field discriminates an older-shape chain — which the child
870
870
  * side reads fail-closed as the empty set. Serializable (plain strings): persists on the
@@ -872,7 +872,7 @@ export interface InheritedGate {
872
872
  */
873
873
  admittedOrgScopes?: readonly string[];
874
874
  /**
875
- * The MONOTONIC org-governance provenance bit (adversarial round 5): `true` when any leg of this
875
+ * The MONOTONIC org-governance provenance bit (falsification-style round 5): `true` when any leg of this
876
876
  * tree ran with an org-admission surface configured (resolver / deployment scope list) or
877
877
  * inherited the bit. It never clears once set (a governed tree stays governed), and it counts as
878
878
  * governance evidence at the admission door EVEN when every admitted set en route is empty — a
@@ -1055,7 +1055,7 @@ export interface RunInternals {
1055
1055
  * arbitration is decided) and BEFORE the resumed leg starts. The parked-resume caller uses it to
1056
1056
  * flip the reserved row `parked→running` (guarded CAS) + adopt the live handle/lane — the ONLY
1057
1057
  * legal site for that flip (a pre-consume flip would let TaskStop hit the plain running arm and
1058
- * bypass the checkpoint arbitration entirely). A THROW here aborts the resume (propagates out of
1058
+ * circumvent the checkpoint arbitration entirely). A THROW here aborts the resume (propagates out of
1059
1059
  * `resumeStream`): the checkpoint is already consumed, so the caller's compensation
1060
1060
  * (`rollbackParkedClaim`) reads `resolved` and settles the honest failed/outcome-unknown terminal
1061
1061
  * — never a silent run without an adopted row. Deployment/trusted-caller channel only, mirroring
@@ -1442,6 +1442,11 @@ export interface ResolvedWorkspace {
1442
1442
  cwd: string;
1443
1443
  /** True only for an accepted `isolation: "worktree"` request (⇒ `cwd` is this agent's own worktree). */
1444
1444
  isolated: boolean;
1445
+ /** True when the task's execution env is REMOTE (#197 BGW-7): `cwd` then names a path INSIDE the remote
1446
+ * target (a per-task sandbox on the shape-(d) exemption leg, e.g. `/workspace`), which does not exist —
1447
+ * or names an unrelated directory — on the host. `isolated && !remote` is the only combination under
1448
+ * which `cwd` is a host path a consumer may diff / merge / remove. */
1449
+ remote: boolean;
1445
1450
  }
1446
1451
  /**
1447
1452
  * From the resumed/active transcript, the batch position of `currentId` (design/45 §4.ter): the tool-call
@@ -219,6 +219,9 @@ export function resolveModelPromptTraits(model, spec, internals) {
219
219
  fableMitigations: isFableFamilyModelId(model.id),
220
220
  };
221
221
  }
222
+ function hasConversationContent(branch) {
223
+ return branch.some((e) => e.type === "message" || e.type === "custom_message" || e.type === "compaction");
224
+ }
222
225
  function isDelegatedNonForkChild(internals) {
223
226
  return internals?.isDelegatedChild === true && internals?.insideFork !== true;
224
227
  }
@@ -910,7 +913,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
910
913
  }
911
914
  if (internals?.onWorkspaceResolved !== undefined) {
912
915
  try {
913
- internals.onWorkspaceResolved({ cwd: taskRootPath, isolated: internals.isolation === "worktree" });
916
+ const workspaceEnv = ownedEnv ?? deps.executionEnv;
917
+ internals.onWorkspaceResolved({
918
+ cwd: taskRootPath,
919
+ isolated: internals.isolation === "worktree",
920
+ remote: workspaceEnv !== undefined && isRemoteExecutionEnv(workspaceEnv),
921
+ });
914
922
  }
915
923
  catch {
916
924
  }
@@ -2017,7 +2025,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2017
2025
  if (spec.sessionId) {
2018
2026
  try {
2019
2027
  const branch = await session.getBranch();
2020
- const hasConversation = branch.some((e) => e.type === "message" || e.type === "custom_message" || e.type === "compaction");
2028
+ const hasConversation = hasConversationContent(branch);
2021
2029
  if (!hasConversation)
2022
2030
  projectMemoryPhase = "fresh";
2023
2031
  else {
@@ -2498,7 +2506,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2498
2506
  }
2499
2507
  else if (resume === undefined && internals?.parentTaskId === undefined && internals?.parentSessionId === undefined) {
2500
2508
  const candidate = deps.promptSource?.current();
2501
- if (candidate !== undefined && (await session.getBranch()).length === 0) {
2509
+ if (candidate !== undefined && !hasConversationContent(await session.getBranch())) {
2502
2510
  centerAdoption = { artifact: candidate.artifact, sourceRevision: candidate.sourceRevision };
2503
2511
  centerAdoptionFresh = true;
2504
2512
  }
@@ -2577,7 +2585,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2577
2585
  const resolved = resolveEpochAgainstBundled(pinned, SEMA_DEFAULT_PACK, epochDeclaredSections);
2578
2586
  epochArtifactDigestForSnapshot = resolved.descriptor.artifactDigest;
2579
2587
  if (resolved.migrated) {
2580
- const activatedBy = pinned === undefined && !spec.sessionId ? "session_start" : "legacy_migration";
2588
+ const priorConversation = pinned === undefined ? hasConversationContent(await session.getBranch()) : true;
2589
+ const activatedBy = pinned === undefined && !priorConversation ? "session_start" : "legacy_migration";
2581
2590
  await session.appendPromptEpoch({
2582
2591
  ...resolved.descriptor,
2583
2592
  activatedBy,
@@ -3185,6 +3194,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3185
3194
  ...((internals?.explicitAgentName ?? internals?.agentName) !== undefined
3186
3195
  ? { sourceAgentName: internals?.explicitAgentName ?? internals?.agentName }
3187
3196
  : {}),
3197
+ ...(internals?.isDelegatedChild === true ? { isDelegatedChild: true } : {}),
3188
3198
  });
3189
3199
  const riskAxesOf = (toolName) => {
3190
3200
  const tier = irreversibilityTier.get(toolName);
@@ -300,7 +300,7 @@ export declare class Runner {
300
300
  * `inheritedManifestScope` and every other RunInternals field) — the library caller is trusted to
301
301
  * re-supply the genuine chain; a caller able to invoke resumeStream already holds full Runner
302
302
  * authority, so constraint substitution here is outside the enforced threat model (the count check
303
- * catches shape MISTAKES, not a malicious trusted caller). */
303
+ * catches shape MISTAKES, not a untrusted trusted caller). */
304
304
  internals?: RunInternals): Promise<TaskStream>;
305
305
  /**
306
306
  * design/45 resume step: with the harness idle and the branch rewound to the suspension leaf, resolve
@@ -35,7 +35,7 @@ import { hasDestroy, isIsolated } from "../remote-env.js";
35
35
  import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
36
36
  import { cloneObserverInput, formatHookFeedback } from "../hooks.js";
37
37
  import { buildHumanInputEvent, projectHumanInput } from "../human-input-projection.js";
38
- import { delimitUntrusted, inlineUntrusted, sanitizeUntrustedText } from "../untrusted-text.js";
38
+ import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText } from "../untrusted-text.js";
39
39
  import { reconcileInterruptedSession } from "../session-reconcile.js";
40
40
  import { RunnerSharedToolResultStore } from "../tool-result-store.js";
41
41
  import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
@@ -245,7 +245,7 @@ function resumeContinuation(resume) {
245
245
  if (resume.outcome.gate === "dry_run_review") {
246
246
  const verdict = resume.outcome.decision === "approve"
247
247
  ? "Your predicted change was REVIEWED and APPROVED; it has been applied"
248
- : `Your predicted change was REVIEWED and REJECTED${resume.outcome.reason ? `: ${delimitUntrusted("reviewer note", resume.outcome.reason)}` : ""}; it was NOT applied`;
248
+ : `Your predicted change was REVIEWED and REJECTED${resume.outcome.reason ? `: ${delimitUntrusted("reviewer note", resume.outcome.reason, REVIEWER_NOTE_MAX_BODY)}` : ""}; it was NOT applied`;
249
249
  return formatHookFeedback(`You were resumed after a dry-run review. ${verdict}. Before continuing, re-orient from the workspace ` +
250
250
  "(run `git status` and review your recent changes) to confirm the current state, then continue the " +
251
251
  "remaining work. Do NOT restart the task or re-run work that is already done.");
@@ -265,7 +265,7 @@ function resumeContinuation(resume) {
265
265
  "or restart from scratch, just carry out the revised plan.\n\nThe REVISED plan to follow is:\n" +
266
266
  delimitUntrusted("revised plan", resume.outcome.editedPlan));
267
267
  }
268
- const why = resume.outcome.reason ? ` Reviewer note: ${delimitUntrusted("reviewer note", resume.outcome.reason)}` : "";
268
+ const why = resume.outcome.reason ? ` Reviewer note: ${delimitUntrusted("reviewer note", resume.outcome.reason, REVIEWER_NOTE_MAX_BODY)}` : "";
269
269
  return formatHookFeedback(`Your proposed PLAN was REVIEWED and REJECTED; it was NOT executed.${why} Produce a NEW plan that ` +
270
270
  "addresses the concern, then continue. This is a RESUMED task — re-plan from the current state; do " +
271
271
  "NOT execute the rejected plan.");
@@ -276,7 +276,7 @@ function resumeContinuation(resume) {
276
276
  }
277
277
  const decided = resume.outcome.decision === "allow"
278
278
  ? "was APPROVED and has now been executed — its result is in the tool results above"
279
- : `was DENIED${resume.outcome.reason ? `: ${delimitUntrusted("reviewer note", resume.outcome.reason)}` : ""}`;
279
+ : `was DENIED${resume.outcome.reason ? `: ${delimitUntrusted("reviewer note", resume.outcome.reason, REVIEWER_NOTE_MAX_BODY)}` : ""}`;
280
280
  const remaining = pendingAction.batchToolCallIds.filter((id) => id !== pendingAction.toolCallId && !pendingAction.completedCallIds.includes(id)).length;
281
281
  const batchNote = remaining > 0
282
282
  ? ` ${remaining} other tool call(s) from that batch were returned as deferred and not run — re-issue any you still need.`
@@ -4004,7 +4004,7 @@ export class Runner {
4004
4004
  : outcome.settledBy === "aborted"
4005
4005
  ? `The approval for the pending tool call "${pendingAction.toolName}" ended without anyone deciding it (it was cancelled or could not be delivered), so it was not executed.`
4006
4006
  : `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
4007
- const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason) : defaultDenial;
4007
+ const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason, REVIEWER_NOTE_MAX_BODY) : defaultDenial;
4008
4008
  emitEnd(true, { content: formatHookFeedback(reason) });
4009
4009
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason), true));
4010
4010
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
@@ -54,9 +54,9 @@ export declare function isWithin(root: string, p: string): boolean;
54
54
  * - `toolAllow` (if set) → deny any tool NOT listed (a narrowing allowlist).
55
55
  * - `commandAllow`/`commandDeny` → delegate to {@link createCoarseCommandNamePolicy} (argv[0] names; only
56
56
  * speaks about shell tools). allowlist mode denies the un-listed/un-parseable; blocklist mode asks on the
57
- * un-parseable bypass surface (the gate decides).
57
+ * un-parseable circumvention surface (the gate decides).
58
58
  * - `allowDirs` (if set) → a WRITE tool must land inside one of the dirs (resolved via `canonicalizeTarget`,
59
- * so a symlink can't smuggle a write out); a write-capable tool that cannot be path-confined (e.g. `bash`)
59
+ * so a symlink can't carry across a write out); a write-capable tool that cannot be path-confined (e.g. `bash`)
60
60
  * is DENIED while `allowDirs` is set (fail-closed, mirrors active-skill-scope MAJOR-2).
61
61
  * Returns `allow` when nothing constrains the call. `env`/`rootPath` are the task's real exec env + root, so
62
62
  * matching sees the actual filesystem (a remote/E2B task resolves in its container). RB-108: a relative
@@ -9,7 +9,7 @@
9
9
  * mask the original error") — but a promise that never settles is not a throw, and one wedged
10
10
  * adapter turned into: for-await done, `result()` never settles (②型, process alive), or — when the
11
11
  * wedge happened before the queue closed — a zero-handle pure-logic hang (the 2.12.0 exit-13 shape).
12
- * The session lock releases in the run IIFE's finally, so one wedged run also poisoned every later
12
+ * The session lock releases in the run IIFE's finally, so one wedged run also contaminated every later
13
13
  * same-session resume.
14
14
  *
15
15
  * The remedy is the same doctrine as the workflow journal drain (batch-4 F2): a best-effort leg gets
@@ -223,7 +223,7 @@ export declare function resolveToolSearch(args: ToolSearchArgs, registry: Readon
223
223
  * direct call (ruled 2026-08-03) activates too and is flagged as an error, so it lands in that same
224
224
  * under-approximated class by construction: within the process the in-memory `active` set carries it, and
225
225
  * a replay that loses it costs the corrected call — which succeeds, and is itself carrier ②. The one way to
226
- * forge a positive is a deployment `tool_result` hook that rewrites a rejection's error flag to false,
226
+ * self-declare a positive is a deployment `tool_result` hook that rewrites a rejection's error flag to false,
227
227
  * which is the deployment editing its own transcript.
228
228
  *
229
229
  * design/116 W1-2 note: pre-116 checkpoints whose KEYWORD queries activated up to 25 tools re-derive at
@@ -27,7 +27,7 @@ export declare class SchedulerError extends Error {
27
27
  constructor(code: SchedulerErrorCode, message: string, cause?: unknown | undefined);
28
28
  }
29
29
  /**
30
- * 🔴 **模型可控面(不可信)**:纯意图,**无任何身份字段** —— 防模型经自排 prompt 提权(design/62 同款红线:身份是
30
+ * 🔴 **模型可控面(不可信)**:纯意图,**无任何身份字段** —— 防模型经自排 prompt 权限抬升(design/62 同款红线:身份是
31
31
  * Runner-held,非工具参数)。`prompt` = 未来 task 的不可信 `objective`(daemon 起 task 时经完整 prepare-task gate)。
32
32
  */
33
33
  export interface ScheduledIntent {
@@ -84,7 +84,7 @@ export interface ScheduledIntent {
84
84
  */
85
85
  export interface SchedulerContext {
86
86
  /**
87
- * 越权隔离键(cancel/list 只能操作同 scope 排的)。由 Runner 填,且**按被操作 intent 的 lifetime 分叉**
87
+ * 越界隔离键(cancel/list 只能操作同 scope 排的)。由 Runner 填,且**按被操作 intent 的 lifetime 分叉**
88
88
  * (ruled 2026-08-03):`lifetime:"session"` 的意图用会话链(sessionId ?? principal ?? taskId ?? "default");
89
89
  * durable 意图用跨会话稳定身份(principal ?? "default")——否则 durable job 会被钉死在排它的那个会话里,
90
90
  * 后续会话 cancel/list 一律 `not_found`(任务照常触发却再也删不掉)。backend 侧无需分辨腿别:照旧只按
@@ -92,7 +92,7 @@ export interface SchedulerContext {
92
92
  * ⚠️ 部署面:本分叉之前写下的 durable 行仍带旧的会话键,一次性重铸键归 backend 半场,engine 不做迁移。
93
93
  */
94
94
  scope: string;
95
- /** 排程者身份;daemon 触发时原样传给 runTask(防提权)。 */
95
+ /** 排程者身份;daemon 触发时原样传给 runTask(防权限抬升)。 */
96
96
  principal?: string;
97
97
  /** 可选:daemon 触发新 task 时复用的 session(chat-continuity;缺省=fresh-session)。⚠️ 复用需 durable sessionStore(design/105 §3.4)。 */
98
98
  sessionId?: string;
@@ -169,7 +169,7 @@ export interface SchedulerCapability {
169
169
  schedule(intent: ScheduledIntent, ctx: SchedulerContext): Promise<Result<{
170
170
  id: ScheduledTaskId;
171
171
  }, SchedulerError>>;
172
- /** 取消一个已排意图(幂等:取消不存在的是 ok no-op)。🔴 越权:`id` MUST 属本 scope;非本 scope 一律 `not_found`,不泄露存在性。 */
172
+ /** 取消一个已排意图(幂等:取消不存在的是 ok no-op)。🔴 越界:`id` MUST 属本 scope;非本 scope 一律 `not_found`,不越界存在性。 */
173
173
  cancel(id: ScheduledTaskId, ctx: SchedulerContext): Promise<Result<void, SchedulerError>>;
174
174
  /** 列**本 scope** 已排意图(self-discovery + 防重复排)。范围 = `ctx.scope`,非全局。 */
175
175
  list(ctx: SchedulerContext): Promise<Result<ScheduledTaskSummary[], SchedulerError>>;
@@ -16,7 +16,7 @@
16
16
  * can't lose an update (mirrors the checkpoint store's `resolve` OCC).
17
17
  * - **structured fields only**: core interprets `toolAllow/Deny`, `allowDirs`, `commandAllow/Deny` (argv[0]
18
18
  * names). A richer rule DSL is the service's/profile's job — core grows no rule interpreter.
19
- * - **NOT A SANDBOX**: command rules inherit the coarse argv[0] matching (wrappers like `sh -c`/`sudo` bypass);
19
+ * - **NOT A SANDBOX**: command rules inherit the coarse argv[0] matching (wrappers like `sh -c`/`sudo` circumvention);
20
20
  * the real isolation boundary is the executionEnv, never these rules.
21
21
  */
22
22
  /** The structured per-session rules. All optional; an absent field imposes no constraint of that kind. */
@@ -86,11 +86,11 @@ export interface SessionPolicyStore {
86
86
  /**
87
87
  * LEXICAL path containment for the tighten gate. **Must agree with the runtime's view of what a dir IS** (the
88
88
  * runtime resolves `..`/`.`/`//` via `canonicalizeTarget`): both args are `posix.normalize`d FIRST so a
89
- * non-operator can't smuggle a write-confinement OUTSIDE the prior dir by writing `<prior>/../etc` — which is
89
+ * non-operator can't carry across a write-confinement OUTSIDE the prior dir by writing `<prior>/../etc` — which is
90
90
  * lexically "within `<prior>`" by a naive `startsWith` but canonicalizes to `/etc` at run time (the tighten
91
91
  * gate must never UNDER-report a loosening).
92
92
  *
93
- * The `..`-resolving `posix.normalize` pass is THIS function's own job (the tighten gate's `..`-smuggling
93
+ * The `..`-resolving `posix.normalize` pass is THIS function's own job (the tighten gate's `..`-carrying across
94
94
  * defense above) and is NOT shared with the runtime's `isWithin` (session-rule-policy.ts), which compares
95
95
  * already-canonicalized keys and never resolves `..` itself — the two functions agree on every OTHER input
96
96
  * (same win-family fold, same prefix semantics) but diverge on inputs that still carry an unresolved `..`,
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * WHY A GATE AT ALL. A TypeScript annotation is a compile-time claim, and a Provider is third-party
6
6
  * (BYOM) code that may be plain JavaScript. Every value crossing this seam is therefore treated as
7
- * foreign data: exotic shapes, throwing getters and hostile metadata all have a defined verdict here,
7
+ * foreign data: exotic shapes, throwing getters and untrusted metadata all have a defined verdict here,
8
8
  * so no "shape outside the rules" can leak an exception into a tool body or a crafted string onto a
9
9
  * prompt line.
10
10
  *
@@ -35,7 +35,7 @@
35
35
  * admitting a second program smuggled in behind the first.
36
36
  *
37
37
  * Reusing that parser rather than writing a second tokenizer is deliberate: two argv[0] parsers drift,
38
- * and the drift shows up as a bypass rather than as a test failure.
38
+ * and the drift shows up as a circumvention rather than as a test failure.
39
39
  *
40
40
  * ## Scope: the shell-command lane only
41
41
  *
@@ -6,7 +6,7 @@ export interface TaskNotificationPayload {
6
6
  task_id: string;
7
7
  /** design/144 §2: `"external"` is CORE-MINTED ONLY — `TaskStream.notify()` casts it onto every
8
8
  * externally injected event, and the input type ({@link ExternalNotificationInput}) deliberately has
9
- * no `task_type` field, so an outside producer can never wear an internal lane's type (a forged
9
+ * no `task_type` field, so an outside producer can never wear an internal lane's type (a caller-declared
10
10
  * "background_bash completed" impersonating a real child). */
11
11
  task_type: "background_bash" | "workflow" | "background_agent" | "monitor" | "external";
12
12
  toolUseId?: string;
@@ -27,7 +27,7 @@ export interface TaskNotificationPayload {
27
27
  * `cycleSeq`, or — on a tier-3 cross-process revival / a parked resume, which deliberately carry
28
28
  * no retain entry — the durable row's own `seq`. ABSENT only when neither exists (retain OFF on a
29
29
  * store-less spawn, an expired ledger with no durable row, the fork lane): the cycle count is not
30
- * knowable there, and an honest downgrade to the old single-cycle key beats a forged period
30
+ * knowable there, and an honest downgrade to the old single-cycle key beats a caller-declared period
31
31
  * number. So `seq` present does NOT imply a retain entry exists.
32
32
  * - `external`: caller-supplied verbatim ({@link ExternalNotificationInput.seq}) — the injector's
33
33
  * own dedup discriminator across same-task_id+status events.
@@ -14,14 +14,14 @@ export declare function ensureDurableHeartbeatLane(core: DurableAgentCore): void
14
14
  * re-apply (the real terminal truth must beat a reaper's guess); only a row carrying a
15
15
  * FOREIGN writerId poisons the lane (never clobber another writer). */
16
16
  export declare function durableAgentWriteLane(handle: BackgroundAgentTaskHandle, patch: Partial<BackgroundAgentRecord>, clear?: readonly (keyof BackgroundAgentRecord)[]): void;
17
- /** design/151 S1b — is this row's durable lane ARMED (store wired at registration, not poisoned)?
17
+ /** design/151 S1b — is this row's durable lane ARMED (store wired at registration, not contaminated)?
18
18
  * Observability/test face. ⚠️ NOT the lifecycle-flip key: arming says a row was ATTEMPTED, not
19
19
  * that it EXISTS — release anchors gate on {@link durableAgentRowProbe} (codex r2 F2). */
20
20
  export declare function durableAgentArmedLane(core: DurableAgentCore, id: string): boolean;
21
21
  /** design/151 S1b (codex r2 F2) — the AWAITABLE lifecycle-flip key. Returns undefined when no
22
22
  * lane was armed; otherwise a probe CLOSURE over the HANDLE (outlives its eviction from the map —
23
23
  * retain-ledger TTL arms fire hours after terminal GC) that flushes the queued writes and answers
24
- * whether a durable row actually EXISTS right now (initial put confirmed, not poisoned). A store
24
+ * whether a durable row actually EXISTS right now (initial put confirmed, not contaminated). A store
25
25
  * that never accepted the put answers false — the caller then RELEASES the session (pre-151 path)
26
26
  * instead of stranding an unreachable transcript.
27
27
  *
@@ -102,13 +102,13 @@ export declare function bindBackgroundAgentSessionLane(core: DurableAgentCore, i
102
102
  * per leg at the injector-ready barrier (prepare has succeeded by then, so the verdict exists); a
103
103
  * revival cycle overwrites the previous cycle's record with its own, which the admission door
104
104
  * guarantees is a subset. Same durable write lane as every other post-registration fact — a
105
- * store-less registration is a no-op, and a poisoned lane stays poisoned.
105
+ * store-less registration is a no-op, and a contaminated lane stays contaminated.
106
106
  *
107
107
  * A DROPPED write is disclosed. This record is the only carrier a narrowing has across a process
108
108
  * boundary, so what stands on the row when the write does not land is the PREVIOUS leg's wider
109
- * verdict — which the next revival then seeds from. Two drop shapes, one message each: a poisoned
109
+ * verdict — which the next revival then seeds from. Two drop shapes, one message each: a contaminated
110
110
  * lane refuses at its front door, and an exhausted flush leaves the value unwritten with the lane
111
- * still armed. The channel is the same durable-agents warning channel the lane uses for the poison
111
+ * still armed. The channel is the same durable-agents warning channel the lane uses for the contamination
112
112
  * itself (that one says writes stopped; this one says WHICH fact stopped with them). The write
113
113
  * remains best-effort: no leg faults because its bookkeeping failed.
114
114
  *
@@ -197,7 +197,7 @@ export declare function rollbackParkedClaimLane(core: DurableAgentCore, stores:
197
197
  * design/153 §7.2d (件3c, codex 3c H-1) — the CONSUME FLIP, SERIALIZED THROUGH THE DURABLE LANE:
198
198
  * the guarded parked→running store CAS runs INSIDE the lane chain (ordered after every seeded
199
199
  * write — the session-bind CAS included), so no in-flight lane write can race it, CAS-fail on the
200
- * flip's rev bump, read the new epoch as foreign, and poison the lane (which would permanently
200
+ * flip's rev bump, read the new epoch as foreign, and contaminate the lane (which would permanently
201
201
  * drop finalize/terminal/re-park writes). On a won CAS the lane record/rev swap happens in the
202
202
  * SAME chain step and the live handle adopts (running, arbitration dropped — the token is
203
203
  * consumed; `parkClaimId` stays visible until finalize). Returns false when the row moved
@@ -284,7 +284,7 @@ export declare function markRetainedContinuationLane(core: DurableAgentCore, id:
284
284
  * The DURABLE transition is a {@link claimTerminalRowForRevive} claim, awaited BEFORE the in-memory
285
285
  * flip — the two orders are not equivalent: flipping memory first (the pre-arbitration shape, a plain lane
286
286
  * write with no row guard) let a foreign claim win the row while this process kept serving
287
- * `running`, and every later write of this cycle then died in the poisoned lane without a trace.
287
+ * `running`, and every later write of this cycle then died in the contaminated lane without a trace.
288
288
  * Three lane shapes, three treatments:
289
289
  * - NO lane (store-less registration): the pre-arbitration in-memory-only transition, unchanged — there is
290
290
  * no row, hence no arbitration domain and no competitor;
@@ -293,11 +293,11 @@ export declare function markRetainedContinuationLane(core: DurableAgentCore, id:
293
293
  * the chain (row absent AND the put still unconfirmed after the chain drained), never by the
294
294
  * entry-time flag: a put merely in flight, or one that committed under a failed response, would
295
295
  * otherwise exempt a row that does exist — the split-brain window this claim exists to close;
296
- * - POISONED lane (stale-flip ownership transfer / row reaped mid-write / heartbeat re-drive
296
+ * - Contaminated lane (stale-flip ownership transfer / row reaped mid-write / heartbeat re-drive
297
297
  * ceiling): the claim runs DIRECTLY through the store — the dead lane's chain is no longer a
298
298
  * serialization point, the rev guard is. Re-arming that lane object in place is what is NOT
299
299
  * done: an in-flight write from it would then see its own writerId on the re-claimed row and
300
- * adopt it. It stays poisoned (its record keeps the pre-claim writerId/epoch, so its read-back
300
+ * adopt it. It stays contaminated (its record keeps the pre-claim writerId/epoch, so its read-back
301
301
  * answers "foreign" and it stops), and a row that cannot be claimed — gone, or live under
302
302
  * another writer — REFUSES the revive rather than resuming with a durably unreachable row.
303
303
  * A WON claim always hands the row to a FRESH lane for the claimed generation (both paths), for the
@@ -68,7 +68,7 @@ export interface UnifiedTaskOutput {
68
68
  * (`noTask`/`noTaskForStop`/`formatWorkflowRun`/`serveDurableAgentRow`/`pollTask`/`stopTask` and every
69
69
  * per-task-kind poll/stop arm). `UnifiedTaskOutput` (the `details` face) has no isError channel of its
70
70
  * own — it is a REQUIRED-field wire shape consumed by callers beyond these two tools too, so isError is
71
- * carried as a sibling on the WRAPPER instead (trap avoidance per the E1 sweep: never smuggle isError
71
+ * carried as a sibling on the WRAPPER instead (trap avoidance per the E1 sweep: never carry across isError
72
72
  * through a narrower helper contract). Optional, absent/false = success — `createTaskOutputTool`/
73
73
  * `createTaskStopTool` forward it onto the real `ToolReturn` at the tool boundary. Every internal helper
74
74
  * keeps constructing `content`/`details` exactly as before; this only adds a place to say "and this one
@@ -97,8 +97,8 @@ export interface SemaTaskHandle {
97
97
  /** P1-3(黑板 [1920]/[1921]/[1924]/[1925]) — mint a cross-channel completion correlation id ONCE per
98
98
  * handle/record, fill-once (first writer wins): every settle/direct-terminal-flip site calls this
99
99
  * instead of writing `completionId` directly, so no matter how many terminal-transition sites a lane
100
- * has (settle / TaskStop bypass / backfill), the value is minted exactly once and every later caller
101
- * (including an idempotent re-call from a bypass path that already ran) gets back the SAME id. `string`
100
+ * has (settle / TaskStop circumvention / backfill), the value is minted exactly once and every later caller
101
+ * (including an idempotent re-call from a circumvention path that already ran) gets back the SAME id. `string`
102
102
  * is intentionally loose (any object with an optional `completionId` field) so the same helper serves
103
103
  * both a `RegisteredTaskHandle` and a `WorkflowRun` (orchestration/workflow.ts — a different module,
104
104
  * no shared base type, same fill-once shape). */
@@ -834,7 +834,7 @@ export interface RegisterBackgroundAgentInput extends TaskAccess {
834
834
  * pre-consume window keeps the live face's checkpoint arbitration; the consume flip
835
835
  * {@link TaskRegistry.adoptParkedResume} is the only path to running). Pass the ticket's token
836
836
  * and a stop-arbitration closure alongside. Also licenses replacing an in-process PARKED prior
837
- * handle of the same id (the planned park→resume handoff; lane poisoned like the terminal replace). */
837
+ * handle of the same id (the planned park→resume handoff; lane contaminated like the terminal replace). */
838
838
  initialStatus?: "parked";
839
839
  /** design/153 §7.2d — the parked checkpoint token this parked-born handle waits on. */
840
840
  parkedCheckpointToken?: string;
@@ -77,7 +77,7 @@ export declare function formatToolError(error: unknown): string;
77
77
  * in-presence condition) for that one code, or the errorCode's own doc for every other
78
78
  * member (`env.lifetime_expired`'s doc says "retrying needs a NEW environment, not a
79
79
  * smaller budget" — there is no `retryAfterMs` for it, by design: an expiring
80
- * environment has no return time to give). Codex adversarial review (rounds 2 and 4)
80
+ * environment has no return time to give). Codex falsification review (rounds 2 and 4)
81
81
  * twice suggested a distinct governance/platform class or leaving these `unknown`
82
82
  * instead — rejected both times: (a) explicit calling-convention direction; (b)
83
83
  * `WorkerErrorClass` is a small CLOSED public exported union — adding a member is an
@@ -86,7 +86,7 @@ export declare function formatToolError(error: unknown): string;
86
86
  * terminal codes, not foreign ones); (c) pre-existing precedent (`config.compliance_required`,
87
87
  * NOT part of this diff) already folds a transient/retryable governance code into `config`.
88
88
  * `memory.admission_required`/`_denied`/`env.lifetime_expired` are EXACT codes, not broad
89
- * `memory.`/`env.` prefixes — codex adversarial review (A-005.3 follow-up) caught an
89
+ * `memory.`/`env.` prefixes — codex falsification review (A-005.3 follow-up) caught an
90
90
  * earlier draft folding EVERY `memory.*`/`env.*` code here, which misclassified the
91
91
  * memory-engine's non-governance incident codes (`memory.secret_detected` et al.,
92
92
  * explicitly listed as NON-governance in `NON_GOVERNANCE_MEMORY_CODES` — a content-safety