{
"$type": "com.whtwnd.blog.entry",
"theme": "github-light",
"title": "atprotoモデレーション概論 label編",
"content": "## atprotoモデレーション概論 label編\n\n[atprotoモデレーション概論 目次](https://whtwnd.com/did:plc:qi6xg6zplzivyu7zrylxuugk/3lb4xttmcxw2m)\n\natprotoのlabel仕様[^labeldef]について概説する。詳細は[公式ドキュメント](https://atproto.com/specs/label)参照。\n\n[^labeldef]: なお、ここでは[`com.atproto.label.defs#labelValueDefinition`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/com/atproto/label/defs.json#L78)には言及しない。atproto lexiconで定義されてはいるが、atproto仕様では言及が無い。中身としてもbskyに引っ張られていそうなので、bskyにおけるlabelの説明をする際に触れる。\n\n注意: 現在のlabel仕様(v1)はまだ若く、[将来の更新が示唆されている](https://github.com/bluesky-social/atproto/discussions/2885#discussioncomment-11000028)。\n\nここではlexicon/実装固有の話には触れないため、抽象度高めの話になる。一度軽く読み流して、必要になってから戻ってきてもいい。\n\n### labelデータ\n\natprotoは汎用のモデレーション機能としてlabelというものを持っている。これはアカウントやコンテンツに付随するメタデータで、例えば「botアカウント」「ブラクラ」「フェイクニュース」のような属性を表す。[^labelex]\n\n[^labelex]: 「成人向けコンテンツの警告もこれか」と思った人は少し待ってほしい。間違いではないのだが、厳密なことを言うとここはもう少し面倒な話がある。\n\nこの情報を誰が付けるか、どう扱うかはサービス依存となっている。ここではサービス非依存の仕様のみ説明し、Bluesky Socialの場合については別で説明する。\n\nlabelのデータ構造はatproto層で定義されており、特徴は以下の通り。\n\n* at-uri(≒recordかアカウント)を対象にとる\n* 発行者のDIDと最大128バイトの文字列(以後labal値と呼ぶ)で識別する\n * 異なる発行者の同名labelは区別される\n* 署名をlabel毎に付与する\n * label自体はrecordではなく、様々な場所に埋め込まれるため、repositoryの署名は使えない\n* 後から取り消しが可能\n * labelに有効期限をつける方法と、キャンセル(`neg`)labelを発行する方法がある\n\n特殊なlabel値として、`!takedown`と`!suspend`が定義されている。これらは、appviewによるtakedownと同様の効果が期待される。\\\n`!takedown`と`!suspend`の明確な差異は定義されていないが、`!suspend`は[アカウント単位でのみ適用することが想定されている](https://github.com/bluesky-social/atproto/pull/2309)模様。また、[一時的な停止であるというニュアンスも強そう](https://github.com/bluesky-social/atproto/discussions/2592#discussioncomment-11211051)。\n\n### labeler\n\nlabelの付け方は大別して2種類が想定されており、その一つがlabelerを使う方法である。\n\nlabelerはlabelを作成・配布する手段を持つサーバで、自身を表すDIDを持つ。エンドポイントやlabel署名の公開鍵はこのDIDドキュメントで指定される。\n\n一般的には、appviewがlabelerからlabelを取得し、APIレスポンスに反映する形で適用する。\n\nlabel配布の手段は自由で、相手に伝わりさえすればなんでもよい。標準として以下の2つのXRPC APIが定義されているが、必須ではない。\n\n* [subscribeLabels](https://github.com/bluesky-social/atproto/blob/main/lexicons/com/atproto/label/subscribeLabels.json): subscribeReposのようなsubscription APIで、発行したラベルをストリームする\n* [queryLabels](https://github.com/bluesky-social/atproto/blob/main/lexicons/com/atproto/label/queryLabels.json): 指定されたat-uriに付けたlabelを返す\n\nここから分かるように、標準では指定したlabelが付いた対象を列挙する方法は無い。が、誰かがsubscribeLabelsの出力を全部溜め込めば列挙することもできる。[^labelenum]\n\n[^labelenum]: この辺りの是非は少し[議論されている](https://github.com/bluesky-social/atproto/discussions/2581)。[プライベートブロックの検討記事](https://docs.bsky.app/blog/block-implementation)も参考になるかもしれない。\n\n#### クライアントによるlabeler指定\n\nlabelerはappview指定のものが自動的に適用される場合もありうるが、一般的にクライアントから指定することが想定されている。具体的には、クライアントがXRPC APIを呼ぶ際のHTTPヘッダで`atproto-accept-labelers`としてlabelerのDIDを指定する。\n\n`atproto-accept-labelers`が指定されたAPIを実行するサーバ(≒appview)は、そのlabelerからlabelを取得し、サービス毎の方法で適用することが期待される。\n\nただし、`!takedown`と`!suspend`に関しては、単に`atproto-accept-labelers`で指定しただけでは反映されない。これらの特殊なlabelも反映したい場合は、labeler指定時に`redact`パラメータを付ける必要がある。[^redactheader]\n\n[^redactheader]: 任意で`redact`を付けられるクライアントも、`!takedown`/`!suspend`を発行するサードパーティlabelerも、実際に見たことはない。\n\n`atproto-accept-labelers`は複数形になっていることからも分かるように、複数labelerを指定できる。とはいえ、無制限に許してしまうと処理が大変だし、その全てが利用可能か分からない。appviewがlabeler非対応の可能性だってある。ということで、実際に適用されたlablerはレスポンスの`atproto-content-labelers`ヘッダから確認できる。\n\n### self label\n\nもう一つのlabel発行方法は、recordの持ち主がrecordにlabelを埋め込むこと。これをself labelと呼ぶ。\n\nself labelは、recordの適当なフィールドに[`com.atproto.label.defs#selfLabel`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/com/atproto/label/defs.json#L66)を含めることで実現する。\\\n[普通のlabel](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/com/atproto/label/defs.json#L5)と違い、`selfLabel`は文字列しか持たない。発行者はrecordの持ち主であることが自明だし、署名もrepositoryの署名があるため、label用にわざわざ書く必要が無いというだけで、情報量としてはほぼ変わらない。\n\nself label可否やフィールド名はcollection毎に異なるため、各lexiconを参照すること。self labelの扱いがappviewやクライアントに依存するのは普通のlabelと同じ。\n\n### Next\n\n[bsky編](https://whtwnd.com/did:plc:qi6xg6zplzivyu7zrylxuugk/3lb4y44tpp62u)",
"createdAt": "2024-11-18T01:45:55.220Z",
"visibility": "public"
}