Skip to content

Commit 8286d8e

Browse files
MajorTalclaude
andcommitted
docs(i18n): document R402_LOCALE_NOT_CANONICAL deploy-time enforcement
The v1.55 gateway enforces RFC 5646 canonical casing on every locale tag in `spec.i18n` (primary lowercase, script Titlecase, 2-alpha region UPPERCASE, 3-digit region preserved, variants lowercase). Non-canonical casing is rejected at deploy time with HTTP 400 `R402_LOCALE_NOT_CANONICAL` carrying `fix: { input, canonical }` so agents auto-correct and retry. The platform refuses to silently canonicalize because translations are typically keyed on the literal locale string in the consumer's DB (`section_translations.language = 'pt-BR'`) - auto-fixing would create a silent split between the spec and column values. Updated both `cli/llms-cli.txt` and `SKILL.md` to replace the now-false "Tags are opaque - no BCP-47 semantic validation" claim with the actual canonical-casing rule + the error envelope shape. 120/120 SKILL tests + 32/32 sync tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 50cc713 commit 8286d8e

2 files changed

Lines changed: 7 additions & 2 deletions

File tree

SKILL.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ Carry-forward semantics: omit `i18n` to carry forward from base release; pass `"
262262

263263
Locale-tag rules (strict, no canonicalization):
264264
- `defaultLocale` MUST be byte-identical to one entry in `locales[]`. The gateway does NOT silently canonicalize; the SDK validates this client-side before planning.
265-
- Each tag MUST match `/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/`. Tags are opaque — there is no BCP-47 semantic validation.
265+
- Each tag MUST match `/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/` AND be in RFC 5646 canonical casing: primary subtag lowercase, script subtag Titlecase, 2-alpha region UPPERCASE, 3-digit (UN M.49) region preserved, variants/extensions lowercase. Examples: `pt-BR`, `zh-Hant`, `zh-Hant-TW`, `de-1996`. Non-canonical casing is rejected at deploy time with `code: "R402_LOCALE_NOT_CANONICAL"` (HTTP 400) carrying `fix: { input, canonical }` so agents can auto-correct and retry. The platform refuses to silently canonicalize because translations are typically keyed on the literal locale string in your DB (`section_translations.language = 'pt-BR'`) — auto-fixing would split the spec from your column values.
266266
- `locales[]` is non-empty, max 50 entries.
267267
- Negotiation returns canonical casing from `locales[]`, NOT the request's casing.
268268

@@ -568,6 +568,11 @@ For agents that need to sign Ethereum transactions. Private keys never leave AWS
568568
- **`send_message`** — send feedback to the Run402 team.
569569
- **`set_agent_contact`** / **`get_agent_contact_status`** / **`verify_agent_contact_email`** — register agent contact info, read assurance status, and start the operator email reply challenge.
570570
- **`start_operator_passkey_enrollment`** — email a Run402 operator passkey enrollment link to the verified contact email.
571+
- **`get_operator_status`** — compact operator-health snapshot (contact assurance, critical items, skipped notifications, accounts, projects, active thresholds). Consumed by `run402 doctor`.
572+
- **`get_notification_preferences`** / **`set_notification_preferences`** — read/update operator notification preferences (cadence, channels, per-class toggles, locale, timezone). Cross-wallet effects need `email_verified`; webhook URL changes need `operator_passkey`.
573+
- **`list_notifications`** — per-delivery-attempt audit log. Paginated; filter by event_type / since.
574+
- **`test_notification`** — fire a real test through the full pipeline. Audit row marked `is_test=true`. Rate-limited per wallet at 1/min.
575+
- **`rotate_webhook_secret`** — new HMAC signing secret for the operator webhook (returned once). Previous remains valid 24h. Requires `operator_passkey`.
571576

572577
### Service status (no auth, no setup)
573578

cli/llms-cli.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ Runtime route failure codes to branch on: `ROUTE_MANIFEST_LOAD_FAILED` (manifest
344344

345345
- Omit `i18n` to carry forward from the base release; pass `"i18n": null` to clear the slice on the new release; pass `{ defaultLocale, locales, detect? }` to replace.
346346
- `defaultLocale` MUST be byte-identical to one entry in `locales[]`. The platform does NOT silently canonicalize; the CLI/SDK validate this client-side before planning.
347-
- Locale tags MUST match `/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/`. Tags are opaque — no BCP-47 semantic validation. `locales[]` is non-empty, max 50 entries.
347+
- Locale tags MUST match `/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/` AND be in RFC 5646 canonical casing: primary subtag lowercase, script subtag Titlecase, 2-alpha region UPPERCASE, 3-digit (UN M.49) region preserved, variants/extensions lowercase. Examples: `pt-BR`, `zh-Hant`, `zh-Hant-TW`, `de-1996`. The gateway rejects non-canonical casing at deploy time with `code: "R402_LOCALE_NOT_CANONICAL"` (HTTP 400) carrying `fix: { input, canonical }` so agents can auto-correct and retry. The platform does NOT silently canonicalize because translations are typically keyed on the literal locale string in your DB (e.g. `section_translations.language = 'pt-BR'`); auto-canonicalization would create a silent split between the spec and your column values. `locales[]` is non-empty, max 50 entries.
348348
- Negotiation returns canonical casing from `locales[]`, NOT the request's casing.
349349
- `detect[]` (default `["accept-language"]`, max 10, `[]` allowed and means "always default") is walked in order; first match wins. Sources: `"accept-language"` (RFC 9110 parsing, RFC 4647 §3.4 lookup-style truncation `zh-Hant-TW` → `zh-Hant` → `zh`; a generic request tag does NOT match a more specific `locales[]` entry — `Accept-Language: es` does NOT match `locales: ["es-MX"]`) and `"cookie:<name>"` (RFC 6265 cookie-name grammar `/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/`, raw value matched case-insensitively against `locales[]`).
350350
- Static-route hits do NOT receive locale negotiation; only routed HTTP function invocations do.

0 commit comments

Comments
 (0)