@sema-agent/core 7.49.5 → 7.50.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 (193) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/dist/agents/observer.js +3 -10
  3. package/dist/agents/subagent-steps.d.ts +6 -14
  4. package/dist/agents/subagent-steps.js +3 -17
  5. package/dist/brain/stream-shared.d.ts +1 -1
  6. package/dist/core/allow-entry.d.ts +1 -1
  7. package/dist/core/allow-entry.js +1 -1
  8. package/dist/core/ask-origin.d.ts +15 -11
  9. package/dist/core/ask-origin.js +6 -14
  10. package/dist/core/auto-mode-prompt.js +6 -2
  11. package/dist/core/checkpoint-store.d.ts +6 -4
  12. package/dist/core/checkpoint-store.js +1 -1
  13. package/dist/core/context-edit.d.ts +2 -2
  14. package/dist/core/decision-receipt.d.ts +2 -2
  15. package/dist/core/decision-receipt.js +30 -11
  16. package/dist/core/delegation-frames.d.ts +66 -18
  17. package/dist/core/delegation-frames.js +22 -0
  18. package/dist/core/engine-notice.d.ts +7 -5
  19. package/dist/core/frame-settlement.d.ts +6 -1
  20. package/dist/core/gate-fold.js +19 -13
  21. package/dist/core/gate-lanes.js +12 -11
  22. package/dist/core/gate-outcome.d.ts +2 -2
  23. package/dist/core/governance-codes.js +2 -0
  24. package/dist/core/hooks.d.ts +28 -20
  25. package/dist/core/human-input-projection.js +2 -0
  26. package/dist/core/lsp-diagnostics.d.ts +3 -0
  27. package/dist/core/lsp-diagnostics.js +6 -0
  28. package/dist/core/lsp-session.d.ts +39 -17
  29. package/dist/core/lsp-session.js +118 -58
  30. package/dist/core/lsp.d.ts +83 -3
  31. package/dist/core/lsp.js +146 -16
  32. package/dist/core/mcp.js +2 -1
  33. package/dist/core/media-byte-cap.d.ts +14 -0
  34. package/dist/core/media-byte-cap.js +15 -3
  35. package/dist/core/memory-engine/consolidation-driver.d.ts +23 -1
  36. package/dist/core/memory-engine/consolidation-driver.js +54 -6
  37. package/dist/core/memory-engine/consolidation-write-gate.d.ts +46 -0
  38. package/dist/core/memory-engine/consolidation-write-gate.js +82 -0
  39. package/dist/core/memory-engine/consolidation.d.ts +17 -0
  40. package/dist/core/memory-engine/consolidation.js +1 -0
  41. package/dist/core/memory-engine/distiller.d.ts +8 -2
  42. package/dist/core/memory-engine/distiller.js +8 -6
  43. package/dist/core/memory-engine/engine.d.ts +37 -4
  44. package/dist/core/memory-engine/engine.js +127 -38
  45. package/dist/core/memory-engine/file-backend.d.ts +8 -3
  46. package/dist/core/memory-engine/file-backend.js +44 -16
  47. package/dist/core/memory-engine/index.d.ts +2 -2
  48. package/dist/core/memory-engine/index.js +1 -1
  49. package/dist/core/memory-engine/landing-admission.d.ts +65 -0
  50. package/dist/core/memory-engine/landing-admission.js +68 -0
  51. package/dist/core/memory-engine/types.d.ts +23 -0
  52. package/dist/core/mode-policy.d.ts +3 -2
  53. package/dist/core/mode-policy.js +2 -0
  54. package/dist/core/permission-rule-model.d.ts +19 -13
  55. package/dist/core/permission-rule-model.js +7 -4
  56. package/dist/core/permission-rule-provider.d.ts +11 -0
  57. package/dist/core/permission-rules.d.ts +1 -1
  58. package/dist/core/permission-rules.js +5 -2
  59. package/dist/core/persisted-rule-arms.d.ts +15 -7
  60. package/dist/core/persisted-rule-arms.js +13 -6
  61. package/dist/core/reminder-mint.d.ts +5 -0
  62. package/dist/core/reminder-mint.js +1 -0
  63. package/dist/core/retired-keys.d.ts +8 -0
  64. package/dist/core/retired-keys.js +4 -0
  65. package/dist/core/rule-lane-reads.d.ts +4 -4
  66. package/dist/core/rule-lane-reads.js +10 -1
  67. package/dist/core/runner/construction-doors.d.ts +10 -4
  68. package/dist/core/runner/construction-doors.js +25 -2
  69. package/dist/core/runner/contracts.d.ts +20 -3
  70. package/dist/core/runner/env-capability-census.js +1 -1
  71. package/dist/core/runner/gate-exit.js +2 -1
  72. package/dist/core/runner/memory-consolidation-driver.d.ts +6 -4
  73. package/dist/core/runner/memory-consolidation-driver.js +5 -2
  74. package/dist/core/runner/memory-consolidation-write-judge.d.ts +17 -0
  75. package/dist/core/runner/memory-consolidation-write-judge.js +14 -0
  76. package/dist/core/runner/permission-rule-lanes.d.ts +16 -22
  77. package/dist/core/runner/permission-rule-lanes.js +13 -43
  78. package/dist/core/runner/person-path-rows.d.ts +244 -0
  79. package/dist/core/runner/person-path-rows.js +387 -0
  80. package/dist/core/runner/prepare-acquire-reconcile.js +8 -1
  81. package/dist/core/runner/prepare-artifact.d.ts +5 -3
  82. package/dist/core/runner/prepare-artifact.js +2 -8
  83. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
  84. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  85. package/dist/core/runner/prepare-context-lane.js +2 -2
  86. package/dist/core/runner/prepare-gate-stations.d.ts +3 -3
  87. package/dist/core/runner/prepare-gate-stations.js +1 -1
  88. package/dist/core/runner/prepare-hands-readface.d.ts +19 -10
  89. package/dist/core/runner/prepare-hands-readface.js +57 -43
  90. package/dist/core/runner/prepare-lsp.d.ts +15 -13
  91. package/dist/core/runner/prepare-lsp.js +11 -11
  92. package/dist/core/runner/prepare-policy-chain.d.ts +8 -4
  93. package/dist/core/runner/prepare-policy-chain.js +6 -7
  94. package/dist/core/runner/prepare-run-refs.js +1 -1
  95. package/dist/core/runner/prepare-task.js +24 -16
  96. package/dist/core/runner/prepare-turn-wiring.d.ts +13 -6
  97. package/dist/core/runner/prepare-turn-wiring.js +19 -8
  98. package/dist/core/runner/read-root-grant.d.ts +1 -25
  99. package/dist/core/runner/read-root-grant.js +1 -17
  100. package/dist/core/runner/retired-runner-deps.d.ts +2 -2
  101. package/dist/core/runner/run-brain-sinks.d.ts +1 -5
  102. package/dist/core/runner/run-brain-sinks.js +2 -4
  103. package/dist/core/runner/run-harness-handlers.js +5 -4
  104. package/dist/core/runner/run-identity-wiring.d.ts +4 -4
  105. package/dist/core/runner/run-identity-wiring.js +10 -1
  106. package/dist/core/runner/run-leg.d.ts +1 -1
  107. package/dist/core/runner/run-leg.js +22 -9
  108. package/dist/core/runner/run-settle-and-teardown.d.ts +0 -4
  109. package/dist/core/runner/run-settle-and-teardown.js +7 -4
  110. package/dist/core/runner/run-terminal-adoption.d.ts +1 -1
  111. package/dist/core/runner/run-terminal-adoption.js +1 -1
  112. package/dist/core/runner/run-turn-boundary.js +2 -2
  113. package/dist/core/runner/runtask.js +8 -8
  114. package/dist/core/runner/session-file-state-replay.d.ts +14 -4
  115. package/dist/core/runner/session-file-state-replay.js +8 -0
  116. package/dist/core/runner/stream-steer-verb.js +3 -0
  117. package/dist/core/runner/tool-end-body.d.ts +5 -4
  118. package/dist/core/runner/tool-end-body.js +4 -4
  119. package/dist/core/runner/tool-output-projection.d.ts +6 -4
  120. package/dist/core/runner/tool-output-projection.js +66 -47
  121. package/dist/core/runner-deps.d.ts +6 -3
  122. package/dist/core/shared-memory/types.d.ts +2 -2
  123. package/dist/core/shell-read-silence.d.ts +23 -0
  124. package/dist/core/shell-read-silence.js +18 -0
  125. package/dist/core/synthetic-read-verdict.d.ts +80 -21
  126. package/dist/core/synthetic-read-verdict.js +89 -18
  127. package/dist/core/task-event.d.ts +113 -25
  128. package/dist/core/task-spec.d.ts +44 -0
  129. package/dist/core/task-tool-shape.d.ts +1 -1
  130. package/dist/core/tool-catalog-entries.js +16 -2
  131. package/dist/core/tool-policy.d.ts +25 -15
  132. package/dist/core/tool-policy.js +9 -13
  133. package/dist/core/trace.d.ts +3 -2
  134. package/dist/core/types.d.ts +3 -3
  135. package/dist/core/types.js +1 -1
  136. package/dist/core/untrusted-text.d.ts +2 -2
  137. package/dist/core/user-attachment.d.ts +164 -0
  138. package/dist/core/user-attachment.js +437 -0
  139. package/dist/engine/compaction/compaction.js +10 -3
  140. package/dist/engine/compaction/utils.js +6 -0
  141. package/dist/engine/execution-env/node-execution-env.d.ts +19 -1
  142. package/dist/engine/execution-env/node-execution-env.js +125 -0
  143. package/dist/engine/harness/agent-harness.d.ts +12 -2
  144. package/dist/engine/harness/agent-harness.js +15 -3
  145. package/dist/engine/harness/types.d.ts +21 -0
  146. package/dist/engine/llm/content-block-render.d.ts +15 -0
  147. package/dist/engine/llm/content-block-render.js +10 -0
  148. package/dist/engine/llm/index.d.ts +1 -0
  149. package/dist/engine/llm/index.js +1 -0
  150. package/dist/engine/llm/types.d.ts +80 -4
  151. package/dist/engine/llm/types.js +43 -1
  152. package/dist/engine/session/import-validate.js +2 -0
  153. package/dist/index.d.ts +6 -4
  154. package/dist/index.js +2 -1
  155. package/dist/internal/harness-types.d.ts +1 -1
  156. package/dist/internal/llm.d.ts +2 -2
  157. package/dist/internal/llm.js +1 -1
  158. package/dist/orchestration/workflow.d.ts +4 -5
  159. package/dist/orchestration/workflow.js +2 -2
  160. package/dist/prompts/default.d.ts +1 -1
  161. package/dist/tools/artifact/artifact-tool.js +1 -1
  162. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  163. package/dist/tools/fs/dir-entries.d.ts +4 -10
  164. package/dist/tools/fs/dir-entries.js +4 -11
  165. package/dist/tools/fs/file-read-check.js +3 -3
  166. package/dist/tools/fs/fs-pdf.js +2 -4
  167. package/dist/tools/fs/fs-read.d.ts +47 -4
  168. package/dist/tools/fs/fs-read.js +63 -19
  169. package/dist/tools/fs/fs-search-tools.d.ts +3 -3
  170. package/dist/tools/fs/fs-search-tools.js +7 -2
  171. package/dist/tools/fs/fs-write.d.ts +1 -1
  172. package/dist/tools/fs/fs-write.js +7 -6
  173. package/dist/tools/fs/index.d.ts +7 -1
  174. package/dist/tools/fs/index.js +56 -34
  175. package/dist/tools/fs/read-deny-home.d.ts +19 -1
  176. package/dist/tools/fs/read-deny-home.js +32 -22
  177. package/dist/tools/fs/read-deny.d.ts +14 -0
  178. package/dist/tools/fs/read-deny.js +8 -0
  179. package/dist/tools/fs/repo-map.d.ts +3 -2
  180. package/dist/tools/fs/repo-map.js +11 -3
  181. package/dist/tools/fs/safety.d.ts +41 -2
  182. package/dist/tools/fs/safety.js +12 -3
  183. package/dist/tools/fs/shell-read-check.d.ts +12 -3
  184. package/dist/tools/fs/shell-read-check.js +28 -15
  185. package/dist/tools/fs/shell-read-device.d.ts +1 -1
  186. package/dist/tools/fs/shell-read-judge.d.ts +9 -4
  187. package/dist/tools/fs/shell-read-judge.js +1 -1
  188. package/dist/tools/loop-tick.d.ts +2 -2
  189. package/package.json +3 -1
  190. package/test/export-surface.snapshot.json +39 -1
  191. package/wire-changes/core@7.50.0.json +1699 -0
  192. package/dist/core/runner/persisted-path-rows.d.ts +0 -88
  193. package/dist/core/runner/persisted-path-rows.js +0 -108
@@ -77,7 +77,7 @@ function originLadder(decision, facts) {
77
77
  return "org_unavailable";
78
78
  if (facts.org === "rule")
79
79
  return "org_rule";
80
- if (facts.ruleCallUnreadable === true)
80
+ if (facts.ruleCallUnreadable === true || facts.ruleStoreUnreadable === true)
81
81
  return "rule_store_unavailable";
82
82
  if (decision.decisionReason === "hook")
83
83
  return "hook";
@@ -210,7 +210,7 @@ export const MODE_ARBITRATION = {
210
210
  transcript_write: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
211
211
  org_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
212
212
  governance_unavailable: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
213
- rule_unreadable: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
213
+ rule_unreadable: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
214
214
  ask_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
215
215
  denial_limit_fallback: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
216
216
  classifier_no_ruling: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
@@ -247,7 +247,7 @@ export function releasableAskClassesFor(face) {
247
247
  throw new TypeError(`releasableAskClassesFor: "${String(face)}" is not an ask face (${ASK_FACES.join(", ")})`);
248
248
  return RELEASABLE_BY_FACE[face];
249
249
  }
250
- export const MODE_RELEASE_REASONS = Object.freeze(["rule_unreadable", "target_unresolved"]);
250
+ export const MODE_RELEASE_REASONS = Object.freeze(["target_unresolved"]);
251
251
  const MODE_RELEASE_REASON_SET = new Set(MODE_RELEASE_REASONS);
252
252
  export function isModeReleaseReason(v) {
253
253
  return MODE_RELEASE_REASON_SET.has(v);
@@ -266,16 +266,8 @@ export function screenAskClassMembers(askClass, classes) {
266
266
  }
267
267
  export function decisionAskClassesOf(d) {
268
268
  const out = new Set();
269
- if (d.guard === "dangerous_delete")
270
- out.add("dangerous_delete");
271
- if (d.guard === "unverifiable_delete")
272
- out.add("unverifiable_delete");
273
- if (d.guard === "unverifiable_shell_text")
274
- out.add("unverifiable_shell_text");
275
- if (d.guard === "transcript_write")
276
- out.add("transcript_write");
277
- if (d.guard === "artifact_shared_read")
278
- out.add("artifact_shared_read");
269
+ for (const guard of d.guards ?? [])
270
+ out.add(guard);
279
271
  if (d.ruleUnreadable !== undefined)
280
272
  out.add("rule_unreadable");
281
273
  if (d.matchedAskRule !== undefined)
@@ -324,7 +316,7 @@ export function classifyAsk(d, facts) {
324
316
  classes.add("ancestor");
325
317
  if (facts.org === "rule")
326
318
  classes.add("org_rule");
327
- if (facts.org === "unavailable")
319
+ if (facts.org === "unavailable" || facts.ruleStoreUnreadable === true)
328
320
  classes.add("governance_unavailable");
329
321
  if (facts.ruleCallUnreadable === true)
330
322
  classes.add("rule_unreadable");
@@ -2,6 +2,7 @@ import { AUTO_MODE_DEFAULT_WINDOW_MAX_CHARS, AUTO_MODE_DEFAULT_WINDOW_MAX_ENTRIE
2
2
  import { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./auto-mode-prompt-assets.js";
3
3
  import { cutEnginePrefix, cutEngineSegments, flattenableUserText } from "./untrusted-text.js";
4
4
  import { readDenyPatternRun } from "../tools/fs/read-deny.js";
5
+ import { renderContentBlock, renderUserAttachmentView, userAttachmentOf } from "../internal/llm.js";
5
6
  export { AUTO_MODE_DEFAULTS_SENTINEL } from "./auto-mode-defaults.js";
6
7
  function mergeRuleSection(user, defaultsInner) {
7
8
  if (!user?.length)
@@ -100,7 +101,10 @@ function excerptTailInclusive(text, cap) {
100
101
  const LAUNCHING_AGENT_FRAME = "(this agent was started by another agent: every entry under [launching agent] reached it through that agent, and none is vouched for as the user's own message)\n";
101
102
  function renderEntry(m, cap, user) {
102
103
  if (m.role === "user") {
103
- const raw = typeof m.content === "string" ? m.content : m.content.map((c) => (c.type === "text" ? c.text : `[${c.type}]`)).join("\n");
104
+ const raw = typeof m.content === "string" ? m.content : m.content.map(renderContentBlock).join("\n");
105
+ const attachment = userAttachmentOf(m);
106
+ if (attachment !== undefined)
107
+ return `[attachment]\n${renderUserAttachmentView(m, attachment, cap)}`;
104
108
  if (m.provenance === "engine-note") {
105
109
  return `[engine note]\n${excerpt(raw, cap)}`;
106
110
  }
@@ -126,7 +130,7 @@ function renderEntry(m, cap, user) {
126
130
  return note === undefined ? `${user}\n${excerpt(text, cap)}` : `${user}\n${note}\n${excerpt(text, cap)}`;
127
131
  }
128
132
  if (m.role === "toolResult") {
129
- const text = m.content.map((c) => (c.type === "text" ? c.text : `[${c.type}]`)).join("\n");
133
+ const text = m.content.map(renderContentBlock).join("\n");
130
134
  return `[tool_result ${m.toolName}${m.isError ? " (error)" : ""}]\n${excerpt(text, cap)}`;
131
135
  }
132
136
  const parts = [];
@@ -2103,15 +2103,17 @@ export type CheckpointErrorReason = "version_newer" | "env_factory_missing" | "g
2103
2103
  * entry / a record member naming no absolute root. */
2104
2104
  | "installation_record_absent" | "installation_record_misaligned" | "installation_record_invalid";
2105
2105
  /**
2106
- * The codes a RESUME's pre-claim configuration screen carries as `detail.code` of a `checkpoint.config_refused` — the seven
2107
- * words prepare's mode, output-style, approval-reach, invoked-skill and recorded-mode doors throw, held unchanged so one refusal
2106
+ * The codes a RESUME's pre-claim configuration screen carries as `detail.code` of a `checkpoint.config_refused` — the eight
2107
+ * words prepare's mode, output-style, approval-reach, invoked-skill, attachment and recorded-mode doors throw, held unchanged so one refusal
2108
2108
  * reads the same on a fresh run (`TaskResult.errorCode`) and on a resume. `config.spec_retired_key`: a retired spec key;
2109
2109
  * `config.output_style_invalid`: a malformed output-style seat; `config.permission_mode_invalid`: a mode word outside the closed
2110
2110
  * set (the config's `permissionMode`, or one the row recorded); `config.shell_gate_value_retired`: a retired shell-floor word;
2111
2111
  * `config.approval_reach_invalid`: an `approvalReach` outside its set; `config.user_invoked_skill_unknown` / `skill_deny_unreadable`:
2112
- * `userInvokedSkills` names a skill the config does not carry / one whose `disallowed-tools` entry the grammar reads as nothing.
2112
+ * `userInvokedSkills` names a skill the config does not carry / one whose `disallowed-tools` entry the grammar reads as nothing;
2113
+ * `config.user_attachment_invalid`: the config declares `userAttachments` (a resume sends no new human message) or a malformed
2114
+ * value there.
2113
2115
  */
2114
- export declare const CHECKPOINT_CONFIG_REFUSAL_CODES: readonly ["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired", "config.approval_reach_invalid", "config.user_invoked_skill_unknown", "config.skill_deny_unreadable"];
2116
+ export declare const CHECKPOINT_CONFIG_REFUSAL_CODES: readonly ["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired", "config.approval_reach_invalid", "config.user_invoked_skill_unknown", "config.skill_deny_unreadable", "config.user_attachment_invalid"];
2115
2117
  export type CheckpointConfigRefusalCode = (typeof CHECKPOINT_CONFIG_REFUSAL_CODES)[number];
2116
2118
  export declare const isCheckpointConfigRefusalCode: (word: unknown) => word is CheckpointConfigRefusalCode;
2117
2119
  /** A typed checkpoint-store error so callers branch on `code` (mirrors `SessionError`). */
@@ -304,7 +304,7 @@ export function summarizeCheckpoint(cp) {
304
304
  ...(hasBidi ? { hasBidiControls: true } : {}),
305
305
  };
306
306
  }
307
- export const CHECKPOINT_CONFIG_REFUSAL_CODES = Object.freeze(["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired", "config.approval_reach_invalid", "config.user_invoked_skill_unknown", "config.skill_deny_unreadable"]);
307
+ export const CHECKPOINT_CONFIG_REFUSAL_CODES = Object.freeze(["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired", "config.approval_reach_invalid", "config.user_invoked_skill_unknown", "config.skill_deny_unreadable", "config.user_attachment_invalid"]);
308
308
  export const isCheckpointConfigRefusalCode = (word) => CHECKPOINT_CONFIG_REFUSAL_CODES.some((w) => w === word);
309
309
  export class CheckpointError extends Error {
310
310
  code;
@@ -77,7 +77,7 @@ export declare function contextEditFrontier(window: number): number;
77
77
  * runner wires it on BOTH arms by default — the frontier pass and the MC-R rejection arm share
78
78
  * one persist closure — so the shipped default clears to a pointer, never to a dead end.
79
79
  * ④ the TRIGGER — CC's client machine fires ONLY on the server's `context_hint` 422/424 refusal
80
- * (cli250.js:58375; `tengu_time_based_microcompact` is a historical event name, its `trigger`
80
+ * (cli250.js:58375 `context_hint`; the `tengu_time_based_microcompact` event name is historical, its `trigger`
81
81
  * is the constant `"context_hint"`). Under BYOM there is no such server, so the ask half is
82
82
  * unportable and the refusal half is aligned on wire truth (the MC-R arm classifies the
83
83
  * provider's own context-overflow refusal) — registered divergence D-3. sema additionally
@@ -92,7 +92,7 @@ export declare const MICRO_COMPACT_MACHINES: readonly ["off", "cc"];
92
92
  export type MicroCompactMachine = (typeof MICRO_COMPACT_MACHINES)[number];
93
93
  /**
94
94
  * How many most-recent CANDIDATE tool results keep their content when the stale-result pass runs:
95
- * 5 (CC 2.1.223 `uAp`; 2.1.250 `var k = 5` at the call site cli250.js:58375, and 2.1.245 carries
95
+ * 5 (CC 2.1.223 `uAp`; 2.1.250 `var k=5` beside the call site, cli250.js:58373, and 2.1.245 carries
96
96
  * the same value — zero drift across 245→250; same value again on CC 88's `timeBasedMCConfig`, i.e.
97
97
  * stable across all three corpus generations, and it is a hard-coded constant upstream, not a
98
98
  * setting). The #384 adjudication adopted 5 CONDITIONAL on the whole machine coming with it (20k
@@ -40,7 +40,7 @@ export declare function nestedRecordsOf(receipt: object): object[];
40
40
  * carrier, the member named by `path`: an accessor (never invoked), and a member the prototype or a non-enumerable property
41
41
  * supplies, unless the nearest descriptor holding it spells absence (an `undefined` data property). Descriptors are looked up
42
42
  * as OWN entries of the descriptor map, so a polluted `Object.prototype` names no member. Members outside the contract are
43
- * not looked at. */
43
+ * not looked at — but a retired one (`retired`) is refused by name when the carrier carries it by that same law. */
44
44
  /** The receipt's own answers carry a `kind` discriminant as an own property: narrowing never asks the prototype chain (`in`) for a
45
45
  * word, so a polluted `Object.prototype` cannot answer for the receipt either. */
46
46
  export type Received = {
@@ -50,5 +50,5 @@ export type Received = {
50
50
  kind: "refused";
51
51
  refused: PermissionResult;
52
52
  };
53
- export declare function receiveContract(carrier: object, members: readonly string[], path: string): Received;
53
+ export declare function receiveContract(carrier: object, members: readonly string[], path: string, retired?: Readonly<Record<string, string>>): Received;
54
54
  export {};
@@ -12,7 +12,7 @@ export const isEngineRefusal = (d) => typeof d === "object" && d !== null && ENG
12
12
  const DECISION_REASONS = Object.freeze(["rule", "mode", "hook", "safety", "classifier", "persisted_rule", "sandbox", "org_rule", "org_unavailable", "read_only", "skill", "deployment_rule", "request_rule"]);
13
13
  const DECISION_ACTIONS = Object.keys({ allow: 0, deny: 0, ask: 0 });
14
14
  const GUARDS = Object.keys({ dangerous_delete: 0, unverifiable_delete: 0, unverifiable_shell_text: 0, transcript_write: 0, artifact_shared_read: 0 });
15
- const RULE_UNREADABLE_WORDS = Object.keys({ content_deny: 0, content_ask: 0, persisted_call: 0 });
15
+ const RULE_UNREADABLE_WORDS = Object.keys({ content_deny: 0, content_ask: 0, persisted_call: 0, persisted_rows: 0 });
16
16
  const AUTHORED_BY_WORDS = Object.keys({ deployment: 0 });
17
17
  const memberOf = (words) => {
18
18
  const set = new Set(words);
@@ -26,7 +26,7 @@ const CLOSED_MEMBERS = [
26
26
  { member: "classes", words: ASK_CLASSES, has: isAskClass, list: true },
27
27
  { member: "foldedAskClasses", words: ASK_CLASSES, has: isAskClass, list: true },
28
28
  { member: "mandate", words: PERSISTED_RULE_MANDATES, has: isPersistedRuleMandate },
29
- { member: "guard", words: GUARDS, has: memberOf(GUARDS) },
29
+ { member: "guards", words: GUARDS, has: memberOf(GUARDS), list: true },
30
30
  { member: "ruleUnreadable", words: RULE_UNREADABLE_WORDS, has: memberOf(RULE_UNREADABLE_WORDS) },
31
31
  { member: "authoredBy", words: AUTHORED_BY_WORDS, has: memberOf(AUTHORED_BY_WORDS) },
32
32
  { member: "classifierNoRuling", words: AUTO_MODE_FALLBACK_CAUSES, has: isAutoModeFallbackCause },
@@ -197,7 +197,7 @@ function ordinaryChain(carrier) {
197
197
  }
198
198
  return { kind: "chain", chain };
199
199
  }
200
- export function receiveContract(carrier, members, path) {
200
+ export function receiveContract(carrier, members, path, retired) {
201
201
  const what = path === "" ? "a permission decision" : `a permission decision's "${path.slice(0, -1)}" record`;
202
202
  const ordinary = ordinaryChain(carrier);
203
203
  if (ordinary.kind === "not") {
@@ -210,6 +210,21 @@ export function receiveContract(carrier, members, path) {
210
210
  catch (e) {
211
211
  return { kind: "refused", refused: refusedOutOfContract(`${what} threw while its property descriptors were looked at (${thrownText(e)}) — not an ordinary object; denied fail-closed`) };
212
212
  }
213
+ const nearest = (m) => {
214
+ try {
215
+ if (Object.hasOwn(own, m))
216
+ return { kind: "found", d: own[m] };
217
+ for (let i = 1; i < ordinary.chain.length; i++) {
218
+ const d = Object.getOwnPropertyDescriptor(ordinary.chain[i], m);
219
+ if (d !== undefined)
220
+ return { kind: "found", d };
221
+ }
222
+ return { kind: "found", d: undefined };
223
+ }
224
+ catch (e) {
225
+ return { kind: "refused", refused: refusedOutOfContract(`${what} threw while "${path}${m}" was looked for on its prototype chain (${thrownText(e)}) — not an ordinary object; denied fail-closed`) };
226
+ }
227
+ };
213
228
  const held = Object.create(null);
214
229
  for (const m of members) {
215
230
  const d = Object.hasOwn(own, m) ? own[m] : undefined;
@@ -220,14 +235,10 @@ export function receiveContract(carrier, members, path) {
220
235
  held[m] = d.value;
221
236
  continue;
222
237
  }
223
- let reached = d;
224
- try {
225
- for (let i = 1; reached === undefined && i < ordinary.chain.length; i++)
226
- reached = Object.getOwnPropertyDescriptor(ordinary.chain[i], m);
227
- }
228
- catch (e) {
229
- return { kind: "refused", refused: refusedOutOfContract(`${what} threw while "${path}${m}" was looked for on its prototype chain (${thrownText(e)}) — not an ordinary object; denied fail-closed`) };
230
- }
238
+ const found = nearest(m);
239
+ if (found.kind === "refused")
240
+ return found;
241
+ const reached = found.d;
231
242
  if (reached !== undefined && !spellsAbsence(reached)) {
232
243
  return {
233
244
  kind: "refused",
@@ -236,5 +247,13 @@ export function receiveContract(carrier, members, path) {
236
247
  };
237
248
  }
238
249
  }
250
+ for (const [m, replacement] of Object.entries(retired ?? {})) {
251
+ const found = nearest(m);
252
+ if (found.kind === "refused")
253
+ return found;
254
+ if (found.d !== undefined && !spellsAbsence(found.d)) {
255
+ return { kind: "refused", refused: refusedOutOfContract(`a permission decision carries the retired "${m}" member — ${replacement}; denied fail-closed rather than executing a decision with a member this layer does not read`) };
256
+ }
257
+ }
239
258
  return { kind: "held", held };
240
259
  }
@@ -7,11 +7,18 @@
7
7
  */
8
8
  import type { TaskStatus } from "./task-result.js";
9
9
  import type { RunnerDeps } from "./runner-deps.js";
10
+ import type { TaskEvent } from "./task-event.js";
10
11
  /**
11
12
  * design/129-B — one lifecycle event of a BACKGROUND delegation child, delivered to the
12
13
  * process-level {@link RunnerDeps.onBackgroundChildEvent} observer. Keys: `taskId` is the registry handle
13
14
  * (`a*` domain — the TaskOutput/TaskStop key); `progressTaskId`/`sessionId` are the child RUN's id (uuid
14
15
  * domain — the same key `task_progress` frames carry).
16
+ *
17
+ * `kind: "spawn"` announces a fleet ROW's birth, which is a different fact from a child RUN's start
18
+ * (`TaskEvent` `task_started`): an `a*` row is born when its lane registers it, before the child run
19
+ * prepares (and a child whose prepare throws has a row and no run start); a `wa*` row is born with the
20
+ * workflow's own agent record, including rows that never run a child at all (a replayed or re-parked
21
+ * agent). Each fact has one mint — the lane for the row, the child's spawn station for the run.
15
22
  */
16
23
  export interface BackgroundChildEvent {
17
24
  kind: "spawn" | "tick" | "terminal";
@@ -223,15 +230,19 @@ export interface BackgroundChildEvent {
223
230
  * now sees every delegation lane through one seat.
224
231
  *
225
232
  * EMISSION CHOKEPOINT (the reason this seat covers every lane at once): frames are minted by the
226
- * RUNNER at the child LEG itself — spawn right after the leg's `wiring_manifest` (prepared, nothing
227
- * run yet), terminal when the leg's `TaskResult` assembles — not by the individual spawn lanes. Every
228
- * delegation lane (sync/steer/background/fork/revive, workflow-spawned agents) runs its child through
229
- * this chokepoint, so none of them needs its own emission and none can drift.
233
+ * RUNNER at the child LEG itself — not by the individual spawn lanes. Every delegation lane
234
+ * (sync/steer/background/fork/revive, workflow-spawned agents) runs its child through this
235
+ * chokepoint, so none of them needs its own emission and none can drift.
230
236
  *
231
- * PER-LEG semantics, deliberately: a durable park + resume, or a retained child's revive, is a NEW
232
- * leg — each emits its own spawn/terminal pair, and `identity.legKind` (`"resume"`) says which cycle
233
- * a frame belongs to. Consumers correlate legs of one delegation by `identity.taskId` /
234
- * `identity.parentToolCallId` (stable across cycles).
237
+ * SPAWN IS A PROJECTION, TERMINAL IS PER LEG. The `spawn` phase is not a fact of its own: it is the
238
+ * projection of the child's `task_started` stream frame (`TaskEvent`), delivered from that frame's one
239
+ * mint at the leg's spawn station, at the same instant and under the same gate — so it fires exactly
240
+ * when a CYCLE starts (a first spawn, or a revival of a completed child) and never for a durable
241
+ * RESUME leg, which continues a cycle that already started. The `terminal` phase fires for EVERY
242
+ * delegated leg, resume legs included (a park ends a leg with `"suspended"`; the resume leg that later
243
+ * redeems it ends with its own terminal). Consumers correlate legs of one delegation by
244
+ * `identity.taskId` / `identity.parentToolCallId` (stable across legs), and `identity.legKind`
245
+ * (`"resume"`) names a resume leg's terminal.
235
246
  *
236
247
  * HONEST ABSENCES (recorded, not gaps to fix silently):
237
248
  * · a child whose PREPARE throws emits neither frame (there is no leg identity to report);
@@ -242,14 +253,15 @@ export interface BackgroundChildEvent {
242
253
  * ({@link import("./checkpoint-store.js").CheckpointState.isDelegatedChild}) emits neither frame:
243
254
  * the row carries no evidence the parked leg was a delegated child, and fabricating the axis
244
255
  * would stamp delegation frames onto host-resumed root tasks. Rows minted WITH the axis resume
245
- * with their spawn/terminal pair even deps-only; a resume that re-supplies trusted internals
246
- * (every in-engine lane does) was never affected.
247
- * PAIRING (closed the one hole): a spawn frame is always closed by a terminal frame —
248
- * the ordinary path emits it where the `TaskResult` assembles, and a POST-SPAWN throw that the
249
- * stream layer converts into a synthesized failed result (e.g. `resume.tool_unavailable` re-thrown
250
- * past the run tail) emits the failed terminal from that backstop, gated on a carrier that is set
251
- * only after the spawn emission and cleared by the ordinary terminal (so the two sites can never
252
- * both fire for one leg). A leg whose PROCESS dies mid-flight is the only unpaired spawn.
256
+ * with their terminal even deps-only; a resume that re-supplies trusted internals (every
257
+ * in-engine lane does) was never affected.
258
+ * TERMINAL OWED (closed the one hole): every delegated leg that reached its wiring is closed by
259
+ * exactly one terminal frame — the ordinary path emits it where the `TaskResult` assembles, and a
260
+ * POST-WIRING throw that the stream layer converts into a synthesized failed result (e.g.
261
+ * `resume.tool_unavailable` re-thrown past the run tail) emits the failed terminal from that
262
+ * backstop, gated on a carrier set at the spawn station and cleared by the ordinary terminal (so the
263
+ * two sites can never both fire for one leg). A leg whose PROCESS dies mid-flight is the only one
264
+ * left without its terminal.
253
265
  *
254
266
  * OBSERVATION ONLY (matrix §5.2 Q3: frame-rate lifecycle facts ride an observer seat, never
255
267
  * onNotice): no return capability, and delivery can never alter the child run —
@@ -265,23 +277,59 @@ export interface BackgroundChildEvent {
265
277
  * `frame.hasOwnProperty(...)` (throws) or implicit string coercion (`` `${frame}` `` throws).
266
278
  */
267
279
  export type DelegationLifecycleEvent = {
280
+ /** A cycle started — the projection of the leg's `task_started` frame (one per first spawn or
281
+ * revival; never for a resume leg, so `identity.legKind` is `"child"` on every spawn frame). */
268
282
  phase: "spawn";
269
283
  /** The child LEG's identity envelope (#281 件A — the same frozen object that leg's own hook
270
284
  * invocations carry). `isDelegatedChild` is `true` by construction on every frame here. */
271
285
  identity: import("./hooks.js").HookInvocationIdentity;
272
286
  } | {
273
287
  phase: "terminal";
274
- /** Same envelope as the leg's spawn frame (one mint per leg). */
288
+ /** The leg's identity envelope (one mint per leg — the same object as the cycle's spawn frame on
289
+ * the leg that started it). */
275
290
  identity: import("./hooks.js").HookInvocationIdentity;
276
291
  /** The leg's settled status word — the projection of the `TaskResult.terminal` the spawning lane
277
292
  * receives (the sync lane's previously-void `settled` payload, made public). `"suspended"` means a
278
- * durable park: expect a later `"resume"`-leg spawn/terminal pair if it is redeemed. */
293
+ * durable park: expect a later `"resume"`-leg terminal if it is redeemed (a resume leg starts no
294
+ * cycle, so no spawn frame precedes it). */
279
295
  status: TaskStatus;
280
296
  /** Turns the leg completed. */
281
297
  turns: number;
282
298
  /** The leg's terminal code (the failed cause's `code`), when one was stamped. */
283
299
  errorCode?: string;
284
300
  };
301
+ /**
302
+ * The ONE eligibility reading for a leg's delegated-child ROW frames — its `task_started`, its
303
+ * `task_progress` ticks and its terminal tick all read this and nothing else: a delegated leg, by the
304
+ * identity envelope's effective delegation fact. A row a consumer can open is a row it can close, so
305
+ * no frame of the family may ask a narrower question (whether a spawning tool call is known is
306
+ * attribution, carried on the frames when it exists — never eligibility).
307
+ */
308
+ export declare function emitsChildRowFrames(identity: import("./hooks.js").HookInvocationIdentity): boolean;
309
+ /**
310
+ * The ONE constructor of a delegated child's `task_started` frame ({@link TaskEvent}); the leg's spawn
311
+ * station calls it once per started cycle. Every member is copied from the seat its `task_progress`
312
+ * twin (or the fleet row) reads — the run ids, the prepared model, the delegation facts the spawner
313
+ * threaded, and the display label as the identity envelope bounds it, present under the tick's own
314
+ * law (a label that bounds to nothing is no label) — so the frame introduces no second reading of any
315
+ * of them. `stamp` is the event identity the leg stamps on every frame.
316
+ */
317
+ export declare function taskStartedFrame(run: {
318
+ taskId: string;
319
+ sessionId: string;
320
+ model: string;
321
+ identity: import("./hooks.js").HookInvocationIdentity;
322
+ }, spawner: {
323
+ delegationTaskType?: import("./task-event.js").DelegationTaskType;
324
+ cycleSeq?: number;
325
+ parentTaskId?: string;
326
+ parentSessionId?: string;
327
+ rootSessionId?: string;
328
+ explicitAgentName?: string;
329
+ spawnAgentType?: string;
330
+ } | undefined, stamp: import("./task-event.js").TaskEventIdentity): Extract<TaskEvent, {
331
+ type: "task_started";
332
+ }>;
285
333
  /** Test seam (mirrors `__resetMalformedNoticeSeatAnnouncement`): never called by production code. */
286
334
  export declare function __resetMalformedDelegationSeatAnnouncement(): void;
287
335
  /**
@@ -1,4 +1,26 @@
1
1
  import { observeThenableRejection } from "./safe-notify.js";
2
+ export function emitsChildRowFrames(identity) {
3
+ return identity.isDelegatedChild;
4
+ }
5
+ export function taskStartedFrame(run, spawner, stamp) {
6
+ const { identity } = run;
7
+ return {
8
+ type: "task_started",
9
+ taskId: run.taskId,
10
+ sessionId: run.sessionId,
11
+ ...(spawner?.delegationTaskType !== undefined ? { taskType: spawner.delegationTaskType } : {}),
12
+ ...(spawner?.cycleSeq !== undefined ? { seq: spawner.cycleSeq } : {}),
13
+ ...(spawner?.parentTaskId !== undefined ? { parentTaskId: spawner.parentTaskId } : {}),
14
+ ...(spawner?.parentSessionId !== undefined ? { parentSessionId: spawner.parentSessionId } : {}),
15
+ ...(spawner?.rootSessionId !== undefined ? { rootSessionId: spawner.rootSessionId } : {}),
16
+ ...(identity.agentName ? { name: identity.agentName } : {}),
17
+ ...(spawner?.explicitAgentName !== undefined ? { spawnName: spawner.explicitAgentName } : {}),
18
+ ...(spawner?.spawnAgentType !== undefined ? { agentType: spawner.spawnAgentType } : {}),
19
+ model: run.model,
20
+ startedAt: Date.now(),
21
+ ...stamp,
22
+ };
23
+ }
2
24
  let malformedDelegationSeatAnnounced = false;
3
25
  export function __resetMalformedDelegationSeatAnnouncement() {
4
26
  malformedDelegationSeatAnnounced = false;
@@ -394,11 +394,13 @@ export interface EngineNotice {
394
394
  * rowCount, sessionId, runId }` (`rows` = the first eight non-built-in rows). Audience `user`.
395
395
  * - `"permission.rule_store_unavailable"` — the person's persisted permission rules could not be read (a read
396
396
  * failure, a corrupt or unreadable store, a read that did not answer in time), so that read's rows were SKIPPED:
397
- * none of their allow, ask or deny rows took part in the decisions it served — a deny row among them protected
398
- * nothing meanwhile — and no card is raised for it. `detail.at` names the read that found it first: `"prepare"`
399
- * (the path rows the run's file and shell checks take at run start — the next run reads them again) or `"call"`
400
- * (one call's own rule check — every later call reads again, so the rows apply as soon as a read succeeds). Once
401
- * per run, whichever read fails first; the trace keeps every read's line (`permission.rule_store_unreadable`).
397
+ * none of their allow, ask or deny rows took part in the decisions it served. At `"call"` the call asks a person
398
+ * meanwhile (a deny row among the rows nobody could read may forbid it; the class is `governance_unavailable`,
399
+ * which `bypassPermissions` keeps and a headless run refuses); at `"prepare"` the run's path tables lack the rows
400
+ * for the whole run. `detail.at` names the read that found it first: `"prepare"` (the path rows the run's file and
401
+ * shell checks take at run start — the next run reads them again) or `"call"` (one call's own rule check — every
402
+ * later call reads again, so the rows apply as soon as a read succeeds). Once per run, whichever read fails
403
+ * first; the trace keeps every read's line (`permission.rule_store_unreadable`).
402
404
  * `detail: { at, sessionId, runId }`. Audience `user`.
403
405
  * - `"permission.rule_row_dropped"` — the person's store served project-scoped rows whose scope root is not a
404
406
  * complete absolute path (only a foreign writer or a hand edit makes one; every engine write face refuses it): they
@@ -86,7 +86,8 @@ export declare function noteHostRewrite(seam: FrameSettlement, toolCallId: strin
86
86
  * carrier (the host's decision), withheld otherwise (owed again). */
87
87
  export declare function recordFrameReceipt(seam: FrameSettlement, carrier: string, committedContent: unknown, entryId: string): void;
88
88
  /** The carrier a committed frame was riding, as the harness reports it at the transcript append — a tool
89
- * result with the content it was committed with (after every result transformation). */
89
+ * result with the content it was committed with (after every result transformation); a user-attachment frame by
90
+ * its delivery record's key (`inputId`, `ordinal`). */
90
91
  export type CommittedFrameCarrier = {
91
92
  kind: "engine_note";
92
93
  payload: unknown;
@@ -94,4 +95,8 @@ export type CommittedFrameCarrier = {
94
95
  kind: "tool_result";
95
96
  toolCallId: string;
96
97
  content: unknown;
98
+ } | {
99
+ kind: "attachment";
100
+ inputId: string;
101
+ ordinal: number;
97
102
  };
@@ -7,6 +7,7 @@ import { writeProtectionMandated, writeProtectionTightenDecision } from "./write
7
7
  import { createGateDeadline, awaitWriteProtectionJudgment, gateWaitFailureOf, withGateTimeout } from "./gate-bounded-await.js";
8
8
  import { thrownText } from "./thrown-value.js";
9
9
  import { refuseOutOfContractDecision } from "./tool-policy.js";
10
+ import { applyUnansweredSilence, silencedBy } from "./shell-read-silence.js";
10
11
  import { asksUnstated, permissionSeatAt, unstatedAskMessage } from "./mode-policy.js";
11
12
  export async function runGateFold(pass) {
12
13
  const { input, toolName, toolCallId, callSignal, ledger, notifier, adjudicate } = pass;
@@ -196,25 +197,26 @@ function applyShellReadVerdict(pass, verdict, toolName, shellGated) {
196
197
  pass.shellUnplacedOptions = verdict.unplacedOptions;
197
198
  if (verdict.finding === "unestablishable" || verdict.finding === "declined" || verdict.finding === "unanswered" || verdict.unestablished === true)
198
199
  pass.shellReadUnestablished = true;
200
+ if (verdict.disposition === "refuse") {
201
+ pass.decision = { action: "deny", message: verdict.sentence ?? `tool "${toolName}" reads what the session's read boundary refuses` };
202
+ pass.deniedBy = "read_boundary";
203
+ if (verdict.details !== undefined)
204
+ pass.denyDetails = verdict.details;
205
+ return;
206
+ }
207
+ if (verdict.finding === "unanswered") {
208
+ applyUnansweredSilence(pass, verdict, { message: shellReadAskMessage(toolName, verdict), account, tightenedBy: shellGated ? "shell_gate" : "safety" });
209
+ return;
210
+ }
199
211
  switch (verdict.disposition) {
200
212
  case "vouch":
201
213
  return;
202
- case "refuse":
203
- pass.decision = { action: "deny", message: verdict.sentence ?? `tool "${toolName}" reads what the session's read boundary refuses` };
204
- pass.deniedBy = "read_boundary";
205
- if (verdict.details !== undefined)
206
- pass.denyDetails = verdict.details;
207
- return;
208
214
  case "mandated": {
209
215
  if (verdict.details !== undefined)
210
216
  pass.shellReadDetails = verdict.details;
211
- const unreadable = verdict.finding === "unanswered";
212
217
  if (pass.decision.action !== "allow") {
213
- if (pass.decision.action === "ask") {
214
- pass.decision = !unreadable
215
- ? { ...pass.decision, probeMandated: true }
216
- : { ...pass.decision, requiresRealApproval: true, ...(pass.decision.guard === undefined ? { guard: "unverifiable_shell_text" } : { foldedAskClasses: [...new Set([...(pass.decision.foldedAskClasses ?? []), "unverifiable_shell_text"])] }) };
217
- }
218
+ if (pass.decision.action === "ask")
219
+ pass.decision = { ...pass.decision, probeMandated: true, ...silencedBy(verdict, pass.decision) };
218
220
  return;
219
221
  }
220
222
  const candidate = normalizeReadRootCandidate(verdict.readRootCandidate);
@@ -222,7 +224,8 @@ function applyShellReadVerdict(pass, verdict, toolName, shellGated) {
222
224
  action: "ask",
223
225
  message: shellReadAskMessage(toolName, verdict),
224
226
  decisionReason: "safety",
225
- ...(unreadable ? { requiresRealApproval: true, guard: "unverifiable_shell_text" } : { probeMandated: true }),
227
+ probeMandated: true,
228
+ ...silencedBy(verdict, {}),
226
229
  ...(account !== undefined ? { probeReason: account } : {}),
227
230
  ...(candidate !== undefined ? { probeReadRootCandidate: candidate } : {}),
228
231
  };
@@ -235,12 +238,15 @@ function applyShellReadVerdict(pass, verdict, toolName, shellGated) {
235
238
  verdict.admit?.();
236
239
  if (verdict.details !== undefined)
237
240
  pass.shellReadDetails = verdict.details;
241
+ if (pass.decision.action === "ask")
242
+ pass.decision = { ...pass.decision, ...silencedBy(verdict, pass.decision) };
238
243
  if (pass.decision.action === "allow") {
239
244
  const candidate = normalizeReadRootCandidate(verdict.readRootCandidate);
240
245
  pass.decision = {
241
246
  action: "ask",
242
247
  message: shellReadAskMessage(toolName, verdict),
243
248
  decisionReason: "safety",
249
+ ...silencedBy(verdict, {}),
244
250
  ...(account !== undefined ? { probeReason: account } : {}),
245
251
  ...(candidate !== undefined ? { probeReadRootCandidate: candidate } : {}),
246
252
  };
@@ -26,6 +26,8 @@ const QUESTION_TOOL_NAMES = new Set([ASK_USER_QUESTION_TOOL_NAME]);
26
26
  function parkWith(suspendAsk, parkArgs, carry) {
27
27
  return suspendAsk(...parkArgs, carry);
28
28
  }
29
+ const ruleCallUnreadableOf = (lane) => lane?.unreadable === true && lane.reason !== undefined;
30
+ const ruleStoreUnreadableOf = (lane) => lane?.unreadable === true && lane.reason === undefined;
29
31
  export async function runGateLanes(pass) {
30
32
  const { input, toolName, toolCallId, callSignal, ledger, notifier, screening, adjudicate, resolveAsk, suspendAsk } = pass;
31
33
  const boundedAwaitDeadlineMs = createGateDeadline(input, notifier);
@@ -42,11 +44,12 @@ export async function runGateLanes(pass) {
42
44
  targetUnresolved: pass.targetUnresolved === true,
43
45
  });
44
46
  const readFaceNow = input.readFace?.();
45
- const askOriginFacts = (org, ruleCallUnreadable) => ({
47
+ const askOriginFacts = (org, lane) => ({
46
48
  contentQuestion: heldBy("lanes.contentQuestionClass", QUESTION_TOOL_NAMES, pass.req),
47
49
  markedUnresolvable: markedUnresolvable(),
48
50
  org,
49
- ...(ruleCallUnreadable ? { ruleCallUnreadable } : {}),
51
+ ...(ruleCallUnreadableOf(lane) ? { ruleCallUnreadable: true } : {}),
52
+ ...(ruleStoreUnreadableOf(lane) ? { ruleStoreUnreadable: true } : {}),
50
53
  tightened: pass.tightenedBy,
51
54
  mandateMarks: mandateMarksNow(),
52
55
  ...(input.toolMarkSources !== undefined ? { toolMarkSources: input.toolMarkSources } : {}),
@@ -118,13 +121,12 @@ export async function runGateLanes(pass) {
118
121
  };
119
122
  const orgAnswer = await applyOrgLayer(pass.decision, pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput);
120
123
  pass.decision = orgAnswer.decision;
121
- const ruleCallUnreadableOf = (lane) => lane?.unreadable === true && lane.reason !== undefined;
122
124
  const readPersistedRules = (req) => readPersistedRuleLane(input.persistedRules, req, input.abortSignal);
123
125
  const reread = pass.policyRewrite !== undefined ? withArgsReading(pass.req, pass.policyRewrite, pass.callSignal ?? input.abortSignal) : undefined;
124
126
  const laneReq = reread !== undefined ? await reread : { ...pass.req, ...(pass.policyRewrite !== undefined ? { args: pass.policyRewrite } : {}) };
125
127
  const laneAnswer = input.persistedRules !== undefined && wireOf("lanes.persistedLaneSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME ? await readPersistedRules(laneReq) : undefined;
126
128
  {
127
- const tightened = applyPersistedTightening(pass.decision, laneAnswer);
129
+ const tightened = applyPersistedTightening(pass.decision, laneAnswer, laneReq);
128
130
  pass.decision = tightened.decision;
129
131
  if (tightened.tightened === "deny")
130
132
  pass.deniedBy = "persisted_rule";
@@ -146,7 +148,7 @@ export async function runGateLanes(pass) {
146
148
  pass.req.targetReading = laneReq.targetReading;
147
149
  }
148
150
  const ruleMayClear = () => ruleClearanceOf(pass.decision.action === "ask" ? pass.decision : undefined, mandateMarksNow()).ruleMayClear;
149
- const classMayClear = () => pass.decision.action === "ask" && classifyAsk(pass.decision, askOriginFacts(orgAnswer.org, ruleCallUnreadableOf(laneAnswer))).row.ruleMayClear;
151
+ const classMayClear = () => pass.decision.action === "ask" && classifyAsk(pass.decision, askOriginFacts(orgAnswer.org, laneAnswer)).row.ruleMayClear;
150
152
  let personalEvidence = { dotsAbsent: input.persistedRules === undefined ? "not_wired" : "not_adjudicated" };
151
153
  if (laneAnswer?.unreadable === true)
152
154
  personalEvidence = { dotsAbsent: "unavailable" };
@@ -234,7 +236,7 @@ export async function runGateLanes(pass) {
234
236
  const { allowRuleShadowed: _claimed, ...standing } = pass.decision;
235
237
  pass.decision = shadows.length > 0 ? { ...standing, allowRuleShadowed: shadows } : standing;
236
238
  }
237
- const originFacts = askOriginFacts(orgAnswer.org, ruleCallUnreadableOf(laneAnswer));
239
+ const originFacts = askOriginFacts(orgAnswer.org, laneAnswer);
238
240
  let noRulingMinted = false;
239
241
  const fallbackAskOf = (standingAsk, fallback, latestBlockedAction) => {
240
242
  const { classifierNoRuling: _why, ...standing } = standingAsk;
@@ -582,13 +584,12 @@ export async function runGateLanes(pass) {
582
584
  editDenied = recheck;
583
585
  break;
584
586
  }
585
- let editRuleCallUnreadable = false;
587
+ let editLane;
586
588
  const editReread = withArgsReading(pass.req, editArgs, callSignal ?? input.abortSignal);
587
589
  const editLaneReq = editReread !== undefined ? await editReread : { toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
588
590
  if (input.persistedRules !== undefined && wireOf("lanes.persistedEditSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME) {
589
- const editLane = await readPersistedRules(editLaneReq);
590
- editRuleCallUnreadable = ruleCallUnreadableOf(editLane);
591
- const editTightened = applyPersistedTightening(recheck, editLane);
591
+ editLane = await readPersistedRules(editLaneReq);
592
+ const editTightened = applyPersistedTightening(recheck, editLane, editLaneReq);
592
593
  recheck = editTightened.decision;
593
594
  if (editTightened.tightened === "deny")
594
595
  pass.deniedBy = "persisted_rule";
@@ -619,7 +620,7 @@ export async function runGateLanes(pass) {
619
620
  }
620
621
  const editAskSnapshot = { ...recheck, ruleEvidence: mintRuleEvidence({ dotsAbsent: "not_adjudicated" }) };
621
622
  markedThisRound = undefined;
622
- const editFacts = askOriginFacts(editOrg.org, editRuleCallUnreadable);
623
+ const editFacts = askOriginFacts(editOrg.org, editLane);
623
624
  const editAskDecision = stampAskOrigin(editAskSnapshot, editFacts, classifyStanding(editAskSnapshot, editFacts));
624
625
  if (input.permissionMode !== undefined && pass.askClassification !== undefined) {
625
626
  const editVerdict = arbitrateByMode(input.permissionMode(), pass.askClassification, { ...(editAskDecision.origin !== undefined ? { origin: editAskDecision.origin } : {}), ...(editAskDecision.mandate !== undefined ? { mandate: editAskDecision.mandate } : {}) });
@@ -360,8 +360,8 @@ export type GateDisposition = {
360
360
  * to anyone: the mode, the disclosed class word (`askClass`, the first member of the folded set in `ASK_CLASSES`
361
361
  * order — one of {@link releasableAskClassesFor} on the call's face), the whole set, and the origin / mandate words the card would
362
362
  * have shown. `reason` is a member of {@link import("./ask-origin.js").MODE_RELEASE_REASONS} (`isModeReleaseReason`):
363
- * `"rule_unreadable"` marks a release over a rule the call could not be read against, `"target_unresolved"` a release (the
364
- * shell face) over a write target whose reading could not say where it leads; absent, the release names none. The record carries no face, so the
363
+ * `"target_unresolved"` marks a release (the shell face) over a write target whose reading could not say where it leads;
364
+ * absent, the release names none. The record carries no face, so the
365
365
  * screen asks that SOME face releases the class.
366
366
  */
367
367
  export interface ModeRelease {
@@ -47,6 +47,8 @@ export const NON_GOVERNANCE_MEMORY_CODES = new Set([
47
47
  "memory.consolidation_lease_unavailable",
48
48
  "memory.consolidation_backend_failed",
49
49
  "memory.consolidation_gate_failed",
50
+ "memory.consolidation_write_refused",
51
+ "memory.landing_unstable",
50
52
  "memory.active_set_collapse",
51
53
  "memory.challenge_ledger_oversize",
52
54
  "memory.control_plane_rebuilt",