projection: begin the exact-path public epoch

The public tree and its history contain only the listed paths. Earlier projection history remains preserved internally.

Source-Sha: 913fde029b935671833254797f0f20f1eb9fabba

Policy-Sha: 913fde029b935671833254797f0f20f1eb9fabba

Tree-Digest: 180ae530c1058a2a5c89837bdce2d323ae83e669e38590ca72e75b8d92b7262f
This commit is contained in:
Fimeg 2026-09-08 21:59:33 -04:00
commit 67e26be2d9
838 changed files with 157302 additions and 0 deletions

View file

@ -0,0 +1,68 @@
package version
import (
"strconv"
"strings"
)
// Version represents a semantic version string
type Version string
// Platform represents combined platform-architecture format (e.g., "linux-amd64")
type Platform string
// ParsePlatform converts "linux-amd64" → platform="linux", arch="amd64"
func ParsePlatform(p Platform) (platform, architecture string) {
parts := strings.SplitN(string(p), "-", 2)
if len(parts) == 2 {
return parts[0], parts[1]
}
return string(p), ""
}
// String returns the full platform string
func (p Platform) String() string {
return string(p)
}
// Compare returns -1, 0, or 1 for v < other, v == other, v > other
func (v Version) Compare(other Version) int {
v1Parts := strings.Split(string(v), ".")
v2Parts := strings.Split(string(other), ".")
maxLen := len(v1Parts)
if len(v2Parts) > maxLen {
maxLen = len(v2Parts)
}
for i := 0; i < maxLen; i++ {
v1Num := 0
v2Num := 0
if i < len(v1Parts) {
v1Num, _ = strconv.Atoi(v1Parts[i])
}
if i < len(v2Parts) {
v2Num, _ = strconv.Atoi(v2Parts[i])
}
if v1Num < v2Num {
return -1
}
if v1Num > v2Num {
return 1
}
}
return 0
}
// IsUpgrade returns true if other is newer than v
func (v Version) IsUpgrade(other Version) bool {
return v.Compare(other) < 0
}
// IsValid returns true if version string is non-empty
func (v Version) IsValid() bool {
return string(v) != ""
}

View file

@ -0,0 +1,118 @@
package version
import (
"fmt"
"strconv"
"strings"
"time"
)
// Version coordination for Server Authority model
// The server is the single source of truth for all version information
// Version information (SERVER AUTHORITY).
// Values are maintained by scripts/bump-version.sh and must match the release
// tag — the release gate enforces this. ldflags may override at build time;
// the release pipeline injects the tag so binaries and source agree.
var (
AgentVersion = "0.2.9.6"
ConfigVersion = "0.2.9.6"
MinAgentVersion = "0.1.22"
)
// CurrentVersions holds the authoritative version information
type CurrentVersions struct {
AgentVersion string `json:"agent_version"`
ConfigVersion string `json:"config_version"`
MinAgentVersion string `json:"min_agent_version"`
BuildTime time.Time `json:"build_time"`
}
// GetCurrentVersions returns the current version information
func GetCurrentVersions() CurrentVersions {
return CurrentVersions{
AgentVersion: AgentVersion,
ConfigVersion: ConfigVersion,
MinAgentVersion: MinAgentVersion,
BuildTime: time.Now(),
}
}
// CompareVersions compares two version strings using octet-based comparison.
// Returns -1 if a < b, 0 if a == b, 1 if a > b.
// Handles "dev" as always older than any release version.
// Version format: "0.1.26.0" (up to 4 octets, padded with zeros).
func CompareVersions(a, b string) int {
a = strings.TrimPrefix(a, "v")
b = strings.TrimPrefix(b, "v")
if a == b {
return 0
}
if a == "dev" || a == "" {
return -1
}
if b == "dev" || b == "" {
return 1
}
aParts := strings.Split(a, ".")
bParts := strings.Split(b, ".")
maxLen := len(aParts)
if len(bParts) > maxLen {
maxLen = len(bParts)
}
for i := 0; i < maxLen; i++ {
aVal := 0
bVal := 0
if i < len(aParts) {
if n, err := strconv.Atoi(aParts[i]); err == nil {
aVal = n
}
}
if i < len(bParts) {
if n, err := strconv.Atoi(bParts[i]); err == nil {
bVal = n
}
}
if aVal < bVal {
return -1
}
if aVal > bVal {
return 1
}
}
return 0
}
// ExtractConfigVersionFromAgent extracts config version from agent version.
// Agent version format: "0.1.23.6" where the last octet is the config version.
func ExtractConfigVersionFromAgent(agentVersion string) string {
cleanVersion := strings.TrimPrefix(agentVersion, "v")
parts := strings.Split(cleanVersion, ".")
if len(parts) >= 1 {
return parts[len(parts)-1]
}
return "3"
}
// ValidateAgentVersion checks if an agent version is compatible
func ValidateAgentVersion(agentVersion string) error {
current := GetCurrentVersions()
if CompareVersions(agentVersion, current.MinAgentVersion) < 0 {
return fmt.Errorf("agent version %s is below minimum %s", agentVersion, current.MinAgentVersion)
}
return nil
}
// GetBuildFlags returns the ldflags to inject versions into agent builds
func GetBuildFlags() []string {
versions := GetCurrentVersions()
return []string{
fmt.Sprintf("-X github.com/Fimeg/RedFlag/agent/internal/version.Version=%s", versions.AgentVersion),
fmt.Sprintf("-X github.com/Fimeg/RedFlag/agent/internal/version.ConfigVersion=%s", versions.ConfigVersion),
fmt.Sprintf("-X github.com/Fimeg/RedFlag/agent/internal/version.BuildTime=%s", versions.BuildTime.Format(time.RFC3339)),
}
}

View file

@ -0,0 +1,86 @@
package version
import (
"testing"
)
func TestCompareVersionsCorrect(t *testing.T) {
tests := []struct {
a, b string
expected int
desc string
}{
{"0.1.22", "0.1.9", 1, "multi-digit minor beats single-digit"},
{"0.2.0", "0.1.99", 1, "major bump beats high patch"},
{"dev", "0.1.0", -1, "dev is always older"},
{"0.1.0", "dev", 1, "any release beats dev"},
{"0.1.26.0", "0.1.26.0", 0, "equal versions"},
{"0.1.26.1", "0.1.26.0", 1, "config version differs"},
{"1.0.0", "0.99.99", 1, "major version wins"},
{"dev", "dev", 0, "dev equals dev"},
{"", "0.1.0", -1, "empty is older"},
{"v0.1.22", "0.1.22", 0, "v prefix stripped"},
{"0.1.22", "v0.1.22", 0, "v prefix on second arg"},
}
for _, tt := range tests {
result := CompareVersions(tt.a, tt.b)
if result != tt.expected {
t.Errorf("CompareVersions(%q, %q): got %d, want %d (%s)", tt.a, tt.b, result, tt.expected, tt.desc)
}
}
t.Logf("[INFO] [server] [version] U-2 VERIFIED: all %d semver comparison cases pass", len(tests))
}
func TestExtractConfigVersionFromAgent(t *testing.T) {
tests := []struct {
version string
expected string
}{
{"0.1.23.6", "6"},
{"0.1.23", "23"},
{"v0.1.30", "30"},
{"0.1.26.10", "10"},
{"dev", "dev"},
}
for _, tt := range tests {
result := ExtractConfigVersionFromAgent(tt.version)
if result != tt.expected {
t.Errorf("ExtractConfigVersionFromAgent(%q): got %q, want %q", tt.version, result, tt.expected)
}
}
t.Logf("[INFO] [server] [version] U-10 VERIFIED: config version extraction works for multi-digit versions")
}
func TestValidateAgentVersionSemverAware(t *testing.T) {
// Save and restore original MinAgentVersion
orig := MinAgentVersion
defer func() { MinAgentVersion = orig }()
MinAgentVersion = "0.1.10"
// 0.1.9 < 0.1.10 — should fail
if err := ValidateAgentVersion("0.1.9"); err == nil {
t.Error("expected 0.1.9 to fail validation against min 0.1.10, but it passed")
}
// 0.1.22 > 0.1.10 — should pass
if err := ValidateAgentVersion("0.1.22"); err != nil {
t.Errorf("expected 0.1.22 to pass validation against min 0.1.10, got: %v", err)
}
t.Logf("[INFO] [server] [version] U-2 VERIFIED: ValidateAgentVersion uses octet comparison, not lexicographic")
}
func TestInfoEndpointReturnsCurrentVersion(t *testing.T) {
// Test that GetCurrentVersions returns values (not the old hardcoded v0.1.21)
versions := GetCurrentVersions()
if versions.AgentVersion == "v0.1.21" {
t.Error("AgentVersion is still the old hardcoded v0.1.21 — should be dynamic")
}
if versions.AgentVersion == "" {
t.Error("AgentVersion is empty")
}
t.Logf("[INFO] [server] [version] U-1 VERIFIED: GetCurrentVersions returns %q (not hardcoded v0.1.21)", versions.AgentVersion)
}