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
This commit is contained in:
h z
2026-05-23 23:45:22 +01:00
parent 22d9fb7ed5
commit 5cf4302d50
10 changed files with 76 additions and 343 deletions

View File

@@ -109,12 +109,6 @@ type createTopicBody struct {
SignupCloseAt string `json:"signup_close_at"`
DebateStartAt string `json:"debate_start_at"`
DebateEndAt string `json:"debate_end_at"`
// Optional: per-topic announce-channel target. Both must be set
// (or both omitted = no broadcasts). Creator picks based on the
// debate's intended audience (different guilds may host different
// communities, different channels may serve different categories).
AnnounceGuildBaseURL string `json:"announce_guild_base_url,omitempty"`
AnnounceChannelID string `json:"announce_channel_id,omitempty"`
}
// POST /api/topics
@@ -147,31 +141,16 @@ func (h *TopicsHandler) Create(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Announce target: both fields required or both empty; one-of-two
// is a config error caught here rather than silently treated as
// "no broadcast".
var aGuild, aChannel *string
if body.AnnounceGuildBaseURL != "" || body.AnnounceChannelID != "" {
if body.AnnounceGuildBaseURL == "" || body.AnnounceChannelID == "" {
http.Error(w, "announce_guild_base_url and announce_channel_id must both be set (or both empty for no broadcasts)", http.StatusBadRequest)
return
}
g, c := body.AnnounceGuildBaseURL, body.AnnounceChannelID
aGuild, aChannel = &g, &c
}
created, err := h.store.Create(r.Context(), store.CreateTopicInput{
Title: body.Title,
Summary: body.Summary,
Visibility: models.Visibility(body.Visibility),
VerdictSchemaID: body.VerdictSchemaID,
SignupOpenAt: parsed[0],
SignupCloseAt: parsed[1],
DebateStartAt: parsed[2],
DebateEndAt: parsed[3],
CreatorUserID: caller.ID,
AnnounceGuildBaseURL: aGuild,
AnnounceChannelID: aChannel,
Title: body.Title,
Summary: body.Summary,
Visibility: models.Visibility(body.Visibility),
VerdictSchemaID: body.VerdictSchemaID,
SignupOpenAt: parsed[0],
SignupCloseAt: parsed[1],
DebateStartAt: parsed[2],
DebateEndAt: parsed[3],
CreatorUserID: caller.ID,
})
if err != nil {
http.Error(w, "create failed: "+err.Error(), http.StatusInternalServerError)