@sema-agent/core 7.44.0 → 7.46.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 (159) hide show
  1. package/CHANGELOG.md +418 -1
  2. package/dist/agents/extra-tool-names.d.ts +65 -0
  3. package/dist/agents/extra-tool-names.js +124 -0
  4. package/dist/agents/subagent.d.ts +17 -5
  5. package/dist/agents/subagent.js +65 -38
  6. package/dist/agents/tool-filter.d.ts +82 -5
  7. package/dist/agents/tool-filter.js +134 -4
  8. package/dist/core/agent-config-dir.d.ts +20 -0
  9. package/dist/core/agent-config-dir.js +49 -0
  10. package/dist/core/agent-definition.d.ts +2 -2
  11. package/dist/core/allow-entry.d.ts +30 -4
  12. package/dist/core/allow-entry.js +32 -5
  13. package/dist/core/ask-origin.d.ts +23 -4
  14. package/dist/core/ask-origin.js +11 -5
  15. package/dist/core/checkpoint-store.d.ts +41 -4
  16. package/dist/core/decision-receipt.js +1 -1
  17. package/dist/core/effective-path-target.d.ts +143 -10
  18. package/dist/core/effective-path-target.js +113 -9
  19. package/dist/core/engine-notice.d.ts +25 -3
  20. package/dist/core/engine-notice.js +12 -6
  21. package/dist/core/gate-fold.js +3 -0
  22. package/dist/core/gate-lanes.js +11 -3
  23. package/dist/core/gate-outcome.d.ts +5 -0
  24. package/dist/core/gate-outcome.js +2 -0
  25. package/dist/core/governance-codes.d.ts +3 -1
  26. package/dist/core/governance-codes.js +3 -0
  27. package/dist/core/hooks.d.ts +5 -0
  28. package/dist/core/memory-engine/content-origin.d.ts +0 -34
  29. package/dist/core/memory-engine/content-origin.js +9 -4
  30. package/dist/core/memory-engine/layout.d.ts +5 -1
  31. package/dist/core/memory-engine/layout.js +1 -0
  32. package/dist/core/path-rule-binding.d.ts +65 -0
  33. package/dist/core/path-rule-binding.js +99 -0
  34. package/dist/core/permission-rule-model.d.ts +47 -18
  35. package/dist/core/permission-rule-model.js +38 -42
  36. package/dist/core/permission-rule-provider.d.ts +7 -0
  37. package/dist/core/permission-rule-provider.js +10 -3
  38. package/dist/core/permission-rule-store.d.ts +8 -0
  39. package/dist/core/permission-rule-store.js +6 -2
  40. package/dist/core/permission-rule-sync.js +2 -2
  41. package/dist/core/permission-rules.d.ts +22 -36
  42. package/dist/core/permission-rules.js +50 -40
  43. package/dist/core/real-approval.d.ts +9 -6
  44. package/dist/core/real-approval.js +5 -5
  45. package/dist/core/runner/child-tree.d.ts +110 -0
  46. package/dist/core/runner/child-tree.js +107 -0
  47. package/dist/core/runner/construction-doors.d.ts +5 -0
  48. package/dist/core/runner/construction-doors.js +6 -0
  49. package/dist/core/runner/contracts.d.ts +54 -2
  50. package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
  51. package/dist/core/runner/permission-rule-lanes.js +18 -12
  52. package/dist/core/runner/persisted-path-rows.d.ts +31 -7
  53. package/dist/core/runner/persisted-path-rows.js +31 -12
  54. package/dist/core/runner/prepare-ask-lane.d.ts +6 -0
  55. package/dist/core/runner/prepare-ask-lane.js +4 -2
  56. package/dist/core/runner/prepare-config-doors.js +3 -1
  57. package/dist/core/runner/prepare-execution-env.d.ts +5 -2
  58. package/dist/core/runner/prepare-execution-env.js +11 -3
  59. package/dist/core/runner/prepare-gate-stations.d.ts +6 -0
  60. package/dist/core/runner/prepare-gate-stations.js +3 -1
  61. package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
  62. package/dist/core/runner/prepare-hands-readface.js +6 -6
  63. package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
  64. package/dist/core/runner/prepare-inherited-gate.js +8 -2
  65. package/dist/core/runner/prepare-listings.d.ts +4 -4
  66. package/dist/core/runner/prepare-listings.js +2 -22
  67. package/dist/core/runner/prepare-park-ask.d.ts +5 -0
  68. package/dist/core/runner/prepare-park-ask.js +6 -4
  69. package/dist/core/runner/prepare-policy-chain.d.ts +16 -2
  70. package/dist/core/runner/prepare-policy-chain.js +14 -5
  71. package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
  72. package/dist/core/runner/prepare-suspend-saga.js +7 -1
  73. package/dist/core/runner/prepare-task.js +41 -18
  74. package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
  75. package/dist/core/runner/prepare-turn-wiring.js +2 -1
  76. package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
  77. package/dist/core/runner/prepare-workspace-restore.js +1 -0
  78. package/dist/core/runner/resume-apply.js +17 -3
  79. package/dist/core/runner/resume-checkpoint-screen.js +13 -0
  80. package/dist/core/runner/resume-policy-outcome.js +2 -1
  81. package/dist/core/runner/run-leg.js +5 -1
  82. package/dist/core/runner/run-notification-lane.js +2 -2
  83. package/dist/core/runner/runtask.d.ts +2 -0
  84. package/dist/core/runner/runtask.js +4 -2
  85. package/dist/core/runner/stream-steer-verb.js +2 -2
  86. package/dist/core/runner/sub-agent-deny-lists.d.ts +32 -0
  87. package/dist/core/runner/sub-agent-deny-lists.js +121 -0
  88. package/dist/core/runner/synthetic-tools.d.ts +1 -1
  89. package/dist/core/runner/synthetic-tools.js +3 -2
  90. package/dist/core/runner/write-protection-station.d.ts +3 -2
  91. package/dist/core/runner/write-protection-station.js +1 -0
  92. package/dist/core/runner-deps.d.ts +14 -0
  93. package/dist/core/sensitive-path-policy.d.ts +8 -0
  94. package/dist/core/sensitive-path-policy.js +18 -2
  95. package/dist/core/session-policy-store.d.ts +6 -2
  96. package/dist/core/session-policy-store.js +2 -1
  97. package/dist/core/shell-wrapper-projection.d.ts +11 -0
  98. package/dist/core/shell-wrapper-projection.js +56 -0
  99. package/dist/core/shell-wrapper-table.d.ts +144 -34
  100. package/dist/core/shell-wrapper-table.js +40 -20
  101. package/dist/core/skill-grant.d.ts +10 -2
  102. package/dist/core/skill-grant.js +20 -4
  103. package/dist/core/synthetic-read-verdict.d.ts +3 -0
  104. package/dist/core/synthetic-read-verdict.js +3 -1
  105. package/dist/core/task-notification.d.ts +12 -1
  106. package/dist/core/task-notification.js +13 -1
  107. package/dist/core/task-spec.d.ts +5 -3
  108. package/dist/core/tool-catalog-entries.d.ts +21 -0
  109. package/dist/core/tool-catalog-entries.js +9 -0
  110. package/dist/core/tool-policy.d.ts +45 -12
  111. package/dist/core/tool-policy.js +18 -20
  112. package/dist/core/tool-spec.d.ts +23 -4
  113. package/dist/core/types.d.ts +1 -1
  114. package/dist/core/types.js +1 -1
  115. package/dist/core/write-protect.d.ts +4 -0
  116. package/dist/core/write-protect.js +30 -9
  117. package/dist/index.d.ts +10 -6
  118. package/dist/index.js +9 -5
  119. package/dist/orchestration/run-spec.js +8 -7
  120. package/dist/orchestration/workflow.js +3 -2
  121. package/dist/shell/lex.d.ts +7 -0
  122. package/dist/shell/lex.js +17 -0
  123. package/dist/tools/fs/bash-program-position.d.ts +8 -87
  124. package/dist/tools/fs/bash-program-position.js +33 -69
  125. package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
  126. package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
  127. package/dist/tools/fs/index.d.ts +3 -2
  128. package/dist/tools/fs/program-read-model.d.ts +694 -0
  129. package/dist/tools/fs/program-read-model.js +1252 -0
  130. package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
  131. package/dist/tools/fs/program-read-model.rows.js +613 -0
  132. package/dist/tools/fs/read-deny-home.d.ts +19 -1
  133. package/dist/tools/fs/read-deny-home.js +62 -24
  134. package/dist/tools/fs/read-deny.d.ts +54 -14
  135. package/dist/tools/fs/read-deny.js +104 -50
  136. package/dist/tools/fs/removal-targets.d.ts +83 -0
  137. package/dist/tools/fs/removal-targets.js +236 -0
  138. package/dist/tools/fs/safety.d.ts +3 -0
  139. package/dist/tools/fs/safety.js +10 -3
  140. package/dist/tools/fs/shell-read-argv.d.ts +85 -0
  141. package/dist/tools/fs/shell-read-argv.js +386 -0
  142. package/dist/tools/fs/shell-read-bindings.d.ts +273 -0
  143. package/dist/tools/fs/shell-read-bindings.js +746 -0
  144. package/dist/tools/fs/shell-read-check.js +23 -15
  145. package/dist/tools/fs/shell-read-establish.js +79 -5
  146. package/dist/tools/fs/shell-read-judge.d.ts +11 -1
  147. package/dist/tools/fs/shell-read-judge.js +9 -1
  148. package/dist/tools/fs/shell-read-plan.d.ts +31 -7
  149. package/dist/tools/fs/shell-read-plan.js +299 -393
  150. package/dist/tools/fs/shell-read-word.d.ts +29 -26
  151. package/dist/tools/fs/shell-read-word.js +30 -38
  152. package/dist/tools/fs/shell-read-write-targets.d.ts +15 -37
  153. package/dist/tools/fs/shell-read-write-targets.js +83 -136
  154. package/dist/tools/fs/shell-reentry-text.d.ts +38 -6
  155. package/dist/tools/fs/shell-reentry-text.js +90 -53
  156. package/package.json +1 -1
  157. package/test/export-surface.snapshot.json +49 -3
  158. package/wire-changes/core@7.45.0.json +1084 -0
  159. package/wire-changes/core@7.46.0.json +1522 -0
@@ -1,4 +1,4 @@
1
- import type { GateOutcome } from "./gate-outcome.js";
1
+ import type { GateOutcome, HostDecider } from "./gate-outcome.js";
2
2
  import { type ExecutionOutcomeRecordWord } from "./checkpoint-execution-record.js";
3
3
  import type { CheckpointPause } from "./pause-registry.js";
4
4
  import type { SystemInjectionPriority } from "./task-notification.js";
@@ -625,7 +625,7 @@ export type ResumeOutcome = {
625
625
  * authorization; a replay states its own facts.
626
626
  */
627
627
  hostDecision: {
628
- decidedBy: "person" | "sla_timeout" | "policy";
628
+ decidedBy: HostDecider;
629
629
  approver?: string;
630
630
  };
631
631
  /**
@@ -1095,6 +1095,22 @@ export interface CheckpointState {
1095
1095
  * plain on-disk state that survives a process-local suspend (worktrees included — the tree stays on disk).
1096
1096
  */
1097
1097
  handsCwd?: string;
1098
+ /**
1099
+ * The hands band's ROOT at suspend (the canonical root its tools resolve a root-relative spelling against — the
1100
+ * `root` every adjudicated request of the leg carries). Read by ONE seat: the pre-claim recheck of an edited parked
1101
+ * call, which has no environment yet and judges the edit at the row's coordinates (`handsRoot` + {@link handsCwd}).
1102
+ * After the claim the resumed leg uses its own live root. Absent on a row written before this field (that recheck then
1103
+ * reads the root as unknown, as before) and when the task has no hands. An ignorable optional field: an older reader
1104
+ * that does not know it resumes the row as it always did, so it raises no row format.
1105
+ */
1106
+ handsRoot?: string;
1107
+ /**
1108
+ * The home the leg's caller policies installed their `~/` words at (the execution environment's declared home, absolute),
1109
+ * beside {@link handsRoot}: the pre-claim recheck installs the edit-base policy at the row's root and this home, the way
1110
+ * the leg did. Absent on a row written before this field, on a hands-less task and on an environment that declares no
1111
+ * absolute home — a `~/` row then reads unanchored there (never the engine process's home). Ignorable, like `handsRoot`.
1112
+ */
1113
+ handsHome?: string;
1098
1114
  /**
1099
1115
  * the ACTIVE EnterWorktree session at suspend (worktree.ts keeps it in a shared
1100
1116
  * serializable ref, not a closure-only var, precisely so it lands here). Without it a resume LOST the
@@ -1166,6 +1182,21 @@ export interface CheckpointState {
1166
1182
  * the v2 contract are refused, even when every admitted set en route was EMPTY. Absent on
1167
1183
  * trees that never had a governance surface. */
1168
1184
  orgAdmissionGoverned?: true;
1185
+ /** The chain's top leg's root and home at suspend (data half): the anchors of the person's global / session path rows
1186
+ * and of every `~/` row on this chain (a project row anchors at its own scope root). Absent on older checkpoints and on
1187
+ * a top leg ⇒ the resumed leg is its own top. Rebased with the workspace on a restore elsewhere; a value that is not
1188
+ * an absolute path anchors nothing (the rows it would anchor read `unreadable`). */
1189
+ pathRuleAnchors?: {
1190
+ readonly root: string;
1191
+ readonly home?: string;
1192
+ };
1193
+ /** Where each parent constraint the suspended leg ran under was installed, position for position (data half; `null`: an
1194
+ * entry with no path rules). A resume that re-supplies the chain uninstalled installs each entry there. Rebased with the
1195
+ * workspace; a value that is not an absolute path installs nothing (the entry is installed at the chain top). */
1196
+ pathRuleInstallations?: ReadonlyArray<{
1197
+ readonly root: string;
1198
+ readonly home?: string;
1199
+ } | null>;
1169
1200
  requiresParentConstraint: boolean;
1170
1201
  /** How many opaque parent constraints the suspended leg ran under (present iff `requiresParentConstraint`).
1171
1202
  * The resume pre-CAS ladder rejects a re-supply whose `parentConstraints.length` differs
@@ -2036,14 +2067,20 @@ export declare function summarizeCheckpoint(cp: Checkpoint): CheckpointSummary;
2036
2067
  * steer seat, which no worker version reads and the host decides itself, or a retired mode-seed member of
2037
2068
  * `inheritedGate`, which an engine at 7.38.x or earlier still reads once).
2038
2069
  * · `invalid_outcome` pre-CAS row-integrity arms: `"real_approval_damaged"`, `"real_approval_forged"`,
2039
- * `"read_face_damaged"`, `"read_face_forged"`, `"write_face_damaged"`, `"skill_grants_damaged"`, `"constraint_chain_missing"` (all terminal for the row's
2070
+ * `"read_face_damaged"`, `"read_face_forged"`, `"write_face_damaged"`, `"skill_grants_damaged"`, `"constraint_chain_missing"`, `"hands_coordinates_damaged"` (all terminal for the row's
2040
2071
  * current bytes — no worker version redeems a damaged row).
2041
2072
  */
2042
2073
  export type CheckpointErrorReason = "version_newer" | "env_factory_missing" | "governed_unwired" | "origin_missing" | "ledger_event_unknown" | "seat_retired" | "real_approval_damaged" | "real_approval_forged" | "read_face_damaged" | "read_face_forged"
2043
2074
  /** `state.writeFace` is present but not a well-formed section. No `_forged` twin: the section is
2044
2075
  * legal-and-absent at every version, so there is no version to judge a fabrication against — and a
2045
2076
  * fabricated "open" still needs the LIVE resolution to say open too. */
2046
- | "write_face_damaged" | "skill_grants_damaged" | "constraint_chain_missing";
2077
+ | "write_face_damaged" | "skill_grants_damaged" | "constraint_chain_missing"
2078
+ /** `state.handsRoot` / `state.handsHome` / `state.handsCwd` is present but not an absolute path (the row's hands' coordinates). */
2079
+ | "hands_coordinates_damaged"
2080
+ /** On `resume.parent_constraint_mismatch`: a re-supplied chain entry whose path rules depend on the leg that installed it has
2081
+ * no installation record (a chain written before the record existed) / a record that does not cover the chain entry for
2082
+ * entry / a record member naming no absolute root. */
2083
+ | "installation_record_absent" | "installation_record_misaligned" | "installation_record_invalid";
2047
2084
  /**
2048
2085
  * The codes a RESUME's pre-claim configuration screen carries as `detail.code` of a `checkpoint.config_refused` — the seven
2049
2086
  * words prepare's mode, output-style, approval-reach, invoked-skill and recorded-mode doors throw, held unchanged so one refusal
@@ -11,7 +11,7 @@ export const refusedOutOfContract = (message) => {
11
11
  export const isEngineRefusal = (d) => typeof d === "object" && d !== null && ENGINE_REFUSALS.has(d);
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
- const GUARDS = Object.keys({ unverifiable_delete: 0, transcript_write: 0, artifact_shared_read: 0 });
14
+ const GUARDS = Object.keys({ dangerous_delete: 0, unverifiable_delete: 0, transcript_write: 0, artifact_shared_read: 0 });
15
15
  const RULE_UNREADABLE_WORDS = Object.keys({ content_deny: 0, content_ask: 0, persisted_call: 0 });
16
16
  const AUTHORED_BY_WORDS = Object.keys({ deployment: 0 });
17
17
  const memberOf = (words) => {
@@ -20,22 +20,34 @@
20
20
  *
21
21
  * The fix is not a fourth arm in the lanes: it is that "which base does this tool resolve against, and what
22
22
  * does it target when the slot is empty" is a FACT OF THE TOOL, so the tool declares it once and both
23
- * sides read the declaration. {@link pathTargetBaseOf} is the shared SELECTION (which base), and each side
24
- * then applies its own identity to the selected base — lexical-normal here (the rule family's one path
25
- * identity, zero IO), canonical (symlink-resolving, IO) inside the tool.
23
+ * sides read the declaration. {@link pathTargetBaseOf} is the shared SELECTION (which base).
24
+ *
25
+ * ## The views of one target
26
+ *
27
+ * A target is seen three ways: its LEXICAL view (the spelling joined to the selected base, `.` / `..` folded —
28
+ * zero IO), its CANONICAL view (the same spelling resolved by the execution environment: symlinks, aliases of the
29
+ * tree, the disk's own case) and the HOPS of its leaf's symlink chain. {@link readPathViews} is the one producer of
30
+ * those views — the tool's own resolution and its deny judgment read it, and so does {@link readCallTarget}, which
31
+ * a Runner stamp point calls once per adjudicated call and hands to every rule lane as
32
+ * `ToolCallRequest.targetReading`. Every rule lane reads a call's target through ONE reader, {@link callTargetViewsOf}:
33
+ * the reading's views when the call carries one, else the call's own lexical view (a bare request). The judgment over
34
+ * those views is the deny tables' (`pathRowsReach`): any view a deny or ask row reaches is reached; an allow row admits a
35
+ * call only when it admits every view of the declared reading.
26
36
  *
27
37
  * ## What it does NOT decide
28
38
  *
29
39
  * Nothing here judges anything: it answers "which absolute path is this call about". Containment, the
30
40
  * sensitive-path deny face and the rule precedence walk are unchanged and still run where they ran.
31
- * `undefined` means "this call has no path this reading can locate" — the documented fail-toward-asking
32
- * posture of both lanes (no refusal on the tightening side, no grant on the allow side).
41
+ * `undefined` means "this call names no path" (no declaration, a non-string slot, an absent slot that targets
42
+ * nothing); a path the call does name whose base this reading lacks is `unreadable` — never a guess, never silence.
33
43
  *
34
44
  * RepoMap shares the search tools' root-relative `path` semantics but declares NO path target at all (no
35
45
  * rule family reaches it, and the rule grammar is derived from the catalog's path targets), so it has no
36
46
  * declaration to read; the day it declares one, it reads the same selector as everything else.
37
47
  */
38
- import { type PathFamily, type PathIdentity, type Win32NamespaceRefusal } from "../tools/fs/safety.js";
48
+ import { canonicalizeTarget, type PathFamily, type PathIdentity, type Win32NamespaceRefusal } from "../tools/fs/safety.js";
49
+ import type { TargetViews } from "../tools/fs/read-deny.js";
50
+ import type { ExecutionEnv } from "../internal/harness-types.js";
39
51
  import type { ToolCallPathTarget } from "./tool-face.js";
40
52
  import { type ToolCallLike } from "./tool-registry.js";
41
53
  /** The bases of ONE CALL a target resolves against: the task root, and the live tracked working directory
@@ -71,8 +83,8 @@ export declare function splitAbsoluteGlobPattern(pattern: string, family: PathFa
71
83
  } | undefined;
72
84
  /**
73
85
  * The reading of a call's path slot under ONE declaration: `path` = the absolute target's identity; `unreadable`
74
- * = the slot spells a target but the base it needs is not a usable base of this tree (fed to TIGHTENING rows
75
- * only, as the ask a person clears — never to the allow arm); `refused` = a namespace refusal of the
86
+ * = the slot spells a target (or targets its base) but the base it needs is not supplied or not a usable base of
87
+ * this tree (fed to TIGHTENING rows only, as the ask a person clears — never to the allow arm); `refused` = a namespace refusal of the
76
88
  * spelling itself (a rule speaks about no such spelling: no verdict, no ask — the fs throat delivers the
77
89
  * refusal with the same word); `undefined` = no target in this reading.
78
90
  */
@@ -97,8 +109,9 @@ export type PathTargetReading = {
97
109
  * ③ otherwise a `patternParam`'s ABSOLUTE pattern supplies its own base directory;
98
110
  * ④ otherwise the base itself (the tool searches all of it).
99
111
  *
100
- * A base the deployment did not supply yields `undefined`: a relative spelling with nothing to resolve
101
- * against names no file, and claiming it names `/<spelling>` was a fabrication no fence should judge.
112
+ * A base the caller did not supply yields `unreadable`: a relative spelling with nothing to resolve against
113
+ * names a file this reading cannot locate — claiming it names `/<spelling>` would be a fabrication, and
114
+ * claiming it names nothing would let every rule about that file stay silent.
102
115
  */
103
116
  export declare function effectivePathTargetOf(call: ToolCallLike, pt: ToolCallPathTarget | undefined, bases: ToolPathBases): PathTargetReading;
104
117
  /**
@@ -113,3 +126,123 @@ export declare function effectivePathSpellingOf(call: ToolCallLike, pt: ToolCall
113
126
  } | {
114
127
  readonly refused: Win32NamespaceRefusal;
115
128
  } | undefined;
129
+ /**
130
+ * ONE reading of a call's path slot as VIEWS. `path`: the LEXICAL view (the spelling joined to the selected base and
131
+ * folded — a native spelling of the tree's family), the CANONICAL view when the environment answered (absent ⇒ not asked
132
+ * or no answer: an absent view takes no part in a judgment and is never a refusal) and the HOPS of the leaf's symlink
133
+ * chain when the environment declares one. `unreadable` / `refused` read as in {@link PathTargetReading}.
134
+ */
135
+ export type CallTargetView = {
136
+ readonly kind: "path";
137
+ readonly lexical: string;
138
+ readonly canonical?: string;
139
+ readonly hops?: readonly string[];
140
+ } | {
141
+ readonly kind: "unreadable";
142
+ readonly reason: string;
143
+ } | {
144
+ readonly kind: "refused";
145
+ readonly reason: Win32NamespaceRefusal;
146
+ };
147
+ /**
148
+ * A call's TARGET READING: the views of each reading of its path slot — `protective`, the tightening direction's
149
+ * ({@link protectivePathTargetOf}), and `declared`, the allow direction's ({@link declaredPathTargetOf}; absent on a
150
+ * request that carries no face). It carries no root and no cwd: the call's own `root` / `cwd` are the only copies.
151
+ */
152
+ export interface CallTargetReading {
153
+ readonly protective?: CallTargetView;
154
+ readonly declared?: CallTargetView;
155
+ /** Where each location on the way to a rule row's head that this leg's lanes hold LEADS in the leg's environment (a
156
+ * link, a firmlink, an alias of the tree) — the rows' twins are judged there too. A fact of the judging leg, taken
157
+ * once per location per leg; absent when no row head leads elsewhere. */
158
+ readonly leads?: ReadonlyMap<string, string>;
159
+ }
160
+ /** The environment's answer the canonical view rests on — the throat's own, so the tool keeps reading the refusal it gave. */
161
+ export type CanonicalAnswer = Awaited<ReturnType<typeof canonicalizeTarget>>;
162
+ /** The three views of one spelling as {@link readPathViews} produces them. */
163
+ export interface PathViews {
164
+ readonly lexical: PathTargetReading;
165
+ /** `undefined` ⇒ the environment was not asked (none supplied, or a spelling this reading cannot locate). */
166
+ readonly canonical?: CanonicalAnswer;
167
+ readonly hops: readonly string[];
168
+ }
169
+ /**
170
+ * The ONE producer of a target's views: `spelled` against `base` (the base the tool's declaration selected), under the
171
+ * tree `bases` names. Lexical through the same resolution {@link effectivePathTargetOf} reads; canonical through the
172
+ * throat's own canonicalizer; the hops (when `opts.hops` and the canonical view answered) through the leaf's chain. The
173
+ * tool's resolution and deny judgment read this, and so does {@link readCallTarget}; an environment that THROWS
174
+ * propagates here (each caller holds its own posture: the tool refuses, the reading leaves the view absent).
175
+ */
176
+ export declare function readPathViews(env: ExecutionEnv | undefined, spelled: string, base: string | undefined, bases: ToolPathBases, signal?: AbortSignal, opts?: {
177
+ readonly hops?: boolean;
178
+ }): Promise<PathViews>;
179
+ /** The identity of an ABSOLUTE spelling under the tree `bases` names — the target side's one prelude (family, namespace
180
+ * screen, identity) for a path a judge already holds (a canonical key, a hop, a lexical view). `undefined` when the
181
+ * spelling is not an absolute path of that tree. */
182
+ export declare function absoluteTargetIdentityOf(spelled: string, bases: ToolPathBases): PathIdentity | undefined;
183
+ /**
184
+ * A call's target reading under ONE call's facts: its `root` / `cwd` (the bases the stamp point reads off the leg) and
185
+ * the execution environment `env` (absent ⇒ the lexical views alone — the pre-claim recheck, which has no environment).
186
+ * A canonical view the environment cannot answer (or throws on) is absent, never a refusal (an absent view does not vote).
187
+ * The two readings share their views when they select the same spelling against the same base (one canonicalization).
188
+ */
189
+ export declare function readCallTarget(env: ExecutionEnv | undefined, call: ToolCallLike & {
190
+ readonly root?: string | undefined;
191
+ readonly cwd?: string | undefined;
192
+ }, signal?: AbortSignal, leads?: ReadonlyMap<string, string | undefined>): Promise<CallTargetReading>;
193
+ /**
194
+ * `req` with its arguments replaced by `args` and — when `req` carries a reading of its own args — a reading of the NEW
195
+ * args taken the same way (same environment, same root / cwd, same face). Every seat that rewrites a request's arguments
196
+ * and hands it on (a composition's rewrite cascade, a re-judgment of the final or an edited form) goes through here, so the
197
+ * next layer never judges a request its reading does not describe. `signal` is the CURRENT call's: a reading never carries
198
+ * one call's cancellation into another's. `undefined` ⇒ nothing to re-read: use `{ ...req, args }`.
199
+ */
200
+ export declare function withArgsReading<R extends {
201
+ readonly args: unknown;
202
+ readonly targetReading?: CallTargetReading | undefined;
203
+ }>(req: R, args: unknown, signal: AbortSignal | undefined): Promise<R> | undefined;
204
+ /** The request's target reading, when it describes the request's own `args` (the object it was taken for); otherwise
205
+ * nothing — a rule lane then judges the lexical view it always judged. Every rule lane reads the reading through here. */
206
+ export declare function callTargetReadingOf(req: {
207
+ readonly args: unknown;
208
+ readonly targetReading?: CallTargetReading | undefined;
209
+ }): CallTargetReading | undefined;
210
+ /**
211
+ * THE one reader of a call's target for every rule lane, in one direction's reading (`tighten` = the protective reading,
212
+ * `allow` = the declared one): the views of the call's target reading when it carries one of its own args (lexical,
213
+ * canonical, the leaf chain's hops), else the call's own lexical view — resolved against the call's `root` / `cwd` when it
214
+ * carries either, else against `bare` (a host's direct call of a policy: the bases it declared). `undefined` = the call
215
+ * names no path in this reading (or a spelling the throat refuses — no rule speaks about it); an `unreadable` answer =
216
+ * the call names a path this reading cannot locate.
217
+ */
218
+ export declare function callTargetViewsOf(req: ToolCallLike & {
219
+ readonly root?: string | undefined;
220
+ readonly cwd?: string | undefined;
221
+ readonly targetReading?: CallTargetReading | undefined;
222
+ }, direction: "tighten" | "allow", bare?: ToolPathBases): TargetViews | undefined;
223
+ /**
224
+ * A leg's STAMP seat: `req` with a target reading of its own args, or `undefined` when there is nothing to stamp — the
225
+ * leg's lanes hold no path rule, the request already carries a reading of its args, or its args are not an object. The
226
+ * reading is taken under the request's own `root` / `cwd` when it carries them, else the leg's (the same values every
227
+ * Runner seat stamps), and memoized per args object within one call (its `toolCallId`), so every lane reading one call
228
+ * costs one reading. `undefined`
229
+ * rather than a resolved request, so a seat with nothing to do adds no suspension point.
230
+ */
231
+ export type CallTargetStamp = <R extends ToolCallLike & {
232
+ readonly toolCallId?: string;
233
+ readonly root?: string | undefined;
234
+ readonly cwd?: string | undefined;
235
+ readonly targetReading?: CallTargetReading | undefined;
236
+ }>(req: R, signal?: AbortSignal) => Promise<R & {
237
+ readonly targetReading: CallTargetReading;
238
+ }> | undefined;
239
+ /** The stamp seat of one leg: its execution environment, its root and live working directory, whether its lanes hold a
240
+ * path rule (read per call — an activated skill can add one), and where the heads of the rows its lanes hold lead in its
241
+ * environment (resolved once per location per leg). */
242
+ export declare function legCallTargetStamp(leg: {
243
+ readonly env: ExecutionEnv;
244
+ readonly root: string;
245
+ readonly cwd: () => string | undefined;
246
+ readonly judgesPaths: () => boolean;
247
+ readonly leads: () => Promise<ReadonlyMap<string, string | undefined>>;
248
+ }): CallTargetStamp;
@@ -1,5 +1,7 @@
1
- import { isAbsoluteForFamily, joinForFamily, pathFamilyOf, pathIdentityOf, win32NamespaceScreen } from "../tools/fs/safety.js";
2
- import { pathTargetValue } from "./tool-registry.js";
1
+ import { canonicalizeTarget, isAbsoluteForFamily, joinForFamily, pathFamilyOf, pathIdentityOf, spellingOfIdentity, win32NamespaceScreen } from "../tools/fs/safety.js";
2
+ import { declaredPathTargetOf, pathTargetValue, protectivePathTargetOf } from "./tool-registry.js";
3
+ import { symlinkChainHops } from "./path-identity.js";
4
+ import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, withGateTimeout } from "./gate-bounded-await.js";
3
5
  export function pathTargetBaseOf(pt, bases) {
4
6
  return (pt?.base ?? "cwd") === "root" ? bases.root : (bases.cwd ?? bases.root);
5
7
  }
@@ -31,10 +33,8 @@ export function effectivePathTargetOf(call, pt, bases) {
31
33
  if (spelling.spelled !== undefined)
32
34
  return resolveSpelling(spelling.spelled, spelling.base, bases);
33
35
  const declaredBase = spelling.base;
34
- if (declaredBase === undefined)
35
- return undefined;
36
36
  const family = pathFamilyOf(bases);
37
- return isAbsoluteForFamily(family, declaredBase) ? readingOf(pathIdentityOf(declaredBase, family, bases.root ?? bases.cwd)) : unreadableBase(declaredBase);
37
+ return declaredBase !== undefined && isAbsoluteForFamily(family, declaredBase) ? readingOf(pathIdentityOf(declaredBase, family, bases.root ?? bases.cwd)) : unreadableBase(declaredBase);
38
38
  }
39
39
  export function effectivePathSpellingOf(call, pt, bases) {
40
40
  if (pt === undefined)
@@ -63,9 +63,7 @@ function resolveSpelling(spelled, declaredBase, bases) {
63
63
  const tree = bases.root ?? bases.cwd;
64
64
  if (isAbsoluteForFamily(family, screened.spelled))
65
65
  return readingOf(pathIdentityOf(screened.spelled, family, tree));
66
- if (declaredBase === undefined || family === undefined)
67
- return undefined;
68
- if (!isAbsoluteForFamily(family, declaredBase))
66
+ if (declaredBase === undefined || family === undefined || !isAbsoluteForFamily(family, declaredBase))
69
67
  return unreadableBase(declaredBase);
70
68
  return readingOf(pathIdentityOf(joinForFamily(family, declaredBase, screened.spelled), family, tree));
71
69
  }
@@ -75,5 +73,111 @@ function readingOf(r) {
75
73
  return r.reason === "relative" || r.reason === "unanchored" ? undefined : { kind: "refused", reason: r.reason };
76
74
  }
77
75
  function unreadableBase(base) {
78
- return { kind: "unreadable", reason: `the base this call resolves against (${JSON.stringify(base)}) is not a fully-qualified path of this tree's family` };
76
+ return { kind: "unreadable", reason: base === undefined ? "the call's path is relative to a base that was not supplied" : `the base this call resolves against (${JSON.stringify(base)}) is not a fully-qualified path of this tree's family` };
77
+ }
78
+ export async function readPathViews(env, spelled, base, bases, signal, opts) {
79
+ const lexical = resolveSpelling(spelled, base, bases);
80
+ const family = pathFamilyOf(bases);
81
+ if (env === undefined || family === undefined || lexical?.kind === "unreadable")
82
+ return { lexical, hops: [] };
83
+ const canonical = await canonicalizeTarget(env, spelled, signal, family, base);
84
+ const climbs = (s) => s !== undefined && (family === "win" ? /(^|[\\/])\.\.([\\/]|$)/ : /(^|\/)\.\.(\/|$)/).test(s);
85
+ const followsALink = canonical.ok && (climbs(spelled) || climbs(base) || lexical?.kind !== "path" || absoluteTargetIdentityOf(canonical.key, bases) !== lexical.identity);
86
+ const hops = opts?.hops === true && followsALink ? await withGateTimeout(symlinkChainHops(env, spelled, signal, family, base), DEFAULT_GATE_AWAIT_TIMEOUT_MS, signal, "canonicalChain").catch(() => []) : [];
87
+ return { lexical, canonical, hops };
88
+ }
89
+ export function absoluteTargetIdentityOf(spelled, bases) {
90
+ const r = pathIdentityOf(spelled, pathFamilyOf(bases), bases.root ?? bases.cwd);
91
+ return r.ok ? r.identity : undefined;
92
+ }
93
+ const READ_FOR = new WeakMap();
94
+ const REREAD = new WeakMap();
95
+ export async function readCallTarget(env, call, signal, leads) {
96
+ const bases = { ...(call.root !== undefined ? { root: call.root } : {}), ...(call.cwd !== undefined ? { cwd: call.cwd } : {}) };
97
+ const family = pathFamilyOf(bases);
98
+ const views = new Map();
99
+ const viewOf = (pt) => {
100
+ const sel = effectivePathSpellingOf(call, pt, bases);
101
+ if (sel === undefined)
102
+ return Promise.resolve(undefined);
103
+ if ("refused" in sel)
104
+ return Promise.resolve({ kind: "refused", reason: sel.refused });
105
+ const target = sel.spelled !== undefined ? { spelled: sel.spelled, base: sel.base } : sel.base !== undefined && isAbsoluteForFamily(family, sel.base) ? { spelled: sel.base, base: undefined } : undefined;
106
+ if (target === undefined)
107
+ return Promise.resolve(unreadableBase(sel.base));
108
+ const { spelled, base } = target;
109
+ const key = JSON.stringify([spelled, base ?? null]);
110
+ const prior = views.get(key);
111
+ if (prior !== undefined)
112
+ return prior;
113
+ const read = readPathViews(env, spelled, base, bases, signal, { hops: true })
114
+ .catch(() => ({ lexical: resolveSpelling(spelled, base, bases), hops: [] }))
115
+ .then((v) => viewFrom(v, family));
116
+ views.set(key, read);
117
+ return read;
118
+ };
119
+ const protective = await viewOf(protectivePathTargetOf(call));
120
+ const declared = call.face === undefined ? undefined : await viewOf(declaredPathTargetOf(call));
121
+ const led = leads === undefined ? undefined : [...leads].filter((e) => e[1] !== undefined && e[1] !== e[0]);
122
+ const reading = Object.freeze({ ...(protective !== undefined ? { protective } : {}), ...(declared !== undefined ? { declared } : {}), ...(led !== undefined && led.length > 0 ? { leads: new Map(led) } : {}) });
123
+ if (typeof call.args === "object" && call.args !== null)
124
+ READ_FOR.set(reading, call.args);
125
+ REREAD.set(reading, (args, rereadSignal) => readCallTarget(env, { ...call, args }, rereadSignal, leads));
126
+ return reading;
127
+ }
128
+ export function withArgsReading(req, args, signal) {
129
+ const reading = callTargetReadingOf(req);
130
+ const reread = reading === undefined ? undefined : REREAD.get(reading);
131
+ return reread === undefined ? undefined : reread(args, signal).then((targetReading) => ({ ...req, args, targetReading }));
132
+ }
133
+ function viewFrom(v, family) {
134
+ if (v.lexical === undefined)
135
+ return undefined;
136
+ if (v.lexical.kind !== "path")
137
+ return Object.freeze({ ...v.lexical });
138
+ const canonical = v.canonical?.ok === true ? v.canonical.key : undefined;
139
+ return Object.freeze({ kind: "path", lexical: spellingOfIdentity(v.lexical.identity, family), ...(canonical !== undefined ? { canonical } : {}), ...(v.hops.length > 0 ? { hops: Object.freeze([...v.hops]) } : {}) });
140
+ }
141
+ export function callTargetReadingOf(req) {
142
+ const r = req.targetReading;
143
+ return r !== undefined && READ_FOR.get(r) === req.args ? r : undefined;
144
+ }
145
+ export function callTargetViewsOf(req, direction, bare = {}) {
146
+ const reading = callTargetReadingOf(req);
147
+ if (reading !== undefined) {
148
+ const v = direction === "tighten" ? reading.protective : reading.declared;
149
+ if (v === undefined || v.kind === "refused")
150
+ return undefined;
151
+ if (v.kind === "unreadable")
152
+ return { spellings: [], unreadable: v.reason };
153
+ const spellings = [v.lexical];
154
+ for (const s of [v.canonical, ...(v.hops ?? [])])
155
+ if (s !== undefined && !spellings.includes(s))
156
+ spellings.push(s);
157
+ return { spellings };
158
+ }
159
+ const own = req.root !== undefined || req.cwd !== undefined ? { ...(req.root !== undefined ? { root: req.root } : {}), ...(req.cwd !== undefined ? { cwd: req.cwd } : {}) } : bare;
160
+ const target = effectivePathTargetOf(req, direction === "tighten" ? protectivePathTargetOf(req) : declaredPathTargetOf(req), own);
161
+ if (target === undefined || target.kind === "refused")
162
+ return undefined;
163
+ if (target.kind === "unreadable")
164
+ return { spellings: [], unreadable: target.reason };
165
+ return { spellings: [spellingOfIdentity(target.identity, pathFamilyOf(own))] };
166
+ }
167
+ export function legCallTargetStamp(leg) {
168
+ const taken = new WeakMap();
169
+ return (req, signal) => {
170
+ const args = req.args;
171
+ if (typeof args !== "object" || args === null || callTargetReadingOf(req) !== undefined || !leg.judgesPaths())
172
+ return undefined;
173
+ const root = req.root ?? leg.root;
174
+ const cwd = req.cwd ?? leg.cwd();
175
+ const prior = taken.get(args);
176
+ const reading = prior !== undefined && prior.root === root && prior.cwd === cwd && prior.toolName === req.toolName && prior.toolCallId === req.toolCallId && prior.face === req.face
177
+ ? prior.reading
178
+ : leg.leads().then((leads) => readCallTarget(leg.env, { toolName: req.toolName, args, ...(req.face !== undefined ? { face: req.face } : {}), root, ...(cwd !== undefined ? { cwd } : {}) }, signal, leads));
179
+ if (prior?.reading !== reading)
180
+ taken.set(args, { root, cwd, toolName: req.toolName, toolCallId: req.toolCallId, face: req.face, reading });
181
+ return reading.then((targetReading) => ({ ...req, targetReading }));
182
+ };
79
183
  }
@@ -368,6 +368,11 @@ export interface EngineNotice {
368
368
  * (one call's own rule check — every later call reads again, so the rows apply as soon as a read succeeds). Once
369
369
  * per run, whichever read fails first; the trace keeps every read's line (`permission.rule_store_unreadable`).
370
370
  * `detail: { at, sessionId, runId }`. Audience `user`.
371
+ * - `"permission.rule_row_dropped"` — the person's store served project-scoped rows whose scope root is not a
372
+ * complete absolute path (only a foreign writer or a hand edit makes one; every engine write face refuses it): they
373
+ * take no part in this run's checks — a deny or ask row among them protects nothing — until they are rewritten or
374
+ * removed. Once per run, by whichever read met them first. `detail: { rows, count, sessionId, runId }` — `rows`
375
+ * the first eight `{ rule, scopeRoot }` (each bounded), `count` all of them. Audience `user`.
371
376
  * - `"config.durable_gate_unavailable"` — a durable-approval MANDATE is in force on a leg with NO
372
377
  * checkpoint store: the per-principal `forceDurableGate` entitlement, or the task's own
373
378
  * `durableApproval` opt-in with no live approver seat (`durableMandateSourceOf`, the predicate the
@@ -394,8 +399,9 @@ export interface EngineNotice {
394
399
  * frame's two count keys). They are NOT redelivered (a steer aimed at a finished run must not
395
400
  * fire at the next one — unlike ENGINE notes, which pend per session); the loud half of the
396
401
  * #257 contract's "accepted = enqueued, not consumed" sentence;
397
- * `detail: { steer, cause, taskId?, sessionId?, runId? }` / `{ followUp, cause, taskId?, sessionId?, runId? }`
398
- * (`cause` on both rows, closed `USER_INPUT_UNDRAINED_CAUSES`: `"leg_end"` — this engine's arm, the
402
+ * `detail: { <countKey>, cause, taskId?, sessionId?, runId? }` (the `code` and the `countKey` of each family are the
403
+ * table {@link USER_INPUT_UNDRAINED_FAMILIES}, keyed by the lane the input was queued on — `injectionLaneOf` — and the
404
+ * minter writes exactly those; `cause` on both rows, closed `USER_INPUT_UNDRAINED_CAUSES`: `"leg_end"` — this engine's arm, the
399
405
  * leg ended with it still owed; `"crashed"` — minted by the SERVING LAYER, not this engine, before the
400
406
  * session's next run, for inputs it had accepted whose consumption was not confirmed before the process died: their queue was the
401
407
  * serving layer's, so only it can count them, and it picks the family by the LANE the input was queued on (a `later`
@@ -404,7 +410,7 @@ export interface EngineNotice {
404
410
  * per-run identity, the key this account is actually about — see
405
411
  * {@link undrainedUserInputNotices}; all omitted when the caller has none, never fabricated). A delegated child
406
412
  * leg is announced on its PARENT's session as well — the direct parent only — as a second row per family,
407
- * `detail: { steer | followUp, cause, taskId?, sessionId: <the parent's>, childSessionId }` (no `runId`: nothing
413
+ * `detail: { <countKey>, cause, taskId?, sessionId: <the parent's>, childSessionId }` (no `runId`: nothing
408
414
  * carries the parent's), message tail `(a child leg, <childSessionId>)`; `childSessionId` present = a child reported it.
409
415
  * Per-run, at most once per family (the terminal sweep is a single site) — which is why
410
416
  * `taskId`/`sessionId` cannot key it: both answer the same value for two runs of one session.
@@ -971,6 +977,22 @@ export declare function settleOwedUserInput(account: OwedUserInputAccount, rowRe
971
977
  */
972
978
  export declare const USER_INPUT_UNDRAINED_CAUSES: readonly ["leg_end", "crashed"];
973
979
  export type UserInputUndrainedCause = (typeof USER_INPUT_UNDRAINED_CAUSES)[number];
980
+ /**
981
+ * The two families of the stranded-input account, keyed by the lane the input was queued on (`injectionLaneOf` maps a steer's
982
+ * tier to it): the notice `code` the family is announced under, and the `detail` key that carries its count
983
+ * (`detail[countKey]`, beside `cause`). {@link undrainedUserInputNotices} writes its rows with exactly these two spellings, so a
984
+ * serving layer that mints the `crashed` arm picks its family by lane and spells code and count key the way the engine does.
985
+ */
986
+ export declare const USER_INPUT_UNDRAINED_FAMILIES: Readonly<{
987
+ readonly steer: Readonly<{
988
+ readonly code: "task.user_steer_undrained";
989
+ readonly countKey: "steer";
990
+ }>;
991
+ readonly followUp: Readonly<{
992
+ readonly code: "task.user_followup_undrained";
993
+ readonly countKey: "followUp";
994
+ }>;
995
+ }>;
974
996
  /** Where a child leg's account is ALSO announced: its spawner's session, and that run's task id when the spawner carried one
975
997
  * (it never carries the run id, and the row never invents one). */
976
998
  interface ParentLegIds {
@@ -52,6 +52,12 @@ export function settleOwedUserInput(account, rowReturned, ids, onNotice) {
52
52
  }
53
53
  const SETTLED_OWED_ACCOUNTS = new WeakSet();
54
54
  export const USER_INPUT_UNDRAINED_CAUSES = Object.freeze(["leg_end", "crashed"]);
55
+ const STEER_UNDRAINED = "task.user_steer_undrained";
56
+ const FOLLOWUP_UNDRAINED = "task.user_followup_undrained";
57
+ export const USER_INPUT_UNDRAINED_FAMILIES = Object.freeze({
58
+ steer: Object.freeze({ code: STEER_UNDRAINED, countKey: "steer" }),
59
+ followUp: Object.freeze({ code: FOLLOWUP_UNDRAINED, countKey: "followUp" }),
60
+ });
55
61
  export function parentLegIds(internals) {
56
62
  const parentSessionId = internals?.parentSessionId;
57
63
  if (typeof parentSessionId !== "string" || parentSessionId === "")
@@ -73,17 +79,17 @@ export function undrainedUserInputNotices(counts, taskId, sessionId, runId, pare
73
79
  const cause = "leg_end";
74
80
  if (counts.steer > 0) {
75
81
  const message = `${counts.steer} user steer(s) ${tail}`;
76
- const count = { steer: counts.steer, cause };
77
- own.push({ code: "task.user_steer_undrained", message, detail: { ...count, ...tid, ...sid, ...rid } });
82
+ const count = { [USER_INPUT_UNDRAINED_FAMILIES.steer.countKey]: counts.steer, cause };
83
+ own.push({ code: STEER_UNDRAINED, message, detail: { ...count, ...tid, ...sid, ...rid } });
78
84
  if (toParent)
79
- copies.push({ code: "task.user_steer_undrained", message: `${message}${copyTail}`, detail: { ...count, ...ptid, sessionId: parentSession, childSessionId: sessionId } });
85
+ copies.push({ code: STEER_UNDRAINED, message: `${message}${copyTail}`, detail: { ...count, ...ptid, sessionId: parentSession, childSessionId: sessionId } });
80
86
  }
81
87
  if (counts.followUp > 0) {
82
88
  const message = `${counts.followUp} user follow-up(s) ${tail}`;
83
- const count = { followUp: counts.followUp, cause };
84
- own.push({ code: "task.user_followup_undrained", message, detail: { ...count, ...tid, ...sid, ...rid } });
89
+ const count = { [USER_INPUT_UNDRAINED_FAMILIES.followUp.countKey]: counts.followUp, cause };
90
+ own.push({ code: FOLLOWUP_UNDRAINED, message, detail: { ...count, ...tid, ...sid, ...rid } });
85
91
  if (toParent)
86
- copies.push({ code: "task.user_followup_undrained", message: `${message}${copyTail}`, detail: { ...count, ...ptid, sessionId: parentSession, childSessionId: sessionId } });
92
+ copies.push({ code: FOLLOWUP_UNDRAINED, message: `${message}${copyTail}`, detail: { ...count, ...ptid, sessionId: parentSession, childSessionId: sessionId } });
87
93
  }
88
94
  return [...own, ...copies];
89
95
  }
@@ -13,6 +13,9 @@ export async function runGateFold(pass) {
13
13
  const { input, toolName, toolCallId, callSignal, ledger, notifier, adjudicate } = pass;
14
14
  const boundedAwaitDeadlineMs = createGateDeadline(input, notifier);
15
15
  pass.req = { toolName, args: pass.currentInput, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
16
+ const stamped = input.stampCallTarget?.(pass.req, callSignal ?? input.abortSignal);
17
+ if (stamped !== undefined)
18
+ pass.req = await stamped;
16
19
  pass.decision = adjudicate ? refuseOutOfContractDecision(await (callSignal !== undefined ? adjudicate(pass.req, callSignal) : adjudicate(pass.req))) : { action: "allow" };
17
20
  {
18
21
  const refusedBy = ledger.fromDecision(pass.decision);
@@ -1,3 +1,4 @@
1
+ import { withArgsReading } from "./effective-path-target.js";
1
2
  import { attestClassifierDenyCause, classifierDenyCauseOf, decisionText, refuseOutOfContractDecision } from "./tool-policy.js";
2
3
  import { arbitrateByMode, classifyAsk, fileCommandOptionsSentence, persistedRuleMandateOf, ruleClearanceOf, safetyAxisOf, stampAskOrigin } from "./ask-origin.js";
3
4
  import { classifierParseFailureDenyMessage, denialLimitFallbackMessage, rungDidNotRule, unarmedWindow } from "./auto-mode.js";
@@ -40,6 +41,7 @@ export async function runGateLanes(pass) {
40
41
  writeProtected: pass.writeProtected === true,
41
42
  writeProtectionUnresolved: pass.writeProtectionUnresolved === true,
42
43
  });
44
+ const readFaceNow = input.readFace?.();
43
45
  const askOriginFacts = (org, ruleCallUnreadable) => ({
44
46
  contentQuestion: heldBy("lanes.contentQuestionClass", QUESTION_TOOL_NAMES, pass.req),
45
47
  markedUnresolvable: markedUnresolvable(),
@@ -49,6 +51,7 @@ export async function runGateLanes(pass) {
49
51
  mandateMarks: mandateMarksNow(),
50
52
  ...(input.toolMarkSources !== undefined ? { toolMarkSources: input.toolMarkSources } : {}),
51
53
  ...(input.a2aIsolatePeers === true ? { a2aIsolatePeers: true } : {}),
54
+ ...(readFaceNow !== undefined ? { readFace: readFaceNow } : {}),
52
55
  });
53
56
  const classifyStanding = (unstamped, facts) => (pass.askClassification = classifyAsk(unstamped, facts));
54
57
  let orgTightenCount = 0;
@@ -116,7 +119,8 @@ export async function runGateLanes(pass) {
116
119
  pass.decision = orgAnswer.decision;
117
120
  const ruleCallUnreadableOf = (lane) => lane?.unreadable === true && lane.reason !== undefined;
118
121
  const readPersistedRules = (req) => readPersistedRuleLane(input.persistedRules, req, input.abortSignal);
119
- const laneReq = { ...pass.req, ...(pass.policyRewrite !== undefined ? { args: pass.policyRewrite } : {}) };
122
+ const reread = pass.policyRewrite !== undefined ? withArgsReading(pass.req, pass.policyRewrite, pass.callSignal ?? input.abortSignal) : undefined;
123
+ const laneReq = reread !== undefined ? await reread : { ...pass.req, ...(pass.policyRewrite !== undefined ? { args: pass.policyRewrite } : {}) };
120
124
  const laneAnswer = input.persistedRules !== undefined && wireOf("lanes.persistedLaneSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME ? await readPersistedRules(laneReq) : undefined;
121
125
  {
122
126
  const tightened = applyPersistedTightening(pass.decision, laneAnswer);
@@ -137,6 +141,8 @@ export async function runGateLanes(pass) {
137
141
  if (pass.policyRewrite !== undefined) {
138
142
  pass.currentInput = pass.policyRewrite;
139
143
  pass.req.args = pass.policyRewrite;
144
+ if (laneReq.targetReading !== undefined)
145
+ pass.req.targetReading = laneReq.targetReading;
140
146
  }
141
147
  const ruleMayClear = () => ruleClearanceOf(pass.decision.action === "ask" ? pass.decision : undefined, mandateMarksNow()).ruleMayClear;
142
148
  const classMayClear = () => pass.decision.action === "ask" && classifyAsk(pass.decision, askOriginFacts(orgAnswer.org, ruleCallUnreadableOf(laneAnswer))).row.ruleMayClear;
@@ -574,8 +580,10 @@ export async function runGateLanes(pass) {
574
580
  break;
575
581
  }
576
582
  let editRuleCallUnreadable = false;
583
+ const editReread = withArgsReading(pass.req, editArgs, callSignal ?? input.abortSignal);
584
+ const editLaneReq = editReread !== undefined ? await editReread : { toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
577
585
  if (input.persistedRules !== undefined && wireOf("lanes.persistedEditSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME) {
578
- const editLane = await readPersistedRules({ toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) });
586
+ const editLane = await readPersistedRules(editLaneReq);
579
587
  editRuleCallUnreadable = ruleCallUnreadableOf(editLane);
580
588
  const editTightened = applyPersistedTightening(recheck, editLane);
581
589
  recheck = editTightened.decision;
@@ -586,7 +594,7 @@ export async function runGateLanes(pass) {
586
594
  break;
587
595
  }
588
596
  }
589
- const editSkillRefusal = skillRowRefusal(input.skillGrants, { toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) });
597
+ const editSkillRefusal = skillRowRefusal(input.skillGrants, editLaneReq);
590
598
  if (editSkillRefusal !== undefined) {
591
599
  editDenied = editSkillRefusal;
592
600
  pass.deniedBy = "policy";
@@ -125,6 +125,11 @@ export type SettlementParty = (typeof SETTLEMENT_PARTIES)[number];
125
125
  export type SettlementPartiesMatchTheRecord = AssertAllKeysHandled<Exclude<Settlement["who"]["party"], SettlementParty> | Exclude<SettlementParty, Settlement["who"]["party"]>>;
126
126
  /** Membership test for {@link SettlementParty}. */
127
127
  export declare function isSettlementParty(v: unknown): v is SettlementParty;
128
+ /** Who REPORTED a durable decide (`hostDecision.decidedBy`): a person, the host's own SLA sweep at the row's deadline, or the
129
+ * deployment's policy — the closed set; a host's decide door mirrors the words it accepts from this one. */
130
+ export declare const HOST_DECIDERS: readonly ["person", "sla_timeout", "policy"];
131
+ export type HostDecider = (typeof HOST_DECIDERS)[number];
132
+ export declare const isHostDecider: (word: unknown) => word is HostDecider;
128
133
  /** The DECIDER arms of {@link Settlement} — a person's and the deployment policy's, the two parties that
129
134
  * answer an ask either way. Named so a site that both composes one and quotes its note can read the seat
130
135
  * off the record instead of re-deriving it. */
@@ -25,6 +25,8 @@ const SETTLEMENT_PARTY_SET = new Set(SETTLEMENT_PARTIES);
25
25
  export function isSettlementParty(v) {
26
26
  return SETTLEMENT_PARTY_SET.has(v);
27
27
  }
28
+ export const HOST_DECIDERS = Object.freeze(["person", "sla_timeout", "policy"]);
29
+ export const isHostDecider = (word) => HOST_DECIDERS.some((w) => w === word);
28
30
  export function decidersNote(text) {
29
31
  return text === "" ? undefined : text;
30
32
  }