@o3co/create-auth-policy-verifier 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o3co/create-auth-policy-verifier",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Scaffold a new o3co/auth.policy-verifier project from the standalone template",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -128,7 +128,7 @@ Authorization: Bearer <jwt>
128
128
  | `code` | deny のみ — 呼び出し元がワイヤ上で受け取ったコードと同じ |
129
129
  | `satisfiedBy` | allow のみ — 各グループを満たしたルールの `{ruleType, code}` |
130
130
  | `deniedBy` | deny のみ — 最初に失敗したグループと、そこで拒否した全代替ルール |
131
- | `requestId` | 呼び出し元が送った `x-request-id`。無い場合は省略される |
131
+ | `requestId` | 呼び出し元が送った `x-request-id`(サーバーが受け入れる形のもの。[失敗イベント](#失敗イベント) を参照)。無い場合は省略される |
132
132
  | `durationMs` | Collector パイプラインと evaluator に費やした時間。HTTP の往復時間ではない |
133
133
 
134
134
  判定 1 件につき 1 行なので、N 件の `POST /verify/batch` は同一 `requestId` を持つ N 行を出力します。アラートとインデックスはイベント名に対して張り、メッセージ本文には張らないでください。
@@ -141,6 +141,37 @@ Authorization: Bearer <jwt>
141
141
  - **呼び出し元の `context` オブジェクト。** 自由形式で collector にそのまま渡されるため、呼び出し側サービスのリクエストペイロードが行き着く場所そのものです。これを記録すると監査ストリームがそのペイロードの複製になります。
142
142
  - **ルールの `message` 文字列。** 既に行に載っている resource と action から導かれる内容です。
143
143
 
144
+ ### 失敗イベント
145
+
146
+ 判定できなかった decision は `error`(level 50)でログに出て、その行はどの種類の失敗がどこで起きたかを示します。
147
+
148
+ ```json
149
+ {"level":50,"msg":"verify_internal_error","endpoint":"/verify","requestId":"6f1c…","category":"collector_threw","collector":"attribute.collectors[1] (EntitlementStoreCollector)","err":{"type":"Error","message":"entitlement store is down","stack":"…"}}
150
+ ```
151
+
152
+ | イベント | 応答 | `category` | 併せて示すもの |
153
+ |---|---|---|---|
154
+ | `collector_timeout` | `403 collector_timeout` | `collector_timeout` | `collector`: `VERIFY_COLLECTOR_TIMEOUT_MS` を超えたエントリ、または pipeline が `VERIFY_COLLECTOR_DEADLINE_MS` を超えた場合はリストそのもの(`attribute.collectors`) |
155
+ | `rule_timeout` | `403 rule_timeout` | `rule_timeout` | `rule`: `{ ruleType, code }` |
156
+ | `attribute_conflict` | `403 attribute_conflict` | `attribute_conflict` | —(2 つのコレクターが食い違った。どちらか一方だけの責任ではない) |
157
+ | `verify_internal_error` | `500 internal_error` | `collector_threw`、`rule_threw`、`body_rejected`、`internal` のいずれか | `collector_threw` なら `collector`、`rule_threw` なら `rule` |
158
+
159
+ | `category` | 何が失敗したか |
160
+ |---|---|
161
+ | `collector_timeout` | コレクターが予算を、または pipeline がデッドラインを超えた |
162
+ | `collector_threw` | コレクターが reject / throw した |
163
+ | `attribute_conflict` | 2 つの attribute コレクターが同じスカラーキーに異なる値を書いた |
164
+ | `rule_timeout` | 非同期ルールが予算を、またはルールフェーズがデッドラインを超えた |
165
+ | `rule_threw` | ルールの `verify` が throw した、または `decide` が reject した |
166
+ | `body_rejected` | JSON body parser が、deny エンベロープが 4xx に対応付けていない形で失敗した |
167
+ | `internal` | decision 自身の collect や評価から出てきたのではないもの — throw した resource parser や authenticator(何を throw したかによらず) |
168
+
169
+ この集合は閉じています: アラートとフィルタは `err.message` ではなく `category` の等値に対して張ってください。`collector` は `config/application.conf` の `attribute.collectors` / `rule.collectors` 内のエントリ位置とクラス名です。JWKS に到達できないことは category ではありません — `401` で応答され、`jwt_verification_unavailable` としてログに出ます(後述)。
170
+
171
+ サーバーがこれらの行に加えるものは、トークン・クレーム・`context` のいずれも含みません。collector はその decision についてコレクターのランナーが記録したもので、エラーから読んだ名前ではなく、何も記録されていないタイムアウトは `collector: "unattributed"` です。ルールの `ruleType` と `code` はルールコレクターがリクエストごとにルールを組み立てうるため、識別子の形(英字で始まり、英数字・`_`・`.`・`-` が続く 64 文字以内)の場合だけ載り、それ以外は `redacted` になります。リクエスト ID は検証済みです。`err` は throw されたエラーそのものですが、例外が 1 つあります: core が定義する 3 つの deny エラー(`CollectorTimeoutError`、`RuleTimeoutError`、`AttributeConflictError`)はメッセージと自身のフィールドにコレクター・ルール・属性キーを含むため、分類されたコレクターやルール、同じ識別子の形に制限した属性キー、ヘッダ行だけの stack で組み立て直したものを記録します。それ以外のエラーのメッセージは、その作者の責任です。
172
+
173
+ **リクエストの相関.** 呼び出し元が送った `x-request-id` は、`/verify` と `/verify/batch` が返すすべての応答 — allow、deny、拒否、`500` — にレスポンスヘッダとしてそのまま返され、これらの行と `decision` 行に `requestId` として載ります。これで deny を enforcement 側サービス自身のログと突き合わせられます。載せるのは `A-Z a-z 0-9 - _ . : + / = #` からなる 1〜128 文字の場合だけで、UUID、ULID、16 進や W3C のトレース ID、base64、[protobuf.interceptors](https://github.com/o3co/protobuf.interceptors) が採番する ID はすべて収まります。それ以外は送られなかったものとして扱い — 返さず、ログに出さず、コレクターにも渡しません — 呼び出し元が送らなかった場合にサーバーが採番することもありません。`HTTP_CALLER_AUTH_TOKEN` のゲートは decision エンドポイントより手前で応答するため、ID を返しません。
174
+
144
175
  ### メトリクス
145
176
 
146
177
  `GET /metrics` は Prometheus text exposition format を返します。
@@ -150,6 +181,7 @@ Authorization: Bearer <jwt>
150
181
  | `auth_decisions_total` | counter | `decision` | allow/deny 比率。系列はちょうど 2 本 |
151
182
  | `auth_denials_total` | counter | `code` | どのルールが deny しているか — ログ行の `deniedBy` の集計版 |
152
183
  | `auth_decision_duration_seconds` | histogram | `decision` | パイプラインと evaluator に費やした時間 |
184
+ | `auth_collector_failures_total` | counter | `collector`, `category` | どのファクトソースがどう判定を失敗させているか — 失敗イベント行の `collector` フィールドの集計版。`category` は `collector_timeout` か `collector_threw` |
153
185
  | `http_request_duration_seconds` | histogram | `method`, `route`, `status` | リクエストレート・エラーレート・レイテンシ(RED メソッド) |
154
186
  | `auth_policy_verifier_*` | 各種 | — | Node プロセス既定メトリクス — event loop lag, heap, GC, handles |
155
187
 
@@ -165,6 +197,7 @@ deny はエラーではないので、書く価値のあるアラートは deny
165
197
  - **`route`** は URL ではなく Express の route *パターン*で、マッチしなかったもの(ポートに到達できる何かからの 404 プローブ)はすべて `route="unmatched"` に潰れます。
166
198
  - **`method`** はこのサービスが実際に処理できる 9 メソッドの allowlist で、それ以外は `method="other"` です。Node のパーサは llhttp が知る全メソッド(`PURGE`, `MKCOL`, `PROPFIND` など)をサーバーに渡すため、`req.method` はパスと同程度に呼び出し元の制御下にあります。
167
199
  - **`code`** は deployment が設定したルールに由来するので運用者が有界にできます — ただし `code` は `Rule` インターフェースのフィールドであり、ルールはリクエストごとに構築されるため、カスタム rule collector が resource から code を導出するようになるまでは 1 回の編集です。異なる値 32 個で打ち止め、それ以降は `code="other"` に潰れます。`code="other"` が伸びていること自体が、リクエストごとに code を作っているルールがあるというシグナルです。
200
+ - **`collector`** は設定されたエントリ — コレクターのランナーが記録した `attribute.collectors` / `rule.collectors` 内の位置と識別子の形のクラス名 — または `collector="unattributed"` なので、設定によって有界です。コレクターが自分で作った `CollectorTimeoutError` で別の名前を名乗ることはできません。それでも念のため異なる値 32 個で打ち止め、それ以降は `collector="other"` に潰れます。**`category`** は閉じた列挙値です。
168
201
 
169
202
  #### `/metrics` への到達方法
170
203
 
@@ -128,7 +128,7 @@ Every decision emits one structured event named `decision`, at `info`:
128
128
  | `code` | Deny only — the same code the caller got on the wire |
129
129
  | `satisfiedBy` | Allow only — the `{ruleType, code}` of the rule that satisfied each group |
130
130
  | `deniedBy` | Deny only — the first failing group, and every alternative in it that refused |
131
- | `requestId` | `x-request-id` as sent, when the caller sent one. Omitted otherwise |
131
+ | `requestId` | `x-request-id` as sent, when the caller sent one the server carries (see [Failure events](#failure-events)). Omitted otherwise |
132
132
  | `durationMs` | Time in the collector pipelines and the evaluator — not the HTTP round trip |
133
133
 
134
134
  One line per decision, so a `POST /verify/batch` of N entries emits N lines sharing one `requestId`. Alert and index on the event name, never on message text.
@@ -141,6 +141,37 @@ One line per decision, so a `POST /verify/batch` of N entries emits N lines shar
141
141
  - **the caller's `context` object.** It is free-form and forwarded verbatim to collectors, so it is exactly where a calling service's own request payload ends up. Logging it would make the audit stream a copy of that payload.
142
142
  - **rule `message` text**, which is derived from the resource and action already on the line.
143
143
 
144
+ ### Failure events
145
+
146
+ A decision that could not be made is logged at `error` (level 50), and the line says what kind of failure it was and where:
147
+
148
+ ```json
149
+ {"level":50,"msg":"verify_internal_error","endpoint":"/verify","requestId":"6f1c…","category":"collector_threw","collector":"attribute.collectors[1] (EntitlementStoreCollector)","err":{"type":"Error","message":"entitlement store is down","stack":"…"}}
150
+ ```
151
+
152
+ | Event | Answered | `category` | Also names |
153
+ |---|---|---|---|
154
+ | `collector_timeout` | `403 collector_timeout` | `collector_timeout` | `collector`: the entry that overran `VERIFY_COLLECTOR_TIMEOUT_MS`, or the list itself (`attribute.collectors`) when the pipeline overran `VERIFY_COLLECTOR_DEADLINE_MS` |
155
+ | `rule_timeout` | `403 rule_timeout` | `rule_timeout` | `rule`: `{ ruleType, code }` |
156
+ | `attribute_conflict` | `403 attribute_conflict` | `attribute_conflict` | — (two collectors disagreed; neither alone is answerable) |
157
+ | `verify_internal_error` | `500 internal_error` | `collector_threw`, `rule_threw`, `body_rejected` or `internal` | `collector` for `collector_threw`, `rule` for `rule_threw` |
158
+
159
+ | `category` | What failed |
160
+ |---|---|
161
+ | `collector_timeout` | A collector overran its budget, or a pipeline its deadline |
162
+ | `collector_threw` | A collector rejected or threw |
163
+ | `attribute_conflict` | Two attribute collectors wrote different values to one scalar key |
164
+ | `rule_timeout` | An asynchronous rule overran its budget, or the rule phase its deadline |
165
+ | `rule_threw` | A rule's `verify` threw or its `decide` rejected |
166
+ | `body_rejected` | The JSON body parser failed in a way the deny envelope does not map to a 4xx |
167
+ | `internal` | Anything that did not come out of a decision's own collect or evaluation — a resource parser or authenticator that threw, whatever it threw |
168
+
169
+ The set is closed: alert and filter on `category` by equality, never on `err.message`. `collector` is the entry's position in `attribute.collectors` / `rule.collectors` in `config/application.conf`, plus its class. An unreachable JWKS is not a category — it is answered `401` and logged as `jwt_verification_unavailable` (see below).
170
+
171
+ Nothing the server adds to these lines carries the token, the claims or `context`. The collector is the one the collector runner recorded for that decision, never a name read off an error, and a timeout nothing recorded is `collector: "unattributed"`. A rule's `ruleType` and `code` are carried only when identifier-shaped (a letter, then letters, digits, `_`, `.` or `-`, at most 64 characters) and are `redacted` otherwise, because a rule collector may build rules per request. The request id is validated. `err` is the error as thrown, with one exception: the three deny errors core defines (`CollectorTimeoutError`, `RuleTimeoutError`, `AttributeConflictError`) name a collector, a rule or an attribute key in their message and their own fields, so each is logged rebuilt — the classified collector or rule, the key held to the same identifier shape, and a stack of the header line only. Any other error's message is its author's responsibility.
172
+
173
+ **Request correlation.** An `x-request-id` the caller sent is echoed as a response header on every answer `/verify` and `/verify/batch` give — allow, deny, refusal and `500` — and carried as `requestId` on these lines and the `decision` line, so a denial can be matched to the enforcing service's own log. It is carried only as 1–128 characters of `A-Z a-z 0-9 - _ . : + / = #`, which UUIDs, ULIDs, hex and W3C trace ids, base64 and the ids [protobuf.interceptors](https://github.com/o3co/protobuf.interceptors) mints all fit. Anything else is treated as absent — not echoed, not logged, not forwarded to collectors — and the server never mints one when the caller sent none. The `HTTP_CALLER_AUTH_TOKEN` gate answers before the decision endpoints and does not echo it.
174
+
144
175
  ### Metrics
145
176
 
146
177
  `GET /metrics` serves the Prometheus text exposition format.
@@ -150,6 +181,7 @@ One line per decision, so a `POST /verify/batch` of N entries emits N lines shar
150
181
  | `auth_decisions_total` | counter | `decision` | The allow/deny rate. Exactly two series |
151
182
  | `auth_denials_total` | counter | `code` | Which rule is doing the denying — the aggregate of the log line's `deniedBy` |
152
183
  | `auth_decision_duration_seconds` | histogram | `decision` | Time in the pipelines and the evaluator |
184
+ | `auth_collector_failures_total` | counter | `collector`, `category` | Which fact source is failing decisions, and how — the aggregate of the `collector` field on the failure lines. `category` is `collector_timeout` or `collector_threw` |
153
185
  | `http_request_duration_seconds` | histogram | `method`, `route`, `status` | Request rate, error rate and latency (the RED method) |
154
186
  | `auth_policy_verifier_*` | various | — | Node process defaults — event-loop lag, heap, GC, handles |
155
187
 
@@ -165,6 +197,7 @@ An unbounded label mints a fresh time series per distinct value, which is how a
165
197
  - **`route`** is the Express route *pattern*, never the URL, and anything unmatched — 404 probes from whatever can route to the port — collapses to `route="unmatched"`.
166
198
  - **`method`** is an allowlist of the nine methods this service can serve; everything else is `method="other"`. Node's parser hands the server every method llhttp knows (`PURGE`, `MKCOL`, `PROPFIND`, …), so `req.method` is as caller-controlled as a path.
167
199
  - **`code`** comes from the rules a deployment configured, which makes it operator-bounded — but `code` is a field on the `Rule` interface and rules are built per request, so a custom rule collector is one edit away from deriving it from the resource. It is capped at 32 distinct values, after which the rest collapse to `code="other"`. A climbing `code="other"` is itself the signal that a rule is minting codes per request.
200
+ - **`collector`** is a configured entry — its position in `attribute.collectors` / `rule.collectors` and its identifier-shaped class name, as the collector runner recorded it — or `collector="unattributed"`, so it is bounded by the config. A `CollectorTimeoutError` a collector builds itself cannot name anything else. The label is still capped at 32 distinct values as a backstop, collapsing to `collector="other"`. **`category`** is a closed enum.
168
201
 
169
202
  #### Reaching `/metrics`
170
203
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o3co/auth-policy-verifier-standalone",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "packageManager": "pnpm@10.29.3",
@@ -1,5 +1,5 @@
1
1
  {
2
- "@o3co/auth.policy-verifier.core": "0.10.0",
3
- "@o3co/auth.policy-verifier.builtins": "0.10.0",
4
- "@o3co/auth.policy-verifier.server": "0.10.0"
2
+ "@o3co/auth.policy-verifier.core": "0.11.0",
3
+ "@o3co/auth.policy-verifier.builtins": "0.11.0",
4
+ "@o3co/auth.policy-verifier.server": "0.11.0"
5
5
  }