From 614fc8936d94eb835e8b409e8ea991916e872daa Mon Sep 17 00:00:00 2001 From: gnuxie Date: Mon, 20 Apr 2026 13:09:24 +0100 Subject: [PATCH] Make the node state delta authoritative in existing projections. https://github.com/the-draupnir-project/planning/issues/123 --- .../MemberBanIntentProjectionNode.ts | 52 ++++++++++++------- .../ServerBanIntentProjectionNode.ts | 42 +++++++++------ 2 files changed, 60 insertions(+), 34 deletions(-) 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 0218daf5..e5bf2e62 100644 --- a/packages/matrix-protection-suite/src/Protection/StandardProtections/MemberBanSynchronisation/MemberBanIntentProjectionNode.ts +++ b/packages/matrix-protection-suite/src/Protection/StandardProtections/MemberBanSynchronisation/MemberBanIntentProjectionNode.ts @@ -39,14 +39,17 @@ export type MemberBanInputProjectionNode = ProjectionNode< > & MembershipPolicyRevision; +export interface MemberBanIntentProjectionDelta { + ban: StringUserID[]; + recall: StringUserID[]; +} + // use add/remove for steady state intents // When the intent becomes effectual, matches will be removed // upstream and so this model will remain consistent -export interface MemberBanIntentProjectionDelta { +export interface MemberBanIntentProjectionStateDelta { add: MemberPolicyMatch[]; remove: MemberPolicyMatch[]; - ban: StringUserID[]; - recall: StringUserID[]; } function isPolicyRelevant(policy: LiteralPolicyRule | GlobPolicyRule): boolean { @@ -59,7 +62,7 @@ function isPolicyRelevant(policy: LiteralPolicyRule | GlobPolicyRule): boolean { export type MemberBanIntentProjectionNode = ProjectionNode< [MemberBanInputProjectionNode], MemberBanIntentProjectionDelta, - undefined, + MemberBanIntentProjectionStateDelta, { allMembersWithRules(): MemberPolicyMatches[]; allRulesMatchingMember( @@ -71,8 +74,8 @@ export type MemberBanIntentProjectionNode = ProjectionNode< export const MemberBanIntentProjectionNodeHelper = Object.freeze({ reduceMembershipPolicyDelta( input: MembershipPolicyRevisionDelta - ): Pick { - const output: Pick = { + ): MemberBanIntentProjectionStateDelta { + const output: MemberBanIntentProjectionStateDelta = { add: [], remove: [], }; @@ -89,7 +92,7 @@ export const MemberBanIntentProjectionNodeHelper = Object.freeze({ return output; }, reduceIntentDelta( - input: Pick, + input: MemberBanIntentProjectionStateDelta, policies: PersistentMap< StringUserID, List @@ -102,7 +105,6 @@ export const MemberBanIntentProjectionNodeHelper = Object.freeze({ (rule) => rule.entity as StringUserID ); return { - ...input, ban: intents.intend, recall: intents.recall, }; @@ -138,23 +140,28 @@ export class StandardMemberBanIntentProjectionNode implements MemberBanIntentPro reduceInput( input: ExtractInputDeltaShapes<[MemberBanInputProjectionNode]> - ): ProjectionNodeDelta { + ): ProjectionNodeDelta< + MemberBanIntentProjectionDelta, + MemberBanIntentProjectionStateDelta + > { + const nodeStateDelta = + MemberBanIntentProjectionNodeHelper.reduceMembershipPolicyDelta(input); return { downstreamDelta: MemberBanIntentProjectionNodeHelper.reduceIntentDelta( - MemberBanIntentProjectionNodeHelper.reduceMembershipPolicyDelta(input), + nodeStateDelta, this.intents ), - nodeStateDelta: undefined, + nodeStateDelta, }; } reduceDelta( projectionNodeDelta: ProjectionNodeDelta< MemberBanIntentProjectionDelta, - undefined + MemberBanIntentProjectionStateDelta > ): MemberBanIntentProjectionNode { - const input = projectionNodeDelta.downstreamDelta; + const input = projectionNodeDelta.nodeStateDelta; let nextIntents = this.intents; nextIntents = ListMultiMap.addValues( nextIntents, @@ -174,7 +181,10 @@ export class StandardMemberBanIntentProjectionNode implements MemberBanIntentPro reduceInitialInputs([membershipPolicyRevision]: [ MemberBanInputProjectionNode, - ]): ProjectionNodeDelta { + ]): ProjectionNodeDelta< + MemberBanIntentProjectionDelta, + MemberBanIntentProjectionStateDelta + > { if (!this.isEmpty()) { throw new TypeError( "This can only be called on an empty projection node" @@ -183,17 +193,21 @@ export class StandardMemberBanIntentProjectionNode implements MemberBanIntentPro const matches = membershipPolicyRevision .allMembersWithRules() .map((member) => - member.policies.map((policy) => ({ userID: member.userID, policy })) + member.policies + .filter(isPolicyRelevant) + .map((policy) => ({ userID: member.userID, policy })) ) .flat(); + const nodeStateDelta = { + add: matches, + remove: [], + }; return { downstreamDelta: { - add: matches, - ban: matches.map((match) => match.userID), - remove: [], + ban: [...new Set(matches.map((match) => match.userID))], recall: [], }, - nodeStateDelta: undefined, + nodeStateDelta, }; } 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 a0098a40..5c6ebb08 100644 --- a/packages/matrix-protection-suite/src/Protection/StandardProtections/ServerBanSynchronisation/ServerBanIntentProjectionNode.ts +++ b/packages/matrix-protection-suite/src/Protection/StandardProtections/ServerBanSynchronisation/ServerBanIntentProjectionNode.ts @@ -31,6 +31,9 @@ import { ListMultiMap } from "../../../Projection/ListMultiMap"; export type ServerBanIntentProjectionDelta = { deny: StringServerName[]; recall: StringServerName[]; +}; + +export type ServerBanIntentProjectionStateDelta = { add: (LiteralPolicyRule | GlobPolicyRule)[]; remove: (LiteralPolicyRule | GlobPolicyRule)[]; }; @@ -41,7 +44,7 @@ export type ServerBanIntentProjectionDelta = { export type ServerBanIntentProjectionNode = ProjectionNode< [PolicyListBridgeProjectionNode], ServerBanIntentProjectionDelta, - undefined, + ServerBanIntentProjectionStateDelta, { deny: StringServerName[]; } @@ -50,11 +53,11 @@ export type ServerBanIntentProjectionNode = ProjectionNode< export const ServerBanIntentProjectionHelper = Object.freeze({ reducePolicyDelta( input: PolicyRuleChange[] - ): Pick { - const output: Pick = { + ): ServerBanIntentProjectionStateDelta { + const output: ServerBanIntentProjectionStateDelta = { add: [], remove: [], - } satisfies Pick; + }; for (const change of input) { if (change.rule.kind !== PolicyRuleType.Server) { continue; @@ -85,7 +88,7 @@ export const ServerBanIntentProjectionHelper = Object.freeze({ }, reduceIntentDelta( - input: Pick, + input: ServerBanIntentProjectionStateDelta, policies: PersistentMap< StringServerName, List @@ -98,7 +101,6 @@ export const ServerBanIntentProjectionHelper = Object.freeze({ (rule) => rule.entity as StringServerName ); return { - ...input, deny: intents.intend, recall: intents.recall, }; @@ -128,19 +130,27 @@ export class StandardServerBanIntentProjectionNode implements ServerBanIntentPro reduceInput( input: PolicyRuleChange[] - ): ProjectionNodeDelta { + ): ProjectionNodeDelta< + ServerBanIntentProjectionDelta, + ServerBanIntentProjectionStateDelta + > { + const nodeStateDelta = + ServerBanIntentProjectionHelper.reducePolicyDelta(input); return { downstreamDelta: ServerBanIntentProjectionHelper.reduceIntentDelta( - ServerBanIntentProjectionHelper.reducePolicyDelta(input), + nodeStateDelta, this.policies ), - nodeStateDelta: undefined, + nodeStateDelta, }; } reduceInitialInputs([policyListRevision]: [ PolicyListBridgeProjectionNode, - ]): ProjectionNodeDelta { + ]): ProjectionNodeDelta< + ServerBanIntentProjectionDelta, + ServerBanIntentProjectionStateDelta + > { if (!this.isEmpty()) { throw new TypeError("Cannot reduce initial inputs when inialised"); } @@ -156,14 +166,16 @@ export class StandardServerBanIntentProjectionNode implements ServerBanIntentPro ].filter((rule) => rule.matchType !== PolicyRuleMatchType.HashedLiteral); const names = new Set(serverPolicies.map((policy) => policy.entity)); const downstreamDelta = { - add: serverPolicies, deny: [...names] as StringServerName[], - remove: [], recall: [], }; + const nodeStateDelta = { + add: serverPolicies, + remove: [], + }; return { downstreamDelta, - nodeStateDelta: undefined, + nodeStateDelta, }; } @@ -172,10 +184,10 @@ export class StandardServerBanIntentProjectionNode implements ServerBanIntentPro } reduceDelta({ - downstreamDelta: input, + nodeStateDelta: input, }: ProjectionNodeDelta< ServerBanIntentProjectionDelta, - undefined + ServerBanIntentProjectionStateDelta >): ServerBanIntentProjectionNode { let nextPolicies = this.policies; nextPolicies = ListMultiMap.addValues(