Files
ZacharyZhang-NY eb41b991bf feat(go): durable transaction engine and Kiro IDE 1.0 adapter
Split the monolithic transaction into focused modules: append-only
crash-recoverable journal under ~/.elydora/transactions with owner
tokens and per-transaction namespaces, staged atomic publication
(stage .pending, fsync, rename, fsync), platform-specific atomic
replacement (MoveFileEx, renameat2, renamex_np), private-directory
hardening per OS, lock-guarded recovery, and workspace cleanup that
removes only transaction-created empty directories.

Add the Kiro IDE 1.0 plugin: strict v1 hook schema with one enabled
PreToolUse guard and PostToolUse audit pair bound to the active
workspace, agent-to-workspace identity binding in runtime config, and
exact migration of the legacy elydora-audit.kiro.hook contract.

Gates: go test ./... and -race pass, go vet clean, govulncheck reports
zero reachable vulnerabilities, git diff --check clean.
2026-07-21 19:02:57 -04:00

237 lines
6.9 KiB
Go

package plugins
import (
"bytes"
"errors"
"fmt"
"path/filepath"
"runtime"
"strings"
)
func writeDurableChanges(
changes []*fileChange,
label string,
ops transactionFileOps,
preconditions ...filePrecondition,
) (result error) {
if err := transactionPlatformSupported(); err != nil {
return fmt.Errorf("%s: %w", label, err)
}
if ops.moveNoReplace == nil || ops.replaceWithBackup == nil {
return fmt.Errorf("%s is missing durable atomic file operations", label)
}
stateRoot, err := durableTransactionStateRoot()
if err != nil {
return fmt.Errorf("%s: prepare durable transaction state: %w", label, err)
}
lock, err := acquireDurableTransactionLock(stateRoot)
if err != nil {
return fmt.Errorf("%s: %w", label, err)
}
defer func() {
result = errors.Join(result, lock.close())
}()
if err := recoverPendingDurableTransactions(stateRoot, ops); err != nil {
return fmt.Errorf("%s: pending transaction recovery failed: %w", label, err)
}
filtered, err := filterDurableChanges(changes, label)
if err != nil {
return err
}
if err := assertFilePreconditions(preconditions, label); err != nil {
return fmt.Errorf("%s: %w", label, err)
}
if len(filtered) == 0 {
return nil
}
journal, err := createDurableJournal(stateRoot, label)
if err != nil {
return err
}
if err := initializeDurableLayout(journal, filtered); err != nil {
return preserveUnjournaledNamespace(journal, err)
}
if err := appendDurableJournal(journal, durablePhaseInitializing); err != nil {
return preserveUnjournaledNamespace(journal, err)
}
abort := func(cause error) error {
return abortDurableTransaction(journal, ops, cause)
}
if err := createDurableWorkspaces(journal); err != nil {
return abort(err)
}
if err := stageDurableEntries(journal, filtered); err != nil {
return abort(err)
}
if err := appendDurableJournal(journal, durablePhasePrepared); err != nil {
return abort(err)
}
if err := assertDurableOriginals(journal); err != nil {
return abort(err)
}
if err := assertFilePreconditions(preconditions, label); err != nil {
return abort(fmt.Errorf("%s: %w", label, err))
}
for index := range journal.Entries {
if err := assertFilePreconditions(preconditions, label); err != nil {
return abort(fmt.Errorf("%s: %w", label, err))
}
activeIndex := index
journal.ActiveEntry = &activeIndex
if err := appendDurableJournal(journal, durablePhaseCommitting); err != nil {
return abort(err)
}
if err := commitDurableEntry(&journal.Entries[index], ops); err != nil {
return abort(err)
}
journal.CompletedEntries = index + 1
journal.ActiveEntry = nil
if err := appendDurableJournal(journal, durablePhaseCommitting); err != nil {
return abort(err)
}
}
if err := assertFilePreconditions(preconditions, label); err != nil {
return abort(fmt.Errorf("%s: %w", label, err))
}
if err := verifyDurableJournalCommitted(journal); err != nil {
return abort(err)
}
if err := appendDurableJournal(journal, durablePhaseCommitted); err != nil {
return abort(err)
}
if err := finishCommittedDurableTransaction(journal); err != nil {
return fmt.Errorf(
"%s committed; durable cleanup failed: %w; recovery journal %s",
label,
err,
journal.JournalDir,
)
}
return nil
}
func filterDurableChanges(changes []*fileChange, label string) ([]fileChange, error) {
filtered := make([]fileChange, 0, len(changes))
targets := make(map[string]bool)
for _, change := range changes {
if change == nil || change.existed && !change.remove &&
bytes.Equal(change.original, change.next) &&
sameManagedFileMode(change.originalMode, change.mode) {
continue
}
target, err := filepath.Abs(change.filePath)
if err != nil {
return nil, fmt.Errorf(
"%s cannot resolve target %s: %w",
label,
change.filePath,
err,
)
}
target = filepath.Clean(target)
change.filePath = target
if runtime.GOOS == "windows" {
target = strings.ToLower(target)
}
if targets[target] {
return nil, fmt.Errorf("%s contains duplicate file target %s", label, change.filePath)
}
targets[target] = true
filtered = append(filtered, *change)
}
return filtered, nil
}
func preserveUnjournaledNamespace(journal *durableJournal, cause error) error {
return fmt.Errorf(
"%w; transaction namespace preserved at %s",
cause,
journal.JournalDir,
)
}
func assertDurableOriginals(journal *durableJournal) error {
for _, entry := range journal.Entries {
if _, err := requireDurableArtifact(entry.Path, entry.Label, entry.Original); err != nil {
return fmt.Errorf("%s changed during installation: %w", entry.Label, err)
}
if entry.NextPath != "" {
if _, err := requireDurableArtifact(
entry.NextPath,
"staged "+entry.Label,
entry.Next,
); err != nil {
return err
}
}
}
return nil
}
func commitDurableEntry(entry *durableEntry, ops transactionFileOps) error {
if _, err := requireDurableArtifact(entry.Path, entry.Label, entry.Original); err != nil {
return fmt.Errorf("%s changed at commit boundary: %w", entry.Label, err)
}
switch entry.Kind {
case durableCreate:
if err := ops.moveNoReplace(entry.NextPath, entry.Path); err != nil {
return fmt.Errorf("create %s without replacement: %w", entry.Label, err)
}
case durableUpdate:
if _, err := requireDurableArtifact(
entry.NextPath,
"staged "+entry.Label,
entry.Next,
); err != nil {
return err
}
if entry.OriginalPath != entry.NextPath {
if current, err := readDurableArtifact(entry.OriginalPath, "backup "+entry.Label); err != nil || current != nil {
return errors.Join(fmt.Errorf("backup path is occupied: %s", entry.OriginalPath), err)
}
}
if err := ops.replaceWithBackup(entry.Path, entry.NextPath, entry.OriginalPath); err != nil {
return fmt.Errorf("atomically replace %s: %w", entry.Label, err)
}
case durableDelete:
if err := ops.moveNoReplace(entry.Path, entry.OriginalPath); err != nil {
return fmt.Errorf("capture deleted %s: %w", entry.Label, err)
}
default:
return fmt.Errorf("unknown durable change kind %q", entry.Kind)
}
if err := verifyDurableEntryCommitted(entry); err != nil {
return fmt.Errorf("%s changed immediately after durable commit: %w", entry.Label, err)
}
return syncTransactionDirectory(filepath.Dir(entry.Path))
}
func verifyDurableEntryCommitted(entry *durableEntry) error {
wantTarget := entry.Next
if entry.Kind == durableDelete {
wantTarget = durableArtifact{}
}
if _, err := requireDurableArtifact(entry.Path, entry.Label, wantTarget); err != nil {
return err
}
switch entry.Kind {
case durableUpdate, durableDelete:
_, err := requireDurableArtifact(entry.OriginalPath, "original "+entry.Label, entry.Original)
return err
case durableCreate:
_, err := requireDurableArtifact(entry.NextPath, "consumed staged "+entry.Label, durableArtifact{})
return err
}
return nil
}
func verifyDurableJournalCommitted(journal *durableJournal) error {
for index := range journal.Entries {
if err := verifyDurableEntryCommitted(&journal.Entries[index]); err != nil {
return err
}
}
return nil
}