Files
hzhang 5cf4302d50 refactor(backend): drop backend-driven Fabric broadcast — agent-driven model
The backend no longer broadcasts topic lifecycle events to Fabric. The
new model (per design discussion 2026-05-23 evening):

  - Proposing agent posts a single recruitment fabric-send-message
    immediately after creating a topic (carries topic_id + signup
    window + debate window + title).
  - Downstream agents that decide to participate book a HF on_call
    slot covering the debate window via `hf calendar schedule on_call
    <time> <duration> --job DEBATE-<topic_id>`.
  - HF wakes the agent naturally at slot start; the wake payload
    carries event_data with the DEBATE-<topic_id> code so the agent
    knows why it was woken.
  - The backend stays a pure data + state-machine service and doesn't
    know about Fabric.

Code removed:

  - internal/fabric/announce.go (entire file + empty dir)
  - ticker.go: broadcastLifecycle + broadcastAnnouncement + topicTarget
    helpers; announcer field on Ticker; announce field/arg on NewTicker
  - models/topic.go: AnnounceGuildBaseURL + AnnounceChannelID fields
  - store/topic_store.go: same fields on CreateTopicInput + INSERT
  - handlers/topics.go: same fields on createTopicBody + validation +
    parameter passing to store
  - handlers/verdict.go: announcer field + lifecycle broadcast on
    verdict submit
  - config/config.go: FabricSystemAPIKey field + DIALECTIC_FABRIC_SYSTEM_API_KEY
    env read
  - main.go + routes.go: announcer wiring

Database:

  - migrations/003_drop_topic_announce_target.sql drops the two columns
    added by migration 002. Counterpart commit on the deployment side
    needs DIALECTIC_FABRIC_SYSTEM_API_KEY env removed from
    docker-compose.yml; harmless if left as the backend no longer
    reads it.

Pairs with:
  - Dialectic.OpenclawPlugin: rip announce_* params from
    dialectic_propose_topic (next commit)
  - Fabric.Backend.Center: rip serviceEndpoint field + cli
  - Fabric.Backend.Guild: rip system-key bypass on ApiKeyGuard and
    announce-only-system limit on messaging.controller
  - ClawSkills: rewrite participate-debate + analyze-intel step 4 +
    delete rotate-fabric-system-key workflow
2026-05-23 23:45:22 +01:00

79 lines
2.5 KiB
Go

package models
import (
"encoding/json"
"time"
)
type Visibility string
const (
VisibilityPublic Visibility = "public"
VisibilityPrivate Visibility = "private"
)
type TopicStatus string
const (
TopicStatusCreated TopicStatus = "created"
TopicStatusSignupOpen TopicStatus = "signup_open"
TopicStatusSignupClosed TopicStatus = "signup_closed"
TopicStatusDebating TopicStatus = "debating"
TopicStatusCompleted TopicStatus = "completed"
TopicStatusCancelled TopicStatus = "cancelled"
)
type Camp string
const (
CampPro Camp = "pro"
CampCon Camp = "con"
CampJudge Camp = "judge"
)
// AllCamps is the canonical iteration order used by the allocation algorithm.
var AllCamps = [3]Camp{CampPro, CampCon, CampJudge}
type Topic struct {
ID string `db:"id" json:"id"`
Title string `db:"title" json:"title"`
Summary string `db:"summary" json:"summary"`
Visibility Visibility `db:"visibility" json:"visibility"`
VerdictSchemaID string `db:"verdict_schema_id" json:"verdict_schema_id"`
Status TopicStatus `db:"status" json:"status"`
SignupOpenAt time.Time `db:"signup_open_at" json:"signup_open_at"`
SignupCloseAt time.Time `db:"signup_close_at" json:"signup_close_at"`
DebateStartAt time.Time `db:"debate_start_at" json:"debate_start_at"`
DebateEndAt time.Time `db:"debate_end_at" json:"debate_end_at"`
CreatorUserID string `db:"creator_user_id" json:"creator_user_id"`
VisibilityChangedBy *string `db:"visibility_changed_by" json:"visibility_changed_by,omitempty"`
VisibilityChangedAt *time.Time `db:"visibility_changed_at" json:"visibility_changed_at,omitempty"`
CancelledReason *string `db:"cancelled_reason" json:"cancelled_reason,omitempty"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
// IsCampValid returns true iff c is one of pro|con|judge.
func IsCampValid(c Camp) bool {
for _, k := range AllCamps {
if k == c {
return true
}
}
return false
}
// SignupCampsJSON is a typed wrapper around the JSON-stored willing_camps
// column. We marshal/unmarshal at the boundary so handlers can work with
// the typed slice.
type SignupCampsJSON []Camp
func (s SignupCampsJSON) Marshal() ([]byte, error) { return json.Marshal(s) }
func (s *SignupCampsJSON) UnmarshalDB(raw []byte) error {
if len(raw) == 0 {
*s = nil
return nil
}
return json.Unmarshal(raw, s)
}