feat: enforce singleton local node per instance (#1567)

Only one local node is allowed. Creating a second local returns 409,
and the last local node cannot be deleted or converted to a remote type.
Existing duplicate local nodes from older versions are preserved and can
be cleaned up individually. Zero-local recovery auto-assigns the default
flag. Frontend delete surfaces and the Add Node form respect the new
invariant.

Enforced in DatabaseService (addNode/updateNode/deleteNode guards) and
routes (error translations). Legacy test fixtures use raw SQL helpers.
This commit is contained in:
Anso
2026-07-05 05:30:56 -04:00
committed by GitHub
parent 33231089c3
commit a7e856f447
11 changed files with 507 additions and 119 deletions
+14 -6
View File
@@ -2274,9 +2274,11 @@ paths:
tags: [Nodes]
summary: Register a new node
description: |
Adds a new local or remote node. Remote nodes require an API URL pointing to
another Sencho instance and an API token for authentication.
Requires `node:manage` permission.
Adds a new local or remote node. Only one local node is allowed per instance;
attempting to create a second local node returns 409. Remote nodes require
an API URL pointing to another Sencho instance and an API token for
authentication.
Requires `node:manage` permission. Node type is immutable after creation.
**Note:** API tokens cannot manage nodes.
requestBody:
@@ -2332,7 +2334,7 @@ paths:
"403":
$ref: "#/components/responses/Forbidden"
"409":
description: Node name already exists.
description: Node name already exists, or a local node already exists.
content:
application/json:
schema:
@@ -2362,7 +2364,7 @@ paths:
operationId: updateNode
tags: [Nodes]
summary: Update node
description: Updates node configuration. Requires `node:manage` permission.
description: Updates node configuration. Node type is immutable after creation. Requires `node:manage` permission.
parameters:
- $ref: "#/components/parameters/idPath"
requestBody:
@@ -2409,7 +2411,7 @@ paths:
operationId: deleteNode
tags: [Nodes]
summary: Delete node
description: Removes a node from the fleet. Requires `node:manage` permission.
description: Removes a node from the fleet. The last local node cannot be deleted. Requires `node:manage` permission.
parameters:
- $ref: "#/components/parameters/idPath"
responses:
@@ -2419,6 +2421,12 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/SuccessBoolean"
"400":
description: Cannot delete the only local node (or the default node).
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"403":
$ref: "#/components/responses/Forbidden"
"500":