手形スキーマ
手形 (Tegata) スキーマ、バージョン 0.1 の完全なリファレンスです。手形は <policy-id>.tegata.json
という名前の JSON ドキュメントで、1 つの認可ポリシーを宣言します。本ページでは、すべてのフィールド・
演算子・固定された評価セマンティクスを記載します。
導入は クイックスタート と ポリシーを書く を参照してください。
正規フィールドとメタデータ
すべてのフィールドは 正規 (canonical) か メタデータ のいずれかです。
- 正規 (canonical) フィールドは、コンパイル対象となる認可仕様を構成します。
tegata_hashに 含まれます。 - メタデータ フィールドは、人間によるレビューと監査コンテキストのためのものです。ハッシュから 除外され、コンパイルされません。
実務上の帰結: ルールの並べ替えや description の書き換えは tegata_hash を変えませんが、role・
action・condition・effect の変更は変えます。
トップレベル構造
additionalProperties: false。必須: tegata、policy、rules。
| フィールド | 型 | 正規 | 備考 |
|---|---|---|---|
tegata | string | はい | スキーマバージョン。const "0.1"。 |
policy | object | はい | id 必須。下記参照。 |
attributes | object map | はい | 属性の型宣言。下記参照。 |
audit | object | いいえ | 人間・監査用メタデータ。コンパイルされない。下記参照。 |
rules | array | はい | minItems: 1。ルールオブジェクト 参照。 |
policy
| フィールド | 型 | 正規 | 備考 |
|---|---|---|---|
policy.id | string | はい | kebab-case ^[a-z][a-z0-9]*(-[a-z0-9]+)*$、maxLength: 64。 |
policy.description | string | いいえ | 自由記述。 |
attributes
object map(正規)です。キーは ^(subject|resource|environment|custom).<name...> に一致し、値は
"string" | "integer" | "boolean" です。ここで属性を宣言する必要があるのは、その型がルールの
リテラルから導出できない場合だけです(例: relationship でのみ使う属性)。
relationship でのみ参照される属性を宣言しない場合、その Tegata にはその属性の型の情報源が
ありません。宣言された型も無く、型を推論できる条件リテラルも無いからです。Sekisho は
まさにそれを警告として述べ(S14)、追加すべき宣言を提示します。例:
"attributes": { "subject.org_id": "string", "resource.org_id": "string" }。欠けている型を
供給できるのは書き手だけなので、この診断は Tegata の話で止まります。
型を持たない属性を生成 projection がどう表すかは、その projection の領分であり言語によって 異なります。コンパイラはここで consumer の言語の型名を挙げません。呼び出し側がそのプロパティの 静的チェックを失うことは変わらないので、表現の詳細は各言語の projection のリファレンスを参照して ください。
audit
メタデータ(コンパイルされない)です。フィールド:
| フィールド | 型 | 備考 |
|---|---|---|
source_text | string | 元の自然言語の要件。 |
normalized_text | string | 正規化した言い換え。 |
ambiguities | array | 解釈メモ。各要素: code、message、任意の related_rules[]。 |
解釈ノートはレビューの表面です。エージェントが自分で決めたことは、移行時のパリティ判断や
意味論差の受容も含めて、すべてここに書きます。description だけに書いて済ませません。
related_rules[] の参照整合は関所が検証します(S13)。
ambiguities[].code は次のいずれかです: assumed_interpretation、missing_input、contradiction、
unexpressible_delegated、exception_scope。
ルールオブジェクト
additionalProperties: false。必須: id、effect、subject、actions、resource。
| フィールド | 型 | 必須 | 備考 |
|---|---|---|---|
id | string | はい | kebab-case、maxLength: 64、ファイル内で一意。 |
effect | string | はい | "allow" または "deny"。 |
description | string | いいえ | メタデータ。 |
subject | object | はい | 任意の roles。下記参照。 |
actions | array | はい | 下記参照。 |
resource | object | はい | type 必須。下記参照。 |
relationships | array | いいえ | エンティティ間比較。Relationships 参照。 |
conditions | array | いいえ | 属性制約。Conditions 参照。 |
subject
subject.roles は任意の、空でない、一意な role 値の配列です。ID プロバイダが発行した外部所有の
語彙をそのままの形で保ちます(admin:operate、Domain Admins)。正規化が必要な識別子ではありません。
制約はレビュー可能性のためだけにあります: ASCII 可読、1〜64 文字、制御文字なし、前後の空白なし
(内部の空白は可)。非ASCII は 0.x では意図的に非対応です。аdmin のキリル文字 а のような
同形異字は、レビューで admin と区別できないためです。role は any-of(列挙した
いずれかの role を持てば一致)で照合されます。任意の subject に一致させるには roles を省略します。
その場合もルールには "subject": {} を残します。subject プロパティ自体は必須で、欠落したルールは
Sekisho が拒否します。
actions
必須です。空でない一意な snake_case 識別子の配列(any-of で照合)か、正確なワイルドカード
["*"] のいずれかです。
resource
type は必須で、snake_case 識別子またはワイルドカード "*" です。
Relationships
relationships は任意の配列です。エンティティ間比較(subject 対 resource)が許されるのは ここだけ
です。
| フィールド | 必須 | 正規 | 備考 |
|---|---|---|---|
label | はい | いいえ | 人間可読な名前。 |
subject_attribute | はい | はい | 識別子。 |
op | はい | はい | eq、neq、gt、gte、lt、lte のいずれか。 |
resource_attribute | はい | はい | 識別子。 |
必須であることと正規であることは別です。label は書かなければならず、無い relationship は
スキーマが弾きます。しかし意味は持ちません。ハッシュにも、生成される Rego にも、
subject_attribute・op・resource_attribute から作られるヘルパー名にも入りません。
label を書き換えても再承認は要りません。必須なのは、1 つのルールが複数の relationship を
持てるので、レビューする人にはそれぞれの名前が要るからです。
例。「請求書の提出者がリクエストしたユーザーである」を表すと、次のようになります。
{ "label": "own_submission", "subject_attribute": "user_id", "op": "eq", "resource_attribute": "submitted_by" }
Conditions
conditions は任意の配列です。各 condition は op による判別共用体です。key は スコープ付きキー
(subject.*、resource.*、environment.*、custom.*)です。
| ファミリ | op の値 | 値フィールド | 備考 |
|---|---|---|---|
| スカラー | eq, neq | value(リテラル) | 等価 / 非等価。 |
| 順序 | gt, gte, lt, lte | value(整数または文字列) | boolean は不可。 |
| 集合 | in, not_in | values(空でない一意なリテラル配列) | 集合への所属。 |
| 配列メンバーシップ | contains, not_contains | value(リテラル) | 配列属性への所属。 |
| 存在 | exists, not_exists | 、 | 値なし。 |
例:
{ "key": "resource.amount", "op": "lte", "value": 500000 }
識別子とリテラル
- 識別子(action・resource type・属性名)は snake_case です:アプリが設計し、呼び出し箇所に リテラルとして書く語彙です。
- role 値は識別子ではありません: 所有者(あなたの IdP)が発行した語彙をそのまま保ちます
(レビュー可能性のための制約は
subjectを参照)。 - リテラル は
string・integer・booleanです。 - 整数のみ:v0.1 に浮動小数点数はありません。 金額やしきい値は整数です(例: JPY を整数として)。
評価セマンティクス
これらは固定で、設定変更できません。
| ルール | 挙動 |
|---|---|
| ルール内 | すべての condition(と relationship)は AND されます。 |
| 同一 effect のルール間 | OR されます。その effect のルールがひとつでも一致すれば一致します。 |
| allow 対 deny | deny が allow を上書き(Deny-First)。 |
| 一致する allow がない | deny、Zero-Trust、デフォルト false。 |
roles / actions | any-of で照合。 |
| ワイルドカード | "*" はすべてに一致。関所は使用を情報として通知し (S8)、resource.type: "*" を持つポリシーは全リソース型を管轄と宣言します (C6)。 |
role の all-of が要るときは、ルールを分けるか、subject の属性への条件を使います。
roles と actions に all-of の形はありません。
1 回の判定が対象にするのは 1 件のリソース
check() は 1 つのポリシーを名指し、属性を渡した具体的な 1 件のリソースに対して評価します。
集合に対する判定は Ninka にはありません。
一覧を絞り込むために候補ごとに check() を呼ぶのは正しい使い方です。その結果をまとめるのは
別です。複数の結果を OR して、集合全体に対する 1 つの認可判断を作らないでください。その答えは
どのポリシーも評価していませんし、deny-overrides は 1 回の判定の中で効くのであって、
あとから行った OR には及びません。
deny-overrides が効くのも、名指したポリシーの中だけです。別のポリシーファイルに置いた deny は、 このポリシーの allow を制限しません。そこに置いた禁止は、決して効きません。関所はその配置を 拒否し、効かない禁止がそのまま出荷されるのを止めます。
入力が欠けているとき、ルールはどう動くか
判定は、渡された入力から下されます。ルールが比較する値が欠けているとき、そのルールは 単に一致しないのではありません。挙動はルールの effect によって変わり、どちらの向きでも 欠損がアクセスを広げることはありません。
| ルールの effect | 比較する値が欠けている | 結果 |
|---|---|---|
allow | 条件は成立しない | その allow ルールは一致しない |
deny | 条件は成立したものとして扱う | その deny ルールが発火する |
deny の行は初見で驚くので、はっきり書きます。「classification が banned のドキュメントは
閲覧禁止」という deny ルールがあるとき、判定はこうなります。
resource.classification | deny は発火するか |
|---|---|
"ok" | しない |
"banned" | する |
| 欠損 | する |
""(空文字) | する |
欠損が deny を黙らせるなら、属性を送らないだけで禁止を回避できてしまいます。型が契約と
違う値も同じ扱いです。status を "banned" と比べる deny は、status が true や
["banned"] で届いたときにも発火します。
欠損は「キーが無い」よりも広い言葉です。
- 空文字は欠損として扱います
- 空配列も欠損として扱います。
subject.roles: []は roles を送らない呼び出しと同じで、 role を参照する deny は発火します - オブジェクトは、空でも空でなくても欠損として扱います。手形の値はスカラーか配列だけで、 等しいゴミオブジェクト同士が relationship を成立させてはならないためです
exists と not_exists は例外です。存在そのものを検査するので、どちらの effect でも
字義どおりに評価します。exists の条件が不在で成立することはありません。
必須の入力と、任意の入力
コンパイラは、ポリシーが必要とする入力キーをルール本文から導きます。宣言は要りませんし、 書くこともできません。生成される入力の型は、この導出から決まります。
| そのキーを参照しているもの | 導出結果 |
|---|---|
値を比較する演算子(eq、neq、gt、gte、lt、lte、in、not_in、contains、not_contains)または relationship | 必須 |
exists / not_exists だけ | 任意。意図的な不在が正当な状態であるため |
| 存在検査と値の比較の両方 | 任意 |
action は常に必須です。resource_type も常に必須で、全ルールがワイルドカードのときだけ
任意になります。subject.roles は、いずれかのルールが role を挙げた時点で必須になります。
ここでの必須は「そのキーの由来ルールにおいて必須」という意味です。runtime が必須キーの欠落を
理由に呼び出しを弾くことはありません。approve のルールだけが要るキーの欠落で view の
要求を拒否しては、正当な要求を誤って拒むことになるからです。欠けた入力は診断のチャネルで
知らせ、判定は上の規則が決めます。
完全な例
正規の ninka/invoice-access.tegata.json:
{
"tegata": "0.1",
"policy": {
"id": "invoice-access",
"description": "請求書の閲覧・削除・承認"
},
"attributes": {
"subject.user_id": "string",
"resource.submitted_by": "string"
},
"audit": {
"source_text": "従業員は自分が提出した請求書を閲覧できる。マネージャー・経理・管理者は全ての請求書を閲覧できる。マネージャーは50万円以内の請求書を承認できる。経理は金額によらず承認できる。管理者は請求書を削除できる。",
"ambiguities": [
{
"code": "assumed_interpretation",
"message": "「自分が提出した」を relationship(subject.user_id = resource.submitted_by)で表現した。代理提出・共同提出の概念はないものと仮定。",
"related_rules": ["allow-employee-view-own-invoice"]
},
{
"code": "assumed_interpretation",
"message": "マネージャー・経理・管理者の閲覧を、部署による制限のない全請求書と解釈した。部署でスコープするなら relationship を追加する。",
"related_rules": ["allow-staff-view-invoice"]
},
{
"code": "assumed_interpretation",
"message": "「50万円以内」を resource.amount lte 500000(整数・JPY)と解釈した。境界値50万円ちょうどは承認可に含む。含まない意図なら lt に変更が必要。",
"related_rules": ["allow-manager-approve-invoice-within-limit"]
},
{
"code": "missing_input",
"message": "resource.amount / resource.submitted_by は請求書レコード(DB)から、subject.user_id はセッションから供給される前提。通貨はJPY単一通貨を仮定。",
"related_rules": ["allow-employee-view-own-invoice", "allow-manager-approve-invoice-within-limit"]
}
]
},
"rules": [
{
"id": "allow-employee-view-own-invoice",
"effect": "allow",
"description": "従業員は自分が提出した請求書を閲覧できる",
"subject": { "roles": ["employee"] },
"actions": ["view"],
"resource": { "type": "invoice" },
"relationships": [
{ "label": "own_submission", "subject_attribute": "user_id", "op": "eq", "resource_attribute": "submitted_by" }
]
},
{
"id": "allow-staff-view-invoice",
"effect": "allow",
"description": "マネージャー・経理・管理者は全ての請求書を閲覧できる",
"subject": { "roles": ["manager", "finance", "admin"] },
"actions": ["view"],
"resource": { "type": "invoice" }
},
{
"id": "allow-manager-approve-invoice-within-limit",
"effect": "allow",
"description": "マネージャーは50万円以内の請求書を承認できる",
"subject": { "roles": ["manager"] },
"actions": ["approve"],
"resource": { "type": "invoice" },
"conditions": [ { "key": "resource.amount", "op": "lte", "value": 500000 } ]
},
{
"id": "allow-finance-approve-invoice",
"effect": "allow",
"description": "経理は金額によらず請求書を承認できる",
"subject": { "roles": ["finance"] },
"actions": ["approve"],
"resource": { "type": "invoice" }
},
{
"id": "allow-admin-delete-invoice",
"effect": "allow",
"description": "管理者は請求書を削除できる",
"subject": { "roles": ["admin"] },
"actions": ["delete"],
"resource": { "type": "invoice" }
}
]
}