docs: document channel approval ingress contracts

This commit is contained in:
Peter Steinberger
2026-06-04 22:38:03 -04:00
parent 506c2ee181
commit 86872e0880
2 changed files with 29 additions and 0 deletions

View File

@@ -74,6 +74,7 @@ type ApprovalForwardingTargetMatcher =
type ApprovalOriginOrSessionTargetChecker =
ChannelApprovalForwardingEvaluatorParams["hasOriginOrSessionTarget"];
/** Inputs for checking whether one approval request can use session-native forwarding. */
export type ChannelApprovalForwardingEligibilityParams = {
/** Full config containing exec/plugin approval forwarding settings. */
cfg: OpenClawConfig;
@@ -85,6 +86,7 @@ export type ChannelApprovalForwardingEligibilityParams = {
request: ApprovalRequest;
};
/** Inputs for checking whether approval forwarding is configured for a channel route. */
export type ChannelApprovalPotentialRouteParams = {
/** Full config containing exec/plugin approval forwarding settings. */
cfg: OpenClawConfig;
@@ -96,6 +98,7 @@ export type ChannelApprovalPotentialRouteParams = {
nativeSessionOnly?: boolean;
};
/** Inputs for checking whether one configured target can receive an approval request. */
export type ChannelApprovalExplicitTargetEligibilityParams =
ChannelApprovalForwardingEligibilityParams & {
/** Forwarding target that may be handled by the channel-native approval route. */
@@ -233,6 +236,7 @@ type CustomOriginResolverParams<TTarget> = BaseOriginResolverParams<TTarget> & {
targetsMatch: (a: TTarget, b: TTarget) => boolean;
};
/** Standard channel-native approval destination used by route and origin matchers. */
export type NativeApprovalTarget = {
/** Channel-local destination id. */
to: string;
@@ -242,6 +246,7 @@ export type NativeApprovalTarget = {
threadId?: string | number | null;
};
/** Compare channel-native approval targets with the same normalization used by outbound routes. */
export function nativeApprovalTargetsMatch(params: {
/** Channel id used for route target normalization. */
channel?: string | null;
@@ -266,6 +271,7 @@ export function nativeApprovalTargetsMatch(params: {
});
}
/** Decide whether a channel-native exec approval route replaces the local text prompt. */
export function shouldSuppressLocalNativeExecApprovalPrompt(params: {
/** Full config containing top-level or channel-specific approval settings. */
cfg: OpenClawConfig;
@@ -369,6 +375,7 @@ function nativeApprovalTargetMatcher(channel: string): (left: unknown, right: un
nativeApprovalTargetsMatch({ channel, left, right });
}
/** Infer approval family from the request shape unless the caller already knows it. */
export function resolveApprovalKind(
request: ApprovalRequest,
approvalKind?: ApprovalKind,
@@ -520,6 +527,7 @@ function isExplicitTargetApprovalEligibleViaForwarding(
});
}
/** Build reusable forwarding gates for channels with custom target matching logic. */
export function createChannelApprovalForwardingEvaluator(
params: ChannelApprovalForwardingEvaluatorParams,
) {
@@ -597,6 +605,7 @@ function normalizeApprovalForwardingModeWithDefault(params: {
return params.config.mode ?? params.defaultForwardingMode;
}
/** Create the standard route gates for native channel approval forwarding. */
export function createNativeApprovalChannelRouteGates<TTarget extends NativeApprovalTarget>(
params: NativeApprovalChannelRouteGateParams<TTarget>,
): NativeApprovalChannelRouteGates {
@@ -638,6 +647,8 @@ export function createNativeApprovalChannelRouteGates<TTarget extends NativeAppr
}),
)
.map((candidateAccountId) => normalizeAccountId(candidateAccountId));
// Unscoped targets are safe for a non-default account only when exactly
// one enabled account can receive them; otherwise they would be ambiguous.
return enabledAccountIds.length === 1 && enabledAccountIds[0] === normalizedAccountId;
};
@@ -779,6 +790,7 @@ function normalizeOptionalAccountId(value?: string | null): string | undefined {
return value?.trim() || undefined;
}
/** Create a fallback suppressor that avoids duplicate approval prompts after native delivery. */
export function createNativeApprovalForwardingFallbackSuppressor<
TTarget extends NativeApprovalTarget,
>(
@@ -909,9 +921,11 @@ function hasCustomTargetsMatch<TTarget>(
return typeof params.targetsMatch === "function";
}
/** Resolve a request origin target using standard native approval target matching. */
export function createChannelNativeOriginTargetResolver<TTarget extends NativeApprovalTarget>(
params: NativeOriginResolverParams<TTarget>,
): (input: ApprovalResolverParams) => TTarget | null;
/** Resolve a request origin target for channels with custom target shapes. */
export function createChannelNativeOriginTargetResolver<TTarget>(
params: CustomOriginResolverParams<TTarget>,
): (input: ApprovalResolverParams) => TTarget | null;
@@ -927,6 +941,7 @@ export function createChannelNativeOriginTargetResolver<TTarget>(
});
}
/** Create a resolver for configured approver DM targets. */
export function createChannelApproverDmTargetResolver<
TApprover,
TTarget extends NativeApprovalTarget = NativeApprovalTarget,

View File

@@ -56,15 +56,22 @@ export type {
RouteSenderPolicy,
} from "../channels/message-access/index.js";
/** Redacted identifier material that can be matched against channel allowlist entries. */
export type ChannelIngressSubjectIdentifier = InternalMatchMaterial;
/** Inbound actor identity described by one or more channel-specific identifiers. */
export type ChannelIngressSubject = InternalChannelIngressSubject;
/** Normalized allowlist entry produced by a channel ingress adapter. */
export type ChannelIngressAdapterEntry = InternalNormalizedEntry;
/** Adapter normalization output split into matchable, invalid, and disabled entries. */
export type ChannelIngressAdapterNormalizeResult = InternalChannelIngressNormalizeResult;
/** Channel-specific allowlist normalizer and subject matcher used by ingress policy. */
export type ChannelIngressAdapter = InternalChannelIngressAdapter;
/** SDK-facing input shape for resolving redacted channel ingress state. */
export type ChannelIngressStateInput = MessageAccessChannelIngressStateInput;
declare const CHANNEL_INGRESS_PLUGIN_ID: unique symbol;
/** Branded plugin id used in stable ingress diagnostics and generated gate identifiers. */
export type ChannelIngressPluginId = string & {
readonly [CHANNEL_INGRESS_PLUGIN_ID]: true;
};
@@ -93,6 +100,7 @@ export type ChannelIngressSideEffectResult =
| { kind: "pending-history-recorded" }
| { kind: "local-event-handled" };
/** Minimal redacted decision summary suitable for logs and plugin diagnostics. */
export type RedactedIngressDiagnostics = {
decisiveGateId?: string;
reasonCode: IngressReasonCode;
@@ -107,6 +115,7 @@ export const CHANNEL_INGRESS_GATE_SELECTORS = {
event: { phase: "event", kind: "event" },
} as const satisfies Record<string, ChannelIngressGateSelector>;
/** Input descriptor for a single channel subject identifier before redacted normalization. */
export type ChannelIngressSubjectIdentifierInput = {
value: string;
opaqueId?: string;
@@ -115,6 +124,7 @@ export type ChannelIngressSubjectIdentifierInput = {
sensitivity?: "normal" | "pii";
};
/** Options for the common one-string-id allowlist adapter. */
export type CreateChannelIngressStringAdapterParams = {
kind?: ChannelIngressIdentifierKind;
normalizeEntry?: (value: string) => string | null | undefined;
@@ -125,6 +135,7 @@ export type CreateChannelIngressStringAdapterParams = {
sensitivity?: "normal" | "pii";
};
/** Options for adapters that expand each allowlist entry into multiple identifier records. */
export type CreateChannelIngressMultiIdentifierAdapterParams = {
normalizeEntry: (entry: string, index: number) => readonly ChannelIngressAdapterEntry[];
getEntryMatchKey?: (entry: ChannelIngressAdapterEntry) => string | null | undefined;
@@ -134,12 +145,14 @@ export type CreateChannelIngressMultiIdentifierAdapterParams = {
isWildcardEntry?: (entry: ChannelIngressAdapterEntry) => boolean;
};
/** Legacy DM/group access projection retained for older channel runtime callers. */
export type ChannelIngressDmGroupAccessProjection = {
decision: DmGroupAccessDecision;
reasonCode: DmGroupAccessReasonCode;
reason: string;
};
/** Sender-only group access projection used when command and sender gates are evaluated separately. */
export type ChannelIngressSenderGroupAccessProjection = {
allowed: boolean;
groupPolicy: ChannelIngressPolicyInput["groupPolicy"];
@@ -325,6 +338,7 @@ export function projectIngressAccessFacts(decision: ChannelIngressDecision): Acc
};
}
/** Convert an ingress graph decision plus any local side effect into channel turn admission. */
export function mapChannelIngressDecisionToTurnAdmission(
decision: ChannelIngressDecision,
sideEffect: ChannelIngressSideEffectResult,