@helyx/bot 1.1.0 → 1.2.1
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 +71 -0
- package/README.md +16 -1
- package/dist/auto-moderation-scanner-registry.d.ts +27 -0
- package/dist/auto-moderation-scanner-registry.js +172 -0
- package/dist/cloud-connector.js +3 -0
- package/dist/cloud-control-dispatcher.js +32 -0
- package/dist/command-path-contract.js +1 -0
- package/dist/control-permissions.d.ts +2 -2
- package/dist/control-permissions.js +4 -2
- package/dist/control-server.d.ts +72 -16
- package/dist/control-server.js +150 -250
- package/dist/dashboard-action-control.d.ts +25 -0
- package/dist/dashboard-action-control.js +73 -0
- package/dist/dashboard-action-routes.d.ts +9 -0
- package/dist/dashboard-action-routes.js +36 -0
- package/dist/dashboard-feature-control.d.ts +37 -0
- package/dist/dashboard-feature-control.js +206 -0
- package/dist/dashboard-module-projection.d.ts +9 -0
- package/dist/dashboard-module-projection.js +15 -0
- package/dist/dashboard-preview-import-routes.d.ts +6 -0
- package/dist/dashboard-preview-import-routes.js +121 -0
- package/dist/discord-analytics.d.ts +3 -0
- package/dist/discord-analytics.js +49 -5
- package/dist/discord-auto-moderation-service.d.ts +21 -0
- package/dist/discord-auto-moderation-service.js +425 -0
- package/dist/discord-guild-health.d.ts +2 -2
- package/dist/discord-member-enforcement-service.d.ts +26 -0
- package/dist/discord-member-enforcement-service.js +590 -0
- package/dist/discord-message-enforcement-service.d.ts +9 -0
- package/dist/discord-message-enforcement-service.js +102 -0
- package/dist/discord-violation-thread-service.d.ts +33 -0
- package/dist/discord-violation-thread-service.js +789 -0
- package/dist/ecko-evidence-receiver.d.ts +32 -0
- package/dist/ecko-evidence-receiver.js +173 -0
- package/dist/ecko-transport-auth.d.ts +19 -0
- package/dist/ecko-transport-auth.js +80 -0
- package/dist/ecko-transport-routes.d.ts +19 -0
- package/dist/ecko-transport-routes.js +130 -0
- package/dist/embedded-auto-moderation-pool.d.ts +46 -0
- package/dist/embedded-auto-moderation-pool.js +324 -0
- package/dist/embedded-auto-moderation-scanner.d.ts +34 -0
- package/dist/embedded-auto-moderation-scanner.js +263 -0
- package/dist/embedded-auto-moderation-worker-runtime.d.ts +37 -0
- package/dist/embedded-auto-moderation-worker-runtime.js +37 -0
- package/dist/embedded-auto-moderation-worker.d.ts +2 -0
- package/dist/embedded-auto-moderation-worker.js +13 -0
- package/dist/events.d.ts +5 -1
- package/dist/events.js +243 -4
- package/dist/guilds.d.ts +2 -2
- package/dist/guilds.js +14 -8
- package/dist/hosted-server-moderation.d.ts +16 -5
- package/dist/hosted-server-moderation.js +32 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +84 -14
- package/dist/moderation-action-registry.d.ts +11 -0
- package/dist/moderation-action-registry.js +375 -0
- package/dist/moderation-case-disassociation.d.ts +13 -0
- package/dist/moderation-case-disassociation.js +83 -0
- package/dist/moderation-platform-services.d.ts +27 -0
- package/dist/moderation-platform-services.js +50 -0
- package/dist/modules.d.ts +6 -0
- package/dist/modules.js +65 -3
- package/dist/privileged-message-metadata-cache.d.ts +35 -0
- package/dist/privileged-message-metadata-cache.js +70 -0
- package/dist/scheduled-task-runner.js +24 -1
- package/dist/shard-contracts.d.ts +151 -1
- package/dist/shard-contracts.js +142 -1
- package/dist/shard-coordinator.d.ts +1 -1
- package/dist/shard-ecko-scanner-registry.d.ts +38 -0
- package/dist/shard-ecko-scanner-registry.js +96 -0
- package/dist/shard-router.d.ts +23 -0
- package/dist/shard-router.js +69 -5
- package/dist/shard-supervisor.d.ts +11 -0
- package/dist/shard-supervisor.js +67 -4
- package/dist/shard-worker.d.ts +15 -1
- package/dist/shard-worker.js +95 -17
- package/dist/transient-violation-evidence.d.ts +39 -0
- package/dist/transient-violation-evidence.js +132 -0
- package/documentation/README.md +2 -0
- package/documentation/examples/.env.example +12 -0
- package/documentation/guides/self-hosting.md +17 -1
- package/documentation/modules/auto-moderation.md +51 -0
- package/documentation/modules/moderation.md +42 -0
- package/documentation/reference/environment.md +11 -0
- package/package.json +17 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,76 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Load the embedded Auto Moderation engine only for a self-hosted runtime with its
|
|
8
|
+
scanner provider installed. Core-only and public-module installations no longer
|
|
9
|
+
require the optional Testing module to import or start the bot. Refresh the
|
|
10
|
+
self-host catalogue to reference the corrected bot release.
|
|
11
|
+
|
|
12
|
+
## 1.2.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- 00d33b3: Add the Testing Moderation and Auto Moderation modules, including accountable
|
|
17
|
+
staff cases, private violation threads, Discord-native rule ownership, bounded
|
|
18
|
+
rule imports, deterministic repeat-offender actions and shared Hosted and
|
|
19
|
+
Self-Hosted Enhanced matching. Add the private Helyx Ecko transport, operational
|
|
20
|
+
gate, aggregate health reporting, content-free scanner telemetry and portable
|
|
21
|
+
Self-Hosted composition while preserving Standard native enforcement as the
|
|
22
|
+
failure fallback.
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- Updated dependencies [00d33b3]
|
|
27
|
+
- @helyx/config@1.2.0
|
|
28
|
+
- @helyx/control-protocol@0.5.0
|
|
29
|
+
- @helyx/database@2.2.0
|
|
30
|
+
- @helyx/module-manifest@0.10.0
|
|
31
|
+
- @helyx/platform@1.1.0
|
|
32
|
+
- @helyx/sdk@0.12.0
|
|
33
|
+
- @helyx/status-client@0.2.0
|
|
34
|
+
- @helyx/module-automod@1.0.1
|
|
35
|
+
- @helyx/module-moderation@1.0.1
|
|
36
|
+
|
|
37
|
+
## Unreleased
|
|
38
|
+
|
|
39
|
+
### Minor Changes
|
|
40
|
+
|
|
41
|
+
- Add deployment-neutral Moderation and Auto Moderation adapters, closed
|
|
42
|
+
provider registries, feature-scoped message event handling, and bounded
|
|
43
|
+
dashboard preview/import feature gates for the Phase 6 module boundary.
|
|
44
|
+
- Replace the unused action-v1 and public staff-thread drafts with policy-owned
|
|
45
|
+
action v2, bounded demotion, role-gated private violation threads and a
|
|
46
|
+
memory-only single-delivery evidence path.
|
|
47
|
+
- Keep enhanced scanning local to the embedded adapter for self-hosted
|
|
48
|
+
deployments and explicitly bind hosted scanner calls to the Ecko adapter.
|
|
49
|
+
|
|
50
|
+
## 1.1.2
|
|
51
|
+
|
|
52
|
+
### Patch Changes
|
|
53
|
+
|
|
54
|
+
- 707b68c: Keep hosted server owner observation, manual refresh, and admin leave delivery available independently of the hosted moderation incident gate.
|
|
55
|
+
- Preserve successful support-notification acknowledgements across child-process IPC so delivered Discord notifications are not retried and falsely marked as failed.
|
|
56
|
+
|
|
57
|
+
## 1.1.1
|
|
58
|
+
|
|
59
|
+
### Patch Changes
|
|
60
|
+
|
|
61
|
+
- Bound Discord thread inspection to the scheduled-task deadline, reuse recent
|
|
62
|
+
active-thread snapshots, allow existing Analytics filter exclusions to be
|
|
63
|
+
replaced atomically, and remove duplicate dashboard columns that contributed to
|
|
64
|
+
mobile overflow.
|
|
65
|
+
- Expose the hosted-moderation runtime flag through the authenticated bot control
|
|
66
|
+
boundary so Admin incident-gate availability cannot report a disabled runtime
|
|
67
|
+
as enabled.
|
|
68
|
+
- Deliver Admin leave and blacklist requests immediately through the owning bot
|
|
69
|
+
shard while retaining their durable retry queue, and persist cached Discord
|
|
70
|
+
owner usernames atomically with hosted server reconciliation. Add an audited
|
|
71
|
+
Admin fallback that refreshes a missing owner username through the owning bot
|
|
72
|
+
shard and saves the observation.
|
|
73
|
+
|
|
3
74
|
## 1.1.0
|
|
4
75
|
|
|
5
76
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -34,7 +34,22 @@ upgrades preserve each server's existing enabled or disabled state.
|
|
|
34
34
|
|
|
35
35
|
Gateway intents are derived from installed event contributions. The bot always requests Guilds, adds non-privileged reaction access only for reaction modules, and adds the privileged Server Members intent only when an installed module handles member joins. Deployments containing such a module must enable the matching intent in the Discord Developer Portal before startup.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Moderation integrations use closed, versioned service contracts for native
|
|
38
|
+
Discord Auto Moderation rules, member and message enforcement, restricted
|
|
39
|
+
private violation threads, transient evidence and module-owned action/policy
|
|
40
|
+
providers. Auto Moderation supplies the live rule policy and Moderation owns the
|
|
41
|
+
effect; the caller cannot choose an offence step or action. Feature-scoped
|
|
42
|
+
privileged message listeners are excluded from the hosted main bot; self-hosted
|
|
43
|
+
runtimes may compose the same scanning contract through the explicitly selected
|
|
44
|
+
embedded adapter when the module feature is available. Hosted Ecko transport and
|
|
45
|
+
embedded callers are identity-bound and cannot be interchanged.
|
|
46
|
+
|
|
47
|
+
Dashboard settings previews and managed-resource imports are bounded,
|
|
48
|
+
non-persisting module operations. Core validates their declared inputs and
|
|
49
|
+
fresh feature access before execution, then validates and projects structured
|
|
50
|
+
results before they cross the control boundary.
|
|
51
|
+
|
|
52
|
+
When `HELYX_BOT_CONTROL_SECRET` is configured, the bot also starts a private authenticated HTTP control server on `HELYX_BOT_CONTROL_PORT`. The dashboard API uses this service to obtain real Discord roles and channels and to apply validated module, configuration and permission changes. `/health` is unauthenticated. Ordinary `/v1` routes require the shared bearer secret; the narrow `/v1/ecko/*` routes instead require their dedicated rotating HMAC transport key. Keep this endpoint on a private service network and do not assign it a public domain.
|
|
38
53
|
|
|
39
54
|
Optional Helyx Cloud Connect is a separate outbound management path for self-hosted deployments. It is disabled unless `HELYX_CLOUD_CONNECT_ENABLED=true` and requires a separate local `HELYX_DATA_ENCRYPTION_KEY`. Preserve that key across every restart and deployment. If it is lost or changed, revoke the old installation in the dashboard and use a new one-use code with `/helyx-cloud connect`; the connector stops retrying until that recovery is completed. The owner-only `/helyx-cloud` command supports connection-code enrolment, status, credential rotation, disconnection and bounded support consent. The bot never sends its Discord token, database URL or connector private key to Helyx Cloud, and local Discord operation continues when Cloud is unavailable.
|
|
40
55
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AutoModerationEnhancedResultOutcome, AutoModerationScannerAdapterSelection, AutoModerationScannerConfigurationPageV1, AutoModerationScannerProviderRegistryService, ModuleDefinition, ServiceAccess } from "@helyx/sdk";
|
|
2
|
+
import { AUTOMOD_ENHANCED_CONTRACT_VERSION, AUTOMOD_ENHANCED_PROVIDER_ID } from "@helyx/sdk";
|
|
3
|
+
import type { TransientViolationEvidenceStore } from "./transient-violation-evidence.js";
|
|
4
|
+
/** Closed scanner registry local to an unsharded bot or one guild-owning worker. */
|
|
5
|
+
export declare class BotAutoModerationScannerRegistry implements AutoModerationScannerProviderRegistryService {
|
|
6
|
+
#private;
|
|
7
|
+
private readonly services;
|
|
8
|
+
private readonly runtimeProfile;
|
|
9
|
+
private readonly evidenceStore?;
|
|
10
|
+
private readonly deploymentEnvironment;
|
|
11
|
+
constructor(modules: readonly ModuleDefinition[], services: ServiceAccess, runtimeProfile: "hosted" | "self-hosted", evidenceStore?: TransientViolationEvidenceStore | undefined, deploymentEnvironment?: string);
|
|
12
|
+
select(input: {
|
|
13
|
+
guildId: string;
|
|
14
|
+
moduleId: string;
|
|
15
|
+
featureId: string;
|
|
16
|
+
}): Promise<AutoModerationScannerAdapterSelection>;
|
|
17
|
+
getConfigurationPage(input: {
|
|
18
|
+
adapter: "hosted_ecko" | "embedded";
|
|
19
|
+
providerId: typeof AUTOMOD_ENHANCED_PROVIDER_ID;
|
|
20
|
+
contractVersion: typeof AUTOMOD_ENHANCED_CONTRACT_VERSION;
|
|
21
|
+
cursor?: string;
|
|
22
|
+
limit: number;
|
|
23
|
+
knownFingerprint?: string;
|
|
24
|
+
}): Promise<AutoModerationScannerConfigurationPageV1>;
|
|
25
|
+
acceptResult(input: Parameters<AutoModerationScannerProviderRegistryService["acceptResult"]>[0]): Promise<AutoModerationEnhancedResultOutcome>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=auto-moderation-scanner-registry.d.ts.map
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { HELYX_AUTOMOD_ENHANCED_SCANNING_FEATURE } from "@helyx/module-manifest";
|
|
2
|
+
import { AUTOMOD_ENHANCED_CONFIG_PROTOCOL, AUTOMOD_ENHANCED_CONTRACT_VERSION, AUTOMOD_ENHANCED_EVIDENCE_PROTOCOL, AUTOMOD_ENHANCED_PROVIDER_ID, AUTOMOD_ENHANCED_RESULT_PROTOCOL, AUTOMOD_SCANNER_LIMITS, HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
3
|
+
const AUTOMOD_MODULE_ID = "helyx.automod";
|
|
4
|
+
/** Closed scanner registry local to an unsharded bot or one guild-owning worker. */
|
|
5
|
+
export class BotAutoModerationScannerRegistry {
|
|
6
|
+
services;
|
|
7
|
+
runtimeProfile;
|
|
8
|
+
evidenceStore;
|
|
9
|
+
deploymentEnvironment;
|
|
10
|
+
#provider;
|
|
11
|
+
constructor(modules, services, runtimeProfile, evidenceStore, deploymentEnvironment = runtimeProfile === "hosted"
|
|
12
|
+
? "production"
|
|
13
|
+
: "self-hosted") {
|
|
14
|
+
this.services = services;
|
|
15
|
+
this.runtimeProfile = runtimeProfile;
|
|
16
|
+
this.evidenceStore = evidenceStore;
|
|
17
|
+
this.deploymentEnvironment = deploymentEnvironment;
|
|
18
|
+
const providers = modules.flatMap((module) => module.autoModerationScannerProvider
|
|
19
|
+
? [
|
|
20
|
+
{
|
|
21
|
+
moduleId: module.manifest.id,
|
|
22
|
+
provider: module.autoModerationScannerProvider,
|
|
23
|
+
},
|
|
24
|
+
]
|
|
25
|
+
: []);
|
|
26
|
+
if (providers.length > 1)
|
|
27
|
+
throw new Error("Auto Moderation scanner provider ownership is ambiguous");
|
|
28
|
+
const registered = providers[0];
|
|
29
|
+
if (!registered) {
|
|
30
|
+
this.#provider = null;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (registered.moduleId !== AUTOMOD_MODULE_ID ||
|
|
34
|
+
registered.provider.providerId !== AUTOMOD_ENHANCED_PROVIDER_ID ||
|
|
35
|
+
registered.provider.contractVersion !==
|
|
36
|
+
AUTOMOD_ENHANCED_CONTRACT_VERSION ||
|
|
37
|
+
registered.provider.configurationProtocol !==
|
|
38
|
+
AUTOMOD_ENHANCED_CONFIG_PROTOCOL ||
|
|
39
|
+
registered.provider.resultProtocol !== AUTOMOD_ENHANCED_RESULT_PROTOCOL ||
|
|
40
|
+
registered.provider.evidenceProtocol !==
|
|
41
|
+
AUTOMOD_ENHANCED_EVIDENCE_PROTOCOL)
|
|
42
|
+
throw new Error("Auto Moderation scanner provider registration is invalid");
|
|
43
|
+
this.#provider = registered.provider;
|
|
44
|
+
}
|
|
45
|
+
async select(input) {
|
|
46
|
+
if (!this.#provider ||
|
|
47
|
+
input.moduleId !== AUTOMOD_MODULE_ID ||
|
|
48
|
+
input.featureId !== HELYX_AUTOMOD_ENHANCED_SCANNING_FEATURE)
|
|
49
|
+
return unavailable("dependency_unhealthy");
|
|
50
|
+
try {
|
|
51
|
+
if (!(await this.#moduleEnabled(input.guildId)))
|
|
52
|
+
return unavailable("dependency_unhealthy");
|
|
53
|
+
const access = await this.services
|
|
54
|
+
.get(HELYX_SERVICE_NAMES.featureAccess)
|
|
55
|
+
.check(input);
|
|
56
|
+
if (access.decision !== "granted")
|
|
57
|
+
return unavailable(access.decision === "not_entitled"
|
|
58
|
+
? "feature_unavailable"
|
|
59
|
+
: access.decision);
|
|
60
|
+
return this.runtimeProfile === "hosted"
|
|
61
|
+
? { adapter: "hosted_ecko", available: true }
|
|
62
|
+
: { adapter: "embedded", available: true };
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return unavailable("dependency_unhealthy");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async getConfigurationPage(input) {
|
|
69
|
+
if (!this.#provider ||
|
|
70
|
+
input.adapter !== expectedAdapter(this.runtimeProfile) ||
|
|
71
|
+
input.providerId !== AUTOMOD_ENHANCED_PROVIDER_ID ||
|
|
72
|
+
input.contractVersion !== AUTOMOD_ENHANCED_CONTRACT_VERSION ||
|
|
73
|
+
!Number.isInteger(input.limit) ||
|
|
74
|
+
input.limit < 1 ||
|
|
75
|
+
input.limit > AUTOMOD_SCANNER_LIMITS.configurationPageServers)
|
|
76
|
+
throw new Error("Auto Moderation scanner configuration request is invalid");
|
|
77
|
+
const page = await this.#provider.getConfigurationPage({ services: this.services }, {
|
|
78
|
+
limit: input.limit,
|
|
79
|
+
...(input.cursor ? { cursor: input.cursor } : {}),
|
|
80
|
+
...(input.knownFingerprint
|
|
81
|
+
? { knownFingerprint: input.knownFingerprint }
|
|
82
|
+
: {}),
|
|
83
|
+
});
|
|
84
|
+
validateConfigurationPage(page);
|
|
85
|
+
return page;
|
|
86
|
+
}
|
|
87
|
+
async acceptResult(input) {
|
|
88
|
+
try {
|
|
89
|
+
if (input.adapter !== expectedAdapter(this.runtimeProfile))
|
|
90
|
+
return rejected("wrong_owner");
|
|
91
|
+
if (!this.#provider ||
|
|
92
|
+
input.providerId !== AUTOMOD_ENHANCED_PROVIDER_ID ||
|
|
93
|
+
input.contractVersion !== AUTOMOD_ENHANCED_CONTRACT_VERSION)
|
|
94
|
+
return rejected("unknown_version");
|
|
95
|
+
let result;
|
|
96
|
+
try {
|
|
97
|
+
result = this.#provider.validateResult(input.result);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return rejected("invalid_envelope");
|
|
101
|
+
}
|
|
102
|
+
if (result.protocol !== AUTOMOD_ENHANCED_RESULT_PROTOCOL ||
|
|
103
|
+
result.providerId !== AUTOMOD_ENHANCED_PROVIDER_ID ||
|
|
104
|
+
result.contractVersion !== AUTOMOD_ENHANCED_CONTRACT_VERSION)
|
|
105
|
+
return rejected("unknown_version");
|
|
106
|
+
if (result.environment !== this.deploymentEnvironment)
|
|
107
|
+
return rejected("wrong_owner");
|
|
108
|
+
const evidenceReference = input.evidenceReference ?? null;
|
|
109
|
+
if (evidenceReference &&
|
|
110
|
+
(evidenceReference.source !== "enhanced_scanner" ||
|
|
111
|
+
evidenceReference.sourceEventId !== result.operationId ||
|
|
112
|
+
evidenceReference.configurationRevision !==
|
|
113
|
+
result.configurationRevision ||
|
|
114
|
+
evidenceReference.guildId !== result.guildId ||
|
|
115
|
+
evidenceReference.channelId !== result.channelId ||
|
|
116
|
+
evidenceReference.messageId !== result.messageId ||
|
|
117
|
+
evidenceReference.authorUserId !== result.authorUserId))
|
|
118
|
+
return rejected("invalid_envelope");
|
|
119
|
+
if (!(await this.#moduleEnabled(result.guildId)))
|
|
120
|
+
return rejected("module_unavailable");
|
|
121
|
+
const access = await this.services
|
|
122
|
+
.get(HELYX_SERVICE_NAMES.featureAccess)
|
|
123
|
+
.check({
|
|
124
|
+
guildId: result.guildId,
|
|
125
|
+
moduleId: AUTOMOD_MODULE_ID,
|
|
126
|
+
featureId: HELYX_AUTOMOD_ENHANCED_SCANNING_FEATURE,
|
|
127
|
+
})
|
|
128
|
+
.catch(() => ({ decision: "dependency_unhealthy" }));
|
|
129
|
+
if (access.decision !== "granted")
|
|
130
|
+
return rejected("feature_unavailable");
|
|
131
|
+
try {
|
|
132
|
+
return await this.#provider.handleResult({ services: this.services, evidenceReference }, result);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return rejected("module_unavailable");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
if (input.evidenceReference)
|
|
140
|
+
this.evidenceStore?.discard(input.evidenceReference);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async #moduleEnabled(guildId) {
|
|
144
|
+
const installation = this.services.get(HELYX_SERVICE_NAMES.installations);
|
|
145
|
+
if (!(await installation.isModuleEnabled(guildId, AUTOMOD_MODULE_ID)))
|
|
146
|
+
return false;
|
|
147
|
+
if (!this.services.has(HELYX_SERVICE_NAMES.moduleAvailability))
|
|
148
|
+
return true;
|
|
149
|
+
return this.services
|
|
150
|
+
.get(HELYX_SERVICE_NAMES.moduleAvailability)
|
|
151
|
+
.isAvailable(AUTOMOD_MODULE_ID);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function expectedAdapter(runtimeProfile) {
|
|
155
|
+
return runtimeProfile === "hosted" ? "hosted_ecko" : "embedded";
|
|
156
|
+
}
|
|
157
|
+
function validateConfigurationPage(page) {
|
|
158
|
+
if (page.protocol !== AUTOMOD_ENHANCED_CONFIG_PROTOCOL ||
|
|
159
|
+
page.providerId !== AUTOMOD_ENHANCED_PROVIDER_ID ||
|
|
160
|
+
page.contractVersion !== AUTOMOD_ENHANCED_CONTRACT_VERSION ||
|
|
161
|
+
page.items.length > AUTOMOD_SCANNER_LIMITS.configurationPageServers ||
|
|
162
|
+
Buffer.byteLength(JSON.stringify(page), "utf8") >
|
|
163
|
+
AUTOMOD_SCANNER_LIMITS.configurationPageBytes)
|
|
164
|
+
throw new Error("Auto Moderation scanner configuration page is invalid");
|
|
165
|
+
}
|
|
166
|
+
function unavailable(reason) {
|
|
167
|
+
return { adapter: "unavailable", available: false, reason };
|
|
168
|
+
}
|
|
169
|
+
function rejected(code) {
|
|
170
|
+
return { outcome: "rejected", code };
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=auto-moderation-scanner-registry.js.map
|
package/dist/cloud-connector.js
CHANGED
|
@@ -725,13 +725,16 @@ function connectorCapabilities() {
|
|
|
725
725
|
"guild.module.public.read.v1",
|
|
726
726
|
"guild.module.status.write.v1",
|
|
727
727
|
"guild.module.configuration.write.v1",
|
|
728
|
+
"guild.module.configuration.preview.v1",
|
|
728
729
|
"guild.module.permissions.write.v1",
|
|
729
730
|
"guild.module.logging.write.v1",
|
|
730
731
|
"guild.module.action.execute.v1",
|
|
732
|
+
"guild.module.action.preview.v1",
|
|
731
733
|
"guild.module.resource.list.v1",
|
|
732
734
|
"guild.module.resource.read.v1",
|
|
733
735
|
"guild.module.resource.relation.list.v1",
|
|
734
736
|
"guild.module.resource.preview.v1",
|
|
737
|
+
"guild.module.resource.import.v1",
|
|
735
738
|
"guild.module.resource.create.v1",
|
|
736
739
|
"guild.module.resource.update.v1",
|
|
737
740
|
"guild.module.resource.delete.v1",
|
|
@@ -83,6 +83,15 @@ export class CloudControlDispatcher {
|
|
|
83
83
|
});
|
|
84
84
|
break;
|
|
85
85
|
}
|
|
86
|
+
case "guild.module.configuration.preview.v1": {
|
|
87
|
+
const input = parseOperationInput(request.operation, request.input);
|
|
88
|
+
result = await this.control.previewConfiguration({
|
|
89
|
+
...input,
|
|
90
|
+
actorUserId: requiredActor(actorUserId),
|
|
91
|
+
correlationId,
|
|
92
|
+
});
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
86
95
|
case "guild.module.permissions.write.v1": {
|
|
87
96
|
const input = parseOperationInput(request.operation, request.input);
|
|
88
97
|
result = await this.control.updatePermissions({
|
|
@@ -104,6 +113,20 @@ export class CloudControlDispatcher {
|
|
|
104
113
|
case "guild.module.action.execute.v1": {
|
|
105
114
|
const input = parseOperationInput(request.operation, request.input);
|
|
106
115
|
result = await this.control.executeDashboardAction({
|
|
116
|
+
guildId: input.guildId,
|
|
117
|
+
moduleId: input.moduleId,
|
|
118
|
+
actionId: input.actionId,
|
|
119
|
+
...(input.inspectionRevision === undefined
|
|
120
|
+
? {}
|
|
121
|
+
: { inspectionRevision: input.inspectionRevision }),
|
|
122
|
+
actorUserId: requiredActor(actorUserId),
|
|
123
|
+
correlationId,
|
|
124
|
+
});
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case "guild.module.action.preview.v1": {
|
|
128
|
+
const input = parseOperationInput(request.operation, request.input);
|
|
129
|
+
result = await this.control.previewDashboardAction({
|
|
107
130
|
...input,
|
|
108
131
|
actorUserId: requiredActor(actorUserId),
|
|
109
132
|
correlationId,
|
|
@@ -160,6 +183,15 @@ export class CloudControlDispatcher {
|
|
|
160
183
|
});
|
|
161
184
|
break;
|
|
162
185
|
}
|
|
186
|
+
case "guild.module.resource.import.v1": {
|
|
187
|
+
const input = parseOperationInput(request.operation, request.input);
|
|
188
|
+
result = await this.control.importManagedResource({
|
|
189
|
+
...input,
|
|
190
|
+
actorUserId: requiredActor(actorUserId),
|
|
191
|
+
correlationId,
|
|
192
|
+
});
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
163
195
|
case "guild.module.resource.create.v1": {
|
|
164
196
|
const input = parseOperationInput(request.operation, request.input);
|
|
165
197
|
result = await this.control.createManagedResource({
|
|
@@ -32,6 +32,7 @@ export function collectCommandPermissionTargets(module) {
|
|
|
32
32
|
description: command.description,
|
|
33
33
|
kind: "command",
|
|
34
34
|
})),
|
|
35
|
+
...permissionOperations(module.permissionOperations),
|
|
35
36
|
...(module.interactions?.commands ?? []).flatMap((command) => [
|
|
36
37
|
...permissionOperations(command.permissionOperations),
|
|
37
38
|
...(command.subcommands ?? []).flatMap((subcommand) => permissionOperations(subcommand.permissionOperations)),
|
|
@@ -23,9 +23,9 @@ export interface ResolvedPermissionPolicy extends PermissionPolicyTarget {
|
|
|
23
23
|
version: bigint | null;
|
|
24
24
|
inherited: boolean;
|
|
25
25
|
}
|
|
26
|
-
export type ManagedResourceOperation = "list" | "read" | "preview" | "create" | "update" | "delete" | "action";
|
|
26
|
+
export type ManagedResourceOperation = "list" | "read" | "preview" | "create" | "update" | "delete" | "action" | "import";
|
|
27
27
|
export declare function declaredManagedResourcePermission(module: ModuleDefinition, resourceType: string, operation: ManagedResourceOperation): string | undefined;
|
|
28
|
-
export declare function dashboardActionPermissionId(module: ModuleDefinition, actionId: string): string;
|
|
28
|
+
export declare function dashboardActionPermissionId(module: ModuleDefinition, actionId: string, declaredPermissionId?: string): string;
|
|
29
29
|
export declare function managedResourcePermissionId(module: ModuleDefinition, resourceType: string, operation: ManagedResourceOperation, actionId?: string, declaredPermissionId?: string): string;
|
|
30
30
|
export declare function permissionPolicyKey(target: PermissionPolicyTarget): string;
|
|
31
31
|
export declare function toDashboardPolicy(policy: PermissionPolicy): DashboardPermissionPolicy;
|
|
@@ -4,7 +4,9 @@ export function declaredManagedResourcePermission(module, resourceType, operatio
|
|
|
4
4
|
return undefined;
|
|
5
5
|
return module.manifest.dashboard?.resources?.find(({ id }) => id === resourceType)?.permissions?.[operation];
|
|
6
6
|
}
|
|
7
|
-
export function dashboardActionPermissionId(module, actionId) {
|
|
7
|
+
export function dashboardActionPermissionId(module, actionId, declaredPermissionId) {
|
|
8
|
+
if (declaredPermissionId)
|
|
9
|
+
return declaredPermissionId;
|
|
8
10
|
const namespace = modulePermissionNamespace(module);
|
|
9
11
|
return selectDeclaredPermission(module, [
|
|
10
12
|
`${namespace}.${actionId.replaceAll("-", ".")}`,
|
|
@@ -17,7 +19,7 @@ export function managedResourcePermissionId(module, resourceType, operation, act
|
|
|
17
19
|
return declaredPermissionId;
|
|
18
20
|
const namespace = modulePermissionNamespace(module);
|
|
19
21
|
const resourcePermission = `resource.${resourceType}`;
|
|
20
|
-
const candidates = operation === "create" || operation === "preview"
|
|
22
|
+
const candidates = operation === "create" || operation === "preview" || operation === "import"
|
|
21
23
|
? [
|
|
22
24
|
`${resourcePermission}.create`,
|
|
23
25
|
`${namespace}.create`,
|
package/dist/control-server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type DatabasePool, type GuildShardOwnership } from "@helyx/database";
|
|
2
|
-
import { type DashboardActionResult, type GuildAccessPolicy, type ManagedResourceCreationIntent, type ManagedResourceDetail, type ManagedResourcePage, type ManagedResourceRelationPage, type ManagedResourceValue, type MessageResponse, type ModuleDefinition, type OAuthConsumerCleanupResult, type PermissionActor, type PrivacyCapturePage, type PrivacyErasePage, type PrivacyProviderExecutor, type PublicAchievementProjectionPage, type ServiceAccess } from "@helyx/sdk";
|
|
2
|
+
import { type DashboardActionInspection, type DashboardActionResult, type FeatureAccessDecision, type GuildAccessPolicy, type ManagedResourceCreationIntent, type ManagedResourceDetail, type ManagedResourcePage, type ManagedResourceRelationPage, type ManagedResourceValue, type MessageResponse, type ModuleDefinition, type OAuthConsumerCleanupResult, type PermissionActor, type PrivacyCapturePage, type PrivacyErasePage, type PrivacyProviderExecutor, type PublicAchievementProjectionPage, type ServiceAccess } from "@helyx/sdk";
|
|
3
3
|
import { type ActionReceipt, type SignedAssessmentEnvelope } from "@helyx/verification-contracts";
|
|
4
4
|
import { type Client } from "discord.js";
|
|
5
5
|
import { type FastifyInstance } from "fastify";
|
|
@@ -10,6 +10,12 @@ import { type DashboardPermissionState } from "./control-permissions.js";
|
|
|
10
10
|
import { type DashboardModuleDefinition } from "./dashboard-module-projection.js";
|
|
11
11
|
import { type AvailabilityModuleCatalogue } from "./module-availability-control.js";
|
|
12
12
|
import { registerGuildCommandControlRoutes } from "./guild-command-control-routes.js";
|
|
13
|
+
import { type DashboardActionInput, type DashboardActionPreviewInput } from "./dashboard-action-control.js";
|
|
14
|
+
import { type DashboardActionHandler } from "./dashboard-action-routes.js";
|
|
15
|
+
import { type EckoTelemetrySink } from "./ecko-transport-routes.js";
|
|
16
|
+
import type { EckoEvidenceReceiver } from "./ecko-evidence-receiver.js";
|
|
17
|
+
import type { EckoTransportAuthenticator } from "./ecko-transport-auth.js";
|
|
18
|
+
import type { AutoModerationScannerProviderRegistryService } from "@helyx/sdk";
|
|
13
19
|
interface DashboardLoggingPolicy {
|
|
14
20
|
eventId: string;
|
|
15
21
|
enabled: boolean;
|
|
@@ -93,6 +99,7 @@ export interface BotControlState {
|
|
|
93
99
|
channels: BotControlGuildResources["channels"];
|
|
94
100
|
roles: BotControlGuildResources["roles"];
|
|
95
101
|
modules: DashboardModuleDefinition[];
|
|
102
|
+
featureAccess?: Record<string, Record<string, FeatureAccessDecision>>;
|
|
96
103
|
moduleStatuses: Record<string, "enabled" | "disabled" | "unavailable">;
|
|
97
104
|
globallyDisabledModuleIds: string[];
|
|
98
105
|
configurations: Record<string, {
|
|
@@ -110,7 +117,7 @@ export interface DashboardGuildAccessState extends GuildAccessPolicy {
|
|
|
110
117
|
actorHighestRolePosition: number | null;
|
|
111
118
|
updatedAt: string | null;
|
|
112
119
|
}
|
|
113
|
-
export interface BotControlHandler extends FirstTimeCommandSetupHandler {
|
|
120
|
+
export interface BotControlHandler extends FirstTimeCommandSetupHandler, DashboardActionHandler {
|
|
114
121
|
cleanupOAuthConsumer(input: {
|
|
115
122
|
consumerBindingId: string;
|
|
116
123
|
moduleId: string;
|
|
@@ -176,6 +183,14 @@ export interface BotControlHandler extends FirstTimeCommandSetupHandler {
|
|
|
176
183
|
value: Record<string, unknown>;
|
|
177
184
|
version: number;
|
|
178
185
|
}>;
|
|
186
|
+
previewConfiguration(input: {
|
|
187
|
+
guildId: string;
|
|
188
|
+
moduleId: string;
|
|
189
|
+
value: Record<string, unknown>;
|
|
190
|
+
input: Readonly<Record<string, ManagedResourceValue>>;
|
|
191
|
+
actorUserId: string;
|
|
192
|
+
correlationId: string;
|
|
193
|
+
}): Promise<MessageResponse>;
|
|
179
194
|
updatePermissions(input: {
|
|
180
195
|
guildId: string;
|
|
181
196
|
moduleId: string;
|
|
@@ -192,13 +207,6 @@ export interface BotControlHandler extends FirstTimeCommandSetupHandler {
|
|
|
192
207
|
actorUserId: string;
|
|
193
208
|
correlationId: string;
|
|
194
209
|
}): Promise<DashboardLoggingPolicy>;
|
|
195
|
-
executeDashboardAction(input: {
|
|
196
|
-
guildId: string;
|
|
197
|
-
moduleId: string;
|
|
198
|
-
actionId: string;
|
|
199
|
-
actorUserId: string;
|
|
200
|
-
correlationId: string;
|
|
201
|
-
}): Promise<DashboardActionResult>;
|
|
202
210
|
listManagedResources(input: {
|
|
203
211
|
guildId: string;
|
|
204
212
|
moduleId: string;
|
|
@@ -239,6 +247,18 @@ export interface BotControlHandler extends FirstTimeCommandSetupHandler {
|
|
|
239
247
|
actorUserId: string;
|
|
240
248
|
correlationId: string;
|
|
241
249
|
}): Promise<MessageResponse>;
|
|
250
|
+
importManagedResource(input: {
|
|
251
|
+
guildId: string;
|
|
252
|
+
moduleId: string;
|
|
253
|
+
resourceType: string;
|
|
254
|
+
format: "utf8_text";
|
|
255
|
+
content: string;
|
|
256
|
+
actorUserId: string;
|
|
257
|
+
correlationId: string;
|
|
258
|
+
}): Promise<{
|
|
259
|
+
value: Readonly<Record<string, ManagedResourceValue>>;
|
|
260
|
+
preview: Readonly<Record<string, ManagedResourceValue>>;
|
|
261
|
+
}>;
|
|
242
262
|
createManagedResource(input: {
|
|
243
263
|
guildId: string;
|
|
244
264
|
moduleId: string;
|
|
@@ -413,6 +433,14 @@ export declare class BotControlService implements BotControlHandler {
|
|
|
413
433
|
value: Record<string, unknown>;
|
|
414
434
|
version: number;
|
|
415
435
|
}>;
|
|
436
|
+
previewConfiguration(input: {
|
|
437
|
+
guildId: string;
|
|
438
|
+
moduleId: string;
|
|
439
|
+
value: Record<string, unknown>;
|
|
440
|
+
input: Readonly<Record<string, ManagedResourceValue>>;
|
|
441
|
+
actorUserId: string;
|
|
442
|
+
correlationId: string;
|
|
443
|
+
}): Promise<Readonly<Record<string, ManagedResourceValue>>>;
|
|
416
444
|
updatePermissions(input: {
|
|
417
445
|
guildId: string;
|
|
418
446
|
moduleId: string;
|
|
@@ -429,13 +457,8 @@ export declare class BotControlService implements BotControlHandler {
|
|
|
429
457
|
actorUserId: string;
|
|
430
458
|
correlationId: string;
|
|
431
459
|
}): Promise<DashboardLoggingPolicy>;
|
|
432
|
-
executeDashboardAction(input:
|
|
433
|
-
|
|
434
|
-
moduleId: string;
|
|
435
|
-
actionId: string;
|
|
436
|
-
actorUserId: string;
|
|
437
|
-
correlationId: string;
|
|
438
|
-
}): Promise<DashboardActionResult>;
|
|
460
|
+
executeDashboardAction(input: DashboardActionInput): Promise<DashboardActionResult>;
|
|
461
|
+
previewDashboardAction(input: DashboardActionPreviewInput): Promise<DashboardActionInspection>;
|
|
439
462
|
listManagedResources(input: {
|
|
440
463
|
guildId: string;
|
|
441
464
|
moduleId: string;
|
|
@@ -476,6 +499,18 @@ export declare class BotControlService implements BotControlHandler {
|
|
|
476
499
|
actorUserId: string;
|
|
477
500
|
correlationId: string;
|
|
478
501
|
}): Promise<MessageResponse>;
|
|
502
|
+
importManagedResource(input: {
|
|
503
|
+
guildId: string;
|
|
504
|
+
moduleId: string;
|
|
505
|
+
resourceType: string;
|
|
506
|
+
format: "utf8_text";
|
|
507
|
+
content: string;
|
|
508
|
+
actorUserId: string;
|
|
509
|
+
correlationId: string;
|
|
510
|
+
}): Promise<{
|
|
511
|
+
value: Readonly<Record<string, ManagedResourceValue>>;
|
|
512
|
+
preview: Readonly<Record<string, ManagedResourceValue>>;
|
|
513
|
+
}>;
|
|
479
514
|
createManagedResource(input: {
|
|
480
515
|
guildId: string;
|
|
481
516
|
moduleId: string;
|
|
@@ -526,9 +561,30 @@ export declare function createBotControlServer(input: {
|
|
|
526
561
|
logger?: boolean | {
|
|
527
562
|
level: string;
|
|
528
563
|
};
|
|
564
|
+
runtimeConfiguration?: {
|
|
565
|
+
hostedModerationEnabled: boolean;
|
|
566
|
+
};
|
|
529
567
|
shardHealth?: () => Promise<ShardHealthSnapshot>;
|
|
530
568
|
readiness?: () => Promise<boolean>;
|
|
569
|
+
deliverHostedServerLeave?: (input: {
|
|
570
|
+
operationId: string;
|
|
571
|
+
guildId: string;
|
|
572
|
+
}) => Promise<"queued" | "completed" | "not_currently_installed" | "accepted_for_retry" | "requires_review">;
|
|
573
|
+
refreshHostedServerOwner?: (guildId: string) => Promise<{
|
|
574
|
+
username: string;
|
|
575
|
+
observedAt: string;
|
|
576
|
+
}>;
|
|
531
577
|
guildCommands?: Parameters<typeof registerGuildCommandControlRoutes>[1];
|
|
578
|
+
ecko?: {
|
|
579
|
+
authenticator: EckoTransportAuthenticator;
|
|
580
|
+
registry: AutoModerationScannerProviderRegistryService;
|
|
581
|
+
evidence: EckoEvidenceReceiver;
|
|
582
|
+
gateState(): Promise<{
|
|
583
|
+
state: "enabled" | "draining" | "disabled";
|
|
584
|
+
updatedAt: Date;
|
|
585
|
+
}>;
|
|
586
|
+
telemetry?: EckoTelemetrySink;
|
|
587
|
+
};
|
|
532
588
|
}): Promise<FastifyInstance>;
|
|
533
589
|
export declare function mapBotControlError(error: unknown): {
|
|
534
590
|
status: 400 | 403 | 404 | 409;
|