# Safety

<!-- BEGIN GENERATED: guide-map -->
## Guide map

Read only the section needed for the next decision. Line ranges are exact
for this generated runtime target.

| Section | What it covers | Lines |
| --- | --- | --- |
| [Purpose](#purpose) | Scope, ownership, and the central operating question. | 26-37 |
| [Use This When](#use-this-when) | Tasks and conditions that belong in this guide. | 38-51 |
| [Do Not Use This For](#do-not-use-this-for) | Boundaries and the correct routes for excluded work. | 52-64 |
| [Mental Model](#mental-model) | Concepts and state boundaries needed for correct operation. | 65-82 |
| [Preflight](#preflight) | State, authority, inputs, and readiness to verify before execution. | 83-98 |
| [Risk And Side Effects](#risk-and-side-effects) | Effects, approvals, cost, privacy, and retry boundaries. | 99-110 |
| [Core SDK Surfaces](#core-sdk-surfaces) | Exact source-backed callable contracts owned by this guide. | 111-121 |
| [Data Contracts](#data-contracts) | Required identities, fields, evidence, handles, and durable shapes. | 122-192 |
| [Canonical Operating Pattern](#canonical-operating-pattern) | The normal end-to-end execution sequence. | 193-209 |
| [Examples](#examples) | Representative usage patterns and code. | 210-235 |
| [Verification](#verification) | Evidence required before reporting success. | 236-250 |
| [Troubleshooting](#troubleshooting) | Failure diagnosis and corrective routes. | 251-261 |
| [Related References](#related-references) | Adjacent boundaries and when to cross them. | 262-274 |
<!-- END GENERATED: guide-map -->

## Purpose

Use this reference to decide what an agent may execute directly, what needs a
bounded preview first, and what must stop for explicit human approval. It is a
cross-cutting gate for credit spend, provider jobs, sequence/source staging,
sender/channel actions, direct sends, LinkedIn actions, destination-sync runs,
billing checkout, and mailbox-domain orders.

Safety is about the business outcome, not the function name. The user approves
"publish this sequence to 1,247 contacts with this sender and first send
window", not "call `vf.sequences.publish(...)`".

## Use This When

- A task may spend workspace credits, start provider/Growth jobs, mutate CRM or
  sequence state, stage rows into an audience source, publish/activate a
  sequence, send email or LinkedIn messages, create LinkedIn posts, change
  sender/mailbox state, or create billing/purchase intents.
- A workflow step declares or should declare effects such as `credit_usage`,
  `provider_job`, `artifact_job`, `sequence_stage`, `sequence_publish`,
  `direct_send`, `purchase`, or `browser_write`.
- You are unsure whether a platform write is evidence/sync only or a
  customer-visible mutation.
- You need to phrase an approval request so the user can understand the actual
  outcome and risk.

## Do Not Use This For

- Exact domain-specific call recipes. Use `sequences.md`,
  `sequence_authoring.md`, `mailboxes.md`, `billing.md`, `linkedin.md`,
  `enrichment.md`, `audience_discovery.md`, `host_browser.md`, and
  `workflow_operations.md` for the full operating details.
- Generic legal/compliance advice.
- Replacing SDK gates with environment variables or one-off local scripts.
- Treating every platform write as customer-visible. Workflow Play registration
  and Play sync write workspace code/evidence/history, but they do not execute
  providers, stage audience rows, publish sequences, send messages, or order
  mailboxes by themselves.

## Mental Model

There are five distinct safety boundaries:

| Boundary | What It Means | Normal Agent Behavior |
| --- | --- | --- |
| Local/read-only | Local row shaping, local reports, status reads, existing artifact inspection. | Execute directly after workspace/root checks. |
| Credit/provider job | Source, enrichment, qualification, web, LinkedIn research, host/provider jobs. | Preview input shape and limits; ask for approval before large, unclear, or costly runs. |
| Platform write for review | Draft creation, prompt updates, source staging, Play registration/sync, sender setup actions. | Execute when it is the requested operation and output is reviewable; print ids/counts/results. |
| Customer-visible mutation | Sequence publish/activation, direct email/LinkedIn sends, LinkedIn posts, CRM writes that users/customers will observe. | Require explicit approval immediately before the call. |
| Paid order or billing mutation | Stripe checkout, mailbox-domain order, paid sender/domain purchase flow. | Require explicit approval of price/quantity/outcome immediately before the call. |

Workflow Play sync is not sequence publish. It is a non-activation
evidence/history write from local workflow state into the workspace Play
library. It can be agent-owned by default after a terminal batch/production
run, but it must not be used to hide staging, publish, send, purchase, or
provider execution.

## Preflight

Before any non-local action:

1. Verify workspace identity from the workflow root or active SDK profile.
2. Print the relevant object ids: workspace/org, workflow root, run id, play id,
   job id, artifact id, sequence id, source node id, ingress id, sender id, or
   billing intent id.
3. Classify the operation using the risk table below.
4. For row operations, print input count, required columns, caps/limits, and a
   small sample or preview status.
5. For sequence/source changes, inspect the sequence draft/source state and run
   the relevant source/copy/filter preview first.
6. For billing/sender/channel actions, print the exact price/quantity/account
   or sender/channel outcome when the backend exposes it.

## Risk And Side Effects

| Operation Class | Examples | Gate |
| --- | --- | --- |
| Local transform/report | `vf.from_records(...)`, local `RowSet` transforms, `vf.reports.render(...)` | No approval; verify input/output shape. |
| Platform read | `vf.billing.status(...)`, `vf.senders.status(...)`, `vf.linkedin.status(...)`, `vf.sequences.get(...)` | No approval; preserve ids and status fields. |
| Credit/provider job | `vf.sources.*`, `vf.enrichment.*`, `vf.rows.ai_analyze(mode="qualify", ...)`, `vf.rows.ai_analyze(mode="enrich", ...)`, `vf.web.*`, LinkedIn research jobs | Preview shape/limits; approve large or ambiguous spend. |
| Reviewable platform write | `vf.sequences.create_draft(...)`, `vf.sequences.update_*`, `vf.sequences.stage_artifact_to_source(...)`, `vf.sequences.upload_csv_to_source(...)`, `vf.workflows.register(...)`, `vf.workflows.sync(...)` | Execute only when requested or workflow policy allows it; print diff/counts/result. |
| Direct customer-visible action | `vf.sequences.publish(...)`, `vf.email.send(...)`, `vf.linkedin.send_connect(...)`, `vf.linkedin.send_message(...)`, `vf.linkedin.create_post(...)` | Explicit user approval immediately before invocation. |
| Provider-writing destination sync | `vf.sequences.run_destination_sync(...)`, `vf.sequences.run_destination_sync_schedule_now(...)` | Explicit user approval immediately before invocation; print sequence id, provider, selected/all-audience scope, force-create setting, and expected HubSpot object/timeline effect. |
| Sender/account/billing mutation | `vf.senders.create(...)`, mailbox connection flows, sender account moves, `vf.billing.checkout_url(...)`, `vf.domains.create_mailbox_order(...)` | Confirm the human-visible account/billing outcome; mailbox connection and paid order/checkout require explicit approval immediately before the call. |

## Core SDK Surfaces

<!-- BEGIN GENERATED: core-sdk-surfaces -->
| Function | Use When | Inputs | Output And Handles To Preserve | Side Effect | Source |
| --- | --- | --- | --- | --- | --- |
| `vf.email.send` | Queue a one-off email send through Bridge. | <strong>Required</strong>: `body_text` (str; required): Plain-text email body.; `subject` (str; required): Email subject or copy subject line.; `to_email` (str; required): To email parameter for `email.send`.<br><strong>Optional</strong>: `bcc` (list[str] \| None; optional; default `None`): Bcc parameter for `email.send`.; `cc` (list[str] \| None; optional; default `None`): Cc parameter for `email.send`.; `from_sender_id` (str \| None; optional; default `None`): Sender/mailbox id that should send the email.; `metadata` (JsonObject \| None; optional; default `None`): Metadata parameter for `email.send`.; `reply_to` (str \| None; optional; default `None`): Reply to parameter for `email.send`. | `dict` with outbound send/status result and provider intent metadata.<br><strong>Common next calls</strong>: `vf.email.status` | platform_write | installed SDK: `verbiflow/email.py:14-50` |
| `vf.email.status` | Read a one-off send status through Bridge. | <strong>Required</strong>: `intent_id` (str; required): Outbound send intent id returned by a previous email or LinkedIn send call.<br><strong>Optional</strong>: none | `dict` with outbound send/status result and provider intent metadata. | read | installed SDK: `verbiflow/email.py:52-64` |
| `vf.operations.cancel` | Cancel a current-run operation only before handler dispatch. | <strong>Required</strong>: `invocation_id` (str; required): Invocation ID of an operation owned by the current hosted run.<br><strong>Optional</strong>: none | `sdk_operation` payload returned by the SDK operation. Inspect the documented function reference and `raw`/dictionary fields when debugging provider-specific data.<br><strong>Common next calls</strong>: `vf.operations.get` | platform_write | installed SDK: `verbiflow/operations.py:89-114` |
| `vf.operations.get` | Read one operation owned by the current hosted run. | <strong>Required</strong>: `invocation_id` (str; required): Invocation ID exposed by an SDK response or ApiError.<br><strong>Optional</strong>: none | `sdk_operation` payload returned by the SDK operation. Inspect the documented function reference and `raw`/dictionary fields when debugging provider-specific data.<br><strong>Common next calls</strong>: `vf.operations.cancel` | read | installed SDK: `verbiflow/operations.py:62-87` |
<!-- END GENERATED: core-sdk-surfaces -->

## Data Contracts

### Approval Payloads

Approval prompts must include the business outcome and the ids needed to audit
the action:

- workspace/org identity;
- sequence/source/sender/channel ids when relevant;
- audience row count and sample or source run id for activation;
- first send window or schedule when relevant;
- price/quantity/domain/mailbox count for billing or purchase flows;
- copy/filter/source version or draft version when publishing sequence work;
- idempotency key or workflow claim key when a workflow side effect is being
  executed.

### Workflow Effects

Generated workflows should declare effects on steps and call
`ctx.require_effects(...)` before work that spends credits or mutates state.
Use these effect labels consistently:

- `provider_job`, `artifact_job`, `credit_usage`, `enrichment` for jobs that
  consume provider/Growth resources;
- `sequence_stage` for staging/promoting rows into a sequence source;
- `sequence_publish` for publish/activation;
- `direct_send` for one-off email or LinkedIn sends;
- `purchase` for checkout/mailbox/domain order flows;
- `browser_write` for host/browser actions that change provider state.

Preview mode must not perform customer-visible mutations. Batch/production can
stage rows only when the workflow declares the effect, uses idempotency/claims,
and has the correct mode/policy.

### Claims And Idempotency

For provider mutations that are not naturally idempotent, acquire a workflow
claim before the call and complete or fail it after the provider boundary. Do
not replace this with `SEND_FOR_REAL` or another environment flag.

Prefer purpose-built helpers when they exist. For example,
`ctx.stage_rows_to_sequence_source(...)` handles staging idempotency and
workflow operation recording. For custom side effects, use the claim lifecycle
explicitly:

```python
ctx.require_effects("direct_send")
ctx.confirm_required(
    "Approve sending one follow-up email to Ada Lovelace from sender_123.",
    approval_key="send:ada-lovelace:sender_123",
    sender_id="sender_123",
    recipient="ada@example.com",
)
claim = ctx.claim_once("direct_send", "ada@example.com:sender_123")
if not claim.get("acquired"):
    return {"skipped": True, "claim": claim}
claim_token = str(claim["claim_token"])
try:
    result = vf.email.send(
        to_email="ada@example.com",
        subject="Following up",
        body_text="...",
        from_sender_id="sender_123",
        metadata={"workflow_run_id": ctx.run_id},
    )
    ctx.complete_claim("direct_send", "ada@example.com:sender_123", claim_token=claim_token, result=result)
except Exception as exc:
    ctx.fail_claim("direct_send", "ada@example.com:sender_123", claim_token=claim_token, error=str(exc))
    raise
```

## Canonical Operating Pattern

1. Identify the operation class and read the narrower domain reference.
2. Verify execution authority, project root, and provider readiness.
3. For credit/provider jobs, preview one bounded sample or source preview when
   shape is uncertain; estimate row count/cost when available.
4. For sequence work, run source preview, AI-filter preview when applicable,
   variable coverage, and copy verification before staging or publish.
5. For staging/reviewable writes, print the staged row count, source run id,
   lead list/import id, or draft id.
6. For publish/send/post/purchase, stop and ask for explicit approval using
   the user-visible outcome.
7. Execute exactly the approved mutation, preserve ids, and immediately report
   the result.
8. For workflows, run Play sync after staging/promotion so `/plays`
   reflects the side effect result.

## Examples

### Good Approval Copy

```text
About to publish sequence "Q3 fintech CTOs" in the current workspace.
Audience: 1,247 contacts from source_run_id=sr_123, sender ada@company.com,
first send window 2026-06-17 09:00 PT. Proceed?
```

This is better than asking whether to call `vf.sequences.publish(...)`, because
the user is approving the customer-visible outcome.

### Workflow Play Sync Is Not Activation

```python
result = vf.workflows.sync(
    "/absolute/path/to/workflow",
    run_id="wf_run_123",
)
print(result["play_id"], result["code"]["status"], result["outputs"])
```

This writes workspace Play evidence/history. It does not run providers, stage
rows, publish sequences, send messages, or purchase anything.

## Verification

After a gated operation, verify the same surface that owns the mutation:

- provider/Growth job: preserve `job_id` and `artifact_id`, then inspect job
  status and artifact rows;
- sequence staging: print `source_run_id`, promoted/import ids, staged row
  count, and suppression count when available;
- sequence publish: fetch sequence detail and confirm published/active state;
- direct send or LinkedIn send: preserve `intent_id` and check send status;
- LinkedIn post: preserve post id/url/status from the returned payload;
- sender/mailbox/billing: fetch sender/billing/domain status after the call;
- workflow Play sync: fetch `/plays` or use workflow operation output to verify
  registered play id, run id, snapshot state, and output bundle status.

## Troubleshooting

| Symptom | Likely Cause | What To Do |
| --- | --- | --- |
| User approved one mutation but a helper wants to perform several | Hidden bundled side effects | Split into separate visible calls and approval prompts. |
| Preview mode staged or sent rows | Workflow step declared/executed a customer-visible effect in preview | Fix workflow mode/effects and reset/replay the step. |
| Agent is about to rerun a direct send after crash | Missing claim/idempotency boundary | Inspect workflow claims/provider status before retrying. |
| `dry_run` is suggested for a provider that does not document it | Stale guidance or provider mismatch | Use sandbox/status/preview surfaces from the owning reference; do not invent dry-run flags. |
| Play history synced but sequence audience did not change | Confusing Play sync with staging/publish | Use sequence staging/publish surfaces explicitly, then run Play sync or confirm the Local daemon has synced completed evidence. |
| Billing or mailbox order is unclear | Missing quote/price/quantity | Stop and get a quote/status from billing/domain refs before checkout/order. |

## Related References

| If you are doing this | Read next |
| --- | --- |
| Sequence draft/source/filter/copy/stage/publish | `sequence_authoring.md`, `sequences.md` |
| Sender setup, mailbox connection, sender matching | `senders.md` |
| Billing, plan, checkout, or credit state | `billing.md` |
| Sending-domain search, mailbox readiness, or mailbox-domain order | `mailboxes.md` |
| LinkedIn messages, connection requests, posts, research | `linkedin.md` |
| Provider/source jobs and audience discovery | `audience_discovery.md`, `sources.md` |
| Enrichment jobs and resolver field mapping | `enrichment.md` |
| Workflow modes, effects, claims, or Play sync | `workflow_operations.md`, then `workflow_schedules.md` |
| Data-quality checks before advancing | `testing.md` |
