peek supports IAM testing environments: isolated worlds with their own identities, orgs, Ting types and data, driven by peek's test app secret. A testing environment uses exactly the same code path as production; only the credentials and the storage partition differ. Nothing in a test world can reach production data, and peek never falls back to production when a test credential fails.
There is also Simulation in Peek.app, which is different: local fake data, no IAM, no Ting, no network. Use Simulation to see what bubbles look like; use a testing environment to exercise the full login, send, answer and delivery loop.
Use a testing environment#
You need peek's test app secret for the environment (an ask_… value from your operator) and a test Silicon's SLT or, in a test plane only, its public id.
H=/abs/path/to/test-silicon-home
# 1. log in with the test secret; peek discovers and saves the environment
printf %s "$PEEK_TEST_SECRET" | SILICON_HOME=$H SILICON_ORG=tos peek --app-secret-file - login si:peek-tester
# stderr ends with: Testing environment: peek testing (0192…-uuid)
# 2. from then on, select it by id
SILICON_HOME=$H peek --test 0192…-uuid register side 3
SILICON_HOME=$H peek --test 0192…-uuid register drawing ./logo.js
SILICON_HOME=$H ISI=deliberate peek --test 0192…-uuid send --json \
--ask '{"question":"Ship it?","type":"single_choice","options":["Ship","Hold"]}'
The bubble shows a TEST · peek testing pill and a dashed ring. Answer it in Peek.app, and the test Silicon's webhook receives peek.ask.answered from the test Ting.
| Flag / env | Meaning |
|---|---|
--test <ENV_UUID> / SILICON_PEEK_TEST | Select a saved environment. A raw secret here is refused with the hint "use --app-secret-file -". |
--app-secret-file <PATH|-> | peek's test app secret; - reads one line from stdin. |
--app-secret <ask_…> / PEEK_TEST_APP_SECRET | The same, inline (hidden in help). Conflicts with --app-secret-file. |
- When you pass a secret, peek calls
GET /api/v1/iamwith it, learnstesting_environment_idand the current generation, and saves them to$SILICON_HOME/.peek/testing.json(0600). - The session for a test world is stored under its own key,
"<api_url>#<environment uuid>", next to (never instead of) your production session. - When testing is selected, the last line on stderr is always
Testing environment: <name> (<uuid>), on success and on failure. stdout stays pure JSON. - peek never reads
IAM_TEST_APP_SECRETorIAM_TEST_KEY. In a Silicon's environment those are Ting's test credentials, and sending them to peek's backend would fail. peek iam --json --test <uuid>adds"testing":{"environment_id","name","generation"}.peek reportis refused under--test.
When the world changes#
A testing environment can be cleaned, rotated or rebuilt. peek then answers with a precise error and never retries against production:
| Code | Exit | Meaning | Fix |
|---|---|---|---|
testing_secret_invalid | 3 | the secret is not a valid peek test secret, or was rotated | get the current secret; peek --app-secret-file - login … |
testing_generation_changed | 3 | the environment was cleaned or rebuilt since you logged in | peek --test <uuid> login status, then log in again |
slt_rejected | 3 | the test identity no longer exists | recreate it (operator), then log in |
After a clean, queued events and local history for the old generation are dropped.
How isolation works#
| Layer | Partition |
|---|---|
| CLI store | one session slot per "<api_url>#<context>"; context is production or the environment UUID |
| peekd (the Mac helper) | every row in its database is keyed by context; si:dj in production and si:dj in a test world are different identities, with separate positions, drawings, asks and deliveries |
| Peek.app | one physical set of 8 positions. Production has priority: a test registration of a position production holds succeeds in the test partition, but its bubble waits while a production bubble is there. Test bubbles carry a TEST · <name> pill, a dashed ring and a tooltip with the UUID and generation; drawings get input.context === 'testing'. Settings → Testing has "Show test peeks" (on by default) and lists the environments in use. |
| peek backend | every table has a ctx column, resolved from the validated secret and never from client input; test rows live in a separate database file |
| Ting | types, grants and hooks exist per context; a clean wipes them |
| Telemetry | test events are tagged environment:"testing" and never written to the production tables |
| Speech | test contexts use a separate Deepgram project key (PEEK_DEEPGRAM_TEST_API_KEY), and requests are tagged testing |
Headers on each hop#
The same header name carries different secrets on different hops, so peek never forwards headers blindly:
| Hop | Headers |
|---|---|
| CLI / peekd → peek backend | X-Testing-Environment-Key: <peek test secret> on every route; X-Testing-Environment-Generation: <n> on every mutation except login and refresh (missing or stale → 409 testing_generation_changed) |
| peek backend → IAM | the IAM SDK's testing application mode: X-Testing-Application: Basic … plus Basic auth with peek's test secret |
| peek backend → Ting | IAM_TEST_APP_SECRET and X-Testing-Environment-Key taken from that proof's testing_context, never from the inbound request |
Provision an environment (operators)#
peek must be registered as a Honeycomb lifecycle participant before it is imported into any environment. An app that is not a participant leaves the environment permanently pending. The Honeycomb operator adds {"app_id":"peek","base_url":"https://backend.peek.teamofsilicons.com","token_env":"PEEK_HONEYCOMB_SERVICE_TOKEN"} to HONEYCOMB_LIFECYCLE_PARTICIPANTS.
Then, in order (every step changes remote state):
- Create the environment and fetch its root key:
honeycomb --json environments create tos 'peek testing' --description '…' # → ENV honeycomb --json environments key "$ENV" # → ROOT - Before the import, create a test Carbon, the test org
tosowned by it, and a test Silicon (the test plane's OTP is000000):
Otherwise IAM creates testiam --test $ENV signup --carbon-id peek-admin … iam --test $ENV login … iam --test $ENV org create tos iam --test $ENV -o json silicon create peek-tester …tosowned by a non-login fixture Carbon, and nobody can rotate secrets or register Ting types. - Import peek. The import follows peek's external scopes recursively (peek → ting → honeycomb → briefcase); require every
services[].state == "ready".honeycomb --json environments import "$ENV" 'peek' --revision R - Get the test secrets. A Carbon-driven import prints none, so rotate them:
SLT=$(iam --test $ENV -o json login --app-id honeycomb --grant-org tos --approve-scopes | jq -r .slt) honeycomb --test $ENV login "$SLT" honeycomb --test $ENV --json apps rotate-secret 'peek' --revision RP # .app_secret = PEEK_TS honeycomb --test $ENV --json apps rotate-secret 'ting' --revision RT # .app_secret = TING_TS - Register the six Ting types in the test context, with descriptions byte-identical to production (Ting events), using a test Ting session (
IAM_TEST_APP_SECRET=$TING_TS IAM_TEST_KEY=$ROOT ting login --token-stdin). - Log the test Silicon in to peek:
printf %s "$PEEK_TS" | SILICON_HOME=$H SILICON_ORG=tos peek --app-secret-file - login si:peek-tester. - Give the test Silicon a Ting webhook (
ting login,ting org use tos,ting webhook http://127.0.0.1:18777/events) and run the loop above.
After honeycomb environments action $ENV clean, repeat steps 2 to 7. The repository ships scripts/testing/bootstrap.sh, which does this idempotently.
Local tests without any environment#
The Rust crates are tested against local fakes only: wiremock stands in for the peek backend, IAM, Ting and Deepgram, and every test uses a temporary SILICON_HOME and a private helper socket (PEEK_DAEMON_SOCKET). No test needs a key or touches your real ~/.peek or ~/Library/Application Support/Peek. To run the real binaries by hand against a local peek-server, use the isolated run mode: PEEK_SUPPORT_DIR, PEEK_CACHES_DIR, PEEK_DAEMON_SOCKET, PEEK_NO_SERVICES=1 and PEEK_API_URL keep peekd and Peek.app away from your real data, login items and launch agents.