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.
123 lines
3.7 KiB
Go
123 lines
3.7 KiB
Go
package plugins
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
func recoverInitializingDurableTransaction(journal *durableJournal) error {
|
|
if journal.Phase != durablePhaseInitializing {
|
|
return nil
|
|
}
|
|
verifiedWorkspaces := make(map[string]bool, len(journal.Workspaces))
|
|
for index := range journal.Workspaces {
|
|
workspace := &journal.Workspaces[index]
|
|
if workspace.DirectoryID == "" {
|
|
if err := cleanupUnpersistedDurableWorkspace(workspace.Path); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
if err := verifyPersistedDurableWorkspace(journal, workspace); err != nil {
|
|
return err
|
|
}
|
|
if err := cleanupInterruptedCapabilityProbe(workspace.Path); err != nil {
|
|
return fmt.Errorf("clean interrupted transaction capability probe: %w", err)
|
|
}
|
|
verifiedWorkspaces[durablePathKey(workspace.Path)] = true
|
|
}
|
|
for index := range journal.Entries {
|
|
entry := &journal.Entries[index]
|
|
if entry.NextPath == "" || entry.Next.Identity != "" {
|
|
continue
|
|
}
|
|
current, err := readDurableArtifact(entry.NextPath, "unpersisted staged "+entry.Label)
|
|
if err != nil || current == nil {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
if !verifiedWorkspaces[durablePathKey(entry.Workspace)] {
|
|
return fmt.Errorf(
|
|
"unpersisted staged transaction artifact preserved at %s",
|
|
entry.NextPath,
|
|
)
|
|
}
|
|
if err := removePhysicalOwnedFile(entry.NextPath, current.identity); err != nil {
|
|
return err
|
|
}
|
|
if err := syncTransactionDirectory(entry.Workspace); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cleanupUnpersistedDurableWorkspace(path string) error {
|
|
info, err := os.Lstat(path)
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return nil
|
|
}
|
|
if err != nil || info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
|
return errors.Join(fmt.Errorf("unpersisted transaction workspace changed: %s", path), err)
|
|
}
|
|
if err := verifyPrivateTransactionDirectory(path); err != nil {
|
|
return err
|
|
}
|
|
directoryID, err := transactionDirectoryIdentity(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
entries, err := os.ReadDir(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(entries) > 1 || len(entries) == 1 && entries[0].Name() != "owner" {
|
|
return fmt.Errorf("unexpected object in unpersisted transaction workspace: %s", path)
|
|
}
|
|
if len(entries) == 1 {
|
|
ownerPath := filepath.Join(path, "owner")
|
|
owner, err := readManagedFile(ownerPath, "unpersisted workspace owner", maxDurableJournalBytes)
|
|
if err != nil || owner == nil {
|
|
return errors.Join(fmt.Errorf("unpersisted workspace owner changed: %s", ownerPath), err)
|
|
}
|
|
if err := removePhysicalOwnedFile(ownerPath, owner.identity); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
latestID, err := transactionDirectoryIdentity(path)
|
|
if err != nil || latestID != directoryID {
|
|
return errors.Join(fmt.Errorf("unpersisted transaction workspace changed: %s", path), err)
|
|
}
|
|
if err := os.Remove(path); err != nil {
|
|
return fmt.Errorf("remove unpersisted transaction workspace %s: %w", path, err)
|
|
}
|
|
return syncTransactionDirectory(filepath.Dir(path))
|
|
}
|
|
|
|
func verifyPersistedDurableWorkspace(
|
|
journal *durableJournal,
|
|
workspace *durableWorkspace,
|
|
) error {
|
|
if err := verifyPrivateTransactionDirectory(workspace.Path); err != nil {
|
|
return err
|
|
}
|
|
directoryID, err := transactionDirectoryIdentity(workspace.Path)
|
|
if err != nil || directoryID != workspace.DirectoryID {
|
|
return errors.Join(fmt.Errorf("transaction workspace identity changed: %s", workspace.Path), err)
|
|
}
|
|
marker, err := readManagedFile(
|
|
workspace.MarkerPath,
|
|
"transaction workspace owner",
|
|
maxDurableJournalBytes,
|
|
)
|
|
wantMarker := journal.ID + ":" + workspace.OwnerToken + "\n"
|
|
if err != nil || marker == nil || string(marker.contents) != wantMarker {
|
|
return errors.Join(fmt.Errorf("transaction workspace owner changed: %s", workspace.Path), err)
|
|
}
|
|
return nil
|
|
}
|