Documentation
¶
Overview ¶
Package history implements the `aiwf history ` verb (per-verb subpackage of M-0116; cmd/aiwf/main.go's newRootCmd wires it via NewCmd).
Index ¶
- func HasAuthorizedBy(events []HistoryEvent) bool
- func HasOwnScope(events []HistoryEvent) bool
- func HasScopeData(events []HistoryEvent) bool
- func NewCmd() *cobra.Command
- func RenderActor(e HistoryEvent) string
- func RenderScopeChips(e HistoryEvent, scopeEntities map[string]string, showAuth bool) string
- func RenderTo(to string) string
- func Run(id, root, format string, pretty, showAuth bool) int
- func ScopeMapFor(ctx context.Context, root string, events []HistoryEvent) map[string]string
- func ShortHash(sha string) string
- func SplitMultiValueTrailer(raw string) []string
- func StripTrailers(body string) string
- type HistoryEvent
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func HasAuthorizedBy ¶ added in v0.23.0
func HasAuthorizedBy(events []HistoryEvent) bool
HasAuthorizedBy reports whether any event carries an `aiwf-authorized-by` reference — i.e. the entity was worked under a foreign authorization scope. It is the guard for `aiwf show`'s global authorize-opener grep: source (a) — resolving which foreign scope authorized this entity's work — is the only consumer that needs the repo-wide opener map. Scopes opened directly on the entity (source (b)) come from the entity's own history via cliutil.LoadEntityScopes, so an active direct-scope opener (which has no `aiwf-authorized-by`) must NOT trigger the global grep.
func HasOwnScope ¶ added in v0.23.0
func HasOwnScope(events []HistoryEvent) bool
HasOwnScope reports whether any event is an authorize-opener on the entity itself (aiwf-verb: authorize + aiwf-scope: opened) — i.e. at least one scope was opened directly on it. It is show's guard for the per-entity cliutil.LoadEntityScopes(id) walk (source (b)): an entity whose own history carries no authorize-opener has no direct scopes, so that walk is skipped. Combined with the HasAuthorizedBy guard on the global grep, a scopeless entity resolves no scope table without any git read beyond the history it already loaded — the walk skipped by this guard is what makes `aiwf show` on a scopeless entity as cheap as `aiwf history` (E-0054 / M-0223).
The predicate is exact: cliutil.LoadEntityScopes builds a scope only from an authorize+opened commit carrying aiwf-entity: id, and ReadHistory greps the same aiwf-entity: id (width-tolerantly), so the opener appears in both — if LoadEntityScopes would return a scope, HasOwnScope sees its opener event.
func HasScopeData ¶ added in v0.23.0
func HasScopeData(events []HistoryEvent) bool
HasScopeData reports whether any event carries scope provenance the history text renderer resolves via the global authorize-opener map: an `aiwf-authorized-by` reference or an `aiwf-scope-ends` terminator. It is the guard for `aiwf history`'s BuildScopeEntityMap grep. When it returns false, RenderScopeChips never reads the map — an authorize opener's own `[scope: opened]` chip renders from e.Scope without the map — so the grep is pure waste.
func NewCmd ¶
NewCmd builds `aiwf history <id>`: filters git log for the entity's structured trailers and prints one line per event.
func RenderActor ¶
func RenderActor(e HistoryEvent) string
RenderActor formats the actor column. When a non-human principal is present and differs from the actor (the agent-acts-for-human case from I2.5), the column reads `principal via agent` so the human is visually attributed first. Direct human acts (no principal) render the actor verbatim.
func RenderScopeChips ¶
func RenderScopeChips(e HistoryEvent, scopeEntities map[string]string, showAuth bool) string
RenderScopeChips assembles the trailing chip block for one history row. For `aiwf authorize` rows, a `[<scope> <event>]` chip names the lifecycle event (`opened` / `paused` / `resumed`). For scope-authorized rows, a `[<scope-entity> <auth-short>]` chip names the authorizing scope. For terminal-promote rows that ended one or more scopes, one `[<scope-entity> ended]` chip per ended scope.
scopeEntities maps full auth-SHA to scope-entity id. showAuth flips on the full SHA inline (the --show-authorization flag).
The output begins with a leading " " when non-empty so it sits flush against the Commit column the caller already printed.
func RenderTo ¶
RenderTo formats the target-status column in `aiwf history` text output. Empty (the absent-trailer case for non-promote events and pre-I2 promote commits) renders as "-"; a populated value is shown with a leading arrow so the column reads as a transition target.
func ScopeMapFor ¶ added in v0.23.0
ScopeMapFor returns the authorize-opener → scope-entity map the history text renderer hands to RenderScopeChips, or nil when the loaded events carry no scope data. When nil, no row reads the map (an opener's own [scope: opened] chip renders from e.Scope), so the repo-wide cliutil.AuthorizeOpeners grep is skipped entirely — the E-0054 / M-0223 guard. A grep failure yields nil, so chips render "?" rather than blocking the verb, exactly as the old best-effort BuildScopeEntityMap did.
func ShortHash ¶
ShortHash returns the first 7 hex digits of a SHA, the conventional short form. Falls back to the full hash if it is shorter.
func SplitMultiValueTrailer ¶
SplitMultiValueTrailer splits a `git log %(trailers:key=..., valueonly=true,unfold=true)` cell into one entry per repeated trailer. Multi-value trailers (notably aiwf-scope-ends) are rendered newline-separated by git; we split, trim, and drop empty entries.
func StripTrailers ¶
StripTrailers removes the trailing trailer block from a commit body. `git log %(body)` includes everything after the subject and the separating blank line, including trailers; we only want the prose.
The heuristic walks backward through a contiguous run of trailer-shape `<Token>: <value>` lines at the end of the body. The run is only treated as a trailer block when (a) the run is preceded by a blank line or is the entire body, and (b) the run contains at least one `aiwf-*` trailer. The aiwf-* marker is what distinguishes real trailers (which we always emit) from body prose that happens to look like a trailer (e.g. "decided: 30 days" written by a human).
Types ¶
type HistoryEvent ¶
type HistoryEvent struct {
Date string `json:"date"`
Actor string `json:"actor"`
Verb string `json:"verb"`
Detail string `json:"detail"`
Commit string `json:"commit"`
Body string `json:"body,omitempty"`
To string `json:"to,omitempty"`
Force string `json:"force,omitempty"`
AuditOnly string `json:"audit_only,omitempty"`
Principal string `json:"principal,omitempty"`
OnBehalfOf string `json:"on_behalf_of,omitempty"`
AuthorizedBy string `json:"authorized_by,omitempty"`
Scope string `json:"scope,omitempty"`
ScopeEnds []string `json:"scope_ends,omitempty"`
Reason string `json:"reason,omitempty"`
Tests *gitops.TestMetrics `json:"tests,omitempty"`
}
HistoryEvent is one line of `aiwf history`. The JSON representation is the structured form callers consume.
Body carries the commit's free-form body — typically the human's `--reason` for a status transition, or empty when the verb wasn't invoked with one. Trailers are stripped before storage so Body is pure prose.
To is the target status of a `promote` event, extracted from the `aiwf-to:` trailer (added in I2). Empty for non-promote events and for pre-I2 promote commits that were written before the trailer schema landed; the renderer shows a dash for those rows.
Force is the reason value of an `aiwf-force:` trailer. Empty for non-forced transitions; non-empty marks the event as having bypassed the FSM's transition-legality rule.
AuditOnly is the reason value of an `aiwf-audit-only:` trailer (I2.5 G24 recovery mode). Empty for normal verb commits; non-empty marks the event as a backfilled audit trail for state that was reached via a manual commit. Renders as a `[audit-only: <reason>]` chip in text output, mirroring the `[forced: ...]` rendering.
Principal, OnBehalfOf, AuthorizedBy, Scope, ScopeEnds, Reason expose the I2.5 provenance trailer set. Principal is the human on whose authority the actor ran (always `human/<id>` when set); OnBehalfOf names the human inside whose scope the act lands; AuthorizedBy is the SHA of the authorize commit that opened the scope. Scope carries the lifecycle event for `aiwf authorize` commits (`opened` / `paused` / `resumed`); ScopeEnds is the slice of authorize-SHAs whose scopes the commit terminated (multiple ends per commit are allowed). Reason carries the free-text rationale from `aiwf-reason:`. All fields are empty for pre-I2.5 commits — the renderer treats absence as "no chip".
func EventFromCommit ¶ added in v0.23.0
func EventFromCommit(sha, authorDate, subject, body string, trailers []gitops.Trailer) (HistoryEvent, bool)
EventFromCommit builds a HistoryEvent from one commit's raw fields. sha is the full hash (shortened here to match ReadHistoryChain); authorDate is git's %aI; subject is %s; body is %B (the full raw message, from which the prose body %b is derived); trailers is the parsed trailer block.
Returns ok=false for the prose-mention false-positive ReadHistoryChain also drops (G30): a commit whose aiwf-entity trailer matched a grep but which carries neither aiwf-verb nor aiwf-actor is not a real entity event. Callers skip such commits rather than bucket a blank row.
func ReadHistory ¶
func ReadHistory(ctx context.Context, root, id string) ([]HistoryEvent, error)
ReadHistory shells out to `git log` and returns one HistoryEvent per commit whose `aiwf-entity:` or `aiwf-prior-entity:` trailer matches id. Events are returned oldest-first.
The git format string carries seven fields per record separated by the ASCII unit separator (\x1f), with the ASCII record separator (\x1e) between commits — none of these appear in subjects or trailers, so a single split suffices. Pre-I2 commits without `aiwf-to:` or `aiwf-force:` trailers produce empty strings for those fields; the renderer treats empty as "absent" and emits a dash, which is the load-bearing backwards-compat behavior.
For a bare milestone id (e.g. `M-007`), the query also matches composite-id trailers under that milestone (`M-007/AC-N`) so the milestone view shows its AC events alongside its own. The match is anchored on the literal `/` boundary so `M-007/` cannot prefix- match `M-070/`. A composite id queried directly (`M-007/AC-1`) matches only that AC's events.
func ReadHistoryChain ¶
ReadHistoryChain is readHistory's lineage-aware variant: it greps git log for any aiwf-entity / aiwf-prior-entity trailer matching any id in chain, dedupes by commit SHA, and returns a single oldest-first chronological slice. Used by `aiwf history <id>` after the cmd dispatcher has expanded id through prior_ids lineage. A single-element chain is the pre-G37 behavior; longer chains weave pre-rename and post-rename history into one timeline.