mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
feat(build): add flywheel+legacy archive build tags; default ao omits them (age-focus-membrane-bookkeeper-m1wg.12)
Mechanism first, no behavior change (ADR-0012). Default `go build ./...` and the
shipped `ao` are the spine. `make build-flywheel` (-tags "flywheel legacy") and
`AGENTOPS_LEGACY=1 make build` (-tags legacy) restore the archived satellites.
- cmd/ao/buildtags.go + buildtags_{flywheel,legacy}.go: the tag-gated anchor +
a hidden `ao buildtags` introspection surface (spine vs restored). Hidden, so
no user-facing command surface change.
- Makefile/cli/Makefile: build-flywheel + verify-buildtags targets, GO_TAGS plumbing.
- scripts/verify-buildtags.sh: compiles all 4 variants, asserts default omits +
tags restore (make verify-buildtags).
- docs/architecture/build-tags.md: the convention .13/.14 follow to archive
real commands. Archive-not-delete: code stays buildable per ADR-0004/0009/0011.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
SHELL := /usr/bin/env bash
|
||||
|
||||
.PHONY: local-ci local-ci-fast ci build test docs-check regen-all regen-check clean help
|
||||
.PHONY: local-ci local-ci-fast ci build build-flywheel verify-buildtags test docs-check regen-all regen-check clean help
|
||||
|
||||
# Default: run the release-grade local CI gate.
|
||||
# Note: scripts/ci-local-release.sh already includes build + test + release-binary validation.
|
||||
@@ -14,9 +14,15 @@ local-ci-fast: ## Run local CI without e2e install test, using quick security mo
|
||||
|
||||
ci: local-ci ## Alias for local-ci
|
||||
|
||||
build: ## Build ao CLI binary
|
||||
build: ## Build ao CLI binary (default = spine; archived satellites omitted)
|
||||
$(MAKE) -C cli build
|
||||
|
||||
build-flywheel: ## Build ao with the ADR-0012 archived satellites restored (flywheel + legacy)
|
||||
$(MAKE) -C cli build-flywheel
|
||||
|
||||
verify-buildtags: ## Verify the ADR-0012 build-tag mechanism (default omits; flywheel/legacy restore)
|
||||
$(MAKE) -C cli verify-buildtags
|
||||
|
||||
test: ## Run CLI tests
|
||||
$(MAKE) -C cli test
|
||||
|
||||
|
||||
+26
-3
@@ -1,4 +1,4 @@
|
||||
.PHONY: build test lint clean install sync-hooks inner membrane-parity
|
||||
.PHONY: build build-flywheel test lint clean install sync-hooks inner membrane-parity verify-buildtags
|
||||
|
||||
# Build variables
|
||||
BINARY_NAME=ao
|
||||
@@ -8,6 +8,17 @@ LDFLAGS=-ldflags "-X main.version=$(VERSION)"
|
||||
IDENTIFIER=com.12factoragentops.ao
|
||||
GOLANGCI_LINT ?= ../scripts/golangci-lint-v2.sh
|
||||
|
||||
# ADR-0012 archive build tags. The default build OMITS both the corpus/flywheel
|
||||
# (`flywheel`) and RPI/factory (`legacy`) command sets — they are archived, not
|
||||
# deleted, so the code stays buildable for the revival conditions. Restore them:
|
||||
# make build-flywheel # both tags (flywheel + legacy)
|
||||
# AGENTOPS_LEGACY=1 make build # legacy only
|
||||
GO_TAGS ?=
|
||||
ifdef AGENTOPS_LEGACY
|
||||
GO_TAGS += legacy
|
||||
endif
|
||||
GO_TAGS_FLAG = $(if $(strip $(GO_TAGS)),-tags "$(strip $(GO_TAGS))",)
|
||||
|
||||
# Default target
|
||||
all: build
|
||||
|
||||
@@ -21,14 +32,26 @@ sync-hooks:
|
||||
@cp ../scripts/pawl-review.sh ../scripts/pawl-verdict.sh ../scripts/pawl.sh embedded/pawl/scripts/
|
||||
@cp ../schemas/pawl-verdict.v1.schema.json embedded/pawl/schemas/
|
||||
|
||||
# Build the binary
|
||||
# Build the binary (default = spine; archived satellites omitted)
|
||||
build: sync-hooks
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/ao
|
||||
go build $(LDFLAGS) $(GO_TAGS_FLAG) -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/ao
|
||||
ifeq ($(shell uname -s),Darwin)
|
||||
codesign -f -s - -i $(IDENTIFIER) $(BUILD_DIR)/$(BINARY_NAME)
|
||||
endif
|
||||
|
||||
# Build with the archived satellites restored (ADR-0012): corpus/flywheel
|
||||
# (`flywheel`) + RPI/factory (`legacy`). Used to run or maintain the archived
|
||||
# command set under the revival conditions.
|
||||
build-flywheel: GO_TAGS += flywheel legacy
|
||||
build-flywheel: build
|
||||
|
||||
# Verify the build-tag mechanism: default build omits the tagged set; the
|
||||
# flywheel/legacy tags restore it. Compiles all three variants and checks the
|
||||
# hidden `ao buildtags` introspection surface.
|
||||
verify-buildtags:
|
||||
bash ../scripts/verify-buildtags.sh
|
||||
|
||||
# Run tests
|
||||
test: build
|
||||
go test -v -shuffle=on ./...
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// archiveBuildTags lists the ADR-0012 archive build tags this binary was
|
||||
// compiled with. It is EMPTY in the default build (the spine). Tag-gated files
|
||||
// append to it from their init():
|
||||
//
|
||||
// cmd/ao/buildtags_flywheel.go (//go:build flywheel) -> "flywheel"
|
||||
// cmd/ao/buildtags_legacy.go (//go:build legacy) -> "legacy"
|
||||
//
|
||||
// The mechanism: a command archived behind one of these tags lives in a file
|
||||
// carrying the matching `//go:build <tag>` constraint, so its init() (which
|
||||
// registers the cobra command) is only compiled when that tag is passed. The
|
||||
// default `go build ./...` omits the file entirely — the command is neither
|
||||
// compiled nor registered. `make build-flywheel` (tags: flywheel legacy) or
|
||||
// `AGENTOPS_LEGACY=1 make build` (tag: legacy) restore them. ADR-0012 requires
|
||||
// archive-not-delete, so the code stays buildable for the revival conditions.
|
||||
var archiveBuildTags []string
|
||||
|
||||
// buildtagsCmd is a hidden introspection surface: it reports which archive build
|
||||
// tags the running binary was compiled with, so an operator (or a test) can tell
|
||||
// a spine build from a restored-satellite build. Hidden so the default command
|
||||
// surface is unchanged.
|
||||
var buildtagsCmd = &cobra.Command{
|
||||
Use: "buildtags",
|
||||
Short: "Report which ADR-0012 archive build tags this binary was compiled with",
|
||||
Hidden: true,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
if len(archiveBuildTags) == 0 {
|
||||
fmt.Fprintln(cmd.OutOrStdout(), "spine (no archive build tags; corpus/flywheel + RPI/factory omitted)")
|
||||
return nil
|
||||
}
|
||||
for _, t := range archiveBuildTags {
|
||||
fmt.Fprintln(cmd.OutOrStdout(), t)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(buildtagsCmd)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build flywheel
|
||||
|
||||
package main
|
||||
|
||||
// Compiled only with `-tags flywheel`. Marks the binary as carrying the archived
|
||||
// corpus/flywheel command set (ADR-0012). The default build omits this file.
|
||||
func init() {
|
||||
archiveBuildTags = append(archiveBuildTags, "flywheel")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build legacy
|
||||
|
||||
package main
|
||||
|
||||
// Compiled only with `-tags legacy`. Marks the binary as carrying the archived
|
||||
// RPI/factory command set (ADR-0012). The default build omits this file.
|
||||
func init() {
|
||||
archiveBuildTags = append(archiveBuildTags, "legacy")
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestBuildtags_DefaultBuildIsSpine asserts that the default build (no archive
|
||||
// build tags) reports the spine: archiveBuildTags is empty and `ao buildtags`
|
||||
// prints the spine line. The flywheel/legacy variants are exercised by the
|
||||
// build-tag verification script (scripts/verify-buildtags.sh), which actually
|
||||
// compiles with -tags; here we lock the default-build contract.
|
||||
func TestBuildtags_DefaultBuildIsSpine(t *testing.T) {
|
||||
if len(archiveBuildTags) != 0 {
|
||||
t.Fatalf("default build must carry NO archive build tags, got %v", archiveBuildTags)
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
buildtagsCmd.SetOut(&out)
|
||||
t.Cleanup(func() { buildtagsCmd.SetOut(nil) })
|
||||
|
||||
if err := buildtagsCmd.RunE(buildtagsCmd, nil); err != nil {
|
||||
t.Fatalf("buildtags RunE: %v", err)
|
||||
}
|
||||
got := out.String()
|
||||
if !bytes.Contains([]byte(got), []byte("spine")) {
|
||||
t.Fatalf("default build buildtags output = %q, want it to mention 'spine'", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildtags_HiddenFromDefaultSurface keeps the introspection command off the
|
||||
// public command surface so adding the mechanism is no user-facing behavior
|
||||
// change.
|
||||
func TestBuildtags_HiddenFromDefaultSurface(t *testing.T) {
|
||||
if !buildtagsCmd.Hidden {
|
||||
t.Fatal("buildtags must stay Hidden so the default command surface is unchanged")
|
||||
}
|
||||
}
|
||||
@@ -402,6 +402,7 @@ func TestCobraCommandTreeRegistration(t *testing.T) {
|
||||
"badge",
|
||||
"batch-feedback",
|
||||
"beads",
|
||||
"buildtags",
|
||||
"canon",
|
||||
"capabilities",
|
||||
"chaos-test",
|
||||
@@ -554,6 +555,7 @@ func TestCobraExpectedCmdsMatchRegistration(t *testing.T) {
|
||||
"badge",
|
||||
"batch-feedback",
|
||||
"beads",
|
||||
"buildtags",
|
||||
"canon",
|
||||
"capabilities",
|
||||
"chaos-test",
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# Build tags: archiving the satellites (ADR-0012)
|
||||
|
||||
> How AgentOps keeps the unproven corpus/flywheel and RPI/factory satellites
|
||||
> **buildable but omitted by default**. Decision: [ADR-0012](../adr/ADR-0012-focus-surface-on-membrane-bookkeeper-archive-satellites.md).
|
||||
> The satellites are **archived, not deleted**, because the ADR-0004/0009/0011
|
||||
> revival conditions require the code to stay compilable.
|
||||
|
||||
## The two tags
|
||||
|
||||
| Tag | Archives | Bead |
|
||||
|-----|----------|------|
|
||||
| `flywheel` | corpus/flywheel commands + packages (forge, compile, wiki, harvest, mine, pool, maturity, refinery, flywheel, dedup, defrag, curate, ratchet, …) | `…m1wg.13` |
|
||||
| `legacy` | RPI/factory commands (orchestrate, codex lifecycle, tick, autodev, evolve, loop, turn, harness, operator) | `…m1wg.14` |
|
||||
|
||||
The **default** `go build ./...` (and the shipped `ao`) compile **neither**. They are the spine.
|
||||
|
||||
## How to build each variant
|
||||
|
||||
```bash
|
||||
make build # spine — archived sets omitted (default)
|
||||
make build-flywheel # restore BOTH (-tags "flywheel legacy")
|
||||
AGENTOPS_LEGACY=1 make build # restore the legacy (RPI/factory) set only
|
||||
cd cli && go build -tags flywheel ./... # flywheel only
|
||||
cd cli && go build -tags "flywheel legacy" ./... # both
|
||||
```
|
||||
|
||||
Introspect a binary: `ao buildtags` prints `spine` (default) or the tags it was built with.
|
||||
|
||||
## How to archive a command behind a tag
|
||||
|
||||
Build tags are **file-level** in Go. To archive a command:
|
||||
|
||||
1. Put the command's definition + its `init()` registration (the `rootCmd.AddCommand(...)`
|
||||
call) in a file carrying the tag constraint as the **first line**, followed by a blank line:
|
||||
|
||||
```go
|
||||
//go:build flywheel
|
||||
|
||||
package main
|
||||
// … the cobra command var + func init(){ rootCmd.AddCommand(fooCmd) } …
|
||||
```
|
||||
|
||||
With the tag absent, the file is not compiled, so the command is neither built
|
||||
nor registered — it simply isn't in the spine binary.
|
||||
|
||||
2. **Self-containment is the rule.** Any symbol the archived command references
|
||||
(helper funcs, vars, packages) must also be tag-gated, or the spine build
|
||||
breaks with "undefined". Move shared helpers that the spine still needs into an
|
||||
untagged file; move helpers only the archived command uses into the tagged file.
|
||||
If a spine file references an archived symbol, provide a `//go:build !flywheel`
|
||||
stub.
|
||||
|
||||
3. Whole packages (e.g. `internal/wiki`, `internal/pool`, `internal/ratchet`) are
|
||||
archived by tagging every file in the package, or by ensuring the only importers
|
||||
are themselves tag-gated.
|
||||
|
||||
4. Retag the disposition ledger and regenerate: `make regen-all`, then `make regen-check`.
|
||||
|
||||
## Verifying the mechanism
|
||||
|
||||
`make verify-buildtags` (→ `scripts/verify-buildtags.sh`) compiles the spine,
|
||||
`flywheel`, `legacy`, and combined variants and asserts `ao buildtags` reports
|
||||
each correctly. The default build must omit the tagged sets; the tags must restore
|
||||
them. The `cmd/ao/buildtags*.go` files are the mechanism's anchor and self-test.
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"generated_at": "2026-06-30T14:45:12Z",
|
||||
"generated_at": "2026-06-30T20:43:37Z",
|
||||
"summary": {
|
||||
"skills": 75,
|
||||
"hooks": 0,
|
||||
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# verify-buildtags.sh — prove the ADR-0012 build-tag archive mechanism.
|
||||
#
|
||||
# The default `go build` must OMIT the archived command sets; the `flywheel` and
|
||||
# `legacy` tags must restore them. We can't grep for archived commands until
|
||||
# .13/.14 move real commands behind the tags, so this checks the mechanism via
|
||||
# the hidden `ao buildtags` introspection surface:
|
||||
# default build -> "spine" (no archive tags compiled in)
|
||||
# -tags flywheel -> "flywheel"
|
||||
# -tags legacy -> "legacy"
|
||||
# -tags flywheel legacy-> "flywheel" + "legacy"
|
||||
#
|
||||
# Exit codes: 0 = mechanism works; 1 = a variant did not report as expected;
|
||||
# 2 = script/build error.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CLI="$ROOT/cli"
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
build() { # build <out> [tags...]
|
||||
local out="$1"; shift
|
||||
local tagflag=()
|
||||
if [ "$#" -gt 0 ]; then tagflag=(-tags "$*"); fi
|
||||
( cd "$CLI" && go build "${tagflag[@]}" -o "$out" ./cmd/ao ) || {
|
||||
echo "FAIL: go build (tags='$*') errored — archived code must stay buildable" >&2
|
||||
exit 2
|
||||
}
|
||||
}
|
||||
|
||||
expect() { # expect <binary> <substr> <present|absent>
|
||||
local bin="$1" needle="$2" mode="$3" out
|
||||
out="$("$bin" buildtags 2>/dev/null || true)"
|
||||
# Whole-line match: a tag is emitted on its own line, so the spine's
|
||||
# explanatory text ("…corpus/flywheel…") never false-matches a tag check.
|
||||
case "$mode" in
|
||||
present)
|
||||
if ! printf '%s\n' "$out" | grep -qx "$needle"; then
|
||||
echo "FAIL: '$bin buildtags' = '$out' — expected a line '$needle'" >&2
|
||||
exit 1
|
||||
fi ;;
|
||||
absent)
|
||||
if printf '%s\n' "$out" | grep -qx "$needle"; then
|
||||
echo "FAIL: '$bin buildtags' = '$out' — expected NO line '$needle'" >&2
|
||||
exit 1
|
||||
fi ;;
|
||||
esac
|
||||
}
|
||||
|
||||
echo "verify-buildtags: default (spine) build…"
|
||||
build "$TMP/ao-spine"
|
||||
if ! "$TMP/ao-spine" buildtags 2>/dev/null | grep -q "spine"; then
|
||||
echo "FAIL: default build should report 'spine'" >&2; exit 1
|
||||
fi
|
||||
expect "$TMP/ao-spine" "flywheel" absent
|
||||
expect "$TMP/ao-spine" "legacy" absent
|
||||
|
||||
echo "verify-buildtags: -tags flywheel…"
|
||||
build "$TMP/ao-flywheel" flywheel
|
||||
expect "$TMP/ao-flywheel" "flywheel" present
|
||||
expect "$TMP/ao-flywheel" "legacy" absent
|
||||
|
||||
echo "verify-buildtags: -tags legacy (AGENTOPS_LEGACY path)…"
|
||||
build "$TMP/ao-legacy" legacy
|
||||
expect "$TMP/ao-legacy" "legacy" present
|
||||
expect "$TMP/ao-legacy" "flywheel" absent
|
||||
|
||||
echo "verify-buildtags: -tags 'flywheel legacy' (make build-flywheel)…"
|
||||
build "$TMP/ao-all" flywheel legacy
|
||||
expect "$TMP/ao-all" "flywheel" present
|
||||
expect "$TMP/ao-all" "legacy" present
|
||||
|
||||
echo "OK: build-tag mechanism verified — default build omits archived sets; flywheel/legacy restore them."
|
||||
Reference in New Issue
Block a user