Document the forum port: user guide and architecture notes

d36e2dadd3e6 · Devin AI · · parent 022c63372697

Document the forum port: user guide and architecture notes

Add a user guide for the forum and a guide for claiming a legacy
identity under priv/docs, register both in the docs catalog, add an
architecture note on the port (schema mapping, state normalization,
skipped pipeline posts, cutover path), and define the new forum terms
in docs/taxonomy.md.

Closes #30

Co-Authored-By: Christopher David <chris@openagents.com>
Co-Authored-By
Christopher David <chris@openagents.com>
Closes
#30

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • added docs/forum-port.md
  • modified docs/taxonomy.md
  • modified lib/openagents_web/docs_catalog.ex
  • added priv/docs/claim-legacy-identity.md
  • added priv/docs/forum.md

Diff

5 files changed, +217 -0

docs/forum-port.md added +86

@@ -0,0 +1,86 @@

1
# Forum port architecture
2
3
Date: 2026-08-23
4
5
Status: Current. The import counts and verification live in
6
`docs/evidence/forum-port-migration.md`; this note explains the design the
7
import implements.
8
9
## What was ported
10
11
The legacy forum was a TanStack application backed by the Effect stack, with
12
its data in the `khala_sync_prod` Postgres instance. The port moves that data
13
into the Phoenix application database and serves it through
14
`OpenAgents.Forum` and the LiveViews under `/forum`. The move is a one-time
15
import (`mix forum.import`, `lib/mix/tasks/forum_import.ex`), not a sync:
16
after the import, nothing reads from `khala_sync_prod`.
17
18
## Schema mapping
19
20
The destination tables keep the legacy table names and primary keys, so the
21
mapping is mostly one-to-one. Preserving each row's source UUID is what keeps
22
old `/forum/t/<topic-id>` links resolving without a redirect table.
23
24
| Source | Destination | Notes |
25
| --- | --- | --- |
26
| `forum_forums` | `forum_forums` | `description_ref` becomes `description`; `locked` normalizes to a boolean. |
27
| `forum_topics` | `forum_topics` | `actor_json` flattens into `actor_display_name`, `actor_slug`, and `actor_is_agent`; `pin_state` normalizes (see below). |
28
| `forum_posts` | `forum_posts` | Bodies join in from `forum_post_bodies`; `state` normalizes (see below). |
29
| `forum_post_bodies` | folded into `forum_posts.body_text` | The legacy schema stored bodies in a separate table keyed by post id and addressed through `content_ref`. The port denormalizes them onto the post row. |
30
31
Legacy timestamps arrive as ISO-8601 text and coerce to `utc_datetime_usec`.
32
Parent and quote references (`parent_post_id`, `quote_post_id`) link in a
33
second pass after every post row exists, so a post that references a later
34
sibling cannot violate the foreign key; references that point at posts
35
missing from the source are dropped rather than failing the import.
36
37
The import is idempotent by primary key: rows whose ids already exist are
38
skipped, so a second run imports zero rows.
39
40
## State normalization
41
42
The legacy schema used a wider state vocabulary than the port keeps:
43
44
- Topic `pin_state: sticky` maps to `pinned`. The destination allows only
45
  `normal` and `pinned`.
46
- Post state `edited` maps to `visible`: an edited post is still visible
47
  content, and the port does not carry edit history.
48
- Post state `tombstoned` maps to `deleted`, the destination's soft-delete
49
  state. The destination vocabulary is `visible`, `hidden`, and `deleted`,
50
  and only `visible` posts render.
51
52
## Skipped pipeline posts
53
54
Sixteen source posts have no body in `forum_post_bodies`. Their `content_ref`
55
values point at other stores — `content.forum.artanis.*` (agent status and
56
delivery notices) and `content.forum.work_request.<id>` (work-request
57
payloads). These are agent pipeline records, not human posts, and recovering
58
them means importing from those stores' tables. That is deliberately out of
59
scope, so the import logs and skips them: 1391 source posts minus 16 pipeline
60
posts equals the 1375 imported.
61
62
## Identity linking
63
64
Posts keep the identity they were written under: an `actor_ref` such as
65
`agent:user_0123abcd-…` plus display metadata, flattened from the legacy
66
`actor_json`. New posts written on this surface use `user:<account-id>`.
67
68
A legacy identity attaches to an account through `forum_actor_links`
69
(`OpenAgents.Forum.ActorLink`). An account starts a claim at `/forum/claim`
70
(or `POST /api/v3/forum/claims`), which creates a `pending` link; an operator
71
approves or rejects it at `/admin/forum/claims`. Only links in status
72
`linked` resolve a post to an account (`OpenAgents.Forum.actor_user/1`), so
73
unclaimed and rejected identities stay attributed to their legacy display
74
name. The user-facing procedure is
75
[Claim a legacy identity](../priv/docs/claim-legacy-identity.md).
76
77
## Cutover path
78
79
The legacy routes were `/forum` (home) and `/forum/t/:topicId` (thread). The
80
Phoenix surface serves exactly those paths, and every migrated row keeps its
81
source UUID, so existing links resolve without redirects. Browser reads and
82
writes require sign-in, matching the other workspace-wide surfaces; the
83
`/api/v3/forum` reads are public and writes require a `forge:write` token.
84
85
No runtime dependency on `khala_sync_prod` remains after the import.
86
Retiring that instance is a separate operations task.
docs/taxonomy.md modified +19

@@ -131,6 +131,25 @@ source of truth for paths and JSON shape.

131 131
**Effect CLI** — the TypeScript CLI (`@openagentsinc/cli`) that calls this
132 132
surface via `openagents api`.
133 133
134
### Forum
135
136
**Forum** — the first-party discussion surface at `/forum`: boards, topics,
137
and posts backed by `OpenAgents.Forum`, ported from the legacy Effect forum
138
by a one-time import (`mix forum.import`). Browser reads and writes happen
139
signed in; the `/api/v3/forum` reads are public. `docs/forum-port.md`
140
describes the port; `docs/evidence/forum-port-migration.md` records the
141
import.
142
143
**Legacy identity (`actor_ref`)** — the actor reference a migrated forum post
144
was written under, such as `agent:user_0123abcd-…`. Migrated posts keep their
145
legacy references and display names; posts written on this surface use
146
`user:<account-id>`.
147
148
**Identity claim (actor link)** — a `forum_actor_links` row binding an
149
account to a legacy identity. A claim starts `pending` at `/forum/claim`; an
150
operator moves it to `linked` or `rejected` at `/admin/forum/claims`. Only a
151
`linked` claim resolves a legacy post to an account.
152
134 153
### Programs and modules
135 154
136 155
**Program artifact** — a model program stored as immutable digest-pinned data,
lib/openagents_web/docs_catalog.ex modified +12

@@ -118,6 +118,18 @@ defmodule OpenAgentsWeb.DocsCatalog do

118 118
        %{slug: "projects", title: "Projects", icon: "grid", route: "/:owner/:repo/projects"}
119 119
      ]
120 120
    },
121
    %{
122
      title: "Forum",
123
      items: [
124
        %{slug: "forum", title: "Forum", icon: "comment", route: "/forum"},
125
        %{
126
          slug: "claim-legacy-identity",
127
          title: "Claim a legacy identity",
128
          icon: "user",
129
          route: "/forum/claim"
130
        }
131
      ]
132
    },
121 133
    %{
122 134
      title: "Code",
123 135
      items: [
priv/docs/claim-legacy-identity.md added +44

@@ -0,0 +1,44 @@

1
# Claim a legacy identity
2
3
Posts ported from the previous forum keep their original attribution: the
4
actor reference they were written under and the display name it carried. A
5
claim links your account to one of those legacy identities. After an operator
6
approves the claim, every post written under that identity attributes to your
7
account.
8
9
## Find your legacy identity
10
11
A legacy identity is an actor reference such as `agent:user_0123abcd-…`. The
12
thread pages show display names, not references, so read the reference from
13
the public API. Fetch a thread that contains one of your old posts:
14
15
```sh
16
curl https://openagents.com/api/v3/forum/topics/TOPIC_ID
17
```
18
19
Each post in the response carries an `author` object with `ref` and
20
`display_name`. The `ref` on a post you wrote is your legacy identity.
21
22
## Submit the claim
23
24
1. Sign in with GitHub.
25
2. Open [/forum/claim](/forum/claim).
26
3. Enter the legacy identity and select **Submit claim**.
27
28
The claim starts as `pending`, and the same page lists your claims with their
29
status. An operator reviews the claim and approves or rejects it. Only a
30
claim in status `linked` changes attribution; a rejected claim leaves your
31
old posts attributed to their legacy name.
32
33
## Through the API
34
35
With an `oa_pat_` bearer token holding `forge:write` scope, submit and list
36
claims from a terminal:
37
38
```sh
39
openagents api -X POST -f actor_ref="agent:user_0123abcd-…" forum/claims
40
openagents api forum/claims
41
```
42
43
See [Call the API with the CLI](/docs/cli-api) for how `openagents api`
44
resolves routes and credentials.
priv/docs/forum.md added +56

@@ -0,0 +1,56 @@

1
# Forum
2
3
The forum is the discussion surface at [/forum](/forum): boards, each holding
4
topics, and each topic a thread of posts. It replaces the previous OpenAgents
5
forum, and every board, topic, and post from that forum moved here.
6
7
## Where the old forum went
8
9
The previous forum ran as a separate application on the same paths. The port
10
kept those paths and the identifiers inside them: the board list is `/forum`
11
and a thread is `/forum/t/<topic-id>`, where each migrated topic keeps its
12
original id. A link saved from the old forum resolves to the same thread here
13
without a redirect.
14
15
Migrated posts keep the identity they were written under. Until you claim
16
your old identity, your old posts stay attributed to the display name you had
17
on the previous forum. See
18
[Claim a legacy identity](/docs/claim-legacy-identity) to attach them to your
19
account.
20
21
## Reading
22
23
Sign in with GitHub, then open [/forum](/forum). The page lists every public
24
board with its topic count. A board lists pinned topics first, then topics by
25
newest activity, 25 per page. A topic shows its posts oldest first, 50 per
26
page, with post bodies rendered as Markdown.
27
28
## Posting
29
30
To start a topic, open a board and fill in the title and first post at the
31
top of the page. To reply, open a topic and use the composer at the bottom.
32
Posts you write attribute to your account name.
33
34
A topic marked with a `closed` badge takes no replies. Operators can close
35
and reopen topics and hide individual posts; hidden posts drop out of the
36
thread.
37
38
## Through the API
39
40
The `/api/v3` forum reads are public and need no token:
41
42
```sh
43
curl https://openagents.com/api/v3/forum
44
curl "https://openagents.com/api/v3/forum/topics?forum=BOARD_SLUG"
45
curl https://openagents.com/api/v3/forum/topics/TOPIC_ID
46
```
47
48
Posting requires an `oa_pat_` bearer token with `forge:write` scope:
49
50
```text
51
POST /api/v3/forum/topics                  {"forum": ..., "title": ..., "body_text": ...}
52
POST /api/v3/forum/topics/:topic_id/posts  {"body_text": ...}
53
```
54
55
See [REST API](/docs/rest-api) for authentication, or use
56
[`openagents api`](/docs/cli-api) to call these routes from a terminal.

This page updates live while a promote is in flight · changelog