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
|
- signed participation links for reduced/no-Access participation
|
||||||
- context and workflow metadata for modules such as Scheduling
|
- 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 As A Poll-Backed Workflow
|
||||||
|
|
||||||
Scheduling should use Poll as the reusable response collection primitive, not
|
Scheduling should use Poll as the reusable response collection primitive, not
|
||||||
|
|||||||
Reference in New Issue
Block a user