Files
DeepSeek-Reasonix/internal/shellsafe/testdata/command_effects.json
T
1ac7817ff3 fix(shellsafe): unify command effect classification / 统一命令效果分类并修复只读误判 (#8417)
* refactor(shellsafe): classify durable command effects

Problem: Boolean read-only tables could not distinguish observational and mutating forms of the same command family, so policy and permission consumers drifted.

Root cause: Argument-sensitive checks lived in individual consumers and unknown commands collapsed into one mutation bit.

Fix: Add a shared fail-closed CommandEffect model with parameter-aware Git, date, Go, npm, and Cargo rules, and migrate Bash permission reads to it.

Verification: go test ./internal/shellsafe ./internal/permission

* refactor(evidence): separate durable tool effects

Problem: One mutation boolean simultaneously drove TaskPolicy, workspace serialization, batch recovery, and delivery content debt.

Root cause: Repository metadata writes and content writes were indistinguishable, while evidence duplicated shell argument checks.

Fix: Add ToolEffects projections, derive Bash effects from shellsafe, keep conventional verifiers non-mutating, and make receipts track content mutation only.

Verification: go test ./internal/evidence ./internal/recovery

* refactor(runtime): consume shared tool effects

Problem:
Runtime policy boundaries overloaded schema read-only and mutation booleans, so repository metadata, workspace content, and host state could receive contradictory treatment.

Root cause:
Permission, TaskPolicy, Auto Guard, delivery evidence, workspace leases, proxy resolution, and desktop invalidation projected different semantics from the same call.

Fix:
Carry ToolEffects through the resolved tool plan, reclassify proxy targets before commit or execution, separate state/workspace/content/repository consumers, and promote observed file changes before recording evidence.

Verification:
go test ./internal/shellsafe ./internal/permission ./internal/evidence ./internal/agent ./internal/recovery ./internal/checkpoint

* fix(agent): make mutation barriers effect-aware

Problem:
A failed reader could be treated like a failed mutation, while dependency messages hid which safe write domain caused the batch to stop.

Root cause:
Batch preclassification and post-execution failure handling mixed schema read-only, a coarse mutation fallback, and a bare atomic boolean.

Fix:
Store an immutable sanitized barrier cause, classify static and resolved calls through ToolEffects, keep readers running, and skip only later state writers or verifiers with structured not-run metadata.

Verification:
go test ./internal/agent -run 'TestBatchDependencyBarrier|TestDependentMutationSkippedAfterFailedWriterInBatch|TestToolBeforeFailedWriterReplacementOpensDependencyBarrier'

* test(shellsafe): share command effect matrix

Problem:
Dual-mode command regressions could recur when shellsafe, permission, evidence, and agent tests encoded separate expectations.

Root cause:
There was no shared cross-boundary corpus for certainty, write domains, permission readers, content evidence, TaskPolicy, and batch barriers.

Fix:
Add one testdata matrix consumed across all four boundaries, cover representative read/write/unknown compositions, and make legacy read-only tables private behind compatibility adapters.

Verification:
go test ./internal/shellsafe ./internal/permission ./internal/evidence ./internal/agent -run 'SharedCommandEffectMatrix|ConsumesSharedCommandEffectMatrix|ClassifyBashCommandEffects'

* refactor(runtime): isolate effect projections

Problem:
The complete command-effect change pushed several historically large files beyond the repository's no-growth lint budgets and duplicated focused tests inside an oversized ledger test file.

Root cause:
New effect projection, checkpoint comparison, and tool-plan state lived beside unrelated legacy responsibilities.

Fix:
Extract those responsibilities into focused files, compact internal helpers, and keep legacy adapters as thin documented projections without changing behavior.

Verification:
go test ./internal/shellsafe ./internal/permission ./internal/evidence ./internal/checkpoint ./internal/agent ./internal/recovery
make lint

* fix(runtime): absorb related command-effect PR mechanisms

Problem:
The rebased shell-effect work still missed sibling PR mechanisms and the
git config editor hole.

Root cause:
Fail-closed lifecycle cases, env-prefixed verifiers, and question-keyed
read novelty lived on other branches. classifyGitConfig treated --edit as
a reader.

Fix:
Absorb #7760 matrix rows, #8012 env-prefix verification via ParseStaticCommand,
and #8407 noteQuestion scoring. Keep git config --edit as a writer. Leave
#8614 ExecutionEnv and #8404 runway out of this PR.

Verification:
go test ./internal/shellsafe ./internal/permission ./internal/evidence ./internal/checkpoint ./internal/recovery ./internal/agent
go test -race ./internal/shellsafe ./internal/permission ./internal/evidence ./internal/agent

Co-authored-by: Wooseong Kim <[email protected]>
Co-authored-by: lbx13 <[email protected]>
Co-authored-by: yhh <[email protected]>

* test(evidence): keep command-effect cases out of the oversized ledger test

Problem:
CI lint failed because three command-effect cases were added to
internal/evidence/evidence_test.go, which is already over the test-file-size
ratchet.

Root cause:
The new ToolCallMutates rows lived in the historical ledger test file instead
of the focused effect projection tests.

Fix:
Move the env-prefixed verifier case into effects_test.go. Branch listing and
pure-commit contracts were already covered there.

Verification:
go test ./internal/evidence -run 'TestToolCallMutatesForDeliveryProfile|TestClassifyToolCallSeparatesMutationDomains|TestEnvPrefixVerification|TestReceiptMutationTracksContentNotRepositoryCement'
go run ./tools/repolint

---------

Co-authored-by: Wooseong Kim <[email protected]>
Co-authored-by: lbx13 <[email protected]>
Co-authored-by: yhh <[email protected]>
2026-08-13 04:10:05 +08:00

41 lines
8.6 KiB
JSON

[
{"name":"branch list","command":"git branch -a","certainty":"known","writes":[],"permissionReader":true,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":false,"contentMutation":false,"batchBarrier":false},
{"name":"branch delete","command":"git branch -D private-operand","certainty":"known","writes":["repository"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":false,"batchBarrier":true},
{"name":"tag list","command":"git tag --list 'v1.*'","certainty":"known","writes":[],"permissionReader":true,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":false,"contentMutation":false,"batchBarrier":false},
{"name":"tag create","command":"git tag private-operand","certainty":"known","writes":["repository"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":false,"batchBarrier":true},
{"name":"remote list","command":"git remote -v","certainty":"known","writes":[],"permissionReader":true,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":false,"contentMutation":false,"batchBarrier":false},
{"name":"config list","command":"git config --list","certainty":"known","writes":[],"permissionReader":true,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":false,"contentMutation":false,"batchBarrier":false},
{"name":"config set","command":"git config user.name private-operand","certainty":"known","writes":["repository"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":false,"batchBarrier":true},
{"name":"config edit","command":"git config --edit","certainty":"known","writes":["repository"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":false,"batchBarrier":true},
{"name":"config global edit","command":"git config --global -e","certainty":"known","writes":["host"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":false,"batchBarrier":true},
{"name":"pure commit","command":"git commit -q -m private-operand","certainty":"known","writes":["repository"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":false,"batchBarrier":true},
{"name":"commit all","command":"git commit -am private-operand","certainty":"known","writes":["content","repository"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true},
{"name":"date display","command":"date -u +%s","certainty":"known","writes":[],"permissionReader":true,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":false,"contentMutation":false,"batchBarrier":false},
{"name":"date set","command":"date --set private-operand","certainty":"known","writes":["host"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":false,"batchBarrier":true},
{"name":"npm audit","command":"npm audit","certainty":"known","writes":[],"permissionReader":true,"executesCode":false,"usesNetwork":true,"taskPolicyBlocked":false,"contentMutation":false,"batchBarrier":false},
{"name":"npm audit fix","command":"npm audit fix","certainty":"known","writes":["content"],"permissionReader":false,"executesCode":false,"usesNetwork":true,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true},
{"name":"go list readonly","command":"go list -mod=readonly ./...","certainty":"known","writes":[],"permissionReader":true,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":false,"contentMutation":false,"batchBarrier":false},
{"name":"go list mod","command":"go list -mod=mod ./...","certainty":"known","writes":["content"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true},
{"name":"cargo check","command":"cargo check","certainty":"known","writes":["content"],"permissionReader":false,"executesCode":true,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true},
{"name":"safe pipeline","command":"git branch -a | head -10","certainty":"known","writes":[],"permissionReader":true,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":false,"contentMutation":false,"batchBarrier":false},
{"name":"reader and writer","command":"git status && git tag private-operand","certainty":"known","writes":["repository"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":false,"batchBarrier":true},
{"name":"dynamic expansion","command":"echo $DYNAMIC_VALUE","certainty":"unknown","writes":[],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true},
{"name":"background job","command":"git status &","certainty":"unknown","writes":[],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true},
{"name":"negated reader","command":"! git status","certainty":"unknown","writes":[],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true},
{"name":"assignment wrapper","command":"FOO=bar git status","certainty":"known","writes":[],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":false,"contentMutation":false,"batchBarrier":false},
{"name":"subshell","command":"(ls -la)","certainty":"unknown","writes":[],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true},
{"name":"heredoc","command":"cat <<EOF\nhello\nEOF","certainty":"unknown","writes":[],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true},
{"name":"find listing","command":"find . -name '*.go'","certainty":"known","writes":[],"permissionReader":true,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":false,"contentMutation":false,"batchBarrier":false},
{"name":"find delete","command":"find . -delete","certainty":"known","writes":["content"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true},
{"name":"find exec","command":"find . -exec rm {} ;","certainty":"unknown","writes":[],"permissionReader":false,"executesCode":true,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true},
{"name":"sort output","command":"sort --output=out f","certainty":"known","writes":["content"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true},
{"name":"go env write","command":"go env -w GOFLAGS=-mod=mod","certainty":"known","writes":["host"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":false,"batchBarrier":true},
{"name":"git log output","command":"git log --output=history.patch","certainty":"known","writes":["content"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true},
{"name":"bare tag list","command":"git tag","certainty":"known","writes":[],"permissionReader":true,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":false,"contentMutation":false,"batchBarrier":false},
{"name":"annotated tag","command":"git tag -a v1.0","certainty":"known","writes":["repository"],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":false,"batchBarrier":true},
{"name":"env prefixed reader","command":"GOROOT=/x git status","certainty":"known","writes":[],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":false,"contentMutation":false,"batchBarrier":false},
{"name":"env utility reader","command":"env GOROOT=/x git status","certainty":"known","writes":[],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":false,"contentMutation":false,"batchBarrier":false},
{"name":"env flags","command":"env -i git status","certainty":"unknown","writes":[],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true},
{"name":"dynamic env assignment","command":"FOO=$(whoami) git status","certainty":"unknown","writes":[],"permissionReader":false,"executesCode":false,"usesNetwork":false,"taskPolicyBlocked":true,"contentMutation":true,"batchBarrier":true}
]