Proposed. It finishes
0368
(decisions 3, 5 and 6). Linear STA-717.
Context
ADR 0368 left verification as a service method, a QR code with no code a person can type, a grant check that two concurrent grants for one period could both pass, and a certificate as fields only. Academorix prints certificates that parents and clubs check from paper.Decision
- A public verify query.
VerifyAwardQuery(achievements.award.verify@1,GET /awards/verify?token=or?code=) is in the public API area, open to anonymous callers and not an MCP tool. It answersvalidwith the award (recipient by model key and public id only) ornot_found; a revoked award is indistinguishable from an unknown one, because its index row is deleted on revoke. - A short typed code. Every new award gets 12 random Crockford base32 characters (60 bits), printed as
7K3Q-9XPM-0ABZbeside the QR code. The award keeps the code for a reprint; the unscoped index keeps only the SHA-256 of its canonical form, unique. A typed code is folded first (case, spaces, hyphens, O as 0, I and L as 1). The public area’s rate limit bounds guessing. - One live award per kind, recipient and period. A partial unique index on live awards with a period backs
checkGrant; the loser of two concurrent grants is refusedaward_not_grantablewithperiod_taken. The index is built online; live duplicates must be revoked before it builds. - Certificates through documents.
AwardService.renderCertificatehands the kind’s published print template (certificateTemplate, loaded by the consumer) and the certificate fields torenderDocumentof@stackra/documents; the template’sqrblock encodes{{award.verifyUrl}}and its text may print{{award.verifyCode}}. No layout is code. - Prefixes.
awk,awdandawvbelong toachievements.award_kind,achievements.awardandachievements.award_verificationin gen’s prefix registry.
Consequences
- A certificate can be checked by scanning or by typing; neither leaks who else holds an award.
- Concurrent automatic grants are safe without a lock.
- The package depends on
@stackra/documents.
Enforcement
verify-award.query.test.ts, award.service.test.ts, normalize-award-code.function.test.ts and
0002-add-award-codes-and-unique-grants.migration.test.ts in packages/application/achievements. Planned — the
prefix rows in packages/developer/gen/src/core/constants/public-id-prefixes.constant.ts (gen refuses a taken prefix).