Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions cmd/axis/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,11 @@ func versionCmd() *cobra.Command {
ui.PrintLogo(out, Version)
fmt.Fprintln(out)
fmt.Fprintf(out, "axis %s\n", Version)
if buildinfo.Commit != "" {
fmt.Fprintf(out, " commit: %s\n", buildinfo.Commit)
if commit := buildinfo.ResolvedCommit(); commit != "" {
Comment thread
toasterbook88 marked this conversation as resolved.
fmt.Fprintf(out, " commit: %s\n", commit)
}
if buildinfo.Date != "" {
fmt.Fprintf(out, " built: %s\n", buildinfo.Date)
if built := buildinfo.ResolvedDate(); built != "" {
fmt.Fprintf(out, " built: %s\n", built)
}
goVer := buildinfo.GoVersion
if goVer == "" {
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Start Here

- [Current State](current-state.md) — current orientation doc for the live repo, command surface, coverage snapshot, and known weak spots
- [Agent Worklog](agent-worklog.md) — active coordination surface, file ownership, and current task tracking
- [Agent Worklog](agent-worklog.md) — historical coordination log and file-ownership notes; not the current queue
- [RAM Balancing Research](ram-balancing-research.md) — research-backed design note on how AXIS should model cluster RAM sharing and balancing

## Design Docs
Expand Down
2 changes: 1 addition & 1 deletion docs/agent-worklog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AXIS Agent Worklog

This file is the canonical coordination surface for active AXIS work.
Historical coordination log. The reviewed HEAD below is 2026-06-04. Live orientation is `docs/current-state.md`. The task tables are not the current queue.

## Coordination Contract

Expand Down
2 changes: 1 addition & 1 deletion docs/phase-tracking.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
| Phase 6 | **Completed** | Trust-and-foundations work: GPU/storage/network enrichment, stable identity, and failure-memory placement signals |
| Phase 7 | **Completed** | Runtime hardening: streamed `/run`, reservation durability, forwarded provenance, and discovery freshness |

Current release: **v0.12.2**
Phase completion is not the release record. The repo version constant is `internal/buildinfo/version.go`, repeated in the generated fact in `docs/current-state.md`. Published releases are on GitHub Releases.
12 changes: 8 additions & 4 deletions docs/runbooks/a2a-task-delegation.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,11 @@ curl -s -H "Authorization: Bearer $T" --unix-socket "$S" \
http://localhost/a2a/v1/tasks/pending # -> {"tasks":[]}
```

`axis version` prints `buildinfo.Commit` baked into the binary you just executed
(`cmd/axis/main.go`, `internal/buildinfo`). It does not ask the running daemon who
it is. **`axis update` replaces the binary but does not restart a supervised
daemon** — check the process start time, or the API response, not `axis version`.
`axis version` prints `commit:` from the ldflag `buildinfo.Commit` when that
value is set (`cmd/axis/main.go`, `internal/buildinfo.ResolvedCommit`). A plain
build whose commit ldflag is empty prints `vcs.revision` and appends `-dirty`
when `vcs.modified` is true. `built:` is only the ldflag build time.
`vcs.time` is the revision time and is not a build time. The command does not
ask the running daemon who it is. **`axis update` replaces the binary but does
not restart a supervised daemon** — check the process start time, or the API
response, not `axis version`.
48 changes: 48 additions & 0 deletions internal/buildinfo/build.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,61 @@
package buildinfo

import "runtime/debug"

// Commit, Date, and GoVersion are set at build time via -ldflags.
// Example:
//
// go build -ldflags "-X github.com/toasterbook88/axis/internal/buildinfo.Commit=abc1234
// -X github.com/toasterbook88/axis/internal/buildinfo.Date=2026-04-01T12:00:00Z
// -X github.com/toasterbook88/axis/internal/buildinfo.GoVersion=go1.26.2"
//
// When the commit ldflag is empty, ResolvedCommit uses vcs.revision and
// appends -dirty when vcs.modified is true. ResolvedDate is only the ldflag
// build time. vcs.time is the revision time and is not a build time.
var (
Commit string
Date string
GoVersion string
)

// ResolvedCommit returns the ldflags commit, or the toolchain revision.
// A dirty toolchain revision is marked so it is not printed as a clean commit.
func ResolvedCommit() string {
if Commit != "" {
return Commit
}
info, ok := debug.ReadBuildInfo()
if !ok || info == nil {
return ""
}
return commitFromSettings(info.Settings)
}

func commitFromSettings(settings []debug.BuildSetting) string {
rev := settingFrom(settings, "vcs.revision")
if rev == "" {
return ""
}
if settingFrom(settings, "vcs.modified") == "true" {
return rev + "-dirty"
}
return rev
}

// ResolvedDate returns the ldflag build time. It does not use vcs.time.
func ResolvedDate() string {
return dateFromSettings(Date, nil)
}

func dateFromSettings(date string, _ []debug.BuildSetting) string {
return date
}

func settingFrom(settings []debug.BuildSetting, key string) string {
for _, s := range settings {
if s.Key == key {
return s.Value
}
}
return ""
}
87 changes: 87 additions & 0 deletions internal/buildinfo/build_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package buildinfo

import (
"runtime/debug"
"testing"
)

func TestResolvedCommitPrefersLdflags(t *testing.T) {
old := Commit
t.Cleanup(func() { Commit = old })
Commit = "abc1234"
if got := ResolvedCommit(); got != "abc1234" {
t.Fatalf("ResolvedCommit() = %q, want ldflags value", got)
}
}

func TestResolvedDatePrefersLdflags(t *testing.T) {
old := Date
t.Cleanup(func() { Date = old })
Date = "2026-04-01T12:00:00Z"
if got := ResolvedDate(); got != "2026-04-01T12:00:00Z" {
t.Fatalf("ResolvedDate() = %q, want ldflags value", got)
}
}

func TestResolvedCommitEmptyLdflagMatchesToolchainSettings(t *testing.T) {
old := Commit
t.Cleanup(func() { Commit = old })
Commit = ""
info, ok := debug.ReadBuildInfo()
if !ok || info == nil {
t.Fatal("ReadBuildInfo unavailable")
}
want := commitFromSettings(info.Settings)
if got := ResolvedCommit(); got != want {
t.Fatalf("ResolvedCommit() = %q, want toolchain settings %q", got, want)
}
}

func TestResolvedDateEmptyLdflagStaysEmpty(t *testing.T) {
old := Date
t.Cleanup(func() { Date = old })
Date = ""
if got := ResolvedDate(); got != "" {
t.Fatalf("ResolvedDate() = %q, want empty ldflag", got)
}
}

func TestCommitFromSettingsMarksADirtyTree(t *testing.T) {
settings := []debug.BuildSetting{
{Key: "vcs.revision", Value: "abc1234"},
{Key: "vcs.modified", Value: "true"},
}
if got := commitFromSettings(settings); got != "abc1234-dirty" {
t.Fatalf("commitFromSettings() = %q, want dirty revision", got)
}
settings[1].Value = "false"
if got := commitFromSettings(settings); got != "abc1234" {
t.Fatalf("commitFromSettings() = %q, want clean revision", got)
}
if got := commitFromSettings([]debug.BuildSetting{{Key: "vcs.modified", Value: "true"}}); got != "" {
t.Fatalf("commitFromSettings() = %q, want empty without a revision", got)
}
}

func TestDateFromSettingsIgnoresRevisionTime(t *testing.T) {
settings := []debug.BuildSetting{{Key: "vcs.time", Value: "2020-01-01T00:00:00Z"}}
if got := dateFromSettings("", settings); got != "" {
t.Fatalf("dateFromSettings() = %q, want empty build time", got)
}
if got := dateFromSettings("2026-04-01T12:00:00Z", settings); got != "2026-04-01T12:00:00Z" {
t.Fatalf("dateFromSettings() = %q, want ldflag build time", got)
}
}

func TestSettingFrom(t *testing.T) {
settings := []debug.BuildSetting{{Key: "vcs.revision", Value: "deadbeef"}}
if got := settingFrom(settings, "vcs.revision"); got != "deadbeef" {
t.Fatalf("settingFrom() = %q", got)
}
if got := settingFrom(settings, "vcs.time"); got != "" {
t.Fatalf("missing key = %q, want empty", got)
}
if got := settingFrom(nil, "vcs.revision"); got != "" {
t.Fatalf("nil settings = %q, want empty", got)
}
}
Loading