@sema-agent/core 7.54.1 → 7.55.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.
- package/CHANGELOG.md +437 -0
- package/dist/agents/agent-transcript-tool.js +2 -1
- package/dist/agents/builtin-agents.d.ts +12 -14
- package/dist/agents/cc-peer-directory.d.ts +36 -0
- package/dist/agents/cc-peer-directory.js +138 -0
- package/dist/agents/cc-peer-wire.d.ts +105 -0
- package/dist/agents/cc-peer-wire.js +98 -0
- package/dist/agents/cc-uds-peer-channel.d.ts +40 -0
- package/dist/agents/cc-uds-peer-channel.js +457 -0
- package/dist/agents/cross-session-envelope.d.ts +4 -1
- package/dist/agents/cross-session-envelope.js +8 -1
- package/dist/agents/cross-session-judge.d.ts +2 -2
- package/dist/agents/list-agents-tool.js +2 -1
- package/dist/agents/observer.d.ts +4 -5
- package/dist/agents/observer.js +2 -1
- package/dist/agents/peer-admission.d.ts +6 -0
- package/dist/agents/peer-admission.js +4 -1
- package/dist/agents/peer-directory.d.ts +43 -9
- package/dist/agents/peer-directory.js +66 -23
- package/dist/agents/peer-notices.d.ts +1 -1
- package/dist/agents/peer-session-drain.d.ts +3 -1
- package/dist/agents/peer-session-drain.js +1 -0
- package/dist/agents/read-inbox-tool.js +2 -1
- package/dist/agents/retain-ledger.d.ts +1 -1
- package/dist/agents/roster-store.d.ts +1 -1
- package/dist/agents/send-message-tool.d.ts +10 -0
- package/dist/agents/send-message-tool.js +34 -5
- package/dist/agents/subagent.d.ts +34 -79
- package/dist/agents/subagent.js +45 -46
- package/dist/agents/tool-filter.d.ts +29 -46
- package/dist/agents/tool-filter.js +8 -31
- package/dist/agents/verify.d.ts +3 -3
- package/dist/brain/anthropic.js +14 -9
- package/dist/brain/errors.d.ts +15 -0
- package/dist/brain/errors.js +10 -0
- package/dist/brain/model-presets.d.ts +4 -4
- package/dist/brain/open-responses.js +15 -10
- package/dist/brain/openai.js +17 -12
- package/dist/brain/provider-dialect.d.ts +39 -3
- package/dist/brain/provider-dialect.js +28 -0
- package/dist/brain/reasoning.d.ts +1 -1
- package/dist/brain/request-params.d.ts +55 -5
- package/dist/brain/request-params.js +35 -3
- package/dist/brain/retry.d.ts +1 -1
- package/dist/brain/route-adjudicator.d.ts +15 -0
- package/dist/brain/route-adjudicator.js +15 -0
- package/dist/brain/stream-engine.js +6 -5
- package/dist/brain/stream-shared.d.ts +1 -1
- package/dist/core/advisor-verdict.d.ts +120 -0
- package/dist/core/advisor-verdict.js +92 -0
- package/dist/core/agent-definition.d.ts +7 -8
- package/dist/core/allow-entry.d.ts +49 -23
- package/dist/core/allow-entry.js +57 -32
- package/dist/core/allow-rule-shadow.d.ts +7 -0
- package/dist/core/approval-object.d.ts +2 -1
- package/dist/core/approval-object.js +6 -6
- package/dist/core/ask-origin.d.ts +63 -11
- package/dist/core/ask-origin.js +27 -5
- package/dist/core/ask-question.js +10 -4
- package/dist/core/auto-compaction.d.ts +11 -8
- package/dist/core/auto-compaction.js +4 -2
- package/dist/core/auto-mode-arming.d.ts +2 -2
- package/dist/core/auto-mode-defaults.d.ts +8 -8
- package/dist/core/auto-mode-prompt-assets.d.ts +3 -3
- package/dist/core/auto-mode-prompt.d.ts +1 -1
- package/dist/core/auto-mode.d.ts +17 -9
- package/dist/core/auto-mode.js +9 -6
- package/dist/core/background-agent-store.d.ts +3 -3
- package/dist/core/canonical-json.d.ts +1 -1
- package/dist/core/canonical-json.js +2 -2
- package/dist/core/checkpoint-requirements.d.ts +6 -3
- package/dist/core/checkpoint-requirements.js +4 -14
- package/dist/core/checkpoint-store.d.ts +80 -51
- package/dist/core/checkpoint-store.js +56 -74
- package/dist/core/child-clamp-seats.d.ts +6 -0
- package/dist/core/child-clamp-seats.js +3 -16
- package/dist/core/compliance.d.ts +2 -2
- package/dist/core/context-edit.d.ts +6 -7
- package/dist/core/cross-boundary-taint.d.ts +9 -3
- package/dist/core/cross-boundary-taint.js +5 -3
- package/dist/core/decision-receipt.js +2 -2
- package/dist/core/effective-path-target.d.ts +1 -1
- package/dist/core/engine-notice.d.ts +19 -5
- package/dist/core/exec-output-tail.d.ts +1 -1
- package/dist/core/file-history-retention.d.ts +1 -1
- package/dist/core/file-history-store.d.ts +59 -9
- package/dist/core/file-history-store.js +35 -17
- package/dist/core/gate-fold.js +3 -2
- package/dist/core/gate-lanes.js +12 -8
- package/dist/core/gate-outcome.d.ts +17 -4
- package/dist/core/gate-outcome.js +30 -0
- package/dist/core/governance-codes.d.ts +13 -3
- package/dist/core/governance-codes.js +10 -0
- package/dist/core/hands-band.d.ts +1 -1
- package/dist/core/hooks.d.ts +4 -4
- package/dist/core/host-target.d.ts +49 -0
- package/dist/core/host-target.js +59 -0
- package/dist/core/image-downsample.d.ts +1 -1
- package/dist/core/lsp-diagnostics.d.ts +4 -4
- package/dist/core/lsp.d.ts +3 -3
- package/dist/core/mailbox-store.d.ts +4 -0
- package/dist/core/mcp-connection-pool.d.ts +18 -2
- package/dist/core/mcp-connection-pool.js +53 -6
- package/dist/core/mcp-failure.d.ts +23 -4
- package/dist/core/mcp-failure.js +26 -11
- package/dist/core/mcp-listing-drift.d.ts +10 -0
- package/dist/core/mcp-listing-drift.js +6 -0
- package/dist/core/mcp-server-link.d.ts +173 -0
- package/dist/core/mcp-server-link.js +127 -0
- package/dist/core/mcp-server-spec.d.ts +4 -2
- package/dist/core/mcp-session-end.d.ts +26 -0
- package/dist/core/mcp-session-end.js +29 -0
- package/dist/core/mcp-startup-request.d.ts +17 -0
- package/dist/core/mcp-startup-request.js +17 -0
- package/dist/core/mcp.d.ts +13 -13
- package/dist/core/mcp.js +187 -162
- package/dist/core/media-byte-cap.d.ts +1 -1
- package/dist/core/memory-engine/engine.d.ts +9 -9
- package/dist/core/memory-engine/provenance-wording.d.ts +1 -1
- package/dist/core/memory-engine/tools.js +4 -1
- package/dist/core/mode-policy.d.ts +1 -1
- package/dist/core/namespaced-rule-name.d.ts +33 -7
- package/dist/core/namespaced-rule-name.js +39 -25
- package/dist/core/output-style.d.ts +1 -1
- package/dist/core/param-rule-reach.d.ts +15 -0
- package/dist/core/param-rule-reach.js +30 -0
- package/dist/core/parent-spec-seat.d.ts +8 -1
- package/dist/core/parent-spec-seat.js +4 -0
- package/dist/core/pause-registry.d.ts +16 -5
- package/dist/core/pause-registry.js +6 -2
- package/dist/core/peer-channel.d.ts +70 -0
- package/dist/core/peer-channel.js +1 -0
- package/dist/core/permission-rule-consent.d.ts +11 -5
- package/dist/core/permission-rule-consent.js +37 -30
- package/dist/core/permission-rule-model.d.ts +233 -64
- package/dist/core/permission-rule-model.js +384 -85
- package/dist/core/permission-rule-provider.d.ts +2 -1
- package/dist/core/permission-rule-store.d.ts +20 -25
- package/dist/core/permission-rule-store.js +1 -4
- package/dist/core/permission-rule-sync.d.ts +11 -2
- package/dist/core/permission-rule-sync.js +8 -1
- package/dist/core/permission-rules.d.ts +49 -11
- package/dist/core/permission-rules.js +123 -89
- package/dist/core/persisted-rule-arms.d.ts +4 -2
- package/dist/core/present-plan-tool.js +3 -1
- package/dist/core/protocol-naming.d.ts +2 -2
- package/dist/core/read-only-shell-table.d.ts +1 -1
- package/dist/core/read-only-shell.d.ts +1 -1
- package/dist/core/roles.d.ts +6 -2
- package/dist/core/roles.js +45 -44
- package/dist/core/rule-offer-read.d.ts +53 -0
- package/dist/core/rule-offer-read.js +146 -0
- package/dist/core/rule-owner.d.ts +46 -0
- package/dist/core/rule-owner.js +28 -0
- package/dist/core/runner/advisor-instructions.d.ts +70 -0
- package/dist/core/runner/advisor-instructions.js +61 -0
- package/dist/core/runner/advisor-tool.d.ts +33 -0
- package/dist/core/runner/advisor-tool.js +112 -0
- package/dist/core/runner/announce-once-ledger.d.ts +4 -26
- package/dist/core/runner/announce-once-ledger.js +14 -46
- package/dist/core/runner/approval-feedback.js +6 -3
- package/dist/core/runner/assemble-result.d.ts +1 -1
- package/dist/core/runner/caller-roster.d.ts +7 -0
- package/dist/core/runner/caller-roster.js +11 -0
- package/dist/core/runner/classifier-ladder.d.ts +27 -5
- package/dist/core/runner/classifier-ladder.js +12 -11
- package/dist/core/runner/compaction-restate.d.ts +10 -6
- package/dist/core/runner/compaction-restate.js +9 -3
- package/dist/core/runner/contracts.d.ts +13 -9
- package/dist/core/runner/conversation-segment.d.ts +28 -0
- package/dist/core/runner/conversation-segment.js +42 -0
- package/dist/core/runner/cross-boundary-lane.d.ts +6 -5
- package/dist/core/runner/cross-boundary-lane.js +26 -15
- package/dist/core/runner/decide-continuation.d.ts +7 -4
- package/dist/core/runner/decide-continuation.js +2 -2
- package/dist/core/runner/denial-limit-arms.d.ts +2 -2
- package/dist/core/runner/derived-route-fallback.d.ts +24 -24
- package/dist/core/runner/derived-route-fallback.js +9 -7
- package/dist/core/runner/gate-exit.d.ts +6 -0
- package/dist/core/runner/gate-exit.js +7 -2
- package/dist/core/runner/initial-run-state.js +1 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -2
- package/dist/core/runner/permission-rule-lanes.js +14 -7
- package/dist/core/runner/person-path-rows.js +1 -1
- package/dist/core/runner/prepare-advisor.d.ts +63 -0
- package/dist/core/runner/prepare-advisor.js +77 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-config-doors.d.ts +6 -4
- package/dist/core/runner/prepare-config-doors.js +18 -12
- package/dist/core/runner/prepare-delegation-surface.d.ts +2 -2
- package/dist/core/runner/prepare-delegation-surface.js +1 -1
- package/dist/core/runner/prepare-file-history.js +39 -39
- package/dist/core/runner/prepare-gate-stations.d.ts +1 -4
- package/dist/core/runner/prepare-gate-stations.js +2 -7
- package/dist/core/runner/prepare-inherited-gate.d.ts +5 -4
- package/dist/core/runner/prepare-inherited-gate.js +3 -3
- package/dist/core/runner/prepare-park-ask.js +7 -7
- package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
- package/dist/core/runner/prepare-policy-chain.js +4 -10
- package/dist/core/runner/prepare-project-context.d.ts +5 -2
- package/dist/core/runner/prepare-project-context.js +6 -3
- package/dist/core/runner/prepare-prompt-inputs.js +1 -1
- package/dist/core/runner/prepare-protocol-tools.d.ts +2 -2
- package/dist/core/runner/prepare-protocol-tools.js +3 -2
- package/dist/core/runner/prepare-safety-scan.d.ts +9 -12
- package/dist/core/runner/prepare-safety-scan.js +8 -6
- package/dist/core/runner/prepare-task.js +15 -18
- package/dist/core/runner/prepare-tool-disclosure-mount.js +6 -4
- package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -3
- package/dist/core/runner/prepare-wiring-manifest.js +2 -2
- package/dist/core/runner/resume-policy-outcome.js +1 -1
- package/dist/core/runner/retired-runner-deps.js +1 -1
- package/dist/core/runner/run-attachment-seats.js +0 -1
- package/dist/core/runner/run-compaction-machinery.d.ts +1 -1
- package/dist/core/runner/run-compaction-machinery.js +2 -12
- package/dist/core/runner/run-git-lane.d.ts +1 -0
- package/dist/core/runner/run-harness-handlers.js +2 -3
- package/dist/core/runner/run-leg.js +13 -0
- package/dist/core/runner/run-settle-and-teardown.js +1 -1
- package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
- package/dist/core/runner/run-turn-boundary.d.ts +1 -0
- package/dist/core/runner/run-turn-boundary.js +14 -12
- package/dist/core/runner/session-rule-policy.d.ts +22 -21
- package/dist/core/runner/session-rule-policy.js +13 -28
- package/dist/core/runner/side-call-account.d.ts +32 -8
- package/dist/core/runner/side-call-account.js +23 -1
- package/dist/core/runner/strict-output-schema.d.ts +1 -1
- package/dist/core/runner/sub-agent-deny-lists.js +4 -3
- package/dist/core/runner/synthetic-tools.d.ts +10 -13
- package/dist/core/runner/synthetic-tools.js +4 -2
- package/dist/core/runner/tool-disclosure.js +2 -1
- package/dist/core/runner/tool-face-overlay.d.ts +2 -1
- package/dist/core/runner/tool-face-overlay.js +17 -1
- package/dist/core/runner/tool-progress-lane.d.ts +2 -2
- package/dist/core/runner/tool-turn-repair-seat.d.ts +3 -2
- package/dist/core/runner/turn-attachments.d.ts +60 -67
- package/dist/core/runner-deps.d.ts +37 -37
- package/dist/core/runtime-caps.d.ts +4 -4
- package/dist/core/safety-merge-corpus.js +17 -1
- package/dist/core/same-plain-value.d.ts +40 -0
- package/dist/core/same-plain-value.js +49 -0
- package/dist/core/scheduler.d.ts +1 -1
- package/dist/core/sensitive-path-policy.d.ts +1 -1
- package/dist/core/sensitive-path-policy.js +1 -1
- package/dist/core/session-policy-store.d.ts +77 -22
- package/dist/core/session-policy-store.js +56 -14
- package/dist/core/session-reconcile.d.ts +2 -2
- package/dist/core/session-store.d.ts +80 -30
- package/dist/core/session-store.js +118 -76
- package/dist/core/session.d.ts +24 -46
- package/dist/core/shared-memory/tools.js +3 -1
- package/dist/core/shared-memory/types.d.ts +1 -1
- package/dist/core/side-query.d.ts +10 -0
- package/dist/core/side-query.js +5 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +29 -2
- package/dist/core/store-contracts/file-history-store-contract.js +43 -3
- package/dist/core/store-contracts/session-policy-store-contract.d.ts +7 -0
- package/dist/core/store-contracts/session-policy-store-contract.js +130 -0
- package/dist/core/store-contracts/session-repo-contract.js +125 -0
- package/dist/core/store-contracts/session-store-contract.d.ts +4 -7
- package/dist/core/store-contracts/session-store-contract.js +215 -41
- package/dist/core/store-contracts/workflow-journal-store-contract.js +26 -1
- package/dist/core/surrogate-safe-slice.d.ts +3 -3
- package/dist/core/task-event.d.ts +10 -4
- package/dist/core/task-notification.d.ts +3 -3
- package/dist/core/task-registry-agent.d.ts +4 -4
- package/dist/core/task-registry-shared.d.ts +4 -4
- package/dist/core/task-registry-shared.js +1 -0
- package/dist/core/task-registry.d.ts +4 -4
- package/dist/core/task-registry.js +3 -1
- package/dist/core/task-result.d.ts +3 -3
- package/dist/core/task-spec.d.ts +52 -52
- package/dist/core/task-stream.d.ts +6 -6
- package/dist/core/task-tool-shape.d.ts +4 -4
- package/dist/core/tighten-task-spec.d.ts +1 -1
- package/dist/core/tool-catalog-entries.d.ts +19 -2
- package/dist/core/tool-catalog-entries.js +57 -42
- package/dist/core/tool-conformance.js +1 -0
- package/dist/core/tool-errors.d.ts +2 -2
- package/dist/core/tool-face-reader.js +5 -3
- package/dist/core/tool-face.d.ts +21 -2
- package/dist/core/tool-face.js +1 -1
- package/dist/core/tool-model-gate.d.ts +3 -3
- package/dist/core/tool-name-aliases.d.ts +29 -8
- package/dist/core/tool-name-aliases.js +27 -3
- package/dist/core/tool-name-readings.d.ts +5 -8
- package/dist/core/tool-name-readings.js +1 -2
- package/dist/core/tool-policy.d.ts +24 -5
- package/dist/core/tool-policy.js +34 -10
- package/dist/core/tool-registry.d.ts +14 -7
- package/dist/core/tool-registry.js +17 -7
- package/dist/core/tool-result-store.js +2 -1
- package/dist/core/tool-roster.d.ts +47 -18
- package/dist/core/tool-roster.js +53 -32
- package/dist/core/tool-spec-members.js +1 -1
- package/dist/core/tool-spec.d.ts +35 -21
- package/dist/core/tools.js +3 -2
- package/dist/core/trace.d.ts +4 -0
- package/dist/core/untrusted-egress.d.ts +1 -1
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/core/warm-resume.d.ts +1 -1
- package/dist/core/wiring-manifest.d.ts +49 -0
- package/dist/core/wiring-manifest.js +13 -5
- package/dist/core/workflow-journal-store.d.ts +62 -0
- package/dist/core/workflow-journal-store.js +77 -1
- package/dist/core/workflow-run-store.d.ts +6 -0
- package/dist/core/workflow-run-store.js +2 -0
- package/dist/engine/compaction/compaction.d.ts +9 -9
- package/dist/engine/execution-env/node-execution-env.d.ts +19 -5
- package/dist/engine/execution-env/node-execution-env.js +43 -22
- package/dist/engine/harness/agent-harness.d.ts +14 -8
- package/dist/engine/harness/agent-harness.js +33 -26
- package/dist/engine/harness/types.d.ts +29 -7
- package/dist/engine/harness/types.js +12 -1
- package/dist/engine/llm/types.d.ts +9 -9
- package/dist/engine/llm/validation.d.ts +4 -1
- package/dist/engine/llm/validation.js +13 -1
- package/dist/engine/loop/agent-loop.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +10 -8
- package/dist/engine/loop/types.d.ts +15 -13
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
- package/dist/engine/session/memory-repo.d.ts +10 -11
- package/dist/engine/session/memory-repo.js +13 -8
- package/dist/engine/session/parent-chain-guard.d.ts +3 -2
- package/dist/engine/session/parent-chain-guard.js +1 -1
- package/dist/engine/session/repo-utils.d.ts +31 -3
- package/dist/engine/session/repo-utils.js +42 -5
- package/dist/index.d.ts +27 -15
- package/dist/index.js +21 -11
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/internal/harness.d.ts +3 -3
- package/dist/internal/harness.js +2 -2
- package/dist/internal/llm.d.ts +1 -1
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/goal.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +2 -2
- package/dist/orchestration/run-workflow-tool.js +5 -3
- package/dist/orchestration/workflow-size-guideline.d.ts +4 -4
- package/dist/orchestration/workflow-types.d.ts +18 -0
- package/dist/orchestration/workflow-types.js +6 -0
- package/dist/orchestration/workflow.d.ts +2 -13
- package/dist/orchestration/workflow.js +21 -31
- package/dist/prompt-assembly/event-registry.js +1 -0
- package/dist/prompt-assembly/tool-catalog.d.ts +3 -0
- package/dist/prompt-assembly/tool-catalog.js +3 -0
- package/dist/prompts/default.d.ts +14 -14
- package/dist/scenarios/full-body.d.ts +4 -4
- package/dist/stores/file/adoption/adopt.d.ts +4 -3
- package/dist/stores/file/adoption/adopt.js +24 -31
- package/dist/stores/file/adoption/marker.d.ts +6 -13
- package/dist/stores/file/adoption/marker.js +6 -15
- package/dist/stores/file/checkpoint-store.d.ts +3 -0
- package/dist/stores/file/checkpoint-store.js +9 -3
- package/dist/stores/file/file-history-store.d.ts +20 -19
- package/dist/stores/file/file-history-store.js +152 -135
- package/dist/stores/file/fs-atomic.d.ts +15 -0
- package/dist/stores/file/fs-atomic.js +89 -14
- package/dist/stores/file/index.d.ts +1 -1
- package/dist/stores/file/mailbox-store.d.ts +3 -0
- package/dist/stores/file/mailbox-store.js +3 -1
- package/dist/stores/file/permission-rule-adopt.d.ts +1 -1
- package/dist/stores/file/permission-rule-adopt.js +15 -16
- package/dist/stores/file/permission-rule-store.d.ts +18 -2
- package/dist/stores/file/permission-rule-store.js +21 -14
- package/dist/stores/file/session-policy-store.d.ts +3 -3
- package/dist/stores/file/session-store.d.ts +26 -11
- package/dist/stores/file/session-store.js +77 -40
- package/dist/stores/file/workflow-journal-store.d.ts +10 -4
- package/dist/stores/file/workflow-journal-store.js +28 -13
- package/dist/tools/artifact/artifact-text.d.ts +1 -1
- package/dist/tools/artifact/artifact-tool.js +2 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +3 -3
- package/dist/tools/fs/fs-bash.d.ts +2 -2
- package/dist/tools/fs/fs-bash.js +3 -1
- package/dist/tools/fs/fs-shared.d.ts +8 -8
- package/dist/tools/fs/gh-rate-limit.d.ts +1 -1
- package/dist/tools/fs/notebook.d.ts +2 -2
- package/dist/tools/fs/read-deny.d.ts +29 -7
- package/dist/tools/fs/read-deny.js +63 -22
- package/dist/tools/fs/removal-targets.d.ts +1 -1
- package/dist/tools/fs/safety.d.ts +9 -9
- package/dist/tools/fs/search.d.ts +6 -6
- package/dist/tools/fs/shell-read-check.js +6 -1
- package/dist/tools/fs/shell-read-judge.d.ts +4 -0
- package/dist/tools/fs/shell-read-judge.js +5 -2
- package/dist/tools/loop-tick.d.ts +2 -2
- package/dist/tools/scheduler-tools.js +5 -1
- package/dist/tools/task-list.d.ts +7 -7
- package/dist/tools/task-list.js +5 -1
- package/dist/tools/todo.js +2 -1
- package/dist/tools/web.d.ts +1 -1
- package/dist/tools/web.js +40 -37
- package/dist/tools/worktree.d.ts +3 -3
- package/dist/tools/worktree.js +3 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +5756 -5638
- package/wire-changes/core@7.55.0.json +3318 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +0 -39
- package/dist/core/store-contracts/session-store-incarnation-contract.js +0 -95
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { shellWordsOf } from "../shell/shell-words.js";
|
|
3
|
+
import { lexShell } from "../shell/lex.js";
|
|
4
|
+
import { readProgramPosition } from "../tools/fs/bash-program-position.js";
|
|
3
5
|
import { parsePermissionRule } from "./permission-rule-syntax.js";
|
|
4
6
|
import { compileReadDeny } from "../tools/fs/read-deny.js";
|
|
5
7
|
import { carriesShellRedirection, parseLeadingCommandName, splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier.js";
|
|
6
8
|
import { INLINE_TRUNCATION_MARK, inlineUntrusted } from "./untrusted-text.js";
|
|
7
|
-
import { isShellFaceToolName, pathTargetOf, protectivePathTargetOf } from "./tool-registry.js";
|
|
9
|
+
import { catalogRuleFaceOf, hostTargetOf, isShellFaceToolName, pathTargetOf, protectivePathTargetOf } from "./tool-registry.js";
|
|
10
|
+
import { HOST_UNREADABLE_SENTENCE, hostOfSpelling, isAddressHost } from "./host-target.js";
|
|
8
11
|
import { heldValues } from "./tool-name-readings.js";
|
|
9
12
|
import { carriesDotSegment, isAbsoluteForFamily, isIdentityDir, isPathIdentity, isShellRootedSpellingUnmapped, isUncIdentity, pathFamilyOf, pathIdentityOf } from "../tools/fs/safety.js";
|
|
10
13
|
import { bindPathRules, judgeRuleRows, rowsWhereTheyLead } from "./path-rule-binding.js";
|
|
11
14
|
import { readShellCommand } from "./shell-run-candidates.js";
|
|
12
15
|
import { lineDescriptorsOfText, readOnlyLineObjection, readOnlyShellVerdict } from "./read-only-shell.js";
|
|
13
16
|
import { ARTIFACT_ACTIONS } from "./artifact-host.js";
|
|
14
|
-
import { ARTIFACT_TOOL_NAME } from "./tool-catalog-entries.js";
|
|
17
|
+
import { ARTIFACT_TOOL_NAME, TOOL_CATALOG_ENTRIES } from "./tool-catalog-entries.js";
|
|
18
|
+
import { paramRuleReaches } from "./param-rule-reach.js";
|
|
15
19
|
export const RULE_BEHAVIORS = Object.freeze(["deny", "ask", "allow"]);
|
|
16
20
|
const RULE_BEHAVIOR_SET = new Set(RULE_BEHAVIORS);
|
|
17
21
|
export function isRuleBehavior(v) {
|
|
@@ -43,64 +47,121 @@ export function callPathFaceOf(call) {
|
|
|
43
47
|
export function faceRowsSpeakFor(face) {
|
|
44
48
|
return face !== undefined && Object.values(PATH_RULE_FACES).some((columns) => columns.deny?.includes(face) === true || columns.ask?.includes(face) === true);
|
|
45
49
|
}
|
|
50
|
+
function catalogVocabularyOf(tool) {
|
|
51
|
+
const params = catalogRuleFaceOf(tool)?.params;
|
|
52
|
+
if (params === undefined)
|
|
53
|
+
throw new Error(`permission rules: the catalog declares no rule vocabulary for ${JSON.stringify(tool)}`);
|
|
54
|
+
return params;
|
|
55
|
+
}
|
|
56
|
+
const DELEGATION_RULE_TOOL = (() => {
|
|
57
|
+
const row = TOOL_CATALOG_ENTRIES.find((e) => e.id === "agent");
|
|
58
|
+
if (row === undefined)
|
|
59
|
+
throw new Error("permission rules: the catalog carries no delegation tool row (`agent`)");
|
|
60
|
+
return row.name;
|
|
61
|
+
})();
|
|
46
62
|
export const NAME_RULE_FACES = Object.freeze({
|
|
47
|
-
Skill: Object.freeze({ param: "skill", params: Object.freeze(
|
|
63
|
+
Skill: Object.freeze({ param: "skill", params: Object.freeze(catalogVocabularyOf("Skill")) }),
|
|
48
64
|
[ARTIFACT_TOOL_NAME]: Object.freeze({ param: "action", params: Object.freeze(["action"]), words: ARTIFACT_ACTIONS }),
|
|
65
|
+
[DELEGATION_RULE_TOOL]: Object.freeze({ param: "subagent_type", params: Object.freeze(["subagent_type"]), values: "mounted" }),
|
|
49
66
|
});
|
|
67
|
+
function nameRuleFaceOf(tool) {
|
|
68
|
+
return Object.hasOwn(NAME_RULE_FACES, tool) ? NAME_RULE_FACES[tool] : undefined;
|
|
69
|
+
}
|
|
50
70
|
function keyedValueParamOf(tool) {
|
|
51
|
-
const face =
|
|
71
|
+
const face = nameRuleFaceOf(tool);
|
|
52
72
|
return face?.words !== undefined ? face.param : undefined;
|
|
53
73
|
}
|
|
54
|
-
export function
|
|
55
|
-
|
|
56
|
-
|
|
74
|
+
export function normalizeSubagentType(value) {
|
|
75
|
+
return value.normalize("NFKC").toLowerCase().replace(/[\p{White_Space}\p{Pd}_]+/gu, "");
|
|
76
|
+
}
|
|
77
|
+
export function canonicalNameValue(value, values) {
|
|
78
|
+
if (values.includes(value))
|
|
79
|
+
return { kind: "entry", entry: value };
|
|
80
|
+
const key = normalizeSubagentType(value);
|
|
81
|
+
const hits = key === "" ? [] : [...new Set(values.filter((v) => normalizeSubagentType(v) === key))];
|
|
82
|
+
if (hits.length === 1)
|
|
83
|
+
return { kind: "entry", entry: hits[0] };
|
|
84
|
+
return hits.length > 1 ? { kind: "ambiguous", entries: hits } : { kind: "unknown" };
|
|
85
|
+
}
|
|
86
|
+
export function nameValueAbsentReason(tool) {
|
|
87
|
+
const face = nameRuleFaceOf(tool);
|
|
88
|
+
if (face === undefined || (face.words === undefined && face.values === undefined))
|
|
89
|
+
return undefined;
|
|
90
|
+
const key = face.param;
|
|
91
|
+
return `the ${tool} call names no ${key} as the tool reads it, so the ${key} it runs is not one its arguments show — a rule by ${key} cannot be read against it`;
|
|
57
92
|
}
|
|
58
93
|
export function nameGrammarReads(tool, content) {
|
|
59
94
|
if (ruleToolGrammarOf(tool) !== "name")
|
|
60
95
|
return false;
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
96
|
+
const face = nameRuleFaceOf(tool);
|
|
97
|
+
const key = contentKeyOf(content);
|
|
98
|
+
if (face.words !== undefined)
|
|
99
|
+
return key === face.param;
|
|
100
|
+
return key === undefined || face.params.includes(key) || !(catalogRuleFaceOf(tool)?.params ?? []).includes(key);
|
|
101
|
+
}
|
|
102
|
+
export function mountedValueArmOf(text, tool, behavior) {
|
|
103
|
+
if (behavior === "allow" || nameRuleFaceOf(tool)?.values !== "mounted")
|
|
104
|
+
return undefined;
|
|
105
|
+
const parsed = parseRuleText(text, behavior);
|
|
106
|
+
return "rule" in parsed && parsed.rule.match === "name" ? parsed.rule : undefined;
|
|
107
|
+
}
|
|
108
|
+
export function mountedOnlyArgumentOf(tool, content, mountedParams) {
|
|
109
|
+
if (nameRuleFaceOf(tool)?.values !== "mounted" || mountedParams === undefined)
|
|
110
|
+
return undefined;
|
|
111
|
+
const key = contentKeyOf(content);
|
|
112
|
+
return key !== undefined && mountedParams.includes(key) && !(catalogRuleFaceOf(tool)?.params ?? []).includes(key) ? key : undefined;
|
|
113
|
+
}
|
|
114
|
+
export function mountedArgumentArmOf(rule) {
|
|
115
|
+
const face = nameRuleFaceOf(rule.tool);
|
|
116
|
+
if (rule.behavior === "allow" || rule.match !== "name" || face?.values !== "mounted")
|
|
117
|
+
return undefined;
|
|
118
|
+
const key = contentKeyOf(rule.command);
|
|
119
|
+
if (key === undefined || face.params.includes(key) || !(catalogRuleFaceOf(rule.tool)?.params ?? []).includes(key))
|
|
120
|
+
return undefined;
|
|
121
|
+
const pattern = rule.command.slice(rule.command.indexOf(":") + 1).trim();
|
|
122
|
+
return pattern === "" ? undefined : { param: key, pattern };
|
|
123
|
+
}
|
|
124
|
+
function contentKeyOf(content) {
|
|
64
125
|
const colon = content.indexOf(":");
|
|
65
|
-
return colon > 0
|
|
126
|
+
return colon > 0 ? content.slice(0, colon).trim() : undefined;
|
|
66
127
|
}
|
|
67
|
-
export const
|
|
68
|
-
WebFetch: Object.freeze({ param: "url" }),
|
|
69
|
-
});
|
|
128
|
+
export const DOMAIN_RULE_MARKER = "domain:";
|
|
70
129
|
export function ruleToolGrammarOf(toolName) {
|
|
71
130
|
if (toolName === COMMAND_RULE_TOOL)
|
|
72
131
|
return "command";
|
|
73
132
|
if (Object.hasOwn(NAME_RULE_FACES, toolName))
|
|
74
133
|
return "name";
|
|
75
|
-
if (
|
|
134
|
+
if (hostTargetOf(toolName) !== undefined)
|
|
76
135
|
return "domain";
|
|
77
136
|
return pathTargetOf(toolName) !== undefined ? "path" : undefined;
|
|
78
137
|
}
|
|
79
|
-
export function
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
return undefined;
|
|
83
|
-
const value = args[face.param];
|
|
84
|
-
if (typeof value !== "string")
|
|
85
|
-
return undefined;
|
|
86
|
-
let url;
|
|
87
|
-
try {
|
|
88
|
-
url = new URL(value);
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
138
|
+
export function shellRuleParamOf(tool, content) {
|
|
139
|
+
const colon = content.indexOf(":");
|
|
140
|
+
if (colon <= 0)
|
|
91
141
|
return undefined;
|
|
142
|
+
const param = content.slice(0, colon).trim();
|
|
143
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(param) && (catalogRuleFaceOf(tool)?.params ?? []).includes(param) ? param : undefined;
|
|
144
|
+
}
|
|
145
|
+
export function ruleLaneOf(tool, content) {
|
|
146
|
+
if (isShellFaceToolName(tool))
|
|
147
|
+
return shellRuleParamOf(tool, content) !== undefined && !(tool === COMMAND_RULE_TOOL && content.endsWith(":*")) ? "param" : "command";
|
|
148
|
+
switch (ruleToolGrammarOf(tool)) {
|
|
149
|
+
case "name":
|
|
150
|
+
return nameGrammarReads(tool, content) ? "name" : "param";
|
|
151
|
+
case "path":
|
|
152
|
+
return content.indexOf(":") <= 0 ? "path" : "param";
|
|
153
|
+
case "domain":
|
|
154
|
+
return content.startsWith(DOMAIN_RULE_MARKER) ? "domain" : "param";
|
|
155
|
+
default:
|
|
156
|
+
return "param";
|
|
92
157
|
}
|
|
93
|
-
if ((url.protocol !== "http:" && url.protocol !== "https:") || url.username !== "" || url.password !== "")
|
|
94
|
-
return undefined;
|
|
95
|
-
const host = url.hostname.replace(/\.+$/, "");
|
|
96
|
-
return host === "" ? undefined : host;
|
|
97
158
|
}
|
|
98
159
|
export function nameRuleSubjectOf(toolName, args) {
|
|
99
160
|
const face = Object.hasOwn(NAME_RULE_FACES, toolName) ? NAME_RULE_FACES[toolName] : undefined;
|
|
100
161
|
if (face === undefined || typeof args !== "object" || args === null || Array.isArray(args) || !Object.hasOwn(args, face.param))
|
|
101
162
|
return undefined;
|
|
102
163
|
const value = args[face.param];
|
|
103
|
-
return typeof value === "string" ? value : undefined;
|
|
164
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
104
165
|
}
|
|
105
166
|
export function laneToolNamesOf(req) {
|
|
106
167
|
const words = heldValues("policy.persistedGrammar", { get: ruleWordOf }, req);
|
|
@@ -111,9 +172,9 @@ function ruleWordOf(name) {
|
|
|
111
172
|
return COMMAND_RULE_TOOL;
|
|
112
173
|
return ruleToolGrammarOf(name) !== undefined ? name : undefined;
|
|
113
174
|
}
|
|
114
|
-
export const PERSISTED_RULE_MATCHES = Object.freeze(["exact", "prefix", "wildcard", "subpath", "path", "name", "name_prefix", "host", "subdomain"]);
|
|
175
|
+
export const PERSISTED_RULE_MATCHES = Object.freeze(["exact", "prefix", "wildcard", "pattern", "subpath", "path", "name", "name_prefix", "host", "subdomain"]);
|
|
115
176
|
function isCommandMatch(match) {
|
|
116
|
-
return match === "exact" || match === "prefix" || match === "wildcard";
|
|
177
|
+
return match === "exact" || match === "prefix" || match === "wildcard" || match === "pattern";
|
|
117
178
|
}
|
|
118
179
|
export const RULE_ADD_ORIGINS = Object.freeze(["user", "imported-cc", "starter"]);
|
|
119
180
|
export const MAX_RULE_TEXT_CHARS = 512;
|
|
@@ -282,10 +343,32 @@ const CONTROL_CHARS_GLOBAL_RE = new RegExp(CONTROL_CHARS_RE.source, "gu");
|
|
|
282
343
|
const RULE_SPELLING_REJECT_BYTES = "<>";
|
|
283
344
|
const RULE_SPELLING_REJECT_RE = new RegExp(`[${RULE_SPELLING_REJECT_BYTES}]`);
|
|
284
345
|
function valueRuleFormsSentence() {
|
|
285
|
-
return Object.
|
|
286
|
-
.map((
|
|
346
|
+
return Object.keys(NAME_RULE_FACES)
|
|
347
|
+
.map((tool) => `${nameRuleFormOf(tool)} rules`)
|
|
287
348
|
.join(", ");
|
|
288
349
|
}
|
|
350
|
+
function nameRuleFormOf(tool) {
|
|
351
|
+
return formatRuleText(`<${nameRuleFaceOf(tool).param}>`, "name", tool);
|
|
352
|
+
}
|
|
353
|
+
export function ruleShapeOf(call, behavior) {
|
|
354
|
+
if (isShellFaceToolName(call.toolName))
|
|
355
|
+
return formatRuleText("<command>", "exact", COMMAND_RULE_TOOL);
|
|
356
|
+
switch (ruleToolGrammarOf(call.toolName)) {
|
|
357
|
+
case "name":
|
|
358
|
+
return nameRuleFormOf(call.toolName);
|
|
359
|
+
case "domain":
|
|
360
|
+
return formatRuleText("<host>", "host", call.toolName);
|
|
361
|
+
case "path": {
|
|
362
|
+
if (behavior !== "allow")
|
|
363
|
+
return formatRuleText("<path>", "path", call.toolName);
|
|
364
|
+
const face = callPathFaceOf(call) ?? (pathTargetOf(call.toolName)?.access === "read" ? "read" : "write");
|
|
365
|
+
const word = Object.keys(PATH_RULE_FACES).find((tool) => PATH_RULE_FACES[tool].allow?.includes(face) === true) ?? call.toolName;
|
|
366
|
+
return formatRuleText("<path>", "path", word);
|
|
367
|
+
}
|
|
368
|
+
default:
|
|
369
|
+
return undefined;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
289
372
|
const DISCLOSED_RULE_TEXT_MAX_CHARS = 120;
|
|
290
373
|
export function escapeForDisclosure(value) {
|
|
291
374
|
let text;
|
|
@@ -319,6 +402,10 @@ export function renderUntrustedCommandText(text, maxLen = DISCLOSED_RULE_TEXT_MA
|
|
|
319
402
|
export function hasUnrenderableCharacters(text) {
|
|
320
403
|
return CONTROL_CHARS_RE.test(text);
|
|
321
404
|
}
|
|
405
|
+
export function ruleContentOf(parsed) {
|
|
406
|
+
const content = parsed.ruleContent;
|
|
407
|
+
return content === undefined || (parsed.toolName === COMMAND_RULE_TOOL && foldSpacing(content) === "*") ? undefined : content;
|
|
408
|
+
}
|
|
322
409
|
export function parseRuleText(text, behavior) {
|
|
323
410
|
if (!isRuleBehavior(behavior)) {
|
|
324
411
|
return reject("invalid.grammar", `rule "${text}" was parsed under behavior ${escapeForDisclosure(behavior)}, which is not one of ${RULE_BEHAVIORS.join("/")}`);
|
|
@@ -331,7 +418,8 @@ export function parseRuleText(text, behavior) {
|
|
|
331
418
|
return reject("invalid.control_chars", "rule text contains control characters (C0/C1, tab excepted), which cannot be part of a readable command");
|
|
332
419
|
}
|
|
333
420
|
const parsed = parsePermissionRule(text);
|
|
334
|
-
|
|
421
|
+
const ruleContent = ruleContentOf(parsed);
|
|
422
|
+
if (ruleContent === undefined) {
|
|
335
423
|
return reject("invalid.grammar", `"${text}" is not a Tool(content) rule — a bare tool name claims the whole tool and is not a command rule`);
|
|
336
424
|
}
|
|
337
425
|
if (parsed.toolName !== COMMAND_RULE_TOOL) {
|
|
@@ -339,16 +427,16 @@ export function parseRuleText(text, behavior) {
|
|
|
339
427
|
if (grammar === undefined) {
|
|
340
428
|
return reject("unsupported.tool", `only Bash command rules, rules on path-targeting tools (Read, Edit, Write, …), ${valueRuleFormsSentence()} and WebFetch domain rules have a rule form in this version (got "${parsed.toolName}")`);
|
|
341
429
|
}
|
|
342
|
-
if (!tighten && RULE_SPELLING_REJECT_RE.test(
|
|
430
|
+
if (!tighten && RULE_SPELLING_REJECT_RE.test(ruleContent)) {
|
|
343
431
|
return reject("invalid.grammar", `"${text}" carries a shell redirection byte (${[...RULE_SPELLING_REJECT_BYTES].map((b) => `\`${b}\``).join(" or ")}) — no path, name or host allow spells one, and the grant would not read as what it looks like`);
|
|
344
432
|
}
|
|
345
433
|
if (grammar === "name")
|
|
346
|
-
return parseNameRuleContent(text, parsed.toolName,
|
|
434
|
+
return parseNameRuleContent(text, parsed.toolName, ruleContent, behavior);
|
|
347
435
|
if (grammar === "domain")
|
|
348
|
-
return parseDomainRuleContent(text, parsed.toolName,
|
|
349
|
-
return parsePathRuleContent(text, parsed.toolName,
|
|
436
|
+
return parseDomainRuleContent(text, parsed.toolName, ruleContent, behavior);
|
|
437
|
+
return parsePathRuleContent(text, parsed.toolName, ruleContent, behavior);
|
|
350
438
|
}
|
|
351
|
-
const content =
|
|
439
|
+
const content = foldSpacing(ruleContent) ?? ruleContent;
|
|
352
440
|
const prefixBody = /^(.+):\*$/.exec(content)?.[1];
|
|
353
441
|
let match;
|
|
354
442
|
let body;
|
|
@@ -366,14 +454,18 @@ export function parseRuleText(text, behavior) {
|
|
|
366
454
|
body = wildcardBody;
|
|
367
455
|
}
|
|
368
456
|
else if (hasUnescapedStar(content)) {
|
|
369
|
-
|
|
457
|
+
match = "pattern";
|
|
458
|
+
body = content;
|
|
370
459
|
}
|
|
371
460
|
else {
|
|
372
461
|
match = "exact";
|
|
373
462
|
body = content;
|
|
374
463
|
}
|
|
375
464
|
}
|
|
376
|
-
|
|
465
|
+
if (match === "pattern" && !tighten && hasUnescapedStar(body.trim().split(/[ \t]+/)[0] ?? "")) {
|
|
466
|
+
return reject("unsupported.wildcard", `a \`*\` in the first word of "${text}" leaves open which program runs — an allow names the program it grants (a deny or an ask may leave it open)`);
|
|
467
|
+
}
|
|
468
|
+
const shape = ruleLaneShapeOf(match === "pattern" ? globFloorText(body) : body, MATCH_READING);
|
|
377
469
|
if ("reject" in shape) {
|
|
378
470
|
if (body.trim() === "" || shape.reject === "empty command")
|
|
379
471
|
return reject("invalid.empty_command", `rule "${text}" names no command`);
|
|
@@ -381,14 +473,16 @@ export function parseRuleText(text, behavior) {
|
|
|
381
473
|
return reject("invalid.unbalanced_quotes", `rule "${text}" has unbalanced quoting, so the command it names is not determinable`);
|
|
382
474
|
return reject("invalid.not_simple_command", `rule "${text}" is not a command this lane can name (${shape.reject})`);
|
|
383
475
|
}
|
|
384
|
-
if (match === "wildcard" && shape.segments.length > 1) {
|
|
385
|
-
return reject("unsupported.wildcard",
|
|
476
|
+
if ((match === "wildcard" || match === "pattern") && shape.segments.length > 1) {
|
|
477
|
+
return reject("unsupported.wildcard", match === "pattern"
|
|
478
|
+
? `a \`*\` pattern names one command, and "${text}" names more than one — a compound is covered segment by segment, and the compound-prefix form spells a chain with the \`:*\` marker`
|
|
479
|
+
: `a \`<chain> *\` pattern names more than one command, and the trailing-star form has no chain reading — the compound-prefix form spells one with the \`:*\` marker ("${text}")`);
|
|
386
480
|
}
|
|
387
481
|
const head = commandBasename(shape.names[shape.names.length - 1] ?? "");
|
|
388
|
-
if (
|
|
482
|
+
if (match !== "exact" && BARE_INTERPRETER_NAMES.has(head) && !tighten) {
|
|
389
483
|
return reject("invalid.bare_interpreter_prefix", shape.segments.length > 1
|
|
390
484
|
? `prefix rule "${text}" leaves its final segment open-ended under the interpreter "${head}" — such a rule authorizes running arbitrary programs, which one approval click cannot be read as having granted (an exact rule naming the whole command line is accepted)`
|
|
391
|
-
:
|
|
485
|
+
: `${match === "pattern" ? "pattern" : "prefix"} rule "${text}" is headed by the interpreter "${head}" — such a rule authorizes running arbitrary programs, which one approval click cannot be read as having granted (an exact rule naming the whole command line is accepted)`);
|
|
392
486
|
}
|
|
393
487
|
const command = foldSpacing(body);
|
|
394
488
|
if (command === undefined) {
|
|
@@ -411,6 +505,8 @@ function parseNameRuleContent(text, tool, content, behavior, verifyCanonical = t
|
|
|
411
505
|
const face = NAME_RULE_FACES[tool];
|
|
412
506
|
if (face?.words !== undefined)
|
|
413
507
|
return parseKeyedValueRuleContent(text, tool, content, behavior, { param: face.param, words: face.words });
|
|
508
|
+
if (face?.values === "mounted")
|
|
509
|
+
return parseMountedValueRuleContent(text, tool, content, behavior, face);
|
|
414
510
|
const bare = content.startsWith("/") ? content.slice(1) : content;
|
|
415
511
|
const colon = bare.indexOf(":");
|
|
416
512
|
if (face !== undefined && colon > 0 && face.params.includes(bare.slice(0, colon).trim())) {
|
|
@@ -457,6 +553,28 @@ function parseKeyedValueRuleContent(text, tool, content, behavior, face) {
|
|
|
457
553
|
}
|
|
458
554
|
return { rule: { behavior, rule: formatRuleText(word, "name", tool), tool, match: "name", command: word } };
|
|
459
555
|
}
|
|
556
|
+
function parseMountedValueRuleContent(text, tool, content, behavior, face) {
|
|
557
|
+
const form = nameRuleFormOf(tool);
|
|
558
|
+
const key = contentKeyOf(content);
|
|
559
|
+
if (key !== undefined && face.params.includes(key)) {
|
|
560
|
+
return reject("invalid.grammar", `"${text}" is the parameter spelling, which a ${tool} rule does not have — a ${tool} rule names the ${face.param} itself: ${form}`);
|
|
561
|
+
}
|
|
562
|
+
if (behavior === "allow" && key !== undefined && (catalogRuleFaceOf(tool)?.params ?? []).includes(key)) {
|
|
563
|
+
return reject("invalid.grammar", `"${text}" is a rule on the ${key} argument, which an allow entry has no form for — an allow reads one way, so it does not grant the ${face.param} "${content}" either; a ${tool} allow names the ${face.param}: ${form}`);
|
|
564
|
+
}
|
|
565
|
+
if (content === "" || content.trim() !== content) {
|
|
566
|
+
return reject("invalid.grammar", `the ${face.param} in "${text}" is empty or carries surrounding whitespace — a ${tool} rule names the ${face.param} exactly as it is spelled, so the spelling must be the one meant`);
|
|
567
|
+
}
|
|
568
|
+
if (content.includes("*")) {
|
|
569
|
+
return reject("unsupported.wildcard", `a \`*\` in the ${face.param} of "${text}" has no rule form — a ${tool} rule names one ${face.param} (${form}); the whole tool is the bare ${tool} entry`);
|
|
570
|
+
}
|
|
571
|
+
const canonical = formatRuleText(content, "name", tool);
|
|
572
|
+
const back = parsePermissionRule(canonical);
|
|
573
|
+
if (back.toolName !== tool || back.ruleContent !== content) {
|
|
574
|
+
return reject("invalid.grammar", `the ${face.param} in "${text}" has no spelling the rule syntax reads back (a backslash or an unbalanced parenthesis) — no ${tool} rule can name it`);
|
|
575
|
+
}
|
|
576
|
+
return { rule: { behavior, rule: canonical, tool, match: "name", command: content } };
|
|
577
|
+
}
|
|
460
578
|
export function nameRuleAdmits(rule, name) {
|
|
461
579
|
if (rule.match === "name")
|
|
462
580
|
return name === rule.command;
|
|
@@ -464,14 +582,31 @@ export function nameRuleAdmits(rule, name) {
|
|
|
464
582
|
return name.startsWith(rule.command);
|
|
465
583
|
return false;
|
|
466
584
|
}
|
|
585
|
+
export function nameRuleReach(rule, name, values) {
|
|
586
|
+
const face = nameRuleFaceOf(rule.tool);
|
|
587
|
+
if (face?.values !== "mounted")
|
|
588
|
+
return nameRuleAdmits(rule, name) ? "reached" : "not_reached";
|
|
589
|
+
if (rule.match !== "name")
|
|
590
|
+
return "not_reached";
|
|
591
|
+
if (values === undefined)
|
|
592
|
+
return "unread";
|
|
593
|
+
const operand = canonicalNameValue(rule.command, values);
|
|
594
|
+
const called = canonicalNameValue(name, values);
|
|
595
|
+
if (operand.kind === "ambiguous")
|
|
596
|
+
return rule.behavior !== "allow" && called.kind === "entry" && operand.entries.includes(called.entry) ? "reached" : "not_reached";
|
|
597
|
+
if (operand.kind === "entry" && called.kind === "entry")
|
|
598
|
+
return operand.entry === called.entry ? "reached" : "not_reached";
|
|
599
|
+
return name === rule.command ? "reached" : "not_reached";
|
|
600
|
+
}
|
|
601
|
+
export function nameValuesUnreadReason(tool) {
|
|
602
|
+
const face = nameRuleFaceOf(tool);
|
|
603
|
+
return `the ${tool} tool this call reaches declares no ${face?.param ?? "value"} values it offers, so which ${face?.param ?? "value"} the call runs cannot be read against a ${tool} rule`;
|
|
604
|
+
}
|
|
467
605
|
function parseDomainRuleContent(text, tool, content, behavior) {
|
|
468
|
-
if (
|
|
469
|
-
return reject("unsupported.tool", `a ${tool} rule by host has an allow form only in this version ("${text}") — a ${behavior} by host is not read; write the ${behavior} on the whole ${tool} tool`);
|
|
470
|
-
}
|
|
471
|
-
if (!content.startsWith("domain:")) {
|
|
606
|
+
if (!content.startsWith(DOMAIN_RULE_MARKER)) {
|
|
472
607
|
return reject("invalid.grammar", `a ${tool} rule names a host: ${tool}(domain:<host>) for one, ${tool}(domain:*.<domain>) for every host below a domain ("${text}")`);
|
|
473
608
|
}
|
|
474
|
-
const spelled = content.slice(
|
|
609
|
+
const spelled = content.slice(DOMAIN_RULE_MARKER.length);
|
|
475
610
|
if (spelled === "*")
|
|
476
611
|
return reject("unsupported.wildcard", `"${text}" names every host — the whole tool is the bare ${tool} entry`);
|
|
477
612
|
const below = spelled.startsWith("*.");
|
|
@@ -488,24 +623,6 @@ function parseDomainRuleContent(text, tool, content, behavior) {
|
|
|
488
623
|
const match = below ? "subdomain" : "host";
|
|
489
624
|
return { rule: { behavior, rule: formatRuleText(host, match, tool), tool, match, command: host } };
|
|
490
625
|
}
|
|
491
|
-
function hostOfSpelling(spelled) {
|
|
492
|
-
if (spelled === "" || /[\s/\\@?#%]/.test(spelled) || (spelled.includes(":") && !/^\[[^\]]+\]$/.test(spelled)))
|
|
493
|
-
return undefined;
|
|
494
|
-
let url;
|
|
495
|
-
try {
|
|
496
|
-
url = new URL(`https://${spelled}/`);
|
|
497
|
-
}
|
|
498
|
-
catch {
|
|
499
|
-
return undefined;
|
|
500
|
-
}
|
|
501
|
-
if (url.port !== "" || url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "")
|
|
502
|
-
return undefined;
|
|
503
|
-
const host = url.hostname.replace(/\.+$/, "");
|
|
504
|
-
return host === "" ? undefined : host;
|
|
505
|
-
}
|
|
506
|
-
function isAddressHost(host) {
|
|
507
|
-
return host.startsWith("[") || /^\d+\.\d+\.\d+\.\d+$/.test(host);
|
|
508
|
-
}
|
|
509
626
|
export function domainRuleAdmits(rule, host) {
|
|
510
627
|
if (rule.match === "host")
|
|
511
628
|
return host === rule.command;
|
|
@@ -712,6 +829,10 @@ const NOT_REACHED = { reach: "not_reached" };
|
|
|
712
829
|
export function programRunReachOf(rule, command) {
|
|
713
830
|
if (!isCommandMatch(rule.match))
|
|
714
831
|
return NOT_REACHED;
|
|
832
|
+
if (rule.match === "pattern") {
|
|
833
|
+
const pattern = commandGlobOf(rule.command, "words");
|
|
834
|
+
return pattern === undefined ? NOT_REACHED : anySegmentReach(readCommandMemo(command), (argv) => globArgvReachOf(pattern, argv));
|
|
835
|
+
}
|
|
715
836
|
const body = readRuleBody(rule.command);
|
|
716
837
|
if (body === undefined)
|
|
717
838
|
return NOT_REACHED;
|
|
@@ -727,7 +848,8 @@ export function programRunReachOf(rule, command) {
|
|
|
727
848
|
return hidden === undefined ? NOT_REACHED : { reach: "unreadable", reason: hidden.peelUnreadable };
|
|
728
849
|
let outcome = REACHED;
|
|
729
850
|
for (let i = 0; i < body.segments.length; i++) {
|
|
730
|
-
const
|
|
851
|
+
const bodyArgv = body.segments[i].argv;
|
|
852
|
+
const r = segmentReachOf(shape.segments[i], (argv) => argvReachOf(bodyArgv, argv, exact || i < body.segments.length - 1));
|
|
731
853
|
if (r.reach === "not_reached")
|
|
732
854
|
return hidden === undefined ? NOT_REACHED : { reach: "unreadable", reason: hidden.peelUnreadable };
|
|
733
855
|
if (r.reach === "unreadable")
|
|
@@ -736,9 +858,12 @@ export function programRunReachOf(rule, command) {
|
|
|
736
858
|
return outcome;
|
|
737
859
|
}
|
|
738
860
|
const bodyArgv = body.segments[0].argv;
|
|
861
|
+
return anySegmentReach(shape, (argv) => argvReachOf(bodyArgv, argv, exact));
|
|
862
|
+
}
|
|
863
|
+
function anySegmentReach(shape, reachOf) {
|
|
739
864
|
let unreadable;
|
|
740
865
|
for (const segment of shape.segments) {
|
|
741
|
-
const r = segmentReachOf(
|
|
866
|
+
const r = segmentReachOf(segment, reachOf);
|
|
742
867
|
if (r.reach === "reached")
|
|
743
868
|
return REACHED;
|
|
744
869
|
if (r.reach === "unreadable")
|
|
@@ -749,12 +874,12 @@ export function programRunReachOf(rule, command) {
|
|
|
749
874
|
export function ruleReachesProgramRun(rule, command) {
|
|
750
875
|
return programRunReachOf(rule, command).reach === "reached";
|
|
751
876
|
}
|
|
752
|
-
function segmentReachOf(
|
|
877
|
+
function segmentReachOf(segment, reachOf) {
|
|
753
878
|
if (segment.unreadable !== undefined)
|
|
754
879
|
return { reach: "unreadable", reason: segment.unreadable };
|
|
755
880
|
let unreadable;
|
|
756
881
|
for (const argv of [segment.argv, ...segment.peeled]) {
|
|
757
|
-
const r =
|
|
882
|
+
const r = reachOf(argv);
|
|
758
883
|
if (r.reach === "reached")
|
|
759
884
|
return REACHED;
|
|
760
885
|
if (r.reach === "unreadable")
|
|
@@ -783,6 +908,148 @@ function argvReachOf(bodyArgv, argv, exact) {
|
|
|
783
908
|
const loose = argv.slice(bodyArgv.length).find((w) => w.expands === "many");
|
|
784
909
|
return loose === undefined ? NOT_REACHED : { reach: "unreadable", reason: `an unquoted expansion (${loose.raw}) may expand to nothing` };
|
|
785
910
|
}
|
|
911
|
+
const GLOB_STAR = -1;
|
|
912
|
+
const GLOB_HOLE = -2;
|
|
913
|
+
const GLOB_GROUP = -3;
|
|
914
|
+
function globFloorText(body) {
|
|
915
|
+
let out = "";
|
|
916
|
+
for (let i = 0; i < body.length; i++) {
|
|
917
|
+
if (body[i] === "\\" && body[i + 1] === "*") {
|
|
918
|
+
out += "*";
|
|
919
|
+
i++;
|
|
920
|
+
}
|
|
921
|
+
else
|
|
922
|
+
out += body[i] === "*" ? "x" : body[i];
|
|
923
|
+
}
|
|
924
|
+
return out;
|
|
925
|
+
}
|
|
926
|
+
function commandGlobOf(body, reading) {
|
|
927
|
+
const shape = ruleLaneShapeOf(globFloorText(body), MATCH_READING);
|
|
928
|
+
return "reject" in shape || shape.segments.length !== 1 ? undefined : compileGlob(reading === "words" ? unquotedPattern(body) : body);
|
|
929
|
+
}
|
|
930
|
+
function unquotedPattern(body) {
|
|
931
|
+
let out = "";
|
|
932
|
+
let quote;
|
|
933
|
+
for (const ch of body) {
|
|
934
|
+
if (quote === undefined ? ch === "'" || ch === '"' : ch === quote)
|
|
935
|
+
quote = quote === undefined ? ch : undefined;
|
|
936
|
+
else
|
|
937
|
+
out += ch;
|
|
938
|
+
}
|
|
939
|
+
return out;
|
|
940
|
+
}
|
|
941
|
+
function compileGlob(pattern) {
|
|
942
|
+
const nodes = [];
|
|
943
|
+
const groupEnds = new Map();
|
|
944
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
945
|
+
const ch = pattern[i];
|
|
946
|
+
if (ch === "\\" && pattern[i + 1] === "*") {
|
|
947
|
+
nodes.push(0x2a);
|
|
948
|
+
i++;
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
if (ch === "/" && pattern.startsWith("**/", i + 1)) {
|
|
952
|
+
let j = i + 1;
|
|
953
|
+
while (pattern.startsWith("**/", j))
|
|
954
|
+
j += 3;
|
|
955
|
+
nodes.push(0x2f, GLOB_GROUP, GLOB_STAR, 0x2f);
|
|
956
|
+
groupEnds.set(nodes.length - 3, nodes.length);
|
|
957
|
+
i = j - 1;
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
nodes.push(ch === "*" ? GLOB_STAR : ch.charCodeAt(0));
|
|
961
|
+
}
|
|
962
|
+
return { nodes, groupEnds };
|
|
963
|
+
}
|
|
964
|
+
function globMatches(glob, subject, holes) {
|
|
965
|
+
const { nodes, groupEnds } = glob;
|
|
966
|
+
const m = nodes.length;
|
|
967
|
+
let live = new Uint8Array(m + 1);
|
|
968
|
+
let next = new Uint8Array(m + 1);
|
|
969
|
+
const close = (set) => {
|
|
970
|
+
for (let p = 0; p < m; p++) {
|
|
971
|
+
if (set[p] !== 1)
|
|
972
|
+
continue;
|
|
973
|
+
if (nodes[p] === GLOB_STAR || nodes[p] === GLOB_GROUP)
|
|
974
|
+
set[p + 1] = 1;
|
|
975
|
+
if (nodes[p] === GLOB_GROUP)
|
|
976
|
+
set[groupEnds.get(p)] = 1;
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
live[0] = 1;
|
|
980
|
+
close(live);
|
|
981
|
+
for (const unit of subject) {
|
|
982
|
+
next.fill(0);
|
|
983
|
+
if (unit === GLOB_HOLE && holes === "any")
|
|
984
|
+
next.fill(1, live.indexOf(1));
|
|
985
|
+
else {
|
|
986
|
+
for (let p = 0; p < m; p++) {
|
|
987
|
+
if (live[p] !== 1)
|
|
988
|
+
continue;
|
|
989
|
+
if (nodes[p] === GLOB_STAR)
|
|
990
|
+
next[p] = 1;
|
|
991
|
+
else if (nodes[p] === unit)
|
|
992
|
+
next[p + 1] = 1;
|
|
993
|
+
}
|
|
994
|
+
close(next);
|
|
995
|
+
}
|
|
996
|
+
if (next.indexOf(1) < 0)
|
|
997
|
+
return false;
|
|
998
|
+
[live, next] = [next, live];
|
|
999
|
+
}
|
|
1000
|
+
return live[m] === 1;
|
|
1001
|
+
}
|
|
1002
|
+
function unitsOf(text) {
|
|
1003
|
+
const out = [];
|
|
1004
|
+
for (let i = 0; i < text.length; i++)
|
|
1005
|
+
out.push(text.charCodeAt(i));
|
|
1006
|
+
return out;
|
|
1007
|
+
}
|
|
1008
|
+
function globAdmitsSpelling(body, folded) {
|
|
1009
|
+
const glob = commandGlobOf(body, "spelling");
|
|
1010
|
+
return glob !== undefined && globMatches(glob, unitsOf(folded), "inside_star");
|
|
1011
|
+
}
|
|
1012
|
+
function globArgvReachOf(glob, argv) {
|
|
1013
|
+
if (argv.length === 0)
|
|
1014
|
+
return NOT_REACHED;
|
|
1015
|
+
const subject = [];
|
|
1016
|
+
let hole;
|
|
1017
|
+
let anchored = false;
|
|
1018
|
+
argv.forEach((w, k) => {
|
|
1019
|
+
if (w.expands !== "many") {
|
|
1020
|
+
if (anchored)
|
|
1021
|
+
subject.push(0x20);
|
|
1022
|
+
anchored = true;
|
|
1023
|
+
}
|
|
1024
|
+
if (w.expands === false)
|
|
1025
|
+
subject.push(...unitsOf(w.text));
|
|
1026
|
+
else {
|
|
1027
|
+
subject.push(GLOB_HOLE);
|
|
1028
|
+
hole ??= { at: k, raw: w.raw };
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
if (globMatches(glob, subject, "inside_star"))
|
|
1032
|
+
return REACHED;
|
|
1033
|
+
if (hole === undefined || !globMatches(glob, subject, "any"))
|
|
1034
|
+
return NOT_REACHED;
|
|
1035
|
+
return { reach: "unreadable", reason: hole.at === 0 ? "the program name is an expansion" : `an expansion (${hole.raw}) stands where the rule's pattern reads` };
|
|
1036
|
+
}
|
|
1037
|
+
export function commandPatternMayRun(body, programs) {
|
|
1038
|
+
const lexed = lexShell(body);
|
|
1039
|
+
const words = "parseFailure" in lexed ? undefined : lexed.segments[0]?.tokens.filter((t) => t.kind === "word");
|
|
1040
|
+
if (words === undefined || words.length === 0)
|
|
1041
|
+
return true;
|
|
1042
|
+
const position = readProgramPosition(words);
|
|
1043
|
+
const program = position.program;
|
|
1044
|
+
if (program === undefined || position.refusal !== undefined || hasUnescapedStar(words[program.index]?.raw ?? "*"))
|
|
1045
|
+
return true;
|
|
1046
|
+
const rest = words.slice(program.index + 1).map((w) => ` ${w.raw}`).join("");
|
|
1047
|
+
const glob = compileGlob(unquotedPattern(`${program.basename}${rest}`.toLowerCase()));
|
|
1048
|
+
return programs.some((p) => {
|
|
1049
|
+
const head = unitsOf(p.toLowerCase());
|
|
1050
|
+
return globMatches(glob, head, "inside_star") || globMatches(glob, [...head, 0x20, GLOB_HOLE], "any");
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
786
1053
|
function readRuleBody(body) {
|
|
787
1054
|
const shape = readShellCommand(body);
|
|
788
1055
|
if (shape.grouped || shape.segments.length === 0)
|
|
@@ -806,6 +1073,8 @@ function admitsUnder(rule, command, reading) {
|
|
|
806
1073
|
return false;
|
|
807
1074
|
if (rule.match === "exact")
|
|
808
1075
|
return folded === rule.command;
|
|
1076
|
+
if (rule.match === "pattern")
|
|
1077
|
+
return shape.segments.length === 1 && globAdmitsSpelling(rule.command, folded);
|
|
809
1078
|
const bodyShape = ruleLaneShapeOf(rule.command, reading);
|
|
810
1079
|
if ("reject" in bodyShape || foldSpacing(rule.command) === undefined)
|
|
811
1080
|
return false;
|
|
@@ -815,9 +1084,9 @@ function admitsUnder(rule, command, reading) {
|
|
|
815
1084
|
}
|
|
816
1085
|
export const EDITED_RULE_BREADTH_WARNING_CODES = Object.freeze(["broad_prefix", "compound_prefix"]);
|
|
817
1086
|
export function ruleBreadthWarningsOf(rule) {
|
|
818
|
-
if (rule.match !== "prefix" && rule.match !== "wildcard")
|
|
1087
|
+
if (rule.match !== "prefix" && rule.match !== "wildcard" && rule.match !== "pattern")
|
|
819
1088
|
return [];
|
|
820
|
-
const bodyShape = ruleLaneShapeOf(rule.command, MATCH_READING);
|
|
1089
|
+
const bodyShape = ruleLaneShapeOf(rule.match === "pattern" ? globFloorText(rule.command) : rule.command, MATCH_READING);
|
|
821
1090
|
if ("reject" in bodyShape)
|
|
822
1091
|
return [];
|
|
823
1092
|
if (bodyShape.segments.length > 1) {
|
|
@@ -1102,8 +1371,10 @@ export function adjudicatePersistedPathRules(rules, call, views, anchorsOf, lead
|
|
|
1102
1371
|
return undefined;
|
|
1103
1372
|
}
|
|
1104
1373
|
export function adjudicatePersistedNameRules(rules, call) {
|
|
1105
|
-
|
|
1106
|
-
|
|
1374
|
+
const name = nameRuleSubjectOf(call.tool, call.request.args);
|
|
1375
|
+
const values = call.request.face?.nameValues;
|
|
1376
|
+
if (name === undefined) {
|
|
1377
|
+
if (nameValueAbsentReason(call.tool) === undefined)
|
|
1107
1378
|
return undefined;
|
|
1108
1379
|
for (const behavior of ["deny", "ask"]) {
|
|
1109
1380
|
const row = rules.find((rule) => rule.match === "name" && eligiblePersisted(rule, call, behavior));
|
|
@@ -1114,21 +1385,48 @@ export function adjudicatePersistedNameRules(rules, call) {
|
|
|
1114
1385
|
}
|
|
1115
1386
|
for (const behavior of RULE_BEHAVIORS_BY_PRECEDENCE) {
|
|
1116
1387
|
for (const rule of rules) {
|
|
1117
|
-
if (eligiblePersisted(rule, call, behavior)
|
|
1388
|
+
if (!eligiblePersisted(rule, call, behavior))
|
|
1389
|
+
continue;
|
|
1390
|
+
const arm = mountedArgumentArmOf(rule);
|
|
1391
|
+
if (arm !== undefined && paramRuleReaches(call.request, arm.param, arm.pattern))
|
|
1392
|
+
return { behavior, rules: [rule] };
|
|
1393
|
+
const reach = nameRuleReach(rule, name, values);
|
|
1394
|
+
if (reach === "reached")
|
|
1118
1395
|
return { behavior, rules: [rule] };
|
|
1396
|
+
if (reach === "unread" && behavior !== "allow")
|
|
1397
|
+
return behavior === "deny" ? { behavior, rules: [rule] } : { behavior, rules: [rule], unreadable: nameValuesUnreadReason(call.tool) };
|
|
1119
1398
|
}
|
|
1120
1399
|
}
|
|
1121
1400
|
return undefined;
|
|
1122
1401
|
}
|
|
1123
1402
|
export function adjudicatePersistedDomainRules(rules, call) {
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1403
|
+
for (const behavior of RULE_BEHAVIORS_BY_PRECEDENCE) {
|
|
1404
|
+
const reading = behavior === "allow" ? call.hosts.grant : call.hosts.tighten;
|
|
1405
|
+
if (reading === undefined)
|
|
1406
|
+
continue;
|
|
1407
|
+
const speaks = (rule) => (rule.match === "host" || rule.match === "subdomain") && eligiblePersisted(rule, call, behavior);
|
|
1408
|
+
if ("unreadable" in reading) {
|
|
1409
|
+
if (behavior === "allow")
|
|
1410
|
+
continue;
|
|
1411
|
+
const row = rules.find(speaks);
|
|
1412
|
+
if (row === undefined)
|
|
1413
|
+
continue;
|
|
1414
|
+
return behavior === "deny" ? { behavior: "deny", rules: [row] } : { behavior: "ask", rules: [row], unreadable: `the host the call reaches cannot be read (${HOST_UNREADABLE_SENTENCE[reading.unreadable]})` };
|
|
1415
|
+
}
|
|
1416
|
+
const row = rules.find((rule) => speaks(rule) && domainRuleAdmits(rule, reading.host));
|
|
1417
|
+
if (row !== undefined)
|
|
1418
|
+
return { behavior, rules: [row] };
|
|
1129
1419
|
}
|
|
1130
1420
|
return undefined;
|
|
1131
1421
|
}
|
|
1422
|
+
export function suggestRuleForHost(tool, host) {
|
|
1423
|
+
if (ruleToolGrammarOf(tool) !== "domain")
|
|
1424
|
+
return undefined;
|
|
1425
|
+
const parsed = parseRuleText(formatRuleText(host, "host", tool), "allow");
|
|
1426
|
+
if (!("rule" in parsed) || parsed.rule.match !== "host" || parsed.rule.command !== host)
|
|
1427
|
+
return undefined;
|
|
1428
|
+
return { kind: "single", rule: parsed.rule.rule, match: "host", command: host };
|
|
1429
|
+
}
|
|
1132
1430
|
export function suggestRuleForName(tool, name) {
|
|
1133
1431
|
if (ruleToolGrammarOf(tool) !== "name")
|
|
1134
1432
|
return undefined;
|
|
@@ -1163,6 +1461,7 @@ export function segmentCoverageOf(command, rules, call) {
|
|
|
1163
1461
|
});
|
|
1164
1462
|
}
|
|
1165
1463
|
export const RULE_OFFER_BATCH_MEMBER_KINDS = Object.freeze(["command", "directoryRead"]);
|
|
1464
|
+
export const RULE_OFFER_KINDS = Object.freeze(["single", "batch"]);
|
|
1166
1465
|
export const UNCOVERED_SEGMENT_REASONS = Object.freeze(["redirection", "no_rule_form", "cap_overflow"]);
|
|
1167
1466
|
export const UNCOVERED_SEGMENT_REASON_BASELINE = {
|
|
1168
1467
|
redirection: "This part carries a redirection, so it is approved per use — no rule can cover this spelling. If you trust its redirection-free form, a rule can be written for that form (the card's edit row, or settings).",
|