Skip to content

Commit d28dc73

Browse files
authored
docs(spec): define verification code lifetime per method (#55)
Adds section 9.5. Core computes expires_at from the method and the tenant's configured TTL, within bounds core enforces. Defaults are 10 minutes for phone OTP, 24 hours for domain email, and 21 days for postcard, which was previously the only stated value. Closes #38 Signed-off-by: Matthew Wren <info@origindev.com>
1 parent a5719d2 commit d28dc73

1 file changed

Lines changed: 36 additions & 2 deletions

File tree

docs/spec/events-and-adapters.md

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,7 @@ A code is printed on a postcard and mailed to the listed address. Strong evidenc
915915

916916
Implemented as a `postal.send` capability adapter. Print-and-mail APIs exist as a commercial service category; verify current pricing and availability independently. Expect roughly one to two dollars per piece and several days of delivery time.
917917

918-
- Code is 6 digits, valid **21 days**, single use.
918+
- Code is 6 digits, single use. Lifetime is configurable per tenant within the bounds in §9.5; the default is 21 days.
919919
- One postcard per listing per 30 days, rate-limited so an attacker cannot generate mail volume at the operator's expense.
920920
- Emits `claim.verification_started` with `method: "postcard"`, then `postal.dispatched` on adapter confirmation.
921921
- **Cost falls on the operator**, so the admin UI must show per-piece cost at the point of enabling it.
@@ -950,6 +950,40 @@ A competitor or ex-employee claiming a listing takes control of that business's
950950

951951
**Disputes go to moderation, never auto-transfer.** A second claim on an already-claimed listing emits `claim.disputed` and opens a `moderation.queued` item. Verification alone never moves ownership away from a sitting owner. Rate-limit claim attempts per IP and per account.
952952

953+
### 9.5 Verification code lifetime
954+
955+
**Core computes `expires_at`, never the caller.** A lifetime is a rule, and rules belong to core. `claim.verification_started` carries the computed value; an adapter that needs to tell a user when their code dies reads it from the event.
956+
957+
Lifetime is tenant-configurable within bounds core enforces. A tenant may tune it to its own audience; it may not configure a lifetime that makes the method meaningless.
958+
959+
| Method | Default | Minimum | Maximum |
960+
|---|---|---|---|
961+
| `phone_otp` | 10 minutes | 5 minutes | 60 minutes |
962+
| `domain_email` | 24 hours | 15 minutes | 48 hours |
963+
| `postcard` | 21 days | 7 days | 45 days |
964+
| `gbp_oauth` ||||
965+
| `manual` ||||
966+
967+
`gbp_oauth` has no OSDS-side code; Google owns that session. `manual` has no code at all — an admin decides when they have seen enough.
968+
969+
```jsonc
970+
// tenant configuration
971+
"claim_verification": {
972+
"enabled_methods": ["manual", "phone_otp"],
973+
"ttl": {
974+
"phone_otp_minutes": 10,
975+
"domain_email_minutes": 1440,
976+
"postcard_days": 21
977+
}
978+
}
979+
```
980+
981+
A value outside the bounds is rejected at configuration time, not silently clamped at use. The admin UI shows the bounds at the point of entry, and says why the ceiling exists — a code that outlives the session it was sent for is a code someone else can find later.
982+
983+
**Bounds are core's, not the tenant's.** They exist because a 48-hour SMS code and a 10-minute one are different security properties, and an operator tuning a form field is not making a security decision knowingly.
984+
985+
Attempt limits are a separate concern and are not specified here — see §15.6. `claim.verification_failed` carries `attempt`, which currently has no defined ceiling.
986+
953987
---
954988

955989
## 10. Reference adapter: `gohighlevel`
@@ -1062,4 +1096,4 @@ minio (bundled S3-compatible storage; overridable via S3_* vars)
10621096
3. **Data model and migrations** — entitlement and slot tables are specified behaviourally, not yet as schema.
10631097
4. **Owner dashboard scope** — what an owner can edit without re-verification, and what re-opens moderation.
10641098
5. **Import pipeline detail** — CSV column mapping, dedupe strategy against `suppression_key`, batch rollback mechanics.
1065-
6. **Rate limiting and abuse** — public API limits, claim attempt limits, review submission limits.
1099+
6. **Rate limiting and abuse** — public API limits, claim attempt limits, review submission limits. Includes a ceiling for the attempt counter on claim.verification_failed (§9.5).

0 commit comments

Comments
 (0)