mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
* fix(guard): bind repair apply to confirmed preview * Harden Guard preview confirmation binding Problem: non-interactive apply-plan could accept a bare --yes, while preview IDs omitted derived-state contents and full pending-update identity. Root cause: confirmation hashes covered only display previews and were checked once before the plan loop, so state-dependent actions could drift after confirmation. Fix: require a prior preview ID for non-interactive apply-plan, bind opaque file and transaction state, re-check each action immediately before execution, and preserve unrelated multi-action behavior. Update English and Chinese recovery docs. Verification: go test ./internal/repair ./cmd/reasonix-guard; go test -race ./internal/repair; go vet ./...; go test ./...; git diff --check. Co-authored-by: SivanCola <[email protected]> * Serialize confirmed Guard repair mutations Problem: preview IDs were rechecked before mutation, but another process could still replace a target during the final rename window or race on last-repair.json, losing or mixing undo records. Root cause: repair actions lacked a shared transaction lock and per-target cross-process locks, while confirmed snapshot/config bytes were read again after approval and replacement writes could overwrite a concurrent recreation. Fix: serialize repair bookkeeping before canonical target locks, revalidate confirmed state inside the lock and around renames, publish restored content with create-only atomic writes, bind complete pending-update identity, preserve concurrent writers, and keep undo under the same lock order. Add focused coverage for wait/recheck, rename windows, snapshot bytes, unreadable LKG nodes, transaction serialization, and rollback identity. Verification: go test ./internal/fileutil ./internal/config ./internal/repair ./cmd/reasonix-guard; go test -race ./internal/fileutil ./internal/repair; go vet ./...; env -u DEEPSEEK_API_KEY go test ./...; Windows test-binary cross-compilation for internal/repair and cmd/reasonix-guard; git diff --check. Co-authored-by: SivanCola <[email protected]> * Normalize Windows repair lock test paths Problem: the Windows CI test waited for a raw backslash path while the lock hook receives the canonical lower-case slash form, so the test timed out before exercising the recheck. Root cause: the regression test compared platform-specific input text instead of the production lock key representation. Fix: normalize test hook paths with the same Windows case and separator rules used by repair mutation locks. Verification: focused repair lock tests pass on macOS; Windows amd64 test-binary compilation passes. Co-authored-by: SivanCola <[email protected]> * test(jobs): remove StartForSession race Keep the worker blocked while assertions inspect its state and protect job fields with the existing mutex. This prevents the race job from observing concurrent completion writes when validating accepted input. Co-authored-by: PaulDing98 <[email protected]> * fix(repair): bind target identity into Guard preview confirmation Problem: - previewId hashed only exported preview fields and content-based state IDs, so identical reasonix.toml content in two project roots produced the same confirmation and apply-plan --yes could mutate the unconfirmed project. - Mutation locks used Abs/Clean only, so symlink path aliases (and macOS case variants) took different locks for the same real node. - rollback_update bound only the pending transaction JSON and skipped target locks, so a stable pending update could still roll back an unconfirmed live release unit. - When a target was recreated after quarantine rename, the move was not recorded in last-repair, so UndoLastRepair could restore an unrelated prior repair. Root cause: - Confirmation identity omitted canonical target identity; lock keys and rollback binding did not converge on real paths or live release-unit state; post-rename failure paths returned before persisting the already-moved node. Fix: - Embed an irreversible canonical target identity in per-file state IDs used by previewId, while verifying quarantined nodes against the original path identity. - Resolve existing symlink parents and case-fold on Windows/macOS when building mutation-lock keys and target identities. - Bind current release-unit file states into rollback preview state, take shared target locks during prepare/rollback, and re-check under those locks. - Persist the quarantine/move transaction when a concurrent recreate is detected so undo restores the confirmed node and retains the rewrite as redo. Verification: - go test ./internal/repair/ -count=1 - go test -race ./internal/repair/ ./internal/fileutil/ -count=1 - go test ./cmd/reasonix-guard/ -count=1 - go vet ./internal/repair/ * fix(repair): close symlink, app-bundle, and macOS handoff confirmation gaps Problem: - canonicalRepairPath EvalSymlinks'd the full path, so two leaf symlinks sharing a referent produced the same previewId, and renaming a confirmed symlink leaf changed its identity and made apply fail. - App-bundle rollback bound only directory kind/mode for the live .app and omitted the backup tree, so interior executable drift still accepted an old previewId and rolled back. - macOS self-update held mutation locks only while writing the pending transaction; the actual mv/ditto ran in a shell helper that ignored the shared flock, racing Guard rollback into mixed bundles. Root cause: - Leaf nodes are what Lstat/Rename mutate, but identity resolved through them; directory release units need tree digests; handoff and repair used different processes without a shared lock holder over the swap window. Fix: - Resolve only parent-directory symlinks and reattach the original leaf name for mutation locks and preview target identity. - Bind full tree digests for directory release-unit nodes and include backup paths in pending-update preview state. - Replace the macOS shell handoff with a detached self-subprocess that waits for the old PID, then holds LockRepairMutations around the directory swap. - Export LockRepairMutations/Timeout for desktop handoff and add regressions. Verification: - go test ./internal/repair/ -count=1 - go test -race ./internal/repair/ -count=1 - go test ./cmd/reasonix-guard/ -count=1 - cd desktop && go test -count=1 -run TestMacUpdate . - go vet ./internal/repair/ and desktop * fix(repair): secure updater claims and path case locks Problem: The detached macOS updater trusted filesystem paths from command-line arguments, and repair identities folded path case by operating system rather than by the target filesystem. Root cause: The handoff only checked that a pending-update file existed before swapping bundles. Path canonicalization assumed every macOS and Windows filesystem was case-insensitive. Fix: Persist optional handoff paths and owner identity in the pending transaction, claim them under strict pending and target locks, revalidate the staged bundle before swapping, and remove filesystem paths from child arguments. Detect case sensitivity from the actual macOS directory or Windows per-directory flag before folding lock and preview identities. Verification: - go test ./internal/repair/ -count=1 - go test -race ./internal/repair/ ./internal/fileutil/ -count=1 - go test ./cmd/reasonix-guard/ -count=1 - cd desktop && go test -count=1 -run TestMacUpdate . - cd desktop && go test -race -count=1 -run TestMacUpdate . - go vet ./internal/repair/ - cd desktop && go vet . - GOOS=windows GOARCH=amd64 go test -c ./internal/repair * fix(repair): scope case probe helper by platform Problem: The Linux golangci-lint job reported existingRepairPathParent as unused. Root cause: The helper lived in a common file while its only callers were excluded from Linux builds by Darwin and Windows build tags. Fix: Move the helper into a Darwin-or-Windows platform file so it is compiled exactly where the filesystem case probes use it. Verification: - golangci-lint run --timeout=5m - go test ./internal/repair/ -count=1 - cd desktop && go test -count=1 -run TestMacUpdate . - GOOS=windows GOARCH=amd64 go test -c ./internal/repair * fix(repair): preserve verified rollback recovery Problem: - File rollback could install backup bytes that changed after preflight validation. - A failed macOS backup restore still cleared pending recovery state and cleanup material. - NFC and NFD aliases on APFS produced different repair lock and preview identities. Root cause: The staged-copy hash was ignored, macOS rollback cleanup was unconditional, and canonical repair paths handled case aliases without filesystem-aware Unicode normalization. Fix: Verify the staged rollback bytes against the transaction hash before mutating live targets. Make macOS rollback compensating and retain pending, backup, and staging state when restoration fails. Normalize repair identities to NFD only on APFS and HFS while preserving distinct names on other filesystems. Verification: - go test ./internal/repair/ -count=1 - go test -race ./internal/repair/ ./internal/fileutil/ -count=1 - go test ./cmd/reasonix-guard/ -count=1 - cd desktop && go test ./... -count=1 - cd desktop && go test -race -count=1 -run TestMacUpdate . - go vet ./internal/repair/ - cd desktop && go vet . - golangci-lint run --timeout=5m - GOOS=windows GOARCH=amd64 go test -c ./internal/repair * Harden transactional updater handoffs Problem: Repair and self-update paths could continue without a pending-state lock, apply a prepared update after its transaction or release unit changed, or restore an unverified macOS bundle tree. Linux also persisted a CLI sibling that transaction validation rejected. Root cause: Pending transitions used a best-effort unbounded lock, file updaters locked paths without claiming the exact prepared transaction, and app-bundle handoff/rollback validation did not bind both the staged tree and original backup tree across the rename window. Fix: Use bounded fail-closed pending locks and a consistent pending-to-target lock order. Claim Linux and Windows replacements by version, creation identity, platform, and canonical release-unit paths. Bind macOS staging, original, installed, and backup trees; recover rejected handoffs only when the original bundle is still verified; protect backup paths and reject symlink escapes. Add focused concurrency, drift, rollback, compatibility, and cross-platform tests. Verification: - go test ./internal/repair/ ./internal/fileutil/ ./cmd/reasonix-guard/ -count=1 - go test -race ./internal/repair/ ./internal/fileutil/ ./cmd/reasonix-guard/ -count=1 - cd desktop && go test ./... -count=1 - cd desktop && go test -race ./... -count=1 - go vet ./... and cd desktop && go vet ./... - golangci-lint run --timeout=5m ./... - Windows amd64/arm64 test-binary compilation for repair, desktop, and update-helper Co-authored-by: SivanCola <[email protected]> * Complete fail-closed repair and updater transactions Problem: Repair, undo, and self-update entry points could act on state that changed while waiting for locks. File-update completion also needed a crash-safe installed-unit binding, and platform handoffs could execute mutable or incompletely claimed payloads. Root cause: Invocation identity was sampled too late or incompletely, mutable pending state doubled as installed state, cleanup paths did not consistently compare ownership, and Windows/macOS handoffs did not bind every source, backup, staging, and release-unit transition. Fix: Bind every mutating invocation before lock waits and recheck it under a pending-to-target lock order. Persist create-only installed-state sidecars, preserve recovery material on ambiguity, use atomic no-replace publication, validate complete app and file trees, isolate and hold the verified Windows installer through execution, and make rollback/undo crash-retryable. Verification: go test ./internal/repair ./internal/fileutil ./cmd/reasonix-guard -count=1 cd desktop && go test ./... -count=1 Windows amd64 and arm64 update-helper test-binary compilation gofmt -l internal/repair desktop git diff --check Co-authored-by: SivanCola <[email protected]> * Document fail-closed legacy update recovery Problem: Recovery documentation described additive update state as generally backward compatible and a Linux comment still described the obsolete mutable-pending design. Root cause: The text was not updated when installed release-unit state moved to a transaction-unique sidecar and legacy destructive actions began requiring explicit ownership proof. Fix: Document immutable pending state, crash-safe sidecar recovery, and the distinction between readable legacy transactions and authorization for handoff, healthy commit, or app-bundle rollback. Verification: git diff --cached --check Focused repair and updater tests, full race suites, vet, lint, and cross-platform compilation passed against the merged main-v2 baseline. Co-authored-by: SivanCola <[email protected]> * Close updater execution and compensation races Problem: - Generic failure-marker writes could let a stale invocation overwrite a newer transaction marker. - Legacy app rollback could move a symlink backup before rejecting it, and file publish could leave a tampered staged node live. - Windows self-update could execute a helper that changed after transaction prepare, overwrite a concurrently allocated cache path, or add a portable alias that was absent before. - macOS updater commands were resolved through PATH. Root cause: Mutation ownership was not rebound under the pending lock at every public entry point. Helper validation proved only PE architecture, staged-file verification followed symlinks, and helper cache publication used replace semantics. Fix: Rebind generic failure markers after lock acquisition, reject non-directory app backups before mutation, require regular-file staged payloads, compensate failed publication by retaining the rejected node and restoring the verified prior file, and preserve pending recovery state. Bind the Windows helper to the exact prepared release-unit SHA-256, create a unique cache node without replacement, freeze it through process creation, preserve the original portable-alias shape, and use absolute macOS system command paths. Verification: - go test ./... -count=1 - go test -race ./internal/repair ./internal/fileutil ./cmd/reasonix-guard -count=1 - cd desktop && go test ./... -count=1 - cd desktop && go test -race ./... -count=1 - go vet ./... and cd desktop && go vet ./... - golangci-lint 2.12.2 for root and desktop - cache guard: 8/8 pass - Windows amd64/arm64 repair, desktop, and update-helper test-binary compilation - Linux amd64 repair test-binary compilation Co-authored-by: SivanCola <[email protected]> * Tidy desktop updater dependencies Problem: The desktop CI go-mod-tidy gate failed because the updater no longer imports github.com/minio/selfupdate, but the module files still retained that dependency and its stale transitive checksums. Root cause: The transactional updater implementation replaced the selfupdate package without running the desktop module tidy gate afterward. Fix: Remove the unused selfupdate requirement and the transitive checksum entries that are no longer reachable from the desktop module graph. Verification: - cd desktop && go mod tidy (idempotent) - cd desktop && go vet ./... - focused workspace-rebuild timing tests: 10/10 pass - git diff --check Co-authored-by: SivanCola <[email protected]> * Harden prepared repair journals and Windows release-unit signing Problem: - Repair mutations could leave ambiguous undo state after a crash between rename and last-repair.json publication. - Concurrent writers could replace a pending journal or cleanup node after ownership checks, turning cleanup into deletion of unowned material. - Windows portable/installer updates did not bind signed payload identity through packaging, and Linux release-unit publication did not return exact installed-state receipts. Root cause: Undo bookkeeping treated last-repair.json as the only durable intent, cleanup used path-based remove after verification, and Windows packaging trusted unsigned payload layout without a create-only signed manifest contract. Fix: - Introduce create-only pending-repair.json write-ahead journals, promote or discard them under the repair lock, and fail closed on ambiguous recovery. - Displace cleanup targets to unique siblings, re-verify identity, and never unlink a pending journal that changed before cleanup. - Bind Windows signed payload manifests through packaging/signing and require exact installed-state receipts for Linux release-unit publication. Verification: - go test ./internal/repair -count=1 (pass) - focused security coverage for prepared intent, pending cleanup ownership, legacy healthy/rollback fail-closed, and macOS staging cleanup Co-authored-by: SivanCola <[email protected]> * fix(ci): use resolved version for payload manifest Problem: The Windows payload manifest step referenced a nonexistent ver step output, so actionlint rejected the release workflow. Root cause: The build job receives the release version from needs.resolve.outputs.version, but the step used steps.ver.outputs.version. Fix: Bind the signed payload manifest to the resolved release version output. Verification: actionlint v1.7.7 and scripts/release-workflows.test.sh pass. * fix(repair): bind prepared create ownership to published Windows modes Problem: - Windows CI rejected undo after a crash following snapshot create: the restored config file remained because CreatedStateID ownership failed. - Desktop packaging contract tests still expected steps.ver.outputs.version after the release workflow switched to needs.resolve.outputs.version. Root cause: - Prepared create intents predicted FileMode 0o600, but Windows Lstat reports regular files as 0444/0666. Undo then treated the published file as an unowned concurrent write and left it in place. - The packaging test was not updated with the resolved-version workflow fix. Fix: - Predict create-state IDs with platform-published file modes and rebind the pending journal to the exact on-disk node after AtomicCreateFile. - Align the Windows signing packaging contract test with the resolve job version output. Verification: - go test ./internal/repair -count=1 - go test ./internal/repair -run TestPreparedCreateStateIDMatchesAtomicCreate,TestApplyRepairPlanPersistsMissingSnapshotTargetBeforeCreate - cd desktop && go test -run TestWindowsReleaseSignsPayloadBeforeRepackaging . Co-authored-by: SivanCola <[email protected]> * fix(repair): reject prepared create ownership drift Problem: A file replaced between AtomicCreateFile and ownership rebinding could be claimed by the repair transaction and removed by undo. Root cause: The pending journal was rewritten to bind whatever state happened to be present after publication instead of proving it matched the precomputed owned state. Fix: Keep the write-ahead ownership binding immutable and fail closed when the published path differs; add a regression test that preserves a concurrent replacement through undo. Verification: focused crash/create tests, go test ./internal/repair, repair/fileutil race tests, Guard tests, go vet, and Windows repair test cross-compile pass. --------- Co-authored-by: SivanCola <[email protected]> Co-authored-by: PaulDing98 <[email protected]>