@sema-agent/core 7.55.0 → 7.56.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 +513 -0
- package/dist/agents/cascade.js +1 -1
- package/dist/agents/cc-peer-directory.js +1 -0
- package/dist/agents/child-model-seat.d.ts +0 -11
- package/dist/agents/child-model-seat.js +0 -3
- package/dist/agents/list-agents-tool.d.ts +3 -1
- package/dist/agents/list-agents-tool.js +7 -7
- package/dist/agents/peer-directory.d.ts +22 -4
- package/dist/agents/peer-directory.js +24 -2
- package/dist/agents/peer-session-drain.js +2 -2
- package/dist/agents/repair-loop.js +2 -2
- package/dist/agents/send-message-tool.js +4 -4
- package/dist/agents/subagent.d.ts +6 -4
- package/dist/agents/subagent.js +64 -60
- package/dist/agents/tool-filter.d.ts +7 -5
- package/dist/agents/tool-filter.js +12 -9
- package/dist/agents/verify.d.ts +3 -2
- package/dist/agents/verify.js +3 -3
- package/dist/brain/anthropic.js +30 -73
- package/dist/brain/media-degrade.d.ts +18 -7
- package/dist/brain/media-degrade.js +13 -2
- package/dist/brain/model-capability.d.ts +37 -0
- package/dist/brain/model-capability.js +34 -0
- package/dist/brain/model-presets.d.ts +2 -8
- package/dist/brain/model-presets.js +1 -6
- package/dist/brain/open-responses.js +13 -26
- package/dist/brain/openai.js +14 -87
- package/dist/brain/reasoning-request.d.ts +96 -0
- package/dist/brain/reasoning-request.js +174 -0
- package/dist/brain/reasoning.d.ts +11 -11
- package/dist/brain/reasoning.js +4 -3
- package/dist/brain/request-params.d.ts +7 -4
- package/dist/brain/request-params.js +7 -4
- package/dist/brain/status-sink.d.ts +20 -0
- package/dist/brain/status-sink.js +7 -0
- package/dist/core/a2a.d.ts +2 -1
- package/dist/core/a2a.js +19 -13
- package/dist/core/agent-definition.d.ts +10 -0
- package/dist/core/allow-entry.d.ts +20 -13
- package/dist/core/allow-entry.js +26 -22
- package/dist/core/allow-rule-shadow.d.ts +3 -3
- package/dist/core/ask-origin.d.ts +64 -18
- package/dist/core/ask-origin.js +63 -23
- package/dist/core/ask-question.d.ts +51 -8
- package/dist/core/ask-question.js +3 -4
- package/dist/core/ask-unresolvable-notice.d.ts +8 -3
- package/dist/core/ask-unresolvable-notice.js +16 -2
- package/dist/core/checkpoint-generation.d.ts +53 -0
- package/dist/core/checkpoint-generation.js +35 -0
- package/dist/core/checkpoint-queries.d.ts +180 -0
- package/dist/core/checkpoint-queries.js +272 -0
- package/dist/core/checkpoint-requirements.d.ts +13 -1
- package/dist/core/checkpoint-requirements.js +4 -0
- package/dist/core/checkpoint-store.d.ts +88 -24
- package/dist/core/checkpoint-store.js +24 -9
- package/dist/core/cross-boundary-taint.d.ts +6 -5
- package/dist/core/cross-boundary-taint.js +1 -3
- package/dist/core/decision-receipt.d.ts +37 -3
- package/dist/core/decision-receipt.js +17 -17
- package/dist/core/delegation-not-started.d.ts +6 -0
- package/dist/core/delegation-not-started.js +17 -0
- package/dist/core/engine-attestation.d.ts +93 -0
- package/dist/core/engine-attestation.js +70 -0
- package/dist/core/engine-capabilities.d.ts +4 -1
- package/dist/core/engine-capabilities.js +1 -1
- package/dist/core/engine-notice.d.ts +30 -11
- package/dist/core/file-history-store.d.ts +90 -14
- package/dist/core/file-history-store.js +23 -15
- package/dist/core/gate-fold.d.ts +1 -1
- package/dist/core/gate-fold.js +22 -10
- package/dist/core/gate-lanes.js +73 -64
- package/dist/core/gate-outcome.d.ts +26 -5
- package/dist/core/gate-outcome.js +26 -0
- package/dist/core/governance-codes.d.ts +5 -1
- package/dist/core/governance-codes.js +6 -0
- package/dist/core/hooks.d.ts +17 -16
- package/dist/core/hooks.js +3 -1
- package/dist/core/host-floor.d.ts +128 -0
- package/dist/core/host-floor.js +206 -0
- package/dist/core/host-target.d.ts +38 -3
- package/dist/core/host-target.js +38 -5
- package/dist/core/mcp.d.ts +5 -1
- package/dist/core/mcp.js +67 -41
- package/dist/core/memory-engine/delegation-settlement.js +15 -24
- package/dist/core/memory-engine/engine.js +4 -4
- package/dist/core/memory-engine/layout.d.ts +60 -8
- package/dist/core/memory-engine/layout.js +65 -6
- package/dist/core/model-seat.d.ts +10 -3
- package/dist/core/mods/classic-events.d.ts +67 -0
- package/dist/core/mods/classic-events.js +1 -0
- package/dist/core/mods/dollar.d.ts +13 -0
- package/dist/core/mods/extension-call.d.ts +31 -0
- package/dist/core/mods/extension-call.js +43 -0
- package/dist/core/mods/kernel/dispatch.d.ts +20 -3
- package/dist/core/mods/kernel/dispatch.js +140 -69
- package/dist/core/mods/kernel/events.d.ts +47 -11
- package/dist/core/mods/kernel/events.js +66 -42
- package/dist/core/mods/kernel/index.d.ts +4 -4
- package/dist/core/mods/kernel/index.js +2 -2
- package/dist/core/mods/kernel/receive.d.ts +39 -5
- package/dist/core/mods/kernel/receive.js +290 -17
- package/dist/core/mods/kernel/registration.d.ts +33 -2
- package/dist/core/mods/kernel/registration.js +6 -7
- package/dist/core/mods/kernel/site-rules.d.ts +44 -16
- package/dist/core/mods/kernel/site-rules.js +24 -42
- package/dist/core/mods/spec-seats.d.ts +69 -0
- package/dist/core/mods/spec-seats.js +114 -0
- package/dist/core/mods/stations.d.ts +28 -8
- package/dist/core/mods/stations.js +29 -13
- package/dist/core/mods/tool-call.d.ts +79 -0
- package/dist/core/mods/tool-call.js +152 -0
- package/dist/core/namespaced-rule-name.d.ts +32 -38
- package/dist/core/namespaced-rule-name.js +29 -36
- package/dist/core/parent-spec-seat.d.ts +6 -1
- package/dist/core/parent-spec-seat.js +2 -0
- package/dist/core/park-selfcheck.js +1 -0
- package/dist/core/permission-rule-consent.d.ts +19 -0
- package/dist/core/permission-rule-consent.js +116 -26
- package/dist/core/permission-rule-model.d.ts +14 -3
- package/dist/core/permission-rule-model.js +39 -24
- package/dist/core/permission-rule-org.d.ts +6 -2
- package/dist/core/permission-rule-org.js +7 -3
- package/dist/core/permission-rule-provider.d.ts +5 -2
- package/dist/core/permission-rule-provider.js +41 -11
- package/dist/core/permission-rule-store.d.ts +3 -2
- package/dist/core/permission-rule-store.js +11 -2
- package/dist/core/permission-rule-sync.js +3 -2
- package/dist/core/permission-rule-syntax.d.ts +5 -4
- package/dist/core/permission-rule-syntax.js +2 -2
- package/dist/core/permission-rules.d.ts +4 -4
- package/dist/core/permission-rules.js +11 -20
- package/dist/core/persisted-rule-arms.d.ts +2 -2
- package/dist/core/persisted-rule-arms.js +10 -12
- package/dist/core/principal-name.d.ts +20 -0
- package/dist/core/principal-name.js +22 -0
- package/dist/core/prompt-section-seat.js +4 -6
- package/dist/core/protocol-http-auth.d.ts +26 -5
- package/dist/core/protocol-http-auth.js +137 -4
- package/dist/core/retired-keys.d.ts +16 -0
- package/dist/core/retired-keys.js +16 -4
- package/dist/core/rewrite-rejudge.d.ts +60 -0
- package/dist/core/rewrite-rejudge.js +36 -0
- package/dist/core/roles.d.ts +28 -1
- package/dist/core/roles.js +31 -19
- package/dist/core/rule-lane-reads.d.ts +20 -15
- package/dist/core/rule-lane-reads.js +45 -17
- package/dist/core/rule-owner.d.ts +3 -3
- package/dist/core/rule-owner.js +3 -2
- package/dist/core/runner/announce-once-ledger.d.ts +14 -0
- package/dist/core/runner/announce-once-ledger.js +5 -0
- package/dist/core/runner/assemble-result.d.ts +1 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/call-hook-stations.d.ts +3 -2
- package/dist/core/runner/caller-roster.d.ts +16 -4
- package/dist/core/runner/caller-roster.js +33 -6
- package/dist/core/runner/capability-withheld-seat.d.ts +5 -0
- package/dist/core/runner/capability-withheld-seat.js +24 -0
- package/dist/core/runner/classifier-ladder.d.ts +7 -9
- package/dist/core/runner/classifier-ladder.js +3 -3
- package/dist/core/runner/construction-doors.js +3 -0
- package/dist/core/runner/contracts.d.ts +28 -25
- package/dist/core/runner/cross-boundary-lane.d.ts +17 -13
- package/dist/core/runner/cross-boundary-lane.js +40 -27
- package/dist/core/runner/denial-limit-arms.d.ts +2 -2
- package/dist/core/runner/denial-limit-arms.js +1 -1
- package/dist/core/runner/file-history-request.d.ts +18 -0
- package/dist/core/runner/file-history-request.js +21 -13
- package/dist/core/runner/file-history-restore.d.ts +45 -0
- package/dist/core/runner/file-history-restore.js +46 -0
- package/dist/core/runner/gate-exit.d.ts +3 -3
- package/dist/core/runner/gate-exit.js +4 -3
- package/dist/core/runner/image.d.ts +7 -17
- package/dist/core/runner/image.js +9 -60
- package/dist/core/runner/permission-rule-lanes.d.ts +4 -4
- package/dist/core/runner/permission-rule-lanes.js +26 -10
- package/dist/core/runner/person-path-rows.d.ts +12 -9
- package/dist/core/runner/person-path-rows.js +18 -7
- package/dist/core/runner/prepare-ask-lane.d.ts +2 -2
- package/dist/core/runner/prepare-ask-lane.js +5 -2
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -6
- package/dist/core/runner/prepare-caps-and-workflow.js +4 -3
- package/dist/core/runner/prepare-config-doors.d.ts +29 -38
- package/dist/core/runner/prepare-config-doors.js +42 -25
- package/dist/core/runner/prepare-defer-classify.d.ts +4 -3
- package/dist/core/runner/prepare-defer-classify.js +7 -8
- package/dist/core/runner/prepare-file-history.d.ts +48 -8
- package/dist/core/runner/prepare-file-history.js +7 -39
- package/dist/core/runner/prepare-gate-stations.d.ts +1 -1
- package/dist/core/runner/prepare-gate-stations.js +10 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +2 -8
- package/dist/core/runner/prepare-hands-readface.js +15 -14
- package/dist/core/runner/prepare-inherited-gate.d.ts +6 -3
- package/dist/core/runner/prepare-inherited-gate.js +5 -3
- package/dist/core/runner/prepare-memory-engine-session.js +1 -1
- package/dist/core/runner/prepare-park-ask.js +5 -5
- package/dist/core/runner/prepare-policy-chain.d.ts +6 -3
- package/dist/core/runner/prepare-policy-chain.js +65 -72
- package/dist/core/runner/prepare-prompt-assembly.d.ts +5 -7
- package/dist/core/runner/prepare-prompt-assembly.js +10 -8
- package/dist/core/runner/prepare-prompt-inputs.d.ts +11 -9
- package/dist/core/runner/prepare-prompt-inputs.js +3 -5
- package/dist/core/runner/prepare-protocol-tools.d.ts +2 -0
- package/dist/core/runner/prepare-protocol-tools.js +3 -3
- package/dist/core/runner/prepare-safety-scan.d.ts +1 -1
- package/dist/core/runner/prepare-safety-scan.js +0 -17
- package/dist/core/runner/prepare-suspend-saga.d.ts +2 -0
- package/dist/core/runner/prepare-suspend-saga.js +5 -6
- package/dist/core/runner/prepare-task.d.ts +1 -1
- package/dist/core/runner/prepare-task.js +29 -20
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +7 -4
- package/dist/core/runner/prepare-tool-disclosure-mount.js +20 -16
- package/dist/core/runner/prepare-turn-wiring.d.ts +0 -4
- package/dist/core/runner/prepare-turn-wiring.js +2 -7
- package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -2
- package/dist/core/runner/prepare-wiring-manifest.js +3 -3
- package/dist/core/runner/resume-admission.js +1 -1
- package/dist/core/runner/resume-apply.js +11 -12
- package/dist/core/runner/resume-checkpoint-screen.js +3 -0
- package/dist/core/runner/resume-claim.d.ts +4 -2
- package/dist/core/runner/resume-claim.js +15 -8
- package/dist/core/runner/retired-runner-deps.js +1 -0
- package/dist/core/runner/rewind-files.d.ts +15 -0
- package/dist/core/runner/rewind-files.js +132 -0
- package/dist/core/runner/run-attachment-seats.js +2 -1
- package/dist/core/runner/run-brain-sinks.d.ts +4 -1
- package/dist/core/runner/run-brain-sinks.js +4 -2
- package/dist/core/runner/run-harness-handlers.js +12 -4
- package/dist/core/runner/run-leg.d.ts +1 -1
- package/dist/core/runner/run-leg.js +30 -18
- package/dist/core/runner/run-settle-and-teardown.js +2 -1
- package/dist/core/runner/run-turn-boundary.js +1 -10
- package/dist/core/runner/runtask.d.ts +3 -0
- package/dist/core/runner/runtask.js +8 -9
- package/dist/core/runner/session-read-file-states.d.ts +15 -0
- package/dist/core/runner/session-read-file-states.js +28 -0
- package/dist/core/runner/session-rule-policy.js +4 -3
- package/dist/core/runner/shell-env-withheld.d.ts +33 -0
- package/dist/core/runner/shell-env-withheld.js +51 -0
- package/dist/core/runner/skills-seat.js +6 -2
- package/dist/core/runner/stream-settle-backstop.js +1 -1
- package/dist/core/runner/sub-agent-deny-lists.js +4 -7
- package/dist/core/runner/synthetic-tools.d.ts +6 -0
- package/dist/core/runner/synthetic-tools.js +4 -1
- package/dist/core/runner/tool-disclosure.d.ts +10 -12
- package/dist/core/runner/tool-disclosure.js +1 -3
- package/dist/core/runner/tool-end-body.d.ts +1 -1
- package/dist/core/runner/tool-end-body.js +10 -9
- package/dist/core/runner/tool-not-executed.d.ts +5 -0
- package/dist/core/runner/tool-not-executed.js +11 -0
- package/dist/core/runner/tool-occurrence.d.ts +8 -0
- package/dist/core/runner/tool-occurrence.js +7 -0
- package/dist/core/runner/turn-attachments.d.ts +33 -15
- package/dist/core/runner/turn-attachments.js +62 -37
- package/dist/core/runner-deps.d.ts +5 -5
- package/dist/core/safety-axis-vocab.d.ts +6 -4
- package/dist/core/safety-axis-vocab.js +6 -5
- package/dist/core/safety-merge-corpus.js +11 -3
- package/dist/core/secret-env.d.ts +26 -3
- package/dist/core/secret-env.js +44 -8
- package/dist/core/select-model.js +3 -3
- package/dist/core/session-policy-store.d.ts +27 -15
- package/dist/core/session-policy-store.js +156 -36
- package/dist/core/session-store.d.ts +29 -10
- package/dist/core/session-store.js +51 -19
- package/dist/core/session.d.ts +82 -16
- package/dist/core/session.js +53 -0
- package/dist/core/shell-read-silence.d.ts +11 -12
- package/dist/core/shell-read-silence.js +9 -5
- package/dist/core/shell-run-candidates.js +3 -1
- package/dist/core/shell-wrapper-projection.js +1 -1
- package/dist/core/shell-wrapper-table.d.ts +14 -7
- package/dist/core/shell-wrapper-table.js +5 -4
- package/dist/core/side-query.js +10 -1
- package/dist/core/skill-grant.d.ts +7 -2
- package/dist/core/skill-grant.js +5 -0
- package/dist/core/skills-directory.js +6 -2
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +11 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +140 -2
- package/dist/core/store-contracts/file-history-store-contract.js +31 -1
- package/dist/core/store-contracts/peer-directory-contract.d.ts +7 -2
- package/dist/core/store-contracts/peer-directory-contract.js +14 -2
- package/dist/core/store-contracts/session-repo-contract.js +51 -0
- package/dist/core/store-contracts/session-store-contract.d.ts +2 -1
- package/dist/core/store-contracts/session-store-contract.js +150 -14
- package/dist/core/stub-env.d.ts +2 -0
- package/dist/core/stub-env.js +1 -0
- package/dist/core/synthetic-read-verdict.js +3 -3
- package/dist/core/task-event.d.ts +32 -4
- package/dist/core/task-notification.d.ts +16 -12
- package/dist/core/task-registry.d.ts +9 -0
- package/dist/core/task-registry.js +11 -1
- package/dist/core/task-result.d.ts +18 -0
- package/dist/core/task-spec.d.ts +18 -13
- package/dist/core/tighten-task-spec.js +2 -1
- package/dist/core/tool-catalog-entries.d.ts +17 -0
- package/dist/core/tool-catalog-entries.js +13 -2
- package/dist/core/tool-check-reading.d.ts +11 -0
- package/dist/core/tool-check-reading.js +11 -6
- package/dist/core/tool-model-gate.d.ts +2 -2
- package/dist/core/tool-name-aliases.d.ts +16 -16
- package/dist/core/tool-name-aliases.js +13 -19
- package/dist/core/tool-name-readings.d.ts +5 -35
- package/dist/core/tool-name-readings.js +1 -9
- package/dist/core/tool-policy.d.ts +68 -198
- package/dist/core/tool-policy.js +149 -198
- package/dist/core/tool-result-store.js +3 -0
- package/dist/core/tool-roster.d.ts +14 -19
- package/dist/core/tool-roster.js +11 -44
- package/dist/core/tool-spec-members.js +7 -2
- package/dist/core/tool-spec.d.ts +19 -11
- package/dist/core/tools.js +49 -9
- package/dist/core/trace.d.ts +2 -2
- package/dist/core/types.d.ts +1 -1
- package/dist/core/user-attachment.d.ts +8 -1
- package/dist/core/user-attachment.js +64 -4
- package/dist/core/wiring-manifest.d.ts +9 -0
- package/dist/core/wiring-manifest.js +1 -0
- package/dist/core/workflow-journal-store.js +1 -1
- package/dist/engine/compaction/compaction.js +3 -2
- package/dist/engine/execution-env/node-execution-env.d.ts +7 -3
- package/dist/engine/execution-env/node-execution-env.js +11 -2
- package/dist/engine/harness/agent-harness.d.ts +25 -1
- package/dist/engine/harness/agent-harness.js +54 -7
- package/dist/engine/harness/types.d.ts +29 -2
- package/dist/engine/llm/types.d.ts +28 -11
- package/dist/engine/loop/agent-loop.d.ts +22 -0
- package/dist/engine/loop/agent-loop.js +16 -26
- package/dist/engine/loop/types.d.ts +16 -6
- package/dist/engine/session/memory-repo.d.ts +9 -2
- package/dist/engine/session/memory-repo.js +11 -1
- package/dist/engine/session/repo-utils.d.ts +6 -5
- package/dist/engine/session/repo-utils.js +14 -6
- package/dist/index.d.ts +22 -10
- package/dist/index.js +15 -7
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/internal/llm.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +6 -4
- package/dist/orchestration/run-workflow-tool.js +5 -3
- package/dist/orchestration/workflow.d.ts +3 -0
- package/dist/orchestration/workflow.js +36 -18
- package/dist/prompt-assembly/artifact.d.ts +2 -2
- package/dist/prompt-assembly/artifact.js +5 -3
- package/dist/prompt-assembly/assemble.d.ts +3 -2
- package/dist/prompt-assembly/assemble.js +1 -1
- package/dist/prompt-assembly/catalog/schema.d.ts +43 -12
- package/dist/prompt-assembly/catalog/schema.js +34 -2
- package/dist/prompt-assembly/catalog/sections.js +0 -1
- package/dist/prompt-assembly/composer.d.ts +3 -1
- package/dist/prompt-assembly/composer.js +1 -1
- package/dist/prompt-assembly/event-registry.js +1 -0
- package/dist/prompt-assembly/overlay.js +3 -3
- package/dist/prompt-assembly/renderers.d.ts +1 -1
- package/dist/prompt-assembly/renderers.js +0 -3
- package/dist/prompt-assembly/role-base.d.ts +15 -0
- package/dist/prompt-assembly/role-base.js +9 -0
- package/dist/prompt-assembly/select.d.ts +39 -14
- package/dist/prompt-assembly/select.js +47 -16
- package/dist/prompt-assembly/sugar.d.ts +17 -11
- package/dist/prompt-assembly/sugar.js +8 -3
- package/dist/prompt-assembly/tool-catalog.d.ts +37 -3
- package/dist/prompt-assembly/tool-catalog.js +114 -4
- package/dist/prompt-assembly/turn-snapshot.d.ts +4 -10
- package/dist/prompt-assembly/turn-snapshot.js +0 -3
- package/dist/prompt-assembly/types.d.ts +0 -7
- package/dist/scenarios/full-body.d.ts +11 -11
- package/dist/scenarios/full-body.js +4 -3
- package/dist/shell/lex.js +10 -1
- package/dist/stores/file/adoption/adopt.js +4 -3
- package/dist/stores/file/adoption/marker.js +3 -2
- package/dist/stores/file/checkpoint-store.d.ts +21 -0
- package/dist/stores/file/checkpoint-store.js +38 -6
- package/dist/stores/file/file-history-store.js +6 -7
- package/dist/stores/file/index.js +2 -1
- package/dist/stores/file/permission-rule-adopt.js +4 -3
- package/dist/stores/file/permission-rule-store.js +11 -5
- package/dist/stores/file/session-store.d.ts +29 -6
- package/dist/stores/file/session-store.js +42 -50
- package/dist/tools/artifact/artifact-tool.js +3 -2
- package/dist/tools/fs/bash-program-position.d.ts +19 -2
- package/dist/tools/fs/bash-program-position.js +56 -9
- package/dist/tools/fs/fs-bash.js +5 -5
- package/dist/tools/fs/fs-glob.d.ts +86 -0
- package/dist/tools/fs/fs-glob.js +226 -0
- package/dist/tools/fs/fs-read.js +14 -14
- package/dist/tools/fs/fs-search-tools.js +7 -7
- package/dist/tools/fs/pattern-kill-guard.d.ts +22 -0
- package/dist/tools/fs/pattern-kill-guard.js +397 -0
- package/dist/tools/fs/pdf.d.ts +9 -6
- package/dist/tools/fs/pdf.js +3 -3
- package/dist/tools/fs/program-read-model.d.ts +57 -7
- package/dist/tools/fs/program-read-model.js +11 -1
- package/dist/tools/fs/program-read-model.rows.js +78 -21
- package/dist/tools/fs/read-deny-home.js +3 -2
- package/dist/tools/fs/removal-targets.d.ts +17 -7
- package/dist/tools/fs/removal-targets.js +47 -20
- package/dist/tools/fs/search.d.ts +10 -0
- package/dist/tools/fs/search.js +20 -26
- package/dist/tools/fs/shell-read-argv.js +89 -12
- package/dist/tools/fs/shell-read-bindings.d.ts +31 -0
- package/dist/tools/fs/shell-read-bindings.js +41 -0
- package/dist/tools/fs/shell-read-check.js +6 -19
- package/dist/tools/fs/shell-read-environment.d.ts +5 -1
- package/dist/tools/fs/shell-read-environment.js +3 -2
- package/dist/tools/fs/shell-read-establish.d.ts +15 -0
- package/dist/tools/fs/shell-read-establish.js +19 -20
- package/dist/tools/fs/shell-read-plan.js +24 -16
- package/dist/tools/fs/shell-read-start.d.ts +6 -1
- package/dist/tools/fs/shell-read-write-targets.d.ts +5 -1
- package/dist/tools/fs/shell-read-write-targets.js +2 -3
- package/dist/tools/fs/shell-reentry-text.d.ts +9 -0
- package/dist/tools/fs/shell-reentry-text.js +40 -2
- package/dist/tools/todo.js +63 -63
- package/dist/tools/web.d.ts +19 -14
- package/dist/tools/web.js +38 -55
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +143 -5
- package/wire-changes/core@7.56.0.json +3891 -0
- package/wire-changes/mods-kernel@7.56.0.json +303 -0
|
@@ -460,6 +460,9 @@ export type RuleRejectCode = "invalid.grammar" | "invalid.empty_command" | "inva
|
|
|
460
460
|
export interface RuleReject {
|
|
461
461
|
code: RuleRejectCode;
|
|
462
462
|
message: string;
|
|
463
|
+
/** For a text that is no rule spelling only because of white space (a tab, a no-break space included) around it or before its `(`:
|
|
464
|
+
* the rule spelling it is a blank away from ({@link respelledRuleTextOf}) — named in `message`, never read in its place. */
|
|
465
|
+
respelling?: string;
|
|
463
466
|
}
|
|
464
467
|
/** The parsed, canonical shape of one rule text under one behavior — everything but the scope and the
|
|
465
468
|
* provenance. The behavior is the parser's INPUT (it decides which validator floor applies), carried
|
|
@@ -718,6 +721,13 @@ export declare function ruleContentOf(parsed: ParsedPermissionRule): string | un
|
|
|
718
721
|
* most wants. The tightening behaviors therefore admit the interpreter-headed prefix form and change
|
|
719
722
|
* nothing else; the allow floor is unchanged.
|
|
720
723
|
*/
|
|
724
|
+
/**
|
|
725
|
+
* The rule spelling a text the rule syntax cannot read is a blank away from: the text with the white space (tabs included) around
|
|
726
|
+
* it and before its first `(` taken off, when that reads as a rule under `behavior` — its canonical text — else `undefined`. A text
|
|
727
|
+
* holding an invisible format character never gets here (its one caller refuses it first: `invalid.control_chars`, or `invalid.too_long`). Only what a refusal SAYS: a rule text is read as written ({@link parseRuleText}), so the way out is named, never
|
|
728
|
+
* taken (a door that reads the respelled text would be a second reading of the same text).
|
|
729
|
+
*/
|
|
730
|
+
export declare function respelledRuleTextOf(text: string, behavior: RuleBehavior): string | undefined;
|
|
721
731
|
export declare function parseRuleText(text: string, behavior: RuleBehavior): {
|
|
722
732
|
rule: ParsedRule;
|
|
723
733
|
} | {
|
|
@@ -745,8 +755,8 @@ export type NameRuleReach = "reached" | "not_reached" | "unread";
|
|
|
745
755
|
export declare function nameRuleReach(rule: Pick<PersistedRule, "tool" | "behavior" | "match" | "command">, name: string, values: readonly string[] | undefined): NameRuleReach;
|
|
746
756
|
/** Why a call is unread against a mounted row's rule ({@link nameRuleReach} `unread`) — the sentence every reader carries. */
|
|
747
757
|
export declare function nameValuesUnreadReason(tool: string): string;
|
|
748
|
-
/** Does a domain rule reach this host (as `hostOfUrl` reads it)?
|
|
749
|
-
*
|
|
758
|
+
/** Does a domain rule reach this host (as `hostOfUrl` reads it)? The pattern's own reach ({@link hostPatternAdmits}); a rule of
|
|
759
|
+
* any other family reaches no host. */
|
|
750
760
|
export declare function domainRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, host: string): boolean;
|
|
751
761
|
/**
|
|
752
762
|
* The closed set of BASES a path-form pattern resolves against — facts of the CALL (its execution
|
|
@@ -1255,7 +1265,8 @@ export interface CallHosts {
|
|
|
1255
1265
|
* that does not parse, is not http(s), carries credentials, or names a host that does not read back as itself) is REFUSED by the
|
|
1256
1266
|
* tool's first eligible deny row, whatever host that row names, and otherwise ASKED by its first eligible ask row in the
|
|
1257
1267
|
* fail-closed form (`unreadable`, never a matched ask); no allow row reaches it, and with no deny or ask row it is reached by
|
|
1258
|
-
* nothing.
|
|
1268
|
+
* nothing. A stored row whose text the rule reader reads as no rule reaches this walk from the person's rule lane only as a deny
|
|
1269
|
+
* or an ask (its fields still tighten); the lane answers a call none of the rows refuses as one it cannot judge.
|
|
1259
1270
|
*/
|
|
1260
1271
|
export declare function adjudicatePersistedDomainRules(rules: readonly PersistedRule[], call: {
|
|
1261
1272
|
tool: string;
|
|
@@ -2,12 +2,12 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { shellWordsOf } from "../shell/shell-words.js";
|
|
3
3
|
import { lexShell } from "../shell/lex.js";
|
|
4
4
|
import { readProgramPosition } from "../tools/fs/bash-program-position.js";
|
|
5
|
-
import { parsePermissionRule } from "./permission-rule-syntax.js";
|
|
5
|
+
import { parsePermissionRule, ruleSpellingOf } from "./permission-rule-syntax.js";
|
|
6
6
|
import { compileReadDeny } from "../tools/fs/read-deny.js";
|
|
7
7
|
import { carriesShellRedirection, parseLeadingCommandName, splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier.js";
|
|
8
8
|
import { INLINE_TRUNCATION_MARK, inlineUntrusted } from "./untrusted-text.js";
|
|
9
9
|
import { catalogRuleFaceOf, hostTargetOf, isShellFaceToolName, pathTargetOf, protectivePathTargetOf } from "./tool-registry.js";
|
|
10
|
-
import { HOST_UNREADABLE_SENTENCE,
|
|
10
|
+
import { HOST_UNREADABLE_SENTENCE, hostPatternAdmits, readHostPattern, spelledHostPattern } from "./host-target.js";
|
|
11
11
|
import { heldValues } from "./tool-name-readings.js";
|
|
12
12
|
import { carriesDotSegment, isAbsoluteForFamily, isIdentityDir, isPathIdentity, isShellRootedSpellingUnmapped, isUncIdentity, pathFamilyOf, pathIdentityOf } from "../tools/fs/safety.js";
|
|
13
13
|
import { bindPathRules, judgeRuleRows, rowsWhereTheyLead } from "./path-rule-binding.js";
|
|
@@ -406,6 +406,15 @@ export function ruleContentOf(parsed) {
|
|
|
406
406
|
const content = parsed.ruleContent;
|
|
407
407
|
return content === undefined || (parsed.toolName === COMMAND_RULE_TOOL && foldSpacing(content) === "*") ? undefined : content;
|
|
408
408
|
}
|
|
409
|
+
export function respelledRuleTextOf(text, behavior) {
|
|
410
|
+
const outer = text.trim();
|
|
411
|
+
const open = outer.indexOf("(");
|
|
412
|
+
const candidate = open < 0 ? outer : outer.slice(0, open).replace(/\s+$/u, "") + outer.slice(open);
|
|
413
|
+
if (candidate === text || ruleSpellingOf(candidate) === undefined)
|
|
414
|
+
return undefined;
|
|
415
|
+
const read = parseRuleText(candidate, behavior);
|
|
416
|
+
return "rule" in read ? read.rule.rule : undefined;
|
|
417
|
+
}
|
|
409
418
|
export function parseRuleText(text, behavior) {
|
|
410
419
|
if (!isRuleBehavior(behavior)) {
|
|
411
420
|
return reject("invalid.grammar", `rule "${text}" was parsed under behavior ${escapeForDisclosure(behavior)}, which is not one of ${RULE_BEHAVIORS.join("/")}`);
|
|
@@ -417,7 +426,17 @@ export function parseRuleText(text, behavior) {
|
|
|
417
426
|
if (CONTROL_CHARS_RE.test(text)) {
|
|
418
427
|
return reject("invalid.control_chars", "rule text contains control characters (C0/C1, tab excepted), which cannot be part of a readable command");
|
|
419
428
|
}
|
|
420
|
-
const parsed =
|
|
429
|
+
const parsed = ruleSpellingOf(text);
|
|
430
|
+
if (parsed === undefined) {
|
|
431
|
+
const respelling = respelledRuleTextOf(text, behavior);
|
|
432
|
+
return {
|
|
433
|
+
reject: {
|
|
434
|
+
code: "invalid.grammar",
|
|
435
|
+
message: `"${text}" is not a rule spelling — a rule is the tool's name exactly as spelled, then its content in parentheses (no blank, tab or invisible character around the rule or before the "("), and the ")" ends it${respelling !== undefined ? `; spelled as a rule it is "${respelling}"` : ""}`,
|
|
436
|
+
...(respelling !== undefined ? { respelling } : {}),
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
}
|
|
421
440
|
const ruleContent = ruleContentOf(parsed);
|
|
422
441
|
if (ruleContent === undefined) {
|
|
423
442
|
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`);
|
|
@@ -498,7 +517,7 @@ export function formatRuleText(command, match, tool) {
|
|
|
498
517
|
if (match === "name")
|
|
499
518
|
return `${tool}(${keyedValueParamOf(tool) !== undefined ? `${keyedValueParamOf(tool)}:` : ""}${command})`;
|
|
500
519
|
if (match === "host" || match === "subdomain")
|
|
501
|
-
return `${tool}(domain:${match
|
|
520
|
+
return `${tool}(domain:${spelledHostPattern({ match, host: command })})`;
|
|
502
521
|
return `${tool}(${command}${match === "prefix" || match === "name_prefix" ? ":*" : match === "wildcard" ? " *" : ""})`;
|
|
503
522
|
}
|
|
504
523
|
function parseNameRuleContent(text, tool, content, behavior, verifyCanonical = true) {
|
|
@@ -606,30 +625,26 @@ function parseDomainRuleContent(text, tool, content, behavior) {
|
|
|
606
625
|
if (!content.startsWith(DOMAIN_RULE_MARKER)) {
|
|
607
626
|
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}")`);
|
|
608
627
|
}
|
|
609
|
-
const
|
|
610
|
-
if (
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
628
|
+
const read = readHostPattern(content.slice(DOMAIN_RULE_MARKER.length));
|
|
629
|
+
if ("problem" in read) {
|
|
630
|
+
switch (read.problem) {
|
|
631
|
+
case "every_host":
|
|
632
|
+
return reject("unsupported.wildcard", `"${text}" names every host — the whole tool is the bare ${tool} entry`);
|
|
633
|
+
case "inner_wildcard":
|
|
634
|
+
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>\``);
|
|
635
|
+
case "empty_label":
|
|
636
|
+
return reject("invalid.grammar", `"${text}" names a host with an empty label (a leading or doubled dot) — write the host itself, and every host below a domain as \`*.<domain>\``);
|
|
637
|
+
case "not_a_host":
|
|
638
|
+
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`);
|
|
639
|
+
case "below_address":
|
|
640
|
+
return reject("invalid.grammar", `"${text}" names hosts below an address, which has none — write the address itself`);
|
|
641
|
+
}
|
|
620
642
|
}
|
|
621
|
-
|
|
622
|
-
return reject("invalid.grammar", `"${text}" names hosts below an address, which has none — write the address itself`);
|
|
623
|
-
const match = below ? "subdomain" : "host";
|
|
643
|
+
const { match, host } = read.pattern;
|
|
624
644
|
return { rule: { behavior, rule: formatRuleText(host, match, tool), tool, match, command: host } };
|
|
625
645
|
}
|
|
626
646
|
export function domainRuleAdmits(rule, host) {
|
|
627
|
-
|
|
628
|
-
return host === rule.command;
|
|
629
|
-
if (rule.match !== "subdomain" || isAddressHost(host) || !host.endsWith(`.${rule.command}`))
|
|
630
|
-
return false;
|
|
631
|
-
const labels = host.slice(0, -(rule.command.length + 1));
|
|
632
|
-
return labels !== "" && !labels.split(".").includes("");
|
|
647
|
+
return (rule.match === "host" || rule.match === "subdomain") && hostPatternAdmits({ match: rule.match, host: rule.command }, host);
|
|
633
648
|
}
|
|
634
649
|
function parsePathRuleContent(text, tool, content, behavior) {
|
|
635
650
|
if (behavior === "allow" && PATH_RULE_FACES[tool]?.allow === undefined) {
|
|
@@ -224,6 +224,9 @@ export interface UnreadableOrgRow {
|
|
|
224
224
|
readonly rule: string;
|
|
225
225
|
readonly code: RuleRejectCode;
|
|
226
226
|
readonly detail?: string;
|
|
227
|
+
/** The rule spelling a row that is no rule spelling only because of white space (a tab, a no-break space) is a blank away from — the
|
|
228
|
+
* text to republish it as; the row itself is still read as unreadable (one spelling reader, fail-closed). */
|
|
229
|
+
readonly respelling?: string;
|
|
227
230
|
readonly behavior: "deny" | "ask";
|
|
228
231
|
readonly reach: OrgRowReach;
|
|
229
232
|
}
|
|
@@ -327,8 +330,9 @@ export declare function unenforceableOrgRules(rules: readonly OrgPermissionRule[
|
|
|
327
330
|
rule: string;
|
|
328
331
|
code: RuleRejectCode;
|
|
329
332
|
}>;
|
|
330
|
-
/** The reason an unreadable row's verdict carries — its place in the snapshot, its code, and the repair
|
|
331
|
-
|
|
333
|
+
/** The reason an unreadable row's verdict carries — its place in the snapshot, its code, and the repair: for a row that is no rule
|
|
334
|
+
* spelling only because of white space (a tab, a no-break space), its text and the spelling to republish it as. */
|
|
335
|
+
export declare function unreadableOrgRowReason(row: Pick<UnreadableOrgRow, "index" | "code" | "detail" | "rule" | "respelling">): string;
|
|
332
336
|
/** The ONE clause for an org verdict that carries an `unreadable` reason — a rule the call could not be read against, or
|
|
333
337
|
* a row this layer cannot read — on a refusal and on a question alike. */
|
|
334
338
|
export declare function orgUnreadableClause(reason: string): string;
|
|
@@ -284,12 +284,13 @@ function orgStateCopyOf(state) {
|
|
|
284
284
|
function readOrgRow(row, index) {
|
|
285
285
|
const { rule: text, behavior: said } = orgRowPolicyOf(row);
|
|
286
286
|
const behavior = said === "ask" ? "ask" : "deny";
|
|
287
|
-
const unreadable = (code, detail) => ({
|
|
287
|
+
const unreadable = (code, detail, respelling) => ({
|
|
288
288
|
kind: "unreadable",
|
|
289
289
|
index,
|
|
290
290
|
rule: typeof text === "string" ? text : text === undefined ? "<unreadable>" : escapeForDisclosure(text),
|
|
291
291
|
code,
|
|
292
292
|
...(detail !== undefined ? { detail } : {}),
|
|
293
|
+
...(respelling !== undefined ? { respelling } : {}),
|
|
293
294
|
behavior,
|
|
294
295
|
reach: reachOfRowHead(text, behavior),
|
|
295
296
|
});
|
|
@@ -299,7 +300,7 @@ function readOrgRow(row, index) {
|
|
|
299
300
|
return unreadable("invalid.grammar", said === undefined ? "no behavior" : `behavior "${escapeForDisclosure(said)}" is not deny or ask`);
|
|
300
301
|
const parsed = parseRuleText(text, said);
|
|
301
302
|
if ("reject" in parsed)
|
|
302
|
-
return unreadable(parsed.reject.code);
|
|
303
|
+
return unreadable(parsed.reject.code, undefined, parsed.reject.respelling);
|
|
303
304
|
if (parsed.rule.tool !== COMMAND_RULE_TOOL)
|
|
304
305
|
return unreadable("unsupported.tool");
|
|
305
306
|
return { kind: "read", index, rule: text, behavior: said, parsed: parsed.rule };
|
|
@@ -355,6 +356,9 @@ function readListedOrgRules(rules) {
|
|
|
355
356
|
return reading;
|
|
356
357
|
}
|
|
357
358
|
export function unreadableOrgRowReason(row) {
|
|
359
|
+
if (row.respelling !== undefined) {
|
|
360
|
+
return `row ${row.index} of the organization snapshot, "${escapeForDisclosure(row.rule)}", is not a rule spelling (${row.code}); republish it as "${escapeForDisclosure(row.respelling)}"`;
|
|
361
|
+
}
|
|
358
362
|
return `row ${row.index} of the organization snapshot is not a rule this layer reads (${row.code}${row.detail !== undefined ? `: ${row.detail}` : ""}); republish it in the shell tool's command grammar`;
|
|
359
363
|
}
|
|
360
364
|
export function orgUnreadableClause(reason) {
|
|
@@ -365,7 +369,7 @@ export function describeUnreadableOrgRows(unreadable, revision) {
|
|
|
365
369
|
return undefined;
|
|
366
370
|
const named = unreadable
|
|
367
371
|
.slice(0, DISCLOSED_UNREADABLE_ROWS)
|
|
368
|
-
.map((u) => `row ${u.index} "${escapeForDisclosure(u.rule)}" (${u.code}${u.detail !== undefined ? `: ${u.detail}` : ""})`)
|
|
372
|
+
.map((u) => `row ${u.index} "${escapeForDisclosure(u.rule)}" (${u.code}${u.detail !== undefined ? `: ${u.detail}` : ""}${u.respelling !== undefined ? `; republish it as "${escapeForDisclosure(u.respelling)}"` : ""})`)
|
|
369
373
|
.join(", ");
|
|
370
374
|
const more = unreadable.length - DISCLOSED_UNREADABLE_ROWS;
|
|
371
375
|
return (`the org snapshot${revision !== undefined ? ` (revision ${escapeForDisclosure(revision)})` : ""} is served DEGRADED: ${unreadable.length} row(s) this layer cannot read — ${named}${more > 0 ? ` and ${more} more` : ""}; ` +
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
*/
|
|
37
37
|
import type { PersistedRule, RuleScope } from "./permission-rule-model.js";
|
|
38
38
|
import { type DurableRulePartitionProvider, type QuarantinedRuleAdd } from "./permission-rule-store.js";
|
|
39
|
-
import type
|
|
39
|
+
import { type RuleOwner } from "./rule-owner.js";
|
|
40
40
|
import { type OrgRulePartitionConfig, type OrgRuleResolution } from "./permission-rule-org.js";
|
|
41
41
|
import type { SessionRulePartition } from "./permission-rule-session.js";
|
|
42
42
|
import type { StoreDurability, StoreFidelity } from "./checkpoint-store.js";
|
|
@@ -146,7 +146,10 @@ export interface PermissionRuleStoreProvider {
|
|
|
146
146
|
* provider: an absent principal resolves to zero personal rules, and a provider that forgot it would hand one person's
|
|
147
147
|
* standing approvals to an anonymous task. The ONE exception is an explicitly declared LOCAL-OWNER deployment, where "no
|
|
148
148
|
* principal" is the typed absence of identity — the machine owner's own bucket (a provider with no such face resolves
|
|
149
|
-
* the anonymous store; {@link createPermissionRuleLanes} refuses that declaration loudly).
|
|
149
|
+
* the anonymous store; {@link createPermissionRuleLanes} refuses that declaration loudly). "No principal" is ABSENCE only —
|
|
150
|
+
* `undefined`, and the empty string the engine has always spelled the same ({@link readPrincipal}). A principal that was provided
|
|
151
|
+
* and is not a name (white space, an invisible or control character) is a bad value and is refused here: read as absence it
|
|
152
|
+
* would be the machine owner's bucket on a local-owner deployment.
|
|
150
153
|
*/
|
|
151
154
|
export declare function ownerStoreOf(provider: PermissionRuleStoreProvider, principal: string | undefined, localOwnerDeclared: boolean): PermissionRuleStore;
|
|
152
155
|
/** What {@link createPermissionRuleStoreProvider} composes. */
|
|
@@ -1,17 +1,39 @@
|
|
|
1
|
-
import { escapeForDisclosure } from "./permission-rule-model.js";
|
|
1
|
+
import { escapeForDisclosure, isRuleBehavior, parseRuleText } from "./permission-rule-model.js";
|
|
2
2
|
import { PERMISSION_RULE_WRITER, errText, isValidDurableScope, normalizePersistedRule, sameRuleIdentity, writerOf, } from "./permission-rule-store.js";
|
|
3
|
+
import { readRuleOwner } from "./rule-owner.js";
|
|
4
|
+
import { readPrincipal } from "./principal-name.js";
|
|
3
5
|
import { UNGOVERNED_ORG_RESOLUTION, compileOrgShadowPredicate, createOrgRulePartition } from "./permission-rule-org.js";
|
|
4
6
|
export function ruleSourceOf(scope) {
|
|
5
7
|
return scope.kind === "global" ? "user" : scope.kind;
|
|
6
8
|
}
|
|
7
9
|
export function ownerStoreOf(provider, principal, localOwnerDeclared) {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
+
const reading = readPrincipal(principal);
|
|
11
|
+
if (reading.kind === "invalid") {
|
|
12
|
+
throw new Error(`the principal given is not a principal name (got ${reading.fault}) — refusing to resolve a bucket for it; a provided value that names nobody is not "no principal"`);
|
|
13
|
+
}
|
|
14
|
+
if (reading.kind === "name")
|
|
15
|
+
return provider.forPrincipal(reading.name);
|
|
16
|
+
return localOwnerDeclared && provider.forLocalOwner !== undefined ? provider.forLocalOwner() : provider.forPrincipal(undefined);
|
|
10
17
|
}
|
|
11
18
|
function usableSessionId(sessionId) {
|
|
12
19
|
return typeof sessionId === "string" && sessionId !== "" ? sessionId : undefined;
|
|
13
20
|
}
|
|
14
21
|
const EMPTY_DURABLE = { rules: [], tombstones: [], rev: 0 };
|
|
22
|
+
function durableReadOf(settled) {
|
|
23
|
+
if (!settled.ok)
|
|
24
|
+
return settled;
|
|
25
|
+
for (const [kind, rows] of [["rule", settled.v.rules], ["tombstone", settled.v.tombstones]]) {
|
|
26
|
+
const at = rows.findIndex((r) => !isRuleBehavior(r?.behavior));
|
|
27
|
+
if (at < 0)
|
|
28
|
+
continue;
|
|
29
|
+
const row = rows[at];
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
e: new Error(`the persisted rule partition served a ${kind} row (#${at}, ${escapeForDisclosure(row?.rule)}) whose behavior word ${escapeForDisclosure(row?.behavior)} is outside deny/ask/allow — whether it refuses a call is unknown, so the partition is read as one that could not be read`),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return settled;
|
|
36
|
+
}
|
|
15
37
|
export function createPermissionRuleStoreProvider(cfg) {
|
|
16
38
|
if (cfg === null || typeof cfg !== "object") {
|
|
17
39
|
throw new Error("createPermissionRuleStoreProvider takes a config object naming the partitions to compose");
|
|
@@ -61,7 +83,7 @@ class UnifiedPermissionRuleStore {
|
|
|
61
83
|
const disclosures = [];
|
|
62
84
|
const [orgSettled, durableSettled, sessionRows] = await Promise.all([
|
|
63
85
|
this.org !== undefined ? this.org.resolve() : Promise.resolve({ ...UNGOVERNED_ORG_RESOLUTION, rules: [], disclosures: [] }),
|
|
64
|
-
this.durable !== undefined ? this.durable.list().then((v) => ({ ok: true, v }), (e) => ({ ok: false, e })) : Promise.resolve({ ok: true, v: EMPTY_DURABLE }),
|
|
86
|
+
this.durable !== undefined ? this.durable.list().then((v) => durableReadOf({ ok: true, v }), (e) => ({ ok: false, e })) : Promise.resolve({ ok: true, v: EMPTY_DURABLE }),
|
|
65
87
|
this.readSessionRows(usableSessionId(query?.sessionId), disclosures),
|
|
66
88
|
]);
|
|
67
89
|
const orgResolution = orgSettled;
|
|
@@ -106,7 +128,7 @@ class UnifiedPermissionRuleStore {
|
|
|
106
128
|
const served = structuredClone(await this.session.read(sessionId));
|
|
107
129
|
const rows = served.filter((r) => {
|
|
108
130
|
const scope = r?.scope;
|
|
109
|
-
return scope?.kind === "session" && scope.sessionId === sessionId && !("reject" in normalizePersistedRule(r));
|
|
131
|
+
return scope?.kind === "session" && scope.sessionId === sessionId && (!("reject" in normalizePersistedRule(r)) || ((r.behavior === "deny" || r.behavior === "ask") && "reject" in parseRuleText(r.rule, r.behavior)));
|
|
110
132
|
});
|
|
111
133
|
if (rows.length < served.length) {
|
|
112
134
|
disclosures.push(`the session rule partition served ${served.length - rows.length} row(s) that are not canonical session rows of this session — dropped, not adjudicated`);
|
|
@@ -157,8 +179,17 @@ export async function effectivePermissionRules(opts) {
|
|
|
157
179
|
return await resolveOwnerStore(opts).effective(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : undefined);
|
|
158
180
|
}
|
|
159
181
|
export function resolveOwnerStore(opts) {
|
|
160
|
-
|
|
161
|
-
|
|
182
|
+
const owner = opts.owner === undefined ? undefined : readRuleOwner(opts.owner);
|
|
183
|
+
if (opts.owner !== undefined && owner === undefined) {
|
|
184
|
+
throw new Error("the owner given is neither a principal nor the local owner (a principal owner names a principal name) — refusing to resolve a bucket nobody can name");
|
|
185
|
+
}
|
|
186
|
+
const given = readPrincipal(opts.principal);
|
|
187
|
+
if (given.kind === "invalid") {
|
|
188
|
+
throw new Error(`the principal given is not a principal name (got ${given.fault}) — pass a verified principal, the local owner, or neither; a provided value that names nobody is not "no principal"`);
|
|
189
|
+
}
|
|
190
|
+
const principal = given.kind === "name" ? given.name : undefined;
|
|
191
|
+
if (owner?.kind === "local-owner") {
|
|
192
|
+
if (principal !== undefined) {
|
|
162
193
|
throw new Error("pass either a principal or a local-owner, not both — a bucket has one owner");
|
|
163
194
|
}
|
|
164
195
|
const store = opts.provider.forLocalOwner?.();
|
|
@@ -167,9 +198,8 @@ export function resolveOwnerStore(opts) {
|
|
|
167
198
|
}
|
|
168
199
|
return store;
|
|
169
200
|
}
|
|
170
|
-
if (
|
|
171
|
-
throw new Error(`contradictory identity: principal "${escapeForDisclosure(
|
|
201
|
+
if (owner?.kind === "principal" && principal !== undefined && owner.principal !== principal) {
|
|
202
|
+
throw new Error(`contradictory identity: principal "${escapeForDisclosure(principal)}" and owner principal "${escapeForDisclosure(owner.principal)}" disagree`);
|
|
172
203
|
}
|
|
173
|
-
|
|
174
|
-
return opts.provider.forPrincipal(principal);
|
|
204
|
+
return opts.provider.forPrincipal(owner?.kind === "principal" ? owner.principal : principal);
|
|
175
205
|
}
|
|
@@ -45,7 +45,7 @@ import type { PersistedRule, RuleAdd, RuleDot, RuleScope, RuleTombstone } from "
|
|
|
45
45
|
import { type RuleBehavior } from "./permission-rule-model.js";
|
|
46
46
|
import type { RuleSyncDropReason, RuleQuarantineReason } from "./governance-codes.js";
|
|
47
47
|
import type { StoreDurability, StoreFidelity } from "./checkpoint-store.js";
|
|
48
|
-
import type
|
|
48
|
+
import { type RuleOwner } from "./rule-owner.js";
|
|
49
49
|
/** One store read: the rules as persisted, their tombstones, and the revision they were read at. */
|
|
50
50
|
export interface StoredRules {
|
|
51
51
|
/** Rules with their FULL add sets — tombstoned dots already removed by the backend on read. */
|
|
@@ -93,7 +93,8 @@ export interface DurableRulePartition {
|
|
|
93
93
|
/** Declared serialization fidelity, same doctrine as the other store families. */
|
|
94
94
|
readonly fidelity?: StoreFidelity;
|
|
95
95
|
}
|
|
96
|
-
/** Resolves one owner's durable partition. `undefined`
|
|
96
|
+
/** Resolves one owner's durable partition. `undefined` — or anything that is not a principal name
|
|
97
|
+
* (`principalNameOf`: the empty and the blank string included) ⇒ a partition that reports zero rules. This is
|
|
97
98
|
* the BACKEND contract (file, in-memory, an out-of-repo SQL twin); the engine never consumes it
|
|
98
99
|
* directly — `createPermissionRuleStoreProvider({ durable })` composes it into the unified store. */
|
|
99
100
|
export interface DurableRulePartitionProvider {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
+
import { readRuleOwner } from "./rule-owner.js";
|
|
4
5
|
import { thrownText } from "./thrown-value.js";
|
|
5
6
|
import { validatePath } from "./effective-path-target.js";
|
|
6
7
|
export const PERMISSION_RULES_LATER_GRAMMAR_CODE = "permission_rules.later_grammar";
|
|
@@ -510,6 +511,10 @@ async function readRuleLiveness(store, identity) {
|
|
|
510
511
|
}
|
|
511
512
|
}
|
|
512
513
|
const REMOVE_MAX_ATTEMPTS = 8;
|
|
514
|
+
const NO_TARGET_OWNER = "the principal given is neither a principal name (a string with a visible character, no control or separator character, and no format character but a joiner) nor an owner (a principal owner names a principal name; the local owner is { kind: \"local-owner\" })";
|
|
515
|
+
function persistedRuleTargetOwner(target) {
|
|
516
|
+
return readRuleOwner(typeof target === "string" ? { kind: "principal", principal: target } : target);
|
|
517
|
+
}
|
|
513
518
|
export async function removePersistedRule(opts) {
|
|
514
519
|
const behavior = opts.behavior;
|
|
515
520
|
const rule = opts.rule;
|
|
@@ -519,7 +524,9 @@ export async function removePersistedRule(opts) {
|
|
|
519
524
|
return { status: "failed", error: `removePersistedRule: behavior ${escapeForDisclosure(behavior)} is not one of ${["deny", "ask", "allow"].join("/")} — refusing to name a rule identity nothing can carry`, committed: "no" };
|
|
520
525
|
}
|
|
521
526
|
const identity = { behavior, rule, scope };
|
|
522
|
-
const owner =
|
|
527
|
+
const owner = persistedRuleTargetOwner(target);
|
|
528
|
+
if (owner === undefined)
|
|
529
|
+
return { status: "failed", error: `removePersistedRule: ${NO_TARGET_OWNER} — refusing to remove from a bucket nobody can name`, committed: "no" };
|
|
523
530
|
const provider = opts.provider;
|
|
524
531
|
if (scope?.kind === "session") {
|
|
525
532
|
return {
|
|
@@ -686,7 +693,9 @@ export async function addPersistedRule(opts) {
|
|
|
686
693
|
if ("reject" in parsed)
|
|
687
694
|
return { status: "failed", error: `addPersistedRule: ${parsed.reject.code}: ${parsed.reject.message}`, committed: "no" };
|
|
688
695
|
const identity = { behavior, rule: parsed.rule.rule, scope };
|
|
689
|
-
const owner =
|
|
696
|
+
const owner = persistedRuleTargetOwner(target);
|
|
697
|
+
if (owner === undefined)
|
|
698
|
+
return { status: "failed", error: `addPersistedRule: ${NO_TARGET_OWNER} — refusing to write into a bucket nobody can name`, committed: "no" };
|
|
690
699
|
const provider = opts.provider;
|
|
691
700
|
if (owner.kind === "local-owner" && provider.forLocalOwner === undefined) {
|
|
692
701
|
return { status: "failed", error: "this provider has no local-owner bucket (forLocalOwner is not implemented) — a local-owner rule cannot be added through it", committed: "no" };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { escapeForDisclosure, isRuleBehavior, parseRuleText, RULE_ADD_ORIGINS } from "./permission-rule-model.js";
|
|
2
|
+
import { principalNameOf } from "./principal-name.js";
|
|
2
3
|
import { PERMISSION_RULES_LATER_GRAMMAR_CODE, applyTombstones, errText, isProjectScopeRoot, joinFrontiers, joinRuleStates, normalizePersistedRule, readRuleWriteOutcome, ruleSyncVector, sameRuleIdentity, screenRuleSyncState, writerOf, } from "./permission-rule-store.js";
|
|
3
4
|
import { emitTrace } from "./trace.js";
|
|
4
5
|
import { SESSION_RULES_GRAMMAR, readRuleGrammarStamp } from "./session-policy-store.js";
|
|
@@ -23,8 +24,8 @@ export async function syncPermissionRules(opts) {
|
|
|
23
24
|
throw new Error(`contradictory identity: principal "${escapeForDisclosure(principal)}" and owner principal "${escapeForDisclosure(owner.principal)}" disagree`);
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
|
-
if (
|
|
27
|
-
throw new Error("syncPermissionRules requires a verified principal — an unauthenticated deployment has no cloud bucket to sync");
|
|
27
|
+
if (principalNameOf(principal) === undefined) {
|
|
28
|
+
throw new Error("syncPermissionRules requires a verified principal — an unauthenticated deployment has no cloud bucket to sync (a blank name, or one holding a control, separator or invisible format character, is not a principal)");
|
|
28
29
|
}
|
|
29
30
|
const traceClock = opts.now ?? Date.now;
|
|
30
31
|
const disclose = (result) => {
|
|
@@ -29,10 +29,11 @@ export declare function lastIndexOfUnescaped(s: string, ch: string): number;
|
|
|
29
29
|
*/
|
|
30
30
|
export declare function parsePermissionRule(rule: string): ParsedPermissionRule;
|
|
31
31
|
/**
|
|
32
|
-
* Can a rule name this tool name — is it the rule syntax's name head ({@link RULE_TOOL_NAME})
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
* Can a rule name this tool name — is it the rule syntax's name head ({@link RULE_TOOL_NAME}) with no `*` in it? A `*` makes a
|
|
33
|
+
* spelling a glob over names, not a name: a tool whose name held one would be reached by every rule whose glob matches it and
|
|
34
|
+
* could never be named on its own (the reference's mount spelling holds no `*` either). The mount contract for every name a
|
|
35
|
+
* caller tool answers to (its name and each alias), so each one is a name every list can write; a protocol-minted name
|
|
36
|
+
* (`<ns>__<peer>__<tool>`, segments in `[A-Za-z0-9_-]`) is always one.
|
|
36
37
|
*/
|
|
37
38
|
export declare function isNameableToolName(name: string): boolean;
|
|
38
39
|
/**
|
|
@@ -36,9 +36,9 @@ export function parsePermissionRule(rule) {
|
|
|
36
36
|
return { toolName: name.trim() };
|
|
37
37
|
return { toolName: name.trim(), ruleContent: unescapeRuleContent(content) };
|
|
38
38
|
}
|
|
39
|
-
const RULE_TOOL_NAME = /^
|
|
39
|
+
const RULE_TOOL_NAME = /^[A-Za-z0-9_*](?:[A-Za-z0-9_.:*-]*[A-Za-z0-9_*-])?$/;
|
|
40
40
|
export function isNameableToolName(name) {
|
|
41
|
-
return typeof name === "string" && name
|
|
41
|
+
return typeof name === "string" && !name.includes("*") && RULE_TOOL_NAME.test(name);
|
|
42
42
|
}
|
|
43
43
|
export function ruleSpellingOf(rule) {
|
|
44
44
|
if (rule !== rule.trim())
|
|
@@ -90,8 +90,8 @@ export interface PermissionRuleIssue {
|
|
|
90
90
|
/** A rule the rule syntax reads no rule in (`ruleSpellingOf`) whose name is the reason: blanks around the rule, a
|
|
91
91
|
* character outside a tool name's spelling (a variation selector, a filler, a zero-width space), a blank or an invisible
|
|
92
92
|
* character before the `(`, a leading `-`. Refused — the lenient reading would deny a tool under another spelling, or none.
|
|
93
|
-
*
|
|
94
|
-
*
|
|
93
|
+
* (A `*` in a tool name is no such spelling: a deny or ask rule reads it as a glob over the whole name, `*` alone as every
|
|
94
|
+
* tool.) */
|
|
95
95
|
| "invalid.tool_name" | "invalid.param_split" | "invalid.primary_field" | "invalid.cap_exceeded"
|
|
96
96
|
/** An ALLOW rule (any spelling): a policy fold says deny / ask only — "this does not need asking" is the allow
|
|
97
97
|
* layer's (`TaskSpec.allowedTools`, `RunnerDeps.allowedTools`). One issue per allow rule. */
|
|
@@ -206,8 +206,8 @@ export interface PermissionRulePolicyOptions {
|
|
|
206
206
|
*/
|
|
207
207
|
pathBases?: PathRuleBases;
|
|
208
208
|
}
|
|
209
|
-
import {
|
|
210
|
-
export {
|
|
209
|
+
import { isCoveringRuleName, ruleNameCovers, wildcardMatch } from "./namespaced-rule-name.js";
|
|
210
|
+
export { isCoveringRuleName, ruleNameCovers, wildcardMatch };
|
|
211
211
|
/**
|
|
212
212
|
* design/388 A-6/7/8/9 (D-3): the per-tool RULE FACE — the parameters a `Tool(param:pattern)` rule may name
|
|
213
213
|
* (`params`, the SCALAR top-level keys ∪ the path target's aliases), the ones the generic lexical matcher must
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { attest } from "./tool-policy.js";
|
|
2
|
-
import { MCP_NAMESPACE
|
|
2
|
+
import { MCP_NAMESPACE } from "./protocol-table.js";
|
|
3
3
|
import { catalogRuleFaceOf, isShellFaceToolName, pathTargetOf } from "./tool-registry.js";
|
|
4
4
|
import { HOST_UNREADABLE_SENTENCE, callHostTargetOf } from "./host-target.js";
|
|
5
5
|
import { indexOfUnescaped, lastIndexOfUnescaped, parsePermissionRule, ruleSpellingOf } from "./permission-rule-syntax.js";
|
|
@@ -36,9 +36,8 @@ export const DEFAULT_CAPS = {
|
|
|
36
36
|
maxStars: 16,
|
|
37
37
|
maxScalarValueChars: DEFAULT_MAX_SCALAR_VALUE_CHARS,
|
|
38
38
|
};
|
|
39
|
-
import {
|
|
40
|
-
|
|
41
|
-
export { isNamespacedCoveringRuleName, namespacedRuleNameCovers, wildcardMatch };
|
|
39
|
+
import { isCoveringRuleName, ruleNameCovers, wildcardMatch } from "./namespaced-rule-name.js";
|
|
40
|
+
export { isCoveringRuleName, ruleNameCovers, wildcardMatch };
|
|
42
41
|
const vocabularyOf = (face) => face.params ?? face.primaryParams;
|
|
43
42
|
const carriedPhrase = (face) => {
|
|
44
43
|
const vocabulary = vocabularyOf(face);
|
|
@@ -119,22 +118,13 @@ function compile(rules, caps, primaryFieldGeneric, ruleFaces, pathBases = {}) {
|
|
|
119
118
|
bad(text, why.code, why.message);
|
|
120
119
|
continue;
|
|
121
120
|
}
|
|
122
|
-
if (parsed.toolName === "*") {
|
|
123
|
-
bad(text, "invalid.tool_name", "`*` names no tool — a rule reaches the tool it names, so this one would compile and never match; name the tool", { name: "defaultAction" });
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
121
|
const toolName = parsed.toolName;
|
|
127
122
|
const parenthesised = parsed.ruleContent !== undefined || indexOfUnescaped(text, "(") !== -1;
|
|
128
123
|
if (toolName.startsWith(MCP_NAMESPACE.prefix) && parenthesised) {
|
|
129
124
|
bad(text, "unsupported.mcp_paren", "MCP rules do not support patterns in parentheses (CC parity); a rule names a whole tool (`mcp__<server>__<tool>`) or a whole server (`mcp__<server>`), and what a tool does is declared per tool in the MCP server's spec", { name: "toolAxes" });
|
|
130
125
|
continue;
|
|
131
126
|
}
|
|
132
|
-
|
|
133
|
-
if (wildPeer !== undefined) {
|
|
134
|
-
bad(text, "invalid.tool_name", wildPeer);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
if (parenthesised && isNamespacedCoveringRuleName(toolName, verb)) {
|
|
127
|
+
if (ruleContentOf(parsed) !== undefined && isCoveringRuleName(toolName, verb)) {
|
|
138
128
|
bad(text, "unsupported.covering_paren", `"${toolName}" reaches a SET of tools, and the generic param DSL is keyed by exact tool name — a parenthesised rule on it can never match. ` +
|
|
139
129
|
`Write the parenthesised rule against one exact tool name, or drop the parentheses to keep the covering rule.`);
|
|
140
130
|
continue;
|
|
@@ -142,7 +132,7 @@ function compile(rules, caps, primaryFieldGeneric, ruleFaces, pathBases = {}) {
|
|
|
142
132
|
const entry = byTool.get(toolName) ?? { bare: {}, param: { deny: [], ask: [] }, content: { deny: [], ask: [] } };
|
|
143
133
|
const content = ruleContentOf(parsed);
|
|
144
134
|
if (content === undefined) {
|
|
145
|
-
if (
|
|
135
|
+
if (isCoveringRuleName(toolName, verb)) {
|
|
146
136
|
namespacedCovering[verb].push({ ruleName: toolName, ruleText: text, ...(r.source !== undefined ? { source: r.source } : {}) });
|
|
147
137
|
continue;
|
|
148
138
|
}
|
|
@@ -339,7 +329,8 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
339
329
|
for (const r of namespacedCovering[behavior])
|
|
340
330
|
nameSets[behavior].push(r.ruleName);
|
|
341
331
|
}
|
|
342
|
-
const
|
|
332
|
+
const heldFor = (table, req) => heldValues("policy.permissionRules", table, req);
|
|
333
|
+
const coveringHit = (lane, req) => lane.length === 0 ? undefined : heldFor({ get: (name) => lane.find((r) => ruleNameCovers(r.ruleName, name)) }, req)[0];
|
|
343
334
|
const hasCovering = namespacedCovering.deny.length > 0 || namespacedCovering.ask.length > 0;
|
|
344
335
|
const faceRules = {
|
|
345
336
|
deny: [...byTool.values()].flatMap((e) => e.content.deny.filter((r) => r.faces !== undefined)),
|
|
@@ -454,7 +445,7 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
454
445
|
requiresRealApproval: true,
|
|
455
446
|
});
|
|
456
447
|
const entryFor = (req) => {
|
|
457
|
-
const entries =
|
|
448
|
+
const entries = heldFor(byTool, req);
|
|
458
449
|
if (entries.length <= 1)
|
|
459
450
|
return entries[0];
|
|
460
451
|
const bare = {};
|
|
@@ -474,12 +465,12 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
474
465
|
...(pathSpecs.length > 0 ? { pathRules: (anchors) => boundAt(policyAnchors(pathBases, anchors)).rows } : {}),
|
|
475
466
|
check(req) {
|
|
476
467
|
const entry = entryFor(req);
|
|
477
|
-
const covering = hasCovering
|
|
468
|
+
const covering = hasCovering;
|
|
478
469
|
if (entry?.bare.deny) {
|
|
479
470
|
return { action: "deny", message: ruleDenial(req, entry.bare.deny.ruleText, entry.bare.deny.source) };
|
|
480
471
|
}
|
|
481
472
|
if (covering) {
|
|
482
|
-
const coveringDeny = coveringHit(namespacedCovering.deny, req
|
|
473
|
+
const coveringDeny = coveringHit(namespacedCovering.deny, req);
|
|
483
474
|
if (coveringDeny)
|
|
484
475
|
return { action: "deny", message: ruleDenial(req, coveringDeny.ruleText, coveringDeny.source) };
|
|
485
476
|
}
|
|
@@ -509,7 +500,7 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
509
500
|
}
|
|
510
501
|
}
|
|
511
502
|
if (covering) {
|
|
512
|
-
const coveringAsk = coveringHit(namespacedCovering.ask, req
|
|
503
|
+
const coveringAsk = coveringHit(namespacedCovering.ask, req);
|
|
513
504
|
if (coveringAsk)
|
|
514
505
|
return { action: "ask", message: ruleAskMessage(coveringAsk.ruleText, coveringAsk.source), matchedAskRule: coveringAsk.ruleText };
|
|
515
506
|
}
|
|
@@ -44,7 +44,7 @@ export declare function disclosedRuleSet(rules: readonly PersistedRuleHitRule[])
|
|
|
44
44
|
* lane's allow carries: one authority, one word) and the rule text disclosed through the display
|
|
45
45
|
* transform; the STATION attributes the refusal (`tightened: "deny"` ⇒ it writes the pass's refusing
|
|
46
46
|
* layer — the attribution is a station's word and lives in the station file). A path row's hit reports where
|
|
47
|
-
* it met the call; with
|
|
47
|
+
* it met the call; with the face the call's own (`call`, the request the lane read), the deny carries that as its attested `pathDenial`, so every exit that
|
|
48
48
|
* reads the deny (the gate's exit, the approved-edit re-check, a resumed leg's belt) mints the same card from the same fact.
|
|
49
49
|
* · ASK — on an allow, a fresh ask is minted carrying `matchedAskRule` (the marker an explicit `ask`
|
|
50
50
|
* permission rule stamps, so the ask's origin derives as `ask_rule`: the classifier never answers
|
|
@@ -69,7 +69,7 @@ export declare function disclosedRuleSet(rules: readonly PersistedRuleHitRule[])
|
|
|
69
69
|
* resumed-call belt, which vetoes only the call form: a person already answered that call's question.
|
|
70
70
|
* An allow hit (or no hit) returns the decision untouched: the allow arm is the station's.
|
|
71
71
|
*/
|
|
72
|
-
export declare function applyPersistedTightening(decision: PermissionResult, read: PersistedRuleRead | undefined, call
|
|
72
|
+
export declare function applyPersistedTightening(decision: PermissionResult, read: PersistedRuleRead | undefined, call: ToolCallRequest): {
|
|
73
73
|
decision: PermissionResult;
|
|
74
74
|
tightened?: "deny" | "ask" | "unreadable" | "store_unreadable";
|
|
75
75
|
};
|