# [ERC8436] 複数のAIエージェントが投票で作業の承認結果を決める仕組み

> ソフトウェア開発を依頼し、AIエージェントから成果物が提出された場面を考えます。発注者は、1人の評価者ではなく、技術面を確認する5人のAIエージェントに受入れ判断を頼みたいとします。

- URL: https://cardene.dev/articles/7d23cebc8935c3282be1
- 公開日: 2026-10-16
- 更新日: 2026-10-16
- トピック: Blockchain
- タグ: ethereum, erc, ai, solidity

## はじめに

初めまして。
『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください！

https://cardene.notion.site/EIP-2a03fa3ea33d43baa9ed82288f98d4a9

https://twitter.com/cardene777

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

AIエージェントが作った成果物を、別のAIエージェントに評価させる場面が増えています。
ただし、1人の判定者へ任せると、その判定者の停止や偏りが作業の受入れ結果を決めてしまいます。

ERC8436は、複数の主体による投票、判断できなかった結果、異議申立て、業務処理への反映を1つのオンチェーン手続きとして記録する規格です。
誰が投票できるかと、誰が資産や作業状態を変更できるかを分けます。

2026年10月16日時点では `Draft` です。
仕様、全 `API`、公式参考実装、公式テストベクタ、導入時の安全性を解説します。

## 概要

ソフトウェア開発を依頼し、AIエージェントから成果物が提出された場面を考えます。
発注者は、1人の評価者ではなく、技術面を確認する5人のAIエージェントに受入れ判断を頼みたいとします。
3人以上が承認すれば成果物を受け入れ、3人以上が反対すれば拒否します。

単に投票結果だけを記録しても、発注者の作業コントラクトが投票結果に従うとは限りません。
評価者の評判や保有トークンは投票資格の根拠になっても、発注者の資産や作業状態を変更する権限にはならないためです。
必要票へ届かなかった場合を承認または拒否へ読み替えると、同じ投票から実装ごとに異なる結果も生まれます。

そこでERC8436は、参加者、必要票、制限時間、異議申立ての回数を投票前に固定します。
この固定した投票規則を `Decision Policy` と呼びます。
同じ成果物について、同じ質問を、固定した規則で判定する一連の記録が `Issue` です。

投票結果を業務処理へ反映すると事前に約束するコントラクトまたはアカウントを `consumer` と呼びます。
`consumer` は、承認時の処理、拒否時の処理、必要票へ届かなかった時の処理を投票前に固定します。
賛成でも反対でもない結果は `NoDecision` として残り、 `consumer` が事前に指定した既定処理へ渡されます。

投票では、各参加者が同じ `Issue`、同じ `round`、同じ判断グループへ1票だけ提出します。
必要票が集まるか投票時間が終了すると、そのグループの判断が決まります。
複数のグループを使う場合は、技術面と経済面の両方が賛成した時だけ承認する構成や、拒否権 `Body` が `approve ballot` を出した時に全体を止める構成も作れます。

最初の判断に異議申立てがあれば、新しい `round` を開始します。
異議申立て後に必要票へ届かなかった時、前回の判断を残すか、全体を `NoDecision` にするかも投票前の規則へ含めます。
最終結果が確定した後だけ、 `consumer` が自分の権限で成果物の受入れまたは拒否を実行します。

#### 複数人で作業成果を判定する場面

5人中3人の承認を求める場合、同じ成果物と質問を5人へ渡します。
各参加者は、自分が確認した内容に基づいて賛成または反対を提出します。
3票の賛成が集まれば `Yes`、3票の反対が集まれば `No` です。

期限までに賛成が2票、反対が2票、未投票が1人なら、どちらの条件にも届きません。
ERC8436は、賛否どちらの必要票にも届かなかった投票を `NoDecision` として記録します。
未達を自動で拒否へ変換しないため、発注者は投票開始前に**`NoDecision` なら既存の納期処理へ戻す**と決められます。

#### 結果を受け入れる相手

投票参加者は判断を作りますが、作業報酬や成果物の状態を変更しません。
作業コントラクトを操作できる `consumer` だけが、最終結果を業務処理へ反映します。

`consumer` は、 `Issue` を自分で作る、事前の受入れ条件を登録する、第三者が作った `Issue` を投票前に承認する、のいずれかで約束を固定します。
この投票前の約束を `Consumer commitment` と呼びます。
約束が成立した `Issue` は `Binding` です。
約束がない `Issue` は `Advisory` として判断記録を残せますが、業務処理を拘束しません。

#### 投票開始から業務処理まで

発注者側の `adapter` は、作業コントラクトから成果物、期限、承認時と拒否時の処理を読み取って `Issue` を作ります。
`Issue Registry` は、固定済みの投票規則と `consumer` の約束を確認して最初の `round` を開始します。
参加者の投票、必要票の判定、異議申立てを経て、最終結果を保存します。

![ERC8436で作業成果を複数のAIエージェントが判定しconsumerが業務処理へ反映する流れ](/images/qiita-de932ee0-d261-4d48-ae27-867fd15eed03.webp)

最終結果が `Yes` なら `adapter` が作業コントラクトの受入れ関数を呼び、 `No` なら拒否関数を呼びます。
呼び出しに失敗しても投票結果は変わりません。
`consumer` は失敗を実行記録へ残し、作業コントラクトの条件が許す範囲で再試行します。

## 動機

複数の署名を集める仕組みや、トークン保有者が提案へ投票する仕組みはすでにあります。
しかし、具体的な成果物と質問、結果に従う `consumer`、必要票へ届かなかった結果、異議申立て、業務処理の成否までを同じ記録へ結び付けるには、追加の約束が必要です。

1人の評価者だけを指定する作業コントラクトでは、評価者の停止がそのまま処理停止になります。
複数の投票者を置いても、投票者と業務実行者の権限を分けなければ、評価資格を持つ主体が発注者の資産まで動かせる設計になります。

ERC8436は、判断を作る手続きと判断を実行する権限を分離します。
投票規則と `consumer` の約束を先に固定し、投票後に都合のよい結果だけを採用する余地も狭めます。

#### 既存の投票部品で欠ける記録

| 方式 | 質問と対象 | `NoDecision` | 複数グループ | 異議申立て | 業務処理との接続 |
| --- | --- | --- | --- | --- | --- |
| `multisig` | 呼び出し内容へ埋め込む | 個別設計 | 単一の署名者集合 | 個別設計 | 署名成立で直接実行する |
| `governor` | 提案ごとに持つ | `quorum` 未達として扱う | 通常は1つの選挙人集合 | 個別設計 | 提案の実行へ接続する |
| 単一 `evaluator` | 作業ごとに持つ | `evaluator` の応答規則次第 | 持たない | 個別設計 | 作業コントラクトが結果を読む |
| ERC8436 | `Issue` へ固定する | 独立した正式結果 | `Body` ごとに分離する | `Policy` へ固定する | `consumer` が自分の権限で実行する |

ERC8436が必要になるのは、投票数だけでなく**どの質問を、誰の判断で、どの業務へ反映するか**を再現したい場合です。

#### 投票資格と実行権限の分離

投票資格は、固定された参加者一覧や、特定の報告者として `Policy` へ入ります。
一方、業務実行権限は `consumer` がすでに持つ権限です。
投票参加者が高い評価を持っていても、 `consumer` の約束なしに報酬や作業状態を変更できません。

この分離により、参加者の選定方法を将来の別仕様へ委ねられます。
身元、評判、利害関係の除外を追加しても、 `consumer` の権限モデルを変更せずに済みます。

#### 決まらなかった結果の扱い

投票では、賛成にも反対にも届かないまま期限を迎える場合があります。
単一の報告者が期限までに応答しない場合もあります。
拒否権を持つグループが、拒否も許可も明示しない場合もあります。

ERC8436は、これらを `NoDecision` の理由として分けます。
`consumer` は `NoDecision` 時の処理を投票前に固定するため、結果を見てから承認扱いか拒否扱いかを選べません。

#### 判断記録と業務実行の分離

`Issue Registry` は、投票と異議申立てから最終結果を作ります。
外部コントラクトの資産や作業状態は変更しません。

`consumer` は最終結果を読み、自分が操作できるコントラクトへ承認または拒否を送ります。
外部呼び出しが失敗した場合、最終結果を巻き戻さず、 `Failed` の実行記録を残します。
判断の確定と業務処理の成功を別々に監査できます。

## 仕様

ERC8436は、 `Policy Registry` と `Issue Registry` を同時にデプロイする構成を定めます。
`Policy Registry` は変更できない投票規則を保存し、 `Issue Registry` は具体的な判断案件、投票回、最終結果を保存します。

### `ACDFTypes`

`ACDFTypes` ライブラリは、 `Policy`、 `Issue`、 `round`、結果で共有する `Enum` 型と構造体を定義します。

#### `BodyKind`

```js
enum BodyKind { ROSTER_KOFN, AUTHORIZED_SUBMITTER }
```

`BodyKind` は、1つの判断グループが固定名簿の投票で決めるか、固定された報告者が結果を提出するかを表します。

| 値 | 入力 | 判断結果 |
| --- | --- | --- |
| `ROSTER_KOFN` | 名簿の各 `member` が投票する | K票の賛成またはN-K+1票の反対で決まる |
| `AUTHORIZED_SUBMITTER` | 1つの `address` が結果を提出する | `Yes`、 `No`、 `NoDecision` の提出値を使う |

#### `Acceptance`

```js
enum Acceptance { ON_CHAIN_TALLY, SIGNED_BALLOTS, AUTHORIZED_SUBMITTER }
```

`Acceptance` は、 `Body` が判断材料を受け取る方式です。

| 値 | 入力条件 | 後続の判定 |
| --- | --- | --- |
| `ON_CHAIN_TALLY` | `voter` 自身が `transaction` を送る | `roster` と重複を確認して票数へ加える |
| `SIGNED_BALLOTS` | `voter` が署名し、任意の `relayer` が提出する | 署名検証後に同じ票数判定へ進む |
| `AUTHORIZED_SUBMITTER` | 固定報告者が `Body` 結果を送る | 個別票を数えず提出値を採用する |

#### `Combinator`

```js
enum Combinator { BODY, ALL, ANY, KOFM, VETO }
```

`Combinator` は、 `Body` または子 `Node` の四値判断を合成する方法です。

| 値 | 条件 | 合成結果 |
| --- | --- | --- |
| `BODY` | 1つの `Body` を参照する | `Body` の値を返す |
| `ALL` | すべての子が `Yes` | `Yes` になる。1つでも `No` なら `No` になる |
| `ANY` | 1つ以上の子が `Yes` | `Yes` になる。すべて `No` なら `No` になる |
| `KOFM` | M個の子のうちK個が `Yes` | 閾値到達で `Yes`、到達不能で `No` になる |
| `VETO` | `target Node` と拒否権 `Body` を別々に参照する | 拒否権 `Body` の `Yes` で `No`、明示的な `No` で `target` を通す |

#### `VetoSilence`

```js
enum VetoSilence { PASS_THROUGH, REQUIRE_CLEARANCE }
```

`VetoSilence` は、拒否権 `Body` が期限までに結論を出さなかった時の扱いです。
拒否権 `Body` では、 `approve ballot` による `Yes` が `veto` を意味し、 `No` が明示的な `clearance` を意味します。
`PASS_THROUGH` は沈黙時に `target` の結果を通し、 `REQUIRE_CLEARANCE` は明示的な `No` がないため `NoDecision` にします。

| 値 | 入力または保存状態 | 後続の判定 | 例 |
| --- | --- | --- | --- |
| `PASS_THROUGH` | 拒否権 `Body` が期限まで沈黙する | `target` の値を使う | 技術 `Body` の `Yes` をそのまま `root` へ渡す |
| `REQUIRE_CLEARANCE` | 拒否権 `Body` が期限まで沈黙する | `NoDecision(NO_CLEARANCE)` にする | 審査 `Body` の明示的な `No` を待つ |

#### `AppealStanding`

```js
enum AppealStanding { ANYONE, CONSUMER_OR_FILER }
```

`AppealStanding` は、異議申立てを開始できる主体を表します。
`ANYONE` は任意の `address` を許し、 `CONSUMER_OR_FILER` は `consumer` または `Issue` 作成者だけを許します。

| 値 | `caller` 条件 | 後続の判定 | 例 |
| --- | --- | --- | --- |
| `ANYONE` | 任意の `caller` | `window` と回数を確認する | `keeper` が `appeal transaction` を送る |
| `CONSUMER_OR_FILER` | `consumer` または `filer` | それ以外を拒否する | 発注者または `Issue` 作成者が再審を求める |

#### `AppealMode`

```js
enum AppealMode { PRESERVE_UNLESS_OVERTURNED, REQUIRE_FRESH_DECISION }
```

`AppealMode` は、 `appeal round` が `NoDecision` になった時に前 `round` の判断を残すかを表します。
`PRESERVE_UNLESS_OVERTURNED` は直近の `Yes` または `No` を採用し、 `REQUIRE_FRESH_DECISION` は `Issue` 全体を `NoDecision` にします。

| 値 | `appeal` 結果 | `Final` 結果 | 例 |
| --- | --- | --- | --- |
| `PRESERVE_UNLESS_OVERTURNED` | 新 `round` が `NoDecision` | 直近の `Yes` または `No` を採用する | `round` 1の `Yes` を残す |
| `REQUIRE_FRESH_DECISION` | 新 `round` が `NoDecision` | `Issue` 全体を `NoDecision` にする | 再審で必要票へ届かず `effect` を選ばない |

#### `BodySpec`

```js
struct BodySpec {
    BodyKind kind;
    Acceptance acceptance;
    address[] members;
    uint32 k;
    uint64 window;
}
```

`BodySpec` 構造体は、1つの判断グループの参加者、入力方式、閾値、期限をまとめます。
`Policy` 作成者が `PolicySpec` へ組み込み、 `Policy Registry` が保存し、 `Issue Registry` の `Body` 判定と `bodyOf` 関数が参照します。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `kind` | 名簿投票または固定報告者を選びます。 |
| `acceptance` | オンチェーン投票、署名投票、報告者提出を選びます。 |
| `members` | `zero` 以外で重複しない参加者です。報告者方式では1件です。 |
| `k` | 名簿投票で必要な賛成票数です。 |
| `window` | `round` 開始から入力を受け付ける秒数です。 |

#### `Node`

```js
struct Node {
    Combinator op;
    uint32 body;
    uint32 k;
    uint32 target;
    uint32 vetoBody;
    VetoSilence silence;
    uint32[] children;
}
```

`Node` 構造体は、 `Body` 結果をどの論理で合成するかを表します。
`nodes[0]` が最終判断を返す `root` です。
`Policy` 作成者が `tree` を組み、 `Policy Registry` が保存し、 `Issue Registry` の `nodeStatus` 関数が `root` まで評価します。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `op` | `Body` 参照、 `ALL`、 `ANY`、 `K-of-M`、 `VETO` を選びます。 |
| `body` | `BODY` で参照する `Body` 番号です。 |
| `k` | `KOFM` で必要な `Yes` の数です。 |
| `target` | `VETO` で保護する `Node` 番号です。 |
| `vetoBody` | 拒否権を持つ `Body` 番号です。 |
| `silence` | 拒否権 `Body` が沈黙した時の扱いです。 |
| `children` | `ALL`、 `ANY`、 `K-of-M` で参照する子 `Node` 番号です。 |

#### `PolicySpec`

```js
struct PolicySpec {
    bytes32 family;
    uint32 version;
    bytes32 previous;
    address updateAuthority;
    BodySpec[] bodies;
    Node[] nodes;
    uint32 maxAppeals;
    uint64 appealWindow;
    uint8 appealable;
    AppealStanding appealStanding;
    AppealMode appealMode;
    uint64 maxTotalDuration;
    uint64 ackWindow;
    bool allowAdvisory;
    bytes32 descriptorHash;
}
```

`PolicySpec` 構造体は、投票資格、閾値、合成、時間、異議申立て、受入れ条件を1つにまとめます。
全フィールドの `ABI encoding` から `policyId` を計算するため、説明用 `JSON` と実行規則の食い違いを検出できます。
`Policy` 作成者が `registerPolicy` 関数へ渡し、 `Policy Registry` が変更不能な値として保存し、 `Issue Registry` と各 `read` 関数が参照します。

- **フィールド**

| グループ | フィールド | 意味 |
| --- | --- | --- |
| 版 | `family`、 `version`、 `previous`、 `updateAuthority` | 直前 `Policy` への参照、増加する版番号、公開主体です。 |
| 判断 | `bodies`、 `nodes` | 参加者、閾値、合成 `tree` です。 |
| `appeal` | `maxAppeals`、 `appealWindow`、 `appealable`、 `appealStanding`、 `appealMode` | 異議申立ての回数、時間、主体、採用規則です。 |
| 時間 | `maxTotalDuration`、 `ackWindow` | 手続き全体と事後承認の上限です。 |
| 補助 | `allowAdvisory`、 `descriptorHash` | `Advisory` 開始と `off-chain` 説明の `hash` です。 |

#### `ProcedureState`

```js
enum ProcedureState { None, Filed, Deciding, Provisional, Final, Withdrawn }
```

`ProcedureState` は、 `Issue` が受付から終了までのどこにあるかを保存します。

| 値 | 保存状態 | 許される処理 |
| --- | --- | --- |
| `None` | `Issue` が存在しない | 後続操作を拒否する |
| `Filed` | 受付済みで投票開始前 | 承認、 `Advisory` 開始、期限切れ、取消を受け付ける |
| `Deciding` | `round` が進行中 | 投票、 `Body` 結果提出、 `settle` を受け付ける |
| `Provisional` | `round` 結果が決まり異議申立て待ち | `appeal` または `finalize` を受け付ける |
| `Final` | 最終結果を保存済み | `consumer` が結果を実行できる |
| `Withdrawn` | 投票前に取消済み | `terminal` として後続操作を拒否する |

#### `OutcomeType`

```js
enum OutcomeType { None, Decided, NoDecision }
```

`OutcomeType` は、 `Final` 時に `Yes` または `No` が決まったか、判断を作れなかったかを表します。
`None` では `effect` を実行せず、 `Decided` では `outcomeYes` に対応する `effect`、 `NoDecision` では `disposition` を選びます。

| 値 | 保存状態 | 後続の判定 | 例 |
| --- | --- | --- | --- |
| `None` | `Final` 前または結果未保存 | `effect` を実行しない | `Deciding` 中の `Issue` |
| `Decided` | `Yes` または `No` を `Final` へ保存済み | `outcomeYes` から `effect` を選ぶ | `Yes` なら `accept` を呼ぶ |
| `NoDecision` | 判断不能を `Final` へ保存済み | 事前の `disposition` を使う | `quorum` 不足で既定処理へ戻る |

#### `AcceptanceMode`

```js
enum AcceptanceMode { CONSUMER_FILED, STANDING_ACCEPTANCE, POST_ACK }
```

`AcceptanceMode` は、 `consumer` が結果に従う約束を投票前に固定する方法です。

| 値 | 約束を作る操作 | `admission` |
| --- | --- | --- |
| `CONSUMER_FILED` | `consumer` 自身が `Issue` を作る | 直ちに `Binding` で開始する |
| `STANDING_ACCEPTANCE` | `consumer` が事前条件を登録する | 条件一致時に `Binding` で開始する |
| `POST_ACK` | `filer` が作り、 `consumer` が期限内に承認する | 承認後に `Binding` で開始する |

#### `EffectClass`

```js
enum EffectClass { Advisory, Binding }
```

`EffectClass` は、最終結果が `consumer` の事前の約束を持つかを表します。
`Advisory` は判断記録だけを残し、 `Binding` は固定済みの `effects` と `obligation` を持ちます。

| 値 | 保存状態 | 後続の判定 | 例 |
| --- | --- | --- | --- |
| `Advisory` | `consumer commitment` なし | `enactment` を記録しない | 未承認 `Issue` を参考判断として開始する |
| `Binding` | `consumer`、 `effects`、 `obligation` を固定済み | `Final-Decided` 後に `effect` を選べる | `Yes` で作業を受け入れる |

#### `NodeStatus`

```js
enum NodeStatus { Pending, Yes, No, NoDecision }
```

`NodeStatus` は、 `Body` または合成 `Node` の現在の四値判断です。
例として、 `K-of-N` の必要票へ届けば `Yes`、 `blocking threshold` へ届けば `No`、期限前でどちらにも届かなければ `Pending` を返します。
`Pending` は入力で変わる状態、 `Yes` と `No` は実質判断、 `NoDecision` は期限内に判断条件が成立しなかった状態です。

#### `Reason`

```js
enum Reason {
    NONE,
    QUORUM_NOT_MET,
    SUBMITTER_SILENT,
    VETOED,
    NO_CLEARANCE,
    NOT_REACHED,
    TOTAL_TIMEOUT,
    NO_ACCEPTANCE
}
```

`Reason` は、 `NoDecision`、拒否権、未承認が発生した理由を保存します。

| 値 | 発生条件 |
| --- | --- |
| `NONE` | 特別な失敗理由がない |
| `QUORUM_NOT_MET` | 名簿投票が必要票へ届かない |
| `SUBMITTER_SILENT` | 固定報告者が期限までに提出しない |
| `VETOED` | 拒否権 `Body` が拒否する |
| `NO_CLEARANCE` | 明示許可が必要な拒否権 `Body` が沈黙する |
| `NOT_REACHED` | 子 `Node` から合成結果を作れない |
| `TOTAL_TIMEOUT` | `hard deadline` までに `round` が終わらない |
| `NO_ACCEPTANCE` | `POST_ACK` が承認されない |

#### `EnactmentStatus`

```js
enum EnactmentStatus { NotEnacted, Enacted, Failed }
```

`EnactmentStatus` は、 `consumer` が `Final` 結果を業務コントラクトへ反映できたかを表します。
`Failed` は後から `Enacted` へ更新できますが、 `Enacted` は `terminal` です。

| 値 | 保存状態 | 後続の判定 | 例 |
| --- | --- | --- | --- |
| `NotEnacted` | 実行報告なし | 実行または再試行を待つ | `Final-Decided` 直後 |
| `Failed` | `target call` 失敗を報告済み | 条件が戻れば再試行できる | 期限条件で `accept` が失敗する |
| `Enacted` | `effect` 成功を報告済み | 後続更新を拒否する | `accept` 完了を記録する |

#### `Subject`

```js
struct Subject {
    uint256 chainId;
    address target;
    uint256 id;
    bytes32 dataHash;
}
```

`Subject` 構造体は、どの `chain` のどの `target` にある何を評価するかを固定します。
`filer` または `adapter` が作り、 `Issue Registry` が `Issue` 内へ保存し、 `getIssue` 関数と `consumer adapter` が照合します。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `chainId` | 評価対象が属する `chain` です。 |
| `target` | 作業コントラクトや対象コントラクトです。 |
| `id` | `task`、 `submission`、 `token` の識別番号です。 |
| `dataHash` | 評価する具体的な内容を固定する `hash` です。 |

#### `IssueInput`

```js
struct IssueInput {
    bytes32 policyId;
    AcceptanceMode mode;
    bytes32 acceptanceId;
    address consumer;
    Subject subject;
    bytes32 question;
    bytes32 effectYes;
    bytes32 effectNo;
    bytes32 disposition;
    bytes32 obligationId;
    uint64 consumerDeadline;
}
```

`IssueInput` 構造体は、 `filer` が `Issue` を作る時に渡す値をまとめます。
`file` 関数が `acceptance mode` 別に値を検証し、 `admission` 後は `Issue Registry` の `Issue` へ固定し、 `getIssue` 関数から読めます。

- **フィールド**

| グループ | フィールド | 意味 |
| --- | --- | --- |
| 規則 | `policyId`、 `mode`、 `acceptanceId` | 適用 `Policy` と `consumer` の約束です。 |
| 主体と対象 | `consumer`、 `subject`、 `question` | 実行主体、評価対象、質問です。 |
| 結果処理 | `effectYes`、 `effectNo`、 `disposition` | `Yes`、 `No`、 `NoDecision` 時の処理です。 |
| 義務と期限 | `obligationId`、 `consumerDeadline` | 並行 `Issue` を防ぐ範囲と `consumer` 側期限です。 |

#### `StandingAcceptanceInput`

```js
struct StandingAcceptanceInput {
    bytes32 policyId;
    address subjectTarget;
    bytes32 question;
    address[] filers;
    bytes32 effectYes;
    bytes32 effectNo;
    bytes32 disposition;
    bytes32 obligationId;
    uint64 validUntil;
}
```

`StandingAcceptanceInput` 構造体は、 `consumer` が将来の `Issue` へ事前に適用する条件をまとめます。
`subjectTarget` と `question` は `zero` で任意を表し、 `filers` は空配列で任意の作成者を許します。
`consumer` が `registerStandingAcceptance` 関数へ渡し、 `Issue Registry` が `acceptance record` として保存し、 `file` 関数が照合します。
`obligationId == 0` の場合、 `admission` 時に `exact Subject` の `ABI encoding` から `Issue` 用 `obligationId` を導出するため、 `filer` は排他範囲を選べません。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `policyId` | 受け入れる正確な `Policy` です。 |
| `subjectTarget` | `zero` なら任意、 `zero` 以外なら一致を求める `target` です。 |
| `question` | `zero` なら任意、 `zero` 以外なら一致を求める質問です。 |
| `filers` | `Issue` 作成を許す `address` です。空配列なら制限しません。 |
| `effectYes` | `Yes` 時に約束する `effect` です。 |
| `effectNo` | `No` 時に約束する `effect` です。 |
| `disposition` | `NoDecision` 時の処理です。 |
| `obligationId` | 排他範囲です。 `zero` なら `exact Subject` から導出します。 |
| `validUntil` | 事前登録の有効期限です。 |

#### `Issue`

```js
struct Issue {
    bytes32 policyId;
    AcceptanceMode mode;
    EffectClass effectClass;
    ProcedureState state;
    address filer;
    address consumer;
    bytes32 acceptanceId;
    Subject subject;
    bytes32 question;
    bytes32 effectYes;
    bytes32 effectNo;
    bytes32 disposition;
    bytes32 obligationId;
    bytes32 obligationKey;
    uint64 filedAt;
    uint64 admittedAt;
    uint64 consumerDeadline;
    uint64 hardDeadline;
    uint64 finalAt;
    uint32 roundCount;
    OutcomeType outcomeType;
    bool outcomeYes;
    Reason reason;
    uint32 sourceRound;
    uint64 decidedAt;
}
```

`Issue` 構造体は、 `admission` で固定した値、期限、現在状態、採用結果を保存します。
`Final` 後は `outcomeType`、 `sourceRound`、 `decidedAt` から、どの `round` の判断を採用したか追跡できます。
`Issue Registry` が `file`、 `acknowledge`、 `admitAdvisory` で作成・更新し、 `getIssue` 関数が全保存値を返します。

- **フィールド**

| グループ | フィールド | 詳細 |
| --- | --- | --- |
| 規則・状態 | `policyId`、 `mode`、 `effectClass`、 `state` | 固定 `Policy`、受付方式、拘束性、手続き状態です。 |
| 主体・対象 | `filer`、 `consumer`、 `acceptanceId`、 `subject`、 `question` | 作成者、実行主体、事前登録、評価対象、質問です。 |
| 処理・排他 | `effectYes`、 `effectNo`、 `disposition`、 `obligationId`、 `obligationKey` | 最終結果の処理と並行 `Issue` を止める `key` です。 |
| 時刻 | `filedAt`、 `admittedAt`、 `consumerDeadline`、 `hardDeadline`、 `finalAt` | 受付、開始、 `consumer` 期限、手続き上限、確定時刻です。 |
| 結果 | `roundCount`、 `outcomeType`、 `outcomeYes`、 `reason`、 `sourceRound`、 `decidedAt` | `round` 数、結果種別、賛否、理由、採用 `round`、判断形成時刻です。 |

#### `RoundState`

```js
struct RoundState {
    uint64 startedAt;
    uint64 deadline;
    bool settled;
    NodeStatus status;
    Reason reason;
    uint64 at;
    uint64 appealOpenUntil;
}
```

`RoundState` 構造体は、1つの `round` の開始、結果形成、 `settle`、異議申立て期限を保存します。
`at` は `settle transaction` の時刻ではなく、票または期限によって `root` 値が変わらなくなった時刻です。
`Issue Registry` が `round` 開始時と `settleRound` 関数で更新し、 `getRound` 関数が履歴を返します。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `startedAt` | `round` 開始時刻です。 |
| `deadline` | 最長 `Body window` から決まる終了時刻です。 |
| `settled` | `root` 結果を保存済みかを示します。 |
| `status` | `settle` した四値判断です。 |
| `reason` | 判断または `NoDecision` の理由です。 |
| `at` | 判断が変わらなくなった時刻です。 |
| `appealOpenUntil` | 異議申立てを受け付ける上限時刻です。 |

#### `BodyState`

```js
struct BodyState {
    uint32 yes;
    uint32 no;
    bool submitted;
    NodeStatus submitted_;
    uint64 decidedAt;
}
```

`BodyState` 構造体は、 `Body` ごとの票数または固定報告者の提出値を保存します。
名簿投票では `yes` と `no`、固定報告者では `submitted` と `submitted_` を読みます。
`Issue Registry` が投票受理または `Body` 結果提出時に更新し、 `getBodyState` 関数と `bodyStatus` 関数が参照します。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `yes` | 受理済み `approve ballot` 数です。 |
| `no` | 受理済み `block ballot` 数です。 |
| `submitted` | 固定報告者が提出済みかを示します。 |
| `submitted_` | 固定報告者が提出した `Yes`、 `No`、 `NoDecision` です。 |
| `decidedAt` | `threshold` 到達または提出時刻です。 |

#### `Enactment`

```js
struct Enactment {
    EnactmentStatus status;
    address reporter;
    bytes32 ref;
    uint64 at;
}
```

`Enactment` 構造体は、 `consumer` が業務 `effect` を実行した結果、報告者、外部参照、報告時刻を保存します。
`consumer` が `recordEnactment` 関数で報告し、 `Issue Registry` が `Issue` ・ `consumer` ・ `effect ID` の組で保存し、 `getEnactment` 関数が返します。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `status` | `NotEnacted`、 `Failed`、 `Enacted` の実行状態です。 |
| `reporter` | 実行結果を報告した `consumer` です。 |
| `ref` | 外部 `transaction` や失敗理由を結ぶ参照値です。 |
| `at` | 報告時刻です。 |

#### `Result`

```js
struct Result {
    ProcedureState state;
    OutcomeType outcomeType;
    bool outcomeYes;
    Reason reason;
    uint64 decidedAt;
    uint64 finalAt;
    bytes32 policyId;
    uint32 roundCount;
    uint32 sourceRound;
    EffectClass effectClass;
    address consumer;
    bytes32 effectYes;
    bytes32 effectNo;
    bytes32 disposition;
    bytes32 obligationId;
    bool adoptedFromEarlierRound;
}
```

`Result` 構造体は、 `client` が現在状態、最終判断、採用 `round`、 `consumer`、業務 `effect` をまとめて読む値です。
`state == Final` と `outcomeType == Decided` を両方確認してから、 `outcomeYes` に対応する `effect` を選びます。
`Issue Registry` が保存済み `Issue` と `round` 履歴から組み立て、 `getResult` 関数が `consumer adapter` や `client` へ返します。

- **フィールド**

| グループ | フィールド | 詳細 |
| --- | --- | --- |
| 手続き | `state`、 `outcomeType`、 `outcomeYes`、 `reason` | 現在状態、結果種別、賛否、理由です。 |
| 時刻・ `round` | `decidedAt`、 `finalAt`、 `roundCount`、 `sourceRound`、 `adoptedFromEarlierRound` | 判断形成、確定、 `round` 数、採用元、以前の `round` 採用 `flag` です。 |
| 規則・主体 | `policyId`、 `effectClass`、 `consumer` | `Policy`、拘束性、実行主体です。 |
| 処理 | `effectYes`、 `effectNo`、 `disposition`、 `obligationId` | `Yes`、 `No`、 `NoDecision` の処理と義務範囲です。 |

### `Conformance` と署名の前提

`client` は `registry address` だけを信用せず、必要な `interface ID` へ対応するかを `supportsInterface` 関数で確認します。
この問い合わせ方法をERC165が定めています。
`Policy Registry` の `interface ID` は `0xb362eb4e`、 `Issue Registry` は `0xd31aae30` です。

:::message
**ERC165**については以下の記事を参考にしてください。
:::

[\[ERC165\] コントラクトが特定のインターフェースをサポートしているか確認する仕組みを理解しよう！](/articles/541184977a5cba51b759)

署名投票では、 `Issue`、 `round`、 `Body`、 `voter`、賛否と、 `chain ID`、 `registry address` を同じ `digest` へ入れます。
ウォレットとコントラクトが同じ `field` 順で `digest` を作る形式がEIP712です。
これにより、 `client` は署名が別 `round` や別 `registry` 向けではないかを確認できます。

:::message
**EIP712**については以下の記事を参考にしてください。
:::

[\[EIP712\] 型付き構造化データのハッシュ化と署名の仕組みを理解しよう！](/articles/f54bc85cb66de5a54257)

`voter` がコントラクトアカウントの場合、 `Issue Registry` は `isValidSignature` 関数を `staticcall` し、 `magic value` を確認します。
この検証方法をERC1271が定めています。
`contract wallet` が現在の権限設定で署名を承認した時だけ、その `ballot` を受理します。

:::message
**ERC1271**については以下の記事を参考にしてください。
:::

[\[ERC1271\] コントラクトで署名を検証する仕組みを理解しよう！](/articles/da237b1b8cc185491512)

### `IACDFPolicyRegistry`

`IACDFPolicyRegistry` は、変更できない `Policy` を登録し、 `family` の版、 `Body`、 `Node`、時間設定を読み出す `interface` です。

#### `supportsInterface`

```js
function supportsInterface(bytes4 interfaceId) external view returns (bool);
```

指定した `interface ID` へ `Policy Registry` が対応するかを返す関数です。

1. **`ID` を受け取る**

    `client` が利用したい `API` 集合の `ID` を渡す。

2. **対応状況を確認する**

    実装済みの `ID` と比較する。

3. **真偽値を返す**

    対応していれば `true`、未対応なら `false` を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `interfaceId` | 確認する4-byteの `interface ID` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `bool` | 対応状況です。 |

- **実行条件**

- `view` 関数であり、 `storage` を更新しません。
- 未対応 `ID` は `revert` せず `false` を返します。

#### `Timing`

```js
struct Timing {
    uint32 maxAppeals;
    uint64 appealWindow;
    uint8 appealable;
    T.AppealStanding appealStanding;
    T.AppealMode appealMode;
    uint64 maxTotalDuration;
    uint64 ackWindow;
    bool allowAdvisory;
    uint64 roundDuration;
}
```

`Timing` 構造体は、 `Policy` の `round`、異議申立て、全体期限、事後承認を `client` がまとめて読む値です。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `maxAppeals` | 追加できる `round` 数です。 |
| `appealWindow` | 異議申立て受付秒数です。 |
| `appealable` | 異議申立て対象を示す `bit` です。 |
| `appealStanding` | 異議申立てできる主体です。 |
| `appealMode` | `appeal round` が `NoDecision` になった時の扱いです。 |
| `maxTotalDuration` | `admission` 後の全体上限秒数です。 |
| `ackWindow` | `POST_ACK` の承認秒数です。 |
| `allowAdvisory` | 未承認 `Issue` を `Advisory` へ移せるかを表します。 |
| `roundDuration` | 各 `Body window` の最大値から導出した `round` 秒数です。 |

#### `PolicyRegistered`

```js
event PolicyRegistered(bytes32 indexed policyId, bytes32 indexed family, uint32 version, address indexed by);
```

新しい `Policy` を保存する時に発行するイベントです。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `policyId` | `Policy` 全体から計算した識別子です。 |
| `family` | `Policy family` です。 |
| `version` | `family` 内の版番号です。 |
| `by` | `Policy` を登録した `address` です。 |

イベントから登録者と版を検索できますが、 `Body` と `Node` の全内容は `read` 関数で取得します。

#### `policyIdOf`

```js
function policyIdOf(T.PolicySpec calldata spec) external pure returns (bytes32);
```

`PolicySpec` 全体の `ABI encoding` から `policyId` を計算する関数です。

1. **`Policy` を符号化する**

    `Body`、 `Node`、時間、 `authority`、 `descriptor hash` を含む構造体を `abi.encode` する。

2. **`hash` を計算する**

    符号化結果へ `keccak256` を適用する。

3. **識別子を返す**

    登録前後で同じ `Policy` を照合できる `bytes32` を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `spec` | 識別子を計算する `Policy` 全体です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `bytes32` | `PolicySpec` から決まる `policyId` です。 |

- **実行条件**

- `pure` 関数であり、登録状態や `Policy` の妥当性は確認しません。
- `client` は登録結果と事前計算値を照合できます。

#### `registerPolicy`

```js
function registerPolicy(T.PolicySpec calldata spec) external returns (bytes32 policyId);
```

`Policy` の構造、 `family` の版、時間条件を検証して保存する関数です。

1. **`Policy` を検証する**

    `Body`、 `Node`、 `role`、 `window`、異議申立て、全体期限を確認する。

2. **`family` の版を検証する**

    初版では `family` を `claim` し、次版では `authority`、 `previous == latest policyId`、 `version > latest version` を確認する。

3. **`Policy` を保存する**

    `PolicySpec` 全体と導出した `roundDuration` を保存し、イベントを発行する。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `spec` | 登録する `Policy` 全体です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `policyId` | 保存した `Policy` の識別子です。 |

- **実行条件**

- `Body` は1件以上32件以下、 `Node` は1件以上64件以下です。
- `member` は `zero` 以外で重複させません。
- `tree` は循環参照や `Body` の `role` 重複を作れません。
- 次版は `family` の `update authority` だけが登録できます。
- 次版の `previous` は `latest policy ID` と一致させ、 `version` は最新版より大きくします。
- `latest + 1` の連番である必要はありません。
- 検証失敗時は `Policy` と `family` 情報を保存しません。

#### `policyExists`

```js
function policyExists(bytes32 policyId) external view returns (bool);
```

指定した `Policy` が登録済みかを返す関数です。

1. **識別子を受け取る**

    `client` が確認する `policyId` を渡す。

2. **登録状態を読む**

    `Policy Registry` の存在フラグを参照する。

3. **真偽値を返す**

    登録済みなら `true` を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `policyId` | 確認する `Policy` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `bool` | 登録状態です。 |

- **実行条件**

- 未登録 `ID` では `false` を返します。
- `storage` を更新しません。

#### `familyAuthority`

```js
function familyAuthority(bytes32 family) external view returns (address);
```

`Policy family` の次版を登録できる `address` を返す関数です。

1. **`family` を指定する**

    `client` が監査する `family ID` を渡す。

2. **`authority` を読む**

    最新 `Policy` が指定した現在の `updateAuthority` を取得する。

3. **`address` を返す**

    次版の公開主体を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `family` | `Policy family` の識別子です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `address` | `family` の `update authority` です。 |

- **実行条件**

- 未登録 `family` では `zero address` になります。
- 既存 `Policy` の内容を変更する権限ではありません。

#### `familyLatest`

```js
function familyLatest(bytes32 family) external view returns (bytes32 policyId, uint32 version);
```

`Policy family` の最新 `ID` と版番号を返す関数です。

1. **`family` を指定する**

    更新履歴を確認する `family ID` を渡す。

2. **最新値を読む**

    最後に登録された `policyId` と `version` を取得する。

3. **2つの値を返す**

    `client` が新規 `Issue` へ採用する候補を判断できる形で返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `family` | `Policy family` の識別子です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `policyId` | `family` の最新 `Policy` です。 |
| `version` | 最新版の番号です。 |

- **実行条件**

- 既存 `Issue` は最新値へ自動更新されません。
- `consumer` は許容する正確な `policyId` を固定します。

#### `roundDurationOf`

```js
function roundDurationOf(bytes32 policyId) external view returns (uint64);
```

`Policy` 内の `Body window` から導出した一 `round` の最大秒数を返す関数です。

1. **`Policy` を指定する**

    期限を確認する `policyId` を渡す。

2. **保存済み時間を読む**

    登録時に計算した `roundDuration` を取得する。

3. **秒数を返す**

    `client` が `round deadline` を予測できる値を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `policyId` | 対象 `Policy` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `uint64` | 一 `round` の最大秒数です。 |

- **実行条件**

- 登録済み `Policy` を指定します。
- 未登録 `ID` は実装の拒否条件に従います。

#### `timingOf`

```js
function timingOf(bytes32 policyId) external view returns (Timing memory);
```

`Policy` の時間設定と異議申立て設定をまとめて返す関数です。

1. **`Policy` を指定する**

    `client` が期限を評価する `policyId` を渡す。

2. **保存値と導出値を読む**

    `PolicySpec` の時間 `field` と `roundDuration` を取得する。

3. **構造体を返す**

    `round`、 `appeal`、 `hard deadline` を同じ単位で比較できる値を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `policyId` | 対象 `Policy` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `Timing` | `Policy` の時間と異議申立て設定です。 |

- **実行条件**

- 登録済み `Policy` を指定します。
- `Issue Registry` の `clock mode` と合わせて読みます。

#### `policyHeader`

```js
function policyHeader(bytes32 policyId) external view returns (bytes32 family, uint32 version, bytes32 previous, address updateAuthority, bytes32 descriptorHash);
```

`Policy family` と `off-chain` 説明を照合する `header` を返す関数です。

1. **`Policy` を指定する**

    版と説明文を確認する `policyId` を渡す。

2. **`header` を読む**

    `family`、 `version`、 `previous`、 `authority`、 `descriptor hash` を取得する。

3. **5つの値を返す**

    `client` が `previous` による系譜、増加する `version`、説明文の `hash` を照合できる形で返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `policyId` | 対象 `Policy` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `family` | `Policy family` です。 |
| `version` | `family` 内の版番号です。 |
| `previous` | 直前の `policyId` です。 |
| `updateAuthority` | 次版を登録できる `address` です。 |
| `descriptorHash` | `off-chain` 説明文の `hash` です。 |

- **実行条件**

- `descriptorHash` は情報用で、投票判定へ使いません。
- 実行規則は保存済み `PolicySpec` から読みます。

#### `bodyCount`

```js
function bodyCount(bytes32 policyId) external view returns (uint256);
```

`Policy` に登録された `Body` 数を返す関数です。

1. **`Policy` を指定する**

    読み出す `policyId` を渡す。

2. **件数を読む**

    保存済み `bodies` の長さを取得する。

3. **件数を返す**

    有効な `Body` 番号を判断できる値を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `policyId` | 対象 `Policy` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `uint256` | `Body` の件数です。 |

- **実行条件**

- 登録済み `Policy` を指定します。
- `Body` 番号は0から件数未満です。

#### `bodyOf`

```js
function bodyOf(bytes32 policyId, uint32 body) external view returns (T.BodySpec memory);
```

指定した `Body` の投票方式、 `member`、閾値、 `window` を返す関数です。

1. **`Policy` と `Body` 番号を指定する**

    `client` が確認する位置を渡す。

2. **`Body` を読む**

    保存済み `BodySpec` を取得する。

3. **構造体を返す**

    `voter eligibility` と判定条件を再現できる値を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `policyId` | 対象 `Policy` です。 |
| `body` | 0始まりの `Body` 番号です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `BodySpec` | 指定 `Body` の全設定です。 |

- **実行条件**

- `body` は `bodyCount` 未満にします。
- 範囲外では `revert` します。

#### `nodeCount`

```js
function nodeCount(bytes32 policyId) external view returns (uint256);
```

`Policy` に登録された `Node` 数を返す関数です。

1. **`Policy` を指定する**

    読み出す `policyId` を渡す。

2. **件数を読む**

    保存済み `nodes` の長さを取得する。

3. **件数を返す**

    合成 `tree` の有効範囲を判断できる値を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `policyId` | 対象 `Policy` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `uint256` | `Node` の件数です。 |

- **実行条件**

- 登録済み `Policy` を指定します。
- `root` は `Node` 0です。

#### `nodeOf`

```js
function nodeOf(bytes32 policyId, uint32 node) external view returns (T.Node memory);
```

指定した `Node` の合成規則を返す関数です。

1. **`Policy` と `Node` 番号を指定する**

    `client` が確認する位置を渡す。

2. **`Node` を読む**

    保存済み `Node` 構造体を取得する。

3. **構造体を返す**

    `Body` 結果から `root` 判断を再計算できる値を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `policyId` | 対象 `Policy` です。 |
| `node` | 0始まりの `Node` 番号です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `Node` | 指定 `Node` の全設定です。 |

- **実行条件**

- `node` は `nodeCount` 未満にします。
- 範囲外では `revert` します。

### `Issue` の作成と受入れ

`consumer` の約束を固定する方法は3種類です。

| `mode` | `Issue` を作る主体 | `commitment` が成立する時点 | 未承認時 |
| --- | --- | --- | --- |
| `CONSUMER_FILED` | `consumer` | `file` 関数の実行時 | 該当しない |
| `STANDING_ACCEPTANCE` | 許可された `filer` | 登録済み条件と一致した時 | `Issue` 作成を拒否する |
| `POST_ACK` | 任意または許可された `filer` | `consumer` が `acknowledge` 関数を呼んだ時 | `expire` または `Advisory` へ進む |

![ERC8436でconsumer commitmentを投票前に固定する3方式](/images/qiita-d01c4e2e-4fc6-4451-bdaa-280c2ec809d7.webp)

`Binding Issue` では、 `consumer`、 `obligationId`、 `question` から `obligationKey` を作ります。
ここでObligationは、同じ `consumer` が同じ `question` を複数 `panel` へ同時に出してはならない業務上の義務です。
同じ `key` に未完了 `Issue` がある間は2件目を拒否し、複数 `panel` へ同じ義務を出して都合のよい結果だけを選ぶ行為を防ぎます。

`admission` では、 `Policy`、 `Subject`、 `question`、 `effects`、 `disposition`、 `consumer`、期限を固定します。
投票開始後に `standing acceptance` を取り消しても進行中 `Issue` は止まりません。
`filer` による `withdrawal` は、まだ `Filed` で投票が始まっていない `Issue` だけに限定されます。

### 投票 `Body` と合成 `Tree`

名簿投票はK票の賛成で `Yes`、N-K+1票の反対で `No` になります。
どちらにも届かず `window` が終了すると `NoDecision` です。
1人の `member` は `Issue`、 `round`、 `Body` ごとに1票だけ提出できます。

署名投票も同じ票数判定を使います。
署名には `round` 番号が入るため、遅れて届いた `transaction` を次 `round` の票として使えません。
`batch` 内に1件でも無効な署名があれば `transaction` 全体を `revert` します。

固定報告者方式では、 `Policy` へ登録した `address` が `Yes`、 `No`、 `NoDecision` を1回だけ提出します。
`window` 内に提出しなければ `SUBMITTER_SILENT` です。

![ERC8436で独立したBody判断をALL・ANY・K-of-M・VETOで合成する構造](/images/qiita-90b6b964-6ae7-45cf-9876-911b37afc1e4.webp)

各 `Body` の票は別々に数えます。
この局所的な四値判断を `Body decision` と呼び、 `composition tree` の `leaf` または `veto` 入力に使います。
技術グループと経済グループの票を1つの票箱へ混ぜず、各 `Body` の四値判断を `tree` で合成します。
同じ `Body` を複数の `BODY Node` へ置くことや、拒否権 `Body` を通常 `Body` として再利用することは禁止されます。

### ProcedureとFinality

`Issue` は `Filed` から `Deciding` へ進み、 `round` 結果が決まると `Provisional` になります。
異議申立てがあれば `Deciding` へ戻り、新しい `round` を開始します。
異議申立てがなければ `Final` へ進みます。

![ERC8436でProcedureState・OutcomeType・EnactmentStatusを分けて追う状態遷移](/images/qiita-81ab201d-d62e-4b68-bd8a-1defae8165a2.webp)

異議申立て期限は、 `settle transaction` の時刻ではなく、必要票がそろった時刻または `window` 終了時刻から計算します。
この判断内容が変わらなくなった時刻が `Formation instant` です。
`settle` を遅らせても `appeal window` を延長できません。

`maxTotalDuration` から計算した `hard deadline` を過ぎた後、誰でも `enforceHardDeadline` 関数を呼べます。
`Deciding` で現在の `root` が `Pending` なら、進行中 `round` を `NoDecision(TOTAL_TIMEOUT)` として閉じます。
すでに `Yes` または `No` ならその判断を保存し、 `Provisional` なら保存済み `round` を使います。
最後に `appeal mode` の採用規則を適用して手続きを終了します。

Procedureの `Final`、判断の `Decided`、業務実行の `Enacted` は別々の値です。
`Final-NoDecision` や、 `Final-Decided` で実行が `Failed` という組み合わせもあります。

### `IACDFRegistry`

`IACDFRegistry` は、 `consumer` の事前登録、 `Issue`、投票、 `round`、最終結果、実行報告を保存する `interface` です。

#### `StandingAcceptanceRegistered`

```js
event StandingAcceptanceRegistered(bytes32 indexed acceptanceId, address indexed consumer, bytes32 indexed policyId);
```

`consumer` が事前の受入れ条件を登録する時に発行するイベントです。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `acceptanceId` | 事前登録の識別子です。 |
| `consumer` | 結果を受け入れる主体です。 |
| `policyId` | 受入れ対象に固定した `Policy` です。 |

このログだけでは、 `Subject` 制約、 `question` 制約、 `filer` 一覧、 `effects`、期限を復元できません。
必要な場合は、登録トランザクションの入力、オフチェーンの索引、実装固有の取得関数で補います。

#### `StandingAcceptanceRevoked`

```js
event StandingAcceptanceRevoked(bytes32 indexed acceptanceId, address indexed consumer);
```

`consumer` が事前の受入れ条件を取り消す時に発行するイベントです。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `acceptanceId` | 取り消した事前登録です。 |
| `consumer` | 取消を実行した `consumer` です。 |

取消後の新規 `Issue` には使えませんが、すでに `admit` された `Issue` は継続します。

#### `IssueFiled`

```js
event IssueFiled(bytes32 indexed issueId, bytes32 indexed policyId, T.AcceptanceMode mode, address indexed filer, address consumer);
```

新しい `Issue` を受け付ける時に発行するイベントです。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `issueId` | `Issue` の識別子です。 |
| `policyId` | 固定した `Policy` です。 |
| `mode` | `consumer commitment` の成立方法です。 |
| `filer` | `Issue` 作成者です。 |
| `consumer` | 想定または確定した `consumer` です。 |

このログで `Issue` の受付主体と `mode` を追えますが、 `Subject`、 `question`、 `effects`、 `obligation`、各期限は `getIssue` 関数で読みます。

#### `IssueAdmitted`

```js
event IssueAdmitted(bytes32 indexed issueId, T.EffectClass effectClass, address indexed consumer, uint64 admittedAt, uint64 hardDeadline);
```

`Issue` を投票手続きへ入れ、 `round` 1を開始する時に発行するイベントです。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `issueId` | `admission` した `Issue` です。 |
| `effectClass` | `Advisory` または `Binding` です。 |
| `consumer` | `Binding` 時の `consumer` です。 |
| `admittedAt` | 開始時刻です。 |
| `hardDeadline` | 手続き全体の終了上限です。 |

このログで投票開始と全体上限を追えますが、 `round` 1の `deadline`、 `Body` 設定、 `Node tree` は `getRound` 関数と `Policy Registry` の `read API` で補います。

#### `IssueAcknowledged`

```js
event IssueAcknowledged(bytes32 indexed issueId, address indexed consumer);
```

`POST_ACK` の `Issue` を `consumer` が承認する時に発行するイベントです。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `issueId` | 承認した `Issue` です。 |
| `consumer` | `effects` と `obligation` を固定した主体です。 |

このログで誰が承認したかを追えますが、確定した `effects`、 `disposition`、 `obligation`、 `consumer deadline` は `getIssue` 関数で読みます。

#### `IssueWithdrawn`

```js
event IssueWithdrawn(bytes32 indexed issueId, T.Reason reason);
```

投票前の取消または未承認期限切れで `Issue` を終了する時に発行するイベントです。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `issueId` | 終了した `Issue` です。 |
| `reason` | `NONE` または `NO_ACCEPTANCE` です。 |

このログで取消または未承認終了を追えますが、取消前に固定されていた `Subject`、 `question`、 `consumer` 提案値は `getIssue` 関数で補います。

#### `BallotCast`

```js
event BallotCast(bytes32 indexed issueId, uint32 indexed round, uint32 indexed body, address voter, bool approve, bool signed);
```

1票を受理して `Body` の票数を更新する時に発行するイベントです。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `issueId` | 投票対象の `Issue` です。 |
| `round` | 1始まりの `round` 番号です。 |
| `body` | `Body` 番号です。 |
| `voter` | 投票者です。 |
| `approve` | 賛成なら `true` です。 |
| `signed` | `relayer` が署名票を提出した場合に `true` です。 |

このログで各 `voter` の受理票を検索できますが、 `Body` の累計票、 `threshold` 到達時刻、他 `member` の未投票状態は `getBodyState` 関数と `bodyStatus` 関数で読みます。

#### `BodyResultSubmitted`

```js
event BodyResultSubmitted(bytes32 indexed issueId, uint32 indexed round, uint32 indexed body, address submitter, T.NodeStatus status);
```

固定報告者が `Body` 結果を提出する時に発行するイベントです。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `issueId` | 対象 `Issue` です。 |
| `round` | 対象 `round` です。 |
| `body` | 対象 `Body` です。 |
| `submitter` | `Policy` へ固定された報告者です。 |
| `status` | `Yes`、 `No`、 `NoDecision` の提出値です。 |

このログで報告者の提出を追えますが、提出済み `flag`、形成時刻、合成後の `root` 判断は `getBodyState` 関数と `nodeStatus` 関数で補います。

#### `RoundSettled`

```js
event RoundSettled(bytes32 indexed issueId, uint32 indexed round, T.NodeStatus status, T.Reason reason, uint64 at, uint64 appealOpenUntil);
```

`root Node` の値を `round` 結果として保存する時に発行するイベントです。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `issueId` | 対象 `Issue` です。 |
| `round` | `settle` した `round` です。 |
| `status` | `root` の四値判断です。 |
| `reason` | `NoDecision` や `VETO` の理由です。 |
| `at` | 値が形成された時刻です。 |
| `appealOpenUntil` | 異議申立て期限です。 |

このログで `settle` 結果を追えますが、 `round` 開始・終了時刻、 `Issue` の現在 `state`、以前の `round` 履歴は `getRound` 関数と `getIssue` 関数で読みます。

#### `Appealed`

```js
event Appealed(bytes32 indexed issueId, uint32 indexed newRound, address indexed by);
```

異議申立てを受け付け、新しい `round` を開始する時に発行するイベントです。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `issueId` | 対象 `Issue` です。 |
| `newRound` | 新しく開始した `round` 番号です。 |
| `by` | 異議申立てを送った `address` です。 |

このログで新 `round` の開始を追えますが、新 `round` の `deadline`、各 `Body` の票数、以前の判断を採用するかは `getRound` 関数、 `getBodyState` 関数、 `getResult` 関数で補います。

#### `Finalized`

```js
event Finalized(bytes32 indexed issueId, T.OutcomeType outcomeType, bool outcomeYes, T.Reason reason, uint32 sourceRound);
```

`Issue` の最終結果を保存する時に発行するイベントです。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `issueId` | `Final` になった `Issue` です。 |
| `outcomeType` | `Decided` または `NoDecision` です。 |
| `outcomeYes` | `Decided` 時の `Yes` または `No` です。 |
| `reason` | `NoDecision` または採用判断の理由です。 |
| `sourceRound` | 採用した判断を作った `round` です。 |

このログで最終判断と採用 `round` を追えますが、 `consumer`、 `effects`、 `final` 時刻、以前の `round` を採用した `flag` は `getResult` 関数で読みます。

#### `EnactmentRecorded`

```js
event EnactmentRecorded(bytes32 indexed issueId, address indexed consumer, bytes32 indexed effectId, T.EnactmentStatus status, address reporter, bytes32 ref);
```

`consumer` が業務 `effect` の実行結果を報告する時に発行するイベントです。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `issueId` | 元の `Issue` です。 |
| `consumer` | `effect` を実行した主体です。 |
| `effectId` | `Yes` または `No` の `effect` 参照です。 |
| `status` | `Enacted` または `Failed` です。 |
| `reporter` | 報告 `transaction` の送信者です。 |
| `ref` | 外部実行を参照する値です。 |

このログで `consumer` の実行報告を追えますが、業務コントラクト側の現在ステートや実行 `transaction` の成功内容は `registry` だけでは復元できず、 `getEnactment` 関数と `target` 側の記録を照合します。

#### `Evidence`

```js
event Evidence(bytes32 indexed issueId, address indexed party, string evidenceURI);
```

`Issue` へ証拠 `URI` を添付する時に発行するイベントです。

- **フィールド**

| フィールド | 詳細 |
| --- | --- |
| `issueId` | 証拠を関連付ける `Issue` です。 |
| `party` | `URI` を提出した `address` です。 |
| `evidenceURI` | 証拠を参照する文字列です。 |

`registry` は `URI` の内容や真実性を検証しません。

#### `supportsInterface`

```js
function supportsInterface(bytes4 interfaceId) external view returns (bool);
```

`Issue Registry` が指定 `interface` へ対応するかを返す関数です。

1. **`ID` を受け取る**

    `client` が必要な `interface ID` を渡す。

2. **対応状況を確認する**

    実装済み `ID` と比較する。

3. **真偽値を返す**

    対応時は `true` を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `interfaceId` | 確認する4-byte `ID` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `bool` | 対応状況です。 |

- **実行条件**

- `view` 関数で `storage` を更新しません。
- 未対応 `ID` では `false` を返します。

#### `policies`

```js
function policies() external view returns (IACDFPolicyRegistry);
```

`Issue Registry` が参照する `Policy Registry` を返す関数です。

1. **保存済み `address` を読む**

    デプロイ時に固定した `Policy Registry` を取得する。

2. **`interface` 型で返す**

    `client` が `Policy` 内容を読み出せる値を返す。

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `IACDFPolicyRegistry` | 対応する `Policy Registry` です。 |

- **実行条件**

- `view` 関数で `storage` を更新しません。
- `client` は返された `registry` のERC165対応も確認します。

#### `registerStandingAcceptance`

```js
function registerStandingAcceptance(T.StandingAcceptanceInput calldata input) external returns (bytes32 acceptanceId);
```

`consumer` が将来の `Issue` へ適用する受入れ条件を事前登録する関数です。

1. **条件を検証する**

    登録済み `Policy` と、 `zero` ではない2つの `effect ID` を確認する。

2. **`consumer` を固定する**

    `msg.sender` を `consumer` として `acceptance` を保存する。

3. **識別子を返す**

    `filer` が `file` 関数で参照する `acceptanceId` を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `input` | `Policy`、対象、質問、 `filer`、 `effects`、義務、期限です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `acceptanceId` | 保存した事前登録の識別子です。 |

- **実行条件**

- `consumer` 自身が呼び出します。
- 登録済みの正確な `policyId` を指定します。
- 未登録 `policyId` または `zero effect` を拒否します。
- 期限と `filer` 一覧は、 `file` 関数が事前登録を使用する時に照合します。

#### `revokeStandingAcceptance`

```js
function revokeStandingAcceptance(bytes32 acceptanceId) external;
```

`consumer` が未使用の将来分を含む事前登録を取り消す関数です。

1. **登録を読む**

    `acceptanceId` に対応する `consumer` を取得する。

2. **`caller` を確認する**

    登録した `consumer` と `msg.sender` を比較する。

3. **取消を保存する**

    `revoked` 状態を記録してイベントを発行する。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `acceptanceId` | 取り消す事前登録です。 |

- **実行条件**

- 登録した `consumer` だけが呼び出せます。
- 取消前に `admit` された `Issue` は継続します。
- 不明 `ID` は、保存済み `consumer` と一致しないため拒否します。
- 同じ `consumer` による再取消は許可され、 `revoked` を再び保存してイベントを発行します。

#### `file`

```js
function file(T.IssueInput calldata input) external returns (bytes32 issueId);
```

判断対象と `consumer commitment` を受け取り、新しい `Issue` を作る関数です。

1. **`mode` 別に入力を検証する**

    `consumer` 自身の作成、事前登録、事後承認のどれを使うか確認する。

2. **`Issue` を保存する**

    `Subject`、 `question`、 `Policy`、提案または確定済み `effects` を保存する。

3. **開始または承認待ちにする**

    `commitment` 成立済みなら `admit` し、 `POST_ACK` なら `Filed` で待つ。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `input` | `Policy`、 `mode`、 `consumer`、 `Subject`、 `question`、 `effects`、義務、期限です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `issueId` | 作成した `Issue` の識別子です。 |

- **実行条件**

- `Policy` は登録済みである必要があります。
- `CONSUMER_FILED` では `consumer` と `caller` を一致させます。
- `STANDING_ACCEPTANCE` では `target`、 `question`、 `filer`、期限を登録条件と照合します。
- `Binding Issue` では同じ `obligationKey` の進行中 `Issue` を拒否します。

#### `acknowledge`

```js
function acknowledge(bytes32 issueId, bytes32 effectYes, bytes32 effectNo, bytes32 disposition, bytes32 obligationId, uint64 consumerDeadline) external;
```

`consumer` が `POST_ACK Issue` の `effects` と義務を投票前に固定する関数です。

1. **`Issue` と期限を確認する**

    `Filed`、 `POST_ACK`、 `ack window` 内であることを確認する。

2. **`consumer` の値を固定する**

    `caller`、 `effects`、 `disposition`、 `obligation`、 `consumer deadline` を保存する。

3. **`round` 1を開始する**

    `Binding` として `admit` し、 `hard deadline` を計算する。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 承認する `Issue` です。 |
| `effectYes` | `Yes` 時の `effect` です。 |
| `effectNo` | `No` 時の `effect` です。 |
| `disposition` | `NoDecision` 時の処理です。 |
| `obligationId` | `consumer` が固定する義務範囲です。 |
| `consumerDeadline` | `consumer` 側の実行期限です。 |

- **実行条件**

- `Issue` に指定された `consumer` だけが呼び出せます。
- `ack window` を過ぎた呼び出しを拒否します。
- 同じ `obligation` の進行中 `Issue` があれば拒否します。

#### `admitAdvisory`

```js
function admitAdvisory(bytes32 issueId) external;
```

未承認の `POST_ACK Issue` を業務拘束のない `Advisory` として開始する関数です。

1. **`Policy` 設定を確認する**

    `allowAdvisory` が有効かを読む。

2. **`Issue` 状態を確認する**

    `Filed` のまま `ack window` を過ぎたことを確認する。

3. **`Advisory` で開始する**

    `consumer effects` を拘束せず `round` 1を開く。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | `Advisory` として開始する `Issue` です。 |

- **実行条件**

- `Policy` が `Advisory` を許可していることを確認します。
- `ack window` 終了後は誰でも呼び出せます。
- すでに `acknowledge`、 `withdraw`、 `admit` された `Issue` を拒否します。
- `Advisory` は `active obligation` を作りません。

#### `expireUnacknowledged`

```js
function expireUnacknowledged(bytes32 issueId) external;
```

`ack window` を過ぎた未承認 `Issue` を終了する関数です。

1. **`Issue` を確認する**

    `Filed` の `POST_ACK` であることを確認する。

2. **期限を確認する**

    `ack window` が終了したかを `clock` で判定する。

3. **`Withdrawn` へ移す**

    `NO_ACCEPTANCE` を記録して終了イベントを発行する。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 期限切れにする `Issue` です。 |

- **実行条件**

- 誰でも呼び出せます。
- 期限前または承認済みの `Issue` を拒否します。
- `Policy` の `allowAdvisory` が `false` の場合だけ成功します。
- `allowAdvisory` が `true` なら、期限後に `admitAdvisory` 関数を使います。

#### `withdraw`

```js
function withdraw(bytes32 issueId) external;
```

`filer` が投票開始前の `Issue` を取り消す関数です。

1. **`caller` を確認する**

    `Issue` の `filer` と `msg.sender` を比較する。

2. **状態を確認する**

    `Issue` が `Filed` であることを確認する。

3. **取消を保存する**

    `Withdrawn` へ移してイベントを発行する。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 取り消す `Issue` です。 |

- **実行条件**

- `filer` だけが呼び出せます。
- `admission` 後の `Issue` は取り消せません。

#### `submitEvidence`

```js
function submitEvidence(bytes32 issueId, string calldata evidenceURI) external;
```

`Issue` へ証拠を参照する `URI` をイベントとして添付する関数です。

1. **`Issue` 状態を確認する**

    `Filed`、 `Deciding`、 `Provisional` のいずれかであることを確認する。

2. **`URI` を受け取る**

    `caller` が指定した証拠参照を受け取る。

3. **イベントを発行する**

    `Issue`、 `caller`、 `URI` をイベントログへ記録する。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 証拠を関連付ける `Issue` です。 |
| `evidenceURI` | `off-chain` 証拠を参照する文字列です。 |

- **実行条件**

- `Final` または `Withdrawn` では受け付けません。
- `registry` は `URI` の内容を取得、検証、実行しません。

#### `castBallot`

```js
function castBallot(bytes32 issueId, uint32 round, uint32 body, bool approve) external;
```

`voter` 自身が1票をオンチェーンで提出する関数です。

1. **対象と資格を確認する**

    `Issue`、現在 `round`、 `Body`、 `member`、 `window` を確認する。

2. **重複を確認する**

    同じ `voter` が同じ `Issue`、 `round`、 `Body` へ未投票かを確認する。

3. **票数を更新する**

    賛成または反対の `count` と投票済み `key` を保存し、イベントを発行する。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 投票対象の `Issue` です。 |
| `round` | 1始まりの `round` 番号です。 |
| `body` | `Policy` 内の `Body` 番号です。 |
| `approve` | 賛成なら `true`、反対なら `false` です。 |

- **実行条件**

- `Body` は `ON_CHAIN_TALLY` である必要があります。
- `caller` は `Body member` に含まれる必要があります。
- 期限後、重複投票、確定済み `Body` を拒否します。

#### `submitSignedBallots`

```js
function submitSignedBallots(bytes32 issueId, uint32 body, address[] calldata voters, bool[] calldata approves, bytes[] calldata signatures) external;
```

複数のEIP712署名票を任意の `relayer` がまとめて提出する関数です。

1. **配列と現在 `round` を確認する**

    三配列の長さ、 `Issue` 状態、 `Body` 方式、 `window` を確認する。

2. **各署名票を検証する**

    `voter`、 `roster`、重複、 `digest`、 `EOA` またはERC1271署名を確認する。

3. **`batch` 全体を保存する**

    全件が有効な場合だけ各票を `count` し、イベントを発行する。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 投票対象の `Issue` です。 |
| `body` | 署名投票を受け付ける `Body` です。 |
| `voters` | 各署名者です。 |
| `approves` | 各署名者の賛否です。 |
| `signatures` | 各EIP712署名です。 |

- **実行条件**

- `Body` は `SIGNED_BALLOTS` である必要があります。
- 署名 `digest` には現在 `round` が入ります。
- 1件でも無効なら `batch` 全体を `revert` し、票数を変更しません。

#### `submitBodyResult`

```js
function submitBodyResult(bytes32 issueId, uint32 round, uint32 body, T.NodeStatus status) external;
```

固定報告者が `Body` の `Yes`、 `No`、 `NoDecision` を提出する関数です。

1. **報告者を確認する**

    `Policy` の `Body member` と `caller` を比較する。

2. **`round` と `window` を確認する**

    現在 `round`、未提出、期限内であることを確認する。

3. **結果を保存する**

    `status`、提出済み `flag`、時刻を保存してイベントを発行する。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 対象 `Issue` です。 |
| `round` | 対象 `round` です。 |
| `body` | 固定報告者方式の `Body` です。 |
| `status` | `Yes`、 `No`、 `NoDecision` です。 |

- **実行条件**

- `Body` は `AUTHORIZED_SUBMITTER` 方式である必要があります。
- `Pending` の提出、再提出、期限後提出を拒否します。

#### `settleRound`

```js
function settleRound(bytes32 issueId) external;
```

現在 `round` の `root Node` を評価し、 `round` 結果を保存する関数です。

1. **`root status` を計算する**

    `Body` の票数、提出値、 `window` と合成 `tree` を評価する。

2. **`formation instant` を計算する**

    結果が変わらなくなった時刻を票または期限から求める。

3. **状態を進める**

    `appeal` 可能なら `Provisional`、対象外なら `Final` へ移す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | `settle` する `Issue` です。 |

- **実行条件**

- 誰でも呼び出せます。
- `Deciding` で `root` が `Pending` ではないことを確認します。
- `settle` 済み `round` を拒否します。

#### `appeal`

```js
function appeal(bytes32 issueId) external;
```

`Provisional` 結果へ異議を申し立て、新しい `round` を開始する関数です。

1. **`standing` と期限を確認する**

    `caller`、 `appeal window`、対象 `outcome`、最大回数を確認する。

2. **前判断の扱いを記録する**

    `Policy` の `appeal mode` に従い、後の採用候補を保持または失効させる。

3. **新しい `round` を開く**

    `round count` を増やし、開始時刻と `deadline` を保存する。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 異議申立て対象の `Issue` です。 |

- **実行条件**

- `Issue` は `Provisional` である必要があります。
- `standing` が限定される `Policy` では `consumer` または `filer` だけが呼び出せます。
- `appeal window` 後、最大回数到達後、 `hard deadline` 超過後を拒否します。

#### `finalize`

```js
function finalize(bytes32 issueId) external;
```

異議申立て期間が終了した `Provisional Issue` を `Final` へ移す関数です。

1. **期限を確認する**

    `appeal window` が終了したかを `clock` で判定する。

2. **採用 `round` を決める**

    最終 `round` と `appeal mode` から採用する `Yes`、 `No`、 `NoDecision` を選ぶ。

3. **`Final` 結果を保存する**

    `outcome`、 `reason`、 `source round`、時刻を保存して `obligation` を解放する。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | `Final` にする `Issue` です。 |

- **実行条件**

- 誰でも呼び出せます。
- `Issue` は `Provisional` である必要があります。
- `appeal window` 終了前は拒否します。

#### `enforceHardDeadline`

```js
function enforceHardDeadline(bytes32 issueId) external;
```

手続き全体の上限時刻を過ぎた `Issue` を、現在までに形成された判断と `appeal mode` に基づいて `Final` へ移す関数です。

1. **`hard deadline` を確認する**

    現在 `clock` が保存済み上限を過ぎたかを判定する。

2. **進行中 `round` の結果を保存する**

    `Deciding` なら現在の `root` を読み、 `Yes` または `No` はその判断を保存する。
    `Pending` の場合だけ `NoDecision(TOTAL_TIMEOUT)` を保存する。
    `Provisional` なら保存済み `round` をそのまま使う。

3. **`Issue` を `Final` にする**

    `appeal mode` の採用規則で `Decided` または `NoDecision` を選び、 `active obligation` を解放する。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 強制終了する `Issue` です。 |

- **実行条件**

- 誰でも呼び出せます。
- `hard deadline` 以前は拒否します。
- `Final` または `Withdrawn` では実行できません。

#### `recordEnactment`

```js
function recordEnactment(bytes32 issueId, bytes32 effectId, T.EnactmentStatus status, bytes32 ref) external;
```

`consumer` が `Final decision` を業務コントラクトへ反映した結果を記録する関数です。

1. **`Issue` と `consumer` を確認する**

    `Binding` かつ `Final` であり、 `caller` が `consumer` かを確認する。

2. **`effect` と `status` を確認する**

    登録済みの `effectYes` または `effectNo` と一致する `effect ID` と、 `Enacted` または `Failed` を確認する。

3. **実行報告を保存する**

    `status`、 `reporter`、 `ref`、時刻を保存してイベントを発行する。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 元の `Issue` です。 |
| `effectId` | 実行した `Yes` または `No effect` です。 |
| `status` | `Enacted` または `Failed` です。 |
| `ref` | 外部 `transaction` や業務記録の参照です。 |

- **実行条件**

- `consumer` だけが呼び出せます。
- `Advisory` では記録できません。
- `Issue Registry` は `outcomeType` と `effect ID` の対応を検証せず、 `Final-NoDecision` も一律には拒否しません。
- 採用結果に対応する `effect ID` の選択と、 `NoDecision` 時に `disposition` を適用する処理は `consumer` が担います。
- `Failed` から `Enacted` へ更新できますが、 `Enacted` 後の変更は拒否します。

#### `getResult`

```js
function getResult(bytes32 issueId) external view returns (T.Result memory);
```

`Issue` の現在状態と、 `Final` 後の判断・ `effects` を返す関数です。

1. **`Issue` を読む**

    保存済み `state`、 `outcome`、 `consumer`、 `effects` を取得する。

2. **採用情報を組み立てる**

    `source round` と以前の `round` 採用 `flag` を計算する。

3. **`Result` を返す**

    `client` が `effect` 実行条件を判定できる構造体を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 読み出す `Issue` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `Result` | `procedure`、 `outcome`、 `Policy`、 `consumer`、 `effects` です。 |

- **実行条件**

- `view` 関数で `storage` を更新しません。
- `Final` 前は `outcomeType` が `None` です。

#### `getIssue`

```js
function getIssue(bytes32 issueId) external view returns (T.Issue memory);
```

`Issue` へ固定した全値と現在状態を返す関数です。

1. **識別子を受け取る**

    `client` が確認する `Issue` を指定する。

2. **保存値を読む**

    `Subject`、 `question`、 `Policy`、 `consumer`、期限、結果を取得する。

3. **`Issue` を返す**

    `admission` 時の固定値と現在状態を同じ構造体で返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 読み出す `Issue` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `Issue` | `Issue` の保存内容です。 |

- **実行条件**

- `view` 関数で `storage` を更新しません。
- 不明 `ID` の扱いは実装の `read` 規則に従います。

#### `getRound`

```js
function getRound(bytes32 issueId, uint32 round) external view returns (T.RoundState memory);
```

指定 `round` の開始、期限、 `settle` 結果、異議申立て期限を返す関数です。

1. **`Issue` と `round` を指定する**

    履歴を確認する位置を渡す。

2. **`RoundState` を読む**

    保存済み時刻、 `status`、 `reason` を取得する。

3. **構造体を返す**

    `client` が形成時刻と `settle` 時刻を混同せず確認できる値を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 対象 `Issue` です。 |
| `round` | 1始まりの `round` 番号です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `RoundState` | 指定 `round` の保存内容です。 |

- **実行条件**

- 作成済み `round` を指定します。
- `view` 関数で `storage` を更新しません。

#### `getBodyState`

```js
function getBodyState(bytes32 issueId, uint32 round, uint32 body) external view returns (T.BodyState memory);
```

指定 `Body` の票数または固定報告者の提出値を返す関数です。

1. **位置を指定する**

    `Issue`、 `round`、 `Body` 番号を渡す。

2. **保存値を読む**

    `yes`、 `no`、提出 `flag`、提出 `status`、決定時刻を取得する。

3. **構造体を返す**

    `client` が `Body` 判断を再計算できる値を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 対象 `Issue` です。 |
| `round` | 対象 `round` です。 |
| `body` | 対象 `Body` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `BodyState` | `Body` の保存内容です。 |

- **実行条件**

- 有効な `round` と `Body` 番号を指定します。
- `view` 関数で `storage` を更新しません。

#### `bodyStatus`

```js
function bodyStatus(bytes32 issueId, uint32 round, uint32 body) external view returns (T.NodeStatus status, T.Reason reason, uint64 at);
```

保存票と `clock` から `Body` の現在判断を計算する関数です。

1. **`Body` 入力を読む**

    票数または固定報告者の提出値と `window` を取得する。

2. **四値を判定する**

    `threshold`、 `blocking threshold`、期限切れを評価する。

3. **判断を返す**

    `status`、 `reason`、値が形成された時刻を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 対象 `Issue` です。 |
| `round` | 対象 `round` です。 |
| `body` | 対象 `Body` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `status` | `Pending`、 `Yes`、 `No`、 `NoDecision` です。 |
| `reason` | `NoDecision` または `VETO` の理由です。 |
| `at` | 値が形成された時刻です。 |

- **実行条件**

- `view` 関数で `storage` を更新しません。
- 期限前で `threshold` 未達なら `Pending` です。

#### `nodeStatus`

```js
function nodeStatus(bytes32 issueId, uint32 round, uint32 node) external view returns (T.NodeStatus status, T.Reason reason, uint64 at);
```

指定 `Node` から下の `Body` と子 `Node` を評価し、現在の合成判断を返す関数です。

1. **`Node` を読む**

    `Policy` から `combinator` と参照先を取得する。

2. **子の四値を合成する**

    `ALL`、 `ANY`、 `K-of-M`、 `VETO` の規則を適用する。

3. **判断を返す**

    `status`、 `reason`、 `formation instant` を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 対象 `Issue` です。 |
| `round` | 対象 `round` です。 |
| `node` | 対象 `Node` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `status` | 合成後の四値判断です。 |
| `reason` | 合成結果の理由です。 |
| `at` | 合成値が形成された時刻です。 |

- **実行条件**

- 有効な `Node` 番号を指定します。
- `view` 関数で `storage` を更新しません。

#### `hasVoted`

```js
function hasVoted(bytes32 issueId, uint32 round, uint32 body, address voter) external view returns (bool);
```

指定 `voter` が同じ `Issue`、 `round`、 `Body` へ投票済みかを返す関数です。

1. **1票 `key` を作る**

    `Issue`、 `round`、 `Body`、 `voter` を組み合わせる。

2. **保存 `flag` を読む**

    受理済み投票の `mapping` を参照する。

3. **真偽値を返す**

    投票済みなら `true` を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 対象 `Issue` です。 |
| `round` | 対象 `round` です。 |
| `body` | 対象 `Body` です。 |
| `voter` | 確認する `address` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `bool` | 投票済みかを表します。 |

- **実行条件**

- 受理済み票だけが `true` になります。
- `view` 関数で `storage` を更新しません。

#### `activeIssueOf`

```js
function activeIssueOf(address consumer, bytes32 obligationKey) external view returns (bytes32);
```

`consumer` の同じ義務について、最後に保存した `Binding Issue` を返す関数です。

1. **`consumer` と `key` を指定する**

    並行 `Issue` を確認する組を渡す。

2. **`active mapping` を読む**

    `admission` で保存し、後続の `admission` まで残る値を取得する。

3. **`Issue ID` を返す**

    未登録なら `zero`、登録後は終端済みを含む最後の `Issue ID` を返す。

返却された `Issue` が `Final` または `Withdrawn` なら排他制約は解放済みで、後続の `admission` が新しい `Issue ID` へ上書きします。
呼出側は返却値だけで進行中と判断せず、 `getIssue` 関数で `state` も確認します。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `consumer` | 結果を実行する主体です。 |
| `obligationKey` | 義務と `question` から作る `key` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `bytes32` | 終端済みを含む最後の `Binding Issue ID` です。未登録なら `zero` です。 |

- **実行条件**

- `Advisory Issue` は `active mapping` へ入りません。
- `view` 関数で `storage` を更新しません。

#### `obligationKeyOf`

```js
function obligationKeyOf(address consumer, bytes32 obligationId, bytes32 question) external pure returns (bytes32);
```

同じ `consumer` の同じ質問に対する義務を識別する `key` を計算する関数です。

1. **3つの値を符号化する**

    `consumer`、 `obligation ID`、 `question` を `abi.encode` する。

2. **`hash` を計算する**

    符号化結果へ `keccak256` を適用する。

3. **`key` を返す**

    `activeIssueOf` で使う `bytes32` を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `consumer` | 結果を実行する主体です。 |
| `obligationId` | `consumer` が固定した義務範囲です。 |
| `question` | 判定する質問の `hash` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `bytes32` | `obligation key` です。 |

- **実行条件**

- `pure` 関数で `storage` を読み書きしません。
- `Binding Issue` では `zero` 以外の `obligationId` を使います。

#### `getEnactment`

```js
function getEnactment(bytes32 issueId, address consumer, bytes32 effectId) external view returns (T.Enactment memory);
```

`consumer` が報告した `effect` 実行結果を返す関数です。

1. **検索 `key` を指定する**

    `Issue`、 `consumer`、 `effect ID` を渡す。

2. **実行記録を読む**

    `status`、 `reporter`、 `ref`、時刻を取得する。

3. **`Enactment` を返す**

    実行成功、失敗、未報告を区別できる構造体を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 元の `Issue` です。 |
| `consumer` | `effect` を実行する主体です。 |
| `effectId` | `Yes` または `No` の `effect` です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `Enactment` | 実行報告の保存内容です。 |

- **実行条件**

- 記録がなければ `NotEnacted` 相当を返します。
- `view` 関数で `storage` を更新しません。

#### `ballotDigest`

```js
function ballotDigest(bytes32 issueId, uint32 round, uint32 body, address voter, bool approve) external view returns (bytes32);
```

署名投票で `wallet` と `registry` が共有するEIP712 `digest` を返す関数です。

1. **`ballot field` を符号化する**

    `Issue`、 `round`、 `Body`、 `voter`、賛否を型付き `data` へ入れる。

2. **`domain` を加える**

    `chain ID` と `Issue Registry address` を `domain` へ入れる。

3. **`digest` を返す**

    `voter` が署名し、 `registry` が検証する32-byte値を返す。

- **引数**

| 引数 | 詳細 |
| --- | --- |
| `issueId` | 投票対象の `Issue` です。 |
| `round` | 署名対象 `round` です。 |
| `body` | 署名対象 `Body` です。 |
| `voter` | 署名者です。 |
| `approve` | 賛成または反対です。 |

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `bytes32` | EIP712署名 `digest` です。 |

- **実行条件**

- `view` 関数で `registry address` と `chain ID` を読みます。
- 署名の有効性や投票資格はこの関数だけでは判定しません。

#### `clock`

```js
function clock() external view returns (uint48);
```

`Issue Registry` が期限計算に使う現在値を返す関数です。

1. **`clock source` を読む**

    実装が採用した `timestamp` または `block` 数を取得する。

2. **48-bit値へ変換する**

    ERC6372互換の型で表す。

3. **現在値を返す**

    `client` が `deadline` と比較できる値を返す。

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `uint48` | 現在の `contract clock` です。 |

- **実行条件**

- `view` 関数で `storage` を更新しません。
- 単位は `CLOCK_MODE` 関数で確認します。

#### `CLOCK_MODE`

```js
function CLOCK_MODE() external view returns (string memory);
```

`clock` 関数の単位と `mode` を文字列で返す関数です。

1. **`mode` を読む**

    実装が固定した `clock mode` を取得する。

2. **文字列を返す**

    `client` が `deadline` を同じ単位で比較できる形式を返す。

- **戻り値**

| 戻り値 | 詳細 |
| --- | --- |
| `string` | ERC6372の `clock mode` です。 |

- **実行条件**

- 参考実装は `timestamp` 秒を使います。
- `block` 数と秒を暗黙に変換しません。

期限計算に使う `clock` 値と単位を公開する `interface` がERC6372です。
`client` は `clock` 関数と `CLOCK_MODE` 関数を読み、 `Body window`、 `appeal window`、 `hard deadline` を同じ単位で比較します。

:::message
**ERC6372**については以下の記事を参考にしてください。
:::

[\[ERC6372\] コントラクトが使用している時間表現を取得する仕組みを理解しよう！](/articles/eacc6a32cbac790df52d)

### Errors

規格は、拒否原因を5つの分類に分け、 `reason string` または同等の `custom error` で区別することを推奨します。
文字列そのものは相互運用要件ではありません。
参考実装の `revert strings` は、これらの分類を追跡するための実装上の手掛かりです。

| 分類 | 拒否する例 | 失敗時 |
| --- | --- | --- |
| `Policy validation` | `Body` 数超過、 `member` 重複、循環 `tree`、時間上限不足 | `Policy` を保存しない |
| `Issues and acceptance` | 未登録 `Policy`、不一致な事前登録、期限切れ、 `active obligation` | `Issue` を `admit` しない |
| `Voting` | 非 `member`、重複票、期限後、誤 `round`、無効署名 | 票数を変更しない |
| `Rounds and finality` | `root` が `Pending`、 `appeal` 期限外、最大回数超過、 `hard deadline` 前 | `procedure state` を変更しない |
| `Enactment` | `Final` 前、 `NoDecision`、誤 `consumer`、誤 `effect`、 `Enacted` 後の更新 | 実行記録を変更しない |

どの分類でも `revert` した `transaction` の `storage` 更新とイベント発行はすべて戻ります。

### ClockとProfile

最小 `profile` は、固定 `roster` の `K-of-N` 投票、オンチェーン集計、1つの `Body`、異議申立てなしです。
署名投票、固定報告者、複数 `Body` の合成、 `VETO`、異議申立ては任意 `profile` として追加できます。

実装は、対応 `interface ID`、 `Policy` 内の `Body` 方式、 `clock mode` を公開します。
`client` はこれらを読み、必要な `profile` へ対応できない `registry` や `Policy` を採用候補から外します。

参加資格の算出、ランダム選出、重み付き投票、二値以外の結果、手数料、 `executor`、 `privacy`、 `multi-consumer`、 `cross-chain receipt` の検証は予約領域です。
ERC8436の `kernel` は、これらを実装済みとして扱いません。

## 補足

#### 実行する規則そのものを `hash` 化する理由

`Policy Registry` は、説明用ファイルの `hash` だけでなく、実際に実行する `PolicySpec` 全体から `policyId` を計算します。
参加者、閾値、合成 `tree`、時間、異議申立てのどれかが変われば、別の `policyId` になります。

コントラクト `address` や `code hash` だけでは、 `Upgradeable proxy` の実装差し替えや、外部 `storage` にある閾値変更を検出できません。
ERC8436の参考実装は `non-upgradeable` で、実行 `parameters` を `Policy Registry` へ保存します。

プロキシの `implementation`、 `beacon`、 `admin` を標準 `slot` から読む方法がERC1967です。
デプロイ監査では、 `slot` だけでなく、実装を交換できる `authority` と `delegatecall` 経路も確認します。

:::message
**ERC1967**については以下の記事を参考にしてください。
:::

[\[ERC1967\] コントラクトのアップグレードの仕組みについて理解しよう！](/articles/8a46bae9f23f2315ba4e)

#### `subject` と `obligation` を分ける理由

`Subject` は何を評価するかを表します。
一方、 `obligation` は `consumer` のどの業務上の義務について、同時に1件だけ `Binding Issue` を許すかを表します。

成果物ごとに `nonce` を変えて `obligation` を毎回別物にすると、同じ質問を複数 `panel` へ出せます。
反対に、全作業へ同じ `obligation` を割り当てると、独立した成果物の判定まで止まります。
`adapter` は作業コントラクトの `task ID`、 `submission ID`、 `version` から安定した範囲を作ります。

#### 四値合成と `appeal mode`

`Yes` と `No` だけで合成すると、入力受付中と判断不能を区別できません。
ERC8436は `Pending`、 `Yes`、 `No`、 `NoDecision` を保ったまま、 `ALL`、 `ANY`、 `K-of-M`、 `VETO` を評価します。

異議申立て後も同じ区別を使います。
`PRESERVE_UNLESS_OVERTURNED` では、 `appeal round` の `NoDecision` が前判断を消しません。
`REQUIRE_FRESH_DECISION` では、 `appeal` 開始時に前判断を採用候補から外すため、新 `round` の `NoDecision` が `Issue` 全体の `NoDecision` になります。

#### `decision` と `execution` の境界

`Final decision` は、 `consumer` へ新しい `authority` を与えません。
`consumer` は、元から操作できる業務コントラクトの `current state` と期限を確認し、自分の権限で `effect` を呼び出します。

`effect call` が失敗しても、 `Issue Registry` の `decision` は変更しません。
`Failed enactment` を記録し、再試行、既定処理、手動回復のどれを使うかは `consumer` 側の規則で決めます。

#### 規格が予約する拡張

`agent identity` や `reputation` は、 `roster` 候補を選ぶ入力にできます。
ERC8004は `agent identity` と `trust signal` を表しますが、ERC8436の投票資格や `consumer authority` を自動で作りません。

:::message
**ERC8004**については以下の記事を参考にしてください。
:::

[\[ERC8004\] トラストレスに動くエージェントの仕組みを理解しよう！](/articles/4958873135ba9892c298)

`weighted vote`、 `sortition`、 `non-binary outcome`、 `privacy`、 `fee`、 `executor`、 `cross-chain result carriage` を追加する場合は、 `Policy ID` へ影響する `parameters` と検証規則を別 `profile` で固定します。

## テストケース

#### 公式 `vector` で固定する計算

公式 `assets` には、3種類の `Policy ID`、 `ballot digest`、83行の `K-of-N` 判定、192行の `composition` 判定、2つの `interface ID` があります。
実装者は、自分の `ABI encoding`、 `EIP712 domain`、四値合成、ERC165 `ID` を公式値と比較できます。

| `vector` | 確認する内容 |
| --- | --- |
| `policy-id.json` | `PolicySpec` 全体から同じ `policyId` を作れるか |
| `ballot-digest.json` | `chain`、 `registry`、 `Issue`、 `round`、 `Body`、 `voter`、賛否から同じ `digest` を作れるか |
| `kofn-tally.json` | `Yes`、 `No`、 `Pending`、 `NoDecision` の閾値境界 |
| `composition.json` | `ALL`、 `ANY`、2-of-3の四値合成 |
| `interface-ids.json` | 二 `interface` の `selector XOR` |

#### `integration evidence` と非保証範囲

公式 `discussion` の `Revision` 3では、現行の `Body role validation`、 `VETO` と `ALL` の合成、オンチェーン投票、 `reject path` を `Sepolia` で実行した記録があります。
参考実装の `Issue Registry` は `IR pipeline` で23,514 `bytes` となり、EIP170の24,576-byte上限内ですが余裕は1,062 `bytes` です。

:::message
**EIP170**については以下の記事を参考にしてください。
:::

[\[EIP170\] コントラクト作成時にサイズ制限を設ける提案を理解しよう！](/articles/eef7d90d07aae21dfe6f)

これらは `encoding` と確認済み経路の証拠です。
第三者による本番採用、全 `profile` の `public network` 検証、 `executor` の稼働、 `roster` の独立性、投票内容の真実性までは保証しません。

## 互換性

#### 既存 `contract` への `adapter` 接続

単一 `evaluator` や `arbitrator address` を受け取る既存コントラクトは、その `address` へERC8436 `adapter` を指定できます。
`adapter` が `Issue` を開き、 `Final` 結果を既存の `accept` または `reject API` へ変換するため、既存コントラクトの `interface` を変更せず接続できます。

ERC8183は、 `client`、 `provider`、 `evaluator` が作業を進める `agent commerce` の状態を定めます。
その単一 `evaluator slot` へ `adapter` を置くと、複数 `Body` の `decision` を1つの評価結果へ変換できます。
この読み替えにより、対応 `API` を選びます。

:::message
**ERC8183**については以下の記事を参考にしてください。
:::

[\[ERC8183\] 依頼の報酬をエスクローで支払う仕組み](/articles/44e076586c6faeabfb3b)

#### 関連規格から読む値

`client` はERC165 `ID` から対応 `API`、 `EIP712 domain` から署名対象、ERC1271 `magic value` から `contract voter` の署名、ERC6372 `clock mode` から期限単位を確認します。
これらを1つでも暗黙値にすると、別 `registry` 向け署名や異なる時間単位を誤って受理します。

ERC8033は、特定 `query` へ情報 `agent` と `judge` を配置する `council oracle` を定めます。
複数の異なる `Body`、 `consumer commitment`、 `NoDecision`、異議申立てを1つの `procedure record` へ固定する用途ではERC8436を使い分けます。

#### `Draft` と `policy version` の固定

ERC8436は `Draft` であり、 `interface` や `Policy layout` が変わる可能性があります。
`consumer` は**`family` の最新版**だけを条件にせず、監査した `registry instance` と正確な `policyId` を固定します。

新しい `Policy version` を登録しても、既存 `Issue` と `standing acceptance` は自動移行しません。
移行時は、新旧 `Policy` の `Body`、 `Node`、 `timing`、 `appeal mode` を比較し、 `consumer` が新しい `policyId` を明示的に受け入れます。

## 参考実装

2つの `registry` は `decision record` を保存し、 `consumer adapter` だけが業務コントラクトへ `effect` を呼び出します。

![ERC8436のPolicy Registry・Issue Registry・consumer adapter・業務コントラクトの責任境界](/images/qiita-bf23ab81-e09e-4af8-a1e1-48478c33ce25.webp)

#### 二 `registry` の保存・評価境界

`ACDFPolicyRegistry` コントラクトは、検証済み `PolicySpec` と `family version` を保存します。
`ACDFRegistry` コントラクトは、その正確な `Policy` を参照して `Issue`、 `round`、 `ballot`、 `result`、 `enactment` を保存します。
後者の保存領域を、以下では `ACDFRegistry acceptance and issue mappings` として整理します。

#### 署名検証の境界

`SignatureChecker` ライブラリは、ERC1271と `canonical ECDSA` の署名妥当性だけを返します。
`member` 資格、現在 `round`、期限、重複票は `ACDFRegistry` コントラクトが別に確認します。

#### `consumer adapter` の実行境界

`ACDFTaskTenderAdapter` コントラクトは、 `Final-Decided` を作業コントラクトの `accept` または `reject` へ変換します。
`target call` の失敗を `catch` して `Failed` を記録しますが、 `decision` は変更しません。

#### `off-chain JSON Schema` と `authoritative registry` の境界

公式の `JSON Schema` は、 `Policy document` と `procedure receipt` を `off-chain tool` 間で交換する形式です。
`receipt` は `Filed`、 `Deciding`、 `Provisional`、 `Final`、 `Withdrawn` を運べ、 `Final` 前は `outcomeType` が `None` になります。
`effect` 実行には別途 `Final` を確認します。

`receipt` は `chain ID`、 `Policy Registry`、 `Issue Registry`、 `Issue ID` を持ちます。
`named registry` の `record` を照合する `carrier` であり、 `receipt` 自体がオンチェーンの正本になるわけではありません。

### `ACDFPolicyRegistry`

#### `policy` の検証と保存

`ACDFPolicyRegistry._validate` は、 `Body` 数、 `Node` 数、 `member`、K値、 `window`、 `tree`、 `Body role`、 `appeal`、 `duration` を確認します。
全検証が終わった後だけ、 `_store` 関数が `Policy` 全体、存在 `flag`、 `round duration`、 `family authority`、 `latest version` を保存します。
この入口と保存処理をまとめた経路が `ACDFPolicyRegistry.registerPolicy/_store` です。

次版では `caller`、 `previous`、 `version` を現在の `family` 情報と照合します。
途中で `revert` すると、どの `mapping` も更新されません。
検証条件は規格要件ですが、 `private helper` の分け方と `mapping layout` は参考実装固有です。

#### `policy` の読み取り

`policyHeader`、 `timingOf`、 `bodyOf`、 `nodeOf` の各関数から、実行に使う全 `parameters` を復元できます。
`off-chain descriptor` は `descriptorHash` で結び付きますが、投票判定へは使いません。

### `ACDFRegistry`

#### `issue` の `admission`

`file`、 `acknowledge`、 `admitAdvisory` の各関数は、 `acceptance mode` に応じて `consumer values` を選びます。
`_admit` 関数は `Policy`、 `active obligation`、 `consumer deadline`、 `hard deadline` を確認し、 `Issue` と `round` 1を同じ `transaction` で保存します。
参考実装の `file/acknowledge/_admit` 経路では、受付方法が違っても同じ `admission` 検証へ合流します。

`Issue ID` を `nonce` から作る方法と `private mapping` の配置は参考実装固有です。
`consumer commitment`、 `admission` で固定する値、 `Binding obligation` の排他性は規格要件です。

#### `ballot` と四値評価

`_castBallot` 関数は `member`、 `round`、 `window`、重複を確認して票数を更新します。
`_bodyStatus`、 `_nodeStatus`、 `_vetoStatus` の各関数は、保存票と `clock` から四値判断を計算します。
以下では、この評価経路を `_castBallot/_bodyStatus/_nodeStatus/_vetoStatus` と表記します。

無効票で `revert` した場合、投票済み `flag`、票数、イベントのすべてが残りません。
`K-of-N` と `composition semantics` は規格要件です。

#### `round` と `finalization`

`settleRound` 関数は `root status` と `formation instant` を保存します。
`appeal` 関数は `standing`、 `window`、最大回数を確認して新 `round` を開きます。
`finalize` 関数と `enforceHardDeadline` 関数は `appeal mode` と `hard cap` から採用結果を決めます。
これらの状態遷移と内部確定処理を合わせた経路が `settleRound/appeal/enforceHardDeadline/_finalize` です。

#### `enactment` の記録

`recordEnactment` 関数は `Final`、 `Binding`、 `consumer`、 `effect`、 `status` を確認します。
`Failed` から `Enacted` への更新は許しますが、 `Enacted` 後の変更は拒否します。
外部 `effect` の実行自体は行いません。

### `SignatureChecker`

#### `contract` 署名と `EOA` 署名

`SignatureChecker.isValid` は、署名者に `code` がある場合、ERC1271の `isValidSignature` 関数を `staticcall` します。
`call` 成功、32 `bytes` 以上の戻り値、 `0x1626ba7e` の `magic value` を確認します。

`EOA` 署名では65-byte長、 `low-s`、 `v` が27または28、 `recover` 結果が `zero` ではないことを確認します。
`malformed signature` や `revert` は `bool` の `false` になり、 `batch` 側が `transaction` 全体を `revert` します。
参考実装の `revert strings` は診断用であり、文字列自体は相互運用条件ではありません。

### `ACDFTaskTenderAdapter`

#### `issue` を開く処理

`ACDFTaskTenderAdapter.open` は、 `caller` 入力だけで `Subject` や `effects` を作りません。
作業コントラクトから `submission`、 `result hash`、 `task version`、提出期限、支払期限、 `accept` と `reject` の `effect ID` を読み取ります。

`adapter authority`、 `pending submission`、 `consumer deadline` を確認した後、 `consumer-filed Binding Issue` を作ります。
作業 `state` が変わっていれば `Issue` を開かず `revert` します。

#### `effect` を実行する処理

`ACDFTaskTenderAdapter.execute` は、 `Issue Registry` から `Final-Decided` を読み、現在の作業 `state` と期限を再確認します。
`Yes` なら `accept`、 `No` なら `reject` を `try` / `catch` で呼び出します。

成功時は `adapter state` を `enacted` へ更新し、 `Issue Registry` へ `Enacted` を報告します。
`target call` 失敗時は `Failed` と `reason event` を残し、再試行を許します。

### `JSONSchema`

#### `policy document` と `result receipt`

`policy-spec.schema.json` は、 `PolicySpec` の全 `field` を `off-chain tool` 間で交換する形式です。
`tool` は `schema validation` 後に全 `field` から `policyId` を再計算し、 `Policy Registry` から同じ `policyId` と `parameters` を読み、実行規則を照合します。

`result-receipt.schema.json` は、全 `procedure state`、 `outcome`、 `Policy`、 `Subject`、 `consumer`、 `effects`、 `enactment` を運びます。
`Final` 前の `receipt` 作成も許されますが、不可逆な `effect` を実行できるのは `registry` が `Final-Decided` を返した時だけです。
この2形式をまとめて参照する時は `policy-spec.schema.json / result-receipt.schema.json` と表記します。

## セキュリティ

#### `consumer authority` と `obligation scope`

`caller` が自己申告した `consumer address` だけで `Binding Issue` を作ると、第三者が権限外の `effect` を約束できます。
`consumer` 自身の `file`、事前登録、事後承認のいずれかを確認し、 `adapter` は業務コントラクトの `state` から `Subject` と `effects` を導出します。
`POST_ACK` で投票後の承認を許すと、 `consumer` は有利な結果だけを `Binding` へ変えられます。
そのため、 `acknowledgment` を `admission` と投票開始より前に確定し、未承認 `Issue` は `ack window` 終了後に `Advisory` または `Withdrawn` へ進めます。

`obligationId` を毎回変えると `panel shopping` を止められません。
広すぎる値を使うと独立した `Issue` まで塞ぐため、同じ業務義務を安定して識別する粒度を選びます。
さらに、 `admission` 後の `withdrawal` を許すと、不利な途中経過を捨てて別 `panel` へ出し直せます。
`withdrawal` は未 `admission` の `Filed` に限定し、投票開始後は `Final` または `hard deadline` まで同じ `Issue` を維持します。

#### `appeal` と期限

`PRESERVE_UNLESS_OVERTURNED` では、 `appeal round` を意図的に `NoDecision` へすると前判断が残ります。
求める安全性が**再審で新しい判断を必須にする**なら `REQUIRE_FRESH_DECISION` を選びます。

`permissionless` な `finalize` や `hard deadline enforcement` があっても、自動 `executor` ではありません。
期限後に `transaction` を送る `keeper`、 `consumer`、利用者の経路を運用で用意します。
`admission` 時は、全 `round` と `appeal` が `consumer deadline` 内に収まるよう `hardDeadline <= consumerDeadline` を確認します。
ただし、 `Final` が `consumer deadline` の直前に形成されると `effect` 実行の余裕はほとんど残らないため、 `adapter` の `arrival deadline` と `consumer deadline` の間に運用上の `buffer` を設けます。

#### 署名と `replay`

`EIP712 domain` は別 `chain` と別 `registry` への署名移動を防ぎます。
`Issue`、 `round`、 `Body`、 `voter`、賛否を署名対象へ含め、1票 `key` で再利用を止めます。

ERC1271の有効性は `contract state` で変わります。
`registry` が受理した票を後から無効化しないため、署名検証時点の `state` と `transaction` 確定性を監視します。
前提は、攻撃者が有効な署名を別の `Issue`、 `round`、 `Body`、 `chain`、 `registry` へ再送できることです。
`EIP712 domain` と `ballot field` のどれかが欠けると、意図しない案件へ同じ賛否が転用され、 `threshold` へ到達する可能性があります。
`domain`、全 `ballot field`、投票済み `key` を同時に照合し、ERC1271の `staticcall` が `revert` または不正な `magic value` を返した票は `batch` 全体で拒否します。
ただし、署名鍵の漏えい、 `contract wallet` の権限変更、確定前の `chain reorg` は `registry` だけでは防げません。

#### `roster` と `evidence`

異なる `address` が同じ `operator` に支配されていても、 `kernel` は検出しません。
`identity`、 `operator cap`、 `conflict exclusion`、 `stake`、 `reputation` は `roster` を作る外部 `profile` で確認します。

`Evidence event` の `URI` は信頼できない `data` として扱います。
取得した文書やAI向け `prompt` を命令として実行せず、 `content type`、 `size`、 `hash`、発行者を `consumer` 側で検証します。

#### `registry` と `chain` の信頼境界

`consumer` は、 `registry instance`、デプロイ済み `bytecode`、管理権限、正確な `Policy version` を固定します。
同じ `interface ID` でも、不正な実装が正しい `semantics` を保証するわけではありません。

`Issue` の `Final` は、対象 `chain` 上の `procedure finality` です。
`source chain` の `reorg` 耐性、 `bridge`、 `cross-chain receipt` の証明までは含みません。
攻撃者が同じ `interface ID` を返す偽 `registry` を配備すると、 `consumer` は存在しない投票や改変された結果を正規の `decision` として読まされます。
また、 `cross-chain receipt` を証明なしで受け入れると、別 `chain` の `Final` や古い `fork` の結果で `effect` が実行されます。
`deployment address`、 `runtime code`、 `proxy` 管理権限、 `Policy Registry`、 `Issue Registry`、 `chain ID` を `allowlist` へ固定し、 `receipt` は対象 `registry` の `read` 結果と照合します。
この対策後も、 `source chain` の `finality assumption`、 `bridge validator`、 `consumer` 自身の `upgrade authority` は信頼境界として残ります。

## 最後に

ERC8436は、複数のAIエージェントによる判断を、具体的な `Subject`、 `question`、 `Policy`、 `consumer commitment` へ結び付けます。
必要票へ届かなかった結果を `NoDecision` として残し、異議申立て後の採用規則も投票前に固定します。

`Policy Registry` は実行規則を保存し、 `Issue Registry` は投票、 `round`、最終結果、実行報告を保存します。
`consumer` だけが自分の `authority` 内で業務 `effect` を実行するため、投票資格と資産を動かす権限を分離できます。

導入時は、 `roster` の独立性、 `obligation` の粒度、 `appeal mode`、 `clock` 単位、 `keeper`、 `registry instance`、 `effect` 失敗後の回復を別々に確認してください。

他でも色々記事を書いているのでぜひよろしければ読んでいってください！

https://amzn.asia/d/gxvJ0Pw

https://cardene.notion.site/EIP-2a03fa3ea33d43baa9ed82288f98d4a9

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://twitter.com/cardene777

https://cardene.substack.com/

https://eips.ethereum.org/EIPS/eip-8436

https://ethereum-magicians.org/t/erc-8436-agent-collective-decision-framework/29850
