ORDER BY created_at DESC, id DESC but cursor was `WHERE created_at <
$2` — when two users were created at the same instant, pagination
could skip the second row at a page boundary or return it twice.
- Add ListCursor { created_at, id } with `<rfc3339>_<uuid>` encode /
decode helpers.
- Change WHERE predicate to `(created_at, id) < ($2, $3)` matching the
ORDER BY.
- Surface the opaque cursor string through UsersListOpts /
UsersListPage / users_admin_api ListUsersResponse instead of the raw
DateTime — keeps the wire format stable while the id half stops the
boundary bug.
Same shape as F-P-005 (execution_logs).
AUDIT.md anchor: F-P-012.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Whitespace + import-grouping fixups in the 9 SDK modules touched by
F-Q-002. No behavior change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace ten near-identical `block_on` helpers (one per SDK module)
with a single shared `sdk::bridge::block_on(service: &str, fut)`.
The helper takes a service-prefix string and a future whose error
implements Display, so each module's `KvError`/`DocsError`/etc.
still self-formats.
Migrated:
- kv, docs, pubsub, users, dead_letters, secrets, files, email
- queue.rs has two helpers; the inline-shaped enqueue path and the
block_on_u64 (u64→i64) wrapper are left in place — the latter
could use the shared helper but the local form is one less call
site per finding overlap. Will revisit on a later pass if needed.
- http.rs is intentionally NOT migrated: it has per-variant error
mapping via `map_http_err` that the generic helper can't express.
Net: -218 / +97 lines across the 9 migrated files. Single source of
truth for the runtime-handle lookup and error wrapping.
AUDIT.md anchor: F-Q-002.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 files needed re-wrapping after the F2 clippy-fix pass landed
un-fmt'd. Pure cosmetic — no behavioral change. Re-runs of
`cargo fmt --all -- --check` now exit clean.
Reviewer-supplied per REVIEW.md §7; folded onto the branch
to avoid a NEEDS-CHANGES round-trip on a purely mechanical fix.
Sweep the v1.1.8-introduced clippy warnings under
--workspace --all-targets --all-features -- -D warnings:
* 12x map_unwrap_or in executor-core/sdk/users.rs — rewrite as
map_or; mostly the Option<X> -> Dynamic conversion shape.
* Doc-list-without-indentation in
app_user_password_reset_repo.rs's module docstring — rewrap
so a continuation line doesn't start with `+ `.
* usize-as-i64 cast in app_user_repo.rs list — use
i64::try_from(...).unwrap_or(i64::MAX) for the cursor cap.
* 2x map_unwrap_or in users_service.rs env helpers — map_or.
* single-pattern match in users_service.rs login — let-else
rewrite so the dummy-Argon2 side-effect stays in the
diverging branch (no semantic change).
Also updated the 10 executor-core integration-test bins
(sdk_email, sdk_kv, sdk_docs, sdk_files, sdk_pubsub,
sdk_secrets, sdk_http, sdk_subscriber_token,
module_redaction_logging, modules) to pass the new
NoopUsersService positional arg into Services::new().
NOTE — the brief specifies running this attestation under `cargo
clean` first. I skipped the clean step because an earlier
`cargo test --workspace` froze this host (the user explicitly
asked me to keep subsequent builds lighter). The incremental
cache was hot for every workspace crate when clippy ran; test
binaries appeared in the Checking output as expected. Flagged in
HANDBACK §6 F2 + §7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
migration 0028: app_user_email_verifications table — token_hash PK,
app_id + user_id FKs cascading, expires_at, consumed_at. Single-use
via atomic UPDATE WHERE consumed_at IS NULL.
UsersServiceImpl gains:
* verifications: Arc<dyn AppUserVerificationRepo>
* email: Arc<dyn EmailService>
users::send_verification_email(user_id, opts) mints a 32-byte token,
stores SHA-256(token), and calls EmailService::send with the body
template's {link} placeholder substituted by link_base + ?token=raw.
EmailError::NotConfigured propagates as UsersError::EmailNotConfigured
so scripts already handling email-disabled mode (v1.1.7 email::send)
don't need new branches.
users::verify_email(token) atomically consumes the one-shot token via
the verifications repo, marks the user's email_verified_at = NOW(),
and emits a "users::email_verified" event for future triggers.
Internal email send uses a synthesized SdkCallCx with principal=None
so the email-service AppEmailSend authz check is skipped (the
users::* surface has already gated on AppUsersWrite — the internal
hop isn't the script's direct call). Documented in HANDBACK §7.
EmailTemplateOpts now requires `from` (the v1.1.7 email service needs
an envelope sender). The brief example omitted it; deviation logged
in HANDBACK §7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rhai bridge for the v1.1.8 users::* namespace, wired through
sdk::register_all. Collection-less surface (mirrors email::/secrets::,
not kv::'s handle pattern) — app_id never appears on the script-side
signature; the service derives it from cx.app_id.
Eighteen functions bound:
* CRUD: users::create / get / find_by_email / update / delete / list
* Auth: users::login (returns session token string or ()),
users::verify (returns user map or ()),
users::logout
* Email-tied: users::send_verification_email / verify_email /
request_password_reset / complete_password_reset /
invite / accept_invite (two arities: with/without
display_name override)
* Roles: users::add_role / remove_role / has_role
Bindings for methods whose service impl isn't in place yet (email
flows, roles, invitations) still route to the trait — the service
returns UsersError::Backend("not yet implemented") which surfaces
as a Rhai runtime error. Later v1.1.8 commits replace the service
stubs without re-touching the bridge.
User map shape: id, email, display_name, email_verified_at,
last_login_at, created_at (rfc3339), updated_at (rfc3339), roles (Vec).
Never password_hash. list returns #{ users: [...], next_cursor }
where next_cursor is the rfc3339 timestamp of the last row.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>