f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
304 lines
9.4 KiB
Go
304 lines
9.4 KiB
Go
package sync
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"go.kenn.io/agentsview/internal/db"
|
|
"go.kenn.io/agentsview/internal/parser"
|
|
)
|
|
|
|
// verifiedSourceSignature is the complete local filesystem identity trusted
|
|
// by the gate. Sidecar fields are zero for providers without an auxiliary
|
|
// source and carry the raw Codex index identity when that provider opts in.
|
|
type verifiedSourceSignature struct {
|
|
size int64
|
|
mtime int64
|
|
inode int64
|
|
device int64
|
|
changeTime int64
|
|
sidecarSize int64
|
|
sidecarMtime int64
|
|
sidecarInode int64
|
|
sidecarDevice int64
|
|
sidecarChangeTime int64
|
|
}
|
|
|
|
// verifiedSourceRecord deliberately combines trust, invalidation, and pass
|
|
// bookkeeping in one map value. Unknown-path invalidations also live here so a
|
|
// concurrent stale promotion cannot recreate trust after the event.
|
|
type verifiedSourceRecord struct {
|
|
signature verifiedSourceSignature
|
|
invalidationGen uint64
|
|
lastSeenPass uint64
|
|
trusted bool
|
|
}
|
|
|
|
// verifiedSourceCapture binds a pre-verification signature to the path's
|
|
// invalidation coordinates. Promotion succeeds only if neither coordinate
|
|
// changed while content verification was in flight.
|
|
type verifiedSourceCapture struct {
|
|
path string
|
|
signature verifiedSourceSignature
|
|
epoch uint64
|
|
invalidation uint64
|
|
}
|
|
|
|
// captureVerifiedSource records that a full pass saw path, snapshots its
|
|
// invalidation coordinates before verification, and reports whether the same
|
|
// signature was previously promoted.
|
|
func (e *Engine) captureVerifiedSource(
|
|
path string,
|
|
signature verifiedSourceSignature,
|
|
) (verifiedSourceCapture, bool) {
|
|
if path == "" {
|
|
return verifiedSourceCapture{}, false
|
|
}
|
|
e.verifiedSourceMu.Lock()
|
|
defer e.verifiedSourceMu.Unlock()
|
|
if e.verifiedSources == nil {
|
|
e.verifiedSources = make(map[string]verifiedSourceRecord)
|
|
}
|
|
record := e.verifiedSources[path]
|
|
if e.verifiedSourceActivePass != 0 {
|
|
record.lastSeenPass = e.verifiedSourceActivePass
|
|
}
|
|
e.verifiedSources[path] = record
|
|
return verifiedSourceCapture{
|
|
path: path,
|
|
signature: signature,
|
|
epoch: e.verifiedSourceEpoch,
|
|
invalidation: record.invalidationGen,
|
|
}, record.trusted && record.signature == signature
|
|
}
|
|
|
|
// promoteVerifiedSource trusts a capture only when no path invalidation,
|
|
// global clear, or completed-pass pruning landed after capture.
|
|
func (e *Engine) promoteVerifiedSource(capture verifiedSourceCapture) {
|
|
if capture.path == "" {
|
|
return
|
|
}
|
|
e.verifiedSourceMu.Lock()
|
|
defer e.verifiedSourceMu.Unlock()
|
|
record, ok := e.verifiedSources[capture.path]
|
|
if !ok ||
|
|
capture.epoch != e.verifiedSourceEpoch ||
|
|
capture.invalidation != record.invalidationGen {
|
|
return
|
|
}
|
|
record.signature = capture.signature
|
|
record.trusted = true
|
|
e.verifiedSources[capture.path] = record
|
|
}
|
|
|
|
// invalidateVerifiedSource drops one path's trust and advances its generation.
|
|
// During an active full pass the invalidation record is marked seen so pruning
|
|
// cannot erase the generation before a stale in-flight promotion observes it.
|
|
func (e *Engine) invalidateVerifiedSource(path string) {
|
|
if path == "" {
|
|
return
|
|
}
|
|
e.verifiedSourceMu.Lock()
|
|
defer e.verifiedSourceMu.Unlock()
|
|
if e.verifiedSources == nil {
|
|
e.verifiedSources = make(map[string]verifiedSourceRecord)
|
|
}
|
|
record := e.verifiedSources[path]
|
|
record.trusted = false
|
|
record.invalidationGen++
|
|
if e.verifiedSourceActivePass != 0 {
|
|
record.lastSeenPass = e.verifiedSourceActivePass
|
|
}
|
|
e.verifiedSources[path] = record
|
|
}
|
|
|
|
// clearVerifiedSources invalidates every trusted source and vetoes all
|
|
// captures taken before the clear without retaining per-path tombstones.
|
|
func (e *Engine) clearVerifiedSources() {
|
|
e.verifiedSourceMu.Lock()
|
|
defer e.verifiedSourceMu.Unlock()
|
|
e.verifiedSources = nil
|
|
e.verifiedSourceEpoch++
|
|
}
|
|
|
|
// beginVerifiedSourcePass starts the seen-generation used by one full source
|
|
// discovery. Changed-path and scoped passes do not call it and never prune.
|
|
func (e *Engine) beginVerifiedSourcePass() uint64 {
|
|
e.verifiedSourceMu.Lock()
|
|
defer e.verifiedSourceMu.Unlock()
|
|
e.verifiedSourcePass++
|
|
if e.verifiedSourcePass == 0 {
|
|
e.verifiedSourcePass++
|
|
}
|
|
e.verifiedSourceActivePass = e.verifiedSourcePass
|
|
return e.verifiedSourceActivePass
|
|
}
|
|
|
|
// finishVerifiedSourcePass prunes records absent from a completed full
|
|
// discovery. Incomplete or superseded passes leave existing trust untouched.
|
|
func (e *Engine) finishVerifiedSourcePass(pass uint64, complete bool) {
|
|
if pass == 0 {
|
|
return
|
|
}
|
|
e.verifiedSourceMu.Lock()
|
|
defer e.verifiedSourceMu.Unlock()
|
|
if e.verifiedSourceActivePass != pass {
|
|
return
|
|
}
|
|
if complete {
|
|
for path, record := range e.verifiedSources {
|
|
if record.lastSeenPass != pass {
|
|
delete(e.verifiedSources, path)
|
|
}
|
|
}
|
|
}
|
|
e.verifiedSourceActivePass = 0
|
|
}
|
|
|
|
// verifiedProviderSourceState captures the local stat/ctime signature before
|
|
// any content verification. Providers must opt in explicitly; forced,
|
|
// path-rewritten, non-regular, and unreliable-change-time sources bypass the
|
|
// gate and retain their existing verification path.
|
|
func (e *Engine) verifiedProviderSourceState(
|
|
provider parser.Provider,
|
|
source parser.SourceRef,
|
|
file parser.DiscoveredFile,
|
|
) (verifiedSourceCapture, int64, bool, bool) {
|
|
if e.forceParse || file.ForceParse || e.pathRewriter != nil ||
|
|
provider.Capabilities().Source.VerifiedLocalStat !=
|
|
parser.CapabilitySupported {
|
|
return verifiedSourceCapture{}, 0, false, false
|
|
}
|
|
path := providerDiscoveredPath(source)
|
|
if path == "" || strings.HasPrefix(path, "s3://") {
|
|
return verifiedSourceCapture{}, 0, false, false
|
|
}
|
|
path = filepath.Clean(path)
|
|
info, err := os.Stat(path)
|
|
if err != nil || !info.Mode().IsRegular() {
|
|
return verifiedSourceCapture{}, 0, false, false
|
|
}
|
|
changeTime, ok := fileChangeTime(path, info)
|
|
if !ok {
|
|
return verifiedSourceCapture{}, 0, false, false
|
|
}
|
|
inode, device := getFileIdentity(info)
|
|
mtime := info.ModTime().UnixNano()
|
|
sidecar := verifiedSourceSignature{}
|
|
if provider.Definition().Type == parser.AgentCodex {
|
|
indexPath := parser.CodexSessionIndexPath(path)
|
|
if indexPath != "" {
|
|
indexInfo, indexErr := os.Stat(indexPath)
|
|
switch {
|
|
case indexErr == nil:
|
|
if !indexInfo.Mode().IsRegular() {
|
|
return verifiedSourceCapture{}, 0, false, false
|
|
}
|
|
indexChangeTime, reliable := fileChangeTime(
|
|
indexPath, indexInfo,
|
|
)
|
|
if !reliable {
|
|
return verifiedSourceCapture{}, 0, false, false
|
|
}
|
|
indexInode, indexDevice := getFileIdentity(indexInfo)
|
|
sidecar.sidecarSize = indexInfo.Size()
|
|
sidecar.sidecarMtime = indexInfo.ModTime().UnixNano()
|
|
sidecar.sidecarInode = indexInode
|
|
sidecar.sidecarDevice = indexDevice
|
|
sidecar.sidecarChangeTime = indexChangeTime
|
|
case !errors.Is(indexErr, os.ErrNotExist):
|
|
return verifiedSourceCapture{}, 0, false, false
|
|
}
|
|
}
|
|
}
|
|
capture, fresh := e.captureVerifiedSource(path, verifiedSourceSignature{
|
|
size: info.Size(),
|
|
mtime: mtime,
|
|
inode: inode,
|
|
device: device,
|
|
changeTime: changeTime,
|
|
sidecarSize: sidecar.sidecarSize,
|
|
sidecarMtime: sidecar.sidecarMtime,
|
|
sidecarInode: sidecar.sidecarInode,
|
|
sidecarDevice: sidecar.sidecarDevice,
|
|
sidecarChangeTime: sidecar.sidecarChangeTime,
|
|
})
|
|
if sidecar.sidecarMtime > mtime {
|
|
mtime = sidecar.sidecarMtime
|
|
}
|
|
return capture, mtime, fresh, true
|
|
}
|
|
|
|
// verifiedProviderSourceFreshInDB preserves the self-healing checks that run
|
|
// below the verified-source fast path. A matching filesystem signature cannot
|
|
// hide a missing active row, forced file-metadata reset, old parser data
|
|
// version, or project value that the current parser knows how to repair.
|
|
func (e *Engine) verifiedProviderSourceFreshInDB(
|
|
source parser.SourceRef,
|
|
wantSize, wantMtime int64,
|
|
) bool {
|
|
path := providerDiscoveredPath(source)
|
|
if path == "" {
|
|
return false
|
|
}
|
|
project, dataVersion, storedSize, storedMtime, ok :=
|
|
e.db.GetSourceRepairStateByPath(path)
|
|
if !ok || parser.NeedsProjectReparse(project) {
|
|
return false
|
|
}
|
|
return storedSize == wantSize &&
|
|
storedMtime == wantMtime &&
|
|
dataVersion >= db.CurrentDataVersion()
|
|
}
|
|
|
|
func (e *Engine) verifiedLocalStatSupported(agent parser.AgentType) bool {
|
|
factory, ok := e.providerFactories[agent]
|
|
return ok && factory != nil &&
|
|
factory.Capabilities().Source.VerifiedLocalStat ==
|
|
parser.CapabilitySupported
|
|
}
|
|
|
|
func (e *Engine) markVerifiedSourceSeen(path string) {
|
|
if path == "" {
|
|
return
|
|
}
|
|
path = filepath.Clean(path)
|
|
e.verifiedSourceMu.Lock()
|
|
defer e.verifiedSourceMu.Unlock()
|
|
if e.verifiedSourceActivePass == 0 {
|
|
return
|
|
}
|
|
record, ok := e.verifiedSources[path]
|
|
if !ok {
|
|
return
|
|
}
|
|
record.lastSeenPass = e.verifiedSourceActivePass
|
|
e.verifiedSources[path] = record
|
|
}
|
|
|
|
// markVerifiedDiscoveredSources preserves trusted records for gateable sources
|
|
// present in the full pre-filter discovery set, including files omitted later
|
|
// by a quick-sync cutoff.
|
|
func (e *Engine) markVerifiedDiscoveredSources(files []parser.DiscoveredFile) {
|
|
if e.pathRewriter != nil {
|
|
return
|
|
}
|
|
for _, file := range files {
|
|
if e.verifiedLocalStatSupported(file.Agent) {
|
|
e.markVerifiedSourceSeen(file.Path)
|
|
}
|
|
}
|
|
}
|
|
|
|
// invalidateVerifiedDiscoveredSource makes a concrete changed-path signal win
|
|
// over a matching stat signature. Only opted-in local providers allocate
|
|
// invalidation records.
|
|
func (e *Engine) invalidateVerifiedDiscoveredSource(file parser.DiscoveredFile) {
|
|
if e.pathRewriter != nil || !e.verifiedLocalStatSupported(file.Agent) {
|
|
return
|
|
}
|
|
e.invalidateVerifiedSource(filepath.Clean(file.Path))
|
|
}
|