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.
This commit is contained in:
1 parent
dd72793cdb
commit
eb41b991bf
70 files changed
+8182
-358
No files matched your search
@@ -0,0 +1,258 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type kiroIdeDirectoryIdentity struct {
|
||||
path string
|
||||
label string
|
||||
info os.FileInfo
|
||||
}
|
||||
|
||||
func snapshotKiroIdeDirectory(path, label string) (kiroIdeDirectoryIdentity, error) {
|
||||
info, err := os.Lstat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return kiroIdeDirectoryIdentity{path: path, label: label}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return kiroIdeDirectoryIdentity{}, fmt.Errorf("inspect %s at %s: %w", label, path, err)
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
||||
return kiroIdeDirectoryIdentity{}, fmt.Errorf("%s is not a physical directory: %s", label, path)
|
||||
}
|
||||
return kiroIdeDirectoryIdentity{path: path, label: label, info: info}, nil
|
||||
}
|
||||
|
||||
func (identity kiroIdeDirectoryIdentity) assertExpected(operation string) error {
|
||||
current, err := os.Lstat(identity.path)
|
||||
if os.IsNotExist(err) && identity.info == nil {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"%s changed during %s: %s: %w",
|
||||
identity.label,
|
||||
operation,
|
||||
identity.path,
|
||||
err,
|
||||
)
|
||||
}
|
||||
if identity.info == nil || current.Mode()&os.ModeSymlink != 0 || !current.IsDir() ||
|
||||
!os.SameFile(identity.info, current) {
|
||||
return fmt.Errorf(
|
||||
"%s changed during %s: %s",
|
||||
identity.label,
|
||||
operation,
|
||||
identity.path,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func assertKiroIdeDirectoryStates(
|
||||
directories []kiroIdeDirectoryIdentity,
|
||||
operation string,
|
||||
) error {
|
||||
for _, directory := range directories {
|
||||
if err := directory.assertExpected(operation); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func establishKiroIdeDirectory(
|
||||
initial kiroIdeDirectoryIdentity,
|
||||
operation string,
|
||||
private bool,
|
||||
) (kiroIdeDirectoryIdentity, error) {
|
||||
if err := initial.assertExpected(operation); err != nil {
|
||||
return kiroIdeDirectoryIdentity{}, err
|
||||
}
|
||||
if initial.info == nil {
|
||||
if err := os.Mkdir(initial.path, 0700); err != nil {
|
||||
return kiroIdeDirectoryIdentity{}, fmt.Errorf(
|
||||
"%s changed during %s before Elydora could create it: %s: %w",
|
||||
initial.label,
|
||||
operation,
|
||||
initial.path,
|
||||
err,
|
||||
)
|
||||
}
|
||||
} else if private && runtime.GOOS != "windows" {
|
||||
if err := os.Chmod(initial.path, 0700); err != nil {
|
||||
return kiroIdeDirectoryIdentity{}, fmt.Errorf(
|
||||
"restrict %s at %s: %w",
|
||||
initial.label,
|
||||
initial.path,
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
current, err := snapshotKiroIdeDirectory(initial.path, initial.label)
|
||||
if err != nil {
|
||||
return kiroIdeDirectoryIdentity{}, err
|
||||
}
|
||||
if current.info == nil {
|
||||
return kiroIdeDirectoryIdentity{}, fmt.Errorf(
|
||||
"%s is missing after creation: %s",
|
||||
initial.label,
|
||||
initial.path,
|
||||
)
|
||||
}
|
||||
if initial.info != nil && !os.SameFile(initial.info, current.info) {
|
||||
return kiroIdeDirectoryIdentity{}, fmt.Errorf(
|
||||
"%s changed during %s: %s",
|
||||
initial.label,
|
||||
operation,
|
||||
initial.path,
|
||||
)
|
||||
}
|
||||
return current, nil
|
||||
}
|
||||
|
||||
func kiroIdePathInside(directory, candidate string) bool {
|
||||
relative, err := filepath.Rel(directory, candidate)
|
||||
if err != nil || filepath.IsAbs(relative) || relative == ".." {
|
||||
return false
|
||||
}
|
||||
if strings.HasPrefix(relative, ".."+string(os.PathSeparator)) {
|
||||
return false
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
return !strings.HasPrefix(strings.ToLower(relative), ".."+string(os.PathSeparator))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func guardedKiroIdeRename(
|
||||
rename renameFunc,
|
||||
directories []kiroIdeDirectoryIdentity,
|
||||
) renameFunc {
|
||||
if rename == nil {
|
||||
rename = atomicRenameNoReplace
|
||||
}
|
||||
return func(source, destination string) error {
|
||||
relevant := make([]kiroIdeDirectoryIdentity, 0, len(directories))
|
||||
for _, directory := range directories {
|
||||
if kiroIdePathInside(directory.path, source) ||
|
||||
kiroIdePathInside(directory.path, destination) {
|
||||
relevant = append(relevant, directory)
|
||||
}
|
||||
}
|
||||
for _, directory := range relevant {
|
||||
if err := directory.assertExpected("Kiro IDE transaction"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := rename(source, destination); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, directory := range relevant {
|
||||
if err := directory.assertExpected("Kiro IDE transaction"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func guardedKiroIdeTransactionOps(
|
||||
rename renameFunc,
|
||||
directories []kiroIdeDirectoryIdentity,
|
||||
) transactionFileOps {
|
||||
if rename != nil {
|
||||
return transactionFileOps{
|
||||
moveNoReplace: guardedKiroIdeRename(rename, directories),
|
||||
compensateNoReplace: guardedKiroIdeRename(atomicRenameNoReplace, directories),
|
||||
}
|
||||
}
|
||||
return transactionFileOps{
|
||||
moveNoReplace: guardedKiroIdeRename(atomicRenameNoReplace, directories),
|
||||
compensateNoReplace: guardedKiroIdeRename(atomicRenameNoReplace, directories),
|
||||
replaceWithBackup: guardedKiroIdeReplace(directories),
|
||||
production: true,
|
||||
}
|
||||
}
|
||||
|
||||
func guardedKiroIdeReplace(
|
||||
directories []kiroIdeDirectoryIdentity,
|
||||
) replaceWithBackupFunc {
|
||||
return func(target, replacement, backup string) error {
|
||||
relevant := make([]kiroIdeDirectoryIdentity, 0, len(directories))
|
||||
for _, directory := range directories {
|
||||
if kiroIdePathInside(directory.path, target) ||
|
||||
kiroIdePathInside(directory.path, replacement) ||
|
||||
kiroIdePathInside(directory.path, backup) {
|
||||
relevant = append(relevant, directory)
|
||||
}
|
||||
}
|
||||
if err := assertKiroIdeDirectoryStates(relevant, "Kiro IDE transaction"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := atomicReplaceWithBackup(target, replacement, backup); err != nil {
|
||||
return err
|
||||
}
|
||||
return assertKiroIdeDirectoryStates(relevant, "Kiro IDE transaction")
|
||||
}
|
||||
}
|
||||
|
||||
func kiroIdeDirectoryKey(path string) string {
|
||||
key := filepath.Clean(path)
|
||||
if runtime.GOOS == "windows" {
|
||||
return strings.ToLower(key)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func kiroIdeInitialDirectory(
|
||||
prepared *kiroIdePreparedTransaction,
|
||||
path string,
|
||||
) (kiroIdeDirectoryIdentity, error) {
|
||||
key := kiroIdeDirectoryKey(path)
|
||||
for _, directory := range prepared.initialDirectories {
|
||||
if kiroIdeDirectoryKey(directory.path) == key {
|
||||
return directory, nil
|
||||
}
|
||||
}
|
||||
return kiroIdeDirectoryIdentity{}, fmt.Errorf(
|
||||
"Kiro IDE transaction is missing a directory snapshot for %s",
|
||||
path,
|
||||
)
|
||||
}
|
||||
|
||||
func appendKiroIdeDirectory(
|
||||
directories []kiroIdeDirectoryIdentity,
|
||||
directory kiroIdeDirectoryIdentity,
|
||||
) []kiroIdeDirectoryIdentity {
|
||||
key := kiroIdeDirectoryKey(directory.path)
|
||||
for index, existing := range directories {
|
||||
if kiroIdeDirectoryKey(existing.path) == key {
|
||||
directories[index] = directory
|
||||
return directories
|
||||
}
|
||||
}
|
||||
return append(directories, directory)
|
||||
}
|
||||
|
||||
func kiroIdeDirectoryPreconditions(
|
||||
directories []kiroIdeDirectoryIdentity,
|
||||
) []filePrecondition {
|
||||
conditions := make([]filePrecondition, 0, len(directories))
|
||||
for _, directory := range directories {
|
||||
if directory.info == nil {
|
||||
continue
|
||||
}
|
||||
conditions = append(conditions, filePrecondition{
|
||||
filePath: directory.path,
|
||||
label: directory.label,
|
||||
directory: directory.info,
|
||||
})
|
||||
}
|
||||
return conditions
|
||||
}
|
||||
Reference in new issue
Block a user