From 5ccd245ad6c3a5558b239cc001f5fc2c8e4490b6 Mon Sep 17 00:00:00 2001 From: gnuxie Date: Fri, 10 Jul 2026 12:35:54 +0100 Subject: [PATCH] Add a builder for protection descriptions. https://github.com/the-draupnir-project/planning/issues/152 --- .../src/Projection/ProjectionDescription.ts | 115 ++++++++++++++++++ .../MemberBanIntentProjectionNode.ts | 35 ++++-- .../ServerBanIntentProjectionNode.ts | 26 ++-- 3 files changed, 155 insertions(+), 21 deletions(-) diff --git a/packages/matrix-protection-suite/src/Projection/ProjectionDescription.ts b/packages/matrix-protection-suite/src/Projection/ProjectionDescription.ts index 94de0244..a25f3389 100644 --- a/packages/matrix-protection-suite/src/Projection/ProjectionDescription.ts +++ b/packages/matrix-protection-suite/src/Projection/ProjectionDescription.ts @@ -28,6 +28,121 @@ export interface ProjectionDescription< export type AnyProjectionDescription = ProjectionDescription; +type ProjectionDescriptionRuntime< + TName extends string, + TPartitionKeys extends ProjectionPartitionKeys, +> = { + readonly name: TName; + readonly partitionKeys: TPartitionKeys; +}; + +export type ProjectionDescriptionBuilder< + TName extends string, + TPartitionKeys extends ProjectionPartitionKeys, + TInputs extends readonly unknown[] = readonly unknown[], + TDownstreamDeltaShape = unknown, + TAccessMixin = Record, +> = { + withInputs< + TNextInputs extends readonly unknown[], + >(): ProjectionDescriptionBuilder< + TName, + TPartitionKeys, + TNextInputs, + TDownstreamDeltaShape, + TAccessMixin + >; + withDownstreamDeltaShape< + TNextDownstreamDeltaShape, + >(): ProjectionDescriptionBuilder< + TName, + TPartitionKeys, + TInputs, + TNextDownstreamDeltaShape, + TAccessMixin + >; + withAccessMixin(): ProjectionDescriptionBuilder< + TName, + TPartitionKeys, + TInputs, + TDownstreamDeltaShape, + TNextAccessMixin + >; + build(): ProjectionDescription< + TInputs, + TDownstreamDeltaShape, + TAccessMixin, + TPartitionKeys + > & { + readonly name: TName; + }; +}; + +function makeProjectionDescriptionBuilder< + TName extends string, + TPartitionKeys extends ProjectionPartitionKeys, + TInputs extends readonly unknown[] = readonly unknown[], + TDownstreamDeltaShape = unknown, + TAccessMixin = Record, +>( + description: ProjectionDescriptionRuntime +): ProjectionDescriptionBuilder< + TName, + TPartitionKeys, + TInputs, + TDownstreamDeltaShape, + TAccessMixin +> { + return { + withInputs() { + return makeProjectionDescriptionBuilder< + TName, + TPartitionKeys, + TNextInputs, + TDownstreamDeltaShape, + TAccessMixin + >(description); + }, + withDownstreamDeltaShape() { + return makeProjectionDescriptionBuilder< + TName, + TPartitionKeys, + TInputs, + TNextDownstreamDeltaShape, + TAccessMixin + >(description); + }, + withAccessMixin() { + return makeProjectionDescriptionBuilder< + TName, + TPartitionKeys, + TInputs, + TDownstreamDeltaShape, + TNextAccessMixin + >(description); + }, + build() { + return description; + }, + }; +} + +export function describeProjection< + const TName extends string, + const TPartitionKeys extends ProjectionPartitionKeys, +>({ + name, + partitionKeys, +}: { + readonly name: TName; + readonly partitionKeys: TPartitionKeys; +}): ProjectionDescriptionBuilder { + return makeProjectionDescriptionBuilder({ + name, + partitionKeys, + }); +} + export type ExtractProjectionDescriptionInputs< TDescription extends AnyProjectionDescription, > = diff --git a/packages/matrix-protection-suite/src/Protection/StandardProtections/MemberBanSynchronisation/MemberBanIntentProjectionNode.ts b/packages/matrix-protection-suite/src/Protection/StandardProtections/MemberBanSynchronisation/MemberBanIntentProjectionNode.ts index 3babd32c..6ce3e567 100644 --- a/packages/matrix-protection-suite/src/Protection/StandardProtections/MemberBanSynchronisation/MemberBanIntentProjectionNode.ts +++ b/packages/matrix-protection-suite/src/Protection/StandardProtections/MemberBanSynchronisation/MemberBanIntentProjectionNode.ts @@ -12,7 +12,10 @@ import { ExtractInputDeltaShapes, ProjectionNode, } from "../../../Projection/ProjectionNode"; -import { ProjectionDescription } from "../../../Projection/ProjectionDescription"; +import { + describeProjection, + ProjectionDescription, +} from "../../../Projection/ProjectionDescription"; import { MemberPolicyMatches, MembershipPolicyRevision, @@ -50,6 +53,14 @@ type MemberBanIntentMap = PersistentMap< List >; +export type MemberBanIntentProjectionAccessMixin = { + allMembersWithRules(): MemberPolicyMatches[]; + isMemberBanned(member: StringUserID): boolean; + allRulesMatchingMember( + member: StringUserID + ): (LiteralPolicyRule | GlobPolicyRule)[]; +}; + function isPolicyRelevant(policy: LiteralPolicyRule | GlobPolicyRule): boolean { return ( policy.recommendation === Recommendation.Ban || @@ -57,17 +68,17 @@ function isPolicyRelevant(policy: LiteralPolicyRule | GlobPolicyRule): boolean { ); } -export type MemberBanIntentProjectionDescription = ProjectionDescription< - [MemberBanInputProjectionNode], - MemberBanIntentProjectionDelta, - { - allMembersWithRules(): MemberPolicyMatches[]; - isMemberBanned(member: StringUserID): boolean; - allRulesMatchingMember( - member: StringUserID - ): (LiteralPolicyRule | GlobPolicyRule)[]; - } ->; +export const MemberBanIntentProjectionDescription = describeProjection({ + name: "MemberBanIntentProjection", + partitionKeys: [], +}) + .withInputs<[MemberBanInputProjectionNode]>() + .withDownstreamDeltaShape() + .withAccessMixin() + .build(); + +export type MemberBanIntentProjectionDescription = + typeof MemberBanIntentProjectionDescription; export type MemberBanIntentProjectionNode = ProjectionNode; diff --git a/packages/matrix-protection-suite/src/Protection/StandardProtections/ServerBanSynchronisation/ServerBanIntentProjectionNode.ts b/packages/matrix-protection-suite/src/Protection/StandardProtections/ServerBanSynchronisation/ServerBanIntentProjectionNode.ts index 7e068be6..0579d322 100644 --- a/packages/matrix-protection-suite/src/Protection/StandardProtections/ServerBanSynchronisation/ServerBanIntentProjectionNode.ts +++ b/packages/matrix-protection-suite/src/Protection/StandardProtections/ServerBanSynchronisation/ServerBanIntentProjectionNode.ts @@ -8,7 +8,7 @@ // import { StringServerName } from "@the-draupnir-project/matrix-basic-types"; -import { ProjectionDescription } from "../../../Projection/ProjectionDescription"; +import { describeProjection } from "../../../Projection/ProjectionDescription"; import { ProjectionNode } from "../../../Projection/ProjectionNode"; import { PolicyListBridgeProjectionNode } from "./PolicyListBridgeProjection"; import { @@ -36,17 +36,25 @@ type ServerBanIntentMap = PersistentMap< List >; +export type ServerBanIntentProjectionAccessMixin = { + deny: StringServerName[]; + isServerDenied(serverName: StringServerName): boolean; +}; + // is there a way that we can adapt this so that it can possibly be swapped // to a lazy ban style protection if acls become exhausted. // not withiout addressing the issues in the member protection tbh. -export type ServerBanIntentProjectionDescription = ProjectionDescription< - [PolicyListBridgeProjectionNode], - ServerBanIntentProjectionDelta, - { - deny: StringServerName[]; - isServerDenied(serverName: StringServerName): boolean; - } ->; +export const ServerBanIntentProjectionDescription = describeProjection({ + name: "ServerBanIntentProjection", + partitionKeys: [], +}) + .withInputs<[PolicyListBridgeProjectionNode]>() + .withDownstreamDeltaShape() + .withAccessMixin() + .build(); + +export type ServerBanIntentProjectionDescription = + typeof ServerBanIntentProjectionDescription; export type ServerBanIntentProjectionNode = ProjectionNode;