mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-11 17:10:38 -10:00
* Unify model credential recovery across Desktop and CLI Problem: Missing or rejected credentials could still reach model requests, setup edits were not transactionally isolated, and Desktop/CLI recovery behavior diverged. Root cause: Credential persistence, request receipts, authentication admission, and recovery UI state were owned by separate paths without a shared atomic connection contract. Fix: Introduce shared model-settings and credential diagnostics services, durable receipt recovery with isolated credential slots, connection-scoped authentication gates, Desktop recovery actions, and CLI setup/auth flows. Preserve drafts and running snapshots while applying new settings at the next admission boundary. Verification: Passed root and Desktop full Go suites, focused race tests, go vet, frontend build/typecheck and lifecycle tests, browser recovery scenarios, Electron smoke, generated inventory checks, and Windows cross-compilation. * Align provider probing with frontend dependencies Problem: The shared provider probe imported the boot frontend from a lower-level package, violating the repository dependency contract and leaving the lint ratchet stale. Root cause: Provider construction ownership was separated from the boot package even though both CLI and Desktop are frontend callers. Fix: Move the bounded request-local probe into boot, update both callers, regenerate inventory, and record the measured lint budget changes for the cross-client recovery feature. Verification: Passed boot, CLI, and focused Desktop tests, repolint, diff checks, and desktop inventory freshness. * Satisfy credential recovery lint contracts Problem: The CI-pinned linter rejected one rollback error path and one receipt assertion. Root cause: The rollback path wrapped only one of two errors, while the test converted a byte slice through fmt.Sprint before string formatting. Fix: Join and wrap both verification errors, and convert receipt bytes directly to string. Verification: Passed the config suite, go vet, repolint, diff checks, and golangci-lint v2.12.2 with zero issues. * Close CodeQL credential disclosure paths Problem: CodeQL found that configuration contents were hashed as password-like data and install-source responses could carry credential-bearing internal state to stdout. Root cause: Config revisions used an unkeyed digest, while public action responses copied the complete internal action before clearing selected fields. Fix: Derive config revisions with the private durable HMAC key and construct public install actions field-by-field, excluding environment, headers, and internal config state. Verification: Added keyed-revision and output-redaction regressions; passed config, installsource, and CLI tests, go vet, repolint, diff checks, and golangci-lint v2.12.2 with zero issues. * Redact plugin install command output Problem: CodeQL continued to treat the install-source tool result as credential-bearing when the CLI printed the raw JSON response. Root cause: The CLI trusted a generic tool output string at its stdout boundary, so upstream sanitization could not prove that future response fields were safe. Fix: Parse only the boolean result and status, normalize status through a closed enum, and emit newly encoded summary JSON. Raw tool output never reaches stdout. Verification: Added status allowlist coverage; passed focused CLI tests, go vet, repolint, diff checks, and golangci-lint v2.12.2 with zero issues. * Fix connection recovery admission and approval identity Problem: saved credentials could leave Desktop and CLI unable to submit, auxiliary authentication could bypass or poison the wrong connection, and install previews lost their approval contract. Root cause: admission consulted stale runtime authentication, request errors lacked the actual model identity, project saves rewrote typed documents, and approval hashing reused a redacted display shape. Fix: apply saved settings before CLI admission, expose runtime-bound Desktop pending state, gate actual requests without wrapping provider capabilities, preserve project document deltas, bind approvals to complete execution inputs with HMAC, restore sanitized CLI previews, and retain Windows repair error chains. Verification: focused regression suites, root/shared and Desktop authentication race suites, frontend typecheck and Composer tests, browser draft/recovery checks, generated contract/inventory checks, repository lint, and Windows cross-platform lint pass. Full root and Desktop suites are also running; exact-head CI will provide native platform evidence.
77 lines
2.4 KiB
Go
77 lines
2.4 KiB
Go
package cli
|
|
|
|
import (
|
|
"encoding/json"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"reasonix/internal/pluginpkg"
|
|
)
|
|
|
|
func TestPluginInstallReturnsFailureExitForFailedJSON(t *testing.T) {
|
|
home := t.TempDir()
|
|
t.Setenv("REASONIX_HOME", home)
|
|
|
|
source := filepath.Join(t.TempDir(), "superpowers")
|
|
writePluginTestFile(t, filepath.Join(source, pluginpkg.CodexManifest), `{
|
|
"name": "superpowers",
|
|
"version": "6.1.1",
|
|
"description": "Planning workflows",
|
|
"skills": "skills"
|
|
}`)
|
|
writePluginTestFile(t, filepath.Join(source, "skills", "using-superpowers", "SKILL.md"), "---\ndescription: Use Superpowers\n---\nUse Superpowers.")
|
|
|
|
firstOut := captureStdout(t, func() {
|
|
if rc := pluginCommand([]string{"install", source, "--yes"}); rc != 0 {
|
|
t.Fatalf("first plugin install rc = %d, want 0", rc)
|
|
}
|
|
})
|
|
var first struct {
|
|
OK bool `json:"ok"`
|
|
}
|
|
if err := json.Unmarshal([]byte(firstOut), &first); err != nil {
|
|
t.Fatalf("first output is not JSON: %v\n%s", err, firstOut)
|
|
}
|
|
if !first.OK {
|
|
t.Fatalf("first output ok = false:\n%s", firstOut)
|
|
}
|
|
|
|
secondOut := captureStdout(t, func() {
|
|
if rc := pluginCommand([]string{"install", source, "--yes"}); rc != 1 {
|
|
t.Fatalf("duplicate plugin install rc = %d, want 1", rc)
|
|
}
|
|
})
|
|
var second struct {
|
|
OK bool `json:"ok"`
|
|
Status string `json:"status"`
|
|
}
|
|
if err := json.Unmarshal([]byte(secondOut), &second); err != nil {
|
|
t.Fatalf("second output is not JSON: %v\n%s", err, secondOut)
|
|
}
|
|
if second.OK || second.Status != "failed" {
|
|
t.Fatalf("duplicate output ok/status = %v/%q, want false/failed\n%s", second.OK, second.Status, secondOut)
|
|
}
|
|
}
|
|
|
|
func TestInstallSourceOutputPreservesPlanAndRedactsSecrets(t *testing.T) {
|
|
out, err := redactInstallSourceJSON(`{"ok":false,"status":"failed","planId":"approved-plan","actions":[{"name":"demo","risk":"high","error":"Authorization: Bearer secret-credential","env":{"API_KEY":"private-value"}}],"next":["preview again"]}`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(out, "secret-credential") || strings.Contains(out, "private-value") {
|
|
t.Fatal("secret leaked")
|
|
}
|
|
var result struct {
|
|
PlanID string `json:"planId"`
|
|
Actions []json.RawMessage `json:"actions"`
|
|
Next []string `json:"next"`
|
|
}
|
|
if err := json.Unmarshal([]byte(out), &result); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.PlanID != "approved-plan" || len(result.Actions) != 1 || len(result.Next) != 1 {
|
|
t.Fatalf("lost plan metadata: %s", out)
|
|
}
|
|
}
|