Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ under the pre-1.0 conventions in [VERSIONING.md](docs/governance/VERSIONING.md):

### Added

- **An authenticated node health route reports the counters an operator pages
on.** `GET /vault/api/v1/node/health` returns the publishes refused for a failed
signature and the ruleset bundles refused on load, both since the node process
started, and the registry-sync outbox's pending, submitted, stalled and rejected
counts. A monitor that cannot scrape `/metrics` can read them with an API key.
The unauthenticated `/health` still answers liveness only. The two process
counters reset on restart; the outbox does not. (#138)

- **An authority or a person with a legitimate interest can read the passport
as it stood at a past moment.** EN 18221:2026 clause 4.2 expects the version of
a passport at a past moment to be retrievable by authenticated, authorised
Expand Down
27 changes: 27 additions & 0 deletions api/components/schemas/access/NodeHealth.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
type: object
required:
- signingFailuresTotal
- rulesetLoadFailuresTotal
properties:
signingFailuresTotal:
type: integer
format: int64
minimum: 0
description: |
Publishes refused because a passport could not be signed, since the node
process started. A restart resets it, so a restart hides failures from
before it.
example: 0
rulesetLoadFailuresTotal:
type: integer
format: int64
minimum: 0
description: |
Compliance Current ruleset bundles refused on load, since the node process
started. A restart resets it.
example: 0
registryOutbox:
$ref: ./RegistryOutboxHealth.yaml
description: |
The registry-sync outbox's counts. Absent on a deployment with no outbox
wired, such as a standalone vault.
25 changes: 25 additions & 0 deletions api/components/schemas/access/RegistryOutboxHealth.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
type: object
required:
- pending
- submitted
- stalled
- rejected
properties:
pending:
type: integer
format: int64
description: Rows awaiting a drain attempt.
submitted:
type: integer
format: int64
description: Rows accepted by the registry and awaiting its verdict.
stalled:
type: integer
format: int64
description: |
Pending and submitted rows that have reached the stall threshold. A row here
is not going to succeed without someone looking at it.
rejected:
type: integer
format: int64
description: Rows the registry terminally rejected.
91 changes: 91 additions & 0 deletions api/openapi.bundled.json
Original file line number Diff line number Diff line change
Expand Up @@ -3576,6 +3576,39 @@
}
}
},
"/vault/api/v1/node/health": {
"get": {
"operationId": "getNodeHealth",
"summary": "Node operational health counters",
"description": "Reports the counters an operator pages on: publishes refused for a failed\nsignature and ruleset bundles refused on load, both since the node process\nstarted, and the registry-sync outbox's pending, submitted, stalled and\nrejected counts. Authenticated, so a monitor that cannot scrape `/metrics`\ncan read them with an API key. The unauthenticated `/health` answers\nliveness only.\n",
"tags": [
"Node"
],
"security": [
{
"BearerApiKey": []
},
{
"BasicAuth": []
}
],
"responses": {
"200": {
"description": "Node operational health counters.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NodeHealth"
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
}
}
}
},
"/vault/api/v1/whoami": {
"get": {
"operationId": "whoami",
Expand Down Expand Up @@ -9495,6 +9528,64 @@
}
}
},
"NodeHealth": {
"type": "object",
"required": [
"signingFailuresTotal",
"rulesetLoadFailuresTotal"
],
"properties": {
"signingFailuresTotal": {
"type": "integer",
"format": "int64",
"minimum": 0,
"description": "Publishes refused because a passport could not be signed, since the node\nprocess started. A restart resets it, so a restart hides failures from\nbefore it.\n",
"example": 0
},
"rulesetLoadFailuresTotal": {
"type": "integer",
"format": "int64",
"minimum": 0,
"description": "Compliance Current ruleset bundles refused on load, since the node process\nstarted. A restart resets it.\n",
"example": 0
},
"registryOutbox": {
"$ref": "#/components/schemas/RegistryOutboxHealth",
"description": "The registry-sync outbox's counts. Absent on a deployment with no outbox\nwired, such as a standalone vault.\n"
}
}
},
"RegistryOutboxHealth": {
"type": "object",
"required": [
"pending",
"submitted",
"stalled",
"rejected"
],
"properties": {
"pending": {
"type": "integer",
"format": "int64",
"description": "Rows awaiting a drain attempt."
},
"submitted": {
"type": "integer",
"format": "int64",
"description": "Rows accepted by the registry and awaiting its verdict."
},
"stalled": {
"type": "integer",
"format": "int64",
"description": "Pending and submitted rows that have reached the stall threshold. A row here\nis not going to succeed without someone looking at it.\n"
},
"rejected": {
"type": "integer",
"format": "int64",
"description": "Rows the registry terminally rejected."
}
}
},
"NodeProfile": {
"type": "string",
"description": "The deployment profile a node was started under (`NODE_PROFILE`), which decides the trust tiers it will boot on. `development` boots on any tier, placeholders included, and is what an unset or unrecognised value means. `sandbox` is a real deployment against test authorities: it refuses a placeholder on a required trust port and accepts a sandbox tier. `production` accepts only live tiers on required ports.",
Expand Down
79 changes: 79 additions & 0 deletions api/openapi.bundled.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2978,6 +2978,31 @@ paths:
$ref: '#/components/schemas/NodeState'
'401':
$ref: '#/components/responses/Unauthorized'
/vault/api/v1/node/health:
get:
operationId: getNodeHealth
summary: Node operational health counters
description: |
Reports the counters an operator pages on: publishes refused for a failed
signature and ruleset bundles refused on load, both since the node process
started, and the registry-sync outbox's pending, submitted, stalled and
rejected counts. Authenticated, so a monitor that cannot scrape `/metrics`
can read them with an API key. The unauthenticated `/health` answers
liveness only.
tags:
- Node
security:
- BearerApiKey: []
- BasicAuth: []
responses:
'200':
description: Node operational health counters.
content:
application/json:
schema:
$ref: '#/components/schemas/NodeHealth'
'401':
$ref: '#/components/responses/Unauthorized'
/vault/api/v1/whoami:
get:
operationId: whoami
Expand Down Expand Up @@ -8247,6 +8272,60 @@ components:
the rules a passport was checked under are observable. Absent on a
standalone vault.
example: baseline
NodeHealth:
type: object
required:
- signingFailuresTotal
- rulesetLoadFailuresTotal
properties:
signingFailuresTotal:
type: integer
format: int64
minimum: 0
description: |
Publishes refused because a passport could not be signed, since the node
process started. A restart resets it, so a restart hides failures from
before it.
example: 0
rulesetLoadFailuresTotal:
type: integer
format: int64
minimum: 0
description: |
Compliance Current ruleset bundles refused on load, since the node process
started. A restart resets it.
example: 0
registryOutbox:
$ref: '#/components/schemas/RegistryOutboxHealth'
description: |
The registry-sync outbox's counts. Absent on a deployment with no outbox
wired, such as a standalone vault.
RegistryOutboxHealth:
type: object
required:
- pending
- submitted
- stalled
- rejected
properties:
pending:
type: integer
format: int64
description: Rows awaiting a drain attempt.
submitted:
type: integer
format: int64
description: Rows accepted by the registry and awaiting its verdict.
stalled:
type: integer
format: int64
description: |
Pending and submitted rows that have reached the stall threshold. A row here
is not going to succeed without someone looking at it.
rejected:
type: integer
format: int64
description: Rows the registry terminally rejected.
NodeProfile:
type: string
description: 'The deployment profile a node was started under (`NODE_PROFILE`), which decides the trust tiers it will boot on. `development` boots on any tier, placeholders included, and is what an unset or unrecognised value means. `sandbox` is a real deployment against test authorities: it refuses a placeholder on a required trust port and accepts a sandbox tier. `production` accepts only live tiers on required ports.'
Expand Down
6 changes: 6 additions & 0 deletions api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ components:
$ref: components/schemas/access/InstalledPlugin.yaml
NodeState:
$ref: components/schemas/access/NodeState.yaml
NodeHealth:
$ref: components/schemas/access/NodeHealth.yaml
RegistryOutboxHealth:
$ref: components/schemas/access/RegistryOutboxHealth.yaml
NodeProfile:
$ref: components/schemas/access/NodeProfile.yaml
WhoamiResponse:
Expand Down Expand Up @@ -446,6 +450,8 @@ paths:
$ref: paths/vault/vault_api_v1_ruleset_reload.yaml
/vault/api/v1/node/state:
$ref: paths/vault/vault_api_v1_node_state.yaml
/vault/api/v1/node/health:
$ref: paths/vault/vault_api_v1_node_health.yaml
/vault/api/v1/whoami:
$ref: paths/vault/vault_api_v1_whoami.yaml
/vault/public/dpp/{dppId}:
Expand Down
24 changes: 24 additions & 0 deletions api/paths/vault/vault_api_v1_node_health.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
get:
operationId: getNodeHealth
summary: Node operational health counters
description: |
Reports the counters an operator pages on: publishes refused for a failed
signature and ruleset bundles refused on load, both since the node process
started, and the registry-sync outbox's pending, submitted, stalled and
rejected counts. Authenticated, so a monitor that cannot scrape `/metrics`
can read them with an API key. The unauthenticated `/health` answers
liveness only.
tags:
- Node
security:
- BearerApiKey: []
- BasicAuth: []
responses:
'200':
description: Node operational health counters.
content:
application/json:
schema:
$ref: ../../components/schemas/access/NodeHealth.yaml
'401':
$ref: ../../components/responses/Unauthorized.yaml
Loading
Loading