# Sandbox and TTFC

Use a disposable HollyHR tenant when testing integrations. HollyHR does not use
one shared global sandbox credential: API keys are tenant-scoped, and the safer
model is a seeded synthetic tenant per reviewer, partner, or trial.

This is the intended product pattern for in-app exploration too: learn inside a
made-up company with realistic departments, people, managers, time off,
documents, and notifications, then switch to a clean real org before using real
company records.

## Five-minute smoke

Set an API key with at least `organisation:read`, `people:read`,
`reference:read`, and `time_off:read`:

```bash
export HOLLYHR_API_BASE_URL="https://{workspace}.hollyhr.com/api/v1"
export HOLLYHR_API_TOKEN="hhr_live_..."
export HOLLYHR_MCP_URL="https://{workspace}.hollyhr.com/api/mcp"
export HOLLYHR_MCP_TOKEN="$HOLLYHR_API_TOKEN"
```

Then run:

```bash
pnpm developer:ttfc:smoke
```

The smoke checks:

- `GET /me`
- `GET /people?limit=1`, including a non-empty seeded-data assertion by default
- `GET /metadata`
- MCP protected-resource metadata
- unauthenticated MCP `WWW-Authenticate` discovery
- authenticated `GET /api/mcp` returning `405 Method Not Allowed`
- MCP SDK initialization, `tools/list`, and `whoami`
- request IDs and rate-limit headers where the live endpoint returns them

The command fails if the full path takes longer than 300 seconds. Override that
only when debugging a known network or client issue:

```bash
HOLLYHR_TTFC_MAX_SECONDS=600 pnpm developer:ttfc:smoke
```

If a hosted smoke fails unexpectedly, check `https://status.hollyhr.com` before
rotating credentials or reseeding the sandbox.

If you are deliberately testing an empty tenant, allow empty people results
explicitly:

```bash
HOLLYHR_TTFC_ALLOW_EMPTY=1 pnpm developer:ttfc:smoke
```

For directory, aggregator, or internal launch evidence, write the smoke result
to JSON:

```bash
HOLLYHR_TTFC_OUTPUT_PATH="./ttfc-evidence.json" pnpm developer:ttfc:smoke
```

For a complete directory-review bundle, pair this smoke output with
[Reviewer demo guide](/reviewer-demo-handoff). The handoff manifest is the
non-secret evidence file; the generated `.env` file is the secret credential
bundle.

If the tenant is expected to advertise OAuth, make that expectation explicit so
the smoke fails while `authorization_servers` is still empty:

```bash
HOLLYHR_TTFC_EXPECT_OAUTH=1 \
HOLLYHR_TTFC_EXPECT_AUTHORIZATION_SERVER="https://<authkit-domain>" \
HOLLYHR_TTFC_OUTPUT_PATH="./ttfc-evidence.json" \
pnpm developer:ttfc:smoke
```

## Scenario seed

For local and internal demo environments, HollyHR includes a beta-confidence
scenario seed with synthetic UK SME data:

```bash
pnpm seed:scenario:beta
```

For developer-platform demos, use the higher-level provisioning command. It
reuses the scenario seed, returns endpoint evidence, and can mint a reveal-once
API key for TTFC:

```bash
HOLLYHR_DATABASE_TARGET_PROOF="postgresql://..." \
pnpm developer:demo:provision -- --create-api-key --output-json=./demo-tenant.json --output-env=./demo-tenant.env
```

When creating credentials for a production-hosted reviewer tenant, pull Vercel
Production env first and derive `HOLLYHR_DATABASE_TARGET_PROOF` from that env
file. Do not assume local `.env` points at the same Neon endpoint as the
deployed app.

When `--output-env` is present, the JSON output redacts the API token and stores
the token only in the mode-`0600` env file. Use `--reveal-token-in-json` only
for an interactive reveal-once flow where the JSON file itself is treated as a
secret.

For a directory reviewer or sales/demo handoff, mint browser login links into
the same secret env file:

```bash
HOLLYHR_DATABASE_TARGET_PROOF="postgresql://..." \
HOLLYHR_ALLOW_DEVELOPER_DEMO_LOGIN_LINKS=1 \
pnpm developer:demo:provision -- \
  --create-api-key \
  --create-login-links \
  --login-personas=hrAdmin,manager,directReport \
  --output-json=./demo-tenant.json \
  --output-env=./demo-tenant.env
```

The JSON evidence redacts login URLs by default. The env file contains
single-use `HOLLYHR_REVIEWER_LOGIN_*` links plus the API/MCP token. Treat the
env file as a secret, and refresh it before an external review window.
The JSON output now includes a `reviewer_handoff` block with the concrete MCP
endpoint, endpoint template, safe starter prompts, login expectations, and TTFC
command to copy into the submission pack.

Then run the smoke against the generated env file:

```bash
dotenv -e ./demo-tenant.env -- sh -c 'HOLLYHR_TTFC_OUTPUT_PATH=./ttfc-evidence.json pnpm developer:ttfc:smoke'
```

To reset the scenario, prove the target database first and explicitly
acknowledge the destructive operation:

```bash
pnpm db:target

HOLLYHR_DATABASE_TARGET_PROOF="postgresql://..." \
HOLLYHR_ALLOW_DEVELOPER_DEMO_RESET=1 \
pnpm developer:demo:provision -- --reset --create-api-key --output-env=./demo-tenant.env
```

Never run the reset against a real customer tenant. The seed is intended for
local, preview, disposable demo, and reviewer-demo environments.

The reviewer/demo tenant should include at least one person, one manager
relationship, departments or teams, one public holiday set, and at least one
time-off record so API, SDK, and MCP smoke runs prove real HR workflows rather
than only authentication.

Use `--include-write-scopes` only for a tenant where writes are explicitly part
of the demo. The key may include `mcp:write`, but MCP commit still fails unless
the server-side write-mode gate is enabled and the host supports form
elicitation.

## Demo orgs versus real orgs

Do not seed fake people into a customer's real tenant. The safe onboarding model
is a separate demo org with its own `orgId`, seeded from the same synthetic
fixtures as the sandbox. A customer can explore that made-up company, then
switch to their real empty org and import real employees. "Start fresh" deletes
or abandons the demo org; it never promotes sample rows into production data.

For per-customer demo clones, pass a unique subdomain:

```bash
HOLLYHR_DATABASE_TARGET_PROOF="postgresql://..." \
pnpm developer:demo:provision -- \
  --subdomain=demo-acme \
  --lifecycle=separate-demo-org \
  --create-api-key \
  --output-json=./demo-acme.json \
  --output-env=./demo-acme.env
```

Non-default scenario subdomains get clone-specific synthetic email addresses, so
multiple demo orgs can coexist without sharing users or reset scope.

## Writes

REST writes require idempotency keys and, for conditional updates, ETags. MCP
writes also require `mcp:write`, `HOLLYHR_MCP_WRITE_MODE=enabled`, and host form
elicitation. Keep write tests in disposable tenants until your DPIA/DPO approval
and tenant rollout plan are complete.
