Admin add-User field errors, voice job label, early invalid-token check #1553

Closed
opened 2026-10-10 16:28:11 +00:00 by kayg · 13 comments
Owner

Three small server-side fixes found in the 2026-10-10 UI refinement round:

  1. POST /api/v1/auth/users (Admin → Users → add) returns 400 invalid_request for a bad username, a bad display name and the Owner role alike, with no field name. Return a field identifier (e.g. {"error":"invalid_request","field":"username"|"display_name"|"role"}) so the UI can say which one is wrong. Display-name length is checked in bytes (128) while the web input allows 160 characters: count characters (Unicode scalar values or grapheme clusters, pick one and document it) and make the server and input limits match.
  2. The background job notes.voice.models shows its raw kind id as its label in Settings → Jobs. Give it the plain label "Preparing Voice Memos" at its registration (crates/plugins/notes/src/voice.rs), the same way other jobs got plain labels in #1538.
  3. /setup?token=<invalid> and an invalid or expired invite link show the full form, and the error appears only after submit. Check the token when the page loads (a cheap read-only validity check endpoint, or the existing one) and show the error state immediately. Must not leak whether an account exists; must stay rate-limited like the existing token checks.
Three small server-side fixes found in the 2026-10-10 UI refinement round: 1. `POST /api/v1/auth/users` (Admin → Users → add) returns 400 `invalid_request` for a bad username, a bad display name and the Owner role alike, with no field name. Return a field identifier (e.g. `{"error":"invalid_request","field":"username"|"display_name"|"role"}`) so the UI can say which one is wrong. Display-name length is checked in bytes (128) while the web input allows 160 characters: count characters (Unicode scalar values or grapheme clusters, pick one and document it) and make the server and input limits match. 2. The background job `notes.voice.models` shows its raw kind id as its label in Settings → Jobs. Give it the plain label "Preparing Voice Memos" at its registration (`crates/plugins/notes/src/voice.rs`), the same way other jobs got plain labels in #1538. 3. `/setup?token=<invalid>` and an invalid or expired invite link show the full form, and the error appears only after submit. Check the token when the page loads (a cheap read-only validity check endpoint, or the existing one) and show the error state immediately. Must not leak whether an account exists; must stay rate-limited like the existing token checks.
Author
Owner

Starting #1553 on job/autherr-1553. Base SHA: ac7fda1a8c (origin/dev).

Starting #1553 on job/autherr-1553. Base SHA: ac7fda1a8c49e902e1b246bea9d167d64e92d58f (origin/dev).
Author
Owner

Finding for #1553: valid_display_name currently compares UTF-8 byte length to 128, while Admin → Users uses maxlength="160"; the Admin create path also returns the same InvalidRequest for malformed fields and the Owner role. VoiceModelDownloadJob has no display name, so Settings → Jobs falls back to notes.voice.models. Setup and invite stores already expose read-only redeemability checks, but the UI reaches them only through the submit-time start routes.
Decision: count Unicode scalar values and cap display names at 160. For early checks, return only { "valid": true|false }; invalid, expired, and used grants share false. Apply the existing per-IP limits and a 10-attempt per-token limit. The start routes will still recheck the grant before creating or consuming anything.

Finding for #1553: `valid_display_name` currently compares UTF-8 byte length to 128, while Admin → Users uses `maxlength="160"`; the Admin create path also returns the same `InvalidRequest` for malformed fields and the Owner role. `VoiceModelDownloadJob` has no display name, so Settings → Jobs falls back to `notes.voice.models`. Setup and invite stores already expose read-only redeemability checks, but the UI reaches them only through the submit-time start routes. Decision: count Unicode scalar values and cap display names at 160. For early checks, return only `{ "valid": true|false }`; invalid, expired, and used grants share `false`. Apply the existing per-IP limits and a 10-attempt per-token limit. The start routes will still recheck the grant before creating or consuming anything.
Author
Owner

Finding during the required web check: scripts/perf-lint rejected the existing cache.raw-read exception for apps/web/src/routes/settings/admin/UsersGroup.svelte because its module scope hash changed after the display-name input handler was added. Updated only that exact hash (31d34e… → e538a5…); the rule, scope, owner, expiry, and apiFetch limit are unchanged. The active findings remain covered by their existing exact entries.

Finding during the required web check: `scripts/perf-lint` rejected the existing `cache.raw-read` exception for `apps/web/src/routes/settings/admin/UsersGroup.svelte` because its module scope hash changed after the display-name input handler was added. Updated only that exact hash (`31d34e…` → `e538a5…`); the rule, scope, owner, expiry, and `apiFetch` limit are unchanged. The active findings remain covered by their existing exact entries.
Author
Owner

Finding while regenerating the API contract: the new setup_check operation made calternal-server openapi fail closed because every published operation needs an explicit action policy. Added explicit public, read-only, non-replayable policies for both token checks and excluded them from CLI/MCP/WebMCP adapters because they are only for browser link navigation. This keeps the grant token out of agent action inputs.

Finding while regenerating the API contract: the new `setup_check` operation made `calternal-server openapi` fail closed because every published operation needs an explicit action policy. Added explicit public, read-only, non-replayable policies for both token checks and excluded them from CLI/MCP/WebMCP adapters because they are only for browser link navigation. This keeps the grant token out of agent action inputs.
Author
Owner

The performance guard also pins this exception in contracts/perf/adoption-1058.json; I refreshed the matching checkpoint hash to the same live UsersGroup.svelte syntax hash. The rule, limit, reason, owner, expiry, and ratchet counts remain unchanged.

The performance guard also pins this exception in `contracts/perf/adoption-1058.json`; I refreshed the matching checkpoint hash to the same live `UsersGroup.svelte` syntax hash. The rule, limit, reason, owner, expiry, and ratchet counts remain unchanged.
Author
Owner

The refreshed source check identified 12 existing #1058 list-scope exceptions on the same UsersGroup.svelte loop. Its normalized scope hash changed with this feature file edit. I refreshed the 12 exact hashes in both the exception ledger and adoption checkpoint; their rules, limits, reasons, owner links, expiry, and ratchet counts are unchanged.

The refreshed source check identified 12 existing #1058 list-scope exceptions on the same `UsersGroup.svelte` loop. Its normalized scope hash changed with this feature file edit. I refreshed the 12 exact hashes in both the exception ledger and adoption checkpoint; their rules, limits, reasons, owner links, expiry, and ratchet counts are unchanged.
Author
Owner

The real loopback adversarial probe passed 59 bounded requests. It checked malformed and oversized check bodies, path-like and bidi-Unicode tokens, both check endpoints' one-bit response, a concurrent per-token limit, and the existing ceremony/IP limits. Result: PASS: 59 bounded local requests; malformed/oversized, path-like and Unicode grant checks were safely refused; token and concurrent limits returned HTTP 429; 16 admitted flows; capacity HTTP 429.

The auth token-check profile ran locally because perf-run status showed the shared VM held by tabswitch2-fixture-10k. The host load averages recorded with the run were 12.62, 13.89 and 16.34, so treat the timing as noisy local evidence. There is no auth token-check baseline in docs/perf/baseline.json. Setup-check average/burst p50/p95 were 2.076/8.247 ms and 4.230/5.361 ms; invite-check average/burst p50/p95 were 2.091/2.910 ms and 4.265/5.620 ms. RSS was about 26.8 MB. No performance regression conclusion is drawn without a comparable baseline.

The real loopback adversarial probe passed 59 bounded requests. It checked malformed and oversized check bodies, path-like and bidi-Unicode tokens, both check endpoints' one-bit response, a concurrent per-token limit, and the existing ceremony/IP limits. Result: `PASS: 59 bounded local requests; malformed/oversized, path-like and Unicode grant checks were safely refused; token and concurrent limits returned HTTP 429; 16 admitted flows; capacity HTTP 429`. The auth token-check profile ran locally because `perf-run status` showed the shared VM held by `tabswitch2-fixture-10k`. The host load averages recorded with the run were 12.62, 13.89 and 16.34, so treat the timing as noisy local evidence. There is no auth token-check baseline in `docs/perf/baseline.json`. Setup-check average/burst p50/p95 were 2.076/8.247 ms and 4.230/5.361 ms; invite-check average/burst p50/p95 were 2.091/2.910 ms and 4.265/5.620 ms. RSS was about 26.8 MB. No performance regression conclusion is drawn without a comparable baseline.
Author
Owner

#1553 implementation complete

Head: e81252ef0ffeb24dcfbd9d3a1cc410285cee01ec (Probe malformed and concurrent registration checks). Three atomic commits:

  • ed9cbce23 Label the Voice model setup job
  • 05f87357e Check registration links and report invalid Admin fields
  • e81252ef0 Probe malformed and concurrent registration checks

Built

  • Admin create-user validation returns an optional field in the existing 400 body while keeping invalid_request, status, and message stable. ApiError.field is available to the web client. Server and input limits both allow 160 Unicode scalar values; the HTML maxlength is 320 UTF-16 units and input handling caps scalar values.
  • Voice model downloads show “Preparing Voice Memos” in the queue.
  • /auth/setup/check and /auth/invites/check check links without creating ceremony state. They return only { "valid": boolean }; invalid, expired, revoked, and used grants share false. Checks are read-only, bounded to 4 KiB, rate-limited by the existing IP budget and a token-digest bucket, and start/finish still recheck authority. The endpoints are excluded from generated action adapters.
  • Updated OpenAPI/action policy and the API client. Extended auth regression tests, the finite local benchmark, and tests/adversarial/auth_ceremony.py.

Files: crates/calternal-auth/src/{api.rs,error.rs,store.rs}, crates/calternal-server/src/wire.rs, crates/plugins/notes/src/voice.rs, apps/web/src/lib/auth/{passkeys.ts,passkeys.test.ts}, apps/web/src/lib/auth/components/CreateAccountFlow.svelte, apps/web/src/routes/settings/admin/UsersGroup.svelte{,.test.ts}, packages/api-client/src/{index.ts,index.test.ts,generated.ts}, contracts/{openapi.json,actions.json,action-policy.json}, contracts/perf/{registry.json,exceptions.json,adoption-1058.json}, bench/auth-ceremony.py, tests/adversarial/auth_ceremony.py.

Decisions

  • Display-name length uses Unicode scalar values, matching Rust str::chars() and JS Array.from(). The browser attribute uses twice the character limit because HTML counts UTF-16 units.
  • Field names are username, display_name, and role; ordinary validation errors omit field.
  • Link checks expose one validity bit. Starts and finishes remain authoritative. Check endpoints are browser-navigation reads and do not enter tool/action surfaces.
  • Expected cost: one indexed grant read plus fixed bounded rate-limit lookups, independent of User count.

UX gaps

Closed: setup/invite pages detect unusable links before offering the passkey form; checking, retry, and rate-limit states have status/alert semantics. The existing shared Pill is used for retry.
Left: Admin user creation does not yet map ApiError.field to an inline input message; per the brief this change exposes the API field only. The next UI mapping can associate it with the matching input and display/focus that field.

Evidence

Eight actual production-build Chromium captures use macOS platform emulation and cover setup and invite invalid-link states at 390 px and 1440 px in light and dark themes. Each asserts the real {valid:false} response, visible error, hidden form, and no update toast:

One finite local Linux profile (perf VM was occupied; local host was noisy, load average [12.6220703125, 13.892578125, 16.337890625]; no comparable token-check baseline exists): setup check p50/p95 2.076/8.247 ms average and 4.230/5.361 ms burst; invite check 2.091/2.910 ms average and 4.265/5.620 ms burst; RSS approximately 26.8–26.9 MB. Treat these as local observations, not a baseline comparison. The extended local adversarial probe passed: PASS: 59 bounded local requests; malformed/oversized, path-like and Unicode grant checks were safely refused; token and concurrent limits returned HTTP 429; 16 admitted flows; capacity HTTP 429.

Gates (after git fetch origin && git merge origin/dev; merge said Already up to date.)

  • cargo fmt --check: passed, no output (exit 0).
  • cargo clippy -p calternal-auth --all-targets -- -D warnings: Finished \dev` profile [unoptimized + debuginfo] target(s) in 1.62s`
  • cargo test -p calternal-auth: 139 passed, 2 failed, 3 ignored. The failures were existing store::tests::app_password_revoke_rejects_queued_verification (RateLimited at crates/calternal-auth/src/store.rs:5098:49) and store::tests::share_invites_serialize_the_last_slot (Unavailable at crates/calternal-auth/src/store.rs:6308:14). The final test output was test result: FAILED. 139 passed; 2 failed; 3 ignored; 0 measured; 0 filtered out; finished in 128.20s. Host load was above 20; these are SLOW contention failures. New #1553 auth tests passed. Existing expectations were not changed.
  • cargo clippy -p calternal-server --all-targets -- -D warnings: Finished \dev` profile [unoptimized + debuginfo] target(s) in 51.80s`
  • cargo test -p calternal-server: test result: ok. 276 passed; 0 failed; 12 ignored; 0 measured; 0 filtered out; finished in 213.70s; test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 53.14s; test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.44s.
  • cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings: Finished \dev` profile [unoptimized + debuginfo] target(s) in 49.13s`
  • cargo test -p calternal-plugin-notes: test result: ok. 324 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 337.47s; test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.84s; test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s.
  • apps/web bun run check: passed; perf-lint: PASS; 0 violations; 22247 scoped exceptions; svelte-check found 0 errors and 0 warnings.
  • apps/web bun run test -- --maxWorkers=2 src/routes src/lib/auth: passed; Test Files 26 passed (26), Tests 135 passed (135), duration 48.59s. Perf guard: Ran 140 tests in 0.042s ... OK; perf-source: 8 pass / 0 fail / 16 expect().
  • bun --cwd packages/api-client test: passed, 24 pass, 0 fail, 58 expect() calls.
  • scripts/action_registry.py --check, perf-lint, and git diff --check: passed.

Known gap: the two auth crate SLOW/load failures above. The final cleanup and clean-worktree check are in progress; no push, deploy, or merge was performed. Issue remains open.

#1553 implementation complete Head: `e81252ef0ffeb24dcfbd9d3a1cc410285cee01ec` (`Probe malformed and concurrent registration checks`). Three atomic commits: - `ed9cbce23` Label the Voice model setup job - `05f87357e` Check registration links and report invalid Admin fields - `e81252ef0` Probe malformed and concurrent registration checks ## Built - Admin create-user validation returns an optional `field` in the existing 400 body while keeping `invalid_request`, status, and message stable. `ApiError.field` is available to the web client. Server and input limits both allow 160 Unicode scalar values; the HTML `maxlength` is 320 UTF-16 units and input handling caps scalar values. - Voice model downloads show “Preparing Voice Memos” in the queue. - `/auth/setup/check` and `/auth/invites/check` check links without creating ceremony state. They return only `{ "valid": boolean }`; invalid, expired, revoked, and used grants share `false`. Checks are read-only, bounded to 4 KiB, rate-limited by the existing IP budget and a token-digest bucket, and start/finish still recheck authority. The endpoints are excluded from generated action adapters. - Updated OpenAPI/action policy and the API client. Extended auth regression tests, the finite local benchmark, and `tests/adversarial/auth_ceremony.py`. Files: `crates/calternal-auth/src/{api.rs,error.rs,store.rs}`, `crates/calternal-server/src/wire.rs`, `crates/plugins/notes/src/voice.rs`, `apps/web/src/lib/auth/{passkeys.ts,passkeys.test.ts}`, `apps/web/src/lib/auth/components/CreateAccountFlow.svelte`, `apps/web/src/routes/settings/admin/UsersGroup.svelte{,.test.ts}`, `packages/api-client/src/{index.ts,index.test.ts,generated.ts}`, `contracts/{openapi.json,actions.json,action-policy.json}`, `contracts/perf/{registry.json,exceptions.json,adoption-1058.json}`, `bench/auth-ceremony.py`, `tests/adversarial/auth_ceremony.py`. ## Decisions - Display-name length uses Unicode scalar values, matching Rust `str::chars()` and JS `Array.from()`. The browser attribute uses twice the character limit because HTML counts UTF-16 units. - Field names are `username`, `display_name`, and `role`; ordinary validation errors omit `field`. - Link checks expose one validity bit. Starts and finishes remain authoritative. Check endpoints are browser-navigation reads and do not enter tool/action surfaces. - Expected cost: one indexed grant read plus fixed bounded rate-limit lookups, independent of User count. ## UX gaps Closed: setup/invite pages detect unusable links before offering the passkey form; checking, retry, and rate-limit states have status/alert semantics. The existing shared `Pill` is used for retry. Left: Admin user creation does not yet map `ApiError.field` to an inline input message; per the brief this change exposes the API field only. The next UI mapping can associate it with the matching input and display/focus that field. ## Evidence Eight actual production-build Chromium captures use macOS platform emulation and cover setup and invite invalid-link states at 390 px and 1440 px in light and dark themes. Each asserts the real `{valid:false}` response, visible error, hidden form, and no update toast: - Setup: [phone light](https://git.kayg.org/attachments/dc805ae4-3ebb-4700-aa64-4969d938b1ec), [phone dark](https://git.kayg.org/attachments/a8b6d1fa-5985-4070-9773-37aa768b4288), [desktop light](https://git.kayg.org/attachments/1c03cf4d-3b24-487f-867d-62aaf660cd01), [desktop dark](https://git.kayg.org/attachments/491bdaa1-ffd7-473c-9c1c-6a3b9fc54793). - Invite: [phone light](https://git.kayg.org/attachments/1d2e02f5-21fb-4d6f-8fad-b224481befab), [phone dark](https://git.kayg.org/attachments/60bb08be-5e42-49bf-8aee-fe46c64f7eca), [desktop light](https://git.kayg.org/attachments/dcf73f3d-bcae-4a40-ae00-11f2c365c7a8), [desktop dark](https://git.kayg.org/attachments/4d91d811-476c-44ed-8e29-d385e5c3fdef). One finite local Linux profile (perf VM was occupied; local host was noisy, load average `[12.6220703125, 13.892578125, 16.337890625]`; no comparable token-check baseline exists): setup check p50/p95 2.076/8.247 ms average and 4.230/5.361 ms burst; invite check 2.091/2.910 ms average and 4.265/5.620 ms burst; RSS approximately 26.8–26.9 MB. Treat these as local observations, not a baseline comparison. The extended local adversarial probe passed: `PASS: 59 bounded local requests; malformed/oversized, path-like and Unicode grant checks were safely refused; token and concurrent limits returned HTTP 429; 16 admitted flows; capacity HTTP 429`. ## Gates (after `git fetch origin && git merge origin/dev`; merge said `Already up to date.`) - `cargo fmt --check`: passed, no output (exit 0). - `cargo clippy -p calternal-auth --all-targets -- -D warnings`: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 1.62s` - `cargo test -p calternal-auth`: 139 passed, 2 failed, 3 ignored. The failures were existing `store::tests::app_password_revoke_rejects_queued_verification` (`RateLimited` at `crates/calternal-auth/src/store.rs:5098:49`) and `store::tests::share_invites_serialize_the_last_slot` (`Unavailable` at `crates/calternal-auth/src/store.rs:6308:14`). The final test output was `test result: FAILED. 139 passed; 2 failed; 3 ignored; 0 measured; 0 filtered out; finished in 128.20s`. Host load was above 20; these are SLOW contention failures. New #1553 auth tests passed. Existing expectations were not changed. - `cargo clippy -p calternal-server --all-targets -- -D warnings`: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 51.80s` - `cargo test -p calternal-server`: `test result: ok. 276 passed; 0 failed; 12 ignored; 0 measured; 0 filtered out; finished in 213.70s`; `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 53.14s`; `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.44s`. - `cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings`: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 49.13s` - `cargo test -p calternal-plugin-notes`: `test result: ok. 324 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 337.47s`; `test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.84s`; `test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s`. - `apps/web bun run check`: passed; `perf-lint: PASS; 0 violations; 22247 scoped exceptions`; `svelte-check found 0 errors and 0 warnings`. - `apps/web bun run test -- --maxWorkers=2 src/routes src/lib/auth`: passed; `Test Files 26 passed (26)`, `Tests 135 passed (135)`, duration 48.59s. Perf guard: `Ran 140 tests in 0.042s ... OK`; perf-source: `8 pass / 0 fail / 16 expect()`. - `bun --cwd packages/api-client test`: passed, `24 pass, 0 fail, 58 expect() calls`. - `scripts/action_registry.py --check`, `perf-lint`, and `git diff --check`: passed. Known gap: the two auth crate SLOW/load failures above. The final cleanup and clean-worktree check are in progress; no push, deploy, or merge was performed. Issue remains open.
Author
Owner

Final cleanup is complete: cargo clean removed 17.0 GiB of build output, apps/web/build was removed, and git status is clean. Final HEAD remains e81252ef0f. No push, deploy, or merge was performed.

Final cleanup is complete: cargo clean removed 17.0 GiB of build output, apps/web/build was removed, and git status is clean. Final HEAD remains e81252ef0ffeb24dcfbd9d3a1cc410285cee01ec. No push, deploy, or merge was performed.
Author
Owner

Screenshot evidence update: I added the owner-required tablet captures at 820×1000. The four new captures use the real production web build and this branch's server, and assert HTTP 200 {valid:false}, visible invalid-link alert, and hidden form. Together with the earlier phone and desktop screenshots, setup and invite are now covered at 390, 820, and 1440 px in light and dark with macOS platform emulation.

I built the web production output with build ID ac7fda1a8 to match the shared server assets, then built calternal-server from this branch because the shared origin/dev binary returned 404 for the new check endpoint. The branch server returned HTTP 200 for both check endpoints in all four captures. The screenshots remain ignored under artifacts/; no screenshot is committed.

Screenshot evidence update: I added the owner-required tablet captures at 820×1000. The four new captures use the real production web build and this branch's server, and assert HTTP 200 `{valid:false}`, visible invalid-link alert, and hidden form. Together with the earlier phone and desktop screenshots, setup and invite are now covered at 390, 820, and 1440 px in light and dark with macOS platform emulation. - Setup tablet: [light](https://git.kayg.org/attachments/3cdcc7c8-916d-4457-be70-3f570485c769), [dark](https://git.kayg.org/attachments/1ee07e53-c7b1-40f0-80d3-5dc6eaaad8bd) - Invite tablet: [light](https://git.kayg.org/attachments/8f81a805-1aaf-4cad-9b23-1fcb6bf60c75), [dark](https://git.kayg.org/attachments/449b3cf4-0cff-4f51-92d2-856bf66fc95d) I built the web production output with build ID `ac7fda1a8` to match the shared server assets, then built `calternal-server` from this branch because the shared `origin/dev` binary returned 404 for the new check endpoint. The branch server returned HTTP 200 for both check endpoints in all four captures. The screenshots remain ignored under `artifacts/`; no screenshot is committed.
Author
Owner

Final cleanup after the tablet captures is complete: cargo clean removed 7.4 GiB, apps/web/build and apps/web/.svelte-kit were removed, and git status is clean. The four tablet attachments were fetched back and verified byte-for-byte as 820×1000 PNGs. Final HEAD: e81252ef0f. No push, deploy, or merge was performed.

Final cleanup after the tablet captures is complete: cargo clean removed 7.4 GiB, apps/web/build and apps/web/.svelte-kit were removed, and git status is clean. The four tablet attachments were fetched back and verified byte-for-byte as 820×1000 PNGs. Final HEAD: e81252ef0ffeb24dcfbd9d3a1cc410285cee01ec. No push, deploy, or merge was performed.
Author
Owner

Orchestrator review of job/autherr-1553 e81252ef0: accepted for the next merge round.

  • Screenshots checked (invite 390 light, setup 1440 dark): the invalid-link message shows before the passkey form; no form fields appear.
  • The 2 failing calternal-auth store tests (app_password_revoke_rejects_queued_verification, share_invites_serialize_the_last_slot) are not touched by this diff and failed under host load above 20. The merge job reruns cargo test -p calternal-auth on a quiet host.
  • Follow-up (UI): the Admin → Users add form will map ApiError.field to an inline message on the matching field.
Orchestrator review of job/autherr-1553 e81252ef0: accepted for the next merge round. - Screenshots checked (invite 390 light, setup 1440 dark): the invalid-link message shows before the passkey form; no form fields appear. - The 2 failing `calternal-auth` store tests (`app_password_revoke_rejects_queued_verification`, `share_invites_serialize_the_last_slot`) are not touched by this diff and failed under host load above 20. The merge job reruns `cargo test -p calternal-auth` on a quiet host. - Follow-up (UI): the Admin → Users add form will map `ApiError.field` to an inline message on the matching field.
Author
Owner

Deployed to calternal.cloud in merge round 40 (build c0f8da461, 01:38 IST 2026-10-12). Health state ok, /readyz 200, served-page check passed. Pre-deploy backup taken.

Deployed to calternal.cloud in merge round 40 (build c0f8da461, 01:38 IST 2026-10-12). Health state ok, /readyz 200, served-page check passed. Pre-deploy backup taken.
kayg closed this issue 2026-10-11 20:08:36 +00:00
Commenting is not possible because the repository is archived.
No labels
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal-v0#1553
No description provided.