@sema-agent/core 7.45.0 → 7.47.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 (139) hide show
  1. package/CHANGELOG.md +298 -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 +33 -25
  6. package/dist/agents/tool-filter.d.ts +17 -1
  7. package/dist/agents/tool-filter.js +7 -0
  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/allow-entry.d.ts +6 -4
  11. package/dist/core/allow-entry.js +6 -5
  12. package/dist/core/ask-origin.d.ts +35 -12
  13. package/dist/core/ask-origin.js +24 -7
  14. package/dist/core/checkpoint-store.d.ts +39 -2
  15. package/dist/core/effective-path-target.d.ts +136 -4
  16. package/dist/core/effective-path-target.js +110 -2
  17. package/dist/core/engine-notice.d.ts +12 -4
  18. package/dist/core/gate-fold.js +5 -2
  19. package/dist/core/gate-lanes.js +11 -4
  20. package/dist/core/gate-outcome.d.ts +4 -2
  21. package/dist/core/gate-outcome.js +2 -2
  22. package/dist/core/governance-codes.d.ts +3 -1
  23. package/dist/core/governance-codes.js +3 -0
  24. package/dist/core/hooks.d.ts +3 -0
  25. package/dist/core/path-rule-binding.d.ts +65 -0
  26. package/dist/core/path-rule-binding.js +99 -0
  27. package/dist/core/permission-rule-model.d.ts +59 -24
  28. package/dist/core/permission-rule-model.js +48 -44
  29. package/dist/core/permission-rule-provider.d.ts +7 -0
  30. package/dist/core/permission-rule-provider.js +10 -3
  31. package/dist/core/permission-rule-store.d.ts +8 -0
  32. package/dist/core/permission-rule-store.js +6 -2
  33. package/dist/core/permission-rule-sync.js +2 -2
  34. package/dist/core/permission-rules.d.ts +26 -45
  35. package/dist/core/permission-rules.js +77 -57
  36. package/dist/core/runner/child-tree.d.ts +97 -0
  37. package/dist/core/runner/child-tree.js +104 -0
  38. package/dist/core/runner/construction-doors.d.ts +5 -0
  39. package/dist/core/runner/construction-doors.js +6 -0
  40. package/dist/core/runner/contracts.d.ts +46 -0
  41. package/dist/core/runner/gate-exit.d.ts +4 -1
  42. package/dist/core/runner/gate-exit.js +5 -1
  43. package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
  44. package/dist/core/runner/permission-rule-lanes.js +18 -12
  45. package/dist/core/runner/persisted-path-rows.d.ts +31 -7
  46. package/dist/core/runner/persisted-path-rows.js +31 -12
  47. package/dist/core/runner/prepare-ask-lane.d.ts +4 -0
  48. package/dist/core/runner/prepare-ask-lane.js +4 -2
  49. package/dist/core/runner/prepare-execution-env.d.ts +5 -2
  50. package/dist/core/runner/prepare-execution-env.js +10 -3
  51. package/dist/core/runner/prepare-gate-stations.d.ts +3 -0
  52. package/dist/core/runner/prepare-gate-stations.js +2 -1
  53. package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
  54. package/dist/core/runner/prepare-hands-readface.js +11 -11
  55. package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
  56. package/dist/core/runner/prepare-inherited-gate.js +8 -2
  57. package/dist/core/runner/prepare-park-ask.d.ts +3 -0
  58. package/dist/core/runner/prepare-park-ask.js +5 -3
  59. package/dist/core/runner/prepare-policy-chain.d.ts +14 -2
  60. package/dist/core/runner/prepare-policy-chain.js +13 -5
  61. package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
  62. package/dist/core/runner/prepare-suspend-saga.js +7 -1
  63. package/dist/core/runner/prepare-task.js +37 -18
  64. package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
  65. package/dist/core/runner/prepare-turn-wiring.js +2 -1
  66. package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
  67. package/dist/core/runner/prepare-workspace-restore.js +1 -0
  68. package/dist/core/runner/resume-apply.js +17 -3
  69. package/dist/core/runner/resume-checkpoint-screen.js +13 -0
  70. package/dist/core/runner/run-leg.js +5 -1
  71. package/dist/core/runner/runtask.js +2 -2
  72. package/dist/core/runner/sub-agent-deny-lists.d.ts +1 -1
  73. package/dist/core/runner/write-protection-station.d.ts +3 -2
  74. package/dist/core/runner/write-protection-station.js +1 -0
  75. package/dist/core/runner-deps.d.ts +14 -0
  76. package/dist/core/sensitive-path-policy.d.ts +8 -0
  77. package/dist/core/sensitive-path-policy.js +18 -2
  78. package/dist/core/shell-wrapper-projection.d.ts +11 -0
  79. package/dist/core/shell-wrapper-projection.js +56 -0
  80. package/dist/core/shell-wrapper-table.d.ts +144 -34
  81. package/dist/core/shell-wrapper-table.js +40 -20
  82. package/dist/core/synthetic-read-verdict.d.ts +3 -0
  83. package/dist/core/synthetic-read-verdict.js +3 -1
  84. package/dist/core/tool-catalog-entries.d.ts +21 -0
  85. package/dist/core/tool-catalog-entries.js +12 -2
  86. package/dist/core/tool-face.d.ts +10 -1
  87. package/dist/core/tool-name-readings.d.ts +8 -0
  88. package/dist/core/tool-name-readings.js +2 -0
  89. package/dist/core/tool-policy.d.ts +40 -17
  90. package/dist/core/tool-policy.js +11 -9
  91. package/dist/core/tool-registry.d.ts +9 -0
  92. package/dist/core/tool-registry.js +10 -0
  93. package/dist/core/tool-spec.d.ts +3 -2
  94. package/dist/core/write-protect.d.ts +14 -3
  95. package/dist/core/write-protect.js +41 -10
  96. package/dist/index.d.ts +6 -4
  97. package/dist/index.js +5 -3
  98. package/dist/orchestration/run-spec.js +8 -7
  99. package/dist/orchestration/workflow.js +1 -1
  100. package/dist/shell/lex.d.ts +22 -0
  101. package/dist/shell/lex.js +75 -13
  102. package/dist/tools/fs/bash-program-position.d.ts +14 -90
  103. package/dist/tools/fs/bash-program-position.js +41 -74
  104. package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
  105. package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
  106. package/dist/tools/fs/index.d.ts +3 -2
  107. package/dist/tools/fs/program-read-model.d.ts +711 -0
  108. package/dist/tools/fs/program-read-model.js +1259 -0
  109. package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
  110. package/dist/tools/fs/program-read-model.rows.js +624 -0
  111. package/dist/tools/fs/read-deny-home.d.ts +19 -1
  112. package/dist/tools/fs/read-deny-home.js +62 -24
  113. package/dist/tools/fs/read-deny.d.ts +54 -14
  114. package/dist/tools/fs/read-deny.js +104 -50
  115. package/dist/tools/fs/removal-targets.d.ts +7 -6
  116. package/dist/tools/fs/removal-targets.js +34 -24
  117. package/dist/tools/fs/safety.d.ts +3 -0
  118. package/dist/tools/fs/safety.js +10 -3
  119. package/dist/tools/fs/shell-read-argv.d.ts +86 -0
  120. package/dist/tools/fs/shell-read-argv.js +388 -0
  121. package/dist/tools/fs/shell-read-bindings.d.ts +300 -0
  122. package/dist/tools/fs/shell-read-bindings.js +755 -0
  123. package/dist/tools/fs/shell-read-check.js +23 -15
  124. package/dist/tools/fs/shell-read-establish.js +82 -8
  125. package/dist/tools/fs/shell-read-judge.d.ts +11 -1
  126. package/dist/tools/fs/shell-read-judge.js +9 -1
  127. package/dist/tools/fs/shell-read-plan.d.ts +33 -8
  128. package/dist/tools/fs/shell-read-plan.js +309 -407
  129. package/dist/tools/fs/shell-read-word.d.ts +60 -26
  130. package/dist/tools/fs/shell-read-word.js +47 -37
  131. package/dist/tools/fs/shell-read-write-targets.d.ts +15 -64
  132. package/dist/tools/fs/shell-read-write-targets.js +85 -137
  133. package/dist/tools/fs/shell-reentry-text.d.ts +136 -6
  134. package/dist/tools/fs/shell-reentry-text.js +203 -53
  135. package/dist/tools/monitor.d.ts +3 -3
  136. package/package.json +1 -1
  137. package/test/export-surface.snapshot.json +25 -5
  138. package/wire-changes/core@7.46.0.json +1522 -0
  139. package/wire-changes/core@7.47.0.json +412 -0
@@ -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";
@@ -16,7 +17,7 @@ import { callFaceSeat } from "./tool-roster.js";
16
17
  import { heldBy, wireOf } from "./tool-name-readings.js";
17
18
  import { asksUnstated, modeAcceptsFileCommand, permissionSeatAt, unstatedAskMessage } from "./mode-policy.js";
18
19
  import { ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON } from "./permission-rule-org.js";
19
- import { exitGate } from "./runner/gate-exit.js";
20
+ import { exitGate, passAskFace } from "./runner/gate-exit.js";
20
21
  import { readOrgRuleLane, readPersistedRuleLane } from "./rule-lane-reads.js";
21
22
  import { traceHookCrash } from "./gate-crash-trace.js";
22
23
  import { createGateDeadline, awaitWriteProtectionJudgment, withGateTimeout } from "./gate-bounded-await.js";
@@ -51,6 +52,7 @@ export async function runGateLanes(pass) {
51
52
  ...(input.toolMarkSources !== undefined ? { toolMarkSources: input.toolMarkSources } : {}),
52
53
  ...(input.a2aIsolatePeers === true ? { a2aIsolatePeers: true } : {}),
53
54
  ...(readFaceNow !== undefined ? { readFace: readFaceNow } : {}),
55
+ askFace: passAskFace(pass),
54
56
  });
55
57
  const classifyStanding = (unstamped, facts) => (pass.askClassification = classifyAsk(unstamped, facts));
56
58
  let orgTightenCount = 0;
@@ -118,7 +120,8 @@ export async function runGateLanes(pass) {
118
120
  pass.decision = orgAnswer.decision;
119
121
  const ruleCallUnreadableOf = (lane) => lane?.unreadable === true && lane.reason !== undefined;
120
122
  const readPersistedRules = (req) => readPersistedRuleLane(input.persistedRules, req, input.abortSignal);
121
- const laneReq = { ...pass.req, ...(pass.policyRewrite !== undefined ? { args: pass.policyRewrite } : {}) };
123
+ const reread = pass.policyRewrite !== undefined ? withArgsReading(pass.req, pass.policyRewrite, pass.callSignal ?? input.abortSignal) : undefined;
124
+ const laneReq = reread !== undefined ? await reread : { ...pass.req, ...(pass.policyRewrite !== undefined ? { args: pass.policyRewrite } : {}) };
122
125
  const laneAnswer = input.persistedRules !== undefined && wireOf("lanes.persistedLaneSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME ? await readPersistedRules(laneReq) : undefined;
123
126
  {
124
127
  const tightened = applyPersistedTightening(pass.decision, laneAnswer);
@@ -139,6 +142,8 @@ export async function runGateLanes(pass) {
139
142
  if (pass.policyRewrite !== undefined) {
140
143
  pass.currentInput = pass.policyRewrite;
141
144
  pass.req.args = pass.policyRewrite;
145
+ if (laneReq.targetReading !== undefined)
146
+ pass.req.targetReading = laneReq.targetReading;
142
147
  }
143
148
  const ruleMayClear = () => ruleClearanceOf(pass.decision.action === "ask" ? pass.decision : undefined, mandateMarksNow()).ruleMayClear;
144
149
  const classMayClear = () => pass.decision.action === "ask" && classifyAsk(pass.decision, askOriginFacts(orgAnswer.org, ruleCallUnreadableOf(laneAnswer))).row.ruleMayClear;
@@ -576,8 +581,10 @@ export async function runGateLanes(pass) {
576
581
  break;
577
582
  }
578
583
  let editRuleCallUnreadable = false;
584
+ const editReread = withArgsReading(pass.req, editArgs, callSignal ?? input.abortSignal);
585
+ const editLaneReq = editReread !== undefined ? await editReread : { toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
579
586
  if (input.persistedRules !== undefined && wireOf("lanes.persistedEditSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME) {
580
- const editLane = await readPersistedRules({ toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) });
587
+ const editLane = await readPersistedRules(editLaneReq);
581
588
  editRuleCallUnreadable = ruleCallUnreadableOf(editLane);
582
589
  const editTightened = applyPersistedTightening(recheck, editLane);
583
590
  recheck = editTightened.decision;
@@ -588,7 +595,7 @@ export async function runGateLanes(pass) {
588
595
  break;
589
596
  }
590
597
  }
591
- const editSkillRefusal = skillRowRefusal(input.skillGrants, { toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) });
598
+ const editSkillRefusal = skillRowRefusal(input.skillGrants, editLaneReq);
592
599
  if (editSkillRefusal !== undefined) {
593
600
  editDenied = editSkillRefusal;
594
601
  pass.deniedBy = "policy";
@@ -343,9 +343,11 @@ export type GateDisposition = {
343
343
  /**
344
344
  * A permission MODE released a surviving ask — the allowed disposition's account of the question it did not put
345
345
  * to anyone: the mode, the disclosed class word (`askClass`, the first member of the folded set in `ASK_CLASSES`
346
- * order — one of {@link RELEASABLE_ASK_CLASSES}), the whole set, and the origin / mandate words the card would
346
+ * order — one of {@link releasableAskClassesFor} on the call's face), the whole set, and the origin / mandate words the card would
347
347
  * have shown. `reason` is a member of {@link import("./ask-origin.js").MODE_RELEASE_REASONS} (`isModeReleaseReason`):
348
- * `"rule_unreadable"` marks a release over a rule the call could not be read against; absent, the release names none.
348
+ * `"rule_unreadable"` marks a release over a rule the call could not be read against, `"write_protection_unresolved"` a release (the
349
+ * shell face) over a write target the judge could not resolve; absent, the release names none. The record carries no face, so the
350
+ * screen asks that SOME face releases the class.
349
351
  */
350
352
  export interface ModeRelease {
351
353
  mode: PermissionMode;
@@ -1,4 +1,4 @@
1
- import { isAskClass, isAskOrigin, isModeReleaseReason, isPermissionMode, RELEASABLE_ASK_CLASSES } from "./ask-origin.js";
1
+ import { isAskClass, isAskOrigin, isModeReleaseReason, isPermissionMode, ASK_FACES, releasableAskClassesFor } from "./ask-origin.js";
2
2
  import { isAutoModeFallbackCause } from "./auto-mode.js";
3
3
  export const SETTLEMENT_KINDS = Object.freeze([
4
4
  "human_allowed",
@@ -139,7 +139,7 @@ export function screenGateOutcome(g) {
139
139
  defects.push("I7: a mode release carries no settlement — nobody answered the ask it released");
140
140
  if (r.mode !== "bypassPermissions")
141
141
  defects.push(`I7: a mode release names mode "${String(r.mode)}" — only bypassPermissions releases`);
142
- if (!isAskClass(r.askClass) || !RELEASABLE_ASK_CLASSES.includes(r.askClass))
142
+ if (!isAskClass(r.askClass) || !ASK_FACES.some((face) => releasableAskClassesFor(face).includes(r.askClass)))
143
143
  defects.push(`I7: released.askClass "${String(r.askClass)}" is not a releasable class`);
144
144
  if (!Array.isArray(r.classes) || r.classes.length === 0 || !r.classes.every((c) => isAskClass(c)))
145
145
  defects.push("I7: released.classes must be a non-empty list of ask classes");
@@ -136,7 +136,7 @@ export type NoticeAudience = "user" | "operator";
136
136
  * the union's catalog ({@link EngineNotice.code}). The groups below say only which family a row joins,
137
137
  * so the catalog is not written a second time here and the two cannot drift.
138
138
  */
139
- export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.write_protection_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "permission.allowed_tool_not_granted", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "route.response_model_mismatch", "route.tool_call_id_folded", "context.tool_turn_repaired", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "task.tool_turn_incomplete", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed", "instructions.source_changed", "session.records_skipped", "memory.layer_locked_legacy", "server.model_unreachable"];
139
+ export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.write_protection_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "permission.rule_row_dropped", "permission.allowed_tool_not_granted", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "route.response_model_mismatch", "route.tool_call_id_folded", "context.tool_turn_repaired", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "task.tool_turn_incomplete", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed", "instructions.source_changed", "session.records_skipped", "memory.layer_locked_legacy", "server.model_unreachable"];
140
140
  /** A catalogued code (see {@link ENGINE_NOTICE_CODES}); {@link NOTICE_MINTER} says who mints it. NOT the type of
141
141
  * `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
142
142
  * sink is a supported shape, and narrowing that field would break it. */
@@ -181,6 +181,7 @@ declare const NOTICE_AUDIENCE_TABLE: {
181
181
  readonly "permission.ask_released_by_mode": "user";
182
182
  readonly "permission.read_deny_unapplied": "user";
183
183
  readonly "permission.rule_store_unavailable": "user";
184
+ readonly "permission.rule_row_dropped": "user";
184
185
  readonly "permission.allowed_tool_not_granted": "user";
185
186
  readonly "memory.capture_opted_out": "user";
186
187
  readonly "memory.capture_optout_unpersisted": "user";
@@ -295,6 +296,7 @@ declare const NOTICE_MINTER_TABLE: {
295
296
  readonly "permission.ask_released_by_mode": "engine";
296
297
  readonly "permission.read_deny_unapplied": "engine";
297
298
  readonly "permission.rule_store_unavailable": "engine";
299
+ readonly "permission.rule_row_dropped": "engine";
298
300
  readonly "permission.allowed_tool_not_granted": "engine";
299
301
  readonly "peer.inbound_disposition": "engine";
300
302
  readonly "peer.held_settled": "engine";
@@ -144,6 +144,7 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
144
144
  "permission.ask_released_by_mode",
145
145
  "permission.read_deny_unapplied",
146
146
  "permission.rule_store_unavailable",
147
+ "permission.rule_row_dropped",
147
148
  "permission.allowed_tool_not_granted",
148
149
  "peer.inbound_disposition",
149
150
  "peer.held_settled",
@@ -229,6 +230,7 @@ const NOTICE_AUDIENCE_TABLE = {
229
230
  "permission.ask_released_by_mode": "user",
230
231
  "permission.read_deny_unapplied": "user",
231
232
  "permission.rule_store_unavailable": "user",
233
+ "permission.rule_row_dropped": "user",
232
234
  "permission.allowed_tool_not_granted": "user",
233
235
  "memory.capture_opted_out": "user",
234
236
  "memory.capture_optout_unpersisted": "user",
@@ -324,6 +326,7 @@ const NOTICE_MINTER_TABLE = {
324
326
  "permission.ask_released_by_mode": "engine",
325
327
  "permission.read_deny_unapplied": "engine",
326
328
  "permission.rule_store_unavailable": "engine",
329
+ "permission.rule_row_dropped": "engine",
327
330
  "permission.allowed_tool_not_granted": "engine",
328
331
  "peer.inbound_disposition": "engine",
329
332
  "peer.held_settled": "engine",
@@ -1,6 +1,7 @@
1
1
  import type { ActorAssertion, DocumentContent, ImageContent, TextContent } from "../internal/llm.js";
2
2
  import type { ExecutionEnv, FileError, Result, SessionTreeEntry } from "../internal/harness-types.js";
3
3
  import type { DecisionReason, PermissionResult, ResolvedAsk, ToolCallFace, ToolCallRequest, ToolPolicy } from "./tool-policy.js";
4
+ import type { CallTargetStamp } from "./effective-path-target.js";
4
5
  import type { GateOutcome } from "./gate-outcome.js";
5
6
  import { type PreToolUseScreening } from "./runner/gate-exit.js";
6
7
  export { normalizeOrgGateVerdict, normalizePersistedRuleHit } from "./rule-lane-reads.js";
@@ -1176,6 +1177,8 @@ export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats, All
1176
1177
  * starting directory and answer for a run that has since `cd`ed. Absent ⇒ the contexts carry no
1177
1178
  * `cwd`, which is the honest answer for a run with no tracked one. */
1178
1179
  trackedCwd?: () => string | undefined;
1180
+ /** The leg's target-reading stamp, applied where the gate's request is minted over the final args (a rewrite re-reads it: `withArgsReading`). */
1181
+ stampCallTarget?: CallTargetStamp;
1179
1182
  /** The read-only env capability face put on every {@link HookToolContext} this gate call
1180
1183
  * builds ({@link HookEnvCapabilities}). Built ONCE per task by the runner (after the env is minted) and
1181
1184
  * passed through unchanged; omitted when the deployment wired no execution environment. */
@@ -0,0 +1,65 @@
1
+ import { type PathRuleBase, type PathRuleBases, type PersistedRule, type RuleBehavior, type RuleScope, type ScopeRootIdentity } from "./permission-rule-model.js";
2
+ import { type DenyFace, type JudgedPathRow, type PathRowReach } from "../tools/fs/read-deny.js";
3
+ import { type ToolPolicy } from "./tool-policy.js";
4
+ export { pathRowsReach, rowsWhereTheyLead, type PathRowsHit, type TargetViews } from "../tools/fs/read-deny.js";
5
+ /** One path rule as a binding reads it: the parsed rule, its text, and — for a deny rule that guards a face of the
6
+ * engine's deny tables — those faces (absent: the rule is judged on its own tool's calls only). */
7
+ export interface PathRuleSpec {
8
+ readonly parsed: Pick<PersistedRule, "match" | "command" | "behavior" | "tool">;
9
+ readonly ruleText: string;
10
+ readonly faces?: readonly DenyFace[];
11
+ }
12
+ /** A rule bound to its anchors: a deny-table row (`faces` empty for a rule that guards no table face), its three-valued
13
+ * reach over an absolute spelling, and the spec it was bound from. `unanchored` names the base words its pattern needs
14
+ * that the anchors do not supply (the rule reads `unreadable` everywhere: a gate lane fails closed on it; a deny table
15
+ * cannot judge it and leaves it to the gate). */
16
+ export interface BoundPathRule<S extends PathRuleSpec = PathRuleSpec> extends JudgedPathRow {
17
+ readonly spec: S;
18
+ /** The rule's behavior (a deny table takes the `deny` rows that guard a face). */
19
+ readonly behavior: RuleBehavior;
20
+ readonly reach: (absolutePath: string) => PathRowReach;
21
+ readonly unanchored?: readonly PathRuleBase[];
22
+ }
23
+ /**
24
+ * The ONE binding of path rules to their anchors (`anchors`: one set for every spec, or a set per spec). Each row resolves
25
+ * its pattern once against its anchors and reads an absolute spelling under the anchors' family — a row installed on a win
26
+ * root reads `C:\proj\x`, `C:/proj/x` and `c:\proj\x` as one path; a row whose anchors name no tree reads the spelling's
27
+ * own family. The head (where the row's reach starts) is spelled for the environment, so a leg can judge the row where
28
+ * the head leads.
29
+ */
30
+ export declare function bindPathRules<S extends PathRuleSpec>(specs: readonly S[], anchors: PathRuleBases | ((spec: S) => PathRuleBases)): BoundPathRule<S>[];
31
+ /** A caller policy's anchors on the leg it is installed on: the declared `pathBases` word by word, else the leg's — `/x`
32
+ * at the declared root (else the leg's root), a relative pattern at the declared cwd (else the `/x` anchor), `~/` at the
33
+ * declared home (else the leg environment's). `install` absent (a policy called outside a Runner leg): the declaration
34
+ * alone. */
35
+ export declare function policyAnchors(declared: PathRuleBases, install: PathRuleBases | undefined): PathRuleBases;
36
+ /** A stored row's anchors: a PROJECT row's `/x` and relative words at the row's own scope root (the project it was written
37
+ * for), a global or session row's at the chain's top leg; `~/` at the top leg's home for every row. */
38
+ export declare function persistedRowAnchors(scope: RuleScope, chainTop: PathRuleBases): PathRuleBases;
39
+ /** A leg's root as an identity for project coverage ({@link ScopeRootIdentity}): its canonical root, and each project
40
+ * row's scope root resolved through the leg's resolution (`leads`, each location once per leg) — so a row written under
41
+ * one spelling of the project's directory applies to a leg rooted at another spelling of it. `undefined` when the leg
42
+ * has no canonical root (the spellings alone are compared). */
43
+ export declare function projectScopeIdentity(rules: readonly Pick<PersistedRule, "scope">[], canonicalRoot: string | undefined, leads: HeadLeads): Promise<ScopeRootIdentity | undefined>;
44
+ /** A leg's resolution of the locations rule heads pass through: each location resolved once (twins are not refreshed
45
+ * within a leg — a link re-pointed mid-leg is met through the row as spelled, and at its new location from the next
46
+ * prepare; a location that does not resolve has no twin). `resolve` settles the parts of the given rows and answers
47
+ * every location settled so far. */
48
+ export interface HeadLeads {
49
+ resolve(rows: readonly Pick<JudgedPathRow, "head">[]): Promise<ReadonlyMap<string, string | undefined>>;
50
+ }
51
+ export declare function createHeadLeads(leadsTo: (part: string) => Promise<string | undefined>): HeadLeads;
52
+ /**
53
+ * INSTALL a policy on a leg: its path rules are anchored at `anchors` (the leg's root and home; the policy's declared
54
+ * `pathBases` still win word by word) — once, for every judgment of the policy from then on: its `check` (the copy of each
55
+ * request it hands its inner check carries `ToolCallRequest.pathRuleAnchors`), the deny-table rows
56
+ * (`ToolPolicy.pathRules` answers with this installation's rows whatever it is given), and every child the leg hands
57
+ * the policy to as a parent constraint. A policy with no path rules is returned as it is. Installing an installed policy
58
+ * installs what it was installed FROM; one policy installed at the same anchors is one object (identity is the
59
+ * installation). Every member the policy carries rides as it holds it; the decision its `check` answers is untouched.
60
+ */
61
+ export declare function installPolicyAnchors(policy: ToolPolicy | undefined, anchors: PathRuleBases): ToolPolicy | undefined;
62
+ /** The policy an installation was installed from (`undefined`: not an installation). */
63
+ export declare function installedFrom(policy: ToolPolicy | undefined): ToolPolicy | undefined;
64
+ /** The anchors an installation was installed at (`undefined`: not an installation). */
65
+ export declare function installationAnchorsOf(policy: ToolPolicy | undefined): PathRuleBases | undefined;
@@ -0,0 +1,99 @@
1
+ import { parsePermissionRule } from "./permission-rule-syntax.js";
2
+ import { isUsablePathBase, pathRuleHeadOf, pathRuleReachOf, ruleBasesNeeded } from "./permission-rule-model.js";
3
+ import { pathFamilyOf, spellingOfIdentity } from "../tools/fs/safety.js";
4
+ import { absoluteTargetIdentityOf } from "./effective-path-target.js";
5
+ import { locationLeadsTo, ruleHeadParts } from "../tools/fs/read-deny.js";
6
+ import { carriedMembersOf } from "./tool-policy.js";
7
+ export { pathRowsReach, rowsWhereTheyLead } from "../tools/fs/read-deny.js";
8
+ const NOT_REACHED = { reach: "not_reached" };
9
+ export function bindPathRules(specs, anchors) {
10
+ return specs.map((spec) => {
11
+ const a = typeof anchors === "function" ? anchors(spec) : anchors;
12
+ const family = pathFamilyOf(a);
13
+ const head = pathRuleHeadOf(spec.parsed, a);
14
+ const unanchored = ruleBasesNeeded(spec.parsed).filter((b) => !isUsablePathBase(b === "cwd" ? (a.cwd ?? a.root) : a[b], family));
15
+ const reach = (absolutePath) => {
16
+ const tree = a.root !== undefined || a.cwd !== undefined ? a : { root: absolutePath };
17
+ const id = absoluteTargetIdentityOf(absolutePath, tree);
18
+ return id === undefined ? NOT_REACHED : pathRuleReachOf(spec.parsed, id, a, pathFamilyOf(tree));
19
+ };
20
+ return {
21
+ spec,
22
+ behavior: spec.parsed.behavior,
23
+ rule: spec.ruleText,
24
+ tool: spec.parsed.tool,
25
+ pattern: parsePermissionRule(spec.ruleText).ruleContent ?? "",
26
+ faces: spec.faces ?? [],
27
+ reach,
28
+ reaches: (absolutePath) => reach(absolutePath).reach !== "not_reached",
29
+ ...(head !== undefined ? { head: spellingOfIdentity(head, family) } : {}),
30
+ ...(unanchored.length > 0 ? { unanchored } : {}),
31
+ };
32
+ });
33
+ }
34
+ export function policyAnchors(declared, install) {
35
+ const root = declared.root ?? install?.root;
36
+ const cwd = declared.cwd ?? root;
37
+ const home = declared.home ?? install?.home;
38
+ return { ...(root !== undefined ? { root } : {}), ...(cwd !== undefined ? { cwd } : {}), ...(home !== undefined ? { home } : {}) };
39
+ }
40
+ export function persistedRowAnchors(scope, chainTop) {
41
+ const root = scope.kind === "project" ? scope.root : chainTop.root;
42
+ return { ...(root !== undefined ? { root, cwd: root } : {}), ...(chainTop.home !== undefined ? { home: chainTop.home } : {}) };
43
+ }
44
+ export async function projectScopeIdentity(rules, canonicalRoot, leads) {
45
+ if (canonicalRoot === undefined)
46
+ return undefined;
47
+ const roots = rules.flatMap((r) => (r.scope.kind === "project" ? [{ head: r.scope.root }] : []));
48
+ const led = roots.length > 0 ? await leads.resolve(roots) : new Map();
49
+ return { canonicalRoot, canonicalOf: (root) => locationLeadsTo(root, led) };
50
+ }
51
+ export function createHeadLeads(leadsTo) {
52
+ const pending = new Map();
53
+ const settled = new Map();
54
+ return {
55
+ async resolve(rows) {
56
+ const parts = ruleHeadParts(rows).filter((p) => !settled.has(p));
57
+ await Promise.all(parts.map((part) => {
58
+ let p = pending.get(part);
59
+ if (p === undefined) {
60
+ p = leadsTo(part).catch(() => undefined);
61
+ pending.set(part, p);
62
+ }
63
+ return p.then((led) => settled.set(part, led));
64
+ }));
65
+ return settled;
66
+ },
67
+ };
68
+ }
69
+ const INSTALLED = new WeakMap();
70
+ const INSTALLATIONS = new WeakMap();
71
+ export function installPolicyAnchors(policy, anchors) {
72
+ if (policy === undefined)
73
+ return undefined;
74
+ const from = INSTALLED.get(policy)?.from ?? policy;
75
+ const rules = from.pathRules;
76
+ if (rules === undefined)
77
+ return from;
78
+ const key = JSON.stringify([anchors.root ?? null, anchors.cwd ?? null, anchors.home ?? null]);
79
+ const byAnchors = INSTALLATIONS.get(from) ?? new Map();
80
+ INSTALLATIONS.set(from, byAnchors);
81
+ const prior = byAnchors.get(key);
82
+ if (prior !== undefined)
83
+ return prior;
84
+ const seated = Object.freeze({ ...anchors });
85
+ const installed = {
86
+ ...carriedMembersOf(from),
87
+ pathRules: () => rules(seated),
88
+ check: (req, signal) => from.check({ ...req, pathRuleAnchors: seated }, signal),
89
+ };
90
+ INSTALLED.set(installed, { from, anchors: seated });
91
+ byAnchors.set(key, installed);
92
+ return installed;
93
+ }
94
+ export function installedFrom(policy) {
95
+ return policy === undefined ? undefined : INSTALLED.get(policy)?.from;
96
+ }
97
+ export function installationAnchorsOf(policy) {
98
+ return policy === undefined ? undefined : INSTALLED.get(policy)?.anchors;
99
+ }
@@ -76,7 +76,7 @@ import { type DenyFace } from "../tools/fs/read-deny.js";
76
76
  import type { AssertAllKeysHandled } from "./ask-origin.js";
77
77
  import { type ToolCallLike } from "./tool-registry.js";
78
78
  import { type PathFamily, type PathIdentity } from "../tools/fs/safety.js";
79
- import type { PathTargetReading } from "./effective-path-target.js";
79
+ import { type TargetViews } from "./path-rule-binding.js";
80
80
  /**
81
81
  * The closed set of rule BEHAVIORS — what a matching rule says about the call. The upstream vocabulary
82
82
  * verbatim (`allow` / `deny` / `ask`, the three settings lists), in PRECEDENCE order: when rules of more
@@ -131,7 +131,9 @@ export declare function readRowBehavior(v: unknown): RuleBehavior | undefined;
131
131
  * clears it. The org/hook/matchedAskRule immunities stay: those conjuncts live in the gate's
132
132
  * persisted-rule lane and do not loosen with this set. */
133
133
  export type PersistedRuleTool = string;
134
- /** The one shell tool the command grammar speaks for. */
134
+ /** The command grammar's ONE rule word — the spelling a shell command rule is written in (`Bash(<command>)`, upstream's).
135
+ * A rule word, not a tool list: the calls a command rule reaches are the SHELL FACE's — every catalog definition whose
136
+ * `command` the command grammar judges ({@link laneToolNamesOf}). */
135
137
  export declare const COMMAND_RULE_TOOL = "Bash";
136
138
  /** The one tool whose path family has an ALLOW form (the `Read(//abs-dir/**)` directory grant). */
137
139
  export declare const READ_RULE_TOOL = "Read";
@@ -144,7 +146,7 @@ export declare const READ_RULE_TOOL = "Read";
144
146
  * its tool's name as its reach (`Write(<path>)` is judged on the Write tool, a stricter reading the reference does not make).
145
147
  *
146
148
  * The two columns are enforced where each behavior can be: a deny row is exported to the engine's deny tables
147
- * (`ToolPolicy.pathDenyRules`, the person's rows at prepare), which refuse inside the tool, descend into a search's
149
+ * (`ToolPolicy.pathRules` bound where the policy is installed, the person's rows at prepare), which refuse inside the tool, descend into a search's
148
150
  * results and reach the shell's file commands; an ask is a question asked at the gate before the call runs, so it is
149
151
  * judged there, on the call's own path target only — it does not descend (a search rooted above the asked path is not
150
152
  * asked, and its results are not filtered).
@@ -167,8 +169,9 @@ export declare const NAME_RULE_FACES: Readonly<Record<string, {
167
169
  readonly params: readonly string[];
168
170
  }>>;
169
171
  /**
170
- * Which tools a rule may name — the model's tool set is DERIVED, never a hand-kept list: the shell tool
171
- * takes the COMMAND grammar; every tool the catalog declares a path target for (Read / Edit / Write /
172
+ * Which tools a rule may name — the model's tool set is DERIVED, never a hand-kept list: the command rule word
173
+ * ({@link COMMAND_RULE_TOOL}) takes the COMMAND grammar — the other shell-face tools take none of their own, their command is
174
+ * judged by the `Bash(…)` rules ({@link laneToolNamesOf}); every tool the catalog declares a path target for (Read / Edit / Write /
172
175
  * Glob / NotebookEdit / …, `pathTargetOf`) takes the PATH grammar; the tools of {@link NAME_RULE_FACES} take the NAME grammar.
173
176
  * A rule on any other tool has no content this model can judge and is refused (`unsupported.tool`). The tool set therefore
174
177
  * moves with the catalog's declarations, exactly as the protective fences' covered set does.
@@ -177,9 +180,12 @@ export declare function ruleToolGrammarOf(toolName: string): "command" | "path"
177
180
  /** The value a name-grammar tool's call carries in its name argument — the string every NAME rule is read against, the one a
178
181
  * `Skill` call is resolved by — or undefined (not a name-grammar tool; no such argument; not a string). */
179
182
  export declare function nameRuleSubjectOf(toolName: string, args: unknown): string | undefined;
180
- /** The names the persisted grammar judges a call under: every name the call answers to (the model's spelling, the wire
181
- * name, the other aliases that resolve to it) the grammar speaks for, so a stored `Bash(…)` or `Edit(…)` row reaches a
182
- * tool that answers to that name under another wire name, as a policy's rule does. Empty ⇒ the grammar speaks for none. */
183
+ /** The rule WORDS a call is judged under, once each: for every name the call answers to (the model's spelling, the wire
184
+ * name, the other aliases that resolve to it), the word its rules are written in — a shell-face name's is the command
185
+ * grammar's {@link COMMAND_RULE_TOOL} (a `Bash(<command>)` rule reaches `Monitor` as it reaches `Bash`), a path or name
186
+ * tool's its own name — so a stored `Bash(…)` or `Edit(…)` row reaches a tool that answers to that name under another wire
187
+ * name, as a policy's rule does. Every lane that judges a call by a rule's word reads it here: the policy compiler, the
188
+ * person's stored rows, the org partition, the allow lists. Empty ⇒ no grammar speaks for the call. */
183
189
  export declare function laneToolNamesOf(req: Pick<import("./tool-policy.js").ToolCallRequest, "toolName" | "face">): readonly string[];
184
190
  /**
185
191
  * Match forms. Four members, two of them one predicate:
@@ -600,7 +606,7 @@ export declare function isUsablePathBase(base: string | undefined, family: PathF
600
606
  * defect (a `~/` deny falling back to the engine host's home guarded the wrong directory; a `/…` deny with
601
607
  * no root reached nothing at all).
602
608
  */
603
- export declare function pathRuleReachOf(rule: Pick<PersistedRule, "match" | "command" | "behavior">, target: PathIdentity, bases: PathRuleBases): ProgramRunReachOutcome;
609
+ export declare function pathRuleReachOf(rule: Pick<PersistedRule, "match" | "command" | "behavior">, target: PathIdentity, bases: PathRuleBases, treeFamily?: PathFamily | undefined): ProgramRunReachOutcome;
604
610
  /**
605
611
  * Where a path rule's reach STARTS under the call's bases — its HEAD: the location the resolved pattern names before
606
612
  * its first segment carrying `*` (the whole path when none does), or a directory rule's directory. Everything the rule
@@ -772,7 +778,7 @@ export declare function lexicalNormalAbsolutePathOf(path: string, family: PathFa
772
778
  * there is nothing here for it to clear). As a DENY/ASK the same containment refuses or questions every
773
779
  * path under the directory, on whichever path tool the rule names.
774
780
  */
775
- export declare function directoryRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, path: PathIdentity, bases: PathRuleBases): boolean;
781
+ export declare function directoryRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, path: PathIdentity, bases: PathRuleBases, treeFamily?: PathFamily | undefined): boolean;
776
782
  /** The identity of a directory SPELLING (the cd resolver's `/`-rooted output) under the call's tree, for the
777
783
  * consumers that hand {@link directoryRuleAdmits} a spelling; a rule minted from it stores this form. */
778
784
  export declare function directoryIdentityOf(spelling: string, bases: PathRuleBases): PathIdentity | undefined;
@@ -785,7 +791,18 @@ export declare function directoryIdentityOf(spelling: string, bases: PathRuleBas
785
791
  * r3-② cwd posture), never inferred from the process. ONE function on purpose: this is the scope
786
792
  * conjunct of the single eligibility predicate ({@link eligibleContext}), and a sister predicate
787
793
  * would be the second fact source the 375 §5.1 discipline exists to forbid. */
788
- export declare function scopeCoversCwd(scope: RuleScope, cwd: string | undefined, sessionId?: string): boolean;
794
+ export declare function scopeCoversCwd(scope: RuleScope, cwd: string | undefined, sessionId?: string, identity?: ScopeRootIdentity): boolean;
795
+ /**
796
+ * A leg's root as an IDENTITY for project coverage: its canonical spelling, and the leg's resolution of a project row's
797
+ * scope root to the location it leads to. A DENY or ASK project row written under one spelling of a directory (a link the
798
+ * host spelled, a firmlink) covers a leg rooted at another spelling of the same directory: the row applies by where the two
799
+ * roots ARE, not only by how they are spelled. An allow row never reads it ({@link spelledScope}). A caller that holds no
800
+ * resolution (no leg, no environment) compares the spellings alone.
801
+ */
802
+ export interface ScopeRootIdentity {
803
+ readonly canonicalRoot: string;
804
+ readonly canonicalOf: (root: string) => string | undefined;
805
+ }
789
806
  /** Is this rule live — i.e. does it still carry at least one add? Deleted adds are removed by the store
790
807
  * when tombstones are applied, so a rule with an empty `adds` is a rule that no longer exists. */
791
808
  export declare function isRuleLive(rule: PersistedRule): boolean;
@@ -815,6 +832,7 @@ export declare function eligibleContext(rule: {
815
832
  tool: string;
816
833
  cwd: string | undefined;
817
834
  sessionId?: string;
835
+ scopeIdentity?: ScopeRootIdentity;
818
836
  }): boolean;
819
837
  /**
820
838
  * design/375 §5.1 — MAY this PERSISTED rule participate in admitting this call at all? The liveness
@@ -834,7 +852,16 @@ export declare function eligiblePersisted(rule: PersistedRule, call: {
834
852
  tool: string;
835
853
  cwd: string | undefined;
836
854
  sessionId?: string;
855
+ scopeIdentity?: ScopeRootIdentity;
837
856
  }, behavior: RuleBehavior): boolean;
857
+ /**
858
+ * The call context an ALLOW row's scope is judged in: the spellings alone. A deny or ask row applies wherever its project's
859
+ * directory is (its scope root resolved where it leads — more rows, only ever more protection); an allow row applies only
860
+ * where its scope root is SPELLED, so a link someone can re-point never carries a standing approval to another directory.
861
+ */
862
+ export declare function spelledScope<C extends {
863
+ scopeIdentity?: ScopeRootIdentity;
864
+ }>(call: C): Omit<C, "scopeIdentity">;
838
865
  /**
839
866
  * Find the rules that admit this command for a task in `cwd`, or `undefined`.
840
867
  *
@@ -914,19 +941,23 @@ export declare function adjudicatePersistedRules(rules: readonly PersistedRule[]
914
941
  cwd: string | undefined;
915
942
  sessionId?: string;
916
943
  execCwd?: string;
944
+ scopeIdentity?: ScopeRootIdentity;
917
945
  }): PersistedRuleVerdict | undefined;
918
946
  /**
919
947
  * The path-family sibling of {@link adjudicatePersistedRules}: the same precedence walk over the rules
920
- * of one path-targeting tool, judged on the call's resolved target path.
948
+ * of one path-targeting tool, judged on the call's target views by the one judgment every path rule meets
949
+ * ({@link pathRowsReach}: rows outer, views inner).
950
+ *
951
+ * TWO readings, because the two directions read the call's path slot differently (design/388 B6/B17): the
952
+ * tightening arms judge the PROTECTIVE reading's views (`views.tighten` — the object face's declaration, the catalog's
953
+ * for the name as the floor: reading the catalog for an undeclared same-name tool can only tighten a deny), every row
954
+ * also where its head leads (`leads`, the judging leg's resolution); the allow arm judges the DECLARED reading's views
955
+ * (`views.allow` — the face's OWN declaration only: a same-name tool that declares no path target is unconfirmable, and
956
+ * no grant admits it) with the directory rows, for the Read tool ONLY — a directory allow is a standing yes to reads,
957
+ * never to a write — and admits only when every view lies under the directory. Either reading may be absent (no
958
+ * resolvable path in that reading); the arm that needs it then speaks for nothing.
921
959
  *
922
- * TWO targets, because the two directions read the call's path slot differently (design/388 B6/B17): the
923
- * tightening arms judge the PROTECTIVE target (`protectivePathTargetOf` — the object face's declaration,
924
- * the catalog's for the name as the floor: reading the catalog for an undeclared same-name tool can only
925
- * tighten a deny) with {@link pathRuleReaches}; the allow arm judges the DECLARED target
926
- * (`declaredPathTargetOf` — the face's OWN declaration only: a same-name tool that declares no path
927
- * target is unconfirmable, and no grant admits it) with {@link directoryRuleAdmits}, and for the Read tool
928
- * ONLY — a directory allow is a standing yes to reads, never to a write. Either target may be absent
929
- * (no resolvable path in that reading); the arm that needs it then speaks for nothing.
960
+ * A row is anchored by `anchorsOf` (the person's rows: a project row at its scope root, the rest at the chain's top leg).
930
961
  *
931
962
  * `call.face` is the face the call's protective target is on (`read` / `write`): an ASK row speaks for the call when it
932
963
  * is the call's tool's row, or when {@link PATH_RULE_FACES} puts that face in the row's ask column (a `Read(<path>)` ask
@@ -938,10 +969,11 @@ export declare function adjudicatePersistedPathRules(rules: readonly PersistedRu
938
969
  cwd: string | undefined;
939
970
  sessionId?: string;
940
971
  face?: DenyFace | undefined;
941
- }, targets: {
942
- readonly tighten?: PathTargetReading;
943
- readonly allow?: PathTargetReading;
944
- }, bases: PathRuleBases): PersistedRuleVerdict | undefined;
972
+ scopeIdentity?: ScopeRootIdentity;
973
+ }, views: {
974
+ readonly tighten?: TargetViews;
975
+ readonly allow?: TargetViews;
976
+ }, anchorsOf: (rule: PersistedRule) => PathRuleBases, leads: ReadonlyMap<string, string | undefined>): PersistedRuleVerdict | undefined;
945
977
  /**
946
978
  * The name-family sibling of {@link adjudicatePersistedRules}: the same precedence walk over the rules of one name tool
947
979
  * ({@link NAME_RULE_FACES}), judged on the one string the call names (`nameRuleSubjectOf`). A rule of the tool speaks for the call
@@ -955,6 +987,7 @@ export declare function adjudicatePersistedNameRules(rules: readonly PersistedRu
955
987
  name: string | undefined;
956
988
  cwd: string | undefined;
957
989
  sessionId?: string;
990
+ scopeIdentity?: ScopeRootIdentity;
958
991
  }): PersistedRuleVerdict | undefined;
959
992
  /**
960
993
  * The ONE offer a name tool's approval card carries: the exact rule for the name the call carries (`Skill(<name>)`, the reference's
@@ -1009,6 +1042,8 @@ export declare function segmentCoverageOf(command: string, rules: {
1009
1042
  /** The relative-cd resolution base (see {@link findAdmittingRule}'s member of the same name) —
1010
1043
  * defaults to `cwd`; scope/eligibility stays `cwd`. */
1011
1044
  execCwd?: string;
1045
+ /** The leg's root as an identity for project coverage ({@link ScopeRootIdentity}). */
1046
+ scopeIdentity?: ScopeRootIdentity;
1012
1047
  }): readonly SegmentCoverage[] | undefined;
1013
1048
  /** One row of a per-segment coverage table (see {@link segmentCoverageOf}): the FOLDED, trimmed
1014
1049
  * segment text (a display/correlation seat, never adjudication input) and whether an eligible rule