# Desktop account connectors

Company OS Desktop's **Connectors → Accounts** view is for linking an account. Select Outlook,
Gmail, or another supported account app, press **Connect**, approve access with
the provider in the system browser, and return to the account shown in Company OS.
The flow does not open localhost Company Console. Custom MCP servers and
additional integration setup live in **Advanced**.

Selection checks availability through a read-only RPC. Missing Company OS
provider registration appears as a short installation-availability message;
administrator details are secondary. **Check again** never starts OAuth.
A configured installation opens the provider's approval URL directly and shows
native progress, cancellation, expiry, and retry. Saved email/account identity
is displayed separately from permissions and diagnostics.

Company OS owns state, PKCE, principal/company binding, code exchange, scope
validation, encrypted credentials, refresh, and receipts. Tokens are never
placed in UI projections, tool arguments, or provider approval query strings.
Provider OAuth is separate from authentication to Company OS's MCP server.
Company OS's already-authenticated agents do not require an additional MCP-host
OAuth authorization server for this flow.

## One-time provider registration

For a self-hosted installation, register an OAuth app for Company OS. A model
subscription or an installed coding-agent plugin does not supply this app's
provider credentials. Configure the registrations once for the installation;
customers only approve their own account access. The complete secret-free
template is `config/company-os-oauth.env.example`.

For Google, create a **Desktop app** OAuth client in Google Cloud, configure
the consent screen and its permitted users, and enable the APIs the company
will use. Set these values on the Company OS server:

```text
HSM_OAUTH_GOOGLE_CLIENT_ID
HSM_OAUTH_GOOGLE_CLIENT_SECRET
```

The desktop uses `http://127.0.0.1:<port>/connector/callback`. Desktop OAuth
clients support a loopback callback; a web-client registration must match the
callback actually used. To reserve a specific registered port, set
`HSM_OAUTH_GOOGLE_REDIRECT_URI` to the complete loopback URI. The native app
binds that port and reports an error if it is occupied.

For Microsoft, register the app in Microsoft Entra with a native/public-client
loopback redirect accepted by that registration:

```text
HSM_OAUTH_MICROSOFT_CLIENT_ID
HSM_OAUTH_MICROSOFT_REDIRECT_URI
```

The configured URI must use the exact loopback form above. An optional
`HSM_OAUTH_MICROSOFT_TENANT_ID` selects `common`, `organizations`, `consumers`,
or a tenant UUID. Confidential clients can also supply
`HSM_OAUTH_MICROSOFT_CLIENT_SECRET`. Provider registration and organizational
consent policies still apply.

The existing company credential vault must be configured. Keep credentials in
the server's protected configuration; do not put secrets in connector metadata
or browser URLs. Restart the Company OS server after changing its environment.
Connectors keeps installation availability in the desktop. Open **Administrator details** for
administrator instructions and the missing configuration names. No browser
opens when readiness is false.

### GitHub, Slack, Notion, and Stripe

| Provider | Server configuration | Registered callback | Permissions |
| --- | --- | --- | --- |
| GitHub OAuth App | `HSM_OAUTH_GITHUB_CLIENT_ID`, `HSM_OAUTH_GITHUB_CLIENT_SECRET`, `HSM_OAUTH_GITHUB_REDIRECT_URI` | `http://127.0.0.1:43821/connector/callback` | `read:user`; profile and public repository reads |
| Slack app | `HSM_OAUTH_SLACK_CLIENT_ID`, `HSM_OAUTH_SLACK_CLIENT_SECRET` | `{public-origin}/api/company/connectors/oauth/callback/slack` | Bot `channels:read,channels:history`; public channels shared with the app |
| Notion public integration | `HSM_OAUTH_NOTION_CLIENT_ID`, `HSM_OAUTH_NOTION_CLIENT_SECRET` | `{public-origin}/api/company/connectors/oauth/callback/notion` | Configure Read content capability; users select shared pages. No OAuth scope strings |
| Stripe Extension | `HSM_OAUTH_STRIPE_CLIENT_ID`, `HSM_OAUTH_STRIPE_CLIENT_SECRET`, `HSM_OAUTH_STRIPE_INTEGRATION_TYPE=extension` | `{public-origin}/api/company/connectors/oauth/callback/stripe` | `read_only`; ordinary Stripe Platform registrations do not support this scope |

`HSM_OAUTH_PUBLIC_BASE_URL` must be the deployed HTTPS origin of the same Company
OS API configured in the desktop. It cannot be a separate domain that forwards
unverified codes or a localhost URL. Configure the exact callback at each
provider. No public callback address is configured on the current installation.

GitHub uses PKCE plus its app secret. Slack uses bot-installation credentials,
checks `ok` on API responses, and never substitutes an installer user token.
Notion uses JSON plus HTTP Basic and a pinned `Notion-Version`. Nullable Notion
refresh tokens are accepted; documented long-lived tokens have no invented
expiry. Refresh responses retain the previously confirmed scopes when omitted.

Stripe now returns a connected account ID rather than necessarily returning an
OAuth access token. The platform credential remains encrypted in the company
vault, and native requests bind `Stripe-Account` to the verified `acct_…` identity.
The runtime restricts native calls to the reviewed origin, GET paths, and scopes.
A generic OpenAPI connection cannot borrow that credential. A Stripe restricted
API key is a separate credential flow; this registration is specifically Connect.

BaseLayer uses its API key. Generic OpenAPI and custom MCP servers retain their
configured credential/trust flow in Advanced. Automatic OAuth discovery for an
arbitrary MCP server is not implemented by these provider adapters.

Official references: [GitHub](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps),
[Slack](https://docs.slack.dev/authentication/installing-with-oauth/),
[Notion](https://developers.notion.com/guides/get-started/authorization), and
[Stripe](https://docs.stripe.com/connect/oauth-reference).

## Account apps and agent access

One reviewed identity list now supplies OAuth mappings and account catalog
entries. A partially seeded registry no longer hides Outlook or the other
supported Google apps. Company overrides and disabled connectors take
precedence over platform defaults.

| Account app | Included native read tools |
| --- | --- |
| Outlook / Microsoft 365 Mail | Search/list messages; read a message |
| Gmail / Google Workspace mail | Search/list messages; read a message |
| Google Drive | Search files; read details; export supported document text |
| Google Calendar | List calendars; read events |
| Google Sheets | Read spreadsheet details and cells |
| Google Docs | Read a document |
| Google Slides | Read a presentation |
| Google Analytics | List accounts and properties |
| YouTube | Read channel and video details |
| GitHub | Read profile; list public repositories |
| Slack | List shared public channels; read channel history |
| Notion | Read shared page details and blocks |
| Stripe | Read connected-account balance and payment records |

These are 24 read-only tools. They do not send email, modify documents, delete
items, or publish content. Scopes match the reviewed read operations. Google
Ads remains a sign-in-only integration here: its separately approved developer
token and Executor integration are still required; no native Ads tools are
advertised or installed by OAuth.

The callback saves credentials and installs the app's reviewed tools in the
same governed database transaction. A conflicting custom source/tool is not
overwritten. Reconnect updates account choices rather than duplicating tools.
It invalidates old refresh claims; the broker reads client and refresh-token
configuration from the same claimed credential version. Final activation
rechecks connector availability after provider I/O.

Agents discover these operations through the normal company tool catalog.
Each schema lists the connected account identities and requires `account_id`.
The admitted HTTP adapter validates company, provider, connector, state,
scopes, registry availability, and permitted HTTPS origin before retrieving a
token from the canonical broker. Selection stays in execution arguments and
receipts and is stripped from the provider request. Generic OpenAPI fallback
cannot silently choose a native OAuth account.

## Connection health

A successful identity lookup alone is not verified service access. Outlook,
Gmail, Drive, Calendar, Analytics, YouTube, GitHub, Slack, and Stripe perform a minimal data API read
before a checked connection is recorded. Probe content is not persisted.
Sheets, Docs, Slides, and Notion require a caller-selected document/page ID, so their saved
sign-in remains access-unverified until a governed read succeeds.

Successful reads update access evidence; 401 requests reconnect, and 403
records a permission error. Evidence updates are fenced against newer
credentials. A missing individual document (404) is not treated as revoked
account authorization. A settled run or connected badge does not establish
business-output quality.

## Runtime contract

Routes are scoped under
`/api/company/companies/:company_id/connectors/oauth/:connector_key`:

- `GET`: read-only readiness without secrets or a session.
- `POST /start`: prepare a company/principal-bound provider approval URL.
- `POST /complete`: verify the callback, identity, scopes and supported access
  probe; revalidate authority and atomically save the account and reviewed tools.
- `POST /cancel`: cancel a pending session; claimed/saved sessions cannot be
  falsely reported as cancelled.

Sessions bind company, principal, connector, nonce, registered callback, and
PKCE verifier where supported for ten minutes. For HTTPS providers, the public
`GET /api/company/connectors/oauth/callback/:provider` only deposits one
state-bound result in the pending session. It performs no token exchange or
vault write. Authenticated desktop polling submits only session ID and state
to `/complete`, which checks principal authority again before exchanging the
code and saving the account. The browser receives a static, no-store result
with no-referrer policy. Public callbacks do not carry company access tokens. Invalid callbacks cannot consume another session.
Successful completion is replayable under the same binding without exchanging
the provider code again. Refusals and successful writes retain governance
receipts. Provider calls use the existing admitted integration executor and
terminal settlement; no direct tool-execution bypass was added.

Pending sessions and the bounded completion cache are process-local. A server
restart requires starting sign-in again; replicas must keep one handshake on
the same instance. Saved accounts remain in the company vault.

## Deployment and evidence

The September 7 provisioning follow-up created a Company OS native/public
Microsoft app in Gestion Velora's tenant and saved its client ID, tenant ID,
and registered loopback callback in the ignored, mode-0600 server `.env`.
The live Microsoft readiness endpoint now returns `supported=true, ready=true`,
and the installed desktop presents **Connect** for Outlook. This installation
uses a single-tenant registration. Its delegated permission save and account
consent remain pending explicit approval required by automatic approval review.
No live mailbox read or token refresh has been verified.

A dedicated Google Cloud project, `company-os-velora`, was created. Google
consent setup and the nine-service API activation form are prepared. After
automatic approval review required explicit approval of the testing audience
and API activation, the operator chose **Leave Google pending**. No Google OAuth client has been created, no connector APIs
were enabled by this follow-up, and Google readiness remains false.

These provisioning results do not constitute live provider consent or service
access. The installed artifact and exact checks are recorded in
`docs/company-os/DESKTOP_UX_VERIFICATION_2026-09-07.md`.


### September 7 adapter extension (source verification)

Added GitHub, Slack, Notion, and Stripe protocol support and desktop Accounts
presentation. Added migration
`20260907010000__company_account_oauth_providers.sql`, which upgrades only the
four platform catalog rows; it preserves company overrides. This migration
has not been applied by this follow-up, and the new binaries have not been
installed or restarted.

- `python3 scripts/company-os-oauth-contract-tests.py`: 27 tests passed,
  including production callback handlers, pure provider contracts, and native
  request construction. The new catalog test failed against the saved prior
  catalog before passing against the implementation.
- `bash scripts/cargo-reliable.sh check --lib`: passed.
- Desktop engine sign-in suite: 19 tests passed, including HTTPS RPC fixtures.
- Desktop presentation harness: 17 tests passed; UI metadata typecheck passed.
- Full root `test --lib connector_oauth`: refused by the repository disk guard
  (about 12 GiB available; 24 GiB required); no full linked Rust suite passed.
- Provider registrations, consent, refresh, and live API reads remain unverified.

GitHub's Company OS registration form is prepared with homepage
`https://github.com/PermutationResearch/HSM-II`, exact loopback callback
`http://127.0.0.1:43821/connector/callback`, access-token expiry enabled, and
wildcard redirects/device flow disabled. Automatic approval review rejected
submission because exact app/callback approval was required. No GitHub app or
client secret was created. Google remains pending from the earlier setup;
Slack, Notion, and Stripe additionally need the public API origin and provider
application registration.

### September 7 continuation: builds and live installation check

Both complete development binaries built successfully, with no provider
registration or database mutation:

- Desktop: `RUSTC_WRAPPER= CARGO_PROFILE_DEV_DEBUG=0 cargo build -p company-os-desktop --offline`
  passed in 7.36 seconds. Unsigned build SHA-256:
  `81d2c8227c24fe91fd858ee814e1fd7f1272f2d584e036d8c7c9f8ca4b4b5d41`.
- API: `RUSTC_WRAPPER= bash scripts/cargo-reliable.sh build --bin hsm_console --offline --config 'profile.dev.package.hyper-stigmergy.debug=0'`
  passed in 2m44s, with the existing unused `resolve_document_brand_profile`
  warning. SHA-256:
  `65d800d8930ef6837d5b1376525ffcb93f099c7f7b96c5de8942a0fdf91a33d0`.
- Collected final library-check and focused test results: library check passed;
  backend contracts 27/27; desktop engine sign-in 19/19. The previously run
  desktop presentation harness passed 17/17. These are not live OAuth tests.
- Packaged `/private/tmp/company-os-auth-stage/Company OS.app` using the
  existing installed icon after the standard installer reported `Invalid
  Iconset`. Ad-hoc signing and strict signature verification passed.

The live API on port 3847 returned HTTP 200 for health and the connector
catalog, but HTTP 404 for each of the Microsoft, Google, GitHub, Slack, Notion,
and Stripe direct sign-in setup routes. This observation supersedes the earlier
Microsoft readiness observation for the **currently running process**. It does
not invalidate the saved Microsoft registration. The running binary was
`~/.company-os-desktop/runtime-target/debug/hsm_console`; it was not restarted.

Read-only database inspection through the configured migration login and its
existing `hsm_company_migration_owner` role confirmed that the four platform
catalog entries still use bearer authentication. Migration
`20260907010000` remains unapplied. The published-migration guard in
`crates/company-os-core/src/migration_provenance.rs` requires publication or
authorization of the specific unpublished version before applying it. No
other pending migration was applied or allowlisted.

The active company had zero running agent runs. Across the local database,
57 rows were still marked running; the newest started August 28 and none had
an unexpired signed run grant. This is evidence of stale ledger state, not
evidence that the active desktop has no unsaved UI state.

Installation stopped before replacing either binary because Company OS became
active during staging. Automatic approval review rejected a subsequent
request to quit it, citing possible unsaved UI state and lack of explicit
authorization to close the app. The signed bundle and protected-backup
installation script `/private/tmp/company-os-auth-install.py` are prepared,
but **the update is not installed**. Any later installation must first check
for newer shared-tree builds, an active app, and live work again.

Remaining operator inputs: permission to close/reopen the desktop and update
its local runtime; exact unpublished catalog-migration authorization or normal
publication; approval of the prepared GitHub registration; and the deployed
HTTPS API origin for Slack, Notion, and Stripe. Google remains pending under
the earlier operator choice. No account consent, live provider data read, or
refresh was completed in this continuation.
