Skip to content

docs(auth): spec for per-Agent identity, login & user management (HT-54) - #69

Merged
zaridan merged 4 commits into
mainfrom
feat/ht-54-agents-auth-spec
Jul 18, 2026
Merged

docs(auth): spec for per-Agent identity, login & user management (HT-54)#69
zaridan merged 4 commits into
mainfrom
feat/ht-54-agents-auth-spec

Conversation

@zaridan

@zaridan zaridan commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Spec for review — no code, contract only. Replaces HT-51's single operator password with real per-Agent identity, per that ticket's own "replace, not extend" mandate. HT-54.

The core idea

Free AGPL core = username/password, delivered through an auth-provider seam. Google SSO / magic-link / passkey are licensed marketplace modules that register against the seam — the concrete instance of the AGPL §7 module-marketplace boundary counsel is defining (HT-5). The agent_auth_identities table (one Agent, many login methods) lets a premium provider attach with zero core-schema change.

What it covers

Data model (agents, agent_auth_identities, assignee→Agent FK) · the seam + identity-service · Admin/Agent roles · engine API (/auth/*, /agents/*, /setup) · session-carries-sub · invite-primary + temp-password provisioning · the six web screens (modelled on the FreeScout UX I observed black-box only — its source stays unread — rendered in our design system, Agent/Team vocabulary) · security · rollout.

How it was produced (per the delegation ladder)

Fable-authored (judgment-dense, don't-delegate floor), then an independent parallel Fable-tier adversarial review against the charter, agent-inbox-v1.md, the live schema, and HT-51's shipped auth. It surfaced 9 real findings incl. 3 blockers — all applied by me (see the changelog): a mid-session identity drop on the cookie-refresh path, the acting-Agent header as a real api.ts change + an engine status re-check (bounds disabled/deleted Agents whose cookie is still valid), atomic /setup/last-admin/invite (no check-then-act races), uniform-401 login (no status leak), temp-password activation, a scoped §7 boundary claim + identity-service seam so modules never write core tables directly, invites via the EmailSender transport (not sendReply), and immutable email in v1.

Your calls (spec §12)

Three confirmed (Admin+Agent, /setup, both-provisioning); three open with my recommendation: per-Agent mailbox scoping (recommend defer/don't-build — avoid dead schema), profile-field scope (recommend lean + timezone), acting-Agent trust model (recommend header-assertion under the service token, per-Agent tokens later). Note plainly (§5/§8): a HELPTHREAD_API_TOKEN holder still bypasses role checks — the 403s stratify humans behind the web app, not the service channel.

Once you're happy with the contract, I orchestrate the build (engine + web) as a reviewed, gated wave.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Added the draft “Agents & Authentication” contract covering per-agent identity, admin-managed onboarding, provider discovery, auth verification, and agent administration (invites, password reset, and password workflows).
    • Documented the new session cookie v2 format and how acting-agent trust is determined from a verified session.
    • Outlined security behaviors, including one-time invite tokens, constant-time password checking, and generic authentication failures to reduce enumeration.
  • Breaking Changes
    • Updated conversation assignment to use assignee_agent_id instead of conversations.assignee, requiring UI/API rollout updates.
    • Session behavior changed from payload v1 to v2, requiring users to re-login.

Replaces HT-51's single operator password with real per-Agent accounts
per that ticket's mandate. Free AGPL core = username/password via an
auth-provider seam; Google SSO / magic-link / passkey are marketplace
modules registering against the seam (the concrete AGPL §7 boundary,
HT-5). agents + agent_auth_identities (one Agent, many login methods);
assignee graduates 'me' -> Agent FK; Admin/Agent roles; /setup first
admin; invite-primary + temp-password provisioning; session carries sub.
UX modelled on FreeScout (black-box only), rendered in our design system.

Fable-authored, independently Fable-reviewed against charter + codebase;
all 9 review findings applied (see changelog).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request adds an Agents and Authentication specification covering Agent identity models, provider-based authentication, engine and web flows, session trust, provisioning, security requirements, rollout changes, and scope boundaries.

Changes

Agents and authentication

Layer / File(s) Summary
Identity and assignment data model
specs/auth/agents-and-auth.md
Defines Agent lifecycle, authentication identities, password-provider uniqueness, mailbox access modeling, and the conversations.assignee_agent_id migration.
Provider and authorization boundaries
specs/auth/agents-and-auth.md
Specifies provider registration, identity-service ownership, endpoint authorization, and serialized last-admin enforcement.
Engine APIs and web authentication flows
specs/auth/agents-and-auth.md
Describes first-run setup, authentication, Agent management, invite acceptance, provisioning, and web flows.
Security and rollout contract
specs/auth/agents-and-auth.md
Defines acting-Agent trust, credential and token security, legacy password retirement, rollout changes, scope boundaries, decisions, and changelog updates.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Agent
  participant Web
  participant Engine
  participant Database
  Agent->>Web: Submit credentials
  Web->>Engine: POST /auth/verify
  Engine->>Database: Resolve provider identity
  Database-->>Engine: Agent identity and status
  Engine-->>Web: Session cookie v2 or generic 401
  Web->>Engine: Request with asserted Agent ID
  Engine->>Database: Re-check Agent status
  Engine-->>Web: Authorized response
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the contract-only auth specification for per-Agent identity, login, and user management.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ht-54-agents-auth-spec

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@specs/auth/agents-and-auth.md`:
- Around line 211-216: Update specs/auth/agents-and-auth.md lines 211-216 to
require an advisory transaction lock or a serializable transaction with retry
around active-admin demotion, disabling, and deletion, while retaining the count
predicate as an additional guard. Update specs/auth/agents-and-auth.md lines
230-234 to apply the same serialization to first-admin bootstrap so concurrent
requests cannot create multiple initial Agents; retain the NOT EXISTS predicate
as an additional guard.
- Around line 42-46: Update the Agent definition in the terminology section to
describe an Agent directly as a human member of the support staff who operates
the inbox, removing the “user” characterization. Preserve the distinction that
Agents are human support staff, Assistants are AI actors, and “Users” is
reserved only for explicitly referenced FreeScout terminology.
- Around line 355-363: Define temporary-password lifecycle state for the
fallback authentication path, including an expiry or forced-change indicator and
the enforcement point during authentication. Require an Agent using a temporary
password to change it before normal authenticated access, and clear the
temporary state after a successful change; update the temp-password flow and
first-login activation behavior accordingly.
- Around line 73-78: Resolve the lifecycle contradiction across the Agent status
and authentication sections: choose one model and apply it consistently. Either
create temporary-password Agents as active with an explicit
required-password-change state, or allow password-authenticated invited Agents
and atomically promote them during `/auth/verify`; update the definitions,
provisioning paths, login behavior, and first-login activation requirements to
match the chosen model.
- Around line 86-96: Enforce the one-password-identity-per-agent invariant in
the schema and core identity service. Add a partial unique index on
agent_auth_identities.agent_id for rows where provider = 'password', and update
the core identity service to reject or prevent creating a second password
identity for the same agent while preserving existing behavior for other
providers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c9449784-6b86-49d9-a0d5-0b0533f74a21

📥 Commits

Reviewing files that changed from the base of the PR and between ff0308d and f955143.

📒 Files selected for processing (1)
  • specs/auth/agents-and-auth.md

Comment thread specs/auth/agents-and-auth.md Outdated
Comment thread specs/auth/agents-and-auth.md Outdated
Comment thread specs/auth/agents-and-auth.md
Comment thread specs/auth/agents-and-auth.md Outdated
Comment thread specs/auth/agents-and-auth.md Outdated
- admin-set-password Agents are created 'active' outright; 'invited' is
  exclusively the invite path's status (resolves the lifecycle contradiction)
- rename the fallback honestly: admin-set password, not temporary; forced
  password change deferred to §11
- one password identity per Agent enforced by a partial unique index + the
  identity service
- /setup and last-admin mutations serialized via pg_advisory_xact_lock;
  guard predicates kept as inner guards (READ COMMITTED races)
- §2 vocabulary wording

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
specs/auth/agents-and-auth.md (1)

87-103: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Enforce the provider-specific secret_hash invariant.

The schema permits provider='password' with a NULL hash and non-password providers with arbitrary hashes, even though the contract defines secret_hash as password-only. Add a CHECK constraint or explicitly require the identity service to reject these malformed rows before they reach authentication/reset flows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specs/auth/agents-and-auth.md` around lines 87 - 103, Add a schema CHECK
constraint to agent_auth_identities enforcing that provider='password' requires
a non-NULL secret_hash, while every non-password provider requires secret_hash
to be NULL. Keep the existing uniqueness constraints and indexes unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@specs/auth/agents-and-auth.md`:
- Around line 268-271: Update the agent lifecycle documentation covering PATCH
/agents/{id} and /password to restrict status changes to defined valid
transitions, reserve invited → active with password creation for atomic invite
acceptance, and reject password writes for invited Agents. Ensure the documented
rules prevent arbitrary statuses, disabling an invited Agent, or adding a
password while status remains invited, while preserving the existing
provisioning behavior.
- Around line 268-271: Update the POST /api/v1/agents provisioning contract and
its corresponding section around the additional referenced lines to require a
configured EmailSender before accepting sendInvite=true; reject the request
before Agent insertion when no sender exists, or require the password-based
admin fallback instead. Preserve the existing invited/no-password behavior when
an EmailSender is configured and the active/password behavior for the
alternative path.

---

Outside diff comments:
In `@specs/auth/agents-and-auth.md`:
- Around line 87-103: Add a schema CHECK constraint to agent_auth_identities
enforcing that provider='password' requires a non-NULL secret_hash, while every
non-password provider requires secret_hash to be NULL. Keep the existing
uniqueness constraints and indexes unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 55cc99ca-f5bf-472f-b39b-c19bcc149451

📥 Commits

Reviewing files that changed from the base of the PR and between f955143 and 215a5ac.

📒 Files selected for processing (1)
  • specs/auth/agents-and-auth.md

Comment thread specs/auth/agents-and-auth.md
zaridan and others added 2 commits July 18, 2026 13:16
…ule per-endpoint (HT-54)

- §3.4: agent_mailbox_access modelled in this migration (TJ decision),
  schema-only, nothing consults it in v1
- §8: acting-Agent header rule made precise — required on /agents/*,
  /auth/me, PUT .../assignee; other inbox endpoints stay bearer-only this
  increment (no third breaking change), disabled-Agent consequence stated
- §12: decision 4 confirmed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…led only (HT-54)

invited exits solely via invite acceptance (or delete/re-create); password
writes on an invited Agent are refused. Closes the incoherent states an
unconstrained status field permitted (CodeRabbit round 2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@specs/auth/agents-and-auth.md`:
- Around line 498-502: The draft.3 changelog entry has an incomplete causal
phrase. Update the sentence in the draft.3 bullet to state that the closed
incoherent states were permitted by an unconstrained `status` field, preserving
the existing technical details and meaning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e9514ff-f8a2-43f7-9340-7ce66246722a

📥 Commits

Reviewing files that changed from the base of the PR and between 95a2578 and fd5b9d4.

📒 Files selected for processing (1)
  • specs/auth/agents-and-auth.md

Comment thread specs/auth/agents-and-auth.md
@zaridan
zaridan merged commit 1a32a0f into main Jul 18, 2026
5 checks passed
zaridan added a commit that referenced this pull request Aug 4, 2026
Restores PR references the changelog consolidation dropped. The previous
commit message claimed "ticket and PR references are kept as navigable
provenance" while the diff deleted PR #82, #88, #69 and #85 from the
consolidated entries. Claim and diff now agree.

Rewrites 28 further sites the earlier passes missed. They used phrasings
the patterns did not cover -- "an earlier revision", "the first draft
wrongly hung", "retracted here rather than quietly patched", "corrected
there in this same review round", "review round 2". Each is restated
forward and keeps its technical lesson:

  store/mailboxes.ts  "Why not tell the operator to disconnect and retry?
                       Because that instruction would be false: ..."
  providers/inbound-email.ts
                      "Why not return a NormalizedInboundEmail? That puts
                       the parse inside the provider ..."
  gmail-reconcile.ts  "Why not just ack? The tempting reasoning is ..."

Also adds a supersession note to decision point 4 in agents-and-auth.md,
which still recorded "no scoping behavior or UI" while section 3.4 manages
real grants. The recorded decision is left intact; the note points at what
superseded it.

Two artifacts from the previous pass fixed: an orphaned closing paren in
substrate-v1.md, and a substitution that made a date the grammatical
subject in migrate.ts.

Verified: typecheck, web typecheck, lint clean. Every edit is inside a
comment or markdown prose; no source line outside a comment changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant