docs(plugin-sdk): document sender authentication strength types

Adds a channel-ingress guide section for the two newly public SDK types
(IdentifierAuthentication, SubjectIdentifierAuthentication): the graded
scale, entry vs per-message claims, the min(entry, subject) gate, and the
dangerous/mutableIdentifierMatching compatibility mapping. Addresses the
ClawSweeper P2 on openclaw#117121.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Omar Shahine
2026-08-01 08:19:58 -07:00
parent 0ff888bb48
commit 2460e588bf
2 changed files with 69 additions and 0 deletions

View File

@@ -7666,6 +7666,8 @@ Do not edit it by hand; run `pnpm docs:map:gen`.
- Headings:
- H2: Runtime resolver
- H2: Result
- H2: Sender authentication strength
- H3: Compatibility
- H2: Access groups
- H2: Event modes
- H2: Routes and activation

View File

@@ -74,6 +74,73 @@ Deprecated third-party SDK helpers may rebuild older shapes internally. New
bundled receive paths should not translate modern results back into local
DTOs.
## Sender authentication strength
`IdentifierAuthentication` grades how strongly an identifier proves its holder,
as an ordered scale (strongest first):
| Value | Meaning |
| ------------ | --------------------------------------------------------------- |
| `verified` | a transport fact proves the holder for this identifier |
| `asserted` | a session-authenticated transport vouches for it (the default) |
| `unverified` | an alias two senders could hold at once, such as a display name |
| `mutable` | exact and stable, but this message did not prove its ownership |
Two independent claims feed the gate:
- **Entry side** (static): `ChannelIngressIdentityField.authentication`, how
strongly a field names its holder. Omitted means `asserted`; declare
`verified` only with a transport fact behind it.
- **Subject side** (per message): `subject.authentication`, a map keyed like
`subject.aliases` (with `stableId` under the primary field key) of what _this
message_ proved. Supply it only from a transport that authenticates messages
rather than sessions; omitting it weakens nothing. Resolved state exposes it
as `SubjectIdentifierAuthentication`.
The gate takes `min(entry, subject)` per identifier kind and compares it to
`policy.minIdentifierAuthentication` (default `asserted`). An unforgeable
identifier proves nothing when its message was never authenticated; an
authenticated message proves nothing when the identifier it carries is an alias.
```ts
const identity = defineStableChannelIngressIdentity({
key: "email",
normalize: normalizeEmail,
sensitivity: "pii",
authentication: "verified", // the address exactly names its holder…
});
const result = await resolveChannelMessageIngress({
// …but a given message only proves that when it arrives DKIM-aligned.
identity,
subject: {
stableId: fromAddress,
authentication: { email: dkimAligned ? "verified" : "unverified" },
},
policy: { dmPolicy, groupPolicy, minIdentifierAuthentication: "verified" },
// …
});
```
Silence caps an entry at `asserted`: a channel cannot satisfy a `verified`
minimum by declaring nothing, so reaching `verified` is always an explicit claim.
### Compatibility
The scale supersedes the two-level `dangerous` / `mutableIdentifierMatching`
boolean, which stay honored but deprecated:
| Legacy | Graded equivalent |
| -------------------------------------- | ------------------ |
| entry with no `dangerous` | `asserted` |
| `dangerous: true` | `mutable` |
| default policy | minimum `asserted` |
| `mutableIdentifierMatching: "enabled"` | minimum `mutable` |
`asserted` clears an `asserted` minimum and `mutable` does not, so every
existing admission and rejection is preserved. `subject.authentication` is
optional; absent reads the same as empty.
## Access groups
`accessGroup:<name>` entries stay redacted. Core resolves static