@sema-agent/core 5.27.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 (99) hide show
  1. package/CHANGELOG.md +97 -0
  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 +25 -3
  17. package/dist/core/hooks.js +22 -3
  18. package/dist/core/human-input-projection.d.ts +2 -2
  19. package/dist/core/memory-admission.d.ts +2 -2
  20. package/dist/core/memory-engine/engine.d.ts +1 -1
  21. package/dist/core/memory-engine/engine.js +2 -4
  22. package/dist/core/memory-engine/file-backend.d.ts +68 -9
  23. package/dist/core/memory-engine/file-backend.js +69 -27
  24. package/dist/core/memory-engine/header-hints.d.ts +1 -1
  25. package/dist/core/memory-engine/layout.d.ts +32 -3
  26. package/dist/core/memory-engine/layout.js +132 -8
  27. package/dist/core/memory-engine/types.d.ts +6 -5
  28. package/dist/core/memory-recall.d.ts +1 -1
  29. package/dist/core/memory.d.ts +2 -2
  30. package/dist/core/oracle-isolation.d.ts +2 -2
  31. package/dist/core/permission-rule-consent.d.ts +83 -9
  32. package/dist/core/permission-rule-consent.js +92 -1
  33. package/dist/core/permission-rule-model.d.ts +18 -2
  34. package/dist/core/permission-rule-model.js +21 -0
  35. package/dist/core/permission-rule-org.d.ts +23 -4
  36. package/dist/core/permission-rule-org.js +67 -20
  37. package/dist/core/permission-rule-store.d.ts +1 -1
  38. package/dist/core/permission-rule-store.js +2 -2
  39. package/dist/core/permission-rule-sync.d.ts +15 -1
  40. package/dist/core/permission-rule-sync.js +89 -47
  41. package/dist/core/runner/active-skill-scope.d.ts +1 -1
  42. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  43. package/dist/core/runner/prepare-task.d.ts +8 -3
  44. package/dist/core/runner/prepare-task.js +22 -7
  45. package/dist/core/runner/runtask.d.ts +1 -1
  46. package/dist/core/runner/runtask.js +13 -6
  47. package/dist/core/runner/session-rule-policy.d.ts +2 -2
  48. package/dist/core/runner/teardown-bounded.d.ts +1 -1
  49. package/dist/core/runner/tool-disclosure.d.ts +1 -1
  50. package/dist/core/scheduler.d.ts +4 -4
  51. package/dist/core/session-policy-store.d.ts +3 -3
  52. package/dist/core/shared-memory/normalize.d.ts +1 -1
  53. package/dist/core/skill-tool-specifier.d.ts +1 -1
  54. package/dist/core/task-notification.d.ts +2 -2
  55. package/dist/core/task-registry-agent.d.ts +18 -9
  56. package/dist/core/task-registry-agent.js +51 -21
  57. package/dist/core/task-registry-monitor.js +1 -1
  58. package/dist/core/task-registry-shared.d.ts +13 -4
  59. package/dist/core/tool-errors.d.ts +2 -2
  60. package/dist/core/tool-policy.d.ts +78 -12
  61. package/dist/core/tool-policy.js +74 -7
  62. package/dist/core/tool-result-store.d.ts +109 -8
  63. package/dist/core/tool-result-store.js +95 -15
  64. package/dist/core/trace.d.ts +1 -1
  65. package/dist/core/types.d.ts +92 -22
  66. package/dist/core/types.js +30 -1
  67. package/dist/core/untrusted-text.d.ts +6 -0
  68. package/dist/core/untrusted-text.js +1 -0
  69. package/dist/engine/compaction/compaction.d.ts +1 -1
  70. package/dist/engine/harness/messages.d.ts +1 -1
  71. package/dist/engine/harness/types.d.ts +2 -2
  72. package/dist/engine/llm/types.d.ts +1 -1
  73. package/dist/engine/loop/types.d.ts +2 -2
  74. package/dist/engine/session/import-validate.d.ts +1 -1
  75. package/dist/engine/session/log-digest.d.ts +1 -1
  76. package/dist/fixtures/index.d.ts +18 -2
  77. package/dist/fixtures/index.js +11 -0
  78. package/dist/index.d.ts +2 -2
  79. package/dist/orchestration/goal.d.ts +1 -1
  80. package/dist/orchestration/workflow-types.d.ts +5 -4
  81. package/dist/orchestration/workflow.js +11 -3
  82. package/dist/prompt-assembly/artifact.d.ts +1 -1
  83. package/dist/prompts/default.d.ts +1 -1
  84. package/dist/prompts/default.js +1 -1
  85. package/dist/scenarios/scenario-registry.d.ts +3 -3
  86. package/dist/stores/file/tool-result-store.d.ts +43 -3
  87. package/dist/stores/file/tool-result-store.js +107 -19
  88. package/dist/tools/fs/bash-readonly-classifier.d.ts +2 -2
  89. package/dist/tools/fs/fs-bash.d.ts +8 -1
  90. package/dist/tools/fs/fs-search-tools.d.ts +1 -1
  91. package/dist/tools/fs/fs-shared.d.ts +6 -1
  92. package/dist/tools/fs/fs-shared.js +11 -7
  93. package/dist/tools/fs/index.d.ts +6 -0
  94. package/dist/tools/fs/index.js +2 -0
  95. package/dist/tools/fs/safety.d.ts +1 -1
  96. package/dist/tools/scheduler-tools.d.ts +1 -1
  97. package/dist/tools/task-list.d.ts +1 -1
  98. package/dist/tools/web.d.ts +3 -3
  99. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,102 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.29.0 — 2026-08-13
4
+
5
+ No API-BREAKING changes (exports grow only: `AskOutcome.reason`, `AskRequest.isDelegatedChild`).
6
+
7
+ ### Changed (behavior)
8
+
9
+ - **A human deny speaks a posture and can carry the decider's words.** The sync approval seat
10
+ `AskOutcome` gains `reason?: string` on its object arm: attached to a deny, the text reaches the
11
+ model inside the untrusted fence, bounded to 2048 code points — the same containment and bound as
12
+ the durable leg's reviewer note (whose four relay sites were previously unbounded and now share
13
+ the cap). Refusal messages fork on the issuing run's position (`AskRequest.isDelegatedChild`,
14
+ forks included): a parent-thread run is told to STOP and wait for the user, a delegated child is
15
+ told to adapt or report; the machine-greppable head line is unchanged. On an allow the seat is
16
+ never read; a non-string reason on a deny is refused loudly; timeout denials carry the note under
17
+ a neutral bridge. NOTE: the posture is model-facing text — making a parent-thread denial abort the
18
+ in-flight batch is registered follow-up work.
19
+ - **`worktreeDir` never names a non-host path.** A remote isolated agent's sandbox cwd is no longer
20
+ recorded (`ResolvedWorkspace` gains a required `remote` key; absence on the record now means
21
+ "shared tree OR remote sandbox"), and a workspace observation is authoritative replacement state —
22
+ a local→remote retry clears the stale host path.
23
+ - **Session freshness is judged by conversation content, not branch entries.** A pre-minted
24
+ sessionId or a host-pre-seeded marker (naming the chat, recording a model pick before the first
25
+ turn) no longer stamps `legacy_migration` on a brand-new session's first epoch pin — and no longer
26
+ silently costs such a session its published center prompt (the adoption arm shared the same
27
+ refuted entry-count proxy; all three freshness sites now share one predicate).
28
+ - **The ask-question tool teaches previews.** The tool description carries the Preview feature block
29
+ (CC markdown variant, verbatim), so the preview card form — the only question form whose answer
30
+ carries user notes — is reachable.
31
+ - The resident execution-environment prompt line about denied calls no longer instructs a blanket
32
+ "adjust and continue" — it defers to the denial message's own guidance (prompt golden re-frozen).
33
+
34
+ ## 5.28.0 — 2026-08-12
35
+
36
+ No API-BREAKING changes (exports grow only). Narrowings are tighten-direction and named below.
37
+
38
+ ### Narrowed (behavior, ruled)
39
+
40
+ - **A hook-promoted ask's provenance is stamped, never read back.** Every consumption point keying
41
+ on `decisionReason === "hook"` now reads an engine-stamped value: the fold reconstructs the
42
+ promoted ask from an explicit member whitelist and stamps `"hook"` unconditionally, so a hook
43
+ self-declaring any other word (or extra members like `persistedRuleShadowed`) cannot change how
44
+ the persisted-rule lane, the rule-suggestion factory, or the inherited-frame reuse treat the ask.
45
+ - **A `decisionReason` outside the closed set is refused at the consumption screen.** A
46
+ deployment-authored `ToolPolicy.check` returning an unrecognized word (or routing the field
47
+ through a getter/prototype) is denied fail-closed with the closed set named; plain decisions pass
48
+ by reference. The HOOK seam is exempt by design (the field is a documented non-input there — see
49
+ the stamp above).
50
+ - **The served org snapshot meets the same validator as a fetched one.** A persisted last-known-good
51
+ snapshot carrying a rule the current validator refuses now fails closed during a provider outage
52
+ instead of serving partial policy.
53
+ - **Legacy rule text the current validator refuses no longer stalls sync.** A shape-whole tombstone
54
+ whose TEXT is refused gets the new closed-set reason `invalid_rule_text` — disclosed, quarantined,
55
+ and no longer withholding the whole inbound-adds round (safe: tombstones match byte-identical
56
+ text only, and an add with that text is refused by the same predicate).
57
+ - **Case-colliding scope directories are refused loudly on case-insensitive filesystems.** Two
58
+ scopes differing only by case no longer silently share one physical directory (macOS/Windows);
59
+ collision detection compares folded identity (dev:ino-keyed), with lazy real probing instead of
60
+ platform guessing.
61
+ - **Spill provenance is session-only, and a failing spill degrades instead of poisoning.** The agent
62
+ spill write points stamp `{sessionId}` (matching the offload/budget/compaction form); a failed or
63
+ conflicting spill put defers to a sibling-proven ref or reports honestly — a legacy-shape row can
64
+ no longer make an agent result permanently unreadable (guarded put + generation guard + coherent
65
+ poll snapshot).
66
+
67
+ ### Added
68
+
69
+ - `onNotice` structured notice seat (three families: `config.env_timeout_discarded`,
70
+ `config.materialize_env_discarded`, `tool_result.offload_put_failed` — this entry originally said
71
+ "first two families"; corrected 2026-08-12, the third family shipped in the same release) — absent
72
+ keeps the `console.warn` text verbatim; a present non-function seat is announced once and falls
73
+ back loudly instead of silencing both channels.
74
+ - `tool_end.errorCode` now reads both discriminator spellings — `details.code` first (string), then
75
+ `details.errorKind` — so loop-thrown error frames are classifiable; `code` wins when both exist.
76
+ - `ToolResultStore.deleteBySession?` (optional member; File + in-memory implementations, not on the
77
+ scoped wrapper, not in the published contract kit) — returns `{ deleted, unattributable }`;
78
+ selection keys on recorded provenance, unowned rows are counted and never deleted. A put refused
79
+ over an orphan sidecar un-publishes its own content (pre-call state restored), and a damaged
80
+ re-published survivor is counted unattributable.
81
+ - Adopt-on-read ledger writes are serialized behind the existing cross-process txn mutex (lock-free
82
+ probe fast path; divergence acquires the mutex, reloads the ledger from disk, and re-verifies
83
+ lock ownership at recovery, side-effect and commit points) — cross-process lost-update closed.
84
+ - `runTaskStream` refuses an out-of-shape `resume` synchronously (a resume without `outcome` used to
85
+ hang `stream.result()` forever).
86
+ - `docs/INTEGRATION-CORE.md` — the core→embedder contract document (event-frame per-key obligations,
87
+ code-vocabulary pointers, TaskResult matrix, resume/decide shapes, store optional-member table);
88
+ maintained per L3 release.
89
+ - Rule-suggestion admission honesty (#174 adjacency): the suggestion factory keys on the stamped
90
+ provenance.
91
+
92
+ ### Fixed
93
+
94
+ - Control-character gate: five bench probe arms now genuinely reach their claimed product paths;
95
+ the published B2 index-truncation curve was corrected to `[0, 0, 0.005, 0.602]` (the old counting
96
+ masked a real N200 loss).
97
+ - `TaskResult.retryAfterMs` and `tool_end.errorCode` JSDoc corrected to match code (two accepted
98
+ codes; the errorKind fallback).
99
+
3
100
  ## 5.27.0 — 2026-08-11
4
101
 
5
102
  No API-BREAKING changes (exports grow only). Narrowings are tighten-direction and named below.
@@ -9,7 +9,7 @@ import type { ModelRef, TaskResult, TaskSpec } from "../core/types.js";
9
9
  * APPLICABILITY: cascade only helps when the gate is **decidable** (a schema check, a verifier model, a
10
10
  * concrete assertion). Open-ended "completeness" tasks (find every bug, writing quality) have no oracle —
11
11
  * the gate keeps passing the first plausible cheap answer and escalation idles. Those want breadth /
12
- * adversarial debate (a `team`), not a depth ladder.
12
+ * falsification-style debate (a `team`), not a depth ladder.
13
13
  *
14
14
  * Cost note (design/27 §2): each rung is an independent COLD `runTask` — every escalation repays the
15
15
  * full input cost. Cascade wins when (a) the cheap rung usually passes and (b) the prompt isn't so large
@@ -36,11 +36,11 @@ export type ObserverDigestEvent = {
36
36
  type: "turn_ended";
37
37
  reason: string;
38
38
  };
39
- /** CC `S$r` — anti-injection: a forged envelope tag inside observed content is defused in place. */
39
+ /** CC `S$r` — anti-injection: a caller-declared envelope tag inside observed content is defused in place. */
40
40
  export declare function escapeObserverTags(text: string): string;
41
41
  /**
42
42
  * CC `zZe` @8339092 — verbatim: `t.replace(new RegExp(`<(?=/?${e}(?:[>\\s/]|$))`, "gi"), "<\\")`.
43
- * Defuses a forged occurrence of ONE specific tag (opening AND closing) inside content — the second
43
+ * Defuses a caller-declared occurrence of ONE specific tag (opening AND closing) inside content — the second
44
44
  * escape layer CC applies to the DYNAMIC envelope tag (`<{slug}-activity>`) after joining, which the
45
45
  * static four-tag `escapeObserverTags` (Scg layer) cannot cover. Deviation from CC (hardening): the
46
46
  * tag is regex-escaped before entering the RegExp — CC interpolates raw, safe only because its slugs
@@ -281,7 +281,7 @@ export interface ObserverReportToolOptions {
281
281
  * `<${$Bt} from="${Yp(e)}">\n${zZe($Bt, t)}\n</${$Bt}>` with `$Bt = "agent-message"` @1344442.
282
282
  * Deviation from CC ($Bt=agent-message → observer-report, sovereign choice): we keep a dedicated
283
283
  * frame tag so the observed agent can tell an observer report from generic agent traffic. The
284
- * escape mechanics are CC-faithful: the frame tag itself is zZe-defused in the body (so a forged
284
+ * escape mechanics are CC-faithful: the frame tag itself is zZe-defused in the body (so a caller-declared
285
285
  * `</observer-report>` cannot break the frame) and the `from=` attribute value is Yp-escaped.
286
286
  * Additional hardening beyond CC: the body also rides through the four-event-tag Scg layer.
287
287
  *
@@ -145,7 +145,7 @@ export declare function resetPeerAdmissionRegistryForTests(): void;
145
145
  * mounting is structurally single-sourced and survives any downstream projection of the payload).
146
146
  * Layered WITH the session-level `SUBAGENT_CONSENT_NOTICE` (prompts/default.ts), not replacing it:
147
147
  * that section governs COMMAND authority at session level; this block narrows AUTHORIZATION per
148
- * message. Placement per leg: OUTSIDE the `<teammate-message>` frame (a forged copy inside the
148
+ * message. Placement per leg: OUTSIDE the `<teammate-message>` frame (a caller-declared copy inside the
149
149
  * sender-controlled body arrives escaped, so position distinguishes the real block); the L3 resume
150
150
  * leg carries it in the TRUST-FRAME layer of the peer-resume prompt, never inside the data fence
151
151
  * (a load-bearing instruction inside a "this is DATA" fence would demote itself).
@@ -13,7 +13,7 @@ import type { ModelRef, TaskResult, TaskSpec } from "../core/types.js";
13
13
  * creative quality — because there is no oracle for *completeness*: the verifier can't know how many bugs
14
14
  * the code has or which one was missed, so a student that finds one obvious issue passes the rubric while
15
15
  * silently missing others, and the escalation machinery idles (verified in practice: 0 escalations, the
16
- * verifier becomes pure added cost). Such tasks are solved by **breadth + adversarial debate** (a `team`
16
+ * verifier becomes pure added cost). Such tasks are solved by **breadth + falsification-style debate** (a `team`
17
17
  * council), not by **depth escalation** — the two are orthogonal. See design/12 §六 for the full reasoning.
18
18
  */
19
19
  /** Default teacher (advisor) system prompt — returns ONLY structured JSON guidance. */
@@ -3,7 +3,7 @@ import type { Runner, ResumeTaskConfig } from "../core/runner/runtask.js";
3
3
  import type { CheckpointGate, CheckpointToken, ResumeOutcome } from "../core/checkpoint-store.js";
4
4
  import type { ModelRef, TaskResult, TaskSpec, ToolSpec } from "../core/types.js";
5
5
  /**
6
- * Verification gate (developer mode, design/28 §4). An **independent adversarial verifier** runs after
6
+ * Verification gate (developer mode, design/28 §4). An **independent falsification-style verifier** runs after
7
7
  * an implementation task and tries to BREAK it — read-only, evidence-required, returning a structured
8
8
  * verdict — then the gate loops fix→re-verify until PASS or a round cap.
9
9
  *
@@ -20,7 +20,7 @@ import type { ModelRef, TaskResult, TaskSpec, ToolSpec } from "../core/types.js"
20
20
  * SKILL.md` closest to the code it covers … and if that file does not exist, create it." So upstream's
21
21
  * position is "verification is project-specific; teach the user's repo to describe its own", not "here
22
22
  * is a verifier". This primitive is therefore a SUPERSET of what upstream ships in a different
23
- * direction than the old note implied: a spawned adversarial verifier with a fix loop, bundled. It is
23
+ * direction than the old note implied: a spawned falsification-style verifier with a fix loop, bundled. It is
24
24
  * orthogonal to and composable with the Stop hook (wire the verdict into a stop() hook to make it a
25
25
  * hard completion gate).
26
26
  *
@@ -31,7 +31,7 @@ import type { ModelRef, TaskResult, TaskSpec, ToolSpec } from "../core/types.js"
31
31
  * per task via {@link runWithVerification} (or {@link runDeveloperTask}).
32
32
  *
33
33
  * Boundary vs teacher mode: teacher's Tier-1 is a *lenient* rubric verifier that triggers escalation to
34
- * an advisor (stuck/wrong recovery); this is a *strict adversarial* completion gate with a fix loop.
34
+ * an advisor (stuck/wrong recovery); this is a *strict falsification-style* completion gate with a fix loop.
35
35
  * Orthogonal, composable, not merged.
36
36
  */
37
37
  export declare const VERIFICATION_PROMPT = "You are a verification specialist. Your job is NOT to confirm the implementation works \u2014 it is to try to BREAK it.\n\nYou have two documented failure patterns. First, verification avoidance: faced with a check, you find reasons not to run it \u2014 you read code, narrate what you would test, declare \"PASS,\" and move on. Second, being seduced by the first 80%: a polished result or a passing test suite makes you inclined to pass it, not noticing the edge that crashes, the state that vanishes, the bad input that is unhandled. The first 80% is the easy part. Your entire value is in finding the last 20%.\n\n## Hard boundary \u2014 do not modify the project\nYou are STRICTLY a verifier. Do NOT create, modify, or delete project files; do NOT install packages; do NOT run version-control write operations. Use only the read/probe/execute tools available to you. (If you need a scratch file, use a temp directory, and clean up.)\n\n## Evidence is mandatory\nReading code is NOT verification. Every check must actually run something \u2014 execute the code, hit the endpoint, run the build/tests \u2014 and record the command and its real output. A \"PASS\" with no command output is a skip, not a pass.\n\n## Strategy (adapt to what changed)\n- Build/lib changes: build it, run the full test suite, exercise the public API as a consumer would.\n- Backend/API: start it, call endpoints, check response *shapes* (not just status codes), test error paths.\n- CLI/script: run with representative AND edge inputs (empty, malformed, boundary); check stdout/stderr/exit codes.\n- Bug fix: reproduce the original bug first, verify the fix, then check for regressions and side effects.\n- Refactor (no behavior change): the existing suite must pass unchanged; diff the public surface; same inputs \u2192 same outputs.\nRun the project's own build/tests/linters as a baseline, then apply the type-specific checks. Test results are context, not proof \u2014 the implementer is an LLM too; its tests may be happy-path or circular.\n\n## Adversarial probes (pick the ones that fit)\nBoundary values (0, -1, empty, very long, unicode, max), idempotency (same mutating call twice), orphan operations (ids that don't exist), concurrency (parallel create-if-not-exists). Your verdict must include at least one adversarial probe you actually ran and its result \u2014 even if it was handled correctly.\n\n## Before you FAIL\nCheck you haven't missed why it's actually fine: defensive code elsewhere, intentional behavior documented in comments/specs, or an unfixable external-contract limitation (note that as an observation, not a FAIL). Don't wave away real issues, but don't FAIL on intentional behavior.\n\n## Verdict\nSubmit exactly one verdict via the provided output tool:\n- PASS \u2014 you ran real checks (including \u22651 adversarial probe) and it holds up. Put the commands + observed output in `evidence`.\n- FAIL \u2014 something is broken. Put each concrete problem (with how to reproduce) in `findings`.\n- PARTIAL \u2014 environmental limitation only (no test framework, a tool/server unavailable). Not for \"I'm unsure\": if you can run the check, decide PASS or FAIL. Note what you couldn't verify and why in `findings`.";
@@ -202,7 +202,7 @@ export interface VerificationResult extends TaskResult {
202
202
  verification: VerificationOutcome;
203
203
  }
204
204
  /**
205
- * Verify an already-**completed** implementation `result` behind the independent adversarial verifier,
205
+ * Verify an already-**completed** implementation `result` behind the independent falsification-style verifier,
206
206
  * looping fix→re-verify until PASS (or a round cap) — **without re-running the implementation**. This is the
207
207
  * L3 entry for the L2+L3 composition (design/54 §4): in a fan-out, a worker's module has already been
208
208
  * produced, so the orchestrator runs the mechanical L2 gate ({@link runExecGate}), computes the diff, then
@@ -219,7 +219,7 @@ export interface VerificationResult extends TaskResult {
219
219
  */
220
220
  export declare function verifyCompleted(runner: Runner, result: TaskResult, specBase: ResumeTaskConfig, objective: string, config: VerifyConfig): Promise<VerificationResult>;
221
221
  /**
222
- * Run an implementation task, then gate it behind an independent adversarial verifier, looping
222
+ * Run an implementation task, then gate it behind an independent falsification-style verifier, looping
223
223
  * fix→re-verify until PASS (or a round cap). Returns the implementation result plus the
224
224
  * {@link VerificationOutcome}. The caller decides WHEN to use this (explicit opt-in) — it always
225
225
  * verifies once invoked. If the impl **suspends on a durable HITL gate**, it is surfaced as
@@ -232,7 +232,7 @@ export declare function runWithVerification(runner: Runner, implSpec: TaskSpec,
232
232
  * durable + HITL + verify integration (design/51 P1-b). The mirror of {@link runWithVerification} for the
233
233
  * resume path: `runWithVerification` surfaces a HITL suspend as failed-with-token; once the human
234
234
  * adjudicates, the caller calls this with the `token` + `outcome`, and it resumes the implementation and —
235
- * **if it COMPLETES** — runs the identical adversarial verifier + fix loop. If the resumed run suspends
235
+ * **if it COMPLETES** — runs the identical falsification-style verifier + fix loop. If the resumed run suspends
236
236
  * AGAIN (a later durable gate), it is surfaced as failed-with-token (`unverified`) for the caller to resume
237
237
  * once more. `objective` is the ORIGINAL task objective (the resume carries none of its own) — the verifier
238
238
  * needs it as context; pass the same objective the original `runWithVerification` ran with.
@@ -458,7 +458,7 @@ export interface PairedBinaryComparison {
458
458
  /**
459
459
  * codex B2 — compare two arms' delivered truly-correct as PAIRED binary. Pairs runs by the COMPOSITE
460
460
  * `(taskId, seed)` key (codex MAJOR-A: a bare `seed` collides across tasks — the same repeat index
461
- * recurs per task — so cross-task input would overwrite pairs and poison the McNemar sample);
461
+ * recurs per task — so cross-task input would overwrite pairs and contaminate the McNemar sample);
462
462
  * only rows where BOTH arms delivered (not withheld, both scored) form a pair — a
463
463
  * withheld run has no delivered binary to pair (it is scored in the withhold/avoided-loss axis, not
464
464
  * here). `mde` is the pre-registered minimum detectable effect (default 0.20 absolute = a 20pp swing).
@@ -13,7 +13,7 @@
13
13
  * - `Retry-After` (delta-seconds or HTTP-date), on any retryable response.
14
14
  * - `anthropic-ratelimit-unified-reset` (an absolute epoch-seconds stamp of when the rate-limit window
15
15
  * reopens), read only off a 429 — it is a rate-limit signal.
16
- * When both are present the larger wins. Both are clamped so a hostile/buggy value
16
+ * When both are present the larger wins. Both are clamped so a untrusted/buggy value
17
17
  * (`Retry-After: 2147483647`, a reset stamp years out) cannot pin the caller for hours.
18
18
  */
19
19
  /** Parse a `Retry-After` header (delta-seconds or HTTP-date) to ms; undefined if absent/unparseable. */
@@ -52,7 +52,7 @@ export interface SummarizableFinding {
52
52
  export declare function summarizeRedactions(findings: readonly SummarizableFinding[]): string;
53
53
  /** The optional collector every redaction entry point accepts. `preexistingMarkers` (ANG-11) counts
54
54
  * marker-shaped literals ALREADY present in the input before any pass ran — a nonzero value means the
55
- * text arrived pre-redacted or carries forged markers, either way "marker present" is not proof this
55
+ * text arrived pre-redacted or carries caller-declared markers, either way "marker present" is not proof this
56
56
  * pipeline ran. Caller-owned: pass `{ findings: [] }` and read it back after the call. */
57
57
  export interface RedactionReport {
58
58
  findings: RedactionFinding[];
@@ -8,7 +8,7 @@
8
8
  * self-reported "this ask is sandbox-local" field would be the same trust hole as the
9
9
  * `decisionReason:"safety"` self-declaration this codebase already refuses. Instead, BUILT-IN policy
10
10
  * factories register their instances here (private WeakMap brand — nothing a deployment policy can
11
- * forge by shaping its return value), and everything unregistered reads as `unknown`.
11
+ * self-declare by shaping its return value), and everything unregistered reads as `unknown`.
12
12
  *
13
13
  * Classes and their dominance (STRICT — merge keeps the strongest; only an all-`sandbox_local` set
14
14
  * may auto-admit):
@@ -27,7 +27,7 @@ export type AskClass = "sandbox_local" | "external_authority" | "hook" | "mandat
27
27
  /**
28
28
  * Brand a built-in policy instance with its ask class (engine factories only — not exported from the
29
29
  * package). The `check` binding is LOCKED as a non-writable, non-configurable DATA property before the
30
- * policy is returned (codex adversarial round 4, HIGH): reading `policy.check` and INVOKING it must
30
+ * policy is returned (codex falsification-style round 4, HIGH): reading `policy.check` and INVOKING it must
31
31
  * name the same callable, atomically. A plain `===` re-read was TOCTOU-able by an accessor — a getter
32
32
  * could return the branded function to `policyAskClassOf` and a deployment ask to the gate's
33
33
  * invocation. Locking the property makes the callable un-swappable and un-redefinable, so the class
@@ -234,7 +234,7 @@ export declare function askQuestionContinuationCard(questionId: string, continua
234
234
  * The ONE shape validator for a question batch, shared by the tool body and by the engine's routing lane
235
235
  * (which validates BEFORE it puts anything in front of a person — a malformed batch must be refused to
236
236
  * the model, not rendered). Returns the model-facing error text, or `undefined` when the batch is well
237
- * formed. Total: it never throws on hostile field shapes.
237
+ * formed. Total: it never throws on untrusted field shapes.
238
238
  */
239
239
  export declare function validateAskQuestions(questions: unknown): string | undefined;
240
240
  /** design/173 件3 — engine-side options for the mounted tool (prepare wires them; a deployment
@@ -184,7 +184,20 @@ export function createAskUserQuestionTool(onQuestion, source, opts) {
184
184
  "Usage notes:\n" +
185
185
  '- Users will always be able to select "Other" to provide custom text input\n' +
186
186
  "- Use multiSelect: true to allow multiple answers to be selected for a question\n" +
187
- '- If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label',
187
+ '- If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label' +
188
+ "\n" +
189
+ "\n" +
190
+ "Preview feature:\n" +
191
+ "Use the optional `preview` field on options when presenting concrete artifacts that users need to visually compare:\n" +
192
+ "- ASCII mockups of UI layouts or components\n" +
193
+ "- Code snippets showing different implementations\n" +
194
+ "- Diagram variations\n" +
195
+ "- Configuration examples\n" +
196
+ "\n" +
197
+ "Preview content is rendered as markdown in a monospace box. Multi-line text with newlines is supported. " +
198
+ "When any option has a preview, the UI switches to a side-by-side layout with a vertical option list on " +
199
+ "the left and preview on the right. Do not use previews for simple preference questions where labels and " +
200
+ "descriptions suffice. Note: previews are only supported for single-select questions (not multiSelect).",
188
201
  parameters: Type.Object({
189
202
  questions: Type.Array(Type.Object({
190
203
  question: Type.String({
@@ -6,16 +6,16 @@
6
6
  * 这是一个 **env 上的可选能力**(非一种独立 env 类型),所以不 `extends ExecutionEnv` —— 用交叉类型挂到具体实现上,并经
7
7
  * {@link hasBackgroundShell} 运行时检测(对齐 remote-env.ts 的 `hasDestroy`/`isRemoteExecutionEnv` 模式,零 vendor 接口改动)。
8
8
  *
9
- * 设计裁定(经 codex + workflow 5-lens 双轨对抗复审收敛,design/103 v2):
9
+ * 设计裁定(经 codex + workflow 5-lens 双轨证伪式复审收敛,design/103 v2):
10
10
  * - **不跨 durable suspend**:后台进程在每条退出路径被 dispose;Runner 必须在 suspendVM **之前** 调
11
11
  * {@link BackgroundShellCapability.disposeBackgroundShells}(detached job 不在 suspendVM 的 in-flight 契约射程内)。
12
- * - **越权隔离**:`pollBackground`/`killBackground` 的 shellId MUST 被校验为本 env 自己 spawn 过的;非本 env 走 `not_found`。
12
+ * - **越界隔离**:`pollBackground`/`killBackground` 的 shellId MUST 被校验为本 env 自己 spawn 过的;非本 env 走 `not_found`。
13
13
  * - **按-id-可重读**:实现 MUST 保证按 shellId 跨多次独立调用可重复读取累积/增量输出(execStream 的 consume-once 不满足)。
14
14
  */
15
15
  import type { ExecutionEnv, ExecutionEnvExecOptions, Result } from "../internal/harness-types.js";
16
16
  /**
17
17
  * 一个 long-running / detached 进程的句柄。**env-local、非 durable、对调用方不透明** —— adapter 内部把它映射到真实进程/
18
- * provider job,**绝不**把可猜的 raw provider job id 暴露成 shellId(否则跨租户可枚举,design/103 §3.8 越权红线)。
18
+ * provider job,**绝不**把可猜的 raw provider job id 暴露成 shellId(否则跨租户可枚举,design/103 §3.8 越界红线)。
19
19
  */
20
20
  export type BackgroundShellId = string & {
21
21
  readonly __brand: "BackgroundShellId";
@@ -105,11 +105,11 @@ export interface BackgroundShellCapability {
105
105
  /**
106
106
  * 读一个后台进程**自上次 poll 以来的新增**输出 + 当前状态(cursor 语义)。退出后仍可读残余 + exitCode,直到被 dispose/reap。
107
107
  *
108
- * 🔴 越权契约:`shellId` MUST 被校验为**本 env 自己 spawnBackground 返回过**的;非本 env 一律 `not_found`,绝不解析外部 job id。
108
+ * 🔴 越界契约:`shellId` MUST 被校验为**本 env 自己 spawnBackground 返回过**的;非本 env 一律 `not_found`,绝不解析外部 job id。
109
109
  * 🔴 重读契约:实现 MUST 保证「按 shellId 跨多次独立调用可重复读取累积/增量」(remote 的 execStream consume-once 不满足 —— 见 design/103 §5.2 两条路径)。
110
110
  */
111
111
  pollBackground(shellId: BackgroundShellId): Promise<Result<BackgroundPoll, BackgroundShellError>>;
112
- /** 杀一个后台进程(幂等:杀已死的是 no-op,返回 ok)。`shellId` 同 {@link pollBackground} 的越权校验。 */
112
+ /** 杀一个后台进程(幂等:杀已死的是 no-op,返回 ok)。`shellId` 同 {@link pollBackground} 的越界校验。 */
113
113
  killBackground(shellId: BackgroundShellId): Promise<Result<void, BackgroundShellError>>;
114
114
  /**
115
115
  * 杀掉并清理**本 env 的所有**后台进程。Runner 在每条退出路径调:finish/abort/throw 在 run-loop tail finally,**suspend/
@@ -784,7 +784,7 @@ export interface CheckpointState {
784
784
  /** THIS task's OWN admitted org verdict at suspend (adversarial-review adoption, 2026-08-05):
785
785
  * distinct from {@link admittedOrgScopes} (the incoming PARENT constraint). The resume leg
786
786
  * applies it as the SESSION freeze on the final admitted projection — regardless of origin
787
- * (deployment-origin scopes bypass the chain freeze, never the session freeze) — so a session's
787
+ * (deployment-origin scopes circumvent the chain freeze, never the session freeze) — so a session's
788
788
  * verdict can only NARROW across suspend/resume: re-adjudication still runs every leg (a
789
789
  * revoked grant refuses the resume), but a policy widened mid-session cannot widen THIS
790
790
  * session's mount or the chain its children inherit. Present (possibly EMPTY — an adjudicated
@@ -797,7 +797,7 @@ export interface CheckpointState {
797
797
  * suspend leg did not hold); a scope string = the explicit grant. Present iff
798
798
  * {@link ownAdmittedOrgScopes} is (same adjudication record). */
799
799
  ownAdmittedOrgWriteScope?: string | null;
800
- /** The MONOTONIC org-governance provenance bit (adversarial round 5): `true` when any leg of
800
+ /** The MONOTONIC org-governance provenance bit (falsification-style round 5): `true` when any leg of
801
801
  * the suspended tree ran with an org-admission surface (or inherited the bit). A resume worker
802
802
  * missing the surface still treats the leg as governed — org-shaped scopes respelled outside
803
803
  * the v2 contract are refused, even when every admitted set en route was EMPTY. Absent on
@@ -901,7 +901,7 @@ export declare const RESOURCE_CHECKPOINT_VERSION = 2;
901
901
  * `boundInputHash`) whose enforcement lives ENTIRELY in the resuming worker's resume path. A pre-D-1 worker
902
902
  * (released 1.100.0: `MAX_SUPPORTED`=2, and its resume code has ZERO binding logic) would otherwise resume a
903
903
  * D-1-minted v1 checkpoint and execute the pending tool with NO decision-action verification — the exact
904
- * "approve vendor-A $5 → execute vendor-B $5000" bypass the binding exists to prevent (council BLOCKER #1).
904
+ * "approve vendor-A $5 → execute vendor-B $5000" circumvent the binding exists to prevent (council BLOCKER #1).
905
905
  * Stamping these at **v3 (> the old worker's MAX of 2)** forces a pre-D-1 worker to reject them PRE-CAS
906
906
  * (`unsupported_version`, stays `pending`, retried on a binding-enforcing worker) instead of silently voiding
907
907
  * the binding. A resource_limit suspend keeps stamping v2; a pre-binding (legacy 1.100.0) checkpoint is v1.
@@ -923,7 +923,7 @@ export declare const BINDING_CHECKPOINT_VERSION = 3;
923
923
  */
924
924
  export declare const TOKEN_CHECKPOINT_VERSION = 5;
925
925
  /**
926
- * Org-memory admission state (adversarial round 2, 2026-08-05 — the BINDING_CHECKPOINT_VERSION
926
+ * Org-memory admission state (falsification-style round 2, 2026-08-05 — the BINDING_CHECKPOINT_VERSION
927
927
  * precedent replayed a fourth time): the schema version a suspend stamps when its state carries
928
928
  * org-admission freeze fields (`inheritedGate.admittedOrgScopes` / `ownAdmittedOrgScopes` /
929
929
  * `ownAdmittedOrgWriteScope` — the adjudicated-empty record included). The enforcement — the
@@ -937,7 +937,7 @@ export declare const TOKEN_CHECKPOINT_VERSION = 5;
937
937
  export declare const ORG_ADMISSION_CHECKPOINT_VERSION = 6;
938
938
  /**
939
939
  * F-012 (2026-08-09 — the ORG_ADMISSION_CHECKPOINT_VERSION precedent replayed a fifth time, codex
940
- * adversarial finding): the schema version a suspend stamps when its state carries the F-012 durable
940
+ * falsification-style finding): the schema version a suspend stamps when its state carries the F-012 durable
941
941
  * enforcement fields — `inheritedGate.constraintChain`/`constraintDigest` (the frozen ancestor
942
942
  * projection chain the resume-edit re-adjudication executes) or `delegationProvenance` (the monotonic
943
943
  * provenance aggregate a resume re-seeds). Both enforcements live ENTIRELY in the resuming worker: a
@@ -1022,7 +1022,7 @@ export declare function debitLedger(prior: ResourceLedger | undefined, slice: {
1022
1022
  * per-slice window / walltime). A resumed slice's effective `maxCostUsd` is `min(window, this)`. NB: `0` is a
1023
1023
  * VALID, exhausted ceiling — NOT "unlimited" (only `undefined` is unlimited). Any spend then immediately trips
1024
1024
  * `overBudget`, and the run loop fails an exhausted resume fast (runtask). Never falsy-test this value (a
1025
- * `if (remaining)` would skip a legitimate 0 ceiling and silently bypass the budget). */
1025
+ * `if (remaining)` would skip a legitimate 0 ceiling and silently circumvent the budget). */
1026
1026
  export declare function remainingBudgetMicroUsd(ledger: ResourceLedger | undefined): number | undefined;
1027
1027
  /** design/164 (the TOKEN sibling of {@link remainingBudgetMicroUsd}): tokens the NEXT slice may still
1028
1028
  * spend = `totalTokens − spentTokens`, never negative. `undefined` when no token total is set (the run is
@@ -1270,7 +1270,7 @@ export interface Checkpoint {
1270
1270
  * `durableApproval` (an unattended safety suspend, a non-ask gate, or a legacy pre-1.389 checkpoint — the
1271
1271
  * consumer then falls back to the human-kind scope-only inference for legacy rows and injects NOTHING for
1272
1272
  * unattended parks, so a spawn that never opted in can never gain the opt-in through a resume). Engine-
1273
- * minted from the task's own spec — a worker/tool cannot forge it (`durableApproval` never rides tool args).
1273
+ * minted from the task's own spec — a worker/tool cannot self-declare it (`durableApproval` never rides tool args).
1274
1274
  */
1275
1275
  durableApproval?: {
1276
1276
  scope: string;
@@ -1442,7 +1442,7 @@ export declare class CheckpointError extends Error {
1442
1442
  * rather than evicting: an already-accepted operator instruction is never dropped to make room —
1443
1443
  * the caller delivers or removes a parked steer first. Nothing is written on this path. */
1444
1444
  | "steering.queue_full"
1445
- /** design/171 §6.3 (adversarial review adoption): the append reused an `inputId` already parked on
1445
+ /** design/171 §6.3 (falsification review adoption): the append reused an `inputId` already parked on
1446
1446
  * this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL payload stays the
1447
1447
  * idempotent no-op a retry needs; a differing one is refused, because swallowing it would lose the
1448
1448
  * second operator's instruction — the exact silent loss the queue replaced. Retry with a fresh id.
@@ -1450,7 +1450,7 @@ export declare class CheckpointError extends Error {
1450
1450
  | "steering.duplicate_input_id"
1451
1451
  /** design/144 §3 (governance-gate purity, fail-closed pre-CAS): a `wake` outcome was supplied for a
1452
1452
  * checkpoint that awaits a PENDING GATE DECISION (tool approval / dry-run review / plan review /
1453
- * resource-limit continue). Wake is a pure un-park verb and must never bypass an approval — resume
1453
+ * resource-limit continue). Wake is a pure un-park verb and must never circumvent an approval — resume
1454
1454
  * the checkpoint through its own decide entry instead (the error message names it). */
1455
1455
  | "wake.gate_pending"
1456
1456
  /** design/144 §3 (anti spin-wake): a `wake` outcome carried no `message` and the checkpoint has no
@@ -1498,7 +1498,7 @@ export declare class CheckpointError extends Error {
1498
1498
  field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy";
1499
1499
  /** WHICH pre-CAS refusal arm fired, where one `code` covers several (requested 2026-08-10: a
1500
1500
  * deployment retry policy needs to tell "a newer worker can redeem this row" from "this row is
1501
- * damaged/forged and no worker ever will" — blanket-retriable and gate-shape heuristics were
1501
+ * damaged/caller-declared and no worker ever will" — blanket-retriable and gate-shape heuristics were
1502
1502
  * both refuted downstream, so the throw site carries the fact it always knew). Closed set;
1503
1503
  * additive and optional like `field` — `code` remains the only REQUIRED discriminant.
1504
1504
  * · `unsupported_version` arms: `"version_newer"` (retryable on a newer worker),
@@ -1563,7 +1563,7 @@ export declare class CheckpointError extends Error {
1563
1563
  * rather than evicting: an already-accepted operator instruction is never dropped to make room —
1564
1564
  * the caller delivers or removes a parked steer first. Nothing is written on this path. */
1565
1565
  | "steering.queue_full"
1566
- /** design/171 §6.3 (adversarial review adoption): the append reused an `inputId` already parked on
1566
+ /** design/171 §6.3 (falsification review adoption): the append reused an `inputId` already parked on
1567
1567
  * this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL payload stays the
1568
1568
  * idempotent no-op a retry needs; a differing one is refused, because swallowing it would lose the
1569
1569
  * second operator's instruction — the exact silent loss the queue replaced. Retry with a fresh id.
@@ -1571,7 +1571,7 @@ export declare class CheckpointError extends Error {
1571
1571
  | "steering.duplicate_input_id"
1572
1572
  /** design/144 §3 (governance-gate purity, fail-closed pre-CAS): a `wake` outcome was supplied for a
1573
1573
  * checkpoint that awaits a PENDING GATE DECISION (tool approval / dry-run review / plan review /
1574
- * resource-limit continue). Wake is a pure un-park verb and must never bypass an approval — resume
1574
+ * resource-limit continue). Wake is a pure un-park verb and must never circumvent an approval — resume
1575
1575
  * the checkpoint through its own decide entry instead (the error message names it). */
1576
1576
  | "wake.gate_pending"
1577
1577
  /** design/144 §3 (anti spin-wake): a `wake` outcome carried no `message` and the checkpoint has no
@@ -1619,7 +1619,7 @@ export declare class CheckpointError extends Error {
1619
1619
  field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy";
1620
1620
  /** WHICH pre-CAS refusal arm fired, where one `code` covers several (requested 2026-08-10: a
1621
1621
  * deployment retry policy needs to tell "a newer worker can redeem this row" from "this row is
1622
- * damaged/forged and no worker ever will" — blanket-retriable and gate-shape heuristics were
1622
+ * damaged/caller-declared and no worker ever will" — blanket-retriable and gate-shape heuristics were
1623
1623
  * both refuted downstream, so the throw site carries the fact it always knew). Closed set;
1624
1624
  * additive and optional like `field` — `code` remains the only REQUIRED discriminant.
1625
1625
  * · `unsupported_version` arms: `"version_newer"` (retryable on a newer worker),
@@ -1880,7 +1880,7 @@ export interface CheckpointStore {
1880
1880
  * REJECTED with a typed {@link CheckpointError} `steering.invalid_content` (mirroring runtask.ts's trusted-
1881
1881
  * steer reject) — a dirty steer NEVER enters {@link CheckpointState}, so the persisted state stays clean
1882
1882
  * regardless of `trusted`. (The untrusted RESUME-injection path ALSO sanitizes the text as untrusted data;
1883
- * this persist-time reject is the belt that keeps a forged close tag out of the durable record on BOTH
1883
+ * this persist-time reject is the belt that keeps a caller-declared close tag out of the durable record on BOTH
1884
1884
  * paths.) Text over {@link MAX_PENDING_STEER_CHARS} is rejected with the same code (HRD-APV-4 — the
1885
1885
  * steer is re-delivered into the model context on every resume, so it is capped like every other
1886
1886
  * persisted model-facing field on this row; rejected rather than truncated because cutting an
@@ -72,7 +72,7 @@ export interface FileSnapshotStore {
72
72
  /**
73
73
  * 2c session-sync ([277]): STORE a single content-addressed blob — the symmetric WRITE side of {@link getBlob},
74
74
  * for a two-phase PUSH (upload blobs, THEN import the manifest that references them). VERIFIES content-address
75
- * integrity (`sha256(bytes) === hash`) so a corrupt/mismatched upload can never poison the store (a later
75
+ * integrity (`sha256(bytes) === hash`) so a corrupt/mismatched upload can never contaminate the store (a later
76
76
  * getBlob/restore would otherwise return wrong content); a mismatch → `read_failed`, nothing stored. IMMUTABLE +
77
77
  * content-addressed: a repeat putBlob for the same hash is a no-op (the bytes are identical by definition).
78
78
  * Returns a {@link FileSnapshotResult} (NOT void) so the integrity failure is encodable under the never-throw
@@ -8,7 +8,7 @@ import { type AskClass } from "./ask-class.js";
8
8
  *
9
9
  * - {@link Hooks.preToolUse} runs before a tool executes. It may **rewrite** the args (an `allow` with
10
10
  * `updatedInput`), **restrict** the call (`deny`/`ask`), and/or inject `additionalContext`. It is a
11
- * pre-filter only: a hook's `allow` does NOT bypass the tool-policy gate — the policy still runs on
11
+ * pre-filter only: a hook's `allow` does NOT circumvent the tool-policy gate — the policy still runs on
12
12
  * the (possibly rewritten) args and has the final say (load-bearing invariant; see {@link runToolGate}).
13
13
  * - {@link Hooks.postToolUse} runs after a tool executes. It may replace the result content
14
14
  * (`updatedOutput`) and/or append `additionalContext`.
@@ -55,7 +55,7 @@ export interface Hooks {
55
55
  * JSON on the ordinary path, which clones whole; the residual is reachable only through values a
56
56
  * DEPLOYMENT put there (its own rewrite, or driving `runToolGate` directly), and the face and the
57
57
  * deployment are one trust principal — this seam helps a deployment keep its own declaration, it is not
58
- * a boundary against an adversary.
58
+ * a boundary against an untrusted party.
59
59
  *
60
60
  * If a declared-observational face RETURNS SOMETHING ANYWAY, the return is REFUSED, not obeyed: the
61
61
  * call proceeds as if the hook had no opinion (`undefined`), and the deployment's `onError` face is
@@ -142,7 +142,7 @@ export interface Hooks {
142
142
  * plan-mode write-deny. Closes the observation blind spot where a blocked call fires NEITHER postToolUse
143
143
  * NOR postToolUseFailure (R3 scoping). Does NOT fire for:
144
144
  * - a **durable suspend** (the ask path — CC surfaces that via a `can_use_tool` control_request);
145
- * - a **PreToolUse hook deny** (CC-exact: "PreToolUse hook denies bypass canUseTool and are not
145
+ * - a **PreToolUse hook deny** (CC-exact: "PreToolUse hook denies circumvention canUseTool and are not
146
146
  * covered here").
147
147
  * DOES fire for a **PreToolUse hook CRASH** (RB-442, `source:"hook"`): a throwing hook is intercepted by
148
148
  * the ENGINE, which then makes its OWN fail-closed deny decision. The R5 exclusion above covers the
@@ -417,6 +417,28 @@ export interface HookToolOutput {
417
417
  /**
418
418
  * A PreToolUse hook result: a {@link PermissionResult} (so a hook can `deny`/`ask`, or `allow` with an
419
419
  * `updatedInput` rewrite) plus optional `additionalContext` injected into the eventual tool result.
420
+ *
421
+ * **The provenance members are not hook-settable inputs** (#174). `decisionReason` is the ENGINE's account
422
+ * of which layer produced a verdict, and `persistedRuleShadowed` is the gate's own #144 disclosure that a
423
+ * stored rule matched without clearing the ask. The gate stamps both itself — a `decisionReason` on a hook
424
+ * result is DISCARDED (a hook ask always reads `"hook"` downstream), and a `persistedRuleShadowed` is
425
+ * dropped rather than rendered on an approval card as a rule no lane matched.
426
+ *
427
+ * Which of a hook's OTHER members reach the surviving decision depends on which fold arm ran, so read
428
+ * the arm, not a single list: an ask promoted from a policy `allow` is rebuilt from the hook's `message`,
429
+ * `requiresRealApproval` and `updatedInput`, while a hook ask folded BESIDE a policy ask contributes its
430
+ * message and its mandate to the policy's decision and leaves the policy's own rewrite in place (the
431
+ * hook's rewrite still reaches execution — through the gate's captured rewrite, not through the decision
432
+ * object). Both arms are at the `hookAsk` fold in {@link runToolGate}.
433
+ *
434
+ * `additionalContext` is on neither list and never was consumed as a decision member: it rides its own
435
+ * channel — collected into the gate's `preToolContext` as soon as the hook answers, whichever verdict it
436
+ * returned, and drained onto the tool result by the runner. DELIVERY is narrower than collection: the
437
+ * runner stashes the context only for a call that will EXECUTE, because a blocked or suspended call never
438
+ * reaches the result step that drains it and carries its own model-facing reason instead. The context
439
+ * formerly APPEARED on the promoted ask object as an undeclared property, because that fold spread the
440
+ * whole hook result; the concurrent arm never carried it, so the two arms disagreed on a member neither
441
+ * of them reads. Nothing in the engine reads it off a decision.
420
442
  */
421
443
  export type PreToolUseResult = PermissionResult & {
422
444
  additionalContext?: string;
@@ -98,7 +98,7 @@ function preToolUseCrashReason(subject, err) {
98
98
  function screenPreToolUseResult(r) {
99
99
  if (r === undefined)
100
100
  return undefined;
101
- return refuseOutOfContractDecision(r);
101
+ return refuseOutOfContractDecision(r, { reasonIsNonInput: true });
102
102
  }
103
103
  export function createPreToolUseConstraintPolicy(preToolUse, env, onCrash) {
104
104
  return brandPolicyAskClass({
@@ -125,6 +125,7 @@ export function createPreToolUseConstraintPolicy(preToolUse, env, onCrash) {
125
125
  action: "ask",
126
126
  message: decisionText(r) ?? `approval required for "${req.toolName}" (inherited PreToolUse hook)`,
127
127
  decisionReason: "hook",
128
+ ...(r.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
128
129
  ...(r.updatedInput !== undefined ? { updatedInput: r.updatedInput } : {}),
129
130
  };
130
131
  }
@@ -220,13 +221,31 @@ export async function runToolGate(input) {
220
221
  if ((decision.action === "allow" || decision.action === "ask") && decision.updatedInput !== undefined) {
221
222
  policyRewrite = decision.updatedInput;
222
223
  }
223
- else if (hookAsk?.action === "ask" && hookAsk.updatedInput !== undefined) {
224
+ else if (hookAsk?.updatedInput !== undefined) {
224
225
  policyRewrite = hookAsk.updatedInput;
225
226
  }
226
227
  if (decision.action === "allow" && hookAsk) {
227
- decision = { ...hookAsk, decisionReason: hookAsk.decisionReason ?? "hook" };
228
+ decision = {
229
+ action: "ask",
230
+ ...(hookAsk.message !== undefined ? { message: hookAsk.message } : {}),
231
+ decisionReason: "hook",
232
+ ...(hookAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
233
+ ...(hookAsk.updatedInput !== undefined ? { updatedInput: hookAsk.updatedInput } : {}),
234
+ };
228
235
  denySource = "hook";
229
236
  }
237
+ else if (decision.action === "ask" && hookAsk) {
238
+ const hookMessage = hookAsk.message;
239
+ const foldedMessage = hookMessage !== undefined && hookMessage !== decision.message
240
+ ? `${decision.message !== undefined ? `${decision.message} ` : ""}(a PreToolUse hook also asks: ${hookMessage})`
241
+ : decision.message;
242
+ decision = {
243
+ ...decision,
244
+ decisionReason: "hook",
245
+ ...(foldedMessage !== undefined ? { message: foldedMessage } : {}),
246
+ ...(hookAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
247
+ };
248
+ }
230
249
  if (input.egress && decision.action === "allow") {
231
250
  decision = {
232
251
  action: "ask",
@@ -18,8 +18,8 @@
18
18
  *
19
19
  * The label goes through the same two neutralizers as the external notification's `from="…"` header
20
20
  * (`inlineUntrusted` + `attrEscape` — one-line, capped, break-out-defused, quote-escaped), so a
21
- * hostile id can never close the `[from "…"]` frame, escape an enclosing `<system-reminder>`, or
22
- * smuggle a fake `(unverified)`-free identity. The projection runs BEFORE trust framing at every
21
+ * untrusted id can never close the `[from "…"]` frame, escape an enclosing `<system-reminder>`, or
22
+ * carry across a fake `(unverified)`-free identity. The projection runs BEFORE trust framing at every
23
23
  * call site, so the annotation always sits INSIDE the frame it attributes (a trusted frame's
24
24
  * reminder wrapper, an untrusted frame's fence) and can never be separated from its text.
25
25
  */
@@ -50,7 +50,7 @@ export interface MemoryAdmissionInput {
50
50
  * request-origin scope outside it refuses the prepare; a deployment-origin scope outside it is
51
51
  * dropped and disclosed; a write grant absent from it collapses to read-only. */
52
52
  priorOwnVerdict: OwnOrgAdmissionVerdict | undefined;
53
- /** The monotonic governance-provenance bit (adversarial round 5): true when this leg's TREE is
53
+ /** The monotonic governance-provenance bit (falsification-style round 5): true when this leg's TREE is
54
54
  * org-governed — the deployment surface is configured here, or ANY ancestor/prior leg recorded
55
55
  * the bit (`InheritedGate.orgAdmissionGoverned`, persisted on checkpoints). Counts as governance
56
56
  * evidence even when every admitted set en route is EMPTY: a governed parent's org-less child,
@@ -82,7 +82,7 @@ export interface MemoryAdmissionOutcome {
82
82
  * (an older-shape chain must never read as "unconstrained"); a top-level leg simply has no chain
83
83
  * constraint. The SESSION freeze (the leg's own suspend-time verdict) is a separate input to
84
84
  * {@link admitMemoryScopes} (`priorOwnVerdict`) — it must constrain the FINAL admitted projection
85
- * regardless of origin (deployment-origin scopes bypass the chain freeze, not the session freeze),
85
+ * regardless of origin (deployment-origin scopes circumvent the chain freeze, not the session freeze),
86
86
  * so it cannot ride this chain-side fold.
87
87
  */
88
88
  export declare function foldAdmissionFreeze(input: {