API¶
以下の仕様は実装からビルド時に生成している。サーバの実際の振る舞いと食い違うことが ない。
稼働中のインスタンスも同じものを出す。Swagger UI は /api/docs、ReDoc は
/api/redoc、生の文書は /api/openapi.json。
役割ごとに開く口¶
minter に解決の口は無く、resolver に採番の口も無い。 別プロセスなので別々に スケールでき、resolver は読み取り専用のレプリカに向けられる。
スキーマからは読み取れないこと¶
採番は、頼めば冪等になる。 request_id を付けて送れば、同じ要求の再送には
前回と同じ ARK が返り、新しくは採らない。万オーダーの投入では途中で接続が切れる
ほうが普通で、控えが無いまま再送すると誰も指していない識別子が増える——ARK は
それを回収できない。
リクエストの shoulder は範囲を広げない。 省略すれば組織の既定が使われ、名指した 場合に問われるのは「それが自分の範囲の内側かどうか」だけ。
委譲された shoulder は 307 を返す。プロキシはしない。 その名前空間の採番が外で 行われるなら、行き先を教える。代理で呼ばないのは、応答が失われたときに 向こうでは採番されたがこちらは知らない ARK が生まれるから。
公開前に採ることができ、そのあいだは消せる。 POST /api/mint に
{"reserve": true} を付けると、その ARK は公開のリゾルバでは解決しない(知らない
名前と同じに答える。閉域のリゾルバは ARKHE_RESOLVE_UNPUBLISHED で解決する)。POST /api/publish で公開すると解決を始め、以後は削除できない
——POST /api/delete は 409 で断る。取り下げた名前は二度と採られない。
壊さないものを参照。
公開は取り下げられ、出し直せる。 POST /api/unpublish(ark:unpublish)は行を
残したまま解決を止め、POST /api/publish で戻す。どちらも呼ぶ側の届く範囲の
内側だけ——組織は自分の shoulder、NAAN 管理者は自 NAAN。一度でも外に出した名前
なので、取り下げには reason と、ARK を打ち直す confirm が要る。削除も同じで、
一度でも公開したものには同じ 2 つを要求する。重さは名前の履歴に従い、呼ぶ側の位
には従わない。
捨てる一括は 1 リクエストで済む。 POST /api/delete/bulk(ark:delete)は
一度も公開していない ARK をまとめて消す——予約でまとめて採るのが実際の使い方である
以上、捨てるのも採るのと同じ安さでなければ、放置された番号が台帳に残るだけになる。
公開中、または一度でも公開した行が 1 つでもあれば、リクエスト全体が 409 で落ちる。
その名前は POST /api/delete を 1 件で通す——理由と ARK の打ち直しを求める経路である。
POST /api/purge は 2 手を一度に行う(ark:purge、届く範囲の内側)。裁判所の
削除命令や、公開してはならなかった個人情報のためのもので、逃げ道が無ければ誰かが
DB を直接叩くからである。理由が要り、confirm に ARK を打ち直させ、監査に残る。
名前は解放しないので、残っている参照は 404 になるだけで、別のものを指すことは
ない。
一括操作は部分適用しない。 一括更新の 1 件でも欠けるか範囲外なら、全体が失敗する。
arklet は順序不定の問い合わせ結果を入力と zip しており、別のレコードの値を書き込み
うるバグがあった。
解決¶
解決は OpenAPI では十分に表せない——1 本の経路で、接尾によって振る舞いが変わるため。
| 要求 | 応答 |
|---|---|
/ark:99999/x9tn1qkq2g7 |
302 で対象へ。行き先が無ければ記述を返す |
/ark:99999/x9tn1qkq2g7/page/3 |
302 で 対象/page/3 へ。suffix passthrough。子に識別子は要らない |
/ark:99999/x9tn1qkq2g7? |
ERC/ANVL の kernel(who / what / when / where) |
/ark:99999/x9tn1qkq2g7?? |
上に加えて永続性宣言 |
/ark:99999/x9tn1qkq2g7?info |
同じ内容を、Accept に応じてページ・JSON・ANVL で |
/ark:99999/x9tn1qkq2g7?json |
?info の JSON を名指しする別名 |
/ark:12345/…(未知 NAAN) |
302 でグローバルリゾルバへ |
/.well-known/ark |
text/plain。リゾルバのルートパス(末尾は /) |
/.well-known/ark+Accept: application/json |
このリゾルバが何を預かっているか。採番を外に委ねているならその案内先 |
裸の ? は、プロトコルの層でクエリ文字列なしと区別できない(ASGI でも同じ)。
前段が生の URI を渡せるなら ARKHE_RAW_URI_HEADER を設定する。
誤りには符号(ARKHE-1011)と英語の message、構造化された detail が付く。
文面ではなく符号で判定すること——一覧はエラーにある。
THUMP のヘッダと where¶
識別子についてリゾルバ自身が答える応答——? ?? ?info ?json、行き先の無い
記述、保留、404——には §5.2 の 2 つのヘッダが付く。
Link は、inflection を知らない受信者に対して「この応答は取得した URL の表現では
なく、修飾の付いていない ARK を記述したものである」と示すためにある。仕様の
応答例は <…> rel="describes"; と書いているが、これは
RFC 8288 のリンク値として不正なので、
arkhe は <…>; rel="describes" を出す——同じことを、標準のパーサが読める形で
言う。転送にはどちらも付けない。転送は識別子についての答えではなく、対象への誘導
だからである。
ERC の where は ARK であって、転送先ではない。§5.1.2 が「一時的な転送先では
なく長期的な識別子」と定めている要素で、行き先を付け替えても where は動かない
——それがこの要素の価値そのものである。今の行き先は kernel の外の redirect として、
あるときだけ出す。
?info は求められた媒体で答える¶
§5.2 は「THUMP の応答の形は返す content type が示す」と書いている。?info は
それに従って出し分ける——既定は人が読むページ、application/json なら機械向け、
text/plain なら ?? と同じ ANVL。中身はどれも同じ「記述+永続性宣言」である
(§5「?info は記述と permanence を 1 回で返す」)。?json はその JSON を名指しする
別名として残してある。どちらにも Vary: Accept, Accept-Language が付く。
ページ自体も翻訳される(?info&lang=en → Accept-Language → 既定)。
?lang= は使えない——この口ではクエリ文字列そのものが inflection なので、
言語は & の後ろに書く。
%-エンコードされた文字¶
予約文字(% - . /)は、その予約された意味を隠す目的でなら %-エンコード
してよい——%2F は「ここに / はあるが成分の区切りではない」と書く唯一の方法である。
したがって ark:99999/x9tn1qkq2g7%2Fc2 と ark:99999/x9tn1qkq2g7/c2 は別の識別子
で、仕様はエンコードされた文字が復号形で現れることを禁じている(draft-kunze-ark-42
§3.2)。arkhe は ASGI の raw_path からエンコードを保ったまま経路を読み、16 進の
大小だけ揃える(%2f → %2F。手順5)。復号はしない。
前段もエンコードを素通しにすること。 nginx なら proxy_pass にパスを書かない
(proxy_pass http://backend;。書くと復号済みのものを再エンコードする)。Apache なら
AllowEncodedSlashes NoDecode。前段が %2F を潰す構成では復元できず、その種の名前は
解決できない。
/.well-known/ark¶
draft-kunze-ark-42 §5.6 は ark を Well-Known URIs レジストリ
(RFC 8615)に登録し、応答を
「リゾルバのルートパスを含む plain text、末尾は /」と定めた——そこに
compact ARK を継ぎ足すと解決の要求になる、という約束である。Accept を
送らない相手にも */* の相手にもこれを返す。ここで JSON を返すと、仕様どおりに
読む発見クライアントからは「このホストに ARK リゾルバは無い」に見える。
arkhe 独自の在庫——預かっている名前空間、委譲した shoulder とその minter、
止めている名前空間——は同じ URL の JSON 表現なので、名指しで求める:
どちらにも Vary: Accept が付く。パスは ASGI の root_path から採るので、
プレフィクス付きでマウントするなら設定すること(uvicorn --root-path /pid)
——さもないと、無い口を案内することになる。