Add type: event for state transitions (bd-ecmd)

Adds TypeEvent issue type for recording operational state changes as
immutable beads. Events capture:
- event_category: namespaced category (e.g., patrol.muted, agent.started)
- event_actor: entity URI who caused the event
- event_target: entity URI or bead ID affected
- event_payload: event-specific JSON data

Changes:
- Add TypeEvent constant and IsValid() support in types.go
- Add event fields to Issue struct with ComputeContentHash support
- Add --event-category/actor/target/payload flags to bd create
- Add event fields to RPC CreateArgs and UpdateArgs
- Add migration 033_event_fields to add columns to issues table
- Update insertIssue and queries to include event fields
- Fix migrations_test.go for new column requirements

This enables:
- bd activity --follow showing events
- bd list --type=event --target=agent:deacon
- Full audit trail for operational state
- HOP-compatible transaction records

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-30 16:13:39 -08:00
parent aa2c66c4f7
commit 21a0ff6d0d
5 changed files with 109 additions and 65 deletions

File diff suppressed because one or more lines are too long

View File

@@ -130,6 +130,17 @@ var createCmd = &cobra.Command{
FatalError("--role-type and --agent-rig flags require --type=agent") FatalError("--role-type and --agent-rig flags require --type=agent")
} }
// Event-specific flags
eventCategory, _ := cmd.Flags().GetString("event-category")
eventActor, _ := cmd.Flags().GetString("event-actor")
eventTarget, _ := cmd.Flags().GetString("event-target")
eventPayload, _ := cmd.Flags().GetString("event-payload")
// Validate event-specific flags require --type=event
if (eventCategory != "" || eventActor != "" || eventTarget != "" || eventPayload != "") && issueType != "event" {
FatalError("--event-category, --event-actor, --event-target, and --event-payload flags require --type=event")
}
// Handle --rig or --prefix flag: create issue in a different rig // Handle --rig or --prefix flag: create issue in a different rig
// Both flags use the same forgiving lookup (accepts rig names or prefixes) // Both flags use the same forgiving lookup (accepts rig names or prefixes)
targetRig := rigOverride targetRig := rigOverride
@@ -273,6 +284,10 @@ var createCmd = &cobra.Command{
MolType: string(molType), MolType: string(molType),
RoleType: roleType, RoleType: roleType,
Rig: agentRig, Rig: agentRig,
EventCategory: eventCategory,
EventActor: eventActor,
EventTarget: eventTarget,
EventPayload: eventPayload,
} }
resp, err := daemonClient.Create(createArgs) resp, err := daemonClient.Create(createArgs)
@@ -322,6 +337,10 @@ var createCmd = &cobra.Command{
MolType: molType, MolType: molType,
RoleType: roleType, RoleType: roleType,
Rig: agentRig, Rig: agentRig,
EventKind: eventCategory,
Actor: eventActor,
Target: eventTarget,
Payload: eventPayload,
} }
ctx := rootCtx ctx := rootCtx
@@ -503,7 +522,7 @@ func init() {
createCmd.Flags().String("title", "", "Issue title (alternative to positional argument)") createCmd.Flags().String("title", "", "Issue title (alternative to positional argument)")
createCmd.Flags().Bool("silent", false, "Output only the issue ID (for scripting)") createCmd.Flags().Bool("silent", false, "Output only the issue ID (for scripting)")
registerPriorityFlag(createCmd, "2") registerPriorityFlag(createCmd, "2")
createCmd.Flags().StringP("type", "t", "task", "Issue type (bug|feature|task|epic|chore|merge-request|molecule|gate|agent|role|convoy)") createCmd.Flags().StringP("type", "t", "task", "Issue type (bug|feature|task|epic|chore|merge-request|molecule|gate|agent|role|convoy|event)")
registerCommonIssueFlags(createCmd) registerCommonIssueFlags(createCmd)
createCmd.Flags().StringSliceP("labels", "l", []string{}, "Labels (comma-separated)") createCmd.Flags().StringSliceP("labels", "l", []string{}, "Labels (comma-separated)")
createCmd.Flags().StringSlice("label", []string{}, "Alias for --labels") createCmd.Flags().StringSlice("label", []string{}, "Alias for --labels")
@@ -523,6 +542,11 @@ func init() {
// Agent-specific flags (only valid when --type=agent) // Agent-specific flags (only valid when --type=agent)
createCmd.Flags().String("role-type", "", "Agent role type: polecat|crew|witness|refinery|mayor|deacon (requires --type=agent)") createCmd.Flags().String("role-type", "", "Agent role type: polecat|crew|witness|refinery|mayor|deacon (requires --type=agent)")
createCmd.Flags().String("agent-rig", "", "Agent's rig name (requires --type=agent)") createCmd.Flags().String("agent-rig", "", "Agent's rig name (requires --type=agent)")
// Event-specific flags (only valid when --type=event)
createCmd.Flags().String("event-category", "", "Event category (e.g., patrol.muted, agent.started) (requires --type=event)")
createCmd.Flags().String("event-actor", "", "Entity URI who caused this event (requires --type=event)")
createCmd.Flags().String("event-target", "", "Entity URI or bead ID affected (requires --type=event)")
createCmd.Flags().String("event-payload", "", "Event-specific JSON data (requires --type=event)")
// Note: --json flag is defined as a persistent flag in main.go, not here // Note: --json flag is defined as a persistent flag in main.go, not here
rootCmd.AddCommand(createCmd) rootCmd.AddCommand(createCmd)
} }

View File

@@ -102,6 +102,11 @@ type CreateArgs struct {
// Agent identity fields (only valid when IssueType == "agent") // Agent identity fields (only valid when IssueType == "agent")
RoleType string `json:"role_type,omitempty"` // polecat|crew|witness|refinery|mayor|deacon RoleType string `json:"role_type,omitempty"` // polecat|crew|witness|refinery|mayor|deacon
Rig string `json:"rig,omitempty"` // Rig name (empty for town-level agents) Rig string `json:"rig,omitempty"` // Rig name (empty for town-level agents)
// Event fields (only valid when IssueType == "event")
EventCategory string `json:"event_category,omitempty"` // Namespaced category (e.g., patrol.muted, agent.started)
EventActor string `json:"event_actor,omitempty"` // Entity URI who caused this event
EventTarget string `json:"event_target,omitempty"` // Entity URI or bead ID affected
EventPayload string `json:"event_payload,omitempty"` // Event-specific JSON data
} }
// UpdateArgs represents arguments for the update operation // UpdateArgs represents arguments for the update operation
@@ -142,6 +147,11 @@ type UpdateArgs struct {
// Agent identity fields // Agent identity fields
RoleType *string `json:"role_type,omitempty"` // polecat|crew|witness|refinery|mayor|deacon RoleType *string `json:"role_type,omitempty"` // polecat|crew|witness|refinery|mayor|deacon
Rig *string `json:"rig,omitempty"` // Rig name (empty for town-level agents) Rig *string `json:"rig,omitempty"` // Rig name (empty for town-level agents)
// Event fields (only valid when IssueType == "event")
EventCategory *string `json:"event_category,omitempty"` // Namespaced category (e.g., patrol.muted, agent.started)
EventActor *string `json:"event_actor,omitempty"` // Entity URI who caused this event
EventTarget *string `json:"event_target,omitempty"` // Entity URI or bead ID affected
EventPayload *string `json:"event_payload,omitempty"` // Event-specific JSON data
} }
// CloseArgs represents arguments for the close operation // CloseArgs represents arguments for the close operation

View File

@@ -122,6 +122,19 @@ func updatesFromArgs(a UpdateArgs) map[string]interface{} {
if a.Rig != nil { if a.Rig != nil {
u["rig"] = *a.Rig u["rig"] = *a.Rig
} }
// Event fields
if a.EventCategory != nil {
u["event_category"] = *a.EventCategory
}
if a.EventActor != nil {
u["event_actor"] = *a.EventActor
}
if a.EventTarget != nil {
u["event_target"] = *a.EventTarget
}
if a.EventPayload != nil {
u["event_payload"] = *a.EventPayload
}
return u return u
} }
@@ -208,6 +221,11 @@ func (s *Server) handleCreate(req *Request) Response {
// Agent identity fields // Agent identity fields
RoleType: createArgs.RoleType, RoleType: createArgs.RoleType,
Rig: createArgs.Rig, Rig: createArgs.Rig,
// Event fields (map protocol names to internal names)
EventKind: createArgs.EventCategory,
Actor: createArgs.EventActor,
Target: createArgs.EventTarget,
Payload: createArgs.EventPayload,
} }
// Check if any dependencies are discovered-from type // Check if any dependencies are discovered-from type

View File

@@ -680,6 +680,11 @@ var allowedUpdateFields = map[string]bool{
"rig": true, "rig": true,
// Molecule type field // Molecule type field
"mol_type": true, "mol_type": true,
// Event fields
"event_category": true,
"event_actor": true,
"event_target": true,
"event_payload": true,
} }
// validatePriority validates a priority value // validatePriority validates a priority value