docs(poll): document governed participation boundary
This commit is contained in:
66
README.md
66
README.md
@@ -50,6 +50,72 @@ and authenticated API routes for:
|
||||
- signed participation links for reduced/no-Access participation
|
||||
- context and workflow metadata for modules such as Scheduling
|
||||
|
||||
## Governed signed participation
|
||||
|
||||
The v0.1.10 contract lets a consuming module bind an invitation to an exact
|
||||
response gateway (`module_id`, `resource_type`, and `resource_id`) and snapshot
|
||||
generic participation rules onto it. A bound invitation cannot use the legacy
|
||||
`GET /poll/public/{token}` or `POST /poll/public/{token}/responses` routes: both
|
||||
return the same generic not-found response used for invalid, expired, and
|
||||
revoked links. There is deliberately no browser-facing Poll bypass when the
|
||||
owning gateway is missing.
|
||||
|
||||
That boundary is Poll-wide. Setting `context_module` declares a module-owned
|
||||
Poll; for a standalone Poll, its first governed invitation opts the whole Poll
|
||||
into governed participation. From then on, direct authenticated response
|
||||
writes, ordinary invitation creation, and every legacy public link fail closed,
|
||||
including links created before governance was enabled. Governed invitations can
|
||||
only be created through the in-process participation capability and their
|
||||
gateway must match the Poll's declared module resource (or the standalone
|
||||
Poll's first gateway). Polls that never opt into governance retain their
|
||||
ordinary authenticated and signed-link behavior.
|
||||
|
||||
Module ownership also protects management boundaries. Generic Poll update,
|
||||
lifecycle, option, and invitation-revocation paths cannot mutate an owned Poll,
|
||||
and generic raw-response and invitation projections cannot bypass the owning
|
||||
module's privacy policy. The in-process capability supplies the exact module,
|
||||
resource type, and resource id again for every mutation; Poll compares that
|
||||
owner while holding its row lock. Aggregate result summaries remain reusable.
|
||||
|
||||
Consumers resolve and submit bound invitations through the
|
||||
`poll.participation_gateway` in-process capability. The policy covers:
|
||||
|
||||
- at most one non-`unavailable` selection
|
||||
- whether `maybe` is accepted
|
||||
- a maximum number of definitive participants per option
|
||||
- response comments (trimmed and limited to 4,000 characters)
|
||||
- email for anonymous participants
|
||||
- an anonymous-password verification requirement
|
||||
|
||||
For availability polls, `available` reserves capacity while `maybe` does not.
|
||||
Poll re-enforces these rules under its Poll-row lock, so concurrent final-place
|
||||
claims serialize on PostgreSQL. A gateway-owned password never crosses the
|
||||
capability boundary: the owning module stores the sole salted verifier,
|
||||
throttles attempts before checking it, and supplies only the
|
||||
`anonymous_password` verification attestation. Poll rejects secret-like
|
||||
invitation/response metadata and stores no password column.
|
||||
|
||||
Authenticated module flows do not need to retain a public bearer token. They
|
||||
can resolve and submit by exact tenant, Poll, governed invitation, gateway, and
|
||||
respondent identifiers. Poll rejects mismatched identities and applies the same
|
||||
Poll-row lock and policy checks used by the public-token path. A consumer can
|
||||
lazily create that governed invitation, retain only its id, and discard the raw
|
||||
token when no public link is needed.
|
||||
|
||||
An owning gateway can update a non-revoked invitation's expiry in place. A past
|
||||
timestamp expires the existing link immediately; a later future timestamp or
|
||||
`null` reactivates that same link without exposing or rotating its bearer token.
|
||||
Revoked invitations remain revoked, and exact retries are idempotent.
|
||||
|
||||
The capability also supports durable invitation-scoped idempotency keys,
|
||||
response prefill, option addition/removal, and invitation revocation. Exact
|
||||
submission retries return the existing response; key reuse with different
|
||||
content is rejected. Because a response is an editable resource, replay returns
|
||||
its current representation rather than an immutable snapshot of the first
|
||||
submission. Option removal and option changes invalidate only answers bound to
|
||||
that option, and repeated removal/revocation is an idempotent replay even after
|
||||
the Poll has moved out of an editable lifecycle state.
|
||||
|
||||
## Scheduling As A Poll-Backed Workflow
|
||||
|
||||
Scheduling should use Poll as the reusable response collection primitive, not
|
||||
|
||||
Reference in New Issue
Block a user