@nathapp/nax-agent 0.1.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 +24 -0
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/dist/coding-tools/coding-tool-bash.d.ts +46 -0
- package/dist/coding-tools/coding-tool-bash.js +44 -0
- package/dist/coding-tools/coding-tool-extras.d.ts +35 -0
- package/dist/coding-tools/coding-tool-extras.js +56 -0
- package/dist/coding-tools/coding-tool-sandbox.d.ts +74 -0
- package/dist/coding-tools/coding-tool-sandbox.js +215 -0
- package/dist/coding-tools/coding-tool-support.d.ts +132 -0
- package/dist/coding-tools/coding-tool-support.js +159 -0
- package/dist/coding-tools/universal-coding-tools.d.ts +11 -0
- package/dist/coding-tools/universal-coding-tools.js +5 -0
- package/dist/command-interceptor/index.d.ts +55 -0
- package/dist/command-interceptor/index.js +60 -0
- package/dist/command-interceptor/shell.d.ts +36 -0
- package/dist/command-interceptor/shell.js +137 -0
- package/dist/command-safety/build.d.ts +19 -0
- package/dist/command-safety/build.js +47 -0
- package/dist/command-safety/guard.d.ts +32 -0
- package/dist/command-safety/guard.js +179 -0
- package/dist/command-safety/identifiers.d.ts +24 -0
- package/dist/command-safety/identifiers.js +20 -0
- package/dist/command-safety/index.d.ts +17 -0
- package/dist/command-safety/index.js +11 -0
- package/dist/command-safety/questions.d.ts +19 -0
- package/dist/command-safety/questions.js +60 -0
- package/dist/command-safety/row.d.ts +2 -0
- package/dist/command-safety/row.js +14 -0
- package/dist/command-safety/rule-scorer.d.ts +9 -0
- package/dist/command-safety/rule-scorer.js +391 -0
- package/dist/command-safety/shadow.d.ts +33 -0
- package/dist/command-safety/shadow.js +188 -0
- package/dist/command-safety/systemone-client.d.ts +16 -0
- package/dist/command-safety/systemone-client.js +92 -0
- package/dist/command-safety/tap.d.ts +37 -0
- package/dist/command-safety/tap.js +77 -0
- package/dist/command-safety/temp-only.d.ts +6 -0
- package/dist/command-safety/temp-only.js +215 -0
- package/dist/command-safety/tmp-write.d.ts +2 -0
- package/dist/command-safety/tmp-write.js +112 -0
- package/dist/command-safety/types.d.ts +185 -0
- package/dist/command-safety/types.js +21 -0
- package/dist/config/bash-approval.d.ts +33 -0
- package/dist/config/bash-approval.js +30 -0
- package/dist/config/catalog-overrides.d.ts +94 -0
- package/dist/config/catalog-overrides.js +7 -0
- package/dist/config/native-agent/index.d.ts +8 -0
- package/dist/config/native-agent/index.js +8 -0
- package/dist/config/schemas-sandbox.d.ts +31 -0
- package/dist/config/schemas-sandbox.js +45 -0
- package/dist/cost/core/index.d.ts +8 -0
- package/dist/cost/core/index.js +7 -0
- package/dist/cost/estimate.d.ts +34 -0
- package/dist/cost/estimate.js +65 -0
- package/dist/cost/model-spec.d.ts +28 -0
- package/dist/cost/model-spec.js +29 -0
- package/dist/cost/standard-types.d.ts +9 -0
- package/dist/cost/standard-types.js +1 -0
- package/dist/cost/usage-math.d.ts +18 -0
- package/dist/cost/usage-math.js +46 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +21 -0
- package/dist/infra/agent-logger.d.ts +22 -0
- package/dist/infra/agent-logger.js +19 -0
- package/dist/infra/credentials-config.d.ts +20 -0
- package/dist/infra/credentials-config.js +15 -0
- package/dist/infra/errors.d.ts +9 -0
- package/dist/infra/errors.js +11 -0
- package/dist/infra/index.d.ts +7 -0
- package/dist/infra/index.js +7 -0
- package/dist/infra/nax-error.d.ts +10 -0
- package/dist/infra/nax-error.js +16 -0
- package/dist/infra/spin-breaker/hash.d.ts +2 -0
- package/dist/infra/spin-breaker/hash.js +5 -0
- package/dist/infra/spin-breaker/index.d.ts +120 -0
- package/dist/infra/spin-breaker/index.js +340 -0
- package/dist/internal/agent-output-env.d.ts +44 -0
- package/dist/internal/agent-output-env.js +57 -0
- package/dist/internal/argv-exec.d.ts +58 -0
- package/dist/internal/argv-exec.js +224 -0
- package/dist/internal/bounded-io.d.ts +24 -0
- package/dist/internal/bounded-io.js +51 -0
- package/dist/internal/command-spec/index.d.ts +29 -0
- package/dist/internal/command-spec/index.js +36 -0
- package/dist/internal/describe-value-type.d.ts +8 -0
- package/dist/internal/describe-value-type.js +14 -0
- package/dist/internal/exec-framing.d.ts +18 -0
- package/dist/internal/exec-framing.js +22 -0
- package/dist/internal/file-lock.d.ts +91 -0
- package/dist/internal/file-lock.js +212 -0
- package/dist/internal/file-size.d.ts +2 -0
- package/dist/internal/file-size.js +12 -0
- package/dist/internal/git-add.d.ts +43 -0
- package/dist/internal/git-add.js +59 -0
- package/dist/internal/git-env.d.ts +59 -0
- package/dist/internal/git-env.js +93 -0
- package/dist/internal/git-exec.d.ts +58 -0
- package/dist/internal/git-exec.js +115 -0
- package/dist/internal/path-file-lock.d.ts +23 -0
- package/dist/internal/path-file-lock.js +25 -0
- package/dist/internal/process-alive.d.ts +42 -0
- package/dist/internal/process-alive.js +56 -0
- package/dist/internal/process-kill.d.ts +27 -0
- package/dist/internal/process-kill.js +54 -0
- package/dist/internal/realpath.d.ts +29 -0
- package/dist/internal/realpath.js +52 -0
- package/dist/internal/redact.d.ts +40 -0
- package/dist/internal/redact.js +161 -0
- package/dist/internal/shell-quote.d.ts +2 -0
- package/dist/internal/shell-quote.js +4 -0
- package/dist/internal/sort.d.ts +20 -0
- package/dist/internal/sort.js +20 -0
- package/dist/internal/strip-control-chars.d.ts +19 -0
- package/dist/internal/strip-control-chars.js +38 -0
- package/dist/internal/thenable.d.ts +10 -0
- package/dist/internal/thenable.js +15 -0
- package/dist/internal.d.ts +91 -0
- package/dist/internal.js +91 -0
- package/dist/native/adapter-deps.d.ts +46 -0
- package/dist/native/adapter-deps.js +43 -0
- package/dist/native/auth-types.d.ts +70 -0
- package/dist/native/auth-types.js +13 -0
- package/dist/native/auth.d.ts +133 -0
- package/dist/native/auth.js +362 -0
- package/dist/native/client.d.ts +50 -0
- package/dist/native/client.js +174 -0
- package/dist/native/complete.d.ts +33 -0
- package/dist/native/complete.js +88 -0
- package/dist/native/credentials/chained-store.d.ts +30 -0
- package/dist/native/credentials/chained-store.js +68 -0
- package/dist/native/credentials/change-guard.d.ts +51 -0
- package/dist/native/credentials/change-guard.js +136 -0
- package/dist/native/credentials/exec-source.d.ts +50 -0
- package/dist/native/credentials/exec-source.js +222 -0
- package/dist/native/credentials/fingerprint.d.ts +26 -0
- package/dist/native/credentials/fingerprint.js +174 -0
- package/dist/native/credentials/helper-process.d.ts +79 -0
- package/dist/native/credentials/helper-process.js +259 -0
- package/dist/native/credentials/helper-protocol.d.ts +36 -0
- package/dist/native/credentials/helper-protocol.js +79 -0
- package/dist/native/credentials/index.d.ts +60 -0
- package/dist/native/credentials/index.js +184 -0
- package/dist/native/errors.d.ts +34 -0
- package/dist/native/errors.js +109 -0
- package/dist/native/index.d.ts +25 -0
- package/dist/native/index.js +23 -0
- package/dist/native/model-resolver.d.ts +57 -0
- package/dist/native/model-resolver.js +73 -0
- package/dist/native/models.d.ts +116 -0
- package/dist/native/models.js +183 -0
- package/dist/native/session/ask-human.d.ts +15 -0
- package/dist/native/session/ask-human.js +22 -0
- package/dist/native/session/compaction.d.ts +94 -0
- package/dist/native/session/compaction.js +170 -0
- package/dist/native/session/handle-invalid-tool-call.d.ts +111 -0
- package/dist/native/session/handle-invalid-tool-call.js +159 -0
- package/dist/native/session/loop-events/cache-boundary.d.ts +11 -0
- package/dist/native/session/loop-events/cache-boundary.js +29 -0
- package/dist/native/session/loop-events/external-handler.d.ts +38 -0
- package/dist/native/session/loop-events/external-handler.js +193 -0
- package/dist/native/session/loop-events/index.d.ts +16 -0
- package/dist/native/session/loop-events/index.js +15 -0
- package/dist/native/session/loop-events/payload-guard.d.ts +9 -0
- package/dist/native/session/loop-events/payload-guard.js +31 -0
- package/dist/native/session/loop-events/registry.d.ts +84 -0
- package/dist/native/session/loop-events/registry.js +305 -0
- package/dist/native/session/loop-events/types.d.ts +251 -0
- package/dist/native/session/loop-events/types.js +14 -0
- package/dist/native/session/loop-handlers.d.ts +73 -0
- package/dist/native/session/loop-handlers.js +137 -0
- package/dist/native/session/nudge.d.ts +29 -0
- package/dist/native/session/nudge.js +35 -0
- package/dist/native/session/rate-provenance.d.ts +17 -0
- package/dist/native/session/rate-provenance.js +39 -0
- package/dist/native/session/session.d.ts +139 -0
- package/dist/native/session/session.js +208 -0
- package/dist/native/session/tool-input-exemplar.d.ts +2 -0
- package/dist/native/session/tool-input-exemplar.js +66 -0
- package/dist/native/session/tool-input-validation.d.ts +15 -0
- package/dist/native/session/tool-input-validation.js +146 -0
- package/dist/native/session/tool-mapping.d.ts +18 -0
- package/dist/native/session/tool-mapping.js +27 -0
- package/dist/native/session/tool-result.d.ts +37 -0
- package/dist/native/session/tool-result.js +25 -0
- package/dist/native/session/transcript-store.d.ts +77 -0
- package/dist/native/session/transcript-store.js +200 -0
- package/dist/native/session/truncation-handler.d.ts +47 -0
- package/dist/native/session/truncation-handler.js +83 -0
- package/dist/native/session/turn-accumulator.d.ts +45 -0
- package/dist/native/session/turn-accumulator.js +67 -0
- package/dist/native/session/turn-ask-human.d.ts +23 -0
- package/dist/native/session/turn-ask-human.js +41 -0
- package/dist/native/session/turn-compaction-step.d.ts +74 -0
- package/dist/native/session/turn-compaction-step.js +128 -0
- package/dist/native/session/turn-complete-step.d.ts +77 -0
- package/dist/native/session/turn-complete-step.js +159 -0
- package/dist/native/session/turn-end-event.d.ts +3 -0
- package/dist/native/session/turn-end-event.js +17 -0
- package/dist/native/session/turn-events.d.ts +63 -0
- package/dist/native/session/turn-events.js +48 -0
- package/dist/native/session/turn-loop-round-trip.d.ts +116 -0
- package/dist/native/session/turn-loop-round-trip.js +318 -0
- package/dist/native/session/turn-loop.d.ts +23 -0
- package/dist/native/session/turn-loop.js +250 -0
- package/dist/native/session/turn-result.d.ts +39 -0
- package/dist/native/session/turn-result.js +64 -0
- package/dist/native/session/turn-retry.d.ts +120 -0
- package/dist/native/session/turn-retry.js +182 -0
- package/dist/native/session/turn-tool-batch.d.ts +61 -0
- package/dist/native/session/turn-tool-batch.js +259 -0
- package/dist/native/session/turn-types.d.ts +164 -0
- package/dist/native/session/turn-types.js +36 -0
- package/dist/native/session-adapter.d.ts +56 -0
- package/dist/native/session-adapter.js +369 -0
- package/dist/native/session-affinity.d.ts +36 -0
- package/dist/native/session-affinity.js +43 -0
- package/dist/native/tier-providers.d.ts +18 -0
- package/dist/native/tier-providers.js +20 -0
- package/dist/permissions/approval-audit.d.ts +10 -0
- package/dist/permissions/approval-audit.js +22 -0
- package/dist/permissions/approvals-link.d.ts +20 -0
- package/dist/permissions/approvals-link.js +62 -0
- package/dist/permissions/approvals-store.d.ts +168 -0
- package/dist/permissions/approvals-store.js +259 -0
- package/dist/permissions/approvals-taint.d.ts +33 -0
- package/dist/permissions/approvals-taint.js +138 -0
- package/dist/permissions/ask-chain.d.ts +69 -0
- package/dist/permissions/ask-chain.js +32 -0
- package/dist/permissions/ask.d.ts +23 -0
- package/dist/permissions/ask.js +25 -0
- package/dist/permissions/bash-lex.d.ts +59 -0
- package/dist/permissions/bash-lex.js +279 -0
- package/dist/permissions/grammar.d.ts +12 -0
- package/dist/permissions/grammar.js +24 -0
- package/dist/permissions/index.d.ts +11 -0
- package/dist/permissions/index.js +9 -0
- package/dist/permissions/secret-spans.d.ts +19 -0
- package/dist/permissions/secret-spans.js +88 -0
- package/dist/permissions/types.d.ts +85 -0
- package/dist/permissions/types.js +1 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/node-glob.d.ts +3 -0
- package/dist/runtime/node-glob.js +83 -0
- package/dist/runtime/node-runtime.d.ts +3 -0
- package/dist/runtime/node-runtime.js +126 -0
- package/dist/runtime/slot.d.ts +11 -0
- package/dist/runtime/slot.js +17 -0
- package/dist/runtime/types.d.ts +51 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/which.d.ts +8 -0
- package/dist/runtime/which.js +32 -0
- package/dist/sandbox/argv-quote.d.ts +11 -0
- package/dist/sandbox/argv-quote.js +13 -0
- package/dist/sandbox/defaults.d.ts +27 -0
- package/dist/sandbox/defaults.js +47 -0
- package/dist/sandbox/git-guards.d.ts +64 -0
- package/dist/sandbox/git-guards.js +161 -0
- package/dist/sandbox/index.d.ts +11 -0
- package/dist/sandbox/index.js +10 -0
- package/dist/sandbox/launcher.d.ts +37 -0
- package/dist/sandbox/launcher.js +160 -0
- package/dist/sandbox/messages.d.ts +17 -0
- package/dist/sandbox/messages.js +46 -0
- package/dist/sandbox/policy-builder.d.ts +44 -0
- package/dist/sandbox/policy-builder.js +149 -0
- package/dist/sandbox/policy-inputs.d.ts +53 -0
- package/dist/sandbox/policy-inputs.js +76 -0
- package/dist/sandbox/probe.d.ts +8 -0
- package/dist/sandbox/probe.js +66 -0
- package/dist/sandbox/registry.d.ts +17 -0
- package/dist/sandbox/registry.js +57 -0
- package/dist/sandbox/session-tmp.d.ts +20 -0
- package/dist/sandbox/session-tmp.js +95 -0
- package/dist/sandbox/srt-backend.d.ts +21 -0
- package/dist/sandbox/srt-backend.js +99 -0
- package/dist/sandbox/types.d.ts +102 -0
- package/dist/sandbox/types.js +1 -0
- package/dist/session/adapter-failure.d.ts +60 -0
- package/dist/session/adapter-failure.js +11 -0
- package/dist/session/agent-stream-events.d.ts +103 -0
- package/dist/session/agent-stream-events.js +6 -0
- package/dist/session/interaction-handler.d.ts +53 -0
- package/dist/session/interaction-handler.js +2 -0
- package/dist/session/no-op-interaction-handler.d.ts +9 -0
- package/dist/session/no-op-interaction-handler.js +11 -0
- package/dist/session/protocol-types.d.ts +12 -0
- package/dist/session/protocol-types.js +1 -0
- package/dist/session/session-types.d.ts +456 -0
- package/dist/session/session-types.js +64 -0
- package/dist/session/tool-descriptor.d.ts +23 -0
- package/dist/session/tool-descriptor.js +5 -0
- package/dist/session/turn-deadline.d.ts +17 -0
- package/dist/session/turn-deadline.js +23 -0
- package/dist/tools/ask-request.d.ts +35 -0
- package/dist/tools/ask-request.js +57 -0
- package/dist/tools/bash-cwd.d.ts +82 -0
- package/dist/tools/bash-cwd.js +55 -0
- package/dist/tools/bash.d.ts +69 -0
- package/dist/tools/bash.js +283 -0
- package/dist/tools/delete.d.ts +57 -0
- package/dist/tools/delete.js +154 -0
- package/dist/tools/denial-redirect.d.ts +42 -0
- package/dist/tools/denial-redirect.js +266 -0
- package/dist/tools/deny-paths.d.ts +35 -0
- package/dist/tools/deny-paths.js +76 -0
- package/dist/tools/edit-region.d.ts +50 -0
- package/dist/tools/edit-region.js +90 -0
- package/dist/tools/edit.d.ts +25 -0
- package/dist/tools/edit.js +114 -0
- package/dist/tools/exec-allowlist-text.d.ts +18 -0
- package/dist/tools/exec-allowlist-text.js +24 -0
- package/dist/tools/exec-guard.d.ts +69 -0
- package/dist/tools/exec-guard.js +151 -0
- package/dist/tools/git-commit.d.ts +59 -0
- package/dist/tools/git-commit.js +220 -0
- package/dist/tools/git-flags/index.d.ts +10 -0
- package/dist/tools/git-flags/index.js +10 -0
- package/dist/tools/git.d.ts +88 -0
- package/dist/tools/git.js +397 -0
- package/dist/tools/glob.d.ts +29 -0
- package/dist/tools/glob.js +153 -0
- package/dist/tools/grep.d.ts +21 -0
- package/dist/tools/grep.js +162 -0
- package/dist/tools/index.d.ts +36 -0
- package/dist/tools/index.js +28 -0
- package/dist/tools/narrow-grants.d.ts +19 -0
- package/dist/tools/narrow-grants.js +54 -0
- package/dist/tools/nax-owned-writes.d.ts +135 -0
- package/dist/tools/nax-owned-writes.js +259 -0
- package/dist/tools/package-managers-table.d.ts +146 -0
- package/dist/tools/package-managers-table.js +325 -0
- package/dist/tools/package-managers-types.d.ts +36 -0
- package/dist/tools/package-managers-types.js +6 -0
- package/dist/tools/package-managers.d.ts +85 -0
- package/dist/tools/package-managers.js +391 -0
- package/dist/tools/path-list.d.ts +38 -0
- package/dist/tools/path-list.js +62 -0
- package/dist/tools/policy-bash-raw.d.ts +19 -0
- package/dist/tools/policy-bash-raw.js +210 -0
- package/dist/tools/policy-bash.d.ts +64 -0
- package/dist/tools/policy-bash.js +232 -0
- package/dist/tools/policy-command-branch.d.ts +32 -0
- package/dist/tools/policy-command-branch.js +63 -0
- package/dist/tools/policy-input.d.ts +2 -0
- package/dist/tools/policy-input.js +10 -0
- package/dist/tools/policy-match.d.ts +75 -0
- package/dist/tools/policy-match.js +117 -0
- package/dist/tools/policy-paths-branch.d.ts +97 -0
- package/dist/tools/policy-paths-branch.js +314 -0
- package/dist/tools/policy.d.ts +65 -0
- package/dist/tools/policy.js +232 -0
- package/dist/tools/protected-paths.d.ts +26 -0
- package/dist/tools/protected-paths.js +8 -0
- package/dist/tools/provider-adapt.d.ts +4 -0
- package/dist/tools/provider-adapt.js +32 -0
- package/dist/tools/provider-advertise.d.ts +39 -0
- package/dist/tools/provider-advertise.js +50 -0
- package/dist/tools/provider-grants.d.ts +36 -0
- package/dist/tools/provider-grants.js +72 -0
- package/dist/tools/provider-sanitize.d.ts +19 -0
- package/dist/tools/provider-sanitize.js +53 -0
- package/dist/tools/provider-types.d.ts +41 -0
- package/dist/tools/provider-types.js +26 -0
- package/dist/tools/read-continuation.d.ts +147 -0
- package/dist/tools/read-continuation.js +176 -0
- package/dist/tools/read-file.d.ts +32 -0
- package/dist/tools/read-file.js +85 -0
- package/dist/tools/read.d.ts +25 -0
- package/dist/tools/read.js +182 -0
- package/dist/tools/registry.d.ts +125 -0
- package/dist/tools/registry.js +67 -0
- package/dist/tools/request-capability.d.ts +13 -0
- package/dist/tools/request-capability.js +23 -0
- package/dist/tools/run-command-exec.d.ts +14 -0
- package/dist/tools/run-command-exec.js +161 -0
- package/dist/tools/run-command.d.ts +107 -0
- package/dist/tools/run-command.js +398 -0
- package/dist/tools/runtime-calltool.d.ts +146 -0
- package/dist/tools/runtime-calltool.js +223 -0
- package/dist/tools/runtime.d.ts +149 -0
- package/dist/tools/runtime.js +345 -0
- package/dist/tools/scratchpad.d.ts +20 -0
- package/dist/tools/scratchpad.js +197 -0
- package/dist/tools/spill.d.ts +81 -0
- package/dist/tools/spill.js +251 -0
- package/dist/tools/tool-audit.d.ts +171 -0
- package/dist/tools/tool-audit.js +183 -0
- package/dist/tools/truncate.d.ts +98 -0
- package/dist/tools/truncate.js +297 -0
- package/dist/tools/types.d.ts +145 -0
- package/dist/tools/types.js +24 -0
- package/dist/tools/verb-denial-argv-hint.d.ts +35 -0
- package/dist/tools/verb-denial-argv-hint.js +40 -0
- package/dist/tools/write.d.ts +9 -0
- package/dist/tools/write.js +45 -0
- package/package.json +54 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn resolved grants plus an operation's declaration into a live runtime.
|
|
3
|
+
*
|
|
4
|
+
* nax-permission-mode-allow: consumes permissions already resolved by
|
|
5
|
+
* resolvePermissions; decides none.
|
|
6
|
+
*
|
|
7
|
+
* Callers reach it through `resolveCodingToolSupport`
|
|
8
|
+
* (`coding-tool-support-resolve.ts`, nax side), the single entry point both
|
|
9
|
+
* dispatch hops use.
|
|
10
|
+
*/
|
|
11
|
+
import type { CommandInterceptor } from "#src/command-interceptor/index";
|
|
12
|
+
import type { CommandShadow } from "#src/command-safety/index";
|
|
13
|
+
import type { BashApprovalMode } from "#src/config/bash-approval";
|
|
14
|
+
import type { QualityCommandSpec } from "#src/internal/command-spec/index";
|
|
15
|
+
import type { AskResolver } from "#src/permissions/index";
|
|
16
|
+
import type { CommandLauncher } from "#src/sandbox/index";
|
|
17
|
+
import { type CodingTool, type CodingToolName, type CodingToolRuntime, type DeclaredCommandRunner, type ProtectedPathsPolicy, type ToolAuditSink, type ToolGrant, type ToolPatternNarrowing } from "#src/tools/index";
|
|
18
|
+
export interface CodingToolSupport {
|
|
19
|
+
readonly runtime: CodingToolRuntime;
|
|
20
|
+
readonly tools: readonly CodingTool[];
|
|
21
|
+
readonly auditSink: ToolAuditSink;
|
|
22
|
+
}
|
|
23
|
+
export declare function buildCodingToolSupport(args: {
|
|
24
|
+
root?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Execution root for Exec's `target: "repoRoot"` form.
|
|
27
|
+
*
|
|
28
|
+
* Under story worktree isolation this is the story's worktree root
|
|
29
|
+
* (`<repo>/.nax-wt/<storyId>`), NOT the main checkout — a repo-scoped command
|
|
30
|
+
* that used `PackageView.repoRoot` here wrote the user's real working tree
|
|
31
|
+
* (nax#2093). Falls back to `root` when absent. Post single-frame redesign
|
|
32
|
+
* (PR2) `root` is already `storyExecRoot`, so `resolveCodingToolSupport`
|
|
33
|
+
* passes no separate value and the fallback supplies it.
|
|
34
|
+
*/
|
|
35
|
+
repoRoot?: string;
|
|
36
|
+
/**
|
|
37
|
+
* The story's ABSOLUTE package dir for Exec's `target: "package"` cwd.
|
|
38
|
+
*
|
|
39
|
+
* Post-root-move, `codingToolRoot` is `storyExecRoot` (the repo/worktree
|
|
40
|
+
* root), so `args.root` can no longer
|
|
41
|
+
* stand in for the package dir: `run-command-exec.ts` computes
|
|
42
|
+
* `relative(repoRoot, packageWorkdir)`, which would always be "" and make
|
|
43
|
+
* `package-managers.ts`'s `effectiveTarget` collapse EVERY Exec call —
|
|
44
|
+
* `target: "package"` included — onto the repo root. It MUST be an
|
|
45
|
+
* absolute path: `packageWorkdir` is compared against the absolute
|
|
46
|
+
* `repoRoot`, so a relative value yields garbage. Falls back to `root`
|
|
47
|
+
* when absent (single-package repos, legacy callers, tests).
|
|
48
|
+
*/
|
|
49
|
+
packageWorkdir?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Execution cwd for RunCommand's DECLARED (non-Exec) branch, independent
|
|
52
|
+
* of `root` (tool containment). Falls back to `root` when absent.
|
|
53
|
+
*
|
|
54
|
+
* PRODUCER: resolveCodingToolSupport below, computed from
|
|
55
|
+
* `codingToolPackageDir` + `projectDir` (docs/superpowers/specs/2026-09-18-single-frame-redesign-design.md
|
|
56
|
+
* PR 1) so it stays pointed at the story's package dir even after PR2
|
|
57
|
+
* repoints `codingToolRoot`/`root` at the repo root.
|
|
58
|
+
*/
|
|
59
|
+
commandCwd?: string;
|
|
60
|
+
grants?: readonly ToolGrant[];
|
|
61
|
+
declared: readonly CodingToolName[];
|
|
62
|
+
/** Provider-supplied tools for this hop, looked up before the global registry. */
|
|
63
|
+
extraTools?: readonly CodingTool[];
|
|
64
|
+
/** Advertised provider tool name -> owning provider id, for the ledger. */
|
|
65
|
+
providerIdByTool?: ReadonlyMap<string, string>;
|
|
66
|
+
storyId?: string;
|
|
67
|
+
declaredCommands?: ReadonlyMap<string, QualityCommandSpec>;
|
|
68
|
+
/** Port 7, forwarded to RunCommand. Supplied by `resolveCodingToolSupport`. */
|
|
69
|
+
runDeclaredCommand?: DeclaredCommandRunner;
|
|
70
|
+
/** Port 7: the run's interceptor, placed on every tool context. */
|
|
71
|
+
interceptor?: CommandInterceptor;
|
|
72
|
+
/** Port 6: host-owned paths, placed on every tool context. */
|
|
73
|
+
protectedPaths?: ProtectedPathsPolicy;
|
|
74
|
+
stripEnvVars?: readonly string[];
|
|
75
|
+
/** `quality.shell` — the shell the Bash tool spawns. Defaults to /bin/sh. */
|
|
76
|
+
shell?: string;
|
|
77
|
+
auditDir?: string;
|
|
78
|
+
sessionName?: string;
|
|
79
|
+
header?: import("../tools/tool-audit.ts").ToolAuditHeader;
|
|
80
|
+
callId?: string;
|
|
81
|
+
scopeId?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Manifest name of the workspace member at the story's package dir
|
|
84
|
+
* (`packageWorkdir`/`commandCwd`), NOT at `root` — post-PR2 `root` is the
|
|
85
|
+
* repo root, so reading its manifest would scope workspace installs with the
|
|
86
|
+
* root name. See `resolvePackageName`.
|
|
87
|
+
*/
|
|
88
|
+
packageName?: string;
|
|
89
|
+
/** `config.install.allowScripts` (Task 8 adds the field); defaults to false. */
|
|
90
|
+
allowScripts?: boolean;
|
|
91
|
+
/** `config.execution.denyPaths` (nax#1972); forwarded to createCodingToolRuntime verbatim. */
|
|
92
|
+
denyPaths?: readonly string[];
|
|
93
|
+
/** Per-tool narrowing from the op's `toolPatterns` (nax#2013). */
|
|
94
|
+
toolPatterns?: ToolPatternNarrowing;
|
|
95
|
+
/** Stage deny rules (spec R6); forwarded to `compileToolPolicy`. Bypasses `narrowGrants`. */
|
|
96
|
+
denyRules?: readonly ToolGrant[];
|
|
97
|
+
/** Stage ask rules (spec R1/R6); forwarded to `compileToolPolicy`. Bypasses `narrowGrants`. */
|
|
98
|
+
askRules?: readonly ToolGrant[];
|
|
99
|
+
/** PipelineStage this support is built for; carried into every `AskRequest`. */
|
|
100
|
+
pipelineStage?: string;
|
|
101
|
+
/**
|
|
102
|
+
* Absolute path the dispatching op declared as its `fileOutput` (nax#2115);
|
|
103
|
+
* forwarded to `compileToolPolicy` as its `ownedWriteExemption`.
|
|
104
|
+
*/
|
|
105
|
+
fileOutputPath?: string;
|
|
106
|
+
/** nax#2260: `execution.sandbox.filesystem.allowWrite`, forwarded to `compileToolPolicy`. */
|
|
107
|
+
naxAllowWrite?: readonly string[];
|
|
108
|
+
bashApproval?: BashApprovalMode;
|
|
109
|
+
/** Injectable ask resolver (Task 3); defaults to the headless deny resolver. */
|
|
110
|
+
askResolver?: AskResolver;
|
|
111
|
+
/**
|
|
112
|
+
* P5 shadow classifier. Observational, except its optional `guard`
|
|
113
|
+
* (US-004): a flagged allowed `Bash`/`Exec` call becomes an ask.
|
|
114
|
+
*/
|
|
115
|
+
commandShadow?: CommandShadow;
|
|
116
|
+
/** P4: resolved by resolveCodingToolSupport (async); data only here. */
|
|
117
|
+
launcher?: CommandLauncher;
|
|
118
|
+
/** US-001: forwarded into ToolRunContext so Bash/Exec can SIGKILL on cancel. */
|
|
119
|
+
abortSignal?: AbortSignal;
|
|
120
|
+
}): CodingToolSupport | undefined;
|
|
121
|
+
/**
|
|
122
|
+
* Ledger session name.
|
|
123
|
+
*
|
|
124
|
+
* Story-only names collide across the three TDD roles, which all write to one
|
|
125
|
+
* directory -- so a ledger could not answer which session made a call, and that
|
|
126
|
+
* is the evidence ADR-029 parity claims are read from.
|
|
127
|
+
*/
|
|
128
|
+
export declare function buildLedgerSessionName(opts: {
|
|
129
|
+
storyId?: string;
|
|
130
|
+
sessionRole?: string;
|
|
131
|
+
featureName?: string;
|
|
132
|
+
}): string;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn resolved grants plus an operation's declaration into a live runtime.
|
|
3
|
+
*
|
|
4
|
+
* nax-permission-mode-allow: consumes permissions already resolved by
|
|
5
|
+
* resolvePermissions; decides none.
|
|
6
|
+
*
|
|
7
|
+
* Callers reach it through `resolveCodingToolSupport`
|
|
8
|
+
* (`coding-tool-support-resolve.ts`, nax side), the single entry point both
|
|
9
|
+
* dispatch hops use.
|
|
10
|
+
*/
|
|
11
|
+
import { getSafeLogger, NaxError } from "#src/infra/index";
|
|
12
|
+
import { advertisedSchemaBytes, BASH_TOOL_NAME, compileToolPolicy, createCodingToolRuntime, createNoOpToolAuditSink, createToolAuditSink, EXEC_TOOL_NAME, } from "#src/tools/index";
|
|
13
|
+
import { resolveBashSupport } from "./coding-tool-bash.js";
|
|
14
|
+
import { buildDeclaredCommandTools } from "./coding-tool-extras.js";
|
|
15
|
+
import { isTempConfined, rawScreenOptionsFor } from "./coding-tool-sandbox.js";
|
|
16
|
+
export function buildCodingToolSupport(args) {
|
|
17
|
+
if (args.declared.length === 0)
|
|
18
|
+
return undefined;
|
|
19
|
+
const grants = args.grants ?? [];
|
|
20
|
+
const bashApproval = args.bashApproval ?? "gated";
|
|
21
|
+
// ADR-030 / F1: under `raw`, a declared Bash gets a SYNTHETIC grant further
|
|
22
|
+
// down (resolveBashSupport) even with zero real grants -- `scoped` with no
|
|
23
|
+
// stage allow rules is the only path there, and must not be
|
|
24
|
+
// indistinguishable from `scoped` + an unrelated allow list. Computed
|
|
25
|
+
// ahead of the guard so the guard can special-case it.
|
|
26
|
+
const rawSyntheticBash = bashApproval === "raw" && args.declared.includes(BASH_TOOL_NAME);
|
|
27
|
+
if (grants.length === 0 && !rawSyntheticBash)
|
|
28
|
+
return undefined;
|
|
29
|
+
// An empty root passed to a spawn or a path join silently means
|
|
30
|
+
// process.cwd() — the directory nax was launched from, which under `-d` is a
|
|
31
|
+
// different repository. That is the #1794 defect; refuse instead. Callers
|
|
32
|
+
// pass packageWorkdir(view), which never yields "".
|
|
33
|
+
//
|
|
34
|
+
// F1 side effect: this throw now also fires for raw + declared Bash + an
|
|
35
|
+
// empty root, where the old guard returned `undefined` first. Correct --
|
|
36
|
+
// the #1794 guard should fire there -- and pinned by a test.
|
|
37
|
+
if (args.root === undefined || args.root.trim() === "") {
|
|
38
|
+
throw new NaxError("Cannot enable coding tools: no working directory was supplied, so the permitted root is unknown.", "CODING_TOOL_ROOT_MISSING", { stage: "tools" });
|
|
39
|
+
}
|
|
40
|
+
// `Exec` is a capability marker in an operation's `tools` declaration, not
|
|
41
|
+
// a registered tool — nothing in registerBuiltinCodingTools carries this
|
|
42
|
+
// name. It decides whether RunCommand gets its argv branch (Task 6); it
|
|
43
|
+
// must never reach runtime.advertised() itself, or the lookup for a tool
|
|
44
|
+
// named "Exec" would simply fail and the marker would vanish from the
|
|
45
|
+
// advertised set without a trace of why.
|
|
46
|
+
const execGrant = grants.findLast((grant) => grant.tool === EXEC_TOOL_NAME);
|
|
47
|
+
const allowExec = args.declared.includes(EXEC_TOOL_NAME) && execGrant !== undefined;
|
|
48
|
+
const advertised = args.declared.filter((name) => name !== EXEC_TOOL_NAME);
|
|
49
|
+
// Gated on DECLARATION ALONE — deliberately not on the grant.
|
|
50
|
+
//
|
|
51
|
+
// Declaration is the ceiling: a tool the runtime can LOOK UP is callable even
|
|
52
|
+
// when `advertised()` never returned it (callTool resolves the name before it
|
|
53
|
+
// consults advertisement), so a stage that grants Bash must not hand it to a
|
|
54
|
+
// reviewer op that never declared it (spec §6 row 11).
|
|
55
|
+
//
|
|
56
|
+
// But an op that DID declare it and holds no grant must be refused by the
|
|
57
|
+
// POLICY, not by a failed name lookup: "unknown tool" carries no redirect,
|
|
58
|
+
// and spec §6 row 1 requires the ungranted case to offer an alternative. So
|
|
59
|
+
// the tool is constructed either way; with no grant, `grantedTools()` still
|
|
60
|
+
// excludes it (never advertised, no schema cost in the prompt), yet the
|
|
61
|
+
// tool's EXISTENCE is what lets the call reach `policy.check` and be denied
|
|
62
|
+
// there -- and only that denial path (in `runtime.callTool`, using
|
|
63
|
+
// `denial-redirect.ts`) can attach a redirect.
|
|
64
|
+
const { effectiveGrants, allowBash, bashDescriptionPatterns } = resolveBashSupport({
|
|
65
|
+
declared: args.declared,
|
|
66
|
+
grants,
|
|
67
|
+
toolPatterns: args.toolPatterns,
|
|
68
|
+
bashApproval,
|
|
69
|
+
});
|
|
70
|
+
const declaredCommands = args.declaredCommands ?? new Map();
|
|
71
|
+
// P4/S5: under `raw`, an UNAVAILABLE launcher refuses every Bash call.
|
|
72
|
+
// US-002: an AVAILABLE one marks the screen sandbox-wrapped, so a PRD it only reads is allowed.
|
|
73
|
+
const rawScreenOptions = rawScreenOptionsFor(args.launcher);
|
|
74
|
+
const sink = args.auditDir !== undefined
|
|
75
|
+
? createToolAuditSink({
|
|
76
|
+
dir: args.auditDir,
|
|
77
|
+
sessionName: args.sessionName ?? "unattached",
|
|
78
|
+
...(args.header ? { header: args.header } : {}),
|
|
79
|
+
})
|
|
80
|
+
: createNoOpToolAuditSink();
|
|
81
|
+
const runtime = createCodingToolRuntime({
|
|
82
|
+
policy: compileToolPolicy(effectiveGrants, args.root, {
|
|
83
|
+
bashApproval,
|
|
84
|
+
...rawScreenOptions,
|
|
85
|
+
...(args.denyRules !== undefined ? { denyRules: args.denyRules } : {}),
|
|
86
|
+
...(args.askRules !== undefined ? { askRules: args.askRules } : {}),
|
|
87
|
+
...(args.fileOutputPath !== undefined ? { ownedWriteExemption: args.fileOutputPath } : {}),
|
|
88
|
+
...(args.naxAllowWrite !== undefined ? { naxAllowWrite: args.naxAllowWrite } : {}),
|
|
89
|
+
}),
|
|
90
|
+
declaredCommands: new Set(declaredCommands.keys()),
|
|
91
|
+
interceptor: args.interceptor,
|
|
92
|
+
protectedPaths: args.protectedPaths,
|
|
93
|
+
...(args.abortSignal !== undefined ? { signal: args.abortSignal } : {}),
|
|
94
|
+
...(args.askResolver !== undefined ? { askResolver: args.askResolver } : {}),
|
|
95
|
+
...(args.commandShadow !== undefined ? { commandShadow: args.commandShadow } : {}),
|
|
96
|
+
// US-004: the sandbox's temp posture reaches the command guard's temp-only
|
|
97
|
+
// exemption. `isTempConfined` keeps the state read out of this function,
|
|
98
|
+
// which sits at its complexity baseline.
|
|
99
|
+
tempConfined: isTempConfined(args.launcher),
|
|
100
|
+
...(args.pipelineStage !== undefined ? { pipelineStage: args.pipelineStage } : {}),
|
|
101
|
+
...(args.storyId !== undefined ? { storyId: args.storyId } : {}),
|
|
102
|
+
...(args.callId !== undefined ? { callId: args.callId } : {}),
|
|
103
|
+
...(args.scopeId !== undefined ? { scopeId: args.scopeId } : {}),
|
|
104
|
+
...(args.denyPaths !== undefined ? { denyPaths: args.denyPaths } : {}),
|
|
105
|
+
sink,
|
|
106
|
+
extraTools: [
|
|
107
|
+
...(args.extraTools ?? []),
|
|
108
|
+
...buildDeclaredCommandTools({
|
|
109
|
+
declaredCommands,
|
|
110
|
+
runDeclaredCommand: args.runDeclaredCommand,
|
|
111
|
+
allowExec,
|
|
112
|
+
execGrant,
|
|
113
|
+
allowBash,
|
|
114
|
+
bashDescriptionPatterns,
|
|
115
|
+
bashApproval,
|
|
116
|
+
humanApproval: args.askResolver?.humanReachable === true,
|
|
117
|
+
root: args.root,
|
|
118
|
+
...(args.repoRoot !== undefined ? { repoRoot: args.repoRoot } : {}),
|
|
119
|
+
...(args.packageWorkdir !== undefined ? { packageWorkdir: args.packageWorkdir } : {}),
|
|
120
|
+
...(args.commandCwd !== undefined ? { commandCwd: args.commandCwd } : {}),
|
|
121
|
+
...(args.allowScripts !== undefined ? { allowScripts: args.allowScripts } : {}),
|
|
122
|
+
...(args.packageName !== undefined ? { packageName: args.packageName } : {}),
|
|
123
|
+
...(args.stripEnvVars !== undefined ? { stripEnvVars: args.stripEnvVars } : {}),
|
|
124
|
+
...(args.shell !== undefined ? { shell: args.shell } : {}),
|
|
125
|
+
...(args.launcher !== undefined ? { launcher: args.launcher } : {}),
|
|
126
|
+
}),
|
|
127
|
+
],
|
|
128
|
+
...(args.providerIdByTool !== undefined ? { providerIdByTool: args.providerIdByTool } : {}),
|
|
129
|
+
});
|
|
130
|
+
const tools = runtime.advertised(advertised);
|
|
131
|
+
// The fixed per-hop cost of advertising provider tools: their schemas enter
|
|
132
|
+
// the prompt whether or not any is called. #2031 shipped the meter and left
|
|
133
|
+
// it unread; this is its consumer, and the same instrument nax#1991's
|
|
134
|
+
// context-burn report needs.
|
|
135
|
+
const providerTools = tools.filter((tool) => args.providerIdByTool?.has(tool.name) === true);
|
|
136
|
+
if (providerTools.length > 0) {
|
|
137
|
+
getSafeLogger()?.debug("tools", "[provider] advertised", {
|
|
138
|
+
storyId: args.storyId,
|
|
139
|
+
count: providerTools.length,
|
|
140
|
+
schemaBytes: advertisedSchemaBytes(providerTools),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (tools.length === 0)
|
|
144
|
+
return undefined;
|
|
145
|
+
return { runtime, tools, auditSink: sink };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Ledger session name.
|
|
149
|
+
*
|
|
150
|
+
* Story-only names collide across the three TDD roles, which all write to one
|
|
151
|
+
* directory -- so a ledger could not answer which session made a call, and that
|
|
152
|
+
* is the evidence ADR-029 parity claims are read from.
|
|
153
|
+
*/
|
|
154
|
+
export function buildLedgerSessionName(opts) {
|
|
155
|
+
const base = opts.storyId ?? opts.featureName;
|
|
156
|
+
if (base === undefined)
|
|
157
|
+
return "unattached";
|
|
158
|
+
return opts.sessionRole === undefined ? base : `${base}-${opts.sessionRole}`;
|
|
159
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools appended to the declaration of every op that receives coding tools.
|
|
3
|
+
*
|
|
4
|
+
* Kept separate from DEFAULT_CODING_TOOLS (which is also the fallback
|
|
5
|
+
* `resolveDeclaredTools` returns for an op that omits `tools`) so the append
|
|
6
|
+
* can filter out the copies such a declaration already carries: advertising a
|
|
7
|
+
* name twice puts two entries into `runtime.advertised()`'s output, and from
|
|
8
|
+
* there a duplicate ToolDefinition in the provider request.
|
|
9
|
+
*/
|
|
10
|
+
import type { CodingToolName } from "#src/tools/index";
|
|
11
|
+
export declare const UNIVERSAL_CODING_TOOLS: readonly CodingToolName[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ShellInterceptRequest, ShellInterceptResult } from "./shell.ts";
|
|
2
|
+
/** Two members: the Git argv site and the Bash shell site (R10). */
|
|
3
|
+
export type Site = "git" | "bash";
|
|
4
|
+
export interface InterceptRequest {
|
|
5
|
+
readonly kind: "argv";
|
|
6
|
+
readonly argv: readonly string[];
|
|
7
|
+
readonly cwd: string;
|
|
8
|
+
readonly site: Site;
|
|
9
|
+
}
|
|
10
|
+
export type InterceptResult = {
|
|
11
|
+
readonly kind: "unchanged";
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: "rewritten";
|
|
14
|
+
readonly argv: readonly string[];
|
|
15
|
+
readonly provider: string;
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: "declined";
|
|
18
|
+
readonly reason: string;
|
|
19
|
+
};
|
|
20
|
+
export interface CommandInterceptor {
|
|
21
|
+
readonly provider: string;
|
|
22
|
+
intercept(req: InterceptRequest): Promise<InterceptResult>;
|
|
23
|
+
/** The Bash site's entry point. Absent on an argv-only interceptor. */
|
|
24
|
+
interceptShell?(req: ShellInterceptRequest): Promise<ShellInterceptResult>;
|
|
25
|
+
/** Consulted ONLY for output of a command this interceptor actually rewrote. */
|
|
26
|
+
postProcess?(output: string, req: InterceptRequest | ShellInterceptRequest): {
|
|
27
|
+
output: string;
|
|
28
|
+
notes?: Record<string, string>;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export interface InterceptOutcome {
|
|
32
|
+
readonly argv: readonly string[];
|
|
33
|
+
readonly executed?: readonly string[];
|
|
34
|
+
readonly provider?: string;
|
|
35
|
+
readonly rewritten: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A rewrite may do exactly one thing: prefix the original argv with the
|
|
39
|
+
* provider's own binary name. Anything else is refused.
|
|
40
|
+
*
|
|
41
|
+
* The argv analogue of R9's shell narrowing, and far tighter because it can be:
|
|
42
|
+
* the mapping is static and built by nax, never a string parsed back out of a
|
|
43
|
+
* subprocess.
|
|
44
|
+
*/
|
|
45
|
+
export declare function validateRewrite(req: InterceptRequest, result: InterceptResult): InterceptResult;
|
|
46
|
+
/**
|
|
47
|
+
* The whole seam, so call sites stay one line (see the file-size gate).
|
|
48
|
+
*
|
|
49
|
+
* Fails open at REWRITE time only (R3): if the interceptor is sick we run the
|
|
50
|
+
* original. A rewritten command that actually RAN and failed keeps its exit
|
|
51
|
+
* code — nax never re-runs raw to disambiguate one, because that makes
|
|
52
|
+
* execution non-idempotent and can cost a full test-suite run.
|
|
53
|
+
*/
|
|
54
|
+
export declare function interceptArgv(argv: readonly string[], cwd: string, interceptor: CommandInterceptor | undefined): Promise<InterceptOutcome>;
|
|
55
|
+
export * from "./shell.ts";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic command-interception seam. rtk is a consumer, not a concept here (R1).
|
|
3
|
+
*
|
|
4
|
+
* Two request shapes, one per site: `argv` for the Git tool, validated by
|
|
5
|
+
* `validateRewrite` below, and `shell` for the model-authored Bash string,
|
|
6
|
+
* validated by `validateShellRewrite` in `./shell`. R10 keeps config-supplied
|
|
7
|
+
* command strings out of both.
|
|
8
|
+
*/
|
|
9
|
+
import { GIT_ESCAPE_FLAGS } from "#src/tools/git-flags/index";
|
|
10
|
+
function decline(reason) {
|
|
11
|
+
return { kind: "declined", reason };
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A rewrite may do exactly one thing: prefix the original argv with the
|
|
15
|
+
* provider's own binary name. Anything else is refused.
|
|
16
|
+
*
|
|
17
|
+
* The argv analogue of R9's shell narrowing, and far tighter because it can be:
|
|
18
|
+
* the mapping is static and built by nax, never a string parsed back out of a
|
|
19
|
+
* subprocess.
|
|
20
|
+
*/
|
|
21
|
+
export function validateRewrite(req, result) {
|
|
22
|
+
if (result.kind !== "rewritten")
|
|
23
|
+
return result;
|
|
24
|
+
const { argv, provider } = result;
|
|
25
|
+
if (argv.length !== req.argv.length + 1)
|
|
26
|
+
return decline("rewrite must add exactly one leading token");
|
|
27
|
+
if (argv[0] !== provider)
|
|
28
|
+
return decline(`rewrite must lead with the provider binary, got ${argv[0]}`);
|
|
29
|
+
for (const [i, token] of req.argv.entries()) {
|
|
30
|
+
if (argv[i + 1] !== token)
|
|
31
|
+
return decline(`rewrite altered token ${i}`);
|
|
32
|
+
}
|
|
33
|
+
if (argv.some((token) => GIT_ESCAPE_FLAGS.includes(token)))
|
|
34
|
+
return decline("argv carries a git escape flag");
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The whole seam, so call sites stay one line (see the file-size gate).
|
|
39
|
+
*
|
|
40
|
+
* Fails open at REWRITE time only (R3): if the interceptor is sick we run the
|
|
41
|
+
* original. A rewritten command that actually RAN and failed keeps its exit
|
|
42
|
+
* code — nax never re-runs raw to disambiguate one, because that makes
|
|
43
|
+
* execution non-idempotent and can cost a full test-suite run.
|
|
44
|
+
*/
|
|
45
|
+
export async function interceptArgv(argv, cwd, interceptor) {
|
|
46
|
+
if (interceptor === undefined)
|
|
47
|
+
return { argv, rewritten: false };
|
|
48
|
+
const req = { kind: "argv", argv, cwd, site: "git" };
|
|
49
|
+
let outcome;
|
|
50
|
+
try {
|
|
51
|
+
outcome = validateRewrite(req, await interceptor.intercept(req));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
outcome = decline("interceptor threw");
|
|
55
|
+
}
|
|
56
|
+
if (outcome.kind !== "rewritten")
|
|
57
|
+
return { argv, rewritten: false };
|
|
58
|
+
return { argv: outcome.argv, executed: outcome.argv, provider: outcome.provider, rewritten: true };
|
|
59
|
+
}
|
|
60
|
+
export * from "./shell.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CommandInterceptor } from "./index.ts";
|
|
2
|
+
export interface ShellInterceptRequest {
|
|
3
|
+
readonly kind: "shell";
|
|
4
|
+
readonly command: string;
|
|
5
|
+
readonly cwd: string;
|
|
6
|
+
readonly site: "bash";
|
|
7
|
+
}
|
|
8
|
+
export type ShellInterceptResult = {
|
|
9
|
+
readonly kind: "unchanged";
|
|
10
|
+
} | {
|
|
11
|
+
readonly kind: "rewritten";
|
|
12
|
+
readonly command: string;
|
|
13
|
+
readonly provider: string;
|
|
14
|
+
} | {
|
|
15
|
+
readonly kind: "declined";
|
|
16
|
+
readonly reason: string;
|
|
17
|
+
};
|
|
18
|
+
export interface ShellInterceptOutcome {
|
|
19
|
+
/** What to execute: the original, or the validated rewrite. */
|
|
20
|
+
readonly command: string;
|
|
21
|
+
readonly provider?: string;
|
|
22
|
+
readonly rewritten: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Accepts a rewrite only if it is the original plus `rtk ` insertions at command
|
|
26
|
+
* words. The provider's own answer is never trusted: every rule below is checked
|
|
27
|
+
* against the byte-exact candidate, and the first failure names why.
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateShellRewrite(req: ShellInterceptRequest, result: ShellInterceptResult): ShellInterceptResult;
|
|
30
|
+
/**
|
|
31
|
+
* The seam, one line at the call site.
|
|
32
|
+
*
|
|
33
|
+
* Fails open at REWRITE time only (R3): no interceptor, no shell method, a
|
|
34
|
+
* throw or a failed validation all run the original command unchanged.
|
|
35
|
+
*/
|
|
36
|
+
export declare function interceptShell(command: string, cwd: string, interceptor: CommandInterceptor | undefined): Promise<ShellInterceptOutcome>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { lexBashCommand } from "#src/permissions/index";
|
|
2
|
+
/** The four bytes an accepted rewrite may add, once or many times. */
|
|
3
|
+
const INSERTION = "rtk ";
|
|
4
|
+
/** A leading `NAME=value` assignment, which precedes (and is not) the command word. */
|
|
5
|
+
const ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
6
|
+
function decline(reason) {
|
|
7
|
+
return { kind: "declined", reason };
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Rule 1: walk the original and the candidate together. The only legal
|
|
11
|
+
* divergence is the candidate holding `rtk ` while the original still has a
|
|
12
|
+
* character to offer; any other difference, and any insertion trailing the
|
|
13
|
+
* original's last character, is refused. Returns the insertion count, or -1.
|
|
14
|
+
*/
|
|
15
|
+
function countInsertions(original, candidate) {
|
|
16
|
+
let i = 0;
|
|
17
|
+
let j = 0;
|
|
18
|
+
let insertions = 0;
|
|
19
|
+
while (j < candidate.length) {
|
|
20
|
+
if (i < original.length && original[i] === candidate[j]) {
|
|
21
|
+
i += 1;
|
|
22
|
+
j += 1;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (i < original.length && candidate.startsWith(INSERTION, j)) {
|
|
26
|
+
insertions += 1;
|
|
27
|
+
j += INSERTION.length;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
return -1;
|
|
31
|
+
}
|
|
32
|
+
return i === original.length ? insertions : -1;
|
|
33
|
+
}
|
|
34
|
+
/** Rule 4's index: the command word follows the leading assignments, if any. */
|
|
35
|
+
function leadingAssignments(tokens) {
|
|
36
|
+
let k = 0;
|
|
37
|
+
while (k < tokens.length && ASSIGNMENT.test(tokens[k].text))
|
|
38
|
+
k += 1;
|
|
39
|
+
return k;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Rules 4 and 5 for one segment: its tokens either match the original's, or
|
|
43
|
+
* match them with a single `rtk` inserted at the command word — and a segment
|
|
44
|
+
* already prefixed with `rtk` is left alone.
|
|
45
|
+
*/
|
|
46
|
+
function classifySegment(original, candidate) {
|
|
47
|
+
const before = original.tokens;
|
|
48
|
+
const after = candidate.tokens;
|
|
49
|
+
if (before.length === after.length) {
|
|
50
|
+
for (let i = 0; i < before.length; i += 1) {
|
|
51
|
+
if (before[i].text !== after[i].text)
|
|
52
|
+
return "invalid";
|
|
53
|
+
}
|
|
54
|
+
return "equal";
|
|
55
|
+
}
|
|
56
|
+
if (after.length !== before.length + 1)
|
|
57
|
+
return "invalid";
|
|
58
|
+
const k = leadingAssignments(before);
|
|
59
|
+
const inserted = after[k];
|
|
60
|
+
if (inserted.text !== "rtk" || inserted.opaque)
|
|
61
|
+
return "invalid";
|
|
62
|
+
if (before[k]?.text === "rtk")
|
|
63
|
+
return "invalid";
|
|
64
|
+
for (let i = 0; i < k; i += 1) {
|
|
65
|
+
if (before[i].text !== after[i].text)
|
|
66
|
+
return "invalid";
|
|
67
|
+
}
|
|
68
|
+
for (let i = k; i < before.length; i += 1) {
|
|
69
|
+
if (before[i].text !== after[i + 1].text)
|
|
70
|
+
return "invalid";
|
|
71
|
+
}
|
|
72
|
+
return "inserted";
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Rule 6: a segment whose own separator is `|` writes into a pipe, and one
|
|
76
|
+
* whose predecessor's separator is `|` reads from it. Compaction would change
|
|
77
|
+
* what the neighbouring program sees, so neither is rewritten.
|
|
78
|
+
*/
|
|
79
|
+
function inPipeline(segments, index) {
|
|
80
|
+
return segments[index].separator === "|" || segments[index - 1]?.separator === "|";
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Accepts a rewrite only if it is the original plus `rtk ` insertions at command
|
|
84
|
+
* words. The provider's own answer is never trusted: every rule below is checked
|
|
85
|
+
* against the byte-exact candidate, and the first failure names why.
|
|
86
|
+
*/
|
|
87
|
+
export function validateShellRewrite(req, result) {
|
|
88
|
+
if (result.kind !== "rewritten")
|
|
89
|
+
return result;
|
|
90
|
+
const insertions = countInsertions(req.command, result.command);
|
|
91
|
+
if (insertions < 0)
|
|
92
|
+
return decline("candidate is not the original plus `rtk ` insertions");
|
|
93
|
+
if (insertions === 0)
|
|
94
|
+
return { kind: "unchanged" };
|
|
95
|
+
const before = lexBashCommand(req.command);
|
|
96
|
+
const after = lexBashCommand(result.command);
|
|
97
|
+
if (before.kind !== "ok" || after.kind !== "ok")
|
|
98
|
+
return decline("original or candidate does not lex");
|
|
99
|
+
if (before.segments.length !== after.segments.length)
|
|
100
|
+
return decline("candidate changed the segment count");
|
|
101
|
+
let inserted = 0;
|
|
102
|
+
for (let i = 0; i < before.segments.length; i += 1) {
|
|
103
|
+
const rewrite = classifySegment(before.segments[i], after.segments[i]);
|
|
104
|
+
if (rewrite === "invalid")
|
|
105
|
+
return decline(`segment ${i} is not the original plus an rtk command word`);
|
|
106
|
+
if (rewrite === "inserted") {
|
|
107
|
+
if (inPipeline(before.segments, i))
|
|
108
|
+
return decline("a rewritten segment would change what a pipe reads");
|
|
109
|
+
inserted += 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (inserted !== insertions)
|
|
113
|
+
return decline("insertions do not line up with commands");
|
|
114
|
+
return { kind: "rewritten", command: result.command, provider: result.provider };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The seam, one line at the call site.
|
|
118
|
+
*
|
|
119
|
+
* Fails open at REWRITE time only (R3): no interceptor, no shell method, a
|
|
120
|
+
* throw or a failed validation all run the original command unchanged.
|
|
121
|
+
*/
|
|
122
|
+
export async function interceptShell(command, cwd, interceptor) {
|
|
123
|
+
if (interceptor === undefined || interceptor.interceptShell === undefined) {
|
|
124
|
+
return { command, rewritten: false };
|
|
125
|
+
}
|
|
126
|
+
const req = { kind: "shell", command, cwd, site: "bash" };
|
|
127
|
+
let outcome;
|
|
128
|
+
try {
|
|
129
|
+
outcome = validateShellRewrite(req, await interceptor.interceptShell(req));
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
outcome = decline("interceptor threw");
|
|
133
|
+
}
|
|
134
|
+
if (outcome.kind !== "rewritten")
|
|
135
|
+
return { command, rewritten: false };
|
|
136
|
+
return { command: outcome.command, provider: outcome.provider, rewritten: true };
|
|
137
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CommandShadow } from "./types.ts";
|
|
2
|
+
export declare const COMMAND_SAFETY_DIR = "command-safety";
|
|
3
|
+
export interface BuildCommandShadowOptions {
|
|
4
|
+
readonly config: {
|
|
5
|
+
readonly shadow?: {
|
|
6
|
+
readonly url: string;
|
|
7
|
+
readonly timeoutMs: number;
|
|
8
|
+
readonly authEnv: string;
|
|
9
|
+
};
|
|
10
|
+
readonly guard?: {
|
|
11
|
+
readonly threshold: number;
|
|
12
|
+
};
|
|
13
|
+
} | undefined;
|
|
14
|
+
readonly outputDir: string;
|
|
15
|
+
readonly runId: string;
|
|
16
|
+
readonly storyId?: string;
|
|
17
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
18
|
+
}
|
|
19
|
+
export declare function buildCommandShadow(opts: BuildCommandShadowOptions): CommandShadow | undefined;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the per-story shadow from config (spec 4.5, 5).
|
|
3
|
+
*
|
|
4
|
+
* The config parameter is typed structurally so this module imports nothing
|
|
5
|
+
* from src/config (D8). Returns undefined when no URL is configured, so no
|
|
6
|
+
* shadow code runs on the call path by default.
|
|
7
|
+
*
|
|
8
|
+
* US-003: when the config also carries a `guard`, the shadow exposes a
|
|
9
|
+
* `CommandGuard` whose `assess` reuses this shadow's classifier cache and
|
|
10
|
+
* rule baseline. Absent guard = `shadow.guard === undefined`, the safe
|
|
11
|
+
* default that no shipped profile opts into.
|
|
12
|
+
*/
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { errorMessage } from "#src/infra/errors";
|
|
15
|
+
import { getSafeLogger } from "#src/infra/index";
|
|
16
|
+
import { appendCommandSafetyRow } from "./row.js";
|
|
17
|
+
import { createCommandShadow } from "./shadow.js";
|
|
18
|
+
import { createSystemOneClient } from "./systemone-client.js";
|
|
19
|
+
export const COMMAND_SAFETY_DIR = "command-safety";
|
|
20
|
+
export function buildCommandShadow(opts) {
|
|
21
|
+
const shadow = opts.config?.shadow;
|
|
22
|
+
if (shadow === undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
const token = opts.env[shadow.authEnv];
|
|
25
|
+
const dir = join(opts.outputDir, COMMAND_SAFETY_DIR);
|
|
26
|
+
let warned = false;
|
|
27
|
+
return createCommandShadow({
|
|
28
|
+
classify: createSystemOneClient({
|
|
29
|
+
url: shadow.url,
|
|
30
|
+
timeoutMs: shadow.timeoutMs,
|
|
31
|
+
...(token !== undefined && token.length > 0 ? { token } : {}),
|
|
32
|
+
}),
|
|
33
|
+
write: (row) => appendCommandSafetyRow(dir, opts.runId, row),
|
|
34
|
+
runId: opts.runId,
|
|
35
|
+
timeoutMs: shadow.timeoutMs,
|
|
36
|
+
...(opts.config?.guard !== undefined ? { guard: opts.config.guard } : {}),
|
|
37
|
+
onWriteError: (err) => {
|
|
38
|
+
if (warned)
|
|
39
|
+
return;
|
|
40
|
+
warned = true;
|
|
41
|
+
getSafeLogger()?.warn("command-safety", "Shadow row append failed; later failures this story are not logged", {
|
|
42
|
+
storyId: opts.storyId,
|
|
43
|
+
error: errorMessage(err),
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|