@sema-agent/core 7.48.0 → 7.49.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 +418 -0
- package/README.md +2 -2
- package/dist/agents/cascade.d.ts +2 -5
- package/dist/agents/child-failure.d.ts +20 -0
- package/dist/agents/child-failure.js +28 -0
- package/dist/agents/cross-session-ref.d.ts +11 -3
- package/dist/agents/cross-session-ref.js +6 -0
- package/dist/agents/cumulative-stats.d.ts +12 -15
- package/dist/agents/cumulative-stats.js +1 -1
- package/dist/agents/extra-tool-names.js +1 -1
- package/dist/agents/inherited-tool-pool.d.ts +17 -13
- package/dist/agents/inherited-tool-pool.js +4 -3
- package/dist/agents/peer-session-drain.d.ts +3 -2
- package/dist/agents/peer-session-drain.js +5 -4
- package/dist/agents/send-message-tool.js +2 -9
- package/dist/agents/subagent.d.ts +10 -29
- package/dist/agents/subagent.js +65 -53
- package/dist/agents/suspend-guard.d.ts +1 -1
- package/dist/agents/tool-filter.d.ts +76 -65
- package/dist/agents/tool-filter.js +122 -92
- package/dist/agents/verify.d.ts +4 -7
- package/dist/agents/verify.js +6 -7
- package/dist/bench/metrics.d.ts +1 -2
- package/dist/brain/anthropic.js +133 -79
- package/dist/brain/errors.d.ts +53 -6
- package/dist/brain/errors.js +22 -2
- package/dist/brain/input-too-long.d.ts +40 -19
- package/dist/brain/input-too-long.js +3 -16
- package/dist/brain/open-responses.js +19 -10
- package/dist/brain/openai.js +123 -32
- package/dist/brain/provider-dialect.d.ts +214 -0
- package/dist/brain/provider-dialect.js +186 -0
- package/dist/brain/request-params.d.ts +27 -8
- package/dist/brain/request-params.js +7 -12
- package/dist/brain/request-route.d.ts +84 -0
- package/dist/brain/request-route.js +41 -0
- package/dist/brain/retry.d.ts +8 -0
- package/dist/brain/retry.js +10 -4
- package/dist/brain/route-adjudicator.d.ts +7 -0
- package/dist/brain/route-adjudicator.js +5 -1
- package/dist/brain/status-sink.d.ts +20 -0
- package/dist/brain/status-sink.js +11 -0
- package/dist/brain/stream-engine.d.ts +52 -1
- package/dist/brain/stream-engine.js +81 -24
- package/dist/brain/stream-shared.d.ts +57 -11
- package/dist/brain/stream-shared.js +28 -2
- package/dist/core/a2a.d.ts +1 -0
- package/dist/core/agent-config-dir.js +3 -2
- package/dist/core/allow-entry.d.ts +21 -12
- package/dist/core/allow-entry.js +38 -31
- package/dist/core/arg-summary.d.ts +17 -24
- package/dist/core/arg-summary.js +169 -68
- package/dist/core/ask-origin.d.ts +42 -24
- package/dist/core/ask-origin.js +18 -13
- package/dist/core/auto-mode-prompt-assets.d.ts +2 -2
- package/dist/core/auto-mode-prompt-assets.js +1 -1
- package/dist/core/auto-mode-prompt.d.ts +11 -1
- package/dist/core/auto-mode-prompt.js +13 -10
- package/dist/core/auto-mode-rebuild.d.ts +7 -0
- package/dist/core/auto-mode-rebuild.js +4 -1
- package/dist/core/canonical-json.d.ts +3 -0
- package/dist/core/canonical-json.js +2 -1
- package/dist/core/checkpoint-requirements.d.ts +892 -0
- package/dist/core/checkpoint-requirements.js +333 -0
- package/dist/core/checkpoint-store.d.ts +94 -65
- package/dist/core/checkpoint-store.js +14 -26
- package/dist/core/child-clamp-seats.d.ts +228 -69
- package/dist/core/child-clamp-seats.js +211 -17
- package/dist/core/classifier-unavailable-arm.d.ts +5 -1
- package/dist/core/classifier-unavailable-arm.js +3 -3
- package/dist/core/compliance.d.ts +3 -0
- package/dist/core/compliance.js +9 -4
- package/dist/core/credential-vocabulary.d.ts +140 -0
- package/dist/core/credential-vocabulary.js +131 -0
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/effective-path-target.d.ts +73 -16
- package/dist/core/effective-path-target.js +63 -33
- package/dist/core/engine-notice.d.ts +32 -7
- package/dist/core/file-history-store.js +2 -2
- package/dist/core/fs-write-gate-policy.d.ts +4 -0
- package/dist/core/fs-write-gate-policy.js +22 -5
- package/dist/core/gate-bounded-await.d.ts +28 -13
- package/dist/core/gate-bounded-await.js +41 -4
- package/dist/core/gate-fold.d.ts +1 -1
- package/dist/core/gate-fold.js +24 -25
- package/dist/core/gate-lanes.js +16 -15
- package/dist/core/gate-outcome.d.ts +20 -5
- package/dist/core/gate-outcome.js +5 -0
- package/dist/core/governance-codes.d.ts +11 -4
- package/dist/core/governance-codes.js +14 -3
- package/dist/core/hooks.d.ts +4 -0
- package/dist/core/locked-config.d.ts +8 -0
- package/dist/core/locked-config.js +14 -2
- package/dist/core/lsp.js +3 -5
- package/dist/core/mailbox-store.d.ts +3 -4
- package/dist/core/mcp-connection-pool.d.ts +75 -0
- package/dist/core/mcp-connection-pool.js +128 -0
- package/dist/core/mcp-failure.d.ts +2 -1
- package/dist/core/mcp-failure.js +13 -2
- package/dist/core/mcp.d.ts +4 -0
- package/dist/core/mcp.js +70 -43
- package/dist/core/memory-engine/content-origin.js +2 -14
- package/dist/core/memory-engine/engine.d.ts +32 -22
- package/dist/core/memory-engine/engine.js +103 -55
- package/dist/core/memory-engine/file-backend.d.ts +13 -30
- package/dist/core/memory-engine/file-backend.js +37 -252
- package/dist/core/memory-engine/index.d.ts +1 -1
- package/dist/core/memory-engine/index.js +1 -1
- package/dist/core/memory-engine/layout.d.ts +139 -9
- package/dist/core/memory-engine/layout.js +350 -7
- package/dist/core/memory-engine/scope-contract.d.ts +10 -0
- package/dist/core/memory-engine/scope-contract.js +8 -0
- package/dist/core/memory-engine/session-incarnation.d.ts +14 -4
- package/dist/core/memory-engine/session-incarnation.js +5 -2
- package/dist/core/memory-engine/types.d.ts +10 -1
- package/dist/core/memory.js +5 -17
- package/dist/core/model-seat.d.ts +4 -4
- package/dist/core/model-seat.js +1 -1
- package/dist/core/mounted-mcp.d.ts +57 -0
- package/dist/core/mounted-mcp.js +19 -0
- package/dist/core/namespaced-rule-name.d.ts +29 -0
- package/dist/core/namespaced-rule-name.js +52 -0
- package/dist/core/park-selfcheck.d.ts +1 -1
- package/dist/core/park-selfcheck.js +16 -6
- package/dist/core/path-guard-rows.d.ts +99 -0
- package/dist/core/path-guard-rows.js +45 -0
- package/dist/core/path-identity.d.ts +22 -16
- package/dist/core/path-identity.js +10 -10
- package/dist/core/path-rule-binding.d.ts +2 -1
- package/dist/core/path-rule-binding.js +3 -1
- package/dist/core/pause-registry.d.ts +16 -2
- package/dist/core/pause-registry.js +6 -0
- package/dist/core/permission-rule-consent.js +0 -7
- package/dist/core/permission-rule-model.d.ts +68 -24
- package/dist/core/permission-rule-model.js +105 -24
- package/dist/core/permission-rule-store.js +2 -2
- package/dist/core/permission-rules.d.ts +3 -30
- package/dist/core/permission-rules.js +4 -54
- package/dist/core/read-root-candidate.d.ts +1 -3
- package/dist/core/read-root-candidate.js +4 -15
- package/dist/core/real-approval.d.ts +4 -3
- package/dist/core/retired-keys.d.ts +13 -0
- package/dist/core/retired-keys.js +14 -0
- package/dist/core/roles.d.ts +2 -3
- package/dist/core/roles.js +15 -6
- package/dist/core/runner/assemble-result.d.ts +0 -7
- package/dist/core/runner/assemble-result.js +19 -31
- package/dist/core/runner/caller-roster.d.ts +78 -0
- package/dist/core/runner/caller-roster.js +219 -0
- package/dist/core/runner/checkpoint-scope.d.ts +16 -16
- package/dist/core/runner/checkpoint-scope.js +2 -2
- package/dist/core/runner/child-tree.js +2 -2
- package/dist/core/runner/contracts.d.ts +68 -35
- package/dist/core/runner/decide-continuation.d.ts +51 -3
- package/dist/core/runner/decide-continuation.js +76 -21
- package/dist/core/runner/denial-limit-arms.d.ts +17 -5
- package/dist/core/runner/denial-limit-arms.js +11 -9
- package/dist/core/runner/extra-body-dropped-seat.d.ts +8 -0
- package/dist/core/runner/extra-body-dropped-seat.js +22 -0
- package/dist/core/runner/finish-reason-seat.d.ts +9 -0
- package/dist/core/runner/finish-reason-seat.js +40 -0
- package/dist/core/runner/gate-exit.d.ts +44 -32
- package/dist/core/runner/gate-exit.js +112 -25
- package/dist/core/runner/inherited-ask-grants.js +6 -4
- package/dist/core/runner/mcp-redial.d.ts +3 -3
- package/dist/core/runner/mcp-redial.js +3 -4
- package/dist/core/runner/permission-rule-lanes.d.ts +1 -2
- package/dist/core/runner/permission-rule-lanes.js +5 -4
- package/dist/core/runner/prepare-artifact.d.ts +4 -0
- package/dist/core/runner/prepare-artifact.js +2 -1
- package/dist/core/runner/prepare-ask-lane.d.ts +2 -0
- package/dist/core/runner/prepare-ask-lane.js +12 -6
- package/dist/core/runner/prepare-boundary-parks.d.ts +5 -8
- package/dist/core/runner/prepare-boundary-parks.js +4 -7
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +11 -15
- package/dist/core/runner/prepare-caps-and-workflow.js +19 -27
- package/dist/core/runner/prepare-config-doors.d.ts +9 -9
- package/dist/core/runner/prepare-config-doors.js +10 -5
- package/dist/core/runner/prepare-defer-classify.d.ts +2 -2
- package/dist/core/runner/prepare-defer-classify.js +5 -3
- package/dist/core/runner/prepare-delegation-surface.d.ts +1 -1
- package/dist/core/runner/prepare-delegation-surface.js +3 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -4
- package/dist/core/runner/prepare-gate-stations.js +9 -7
- package/dist/core/runner/prepare-hands-readface.d.ts +7 -7
- package/dist/core/runner/prepare-hands-readface.js +7 -7
- package/dist/core/runner/prepare-inherited-gate.d.ts +7 -6
- package/dist/core/runner/prepare-inherited-gate.js +14 -15
- package/dist/core/runner/prepare-listings.d.ts +7 -24
- package/dist/core/runner/prepare-listings.js +4 -5
- package/dist/core/runner/prepare-memory-engine-session.d.ts +2 -0
- package/dist/core/runner/prepare-memory-engine-session.js +4 -3
- package/dist/core/runner/prepare-memory.d.ts +6 -0
- package/dist/core/runner/prepare-memory.js +13 -13
- package/dist/core/runner/prepare-offload-wrappers.d.ts +5 -0
- package/dist/core/runner/prepare-offload-wrappers.js +3 -3
- package/dist/core/runner/prepare-park-ask.d.ts +7 -8
- package/dist/core/runner/prepare-park-ask.js +17 -10
- package/dist/core/runner/prepare-policy-chain.js +24 -44
- package/dist/core/runner/prepare-project-context.d.ts +1 -1
- package/dist/core/runner/prepare-project-context.js +3 -4
- package/dist/core/runner/prepare-protocol-tools.d.ts +9 -3
- package/dist/core/runner/prepare-protocol-tools.js +3 -4
- package/dist/core/runner/prepare-run-refs.d.ts +3 -0
- package/dist/core/runner/prepare-run-refs.js +2 -1
- package/dist/core/runner/prepare-safety-scan.d.ts +4 -8
- package/dist/core/runner/prepare-safety-scan.js +1 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +7 -1
- package/dist/core/runner/prepare-suspend-saga.js +28 -12
- package/dist/core/runner/prepare-task.d.ts +1 -1
- package/dist/core/runner/prepare-task.js +67 -56
- package/dist/core/runner/prepare-wiring-manifest.d.ts +11 -10
- package/dist/core/runner/prepare-wiring-manifest.js +5 -8
- package/dist/core/runner/resume-apply.js +7 -14
- package/dist/core/runner/resume-checkpoint-screen.js +11 -4
- package/dist/core/runner/resume-internals-and-config.js +2 -2
- package/dist/core/runner/resume-policy-outcome.js +5 -32
- package/dist/core/runner/resume-review-outcome.js +4 -1
- package/dist/core/runner/retired-runner-deps.d.ts +6 -0
- package/dist/core/runner/retired-runner-deps.js +10 -1
- package/dist/core/runner/run-brain-sinks.d.ts +4 -0
- package/dist/core/runner/run-brain-sinks.js +5 -2
- package/dist/core/runner/run-harness-handlers.js +27 -34
- package/dist/core/runner/run-leg.d.ts +0 -2
- package/dist/core/runner/run-leg.js +32 -33
- package/dist/core/runner/run-terminal-adoption.js +4 -3
- package/dist/core/runner/runtask.js +8 -8
- package/dist/core/runner/session-rule-policy.js +5 -5
- package/dist/core/runner/stream-settle-backstop.js +7 -4
- package/dist/core/runner/sub-agent-deny-lists.d.ts +51 -12
- package/dist/core/runner/sub-agent-deny-lists.js +111 -80
- package/dist/core/runner/terminal-account.d.ts +48 -0
- package/dist/core/runner/terminal-account.js +186 -0
- package/dist/core/runner/tool-end-body.d.ts +54 -17
- package/dist/core/runner/tool-end-body.js +27 -2
- package/dist/core/runner/tool-face-overlay.d.ts +3 -1
- package/dist/core/runner/tool-face-overlay.js +6 -4
- package/dist/core/runner/tool-occurrence.d.ts +106 -0
- package/dist/core/runner/tool-occurrence.js +123 -0
- package/dist/core/runner/tool-progress-lane.d.ts +16 -8
- package/dist/core/runner/tool-progress-lane.js +25 -34
- package/dist/core/runner/turn-attachments.d.ts +1 -1
- package/dist/core/runner/write-protection-station.d.ts +21 -9
- package/dist/core/runner/write-protection-station.js +20 -17
- package/dist/core/runner-deps.d.ts +12 -22
- package/dist/core/secret-env.d.ts +16 -5
- package/dist/core/secret-env.js +23 -63
- package/dist/core/sensitive-path-policy.d.ts +19 -18
- package/dist/core/sensitive-path-policy.js +73 -49
- package/dist/core/session-reconcile.d.ts +3 -0
- package/dist/core/session-reconcile.js +5 -4
- package/dist/core/session-store.d.ts +5 -0
- package/dist/core/session-store.js +12 -0
- package/dist/core/session.d.ts +13 -0
- package/dist/core/shared-memory/normalize.d.ts +17 -4
- package/dist/core/shared-memory/normalize.js +7 -7
- package/dist/core/shell-lexer.js +5 -4
- package/dist/core/skill-grant.js +1 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +13 -0
- package/dist/core/store-contracts/mailbox-store-contract.d.ts +7 -14
- package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +27 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.js +92 -0
- package/dist/core/stub-env.d.ts +2 -0
- package/dist/core/stub-env.js +1 -0
- package/dist/core/synthetic-read-verdict.js +2 -2
- package/dist/core/task-event.d.ts +59 -5
- package/dist/core/task-result.d.ts +1 -1
- package/dist/core/task-spec.d.ts +42 -9
- package/dist/core/terminal-cause.d.ts +94 -11
- package/dist/core/tighten-task-spec.d.ts +5 -4
- package/dist/core/tighten-task-spec.js +20 -25
- package/dist/core/tool-catalog-entries.d.ts +5 -7
- package/dist/core/tool-catalog-entries.js +14 -15
- package/dist/core/tool-face.d.ts +47 -0
- package/dist/core/tool-face.js +5 -0
- package/dist/core/tool-model-gate.d.ts +5 -5
- package/dist/core/tool-name-aliases.d.ts +26 -2
- package/dist/core/tool-name-aliases.js +13 -0
- package/dist/core/tool-name-list.d.ts +30 -0
- package/dist/core/tool-name-list.js +55 -0
- package/dist/core/tool-name-readings.d.ts +1 -9
- package/dist/core/tool-name-readings.js +1 -3
- package/dist/core/tool-policy.d.ts +195 -85
- package/dist/core/tool-policy.js +165 -380
- package/dist/core/tool-registry.d.ts +44 -1
- package/dist/core/tool-registry.js +26 -0
- package/dist/core/tool-roster.d.ts +19 -1
- package/dist/core/tool-roster.js +21 -2
- package/dist/core/tool-spec-members.d.ts +45 -0
- package/dist/core/tool-spec-members.js +169 -0
- package/dist/core/tool-spec.d.ts +46 -8
- package/dist/core/tools.d.ts +6 -2
- package/dist/core/tools.js +8 -4
- package/dist/core/untrusted-egress.js +29 -26
- package/dist/core/untrusted-text.d.ts +13 -10
- package/dist/core/untrusted-text.js +6 -44
- package/dist/core/workflow-journal-store.d.ts +4 -1
- package/dist/core/write-protect.d.ts +65 -29
- package/dist/core/write-protect.js +235 -177
- package/dist/engine/compaction/compaction.d.ts +2 -2
- package/dist/engine/execution-env/node-execution-env.d.ts +5 -23
- package/dist/engine/execution-env/node-execution-env.js +16 -38
- package/dist/engine/execution-env/symlink-chain.d.ts +23 -0
- package/dist/engine/execution-env/symlink-chain.js +32 -0
- package/dist/engine/harness/types.d.ts +7 -0
- package/dist/engine/llm/tool-turns.d.ts +3 -0
- package/dist/engine/llm/tool-turns.js +1 -0
- package/dist/engine/llm/types.d.ts +59 -23
- package/dist/engine/loop/agent-loop.js +16 -2
- package/dist/engine/session/memory-repo.js +11 -3
- package/dist/engine/session/repo-utils.d.ts +8 -0
- package/dist/engine/session/repo-utils.js +13 -0
- package/dist/index.d.ts +20 -23
- package/dist/index.js +17 -20
- package/dist/internal/harness.d.ts +1 -1
- package/dist/internal/harness.js +1 -1
- package/dist/internal/llm.d.ts +2 -2
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/governance-baseline-validity.js +1 -1
- package/dist/orchestration/run-spec.js +3 -3
- package/dist/orchestration/run-workflow-tool.d.ts +16 -35
- package/dist/orchestration/run-workflow-tool.js +38 -78
- package/dist/orchestration/workflow-governance.d.ts +21 -25
- package/dist/orchestration/workflow-governance.js +38 -78
- package/dist/orchestration/workflow-primitives.d.ts +7 -22
- package/dist/orchestration/workflow-primitives.js +5 -12
- package/dist/orchestration/workflow-types.d.ts +1 -1
- package/dist/orchestration/workflow.d.ts +61 -11
- package/dist/orchestration/workflow.js +189 -117
- package/dist/scenarios/full-body.d.ts +1 -1
- package/dist/shell/lex.d.ts +0 -21
- package/dist/shell/lex.js +250 -68
- package/dist/shell/shell-words.d.ts +35 -0
- package/dist/shell/shell-words.js +65 -0
- package/dist/stores/file/checkpoint-store.d.ts +5 -5
- package/dist/stores/file/checkpoint-store.js +5 -2
- package/dist/stores/file/index.d.ts +0 -8
- package/dist/stores/file/index.js +0 -12
- package/dist/stores/file/session-store.d.ts +3 -0
- package/dist/stores/file/session-store.js +44 -19
- package/dist/tools/artifact/artifact-tool.js +2 -0
- package/dist/tools/fs/bash-program-position.d.ts +10 -3
- package/dist/tools/fs/bash-program-position.js +21 -9
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
- package/dist/tools/fs/bash-readonly-classifier.js +8 -7
- package/dist/tools/fs/containment-face.d.ts +4 -0
- package/dist/tools/fs/containment-face.js +3 -0
- package/dist/tools/fs/fs-shared.d.ts +10 -0
- package/dist/tools/fs/fs-shared.js +18 -0
- package/dist/tools/fs/fs-write.js +4 -2
- package/dist/tools/fs/index.d.ts +1 -1
- package/dist/tools/fs/program-read-model.d.ts +39 -18
- package/dist/tools/fs/program-read-model.js +29 -19
- package/dist/tools/fs/program-read-model.rows.d.ts +0 -14
- package/dist/tools/fs/program-read-model.rows.js +5 -5
- package/dist/tools/fs/read-deny-home.d.ts +33 -47
- package/dist/tools/fs/read-deny-home.js +35 -49
- package/dist/tools/fs/read-deny.d.ts +23 -22
- package/dist/tools/fs/read-deny.js +114 -89
- package/dist/tools/fs/removal-targets.d.ts +56 -23
- package/dist/tools/fs/removal-targets.js +65 -87
- package/dist/tools/fs/safety.d.ts +29 -22
- package/dist/tools/fs/safety.js +17 -11
- package/dist/tools/fs/search.js +10 -8
- package/dist/tools/fs/shell-read-argv.js +1 -1
- package/dist/tools/fs/shell-read-bindings.d.ts +169 -37
- package/dist/tools/fs/shell-read-bindings.js +324 -86
- package/dist/tools/fs/shell-read-builtins.d.ts +35 -0
- package/dist/tools/fs/shell-read-builtins.js +43 -0
- package/dist/tools/fs/shell-read-check.d.ts +15 -1
- package/dist/tools/fs/shell-read-check.js +51 -42
- package/dist/tools/fs/shell-read-establish.js +6 -6
- package/dist/tools/fs/shell-read-functions.d.ts +22 -0
- package/dist/tools/fs/shell-read-functions.js +29 -0
- package/dist/tools/fs/shell-read-judge.d.ts +48 -18
- package/dist/tools/fs/shell-read-judge.js +33 -4
- package/dist/tools/fs/shell-read-plan.d.ts +12 -6
- package/dist/tools/fs/shell-read-plan.js +118 -113
- package/dist/tools/fs/shell-read-start.d.ts +29 -6
- package/dist/tools/fs/shell-read-start.js +16 -9
- package/dist/tools/fs/shell-read-word.js +4 -0
- package/dist/tools/fs/shell-read-workdir.d.ts +156 -0
- package/dist/tools/fs/shell-read-workdir.js +281 -0
- package/dist/tools/fs/shell-read-write-targets.d.ts +5 -2
- package/dist/tools/fs/shell-read-write-targets.js +14 -5
- package/dist/tools/fs/shell-reentry-text.d.ts +4 -6
- package/dist/tools/fs/shell-reentry-text.js +3 -11
- package/dist/tools/task-list.d.ts +1 -2
- package/dist/tools/web.js +1 -1
- package/package.json +4 -1
- package/test/export-surface.snapshot.json +107 -143
- package/wire-changes/core@7.49.0.json +4765 -0
- package/dist/agents/teacher.d.ts +0 -159
- package/dist/agents/teacher.js +0 -577
- package/dist/agents/team.d.ts +0 -226
- package/dist/agents/team.js +0 -257
- package/dist/core/strategy-store.d.ts +0 -238
- package/dist/core/strategy-store.js +0 -253
- package/dist/orchestration/builtin-workflows.d.ts +0 -133
- package/dist/orchestration/builtin-workflows.js +0 -159
- package/dist/scenarios/env.d.ts +0 -40
- package/dist/scenarios/env.js +0 -39
- package/dist/scenarios/scenario-registry.d.ts +0 -110
- package/dist/scenarios/scenario-registry.js +0 -140
- package/dist/scenarios/teacher-quickstart.d.ts +0 -40
- package/dist/scenarios/teacher-quickstart.js +0 -12
- package/dist/stores/cc/lockfile.d.ts +0 -10
- package/dist/stores/cc/lockfile.js +0 -98
- package/dist/stores/cc/mailbox-store.d.ts +0 -20
- package/dist/stores/cc/mailbox-store.js +0 -255
- package/dist/stores/cc/roster-adapter.d.ts +0 -12
- package/dist/stores/cc/roster-adapter.js +0 -117
- package/dist/stores/cc/sidecar-transcript.d.ts +0 -97
- package/dist/stores/cc/sidecar-transcript.js +0 -264
- package/dist/stores/cc/task-list-store.d.ts +0 -2
- package/dist/stores/cc/task-list-store.js +0 -125
- package/dist/stores/file/strategy-store.d.ts +0 -97
- package/dist/stores/file/strategy-store.js +0 -341
|
@@ -20,8 +20,9 @@ import type { Checkpoint, CheckpointGate, CheckpointRow, PendingAction, ResumeOu
|
|
|
20
20
|
*
|
|
21
21
|
* - `facet` — the deadline semantic a deployment's reaper applies (see {@link Checkpoint.deadline}):
|
|
22
22
|
* `A` = approval gates, whose `deadline` is an SLA resolve-deny; `B` = unattended-TTL gates, whose
|
|
23
|
-
* `deadline` is an abandonment TTL (`expire`/`reap` only — never a resolve-deny
|
|
24
|
-
* no `tool_end
|
|
23
|
+
* `deadline` is an abandonment TTL (`expire`/`reap` only — never a resolve-deny, so a sweep's word is not a
|
|
24
|
+
* decider on these rows; these pauses produce no `tool_end`, and the one that records its decider — the plan
|
|
25
|
+
* pause — files the settlement on the resolved row alone); `-` = the non-gate park (`task_done`).
|
|
25
26
|
* - `taskStatus` — the `TaskResult.status` word a run assembles when it stops at this gate (approvals and
|
|
26
27
|
* resource slices read `suspended`; the two review pauses read `needs_review`).
|
|
27
28
|
* - `terminal` — `task_done` is the 1C handle / deployment-park convention, not a run pause core mints.
|
|
@@ -79,6 +80,19 @@ export type PendingKind = (typeof PAUSE_REGISTRY)[GateKind]["pending"];
|
|
|
79
80
|
export type ResumeGate = (typeof PAUSE_REGISTRY)[GateKind]["resume"] | "wake";
|
|
80
81
|
/** Runtime twin of {@link GateKind}. */
|
|
81
82
|
export declare function isGateKind(v: unknown): v is GateKind;
|
|
83
|
+
/** The deadline semantic of a parked row — the registry's `facet` column, read with the gate's card: a question parked
|
|
84
|
+
* under an approval gate kind is not an approval, so its deadline voids it (`B`) instead of denying it (`A`). No gate
|
|
85
|
+
* kind of its own (the once-only winner, the reaper deadline and the batch stay one family). Only the literal
|
|
86
|
+
* `"question"` card marks a question. */
|
|
87
|
+
export declare function facetOf(gate: {
|
|
88
|
+
readonly kind: GateKind;
|
|
89
|
+
readonly approvalCard?: unknown;
|
|
90
|
+
}): "A" | "B" | "-";
|
|
91
|
+
/** Is this parked row a question to the user: an approval-kind gate whose card is the reserved `"question"`. */
|
|
92
|
+
export declare function isQuestionGate(gate: {
|
|
93
|
+
readonly kind: GateKind;
|
|
94
|
+
readonly approvalCard?: unknown;
|
|
95
|
+
}): boolean;
|
|
82
96
|
/** The one resume-match predicate: does this outcome gate word answer a checkpoint of this kind? `wake`
|
|
83
97
|
* answers any (it passed its own, stricter, non-gate validation upstream); every other word must be the
|
|
84
98
|
* row's `resume` column. Replaces a six-arm hand-written disjunction. */
|
|
@@ -9,6 +9,12 @@ export const PAUSE_REGISTRY = {
|
|
|
9
9
|
export function isGateKind(v) {
|
|
10
10
|
return typeof v === "string" && Object.prototype.hasOwnProperty.call(PAUSE_REGISTRY, v);
|
|
11
11
|
}
|
|
12
|
+
export function facetOf(gate) {
|
|
13
|
+
return isQuestionGate(gate) ? "B" : PAUSE_REGISTRY[gate.kind].facet;
|
|
14
|
+
}
|
|
15
|
+
export function isQuestionGate(gate) {
|
|
16
|
+
return PAUSE_REGISTRY[gate.kind].pending === "tool_approval" && gate.approvalCard === "question";
|
|
17
|
+
}
|
|
12
18
|
export function resumeGateMatches(gateKind, outcomeGate) {
|
|
13
19
|
return outcomeGate === "wake" || PAUSE_REGISTRY[gateKind].resume === outcomeGate;
|
|
14
20
|
}
|
|
@@ -736,13 +736,6 @@ export async function prepareCcImport(opts) {
|
|
|
736
736
|
skipped.push({ rule: String(entry), reason: `settings ${behavior} entry is not a string` });
|
|
737
737
|
continue;
|
|
738
738
|
}
|
|
739
|
-
if (behavior === "allow" && entry.startsWith("Read(") && "reject" in parseRuleText(entry, behavior)) {
|
|
740
|
-
skipped.push({
|
|
741
|
-
rule: entry,
|
|
742
|
-
reason: "unsupported.form: only the Read(//abs-dir/**) directory form imports in v1 — this entry stays in the settings file, unimported",
|
|
743
|
-
});
|
|
744
|
-
continue;
|
|
745
|
-
}
|
|
746
739
|
if (!/^[A-Za-z][A-Za-z0-9_]*\(.*\)$/.test(entry)) {
|
|
747
740
|
const reason = /^[A-Za-z][A-Za-z0-9_-]*$/.test(entry)
|
|
748
741
|
? "unsupported.form: a bare tool-name entry is a name-set item, not a command rule — it stays in the settings file, unimported"
|
|
@@ -118,12 +118,8 @@ export declare const RULE_BEHAVIORS_BY_PRECEDENCE: readonly RuleBehavior[];
|
|
|
118
118
|
* fabricated tightening is still a fabrication).
|
|
119
119
|
*/
|
|
120
120
|
export declare function readRowBehavior(v: unknown): RuleBehavior | undefined;
|
|
121
|
-
/** The tools the rule lane speaks for
|
|
122
|
-
*
|
|
123
|
-
* read-authorization a compound card's cd segment mints — and its admission is the CLOSED two-point
|
|
124
|
-
* one on {@link directoryRuleAdmits}: a Read tool call under the directory, and a compound's cd
|
|
125
|
-
* segment resolving into it. Nothing else — never a write, never a whole Bash command, never a
|
|
126
|
-
* non-cd segment.
|
|
121
|
+
/** The tools the rule lane speaks for — derived per grammar ({@link ruleToolGrammarOf}): the command word, every catalog
|
|
122
|
+
* path tool, the name tools, the domain tools.
|
|
127
123
|
*
|
|
128
124
|
* design/276 §3.3 (doctrine, for whoever widens this set): a future `SendMessage(…)`-form rule MAY
|
|
129
125
|
* clear the peer-referral ask — that ask is classifier hesitation by construction (#144: allow rules
|
|
@@ -135,7 +131,7 @@ export type PersistedRuleTool = string;
|
|
|
135
131
|
* A rule word, not a tool list: the calls a command rule reaches are the SHELL FACE's — every catalog definition whose
|
|
136
132
|
* `command` the command grammar judges ({@link laneToolNamesOf}). */
|
|
137
133
|
export declare const COMMAND_RULE_TOOL = "Bash";
|
|
138
|
-
/** The
|
|
134
|
+
/** The read face's rule word: a `Read(<path>)` rule speaks for every call that reads a path ({@link PATH_RULE_FACES}). */
|
|
139
135
|
export declare const READ_RULE_TOOL = "Read";
|
|
140
136
|
/**
|
|
141
137
|
* The path rules that speak for a FACE rather than for one tool — one table, one column per tightening behavior (the
|
|
@@ -150,8 +146,13 @@ export declare const READ_RULE_TOOL = "Read";
|
|
|
150
146
|
* results and reach the shell's file commands; an ask is a question asked at the gate before the call runs, so it is
|
|
151
147
|
* judged there, on the call's own path target only — it does not descend (a search rooted above the asked path is not
|
|
152
148
|
* asked, and its results are not filtered).
|
|
149
|
+
*
|
|
150
|
+
* The ALLOW column is the reference's too: an allow by path is read on these two tools only — a `Read(<path>)` allow speaks
|
|
151
|
+
* for the read face, an `Edit(<path>)` allow for the write face and the read face (an edit allowed is a read allowed) — and
|
|
152
|
+
* an allow by path on any other tool has no rule form. It is judged at the gate with the narrow reading
|
|
153
|
+
* ({@link adjudicatePersistedPathRules}): every view of the call's target must lie where the rule names.
|
|
153
154
|
*/
|
|
154
|
-
export declare const PATH_RULE_FACES: Readonly<Record<string, Readonly<Partial<Record<
|
|
155
|
+
export declare const PATH_RULE_FACES: Readonly<Record<string, Readonly<Partial<Record<RuleBehavior, readonly DenyFace[]>>>>>;
|
|
155
156
|
/** The face a call's protective path target is on: `read` for a read slot, `write` for every other access; none without
|
|
156
157
|
* one — the face a {@link PATH_RULE_FACES} rule is judged against, in a rule policy and in the persisted rule lane alike. */
|
|
157
158
|
export declare function callPathFaceOf(call: ToolCallLike): DenyFace | undefined;
|
|
@@ -168,27 +169,44 @@ export declare const NAME_RULE_FACES: Readonly<Record<string, {
|
|
|
168
169
|
readonly param: string;
|
|
169
170
|
readonly params: readonly string[];
|
|
170
171
|
}>>;
|
|
172
|
+
/**
|
|
173
|
+
* The tools whose rule content names the HOST of the URL one parameter carries — the reference's `WebFetch(domain:<host>)`
|
|
174
|
+
* form, one row per tool: `param` is the URL argument the host is read from ({@link domainRuleSubjectOf}). Read by
|
|
175
|
+
* {@link parseDomainRuleContent}; an allow only (see there).
|
|
176
|
+
*/
|
|
177
|
+
export declare const DOMAIN_RULE_FACES: Readonly<Record<string, {
|
|
178
|
+
readonly param: string;
|
|
179
|
+
}>>;
|
|
171
180
|
/**
|
|
172
181
|
* Which tools a rule may name — the model's tool set is DERIVED, never a hand-kept list: the command rule word
|
|
173
182
|
* ({@link COMMAND_RULE_TOOL}) takes the COMMAND grammar — the other shell-face tools take none of their own, their command is
|
|
174
183
|
* judged by the `Bash(…)` rules ({@link laneToolNamesOf}); every tool the catalog declares a path target for (Read / Edit / Write /
|
|
175
|
-
* Glob / NotebookEdit / …, `pathTargetOf`) takes the PATH grammar; the tools of {@link NAME_RULE_FACES} take the NAME grammar
|
|
184
|
+
* Glob / NotebookEdit / …, `pathTargetOf`) takes the PATH grammar; the tools of {@link NAME_RULE_FACES} take the NAME grammar;
|
|
185
|
+
* the tools of {@link DOMAIN_RULE_FACES} the DOMAIN grammar.
|
|
176
186
|
* A rule on any other tool has no content this model can judge and is refused (`unsupported.tool`). The tool set therefore
|
|
177
187
|
* moves with the catalog's declarations, exactly as the protective fences' covered set does.
|
|
178
188
|
*/
|
|
179
|
-
export
|
|
189
|
+
export type RuleGrammar = "command" | "path" | "name" | "domain";
|
|
190
|
+
export declare function ruleToolGrammarOf(toolName: string): RuleGrammar | undefined;
|
|
191
|
+
/**
|
|
192
|
+
* The HOST a domain-grammar tool's call fetches — the string every DOMAIN rule is read against — or undefined: not a domain
|
|
193
|
+
* tool; no URL argument, or one that is not a string; a URL that does not parse, is not http(s), or carries credentials (the
|
|
194
|
+
* fetch tool refuses those itself). The host is the URL parser's (lower case, IDN in its ASCII form, an IPv4 shorthand
|
|
195
|
+
* expanded — the host the fetch connects to), its trailing dots read off.
|
|
196
|
+
*/
|
|
197
|
+
export declare function domainRuleSubjectOf(toolName: string, args: unknown): string | undefined;
|
|
180
198
|
/** The value a name-grammar tool's call carries in its name argument — the string every NAME rule is read against, the one a
|
|
181
199
|
* `Skill` call is resolved by — or undefined (not a name-grammar tool; no such argument; not a string). */
|
|
182
200
|
export declare function nameRuleSubjectOf(toolName: string, args: unknown): string | undefined;
|
|
183
201
|
/** The rule WORDS a call is judged under, once each: for every name the call answers to (the model's spelling, the wire
|
|
184
202
|
* 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
|
|
186
|
-
* tool's its own name — so a stored `Bash(…)` or `Edit(…)` row reaches a tool that answers to that name under another wire
|
|
203
|
+
* grammar's {@link COMMAND_RULE_TOOL} (a `Bash(<command>)` rule reaches `Monitor` as it reaches `Bash`), a path, name or
|
|
204
|
+
* domain tool's its own name — so a stored `Bash(…)` or `Edit(…)` row reaches a tool that answers to that name under another wire
|
|
187
205
|
* 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
206
|
* person's stored rows, the org partition, the allow lists. Empty ⇒ no grammar speaks for the call. */
|
|
189
207
|
export declare function laneToolNamesOf(req: Pick<import("./tool-policy.js").ToolCallRequest, "toolName" | "face">): readonly string[];
|
|
190
208
|
/**
|
|
191
|
-
* Match forms
|
|
209
|
+
* Match forms, one family per grammar (`prefix` and `wildcard` are one predicate):
|
|
192
210
|
* · `exact` — admits the one string the rule spells.
|
|
193
211
|
* · `wildcard` — the TRAILING SPACE-STAR spelling (`Bash(npm run *)`): the body, or the body followed
|
|
194
212
|
* by a space and more, inside the body's own segment count. This is the spelling upstream itself
|
|
@@ -202,15 +220,18 @@ export declare function laneToolNamesOf(req: Pick<import("./tool-policy.js").Too
|
|
|
202
220
|
* compound and importing/minting one as a chain licence would grant what nobody approved.
|
|
203
221
|
* · `subpath` — the directory form (design/382 §2.2): `Tool(//abs-dir/**)`, reaching every path under
|
|
204
222
|
* one absolute directory. Never admits any command; its one predicate is {@link directoryRuleAdmits}.
|
|
205
|
-
* As an ALLOW it exists
|
|
206
|
-
*
|
|
207
|
-
* · `path` — the PATH PATTERN form
|
|
208
|
-
*
|
|
223
|
+
* As an ALLOW it exists on the two face tools ({@link PATH_RULE_FACES}); as a deny/ask on every
|
|
224
|
+
* path-targeting tool.
|
|
225
|
+
* · `path` — the PATH PATTERN form: upstream's file-rule spelling over a tool's path target
|
|
226
|
+
* (`Edit(~/.sema/settings.json)`, `Write(src/**)`, `Read(//etc/*)`), stored AS SPELLED and
|
|
209
227
|
* resolved at match time against the call's bases (`//` absolute, `~/` home, `/` project root,
|
|
210
|
-
* otherwise the working directory). Its predicate is {@link
|
|
211
|
-
*
|
|
228
|
+
* otherwise the working directory). Its predicate is {@link pathRuleReachOf}. As an ALLOW it exists
|
|
229
|
+
* on the two face tools, judged with the narrow reading (every view of the target inside the pattern).
|
|
230
|
+
* · `name` / `name_prefix` — the NAME grammar ({@link parseNameRuleContent}).
|
|
231
|
+
* · `host` / `subdomain` — the DOMAIN grammar ({@link parseDomainRuleContent}): one exact host, or every
|
|
232
|
+
* host below one domain.
|
|
212
233
|
*/
|
|
213
|
-
export type PersistedRuleMatch = "exact" | "prefix" | "wildcard" | "subpath" | "path" | "name" | "name_prefix";
|
|
234
|
+
export type PersistedRuleMatch = "exact" | "prefix" | "wildcard" | "subpath" | "path" | "name" | "name_prefix" | "host" | "subdomain";
|
|
214
235
|
/**
|
|
215
236
|
* Where a rule applies — design/382 §4.1, the THREE consent dimensions.
|
|
216
237
|
*
|
|
@@ -403,6 +424,9 @@ export declare const BARE_INTERPRETER_NAMES: ReadonlySet<string>;
|
|
|
403
424
|
* `! node -e …` is the `node` licence the interpreter table exists to refuse, wearing one extra token.
|
|
404
425
|
* `{`/`}`/`[[`/`]]` are already refused by the floor's argv[0] metacharacter rule; listed anyway,
|
|
405
426
|
* because that rule belongs to another module and is not this lane's to depend on.
|
|
427
|
+
*
|
|
428
|
+
* The words are the shell-word table's `rule_reserved` role (`src/shell/shell-words.ts`): the table is where a word's
|
|
429
|
+
* grammar is stated once, and this lane only reads the projection.
|
|
406
430
|
*/
|
|
407
431
|
export declare const SHELL_RESERVED_WORDS: ReadonlySet<string>;
|
|
408
432
|
/**
|
|
@@ -474,6 +498,10 @@ export declare function escapeForDisclosure(value: unknown): string;
|
|
|
474
498
|
* the scanner while it runs, and the render's own strip would then glue it back together in clear
|
|
475
499
|
* AFTER the scanner missed it. Strip first, scan, then render: the render's strip is idempotent over
|
|
476
500
|
* text this already stripped, so the final bytes are the same as a single render, minus the leak.
|
|
501
|
+
*
|
|
502
|
+
* It knows nothing of the engine's envelope defusal and needs to know nothing: a defused tag or
|
|
503
|
+
* fence sentinel (untrusted-text.ts) is spelled with printable ASCII, so this pass — and any other
|
|
504
|
+
* format-character cleaning a downstream layer applies to a defused string — leaves it as it was.
|
|
477
505
|
*/
|
|
478
506
|
export declare function stripFormatCharacters(text: string): string;
|
|
479
507
|
/**
|
|
@@ -547,6 +575,9 @@ export declare function formatRuleText(command: string, match: PersistedRuleMatc
|
|
|
547
575
|
/** Does a name rule reach this name? `name`: equal; `name_prefix`: begins with it (no boundary — the reference's `startsWith`).
|
|
548
576
|
* A rule of any other family reaches no name. */
|
|
549
577
|
export declare function nameRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, name: string): boolean;
|
|
578
|
+
/** Does a domain rule reach this host (as {@link domainRuleSubjectOf} reads it)? `host`: equal; `subdomain`: a name ending in
|
|
579
|
+
* `.<domain>` with at least one label before it and no empty label. A rule of any other family reaches no host. */
|
|
580
|
+
export declare function domainRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, host: string): boolean;
|
|
550
581
|
/**
|
|
551
582
|
* The closed set of BASES a path-form pattern resolves against — facts of the CALL (its execution
|
|
552
583
|
* environment and its task), never of the rule:
|
|
@@ -946,16 +977,17 @@ export declare function adjudicatePersistedRules(rules: readonly PersistedRule[]
|
|
|
946
977
|
/**
|
|
947
978
|
* The path-family sibling of {@link adjudicatePersistedRules}: the same precedence walk over the rules
|
|
948
979
|
* of one path-targeting tool, judged on the call's target views by the one judgment every path rule meets
|
|
949
|
-
* (
|
|
980
|
+
* (`judgePathRows`: rows outer, views inner).
|
|
950
981
|
*
|
|
951
982
|
* TWO readings, because the two directions read the call's path slot differently (design/388 B6/B17): the
|
|
952
983
|
* tightening arms judge the PROTECTIVE reading's views (`views.tighten` — the object face's declaration, the catalog's
|
|
953
984
|
* for the name as the floor: reading the catalog for an undeclared same-name tool can only tighten a deny), every row
|
|
954
985
|
* also where its head leads (`leads`, the judging leg's resolution); the allow arm judges the DECLARED reading's views
|
|
955
986
|
* (`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)
|
|
957
|
-
*
|
|
958
|
-
*
|
|
987
|
+
* no grant admits it) — the NARROW reading: a row speaks for a call on a face its {@link PATH_RULE_FACES} allow column
|
|
988
|
+
* holds, and admits it only when it admits EVERY view (a link out of the named place is not admitted through its
|
|
989
|
+
* spelling, a case twin is another spelling, no row is judged where its head leads, an unreadable view is admitted by
|
|
990
|
+
* nothing). Either reading may be absent (no resolvable path in that reading); the arm that needs it then speaks for nothing.
|
|
959
991
|
*
|
|
960
992
|
* 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).
|
|
961
993
|
*
|
|
@@ -989,6 +1021,18 @@ export declare function adjudicatePersistedNameRules(rules: readonly PersistedRu
|
|
|
989
1021
|
sessionId?: string;
|
|
990
1022
|
scopeIdentity?: ScopeRootIdentity;
|
|
991
1023
|
}): PersistedRuleVerdict | undefined;
|
|
1024
|
+
/**
|
|
1025
|
+
* The domain-family sibling of {@link adjudicatePersistedNameRules}: the rules of one domain tool ({@link DOMAIN_RULE_FACES})
|
|
1026
|
+
* judged on the host the call fetches ({@link domainRuleSubjectOf}) by {@link domainRuleAdmits}; the first table-order rule
|
|
1027
|
+
* that is eligible and admits the host is the answer. The grammar has an allow form only, so only an allow can speak.
|
|
1028
|
+
*/
|
|
1029
|
+
export declare function adjudicatePersistedDomainRules(rules: readonly PersistedRule[], call: {
|
|
1030
|
+
tool: string;
|
|
1031
|
+
host: string | undefined;
|
|
1032
|
+
cwd: string | undefined;
|
|
1033
|
+
sessionId?: string;
|
|
1034
|
+
scopeIdentity?: ScopeRootIdentity;
|
|
1035
|
+
}): PersistedRuleVerdict | undefined;
|
|
992
1036
|
/**
|
|
993
1037
|
* The ONE offer a name tool's approval card carries: the exact rule for the name the call carries (`Skill(<name>)`, the reference's
|
|
994
1038
|
* "don't ask again for <skill>" row), or undefined when no rule spells exactly that name (the round-trip law: the canonical text,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
+
import { shellWordsOf } from "../shell/shell-words.js";
|
|
2
3
|
import { parsePermissionRule } from "./permission-rule-syntax.js";
|
|
3
4
|
import { compileReadDeny } from "../tools/fs/read-deny.js";
|
|
4
5
|
import { carriesShellRedirection, parseLeadingCommandName, splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier.js";
|
|
@@ -6,7 +7,7 @@ import { inlineUntrusted } from "./untrusted-text.js";
|
|
|
6
7
|
import { isShellFaceToolName, pathTargetOf, protectivePathTargetOf } from "./tool-registry.js";
|
|
7
8
|
import { heldValues } from "./tool-name-readings.js";
|
|
8
9
|
import { carriesDotSegment, isAbsoluteForFamily, isIdentityDir, isPathIdentity, isShellRootedSpellingUnmapped, isUncIdentity, pathFamilyOf, pathIdentityOf } from "../tools/fs/safety.js";
|
|
9
|
-
import { bindPathRules,
|
|
10
|
+
import { bindPathRules, judgeRuleRows, rowsWhereTheyLead } from "./path-rule-binding.js";
|
|
10
11
|
import { readShellCommand } from "./shell-lexer.js";
|
|
11
12
|
import { readOnlyLineObjection, readOnlyShellVerdict } from "./read-only-shell.js";
|
|
12
13
|
export const RULE_BEHAVIORS = Object.freeze(["deny", "ask", "allow"]);
|
|
@@ -30,8 +31,8 @@ if (!isShellFaceToolName(COMMAND_RULE_TOOL))
|
|
|
30
31
|
throw new Error(`permission rules: the command rule word ${JSON.stringify(COMMAND_RULE_TOOL)} names no definition on the catalog's shell face`);
|
|
31
32
|
export const READ_RULE_TOOL = "Read";
|
|
32
33
|
export const PATH_RULE_FACES = Object.freeze({
|
|
33
|
-
[READ_RULE_TOOL]: Object.freeze({ deny: Object.freeze(["read", "write"]), ask: Object.freeze(["read"]) }),
|
|
34
|
-
Edit: Object.freeze({ deny: Object.freeze(["write"]), ask: Object.freeze(["write"]) }),
|
|
34
|
+
[READ_RULE_TOOL]: Object.freeze({ deny: Object.freeze(["read", "write"]), ask: Object.freeze(["read"]), allow: Object.freeze(["read"]) }),
|
|
35
|
+
Edit: Object.freeze({ deny: Object.freeze(["write"]), ask: Object.freeze(["write"]), allow: Object.freeze(["write", "read"]) }),
|
|
35
36
|
});
|
|
36
37
|
export function callPathFaceOf(call) {
|
|
37
38
|
const target = protectivePathTargetOf(call);
|
|
@@ -43,13 +44,37 @@ export function faceAskSpeaksFor(face) {
|
|
|
43
44
|
export const NAME_RULE_FACES = Object.freeze({
|
|
44
45
|
Skill: Object.freeze({ param: "skill", params: Object.freeze(["skill", "args"]) }),
|
|
45
46
|
});
|
|
47
|
+
export const DOMAIN_RULE_FACES = Object.freeze({
|
|
48
|
+
WebFetch: Object.freeze({ param: "url" }),
|
|
49
|
+
});
|
|
46
50
|
export function ruleToolGrammarOf(toolName) {
|
|
47
51
|
if (toolName === COMMAND_RULE_TOOL)
|
|
48
52
|
return "command";
|
|
49
53
|
if (Object.hasOwn(NAME_RULE_FACES, toolName))
|
|
50
54
|
return "name";
|
|
55
|
+
if (Object.hasOwn(DOMAIN_RULE_FACES, toolName))
|
|
56
|
+
return "domain";
|
|
51
57
|
return pathTargetOf(toolName) !== undefined ? "path" : undefined;
|
|
52
58
|
}
|
|
59
|
+
export function domainRuleSubjectOf(toolName, args) {
|
|
60
|
+
const face = Object.hasOwn(DOMAIN_RULE_FACES, toolName) ? DOMAIN_RULE_FACES[toolName] : undefined;
|
|
61
|
+
if (face === undefined || typeof args !== "object" || args === null || Array.isArray(args) || !Object.hasOwn(args, face.param))
|
|
62
|
+
return undefined;
|
|
63
|
+
const value = args[face.param];
|
|
64
|
+
if (typeof value !== "string")
|
|
65
|
+
return undefined;
|
|
66
|
+
let url;
|
|
67
|
+
try {
|
|
68
|
+
url = new URL(value);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
if ((url.protocol !== "http:" && url.protocol !== "https:") || url.username !== "" || url.password !== "")
|
|
74
|
+
return undefined;
|
|
75
|
+
const host = url.hostname.replace(/\.+$/, "");
|
|
76
|
+
return host === "" ? undefined : host;
|
|
77
|
+
}
|
|
53
78
|
export function nameRuleSubjectOf(toolName, args) {
|
|
54
79
|
const face = Object.hasOwn(NAME_RULE_FACES, toolName) ? NAME_RULE_FACES[toolName] : undefined;
|
|
55
80
|
if (face === undefined || typeof args !== "object" || args === null || Array.isArray(args) || !Object.hasOwn(args, face.param))
|
|
@@ -83,11 +108,7 @@ export const BARE_INTERPRETER_NAMES = new Set([
|
|
|
83
108
|
"setsid", "flock", "unshare", "nsenter", "script", "numactl", "prlimit", "systemd-run", "parallel",
|
|
84
109
|
"strace", "ltrace", "valgrind", "gdb", "lldb", "perf", "firejail", "bwrap",
|
|
85
110
|
]);
|
|
86
|
-
export const SHELL_RESERVED_WORDS =
|
|
87
|
-
"!", "[[", "]]", "{", "}", "((", "))",
|
|
88
|
-
"case", "esac", "coproc", "do", "done", "elif", "else", "fi", "for",
|
|
89
|
-
"function", "if", "in", "select", "then", "time", "until", "while",
|
|
90
|
-
]);
|
|
111
|
+
export const SHELL_RESERVED_WORDS = shellWordsOf("rule_reserved");
|
|
91
112
|
export const SUGGESTION_LEXICON = [
|
|
92
113
|
"git stash list", "git stash show", "git stash push", "git stash pop", "git stash drop",
|
|
93
114
|
"git remote show", "git worktree list", "git submodule update", "git submodule status",
|
|
@@ -274,8 +295,10 @@ export function parseRuleText(text, behavior) {
|
|
|
274
295
|
const grammar = ruleToolGrammarOf(parsed.toolName);
|
|
275
296
|
if (grammar === "name")
|
|
276
297
|
return parseNameRuleContent(text, parsed.toolName, parsed.ruleContent, behavior);
|
|
298
|
+
if (grammar === "domain")
|
|
299
|
+
return parseDomainRuleContent(text, parsed.toolName, parsed.ruleContent, behavior);
|
|
277
300
|
if (grammar !== "path") {
|
|
278
|
-
return reject("unsupported.tool", `only Bash command rules, rules on path-targeting tools (Read, Edit, Write, …)
|
|
301
|
+
return reject("unsupported.tool", `only Bash command rules, rules on path-targeting tools (Read, Edit, Write, …), Skill name rules and WebFetch domain rules have a rule form in this version (got "${parsed.toolName}")`);
|
|
279
302
|
}
|
|
280
303
|
return parsePathRuleContent(text, parsed.toolName, parsed.ruleContent, behavior);
|
|
281
304
|
}
|
|
@@ -332,6 +355,8 @@ export function formatRuleText(command, match, tool) {
|
|
|
332
355
|
return `${tool}(//${command.startsWith("/") ? command.slice(1) : command}/**)`;
|
|
333
356
|
if (match === "path" || match === "name")
|
|
334
357
|
return `${tool}(${command})`;
|
|
358
|
+
if (match === "host" || match === "subdomain")
|
|
359
|
+
return `${tool}(domain:${match === "subdomain" ? "*." : ""}${command})`;
|
|
335
360
|
return `${tool}(${command}${match === "prefix" || match === "name_prefix" ? ":*" : match === "wildcard" ? " *" : ""})`;
|
|
336
361
|
}
|
|
337
362
|
function parseNameRuleContent(text, tool, content, behavior, verifyCanonical = true) {
|
|
@@ -371,7 +396,60 @@ export function nameRuleAdmits(rule, name) {
|
|
|
371
396
|
return name.startsWith(rule.command);
|
|
372
397
|
return false;
|
|
373
398
|
}
|
|
399
|
+
function parseDomainRuleContent(text, tool, content, behavior) {
|
|
400
|
+
if (behavior !== "allow") {
|
|
401
|
+
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`);
|
|
402
|
+
}
|
|
403
|
+
if (!content.startsWith("domain:")) {
|
|
404
|
+
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}")`);
|
|
405
|
+
}
|
|
406
|
+
const spelled = content.slice("domain:".length);
|
|
407
|
+
if (spelled === "*")
|
|
408
|
+
return reject("unsupported.wildcard", `"${text}" names every host — the whole tool is the bare ${tool} entry`);
|
|
409
|
+
const below = spelled.startsWith("*.");
|
|
410
|
+
const body = below ? spelled.slice(2) : spelled;
|
|
411
|
+
if (body.includes("*")) {
|
|
412
|
+
return reject("unsupported.wildcard", `a \`*\` in "${text}" other than a leading \`*.\` has no rule form — a ${tool} rule names one host, or every host below one domain with \`*.<domain>\``);
|
|
413
|
+
}
|
|
414
|
+
const host = hostOfSpelling(body);
|
|
415
|
+
if (host === undefined || hostOfSpelling(host) !== host) {
|
|
416
|
+
return reject("invalid.grammar", `"${text}" does not name a host alone — write the host as a URL carries it, with no scheme, path, port or credentials`);
|
|
417
|
+
}
|
|
418
|
+
if (below && isAddressHost(host))
|
|
419
|
+
return reject("invalid.grammar", `"${text}" names hosts below an address, which has none — write the address itself`);
|
|
420
|
+
const match = below ? "subdomain" : "host";
|
|
421
|
+
return { rule: { behavior, rule: formatRuleText(host, match, tool), tool, match, command: host } };
|
|
422
|
+
}
|
|
423
|
+
function hostOfSpelling(spelled) {
|
|
424
|
+
if (spelled === "" || /[\s/\\@?#%]/.test(spelled) || (spelled.includes(":") && !/^\[[^\]]+\]$/.test(spelled)))
|
|
425
|
+
return undefined;
|
|
426
|
+
let url;
|
|
427
|
+
try {
|
|
428
|
+
url = new URL(`https://${spelled}/`);
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
return undefined;
|
|
432
|
+
}
|
|
433
|
+
if (url.port !== "" || url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "")
|
|
434
|
+
return undefined;
|
|
435
|
+
const host = url.hostname.replace(/\.+$/, "");
|
|
436
|
+
return host === "" ? undefined : host;
|
|
437
|
+
}
|
|
438
|
+
function isAddressHost(host) {
|
|
439
|
+
return host.startsWith("[") || /^\d+\.\d+\.\d+\.\d+$/.test(host);
|
|
440
|
+
}
|
|
441
|
+
export function domainRuleAdmits(rule, host) {
|
|
442
|
+
if (rule.match === "host")
|
|
443
|
+
return host === rule.command;
|
|
444
|
+
if (rule.match !== "subdomain" || isAddressHost(host) || !host.endsWith(`.${rule.command}`))
|
|
445
|
+
return false;
|
|
446
|
+
const labels = host.slice(0, -(rule.command.length + 1));
|
|
447
|
+
return labels !== "" && !labels.split(".").includes("");
|
|
448
|
+
}
|
|
374
449
|
function parsePathRuleContent(text, tool, content, behavior) {
|
|
450
|
+
if (behavior === "allow" && PATH_RULE_FACES[tool]?.allow === undefined) {
|
|
451
|
+
return reject("unsupported.tool", `an allow by path is read on Read and Edit rules only — an Edit rule speaks for every tool that edits a file, a Read rule for every tool that reads one ("${text}"); write Edit(<path>) or Read(<path>)`);
|
|
452
|
+
}
|
|
375
453
|
const dirForm = /^\/\/(.+)\/\*\*$/.exec(content);
|
|
376
454
|
if (dirForm !== null) {
|
|
377
455
|
const bodyR = dirForm[1];
|
|
@@ -385,17 +463,9 @@ function parsePathRuleContent(text, tool, content, behavior) {
|
|
|
385
463
|
if (segments.some((sg) => sg === "." || sg === "..")) {
|
|
386
464
|
return reject("invalid.path_traversal", `the directory body of "${text}" carries a \`.\` or \`..\` segment — the lexical normal form is the only accepted spelling, and a traversal segment is not normalized away`);
|
|
387
465
|
}
|
|
388
|
-
if (behavior === "allow" && tool !== READ_RULE_TOOL) {
|
|
389
|
-
return reject("unsupported.tool", `an allow rule on a path tool speaks for the Read directory form only — a standing approval of a WRITE by directory is not a rule form ("${text}"); write it as a deny or an ask, or approve the call`);
|
|
390
|
-
}
|
|
391
466
|
const dir = "/" + bodyR;
|
|
392
467
|
return { rule: { behavior, rule: formatRuleText(dir, "subpath", tool), tool, match: "subpath", command: dir } };
|
|
393
468
|
}
|
|
394
|
-
if (behavior === "allow") {
|
|
395
|
-
return tool === READ_RULE_TOOL
|
|
396
|
-
? reject("invalid.grammar", `only the Read(//abs-dir/**) directory form is a rule this version reads ("${text}") — relative, single-file, single-slash and no-tail spellings have no rule form`)
|
|
397
|
-
: reject("unsupported.tool", `an allow rule on "${tool}" has no rule form — only the Read directory form is a standing approval by path ("${text}"); write it as a deny or an ask, or approve the call`);
|
|
398
|
-
}
|
|
399
469
|
if (content.trim() !== content || content === "") {
|
|
400
470
|
return reject("invalid.grammar", `the path pattern of "${text}" is empty or carries surrounding whitespace — the pattern is stored as spelled, so the spelling must be the one meant`);
|
|
401
471
|
}
|
|
@@ -458,7 +528,7 @@ function resolvePathPattern(pattern, bases, behavior, treeFamily = pathFamilyOf(
|
|
|
458
528
|
}
|
|
459
529
|
const needed = ruleBasesNeeded({ match: "path", command: pattern })[0];
|
|
460
530
|
const baseValue = baseValueOf(needed, bases);
|
|
461
|
-
const base = baseValue !== undefined && isAbsoluteForFamily(family, baseValue) ? pathIdentityOf(baseValue, family, tree) : undefined;
|
|
531
|
+
const base = baseValue !== undefined && isAbsoluteForFamily(family ?? "own", baseValue) ? pathIdentityOf(baseValue, family, tree) : undefined;
|
|
462
532
|
if (base === undefined || !base.ok)
|
|
463
533
|
return { missingBase: needed };
|
|
464
534
|
const normal = base.identity;
|
|
@@ -470,7 +540,7 @@ function resolvePathPattern(pattern, bases, behavior, treeFamily = pathFamilyOf(
|
|
|
470
540
|
return { resolved: (normal === "/" ? "" : normal) + "/" + body };
|
|
471
541
|
}
|
|
472
542
|
export function isUsablePathBase(base, family) {
|
|
473
|
-
return base !== undefined && isAbsoluteForFamily(family, base) && pathIdentityOf(base, family, base).ok;
|
|
543
|
+
return base !== undefined && isAbsoluteForFamily(family ?? "own", base) && pathIdentityOf(base, family, base).ok;
|
|
474
544
|
}
|
|
475
545
|
function segmentGlobMatches(glob, segment) {
|
|
476
546
|
if (!glob.includes("*"))
|
|
@@ -790,7 +860,7 @@ function userHomeTopLevel() {
|
|
|
790
860
|
}
|
|
791
861
|
return HOME_TOP_LEVEL === null ? undefined : HOME_TOP_LEVEL;
|
|
792
862
|
}
|
|
793
|
-
const CURSOR_MOVING_HEADS =
|
|
863
|
+
const CURSOR_MOVING_HEADS = shellWordsOf("base_mover");
|
|
794
864
|
const OPAQUE_CURSOR_MOVERS = new Set(["eval", "source", ".", "trap", "enable", "builtin", "command", "time"]);
|
|
795
865
|
function pushdPopdLeavesCursor(head, args) {
|
|
796
866
|
for (const a of args) {
|
|
@@ -817,7 +887,7 @@ function movesShellCursor(segment) {
|
|
|
817
887
|
return true;
|
|
818
888
|
if (!CURSOR_MOVING_HEADS.has(head))
|
|
819
889
|
return false;
|
|
820
|
-
return !((
|
|
890
|
+
return !(shellWordsOf("stack_mover").has(head) && pushdPopdLeavesCursor(head, words.slice(1)));
|
|
821
891
|
}
|
|
822
892
|
function resolveCdSegmentDirectories(segments, cwd) {
|
|
823
893
|
let priorCd = false;
|
|
@@ -934,9 +1004,11 @@ export function adjudicatePersistedPathRules(rules, call, views, anchorsOf, lead
|
|
|
934
1004
|
const bind = (rows) => bindPathRules(rows.map((rule) => ({ parsed: rule, ruleText: rule.rule, rule })), (s) => anchorsOf(s.rule));
|
|
935
1005
|
for (const behavior of RULE_BEHAVIORS_BY_PRECEDENCE) {
|
|
936
1006
|
if (behavior === "allow") {
|
|
937
|
-
|
|
1007
|
+
const face = call.face;
|
|
1008
|
+
if (views.allow === undefined || face === undefined)
|
|
938
1009
|
continue;
|
|
939
|
-
const
|
|
1010
|
+
const allowing = rules.filter((rule) => (rule.match === "path" || rule.match === "subpath") && PATH_RULE_FACES[rule.tool]?.allow?.includes(face) === true && eligiblePersisted(rule, { ...call, tool: rule.tool }, behavior));
|
|
1011
|
+
const hit = judgeRuleRows(bind(allowing), views.allow, "allow");
|
|
940
1012
|
if (hit !== undefined)
|
|
941
1013
|
return { behavior, rules: [hit.row.spec.rule] };
|
|
942
1014
|
continue;
|
|
@@ -946,7 +1018,7 @@ export function adjudicatePersistedPathRules(rules, call, views, anchorsOf, lead
|
|
|
946
1018
|
const rows = rules.filter((rule) => (rule.match === "path" || rule.match === "subpath") && speaks(rule, behavior));
|
|
947
1019
|
if (rows.length === 0)
|
|
948
1020
|
continue;
|
|
949
|
-
const hit =
|
|
1021
|
+
const hit = judgeRuleRows(rowsWhereTheyLead(bind(rows), leads), views.tighten, "tighten");
|
|
950
1022
|
if (hit === undefined)
|
|
951
1023
|
continue;
|
|
952
1024
|
return hit.unreadable !== undefined ? { behavior: "ask", rules: [hit.row.spec.rule], unreadable: hit.unreadable } : { behavior, rules: [hit.row.spec.rule] };
|
|
@@ -964,6 +1036,15 @@ export function adjudicatePersistedNameRules(rules, call) {
|
|
|
964
1036
|
}
|
|
965
1037
|
return undefined;
|
|
966
1038
|
}
|
|
1039
|
+
export function adjudicatePersistedDomainRules(rules, call) {
|
|
1040
|
+
if (call.host === undefined)
|
|
1041
|
+
return undefined;
|
|
1042
|
+
for (const rule of rules) {
|
|
1043
|
+
if (eligiblePersisted(rule, call, "allow") && domainRuleAdmits(rule, call.host))
|
|
1044
|
+
return { behavior: "allow", rules: [rule] };
|
|
1045
|
+
}
|
|
1046
|
+
return undefined;
|
|
1047
|
+
}
|
|
967
1048
|
export function suggestRuleForName(tool, name) {
|
|
968
1049
|
if (ruleToolGrammarOf(tool) !== "name")
|
|
969
1050
|
return undefined;
|
|
@@ -2,7 +2,7 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import { canonicalize } from "./canonical-json.js";
|
|
3
3
|
import { escapeForDisclosure, isRuleBehavior, isRuleLive, parseRuleText } from "./permission-rule-model.js";
|
|
4
4
|
import { thrownText } from "./thrown-value.js";
|
|
5
|
-
import {
|
|
5
|
+
import { validatePath } from "./effective-path-target.js";
|
|
6
6
|
export function sameRuleOwner(a, b) {
|
|
7
7
|
return a.kind === "principal" ? b.kind === "principal" && a.principal === b.principal : b.kind === "local-owner";
|
|
8
8
|
}
|
|
@@ -152,7 +152,7 @@ export function isValidDurableScope(scope) {
|
|
|
152
152
|
return sc?.kind === "global" || (sc?.kind === "project" && isProjectScopeRoot(sc.root));
|
|
153
153
|
}
|
|
154
154
|
export function isProjectScopeRoot(root) {
|
|
155
|
-
return
|
|
155
|
+
return validatePath(root, { intent: "validate-coordinate" }) !== undefined;
|
|
156
156
|
}
|
|
157
157
|
function isValidDot(dot) {
|
|
158
158
|
const d = dot;
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
* `//` absolute, `~/` home, `/` project root, otherwise working-directory relative; `*` within a segment,
|
|
37
37
|
* `**` across) and is judged on the call's PROTECTIVE path target (design/388: the object face's declaration, the
|
|
38
38
|
* catalog's for the name as the floor), every view of it (`callTargetViewsOf`), by the one judgment every path rule
|
|
39
|
-
* meets (`
|
|
39
|
+
* meets (`judgePathRows`). A pattern's ANCHORS are the rule's own fact, taken once where the policy is installed: the
|
|
40
40
|
* declared `pathBases` word by word, else the leg the Runner installs the policy on (`policyAnchors`) — never the
|
|
41
41
|
* call's live working directory. A declared base that is not a usable absolute path is refused at compile time
|
|
42
42
|
* (`unsupported.path_base`); a policy called outside a Runner leg with no declared base reads such a rule
|
|
@@ -158,35 +158,8 @@ export interface PermissionRulePolicyOptions {
|
|
|
158
158
|
*/
|
|
159
159
|
pathBases?: PathRuleBases;
|
|
160
160
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
* Settings layers; the classic two-pointer walk has zero ReDoS surface and the same semantics as CC's
|
|
164
|
-
* anchored `^…$`+dotAll construction: every non-`*` char is literal, `*` spans anything incl. newlines).
|
|
165
|
-
*/
|
|
166
|
-
export declare function wildcardMatch(pattern: string, value: string): boolean;
|
|
167
|
-
/**
|
|
168
|
-
* Does a namespaced RULE name cover `toolName`? — the tail branch of CC's rule matcher `VRp`
|
|
169
|
-
* (:595780), which is UNCONDITIONAL there (its `globMatching` option gates only the whole-name glob
|
|
170
|
-
* branch, so CC's allow lane — `mVo` :595803, which passes no options — matches MCP segments too).
|
|
171
|
-
* Three covering forms, matching CC's three disjuncts (`<ns>` = any protocol-table prefix):
|
|
172
|
-
* · `<ns>__<peer>` — every tool of that peer (rule tool segment absent);
|
|
173
|
-
* · `<ns>__<peer>__*` — the same set, spelled with the star CC's own settings validator
|
|
174
|
-
* recommends (`"MCP rules do not support patterns in parentheses …
|
|
175
|
-
* use mcp__srv__*"`);
|
|
176
|
-
* · `<ns>__<peer>__get_*` — a glob over the tool segment.
|
|
177
|
-
* A LITERAL tool segment is deliberately NOT covered here: it is an exact tool name, and the exact
|
|
178
|
-
* table already answers it — the same split CC makes with its leading `ruleName === toolName`.
|
|
179
|
-
*
|
|
180
|
-
* Rule and target must be in the SAME namespace, and peer segments compare EXACTLY (CC
|
|
181
|
-
* `s.serverName === a.serverName`). A rule must therefore spell the peer the way the mint does; that
|
|
182
|
-
* is the same string an operator reads off any mounted tool name, and inventing a fuzzier comparison
|
|
183
|
-
* here would let one rule reach a peer — or a protocol — it does not name.
|
|
184
|
-
*/
|
|
185
|
-
export declare function namespacedRuleNameCovers(ruleName: string, toolName: string): boolean;
|
|
186
|
-
/** True ⇔ this rule name is a namespaced name whose reach is a SET of tools rather than one exact tool
|
|
187
|
-
* — i.e. the shapes {@link namespacedRuleNameCovers} answers and an exact name table cannot. Every
|
|
188
|
-
* lane that keys rules by exact tool name needs this to know which entries it must NOT key that way. */
|
|
189
|
-
export declare function isNamespacedCoveringRuleName(name: string): boolean;
|
|
161
|
+
import { isNamespacedCoveringRuleName, namespacedRuleNameCovers, wildcardMatch } from "./namespaced-rule-name.js";
|
|
162
|
+
export { isNamespacedCoveringRuleName, namespacedRuleNameCovers, wildcardMatch };
|
|
190
163
|
/**
|
|
191
164
|
* design/388 A-6/7/8/9 (D-3): the per-tool RULE FACE — the parameters a `Tool(param:pattern)` rule may name
|
|
192
165
|
* (`params`, the SCALAR top-level keys ∪ the path target's aliases), the ones the generic lexical matcher must
|