From 652d4761a60d0edd6155ad8cdc131fdc64b3c7f9 Mon Sep 17 00:00:00 2001 From: iliya Date: Thu, 1 Oct 2026 17:07:36 +0000 Subject: [PATCH 1/3] feat(opencode): add qualified native clock sampling Add a bounded read-only clock helper and boot/domain-qualified samples for original native event admission. Preserve existing journal clock authority; Windows uses precise wall sampling without coarse fallback. Five-platform packaged qualification and notification admission integration remain pending. --- cmd/claude-notifications/main.go | 3 + cmd/claude-notifications/opencode_clock.go | 10 ++ .../opencode_clock_linux_test.go | 54 +++++++ internal/agentnotify/journal/clock_darwin.go | 16 +- internal/agentnotify/journal/clock_windows.go | 46 ++++-- internal/opencodeevent/clockport.go | 146 ++++++++++++++++++ internal/opencodeevent/clockport_darwin.go | 18 +++ internal/opencodeevent/clockport_linux.go | 47 ++++++ internal/opencodeevent/clockport_native.go | 7 + internal/opencodeevent/clockport_other.go | 7 + .../opencodeevent/clockport_other_test.go | 13 ++ internal/opencodeevent/clockport_test.go | 144 +++++++++++++++++ internal/opencodeevent/clockport_windows.go | 12 ++ internal/opencodeevent/clockprotocol.go | 26 ++++ internal/opencodeevent/clockprotocol_test.go | 65 ++++++++ internal/opencodeevent/clockwall_filetime.go | 21 +++ .../opencodeevent/clockwall_filetime_test.go | 42 +++++ internal/opencodeevent/clockwall_native.go | 9 ++ internal/opencodeevent/clockwall_windows.go | 27 ++++ .../opencodeevent/clockwall_windows_test.go | 47 ++++++ 20 files changed, 747 insertions(+), 13 deletions(-) create mode 100644 cmd/claude-notifications/opencode_clock.go create mode 100644 cmd/claude-notifications/opencode_clock_linux_test.go create mode 100644 internal/opencodeevent/clockport.go create mode 100644 internal/opencodeevent/clockport_darwin.go create mode 100644 internal/opencodeevent/clockport_linux.go create mode 100644 internal/opencodeevent/clockport_native.go create mode 100644 internal/opencodeevent/clockport_other.go create mode 100644 internal/opencodeevent/clockport_other_test.go create mode 100644 internal/opencodeevent/clockport_test.go create mode 100644 internal/opencodeevent/clockport_windows.go create mode 100644 internal/opencodeevent/clockprotocol.go create mode 100644 internal/opencodeevent/clockprotocol_test.go create mode 100644 internal/opencodeevent/clockwall_filetime.go create mode 100644 internal/opencodeevent/clockwall_filetime_test.go create mode 100644 internal/opencodeevent/clockwall_native.go create mode 100644 internal/opencodeevent/clockwall_windows.go create mode 100644 internal/opencodeevent/clockwall_windows_test.go diff --git a/cmd/claude-notifications/main.go b/cmd/claude-notifications/main.go index 68815ee74..4017c5e4a 100644 --- a/cmd/claude-notifications/main.go +++ b/cmd/claude-notifications/main.go @@ -35,6 +35,9 @@ var ( ) func main() { + if len(os.Args) > 1 && os.Args[1] == "opencode-clock" { + os.Exit(runOpenCodeClock(os.Args[2:], os.Stdout)) + } if len(os.Args) > 1 && os.Args[1] == "gemini-event" { os.Exit(runGeminiEvent(os.Args[2:], os.Stdin, os.Stdout)) } diff --git a/cmd/claude-notifications/opencode_clock.go b/cmd/claude-notifications/opencode_clock.go new file mode 100644 index 000000000..05eefc0c9 --- /dev/null +++ b/cmd/claude-notifications/opencode_clock.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/777genius/agent-notifications/internal/opencodeevent" + "io" +) + +func runOpenCodeClock(args []string, output io.Writer) int { + return opencodeevent.WriteClockSnapshot(args, output, opencodeevent.NewSystemSnapshotPort()) +} diff --git a/cmd/claude-notifications/opencode_clock_linux_test.go b/cmd/claude-notifications/opencode_clock_linux_test.go new file mode 100644 index 000000000..1d2408620 --- /dev/null +++ b/cmd/claude-notifications/opencode_clock_linux_test.go @@ -0,0 +1,54 @@ +//go:build linux + +package main + +import ( + "bytes" + "context" + "encoding/json" + "os" + "os/exec" + "path/filepath" + "syscall" + "testing" + "time" +) + +// Regression: dispatch reaching ordinary config/setup can block on a hostile +// config FIFO or create state; a pure isolated CLI snapshot must finish silently. +// The child test calls actual main; it does not inject a substitute clock. +func TestOpenCodeClockIsolatedDispatch(t *testing.T) { + if os.Getenv("AN_CLOCK_TEST_CHILD") == "1" { + os.Args = []string{"claude-notifications", "opencode-clock", "--protocol", "1"} + main() + return + } + root := t.TempDir() + config := filepath.Join(root, "config", "agent-notifications") + if err := os.MkdirAll(config, 0700); err != nil { + t.Fatal(err) + } + if err := syscall.Mkfifo(filepath.Join(config, "config.json"), 0600); err != nil { + t.Fatal(err) + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestOpenCodeClockIsolatedDispatch$") + cmd.Dir = root + cmd.Env = []string{"AN_CLOCK_TEST_CHILD=1", "HOME=" + root, "XDG_CONFIG_HOME=" + filepath.Join(root, "config"), "XDG_DATA_HOME=" + filepath.Join(root, "missing-data"), "CLAUDE_PLUGIN_ROOT=" + filepath.Join(root, "missing-plugin"), "AGENT_NOTIFICATIONS_CONFIG=" + filepath.Join(config, "config.json")} + var out, stderr bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + t.Fatal(err, stderr.String(), out.String()) + } + var fields map[string]json.RawMessage + if err := json.Unmarshal(out.Bytes(), &fields); err != nil || len(fields) != 8 || string(fields["protocol"]) != "1" || out.Len() > 1024 || stderr.Len() != 0 { + t.Fatal(out.String(), stderr.String(), err) + } + // Only the hostile config fixture may exist afterwards. + entries := 0 + if err := filepath.WalkDir(root, func(_ string, _ os.DirEntry, err error) error { entries++; return err }); err != nil || entries != 4 { + t.Fatal("unexpected private state", entries, err) + } +} diff --git a/internal/agentnotify/journal/clock_darwin.go b/internal/agentnotify/journal/clock_darwin.go index 7cbd1b8d1..0c03b19b6 100644 --- a/internal/agentnotify/journal/clock_darwin.go +++ b/internal/agentnotify/journal/clock_darwin.go @@ -13,13 +13,23 @@ type PlatformClock struct{} func DefaultClock() Clock { return PlatformClock{} } func (PlatformClock) Sample() Sample { + boot, sec, _, ok := DarwinBootSample() + if !ok { + return Sample{} + } + return Sample{Boot: boot, Seconds: uint64(sec), Available: true} +} + +// DarwinBootSample preserves the existing continuous CLOCK_MONOTONIC_RAW epoch +// and boot-session identity, exposing its integer sec/nsec without new FFI. +func DarwinBootSample() (boot string, sec, nsec int64, ok bool) { boot, e := unix.Sysctl("kern.bootsessionuuid") if e != nil || !validText(boot, 256, true) { - return Sample{} + return "", 0, 0, false } var ts unix.Timespec if e = unix.ClockGettime(unix.CLOCK_MONOTONIC_RAW, &ts); e != nil || ts.Sec < 0 { - return Sample{} + return "", 0, 0, false } - return Sample{Boot: boot, Seconds: uint64(ts.Sec), Available: true} + return boot, ts.Sec, ts.Nsec, true } diff --git a/internal/agentnotify/journal/clock_windows.go b/internal/agentnotify/journal/clock_windows.go index 7cefd424b..73eb6adcd 100644 --- a/internal/agentnotify/journal/clock_windows.go +++ b/internal/agentnotify/journal/clock_windows.go @@ -33,23 +33,48 @@ type bootEnvironmentInfo struct { } func WindowsBootSample() (boot string, sec, nsec int64, ok bool) { + boot, ok = windowsBootIdentity(false) + if !ok { + return "", 0, 0, false + } + sec, nsec, ok = windowsInterruptTime() + if !ok { + return "", 0, 0, false + } + return boot, sec, nsec, true +} + +func windowsBootIdentity(requireLength bool) (string, bool) { var info bootEnvironmentInfo var retLen uint32 if err := windows.NtQuerySystemInformation(windows.SystemBootEnvironmentInformation, unsafe.Pointer(&info), uint32(unsafe.Sizeof(info)), &retLen); err != nil { - return "", 0, 0, false + return "", false } - if info.BootIdentifier == [16]byte{} { - return "", 0, 0, false + // Preserve the existing journal/notifier path; the new precise export + // additionally requires a complete native structure before using its GUID. + if (requireLength && retLen != uint32(unsafe.Sizeof(info))) || info.BootIdentifier == [16]byte{} { + return "", false } - boot = formatWindowsGUID(info.BootIdentifier) + boot := formatWindowsGUID(info.BootIdentifier) if !validText(boot, 256, true) { + return "", false + } + return boot, true +} + +// WindowsPreciseBootSample uses the existing interrupt-time epoch; no coarse +// fallback and no accuracy claim derived from its 100ns representation. +func WindowsPreciseBootSample() (boot string, sec, nsec int64, ok bool) { + boot, ok = windowsBootIdentity(true) + if !ok || queryInterruptTimePrecise.Find() != nil { return "", 0, 0, false } - sec, nsec, ok = windowsInterruptTime() - if !ok { + var t uint64 + _, _, _ = queryInterruptTimePrecise.Call(uintptr(unsafe.Pointer(&t))) + if t == 0 { return "", 0, 0, false } - return boot, sec, nsec, true + return boot, int64(t / 10_000_000), int64(t%10_000_000) * 100, true } func formatWindowsGUID(g [16]byte) string { @@ -60,9 +85,10 @@ func formatWindowsGUID(g [16]byte) string { } var ( - kernel32 = windows.NewLazySystemDLL("kernel32.dll") - queryInterruptTime = kernel32.NewProc("QueryInterruptTime") - getTickCount64 = kernel32.NewProc("GetTickCount64") + kernel32 = windows.NewLazySystemDLL("kernel32.dll") + queryInterruptTimePrecise = kernel32.NewProc("QueryInterruptTimePrecise") + queryInterruptTime = kernel32.NewProc("QueryInterruptTime") + getTickCount64 = kernel32.NewProc("GetTickCount64") ) func windowsInterruptTime() (sec, nsec int64, ok bool) { diff --git a/internal/opencodeevent/clockport.go b/internal/opencodeevent/clockport.go new file mode 100644 index 000000000..e4749a449 --- /dev/null +++ b/internal/opencodeevent/clockport.go @@ -0,0 +1,146 @@ +package opencodeevent + +import ( + "encoding/json" + "errors" + "math" + "strconv" + "strings" + "time" +) + +const MaxClockResponseBytes = 1024 +const maxSnapshotWidthNs int64 = 100_000_000 + +var ErrClockUnavailable = errors.New("clock_unavailable") + +// CounterSample names the OS coordinate; neither caller-supplied quality nor +// wall time can authorize a counter kind. +type CounterSample struct { + Boot, Domain, Kind string + Sec, Nsec int64 +} + +type CounterPort interface{ SampleCounter() (CounterSample, bool) } +type WallPort interface{ SampleWall() time.Time } +type SnapshotPort interface{ SampleSnapshot() (ClockSnapshot, error) } + +// ClockPort is shared by the private helper and future admission. Wall is an +// anchor only: this port does not establish original event age or continuity. +type ClockPort struct { + Counter CounterPort + Wall WallPort +} + +type ClockSnapshot struct { + Boot, ClockDomain, ClockKind string + MonoLoNs, MonoHiNs, WallUnixNs, UncertaintyNs int64 +} + +// qualityAllowance is a fixed local read/quantization budget, not a claim of +// absolute UTC accuracy or clock rate. Native continuity/rate qualification is +// still required. Resolution checks can refuse this budget, never enlarge it. +// The Windows candidate uses the same conditional local profile; 100ns units +// do not prove accuracy. All profiles still need packaged native qualification. +func qualityAllowance(kind string) (int64, bool) { + switch kind { + case "linux-boottime", "darwin-monotonic-raw", "windows-interrupt-precise": + return 1_000_000, true + default: + return 0, false + } +} + +func (p ClockPort) SampleSnapshot() (ClockSnapshot, error) { + if p.Counter == nil || p.Wall == nil { + return ClockSnapshot{}, ErrClockUnavailable + } + a, okA := p.Counter.SampleCounter() + wall := p.Wall.SampleWall() + b, okB := p.Counter.SampleCounter() + lo, okLo := clockNanoseconds(a.Sec, a.Nsec) + hi, okHi := clockNanoseconds(b.Sec, b.Nsec) + w, okWall := clockNanoseconds(wall.Unix(), int64(wall.Nanosecond())) + q, qualified := qualityAllowance(a.Kind) + if !okA || !okB || !okLo || !okHi || !okWall || !qualified || + !validClockUUID(a.Boot) || !validClockDomain(a.Kind, a.Domain) || + a.Boot != b.Boot || a.Domain != b.Domain || a.Kind != b.Kind || + hi < lo || hi-lo > maxSnapshotWidthNs { + return ClockSnapshot{}, ErrClockUnavailable + } + // The whole bracket plus three read allowances (A, wall, B). Admission + // derives q independently, rather than trusting a received uncertainty. + return ClockSnapshot{a.Boot, a.Domain, a.Kind, lo, hi, w, hi - lo + 3*q}, nil +} + +func clockNanoseconds(sec, nsec int64) (int64, bool) { + if sec < 0 || nsec < 0 || nsec >= 1_000_000_000 || sec > (math.MaxInt64-nsec)/1_000_000_000 { + return 0, false + } + return sec*1_000_000_000 + nsec, true +} + +func validClockUUID(s string) bool { + if len(s) != 36 || s == "00000000-0000-0000-0000-000000000000" { + return false + } + for i, c := range s { + if i == 8 || i == 13 || i == 18 || i == 23 { + if c != '-' { + return false + } + } else if !(c >= '0' && c <= '9' || c >= 'a' && c <= 'f') { + return false + } + } + return true +} + +func validClockDomain(kind, domain string) bool { + switch kind { + case "windows-interrupt-precise": + return domain == "windows-kernel" + case "darwin-monotonic-raw": + return domain == "darwin-kernel" + case "linux-boottime": + parts := strings.Split(domain, ":") + if len(parts) != 3 || parts[0] != "linux-time" { + return false + } + for _, s := range parts[1:] { + n, err := strconv.ParseUint(s, 10, 64) + if err != nil || n == 0 || strconv.FormatUint(n, 10) != s { + return false + } + } + return true + default: + return false + } +} + +// JSON emits a closed protocol-1 object. It revalidates exported snapshots so +// later callers cannot accidentally serialize malformed or oversized authority. +func (s ClockSnapshot) JSON() ([]byte, error) { + q, ok := qualityAllowance(s.ClockKind) + if !ok || !validClockUUID(s.Boot) || !validClockDomain(s.ClockKind, s.ClockDomain) || + s.MonoLoNs < 0 || s.MonoHiNs < s.MonoLoNs || s.MonoHiNs-s.MonoLoNs > maxSnapshotWidthNs || + s.WallUnixNs < 0 || s.UncertaintyNs != s.MonoHiNs-s.MonoLoNs+3*q { + return nil, ErrClockUnavailable + } + out, err := json.Marshal(struct { + Protocol int `json:"protocol"` + Boot string `json:"boot"` + Domain string `json:"clockDomain"` + Kind string `json:"clockKind"` + Lo string `json:"monoLoNs"` + Hi string `json:"monoHiNs"` + Wall string `json:"wallUnixNs"` + Uncertainty string `json:"uncertaintyNs"` + }{1, s.Boot, s.ClockDomain, s.ClockKind, strconv.FormatInt(s.MonoLoNs, 10), + strconv.FormatInt(s.MonoHiNs, 10), strconv.FormatInt(s.WallUnixNs, 10), strconv.FormatInt(s.UncertaintyNs, 10)}) + if err != nil || len(out)+1 > MaxClockResponseBytes { + return nil, ErrClockUnavailable + } + return append(out, '\n'), nil +} diff --git a/internal/opencodeevent/clockport_darwin.go b/internal/opencodeevent/clockport_darwin.go new file mode 100644 index 000000000..8109f0bb8 --- /dev/null +++ b/internal/opencodeevent/clockport_darwin.go @@ -0,0 +1,18 @@ +//go:build darwin + +package opencodeevent + +import ( + "strings" + + "github.com/777genius/agent-notifications/internal/agentnotify/journal" +) + +type systemCounter struct{} + +func (systemCounter) SampleCounter() (CounterSample, bool) { + boot, sec, nsec, ok := journal.DarwinBootSample() + // Pinned x/sys exposes no Darwin ClockGetres. Keep the existing integer + // clock binding; the local budget still requires native qualification. + return CounterSample{strings.ToLower(boot), "darwin-kernel", "darwin-monotonic-raw", sec, nsec}, ok +} diff --git a/internal/opencodeevent/clockport_linux.go b/internal/opencodeevent/clockport_linux.go new file mode 100644 index 000000000..1f6cfe0d9 --- /dev/null +++ b/internal/opencodeevent/clockport_linux.go @@ -0,0 +1,47 @@ +//go:build linux + +package opencodeevent + +import ( + "runtime" + "strconv" + + "github.com/777genius/agent-notifications/internal/agentnotify/journal" + "golang.org/x/sys/unix" +) + +type systemCounter struct{} + +func (systemCounter) SampleCounter() (CounterSample, bool) { + // Namespace membership is per thread; do not migrate between handle/tick. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + domain, ok := linuxTimeDomain() + boot, sec, nsec, sampled := journal.LinuxBootSample(journal.TrustedBootIDPath) + after, stable := linuxTimeDomain() + var res unix.Timespec + q, _ := qualityAllowance("linux-boottime") + err := unix.ClockGetres(unix.CLOCK_BOOTTIME, &res) + r, valid := clockNanoseconds(res.Sec, res.Nsec) + wallErr := unix.ClockGetres(unix.CLOCK_REALTIME, &res) + wallRes, wallValid := clockNanoseconds(res.Sec, res.Nsec) + return CounterSample{boot, domain, "linux-boottime", sec, nsec}, + ok && sampled && stable && domain == after && err == nil && valid && r > 0 && r <= q && wallErr == nil && wallValid && wallRes > 0 && wallRes <= q +} + +func linuxTimeDomain() (string, bool) { + // Follow only the fixed kernel namespace handle, then verify nsfs/type. + fd, err := unix.Open("/proc/thread-self/ns/time", unix.O_RDONLY|unix.O_CLOEXEC, 0) + if err != nil { + return "", false + } + defer func() { _ = unix.Close(fd) }() + var st unix.Stat_t + var fs unix.Statfs_t + nstype, err := unix.IoctlRetInt(fd, unix.NS_GET_NSTYPE) + if err != nil || nstype != unix.CLONE_NEWTIME || unix.Fstatfs(fd, &fs) != nil || + fs.Type != unix.NSFS_MAGIC || unix.Fstat(fd, &st) != nil || st.Dev == 0 || st.Ino == 0 { + return "", false + } + return "linux-time:" + strconv.FormatUint(uint64(st.Dev), 10) + ":" + strconv.FormatUint(st.Ino, 10), true +} diff --git a/internal/opencodeevent/clockport_native.go b/internal/opencodeevent/clockport_native.go new file mode 100644 index 000000000..19cb9b379 --- /dev/null +++ b/internal/opencodeevent/clockport_native.go @@ -0,0 +1,7 @@ +package opencodeevent + +// NewSystemSnapshotPort has no config, lease, store, provider or notification +// dependencies. Both helper and admission must sample this same OS coordinate. +func NewSystemSnapshotPort() SnapshotPort { + return ClockPort{Counter: systemCounter{}, Wall: systemWall{}} +} diff --git a/internal/opencodeevent/clockport_other.go b/internal/opencodeevent/clockport_other.go new file mode 100644 index 000000000..6b4a4f12f --- /dev/null +++ b/internal/opencodeevent/clockport_other.go @@ -0,0 +1,7 @@ +//go:build !linux && !darwin && !windows + +package opencodeevent + +type systemCounter struct{} + +func (systemCounter) SampleCounter() (CounterSample, bool) { return CounterSample{}, false } diff --git a/internal/opencodeevent/clockport_other_test.go b/internal/opencodeevent/clockport_other_test.go new file mode 100644 index 000000000..141a8bc05 --- /dev/null +++ b/internal/opencodeevent/clockport_other_test.go @@ -0,0 +1,13 @@ +//go:build !linux && !darwin && !windows + +package opencodeevent + +import "testing" + +// Regression: adding a process-relative or wall-clock fallback on an unknown +// platform manufactures a shared boot coordinate instead of refusing it. +func TestClockSnapshotUnsupportedPlatform(t *testing.T) { + if s, err := NewSystemSnapshotPort().SampleSnapshot(); err != ErrClockUnavailable || s != (ClockSnapshot{}) { + t.Fatal(s, err) + } +} diff --git a/internal/opencodeevent/clockport_test.go b/internal/opencodeevent/clockport_test.go new file mode 100644 index 000000000..08c9a4bf2 --- /dev/null +++ b/internal/opencodeevent/clockport_test.go @@ -0,0 +1,144 @@ +package opencodeevent + +import ( + "bytes" + "encoding/json" + "math" + "strconv" + "testing" + "time" + + "github.com/777genius/agent-notifications/internal/strictjson" +) + +type sequenceCounter struct { + samples []CounterSample + calls int + unavailable bool +} + +func (p *sequenceCounter) SampleCounter() (CounterSample, bool) { + s := p.samples[p.calls] + p.calls++ + return s, !p.unavailable +} + +type wallSample func() time.Time + +func (f wallSample) SampleWall() time.Time { return f() } +func baseCounter() CounterSample { + return CounterSample{"12345678-1234-1234-1234-123456789abc", "linux-time:4:4026531834", "linux-boottime", 2, 0} +} + +// Regression: sampling wall before A/after B or serializing numbers as JSON +// Number loses the bracket and integer authority at the public command boundary. +func TestClockSnapshotBracketAndClosedJSON(t *testing.T) { + a, b := baseCounter(), baseCounter() + b.Nsec = 100_000_000 + c := &sequenceCounter{samples: []CounterSample{a, b}} + p := ClockPort{c, wallSample(func() time.Time { + if c.calls != 1 { + t.Fatal("wall not between counter samples") + } + return time.Unix(3, 7) + })} + s, err := p.SampleSnapshot() + if err != nil || s.MonoLoNs != 2_000_000_000 || s.MonoHiNs != 2_100_000_000 || + s.WallUnixNs != 3_000_000_007 || s.UncertaintyNs != 103_000_000 { + t.Fatal(s, err) + } + c.calls = 0 + var out bytes.Buffer + if code := WriteClockSnapshot([]string{"--protocol", "1"}, &out, p); code != 0 { + t.Fatal(code, out.String()) + } + raw := out.Bytes() + if err := strictjson.Validate(raw, strictjson.Budget{Bytes: 1024, Depth: 2, Entries: 8}); err != nil { + t.Fatal("duplicate or malformed response", err) + } + if err != nil || len(raw) > 1024 { + t.Fatal(string(raw), err) + } + var fields map[string]json.RawMessage + if err = json.Unmarshal(raw, &fields); err != nil || len(fields) != 8 || string(fields["protocol"]) != "1" { + t.Fatal(string(raw), err) + } + for _, k := range []string{"boot", "clockDomain", "clockKind", "monoLoNs", "monoHiNs", "wallUnixNs", "uncertaintyNs"} { + var value string + if json.Unmarshal(fields[k], &value) != nil || value == "" { + t.Fatal(k, string(raw)) + } + if k == "monoLoNs" || k == "monoHiNs" || k == "wallUnixNs" || k == "uncertaintyNs" { + if _, err := strconv.ParseInt(value, 10, 64); err != nil { + t.Fatal(k, err) + } + } + } + s.UncertaintyNs = 0 + if _, err = s.JSON(); err != ErrClockUnavailable { + t.Fatal("caller quality accepted") + } + // Exported snapshots must not bypass revalidation at serialization. + s.UncertaintyNs = 103_000_000 + s.ClockDomain = string(make([]byte, 1025)) + if _, err = s.JSON(); err != ErrClockUnavailable { + t.Fatal("oversized domain accepted") + } +} + +// Regression: treating identities as generic text, wrapping nanoseconds, or +// accepting a changed/slow clock can emit apparently fresh shared authority. +func TestClockSnapshotRefusesInvalidAuthority(t *testing.T) { + cases := []struct { + name string + mutate func(*CounterSample, *CounterSample) + wall time.Time + }{ + {"zero UUID", func(a, b *CounterSample) { a.Boot = "00000000-0000-0000-0000-000000000000"; b.Boot = a.Boot }, time.Unix(3, 0)}, + {"malformed UUID", func(a, b *CounterSample) { a.Boot = "private/path"; b.Boot = a.Boot }, time.Unix(3, 0)}, + {"boot change", func(a, b *CounterSample) { b.Boot = "22345678-1234-1234-1234-123456789abc" }, time.Unix(3, 0)}, + {"domain change", func(a, b *CounterSample) { b.Domain = "linux-time:4:42" }, time.Unix(3, 0)}, + {"missing domain", func(a, b *CounterSample) { a.Domain = "linux-time:4:0"; b.Domain = a.Domain }, time.Unix(3, 0)}, + {"kind change", func(a, b *CounterSample) { b.Kind = "darwin-monotonic-raw" }, time.Unix(3, 0)}, + {"coarse", func(a, b *CounterSample) { a.Kind = "windows-interrupt-coarse"; b.Kind = a.Kind }, time.Unix(3, 0)}, + {"wrong Windows domain", func(a, b *CounterSample) { a.Kind = "windows-interrupt-precise"; b.Kind = a.Kind }, time.Unix(3, 0)}, + {"regression", func(a, b *CounterSample) { b.Nsec = -1 }, time.Unix(3, 0)}, + {"counter regression", func(a, b *CounterSample) { b.Sec = 1 }, time.Unix(3, 0)}, + {"width over bound", func(a, b *CounterSample) { b.Nsec = 100_000_001 }, time.Unix(3, 0)}, + {"invalid nsec", func(a, b *CounterSample) { a.Nsec = 1_000_000_000 }, time.Unix(3, 0)}, + {"overflow", func(a, b *CounterSample) { a.Sec = math.MaxInt64 / 1_000_000_000; a.Nsec = 854_775_808 }, time.Unix(3, 0)}, + {"negative sec", func(a, b *CounterSample) { a.Sec = -1 }, time.Unix(3, 0)}, + {"wall overflow", func(a, b *CounterSample) {}, time.Unix(math.MaxInt64/1_000_000_000, 854_775_808)}, + {"negative wall", func(a, b *CounterSample) {}, time.Unix(-1, 0)}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + a, b := baseCounter(), baseCounter() + tc.mutate(&a, &b) + p := ClockPort{&sequenceCounter{samples: []CounterSample{a, b}}, wallSample(func() time.Time { return tc.wall })} + if s, err := p.SampleSnapshot(); err != ErrClockUnavailable || s != (ClockSnapshot{}) { + t.Fatal(s, err) + } + }) + } + // The last representable nanosecond must survive, not be refused by an + // off-by-one overflow check (the very next nanosecond above is refused). + a := baseCounter() + a.Sec = math.MaxInt64 / 1_000_000_000 + a.Nsec = 854_775_807 + p := ClockPort{&sequenceCounter{samples: []CounterSample{a, a}}, wallSample(func() time.Time { return time.Unix(a.Sec, a.Nsec) })} + if s, err := p.SampleSnapshot(); err != nil || s.MonoHiNs != math.MaxInt64 || s.WallUnixNs != math.MaxInt64 { + t.Fatal(s, err) + } +} + +// Regression: an absent OS port or failed native read is treated as tick zero. +func TestClockSnapshotUnavailable(t *testing.T) { + a := baseCounter() + for _, p := range []ClockPort{{}, {Counter: &sequenceCounter{}}, + {Counter: &sequenceCounter{samples: []CounterSample{a, a}, unavailable: true}, Wall: wallSample(func() time.Time { return time.Unix(3, 0) })}} { + if s, err := p.SampleSnapshot(); err != ErrClockUnavailable || s != (ClockSnapshot{}) { + t.Fatal(s, err) + } + } +} diff --git a/internal/opencodeevent/clockport_windows.go b/internal/opencodeevent/clockport_windows.go new file mode 100644 index 000000000..a72c2fdb8 --- /dev/null +++ b/internal/opencodeevent/clockport_windows.go @@ -0,0 +1,12 @@ +//go:build windows + +package opencodeevent + +import "github.com/777genius/agent-notifications/internal/agentnotify/journal" + +type systemCounter struct{} + +func (systemCounter) SampleCounter() (CounterSample, bool) { + boot, sec, nsec, ok := journal.WindowsPreciseBootSample() + return CounterSample{boot, "windows-kernel", "windows-interrupt-precise", sec, nsec}, ok +} diff --git a/internal/opencodeevent/clockprotocol.go b/internal/opencodeevent/clockprotocol.go new file mode 100644 index 000000000..dbb6ac272 --- /dev/null +++ b/internal/opencodeevent/clockprotocol.go @@ -0,0 +1,26 @@ +package opencodeevent + +import "io" + +// WriteClockSnapshot serves only the exact private command protocol. +func WriteClockSnapshot(args []string, output io.Writer, port SnapshotPort) int { + // Strict private protocol: no flag parser, help, paths or raw errors. + const unavailable = "{\"protocol\":1,\"error\":\"clock_unavailable\"}\n" + if len(args) != 2 || args[0] != "--protocol" || args[1] != "1" || port == nil { + _, _ = io.WriteString(output, unavailable) + return 1 + } + s, err := port.SampleSnapshot() + if err == nil { + var raw []byte + raw, err = s.JSON() + if err == nil { + if n, writeErr := output.Write(raw); writeErr == nil && n == len(raw) { + return 0 + } + return 1 + } + } + _, _ = io.WriteString(output, unavailable) + return 1 +} diff --git a/internal/opencodeevent/clockprotocol_test.go b/internal/opencodeevent/clockprotocol_test.go new file mode 100644 index 000000000..0c44ab894 --- /dev/null +++ b/internal/opencodeevent/clockprotocol_test.go @@ -0,0 +1,65 @@ +package opencodeevent + +import ( + "bytes" + "encoding/json" + "testing" + "time" +) + +// Regression: permissive parsing accepts version skew, extra/duplicate flags, +// or embeds private arguments in a diagnostic instead of the fixed protocol. +func TestClockSnapshotExactArguments(t *testing.T) { + a := baseCounter() + c := &sequenceCounter{samples: []CounterSample{a, a}} + port := ClockPort{c, wallSample(func() time.Time { return time.Unix(3, 0) })} + for _, args := range [][]string{nil, {"--protocol"}, {"--protocol", "2"}, {"--protocol=1"}, + {"--protocol", "1", "private/path"}, {"--protocol", "1", "--protocol", "1"}, {"1", "--protocol"}} { + var out bytes.Buffer + if code := WriteClockSnapshot(args, &out, port); code != 1 || out.String() != "{\"protocol\":1,\"error\":\"clock_unavailable\"}\n" { + t.Fatal(args, code, out.String()) + } + } + if c.calls != 0 { + t.Fatal("invalid command read the OS clock") + } +} + +// Regression: opening only one eligibility allowlist leaves precise Windows +// unusable; opening it broadly grants unknown/coarse kinds or foreign domains. +func TestClockSnapshotWindowsProfileProtocol(t *testing.T) { + for _, tc := range []struct{ kind, domain string }{ + {"windows-interrupt-precise", "windows-kernel"}, + {"windows-interrupt-precise", "darwin-kernel"}, + {"windows-interrupt-precise", "windows-kernel:1"}, + {"windows-interrupt-coarse", "windows-kernel"}, + {"unknown", "windows-kernel"}, + } { + t.Run(tc.kind+"/"+tc.domain, func(t *testing.T) { + a := baseCounter() + a.Kind, a.Domain = tc.kind, tc.domain + p := ClockPort{&sequenceCounter{samples: []CounterSample{a, a}}, wallSample(func() time.Time { return time.Unix(3, 7) })} + var out bytes.Buffer + code := WriteClockSnapshot([]string{"--protocol", "1"}, &out, p) + if tc.kind == "windows-interrupt-precise" && tc.domain == "windows-kernel" { + var frame map[string]json.RawMessage + if err := json.Unmarshal(out.Bytes(), &frame); err != nil || code != 0 || len(frame) != 8 || + string(frame["clockKind"]) != `"windows-interrupt-precise"` || string(frame["clockDomain"]) != `"windows-kernel"` || + string(frame["uncertaintyNs"]) != `"3000000"` || out.Len() > 1024 { + t.Fatal(code, out.String(), err) + } + } else if code != 1 || out.String() != "{\"protocol\":1,\"error\":\"clock_unavailable\"}\n" { + t.Fatal(code, out.String()) + } + }) + } +} + +// Regression: leaking a sample error or accepting unavailable tick zero changes +// the stable failure classification on a syntactically valid command. +func TestClockSnapshotUnavailableProtocol(t *testing.T) { + var out bytes.Buffer + if code := WriteClockSnapshot([]string{"--protocol", "1"}, &out, ClockPort{}); code != 1 || out.String() != "{\"protocol\":1,\"error\":\"clock_unavailable\"}\n" { + t.Fatal(code, out.String()) + } +} diff --git a/internal/opencodeevent/clockwall_filetime.go b/internal/opencodeevent/clockwall_filetime.go new file mode 100644 index 000000000..cc33f59e1 --- /dev/null +++ b/internal/opencodeevent/clockwall_filetime.go @@ -0,0 +1,21 @@ +package opencodeevent + +import ( + "math" + "time" +) + +// FILETIME is unsigned 100ns ticks since 1601, not Unix nanoseconds. Return +// zero time on invalid/overflow; the common port rejects its negative Unix sec. +func filetimeWall(low, high uint32) time.Time { + const unixEpochTicks uint64 = 116_444_736_000_000_000 + ticks := uint64(high)<<32 | uint64(low) + if ticks < unixEpochTicks { + return time.Time{} + } + ticks -= unixEpochTicks + if ticks > uint64(math.MaxInt64)/100 { + return time.Time{} + } + return time.Unix(0, int64(ticks)*100) +} diff --git a/internal/opencodeevent/clockwall_filetime_test.go b/internal/opencodeevent/clockwall_filetime_test.go new file mode 100644 index 000000000..9d9ffc548 --- /dev/null +++ b/internal/opencodeevent/clockwall_filetime_test.go @@ -0,0 +1,42 @@ +package opencodeevent + +import ( + "math" + "testing" + "time" +) + +// Regression: signed FILETIME, float conversion, epoch/unit mistakes or an +// unchecked multiply yields a plausible but wrong wall anchor or wraps it. +func TestClockSnapshotFILETIMEConversion(t *testing.T) { + for _, tc := range []struct { + name string + file uint64 + ns int64 + ok bool + }{ + {"unwritten", 0, 0, false}, + {"before Unix epoch", 116444735999999999, 0, false}, + {"Unix epoch", 116444736000000000, 0, true}, + {"one tick", 116444736000000001, 100, true}, + {"2023-11-14T22:13:20.1234567Z", 133444736001234567, 1700000000123456700, true}, + {"last int64 100ns tick", 208678456368547758, 9223372036854775800, true}, + {"next tick overflows", 208678456368547759, 0, false}, + {"unsigned maximum", math.MaxUint64, 0, false}, + } { + t.Run(tc.name, func(t *testing.T) { + wall := filetimeWall(uint32(tc.file), uint32(tc.file>>32)) + if tc.ok { + if wall.IsZero() || wall.UnixNano() != tc.ns { + t.Fatal(wall, tc.ns) + } + } else { + a := baseCounter() + p := ClockPort{&sequenceCounter{samples: []CounterSample{a, a}}, wallSample(func() time.Time { return wall })} + if s, err := p.SampleSnapshot(); !wall.IsZero() || err != ErrClockUnavailable || s != (ClockSnapshot{}) { + t.Fatal(wall, s, err) + } + } + }) + } +} diff --git a/internal/opencodeevent/clockwall_native.go b/internal/opencodeevent/clockwall_native.go new file mode 100644 index 000000000..5806dad0d --- /dev/null +++ b/internal/opencodeevent/clockwall_native.go @@ -0,0 +1,9 @@ +//go:build !windows + +package opencodeevent + +import "time" + +type systemWall struct{} + +func (systemWall) SampleWall() time.Time { return time.Now() } diff --git a/internal/opencodeevent/clockwall_windows.go b/internal/opencodeevent/clockwall_windows.go new file mode 100644 index 000000000..248ca844e --- /dev/null +++ b/internal/opencodeevent/clockwall_windows.go @@ -0,0 +1,27 @@ +//go:build windows + +package opencodeevent + +import ( + "time" + "unsafe" + + "golang.org/x/sys/windows" +) + +var preciseSystemTime = windows.NewLazySystemDLL("kernel32.dll").NewProc("GetSystemTimePreciseAsFileTime") + +type systemWall struct{} + +func (systemWall) SampleWall() time.Time { return preciseWindowsWall(preciseSystemTime) } + +func preciseWindowsWall(proc *windows.LazyProc) time.Time { + if proc == nil || proc.Find() != nil { + return time.Time{} + } + var ft windows.Filetime + // VOID API: its return register and stale syscall last-error are not status. + // Zero initialization plus checked conversion refuses an unwritten result. + _, _, _ = proc.Call(uintptr(unsafe.Pointer(&ft))) + return filetimeWall(ft.LowDateTime, ft.HighDateTime) +} diff --git a/internal/opencodeevent/clockwall_windows_test.go b/internal/opencodeevent/clockwall_windows_test.go new file mode 100644 index 000000000..ead6e8148 --- /dev/null +++ b/internal/opencodeevent/clockwall_windows_test.go @@ -0,0 +1,47 @@ +//go:build windows + +package opencodeevent + +import ( + "bytes" + "runtime" + "testing" + "time" + + "golang.org/x/sys/windows" +) + +// Regression: an unresolved precise wall API falls back to time.Now/coarse +// time. Resolve an actual missing system export, rather than mocking Find. +func TestClockSnapshotWindowsMissingWallAPI(t *testing.T) { + missing := windows.NewLazySystemDLL("kernel32.dll").NewProc("ANClockUnavailableExport") + for _, proc := range []*windows.LazyProc{nil, missing} { + a := baseCounter() + a.Kind, a.Domain = "windows-interrupt-precise", "windows-kernel" + p := ClockPort{&sequenceCounter{samples: []CounterSample{a, a}}, wallSample(func() time.Time { return preciseWindowsWall(proc) })} + var out bytes.Buffer + if code := WriteClockSnapshot([]string{"--protocol", "1"}, &out, p); code != 1 || out.String() != "{\"protocol\":1,\"error\":\"clock_unavailable\"}\n" { + t.Fatal(code, out.String()) + } + } +} + +// Regression: treating a VOID call's stale last-error as status refuses valid +// wall samples. This uses real system APIs; native execution is a parent gate. +func TestClockSnapshotWindowsNativePreciseWall(t *testing.T) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + setError := windows.NewLazySystemDLL("kernel32.dll").NewProc("SetLastError") + if setError.Find() != nil || preciseSystemTime.Find() != nil { + t.Fatal("precise wall API unavailable") + } + _, _, _ = setError.Call(123) + wall := (systemWall{}).SampleWall() + if wall.IsZero() || wall.Unix() <= 0 || wall.Nanosecond()%100 != 0 { + t.Fatal("invalid precise wall") + } + s, err := NewSystemSnapshotPort().SampleSnapshot() + if err != nil || s.ClockDomain != "windows-kernel" || s.ClockKind != "windows-interrupt-precise" { + t.Fatal("native precise coordinate unavailable", err) + } +} From 7ab6b1e45e7729d1f183b15ea10ffce23f330902 Mon Sep 17 00:00:00 2001 From: iliya Date: Thu, 1 Oct 2026 18:43:57 +0000 Subject: [PATCH 2/3] test(opencode): diagnose native Windows clock availability Compare the actual class90 boot identity ABI and precise counter against independent native calls. Retain the original whole-snapshot assertion and log bounded failure predicates without native identifiers. --- .../journal/clock_diagnostic_windows_test.go | 88 +++++++++++++++++++ .../opencodeevent/clockwall_windows_test.go | 25 ++++++ 2 files changed, 113 insertions(+) create mode 100644 internal/agentnotify/journal/clock_diagnostic_windows_test.go diff --git a/internal/agentnotify/journal/clock_diagnostic_windows_test.go b/internal/agentnotify/journal/clock_diagnostic_windows_test.go new file mode 100644 index 000000000..5c6145e0a --- /dev/null +++ b/internal/agentnotify/journal/clock_diagnostic_windows_test.go @@ -0,0 +1,88 @@ +//go:build windows + +package journal + +import ( + "math" + "runtime" + "strings" + "testing" + "unsafe" + + "golang.org/x/sys/windows" +) + +// Regression: a wrong class/layout, GUID byte order, precise export or tick +// conversion can refuse or misidentify a valid native boot coordinate. The +// oracle uses native GUID formatting and separate DLL bindings, not product +// layout/formatting/constants. ReturnLength is evidence, never a relaxed gate. +func TestWindowsNativeClockDiagnostic(t *testing.T) { + // PHNT ntexapi.h at 025db3a0ec0fa1b303152d570df492625a021331: + // SystemBootEnvironmentInformation (90), GUID/FIRMWARE_TYPE/BootFlags. + // Private ABI: this observation does not qualify alternate returned lengths. + var info struct { + Identifier windows.GUID + Firmware uint32 + _ uint32 + Flags uint64 + } + const expectedLength = 32 + const lengthSentinel uint32 = 0xa5a5a5a5 + t.Logf("native ABI size=%d guid_offset=%d guid_size=%d firmware_offset=%d firmware_size=%d flags_offset=%d flags_size=%d expected_length=%d", + unsafe.Sizeof(info), unsafe.Offsetof(info.Identifier), unsafe.Sizeof(info.Identifier), + unsafe.Offsetof(info.Firmware), unsafe.Sizeof(info.Firmware), unsafe.Offsetof(info.Flags), unsafe.Sizeof(info.Flags), expectedLength) + if unsafe.Sizeof(info) != expectedLength || unsafe.Offsetof(info.Identifier) != 0 || unsafe.Sizeof(info.Identifier) != 16 || + unsafe.Offsetof(info.Firmware) != 16 || unsafe.Sizeof(info.Firmware) != 4 || unsafe.Offsetof(info.Flags) != 24 || unsafe.Sizeof(info.Flags) != 8 { + t.Fatal("diagnostic ABI mismatch") + } + v := windows.RtlGetVersion() + t.Logf("native platform go=%s arch=%s windows_major=%d windows_minor=%d windows_build=%d", runtime.Version(), runtime.GOARCH, v.MajorVersion, v.MinorVersion, v.BuildNumber) + query := windows.NewLazySystemDLL("ntdll.dll").NewProc("NtQuerySystemInformation") + if query.Find() != nil { + t.Fatal("native boot query export unavailable") + } + retLen := lengthSentinel + status, _, _ := query.Call(90, uintptr(unsafe.Pointer(&info)), expectedLength, uintptr(unsafe.Pointer(&retLen))) + statusOK := int32(uint32(status)) >= 0 // NT_SUCCESS; last-error is not NTSTATUS. + nativeGUID := info.Identifier.String() // StringFromGUID2, independent of formatWindowsGUID. + parsed, parseErr := windows.GUIDFromString(nativeGUID) + canonical := strings.ToLower(strings.TrimSuffix(strings.TrimPrefix(nativeGUID, "{"), "}")) + canonicalOK := len(nativeGUID) == 38 && len(canonical) == 36 && parseErr == nil && parsed == info.Identifier + nonzero := info.Identifier != (windows.GUID{}) + bootFalse, falseOK := windowsBootIdentity(false) + bootTrue, trueOK := windowsBootIdentity(true) + stable := statusOK && nonzero && canonicalOK && falseOK && bootFalse == canonical + trueMatches := stable && trueOK && bootTrue == canonical + t.Logf("native boot ntstatus=0x%08x status_ok=%t return_length=%d expected_length=%d length_unchanged=%t length_exact=%t guid_nonzero=%t guid_canonical=%t guid_stable=%t boot_false_available=%t boot_true_available=%t boot_true_matches_native=%t", + uint32(status), statusOK, retLen, expectedLength, retLen == lengthSentinel, retLen == expectedLength, + nonzero, canonicalOK, stable, falseOK, trueOK, trueMatches) + + precise := windows.NewLazySystemDLL("kernel32.dll").NewProc("QueryInterruptTimePrecise") + exportOK := precise.Find() == nil + var before, after uint64 + if exportOK { + _, _, _ = precise.Call(uintptr(unsafe.Pointer(&before))) // VOID, ignore return/last-error. + } + boot, sec, nsec, sampleOK := WindowsPreciseBootSample() + if exportOK { + _, _, _ = precise.Call(uintptr(unsafe.Pointer(&after))) + } + counterNonzero := before != 0 && after != 0 + monotone := exportOK && counterNonzero && after >= before + conversionOK := sampleOK && sec >= 0 && nsec >= 0 && nsec < 1_000_000_000 && nsec%100 == 0 && + sec <= (math.MaxInt64-nsec)/1_000_000_000 && before <= math.MaxInt64/100 && after <= math.MaxInt64/100 + withinNative := false + if conversionOK && monotone { + ns := sec*1_000_000_000 + nsec + withinNative = uint64(ns) >= before*100 && uint64(ns) <= after*100 + } + sampleBootMatches := stable && sampleOK && boot == canonical + t.Logf("native precise export_available=%t counter_nonzero=%t counter_monotone=%t sample_available=%t sample_boot_matches_native=%t conversion_valid=%t sample_within_native_reads=%t", + exportOK, counterNonzero, monotone, sampleOK, sampleBootMatches, conversionOK, withinNative) + if !statusOK || retLen != expectedLength || !nonzero || !canonicalOK || !stable || !trueMatches { + t.Error("native boot contract unavailable; see bounded predicates") + } + if !exportOK || !counterNonzero || !monotone || !sampleBootMatches || !conversionOK || !withinNative { + t.Error("native precise contract unavailable; see bounded predicates") + } +} diff --git a/internal/opencodeevent/clockwall_windows_test.go b/internal/opencodeevent/clockwall_windows_test.go index ead6e8148..19a6ae180 100644 --- a/internal/opencodeevent/clockwall_windows_test.go +++ b/internal/opencodeevent/clockwall_windows_test.go @@ -42,6 +42,31 @@ func TestClockSnapshotWindowsNativePreciseWall(t *testing.T) { } s, err := NewSystemSnapshotPort().SampleSnapshot() if err != nil || s.ClockDomain != "windows-kernel" || s.ClockKind != "windows-interrupt-precise" { + logWindowsSnapshotPredicates(t) t.Fatal("native precise coordinate unavailable", err) } } + +// One additional actual A-wall-B triplet diagnoses a failed snapshot; it is +// neither a retry nor a replacement for the original production assertion. +// Only predicates and bracket width leave the test, never coordinates or IDs. +func logWindowsSnapshotPredicates(t *testing.T) { + t.Helper() + a, okA := (systemCounter{}).SampleCounter() + wall := (systemWall{}).SampleWall() + b, okB := (systemCounter{}).SampleCounter() + lo, okLo := clockNanoseconds(a.Sec, a.Nsec) + hi, okHi := clockNanoseconds(b.Sec, b.Nsec) + _, okWall := clockNanoseconds(wall.Unix(), int64(wall.Nanosecond())) + _, qualified := qualityAllowance(a.Kind) + widthAvailable := okA && okB && okLo && okHi && hi >= lo + var width int64 + if widthAvailable { + width = hi - lo + } + t.Logf("native snapshot A_available=%t B_available=%t A_ns_valid=%t B_ns_valid=%t wall_ns_valid=%t wall_nonzero=%t wall_positive=%t wall_100ns_aligned=%t profile_qualified=%t A_uuid_valid=%t B_uuid_valid=%t A_domain_valid=%t B_domain_valid=%t boot_equal=%t domain_equal=%t kind_equal=%t counter_regressed=%t width_available=%t width_ns=%d width_within_bound=%t", + okA, okB, okLo, okHi, okWall, !wall.IsZero(), wall.Unix() > 0, wall.Nanosecond()%100 == 0, qualified, + validClockUUID(a.Boot), validClockUUID(b.Boot), validClockDomain(a.Kind, a.Domain), validClockDomain(b.Kind, b.Domain), + a.Boot == b.Boot, a.Domain == b.Domain, a.Kind == b.Kind, okA && okB && okLo && okHi && hi < lo, + widthAvailable, width, widthAvailable && width <= maxSnapshotWidthNs) +} From 6602b0674b4c1041ae971008f60b84fedd178b74 Mon Sep 17 00:00:00 2001 From: iliya Date: Thu, 1 Oct 2026 20:18:46 +0000 Subject: [PATCH 3/3] fix(opencode): resolve precise Windows clock through API set Bind QueryInterruptTimePrecise to the documented realtime API contract. Retain strict boot identity and VOID pointer-output checks; report contract loading and export availability independently. --- .../journal/clock_diagnostic_windows_test.go | 23 ++++++++++++------- internal/agentnotify/journal/clock_windows.go | 4 +++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/internal/agentnotify/journal/clock_diagnostic_windows_test.go b/internal/agentnotify/journal/clock_diagnostic_windows_test.go index 5c6145e0a..1c4e12a6a 100644 --- a/internal/agentnotify/journal/clock_diagnostic_windows_test.go +++ b/internal/agentnotify/journal/clock_diagnostic_windows_test.go @@ -57,18 +57,25 @@ func TestWindowsNativeClockDiagnostic(t *testing.T) { uint32(status), statusOK, retLen, expectedLength, retLen == lengthSentinel, retLen == expectedLength, nonzero, canonicalOK, stable, falseOK, trueOK, trueMatches) - precise := windows.NewLazySystemDLL("kernel32.dll").NewProc("QueryInterruptTimePrecise") - exportOK := precise.Find() == nil + // Independent binding to the documented API-set contract, resolved by the OS. + // https://learn.microsoft.com/en-us/uwp/win32-and-com/win32-apis#apis-from-api-ms-win-core-realtime-l1-1-1dll + realtime := windows.NewLazySystemDLL("api-ms-win-core-realtime-l1-1-1.dll") + contractLoadOK := realtime.Load() == nil + precise := realtime.NewProc("QueryInterruptTimePrecise") + contractExportOK := false + if contractLoadOK { + contractExportOK = precise.Find() == nil + } var before, after uint64 - if exportOK { + if contractExportOK { _, _, _ = precise.Call(uintptr(unsafe.Pointer(&before))) // VOID, ignore return/last-error. } boot, sec, nsec, sampleOK := WindowsPreciseBootSample() - if exportOK { + if contractExportOK { _, _, _ = precise.Call(uintptr(unsafe.Pointer(&after))) } counterNonzero := before != 0 && after != 0 - monotone := exportOK && counterNonzero && after >= before + monotone := contractExportOK && counterNonzero && after >= before conversionOK := sampleOK && sec >= 0 && nsec >= 0 && nsec < 1_000_000_000 && nsec%100 == 0 && sec <= (math.MaxInt64-nsec)/1_000_000_000 && before <= math.MaxInt64/100 && after <= math.MaxInt64/100 withinNative := false @@ -77,12 +84,12 @@ func TestWindowsNativeClockDiagnostic(t *testing.T) { withinNative = uint64(ns) >= before*100 && uint64(ns) <= after*100 } sampleBootMatches := stable && sampleOK && boot == canonical - t.Logf("native precise export_available=%t counter_nonzero=%t counter_monotone=%t sample_available=%t sample_boot_matches_native=%t conversion_valid=%t sample_within_native_reads=%t", - exportOK, counterNonzero, monotone, sampleOK, sampleBootMatches, conversionOK, withinNative) + t.Logf("native precise contract_load_available=%t contract_export_available=%t counter_nonzero=%t counter_monotone=%t sample_available=%t sample_boot_matches_native=%t conversion_valid=%t sample_within_native_reads=%t", + contractLoadOK, contractExportOK, counterNonzero, monotone, sampleOK, sampleBootMatches, conversionOK, withinNative) if !statusOK || retLen != expectedLength || !nonzero || !canonicalOK || !stable || !trueMatches { t.Error("native boot contract unavailable; see bounded predicates") } - if !exportOK || !counterNonzero || !monotone || !sampleBootMatches || !conversionOK || !withinNative { + if !contractLoadOK || !contractExportOK || !counterNonzero || !monotone || !sampleBootMatches || !conversionOK || !withinNative { t.Error("native precise contract unavailable; see bounded predicates") } } diff --git a/internal/agentnotify/journal/clock_windows.go b/internal/agentnotify/journal/clock_windows.go index 73eb6adcd..8bc5916d3 100644 --- a/internal/agentnotify/journal/clock_windows.go +++ b/internal/agentnotify/journal/clock_windows.go @@ -84,9 +84,11 @@ func formatWindowsGUID(g [16]byte) string { return fmt.Sprintf("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", d1, d2, d3, g[8], g[9], g[10], g[11], g[12], g[13], g[14], g[15]) } +// Precise export contract: https://learn.microsoft.com/en-us/uwp/win32-and-com/win32-apis#apis-from-api-ms-win-core-realtime-l1-1-1dll var ( kernel32 = windows.NewLazySystemDLL("kernel32.dll") - queryInterruptTimePrecise = kernel32.NewProc("QueryInterruptTimePrecise") + realtime = windows.NewLazySystemDLL("api-ms-win-core-realtime-l1-1-1.dll") + queryInterruptTimePrecise = realtime.NewProc("QueryInterruptTimePrecise") queryInterruptTime = kernel32.NewProc("QueryInterruptTime") getTickCount64 = kernel32.NewProc("GetTickCount64") )