hzhang a43ff2de62 feat: per-topic announce target (move guild+channel from env to topic row)
Operator decision: backend env hard-coding a single guild/channel was
wrong because (a) one Center can host many guilds and (b) one guild
can have many announce channels for different purposes. The
proposing agent now chooses where this topic's lifecycle events go,
passed as create-topic params and stored on the topic row.

Schema migration 002:
- ALTER topics ADD announce_guild_base_url VARCHAR(255) NULL,
                  announce_channel_id     VARCHAR(64)  NULL.
- Both nullable; one-of-two is rejected at POST time; both null =
  topic creator opted out of broadcasts (announcer skips with log).

handlers/topics.go: createTopicBody adds announce_guild_base_url +
announce_channel_id; validates both-or-neither.

fabric/announce.go: rewritten signature. NewAnnouncer takes only
the system api key. PostTopicAnnouncement + PostLifecycleEvent take
a Target {GuildBaseURL, ChannelID} per call. Zero-value Target -> skip.

orchestrator/ticker.go: new helper topicTarget(topic) extracts the
target from the topic row; all broadcasts route through it.

verdict.go: same per-topic target extraction at completion.

config: removed FabricGuildBaseURL, FabricAnnounceChannelID,
FabricBotBearerToken from the Config struct + env reads.
FabricSystemAPIKey env renamed to DIALECTIC_FABRIC_SYSTEM_API_KEY
to disambiguate from the Fabric backend's own
FABRIC_BACKEND_GUILD_SYSTEM_API_KEY (operator: paste the same value
into both - one says "I am the system caller", the other says "I
accept this caller as system").

FABRIC_BOT_BEARER_TOKEN is gone entirely. The upgraded Guild
ApiKeyGuard accepts x-fabric-system-key alone for announce posts;
no per-user Bearer needed. Pairs with the matching change on
nav/Fabric.Backend.Guild commit 985b06a.
2026-05-23 17:53:30 +01:00

Dialectic.Backend — v2 (Go)

Greenfield Go rewrite of the Python v1 backend. Agent-native debate platform per /home/hzhang/arch/DIALECTIC-V2-DESIGN.md.

Python v1 history is preserved on branch archive/python-v1.

What's here (Phase 2A + 2B + 2C, 2026-05-23)

Subsystem Status
HTTP server (chi router)
Config from env (internal/config)
MySQL via sqlx + embedded SQL migrations
Schema: topics, signups, camps, rounds, arguments, verdicts, agent_keys, system_keys, verdict_schemas
Auth middlewares: agent bearer (real), OIDC browser (Phase 2 stub w/ dev bypass)
/api/healthz
/api/topics list / get / create / set-visibility
/api/topics/{id}/signups list / create (agent self-enroll)
Orchestration engine (camp allocation, round driver, judge invocation) Phase 2D
SSE live transcripts Phase 2D
Full OIDC + Keycloak JWKS verification Phase 4
Nginx + CF Origin Cert on server.t3 Phase 2E

Layout

main.go                              entrypoint (load → wire → serve)
go.mod
Dockerfile
docker-compose.dev.yml               backend + mysql for local iteration
internal/
  config/                            12-factor env loader
  db/
    db.go                            sqlx + embedded migration runner
    migrations/001_init.sql          v2 schema, idempotent
  models/                            entity types (sqlx + json tags)
  store/                             query layer (per-entity)
  auth/                              agent api-key + oidc middlewares
  httpapi/
    routes.go                        chi router + auth chains
    handlers/                        per-endpoint handlers

Run locally

docker compose -f docker-compose.dev.yml up --build
# backend on http://localhost:8090
curl http://localhost:8090/api/healthz

Env vars (see internal/config/config.go for the full list):

Var Default (dev) Required in prod
ENV_MODE dev must be prod
HTTP_ADDR 0.0.0.0:8090
CORS_ALLOW_ORIGINS * concrete list (no *)
DB_HOST/PORT/NAME/USER/PASSWORD dev defaults ✓ password required
AGENT_API_KEY_PEPPER
OIDC_ISSUER / OIDC_CLIENT_ID
OIDC_DEV_BYPASS_TOKEN unset ignored in prod
SYSTEM_API_KEY unset populate when announce-channel push lands

Dev bypass for browser routes

In ENV_MODE=dev with OIDC_DEV_BYPASS_TOKEN=<token> set:

curl -H "x-dev-bypass: <token>" http://localhost:8090/api/topics
# attached as user 'dev-operator' with role 'dialectic-admin'

In prod, this header is ignored regardless of value.

Agent bearer for plugin routes

The OpenClaw plugin (Dialectic.OpenclawPlugin, Phase 3) calls with:

Authorization: Bearer <raw-agent-api-key>

The key is hashed with AGENT_API_KEY_PEPPER and matched against agent_keys.key_hash. To provision an agent's key (Phase 3 will add a proper hf user create-dialectic-key CLI; for now, manual SQL):

INSERT INTO agent_keys (agent_id, key_hash)
VALUES ('manager', SHA2(CONCAT('<pepper>:', '<raw>'), 256));

What's next

  • Phase 2D: camp allocation algorithm + round driver + judge invocation. Wired to Fabric announce channel (via system-api-key) + the Dialectic.OpenclawPlugin's tool for agent argument submission.
  • Phase 2E: nginx config + CF Origin Cert + deploy to server.t3.
  • Phase 3: Dialectic.OpenclawPlugin — agent-facing tools.
  • Phase 4: frontend rewrite (STYLE.md + real Keycloak OIDC + visibility toggle UI).
  • Phase 5: end-to-end integration with analyze-intel workflow.
Description
No description provided
Readme 252 KiB
Languages
Go 99.2%
Dockerfile 0.8%