@helyx/bot 1.5.2 → 1.5.4
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/CHANGELOG.md +27 -0
- package/command-root-catalogue.json +452 -0
- package/dist/cached-message-deletion-authority.d.ts +27 -0
- package/dist/cached-message-deletion-authority.js +288 -0
- package/dist/channel-audit-configuration.d.ts +11 -1
- package/dist/channel-audit-configuration.js +28 -6
- package/dist/channel-audit-control.js +42 -2
- package/dist/channel-audit-permissions.js +76 -3
- package/dist/cloud-command-policy-sync.d.ts +26 -0
- package/dist/cloud-command-policy-sync.js +198 -0
- package/dist/cloud-connector.d.ts +6 -1
- package/dist/cloud-connector.js +224 -53
- package/dist/cloud-control-dispatcher.js +18 -0
- package/dist/command-policy-runtime.d.ts +23 -0
- package/dist/command-policy-runtime.js +53 -0
- package/dist/command-root-catalogue.d.ts +5 -0
- package/dist/command-root-catalogue.js +16 -0
- package/dist/control-error-mapping.d.ts +8 -0
- package/dist/control-error-mapping.js +60 -0
- package/dist/control-errors.d.ts +3 -1
- package/dist/control-errors.js +4 -1
- package/dist/control-health-routes.d.ts +10 -0
- package/dist/control-health-routes.js +28 -0
- package/dist/control-server-schemas.d.ts +7 -0
- package/dist/control-server-schemas.js +9 -0
- package/dist/control-server.d.ts +27 -6
- package/dist/control-server.js +103 -122
- package/dist/dashboard-feature-control.d.ts +2 -1
- package/dist/dashboard-feature-control.js +1 -0
- package/dist/dashboard-logging.d.ts +20 -0
- package/dist/dashboard-logging.js +39 -0
- package/dist/dashboard-module-projection.d.ts +1 -1
- package/dist/dashboard-module-projection.js +5 -2
- package/dist/dashboard-preview-import-routes.js +8 -0
- package/dist/database-bootstrap.d.ts +1 -1
- package/dist/database-bootstrap.js +4 -3
- package/dist/discord-channel-audit.js +1 -0
- package/dist/discord-user-identities.d.ts +14 -0
- package/dist/discord-user-identities.js +64 -0
- package/dist/emergency-deletion-dispatcher.d.ts +68 -0
- package/dist/emergency-deletion-dispatcher.js +502 -0
- package/dist/emergency-deletion-rest-transport.d.ts +24 -0
- package/dist/emergency-deletion-rest-transport.js +253 -0
- package/dist/events.d.ts +5 -0
- package/dist/events.js +53 -10
- package/dist/guarded-discord-dispatcher.d.ts +29 -0
- package/dist/guarded-discord-dispatcher.js +204 -0
- package/dist/guild-command-discord-client.d.ts +2 -0
- package/dist/guild-command-discord-client.js +7 -0
- package/dist/guild-command-projection.d.ts +4 -1
- package/dist/guild-command-projection.js +25 -0
- package/dist/guild-command-read-service.d.ts +10 -1
- package/dist/guild-command-read-service.js +37 -0
- package/dist/guild-command-worker.d.ts +19 -4
- package/dist/guild-command-worker.js +356 -26
- package/dist/guild-message-protection-author-roles.d.ts +7 -0
- package/dist/guild-message-protection-author-roles.js +93 -0
- package/dist/guild-message-protection-deletion.d.ts +21 -0
- package/dist/guild-message-protection-deletion.js +138 -0
- package/dist/guild-message-protection-delivery.d.ts +41 -0
- package/dist/guild-message-protection-delivery.js +223 -0
- package/dist/guild-message-update-metadata.d.ts +32 -0
- package/dist/guild-message-update-metadata.js +255 -0
- package/dist/guilds.d.ts +2 -1
- package/dist/guilds.js +11 -1
- package/dist/hosted-server-moderation.d.ts +2 -2
- package/dist/index.js +68 -4
- package/dist/interactions.d.ts +3 -1
- package/dist/interactions.js +77 -12
- package/dist/invite-rollup-maintenance.d.ts +9 -0
- package/dist/invite-rollup-maintenance.js +12 -0
- package/dist/managed-resource-authorization.d.ts +15 -0
- package/dist/managed-resource-authorization.js +37 -0
- package/dist/managed-resource-create-defaults.d.ts +21 -0
- package/dist/managed-resource-create-defaults.js +83 -0
- package/dist/managed-resource-query.d.ts +6 -0
- package/dist/managed-resource-query.js +15 -0
- package/dist/member-role-ids.d.ts +17 -0
- package/dist/member-role-ids.js +66 -0
- package/dist/message-protection-admission.d.ts +60 -0
- package/dist/message-protection-admission.js +279 -0
- package/dist/message-protection-cold-data.d.ts +42 -0
- package/dist/message-protection-cold-data.js +180 -0
- package/dist/message-protection-coordinator.d.ts +73 -0
- package/dist/message-protection-coordinator.js +622 -0
- package/dist/message-protection-settings.d.ts +40 -0
- package/dist/message-protection-settings.js +275 -0
- package/dist/message-protection-summary.d.ts +8 -0
- package/dist/message-protection-summary.js +98 -0
- package/dist/module-availability-control.js +20 -12
- package/dist/module-logging.d.ts +3 -2
- package/dist/module-logging.js +6 -1
- package/dist/modules.js +56 -1
- package/dist/mysql-runtime-persistence.d.ts +1 -0
- package/dist/mysql-runtime-persistence.js +4 -1
- package/dist/nexus-command-recovery-control-routes.d.ts +5 -0
- package/dist/nexus-command-recovery-control-routes.js +48 -0
- package/dist/nexus-command-references.d.ts +6 -0
- package/dist/nexus-command-references.js +33 -0
- package/dist/scheduled-task-runner.js +23 -4
- package/dist/shard-command-startup.d.ts +13 -0
- package/dist/shard-command-startup.js +27 -0
- package/dist/shard-contracts.d.ts +84 -0
- package/dist/shard-contracts.js +33 -0
- package/dist/shard-core-readiness.d.ts +5 -0
- package/dist/shard-core-readiness.js +19 -0
- package/dist/shard-router.d.ts +3 -0
- package/dist/shard-router.js +10 -1
- package/dist/shard-supervisor.d.ts +1 -1
- package/dist/shard-supervisor.js +124 -43
- package/dist/shard-worker.js +25 -15
- package/documentation/README.md +1 -0
- package/documentation/examples/.env.example +0 -45
- package/documentation/guides/self-hosting.md +54 -0
- package/package.json +18 -17
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { restoreCommandPolicyPublication, normalizeCommandPolicyManifest, } from "@helyx/database-contracts";
|
|
2
|
+
import { commandPolicyReplySchema, } from "@helyx/control-protocol/command-policy";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
/** One authenticated socket, one bounded transfer, no customer policy authority. */
|
|
5
|
+
export class CloudCommandPolicySynchronizer {
|
|
6
|
+
input;
|
|
7
|
+
#abort = new AbortController();
|
|
8
|
+
#pending;
|
|
9
|
+
#timer;
|
|
10
|
+
#active;
|
|
11
|
+
#known;
|
|
12
|
+
constructor(input) {
|
|
13
|
+
this.input = input;
|
|
14
|
+
}
|
|
15
|
+
async start() {
|
|
16
|
+
await this.#assertCurrent();
|
|
17
|
+
await this.input.delivery.openSession({
|
|
18
|
+
owner: this.input.owner,
|
|
19
|
+
session: this.input.session,
|
|
20
|
+
signal: this.#abort.signal,
|
|
21
|
+
});
|
|
22
|
+
await this.#assertCurrent();
|
|
23
|
+
if (this.#timer)
|
|
24
|
+
return;
|
|
25
|
+
this.#timer = setInterval(() => {
|
|
26
|
+
void this.runOnce();
|
|
27
|
+
}, this.input.intervalMs ?? 30_000);
|
|
28
|
+
this.#timer.unref();
|
|
29
|
+
void this.runOnce();
|
|
30
|
+
}
|
|
31
|
+
runOnce() {
|
|
32
|
+
if (this.#active)
|
|
33
|
+
return this.#active;
|
|
34
|
+
if (this.#abort.signal.aborted)
|
|
35
|
+
return Promise.resolve();
|
|
36
|
+
this.#active = this.#refresh()
|
|
37
|
+
.catch(() => {
|
|
38
|
+
if (!this.#abort.signal.aborted)
|
|
39
|
+
this.input.onHeld();
|
|
40
|
+
})
|
|
41
|
+
.finally(() => {
|
|
42
|
+
this.#active = undefined;
|
|
43
|
+
});
|
|
44
|
+
return this.#active;
|
|
45
|
+
}
|
|
46
|
+
/** Caller must first pass the current accepted envelope's sequence/epoch guard. */
|
|
47
|
+
accept(correlationId, payload, expiresAt) {
|
|
48
|
+
const pending = this.#pending;
|
|
49
|
+
if (!pending ||
|
|
50
|
+
correlationId !== pending.correlationId ||
|
|
51
|
+
this.#abort.signal.aborted)
|
|
52
|
+
return;
|
|
53
|
+
const reply = commandPolicyReplySchema.parse(payload);
|
|
54
|
+
if (!Number.isFinite(Date.parse(expiresAt)) ||
|
|
55
|
+
Date.parse(expiresAt) <= Date.now())
|
|
56
|
+
throw new Error("command_policy_confirmation_expired");
|
|
57
|
+
clearTimeout(pending.timer);
|
|
58
|
+
this.#pending = undefined;
|
|
59
|
+
pending.resolve({ reply, expiresAt });
|
|
60
|
+
}
|
|
61
|
+
/** Stop local activity before an atomic receipt-first identity transition. */
|
|
62
|
+
cancel() {
|
|
63
|
+
this.#abort.abort();
|
|
64
|
+
clearInterval(this.#timer);
|
|
65
|
+
this.#timer = undefined;
|
|
66
|
+
if (this.#pending) {
|
|
67
|
+
clearTimeout(this.#pending.timer);
|
|
68
|
+
this.#pending.reject(new Error("command_policy_session_closed"));
|
|
69
|
+
this.#pending = undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async stop() {
|
|
73
|
+
this.cancel();
|
|
74
|
+
await this.#active;
|
|
75
|
+
await this.input.delivery.invalidate({
|
|
76
|
+
owner: this.input.owner,
|
|
77
|
+
expectedSession: this.input.session,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async #assertCurrent() {
|
|
81
|
+
this.#abort.signal.throwIfAborted();
|
|
82
|
+
if (this.input.isOwnerCurrent && !(await this.input.isOwnerCurrent()))
|
|
83
|
+
throw new Error("command_policy_owner_changed");
|
|
84
|
+
this.#abort.signal.throwIfAborted();
|
|
85
|
+
}
|
|
86
|
+
async #request(pull) {
|
|
87
|
+
await this.#assertCurrent();
|
|
88
|
+
const correlationId = randomUUID();
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
const timer = setTimeout(() => {
|
|
91
|
+
this.#pending = undefined;
|
|
92
|
+
reject(new Error("command_policy_request_timed_out"));
|
|
93
|
+
}, this.input.timeoutMs ?? 10_000);
|
|
94
|
+
timer.unref();
|
|
95
|
+
this.#pending = { correlationId, resolve, reject, timer };
|
|
96
|
+
try {
|
|
97
|
+
this.input.send(correlationId, pull);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
this.#pending = undefined;
|
|
102
|
+
reject(error instanceof Error
|
|
103
|
+
? error
|
|
104
|
+
: new Error("command_policy_send_failed"));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async #refresh() {
|
|
109
|
+
let entries = [];
|
|
110
|
+
let manifest;
|
|
111
|
+
let pull = {
|
|
112
|
+
kind: "check",
|
|
113
|
+
...(this.#known
|
|
114
|
+
? {
|
|
115
|
+
known: {
|
|
116
|
+
revision: this.#known.revision,
|
|
117
|
+
fingerprint: this.#known.fingerprint,
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
: {}),
|
|
121
|
+
};
|
|
122
|
+
// At most 10,000 one-entry pages plus two source-change retries. Normal
|
|
123
|
+
// pages contain up to 500 entries; no unbounded snapshot churn is accepted.
|
|
124
|
+
let restarts = 0;
|
|
125
|
+
for (let pages = 0; pages < 10_003; pages += 1) {
|
|
126
|
+
const { reply, expiresAt } = await this.#request(pull);
|
|
127
|
+
await this.#assertCurrent();
|
|
128
|
+
if (reply.state === "unavailable" || reply.state === "unconfigured") {
|
|
129
|
+
this.#known = undefined;
|
|
130
|
+
await this.input.delivery.invalidate({
|
|
131
|
+
owner: this.input.owner,
|
|
132
|
+
expectedSession: this.input.session,
|
|
133
|
+
});
|
|
134
|
+
throw new Error("command_policy_source_unavailable");
|
|
135
|
+
}
|
|
136
|
+
if (reply.targetApplicationId !== this.input.applicationId)
|
|
137
|
+
throw new Error("command_policy_application_mismatch");
|
|
138
|
+
const currentManifest = normalizeCommandPolicyManifest(reply.publication);
|
|
139
|
+
if (reply.state === "unchanged") {
|
|
140
|
+
if (!this.#known ||
|
|
141
|
+
JSON.stringify(this.#known) !== JSON.stringify(currentManifest))
|
|
142
|
+
throw new Error("command_policy_unexpected_unchanged");
|
|
143
|
+
if (!(await this.input.delivery.renew({
|
|
144
|
+
owner: this.input.owner,
|
|
145
|
+
session: this.input.session,
|
|
146
|
+
publication: currentManifest,
|
|
147
|
+
confirmationExpiresAt: expiresAt,
|
|
148
|
+
signal: this.#abort.signal,
|
|
149
|
+
})))
|
|
150
|
+
throw new Error("command_policy_renewal_refused");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (reply.state === "snapshot_changed") {
|
|
154
|
+
if (++restarts > 2)
|
|
155
|
+
throw new Error("command_policy_source_churn");
|
|
156
|
+
entries = [];
|
|
157
|
+
manifest = currentManifest;
|
|
158
|
+
pull = {
|
|
159
|
+
kind: "page",
|
|
160
|
+
revision: manifest.revision,
|
|
161
|
+
fingerprint: manifest.fingerprint,
|
|
162
|
+
offset: 0,
|
|
163
|
+
};
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (reply.page.offset !== entries.length ||
|
|
167
|
+
(manifest &&
|
|
168
|
+
JSON.stringify(manifest) !== JSON.stringify(currentManifest)))
|
|
169
|
+
throw new Error("command_policy_noncontiguous_page");
|
|
170
|
+
manifest = currentManifest;
|
|
171
|
+
entries.push(...reply.page.entries);
|
|
172
|
+
if (entries.length > 10_000)
|
|
173
|
+
throw new Error("command_policy_entry_limit");
|
|
174
|
+
if (reply.page.nextOffset !== null) {
|
|
175
|
+
pull = {
|
|
176
|
+
kind: "page",
|
|
177
|
+
revision: manifest.revision,
|
|
178
|
+
fingerprint: manifest.fingerprint,
|
|
179
|
+
offset: reply.page.nextOffset,
|
|
180
|
+
};
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const publication = restoreCommandPolicyPublication(manifest, entries);
|
|
184
|
+
await this.#assertCurrent();
|
|
185
|
+
await this.input.delivery.adopt({
|
|
186
|
+
owner: this.input.owner,
|
|
187
|
+
session: this.input.session,
|
|
188
|
+
publication,
|
|
189
|
+
confirmationExpiresAt: expiresAt,
|
|
190
|
+
signal: this.#abort.signal,
|
|
191
|
+
});
|
|
192
|
+
this.#known = manifest;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
throw new Error("command_policy_page_limit");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=cloud-command-policy-sync.js.map
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { BotEnvironment } from "@helyx/config";
|
|
2
|
-
import { type SupportBugCreatePayload, type SupportBugCreateResponsePayload } from "@helyx/control-protocol";
|
|
2
|
+
import { cloudControlResponsePayloadSchema, type CloudControlRequestPayload, type SupportBugCreatePayload, type SupportBugCreateResponsePayload } from "@helyx/control-protocol";
|
|
3
3
|
import { type CloudConnectorConsentRecord, type DatabasePool } from "@helyx/database";
|
|
4
4
|
import type { Client } from "discord.js";
|
|
5
5
|
import type { Logger } from "pino";
|
|
6
|
+
import { z } from "zod";
|
|
6
7
|
import type { ConfiguredModulePackage } from "./modules.js";
|
|
7
8
|
import type { GuildCommandReadHandler } from "./guild-command-read-service.js";
|
|
8
9
|
import { type CloudConnectorStore } from "./cloud-control-dispatcher.js";
|
|
9
10
|
import { type BotControlHandler } from "./control-server.js";
|
|
11
|
+
import type { CommandPolicyRuntime } from "./command-policy-runtime.js";
|
|
10
12
|
type ConnectorState = "disabled" | "unpaired" | "connecting" | "online" | "offline" | "recovery_required" | "revoked" | "stopped";
|
|
11
13
|
export interface CloudConnectorStatus {
|
|
12
14
|
state: ConnectorState;
|
|
@@ -68,6 +70,8 @@ export declare class CloudConnectorController implements CloudConnector {
|
|
|
68
70
|
database?: never;
|
|
69
71
|
}));
|
|
70
72
|
setControlHandler(handler: BotControlHandler, commands?: GuildCommandReadHandler): void;
|
|
73
|
+
/** Called only after the runtime has claimed its immutable SQL owner scope. */
|
|
74
|
+
setCommandPolicyDelivery(runtime: CommandPolicyRuntime): Promise<void>;
|
|
71
75
|
start(): Promise<void>;
|
|
72
76
|
status(): CloudConnectorStatus;
|
|
73
77
|
getSupportConsent(): Promise<CloudConnectorConsentRecord>;
|
|
@@ -88,6 +92,7 @@ export declare class CloudConnectorUserError extends Error {
|
|
|
88
92
|
constructor(message: string);
|
|
89
93
|
}
|
|
90
94
|
export declare function mapControlError(error: unknown): "not_found" | "conflict" | "bad_request" | "forbidden" | "temporarily_unavailable" | "internal_error";
|
|
95
|
+
export declare function cloudControlFailureResponse(operation: CloudControlRequestPayload["operation"], error: unknown): z.infer<typeof cloudControlResponsePayloadSchema>;
|
|
91
96
|
export declare function safeControlMessage(error: unknown): string;
|
|
92
97
|
export {};
|
|
93
98
|
//# sourceMappingURL=cloud-connector.d.ts.map
|