Keep error classes across links

Error responses now carry the registered name of the thrown error's
class, and the requester rejects with an instance of that class instead
of a generic RequestError. Error classes are registered like message
classes, and plugins list their own in `errors`.

RequestError is replaced by a hierarchy following the HTTP status codes:
ClientError (ValidationError, ForbiddenError, NotFoundError,
TimeoutError, ConflictError) and ServerError (InternalServerError).
PermissionError is PermissionDeniedError and the config errors
InvalidValue, InvalidField and InvalidAccess gain an Error suffix.

Link logs only errors whose class is not registered. These arrive on the
other side as InternalServerError with the original message and stack,
so an error forwarded through the controller is logged once, where it
was thrown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bbassieandClaude Opus 5.5 committed 2026-10-27 22:03:13 +00:00
1 parent ffe82f81d8
commit 1c7c00b033
37 files changed
+589 -310

No files matched your search

+2 -2
View File
@@ -139,7 +139,7 @@ describe("controller/src/ControlConnection", function() {
it("rejects instance.assigned_host in the config", async function() {
await assert.rejects(
create({ "instance.id": 4001, "instance.name": "c1", "instance.assigned_host": 10 }),
new lib.RequestError("instance.assigned_host must be set through the assign-host interface"),
new lib.ValidationError("instance.assigned_host must be set through the assign-host interface"),
);
assert.equal(mockController.instances.has(4001), false);
});
@@ -197,7 +197,7 @@ describe("controller/src/ControlConnection", function() {
await connection.handleLogSetSubscriptionsRequest(
new lib.LogSetSubscriptionsRequest(true, false, [], [])
);
connection.user = { checkPermission() { throw new lib.PermissionError("Permission denied"); } };
connection.user = { checkPermission() { throw new lib.PermissionDeniedError("Permission denied"); } };
connection.checkLogPermission();
assert.equal(mockController.clusterLogger.transports.size, 0);
assert.equal(connection.logTransport, null);