Users and roles
Sill dashboard users are humans who sign in to manage an Account. Each user belongs to exactly one Account, holds one of four roles — owner, admin, reviewer, viewer — and moves through a lifecycle of invited, active, suspended, deactivated. Authentication is magic-link only: Sill stores no passwords, and SSO is on the roadmap (Phase 4), not available today.
Account and user model
Section titled “Account and user model”An Account is the top-level merchant org. A User is a human seat inside that Account.
| Field | Type | Notes |
|---|---|---|
user_id | ULID | Stable identifier. |
account_id | ULID | The Account the user belongs to. One user, one Account. |
email | string | Sign-in identifier. Magic links are sent here. |
name | string | Display name. |
role | owner | admin | reviewer | viewer | See Roles. |
status | invited | active | suspended | deactivated | See Lifecycle. |
mfa_enabled | bool | Reserved; MFA is on the roadmap. |
sso_subject | string? | Reserved; SSO is on the roadmap (Phase 4). |
last_active_at | ISO-8601 UTC | Updated on session refresh. |
created_at | ISO-8601 UTC | Timestamp the user record was minted. |
Secrets such as password hashes or MFA seeds are referenced by opaque *_ref columns and never stored inline.
Sign-in and first user
Section titled “Sign-in and first user”Sign-in is magic-link only. The flow:
- The visitor enters an email address on the dashboard.
- The API issues a short-lived, single-use token and emails a verification link via SES. The token is hashed at rest; the raw value lives only in the email body and the verify URL.
- Clicking the link consumes the token atomically and mints a session cookie.
On a first-ever sign-in for an email, the API auto-creates an Account and an owner user with status = active in the same transaction. There is no separate signup form. The first owner can then bring on additional teammates as more invite tooling lands.
sequenceDiagram
participant U as User (browser)
participant D as Dashboard
participant API as Sill API
participant SES as Email (SES)
U->>D: Enter email
D->>API: POST /v1/auth/sign-in
API->>SES: Send verify link (token hashed at rest)
SES-->>U: Email with verify URL
U->>API: GET /v1/auth/verify?token=...
alt First sign-in for this email
API->>API: Create Account + owner User (status=active)
else Existing user
API->>API: Look up User
end
API-->>U: Set-Cookie: sill_session
U->>D: Authenticated session
Sessions are bound to a hashed cookie value scoped so the dashboard and marketing site share auth state. Sessions roll-refresh past the halfway point of their lifetime; expired or revoked sessions return null from the resolver. Only active users can resolve a session — invited, suspended, and deactivated users are rejected at the auth boundary.
Roles are coarse and code-enforced. Each route handler checks the caller’s role before performing a privileged action.
- owner — full write access across the Account. Created automatically on first sign-in for a brand-new Account. Can change merchant-wide configuration such as buyer-detail retention, settlement rail, and the active policy.
- admin — same write surface as
ownerin code today. The distinction exists for org clarity and future delegation (e.g. owner-only transfer of the Account, billing changes). - reviewer — read access plus the ability to triage the escalation queue and decide individual escalations (approve / reject / dismiss). Cannot mutate sites, policies, catalogs, or account settings.
- viewer — read-only across the Account. Useful for analysts, auditors, and compliance reviewers who need visibility without write power.
Roles versus permissions
Section titled “Roles versus permissions”The table below reflects what the shipping API enforces today. Where a row says “read only,” the user can fetch and view the resource through the API but cannot create, update, or delete it.
| Capability | owner | admin | reviewer | viewer |
|---|---|---|---|---|
| View dashboard, sites, audit log | yes | yes | yes | yes |
| Create / modify sites | yes | yes | read only | read only |
| Verify a domain (domain verification) | yes | yes | read only | read only |
| Publish or change the active policy | yes | yes | read only | read only |
| Manage the merchant catalog | yes | yes | read only | read only |
| Manage the agent-card profile | yes | yes | read only | read only |
| Manage settlement-rail configuration | yes | yes | read only | read only |
| Configure account-wide buyer-detail retention | yes | yes | read only | read only |
| Triage / decide HITL escalations | yes | yes | yes | read only |
| Export a signed audit bundle | yes | yes | read only | read only |
| Run red-team drills | yes | yes | no | no |
| Reveal buyer-detail PII (gated, audited) | yes | yes | no | no |
Forbidden actions return HTTP 403 with {"error":"forbidden","reason":"role_insufficient"}. The role is read from the session-bound Principal; clients cannot self-assert a role.
Lifecycle
Section titled “Lifecycle”A user moves through four states. Only active users can sign in.
flowchart LR invited --> active active --> suspended active --> deactivated suspended --> active suspended --> deactivated
- invited — the user record exists but the human has not yet completed first sign-in. Sessions are refused. (The schema supports this state today; broader invite tooling is rolling out.)
- active — can sign in via magic link and exercise the permissions granted by their role.
- suspended — temporarily blocked from sign-in. The record and history are preserved. Reversible.
- deactivated — terminal off-state. Sessions refused; the user no longer counts as a seat. Audit history referencing the user remains intact (audit records are append-only and Merkle-chained).
The session resolver enforces this directly: any status other than active causes session resolution to fail, even if a prior cookie is still within its TTL.
Sessions
Section titled “Sessions”- Cookie —
sill_session,HttpOnly,Secure,SameSite=Lax, scoped so the dashboard and marketing site share auth state. - TTL — 14 days. Past the halfway point of the lifetime, the resolver issues a rolling refresh and writes the new
expires_atback to the row. - At rest — the cookie value is hashed (SHA-256, base64url) before any DB write. The raw value lives only in the user’s browser.
- Revocation — signing out deletes the row keyed by the hashed cookie value. A daily sweep removes expired magic-link tokens and expired sessions.
What is not yet available
Section titled “What is not yet available”These items are part of the published roadmap, not present-day capability. They are listed here so the docs stay honest about Sill’s bounds.
- SSO / SAML / OIDC. Not available. SSO is Phase 4. The
sso_subjectcolumn exists for future use. - MFA / TOTP. Not enforced. The schema reserves
mfa_enabledandmfa_secret_ref; the magic-link path is the only credential today. - Granular per-site roles. Roles are Account-scoped, not per-site. A user with
vieweron the Account isvieweron every site in that Account. - API keys — the schema models account-scoped or site-scoped API keys with named scopes; programmatic-credential issuance from the dashboard is rolling out.
Frequently asked
Section titled “Frequently asked”Who is created when I sign in for the first time?
Section titled “Who is created when I sign in for the first time?”A new Account and a single owner user are created atomically on first magic-link verification. The Account name and user name default from your email; you can change both from account settings.
Can a user belong to more than one Account?
Section titled “Can a user belong to more than one Account?”No. The model is one user, one Account (account_id is a non-null column on user). To work in two Accounts, you sign in with two different email addresses.
What happens to a deactivated user’s audit history?
Section titled “What happens to a deactivated user’s audit history?”It stays. Audit records are append-only and Merkle-chained — deactivating a user changes who can sign in, not the record of what they did. Past mandate decisions and escalation resolutions that reference the user remain verifiable.
How do magic-link tokens stay safe?
Section titled “How do magic-link tokens stay safe?”The raw token exists in three places only: the email body, the verify-URL query parameter the user clicks, and never anywhere else. The DB stores only a SHA-256 hash. Tokens are short-lived, single-use, and consumed in a transaction so a replay cannot mint two sessions. Rate limits apply per-email and per-IP.
Why are owner and admin enforced identically in many places?
Section titled “Why are owner and admin enforced identically in many places?”Today most write actions accept either role. The distinction exists in the schema and in the role taxonomy so that owner-only actions (Account transfer, future billing-owner gates) can be tightened without a migration. Treat owner as “the seat that can change the seats.”
Can I assign Sill roles to a third-party AI agent?
Section titled “Can I assign Sill roles to a third-party AI agent?”No. Roles describe human dashboard users. Non-human callers are modelled as Agents and authenticated by their signed agent card and ed25519 public key, not by a session cookie. The two are deliberately separate.