> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gavai.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Data sensitivity

> How fields are tagged, redacted, and gated so sensitive values do not appear in surfaces that are not authorized for them.

export const PageMeta = ({audience, readingTime, prereqs, level}) => {
  const items = [audience && ({
    key: "audience",
    label: "AUDIENCE",
    value: audience
  }), readingTime && ({
    key: "time",
    label: "READ",
    value: readingTime
  }), level && ({
    key: "level",
    label: "LEVEL",
    value: level
  }), prereqs && prereqs.length > 0 && ({
    key: "prereqs",
    label: "PREREQS",
    value: prereqs.join(" · ")
  })].filter(Boolean);
  if (items.length === 0) return null;
  return <div className="gv-page-meta" role="group" aria-label="Page metadata">
      {items.map(item => <div className="gv-page-meta__item" key={item.key}>
          <span className="gv-page-meta__label">{item.label}</span>
          <span className="gv-page-meta__value">{item.value}</span>
        </div>)}
    </div>;
};

export const Def = ({term, type, defaultValue, required, children}) => <>
    <dt className="gv-def-list__term">
      <span className="gv-def-list__name">{term}</span>
      {type && <span className="gv-def-list__type">{type}</span>}
      {required && <span className="gv-def-list__required">required</span>}
      {defaultValue !== undefined && <span className="gv-def-list__default">
          default <code>{defaultValue}</code>
        </span>}
    </dt>
    <dd className="gv-def-list__description">{children}</dd>
  </>;

export const DefList = ({children}) => <dl className="gv-def-list">{children}</dl>;

export const ArrowItem = ({children}) => <li className="gv-arrow-item">
    <span className="gv-arrow-item__arrow" aria-hidden="true">{"↳︎"}</span>
    <span className="gv-arrow-item__content">{children}</span>
  </li>;

export const ArrowList = ({children}) => <ul className="gv-arrow-list">{children}</ul>;

export const BrandCard = ({eyebrow, title, href, icon, badge, cta, featured = false, horizontal = false, children}) => {
  const classes = ["gv-card", featured && "gv-card--featured", horizontal && "gv-card--horizontal"].filter(Boolean).join(" ");
  const inner = <>
      {icon && <span className="gv-card__icon" aria-hidden="true">
          <Icon icon={icon} />
        </span>}
      <div className="gv-card__main">
        {(eyebrow || badge) && <div className="gv-card__meta">
            {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
            {badge && <span className="gv-card__badge">{badge}</span>}
          </div>}
        <h3 className="gv-card__title">{title}</h3>
        {children && <div className="gv-card__body">{children}</div>}
        {cta && <span className="gv-card__cta">
            {cta}
            <svg className="gv-card__cta-arrow" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <line x1="7" y1="17" x2="17" y2="7" />
              <polyline points="7 7 17 7 17 17" />
            </svg>
          </span>}
      </div>
    </>;
  return href ? <a className={classes} href={href}>
      {inner}
    </a> : <div className={classes}>{inner}</div>;
};

export const FeatureTable = ({columns, rows, featuredRow, caption, variant}) => <div className={`gv-feature-table ${variant ? `gv-feature-table--${variant}` : ""}`}>
    <table>
      {caption && <caption className="gv-feature-table__caption">{caption}</caption>}
      <thead>
        <tr>
          {columns.map(col => <th key={col.key} scope="col">
              <span className="gv-feature-table__header">{col.label}</span>
            </th>)}
        </tr>
      </thead>
      <tbody>
        {rows.map((row, i) => {
  const rowClass = [i === featuredRow ? "gv-feature-table__row--featured" : "", row.chosen ? "gv-feature-table__row--chosen" : ""].filter(Boolean).join(" ");
  return <tr key={i} className={rowClass}>
              {columns.map(col => {
    const v = row[col.key];
    if (variant === "tradeoff" && Array.isArray(v)) {
      const pill = col.key === "pros" ? "gv-feature-table__pill--pro" : col.key === "cons" ? "gv-feature-table__pill--con" : "";
      return <td key={col.key}>
                      {v.map((item, j) => <span key={j} className={`gv-feature-table__pill ${pill}`}>{item}</span>)}
                    </td>;
    }
    return <td key={col.key}>{v}</td>;
  })}
            </tr>;
})}
      </tbody>
    </table>
  </div>;

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="SECURITY" />

<PageMeta audience="Developers, security reviewers" readingTime="6 min" level="Reference" />

A workspace's schema can hold the kind of data that needs careful handling — emails, phone numbers, payment metadata, contractual details. The platform supports this with a single piece of declarative metadata on each field: a **sensitivity tag**. Tags propagate through the runtime, the rendering pipeline, and the audit trail; they decide what shows up in a log line, what reaches a third-party capability, and what a page block is allowed to render. By the end of this page you will know what the tags are, where they apply, and how to choose the right one when you declare a field.

## A field with a sensitivity tag

You add a `sensitivity` value to a field declaration on the workspace schema. Everything else — redaction in logs, masking in rendered pages, allow-listing on outbound capability calls — falls out of the tag.

```json schema.json theme={null}
{
  "customers": {
    "fields": {
      "id":       { "type": "uuid",   "primary": true },
      "name":     { "type": "string", "sensitivity": "personal" },
      "email":    { "type": "string", "sensitivity": "contact" },
      "ssn_last4":{ "type": "string", "sensitivity": "restricted" },
      "notes":    { "type": "string" }
    }
  }
}
```

`name` is treated as personal data; `email` is contact-class; `ssn_last4` is restricted. `notes` has no tag and is treated as public-by-default — the platform does not infer sensitivity from field names because the same name means different things in different schemas.

## The tag set

Five tags exist. They are ordered by handling strictness; the strictest tag a value carries is the one that wins when a value is composed from several fields.

<FeatureTable
  caption="Sensitivity tag ladder"
  featuredRow={3}
  columns={[
{ key: "tag", label: "Tag" },
{ key: "examples", label: "Typical fields" },
{ key: "default_handling", label: "Default handling" }
]}
  rows={[
{
  tag: "`public`",
  examples: "Product names, public titles, public URLs",
  default_handling: "No redaction. May appear in any log, render anywhere, ship to any capability."
},
{
  tag: "`personal`",
  examples: "Full name, display name, avatar URL",
  default_handling: "Redacted in error logs. Rendered in pages. Allowed to outbound capabilities that opt in."
},
{
  tag: "`contact`",
  examples: "Email address, phone number, postal address",
  default_handling: "Redacted in all log surfaces (errors, request logs, runtime traces). Rendered only in blocks declared as contact-aware. Passed to capabilities that declare a contact channel as their purpose."
},
{
  tag: "`financial`",
  examples: "Order total, plan price, payout amount, payment method last4",
  default_handling: "Redacted across logs. Rendered in pages bound to the workspace owner or end-user-of-record. Passed only to payment-class capabilities."
},
{
  tag: "`restricted`",
  examples: "Government identifiers, health information, contract clauses",
  default_handling: "Never appears in logs, ever. Never sent to a non-payment capability. Reads are audited per-field, not per-record."
}
]}
/>

The tags are a *contract*, not just a hint. The runtime enforces the handling described in the right column; you cannot opt a `restricted` field into a log line by setting a flag elsewhere.

## Where the tag is enforced

Sensitivity is checked at four boundaries. Each one inspects the tag and applies the right action without the page author or the developer having to remember.

<ArrowList>
  <ArrowItem>**At read time.** When a block or a capability reads a row, the runtime annotates each field with its tag. The same record can flow through different surfaces and arrive with the correct masking at each one.</ArrowItem>
  <ArrowItem>**At render time.** The rendering pipeline applies the block's declared sensitivity policy against the field's tag. A Table block that has not opted into contact-class fields renders `email` as a redacted token (`a*****@example.com`) rather than the literal value.</ArrowItem>
  <ArrowItem>**At capability dispatch.** Each capability declares the sensitivity classes it is allowed to receive. A call that would pass a `restricted` field to the email gateway fails at validation, not at the network boundary — `restricted` is not on the gateway's allow-list.</ArrowItem>
  <ArrowItem>**At log emission.** Every log line passes through a tag-aware redactor before it is written. A `contact` value that ends up in an error trace is masked before the trace is stored; the raw value never lands on disk.</ArrowItem>
</ArrowList>

## How redaction looks

The redactor preserves enough shape for an operator to recognize the field type while removing the value. Examples (for `contact`):

```text theme={null}
email     →  a*****@example.com
phone     →  +1 (***) ***-1234
postal    →  *** *** Street, Springfield, **
```

For `restricted`, the redaction is total — the value is replaced with `<redacted:restricted>` and no shape is preserved. The redactor does not produce a deterministic token; the same value redacted twice produces the same masked output only when the field type is what determines the mask, not the underlying value.

## End-user sessions and tag scope

A signed-in end user can see *their own* `contact` and `financial` fields. The runtime resolves the calling end-user's identity from the session and matches it against ownership claims on the record. A row owned by user A renders A's email; the same row read in B's session renders the masked form.

The tag set is not affected by who is reading — `restricted` is restricted for everyone. What changes per session is whether the *masking* applies. A workspace owner reading a customer's record sees the redacted form on a default block; the end user reading their own record sees the unmasked value because the block resolves the ownership claim.

## Tag composition

When a value is composed from several fields — a Table block rendering a row, an email template interpolating columns — the strictest tag in the composition wins. A row that contains one `restricted` field is treated as `restricted` as a whole for the purpose of decisions made *about that composition*. Individual fields keep their own tags for purposes of how they render and where they ship.

This rule prevents accidentally exposing a sensitive value by burying it inside an otherwise-public surface. If you want to render a row publicly, the row must not contain a sensitive field, full stop.

## Declaring a block's sensitivity policy

Blocks declare what tags they handle. The declaration is opt-in: a block that does not opt into `contact` will not render any contact-tagged field. The runtime does not silently auto-render across the boundary.

<DefList>
  <Def term="renders_tags" type="enum[]">
    The tags this block is permitted to render. Anything outside the list is masked.
  </Def>

  <Def term="end_user_only_tags" type="enum[]">
    Tags that render only when the calling end user owns the record. For workspace-owner readers, these masks down to the public form regardless.
  </Def>
</DefList>

The defaults are conservative: a fresh block renders only `public` and `personal`. You opt into stricter tags explicitly.

## Reading sensitivity on a record from the API

When you read a record through the API, the response carries the redacted form by default. Pass `?reveal=contact,financial` on the query (with a token that holds the right scope) to receive the underlying values. The reveal is logged to the audit chain by field — the platform tracks not just that the row was read, but which sensitive fields were unmasked.

## When to tag (and when not to)

Tag a field as the strictest level its content might *ever* hold. Tag escalation is hard — you have to re-decorate every block that touches the field. Tag relaxation is cheap — you change the schema and the platform stops applying the stricter handling. Errors on the strict side are recoverable; errors on the lax side aren't.

Don't tag fields that are derived from other fields. A computed value inherits the strictest tag of its inputs at evaluation time; tagging the derivation separately is redundant and creates two places to keep in sync.

## Related

<Columns cols={2}>
  <BrandCard eyebrow="CONCEPT" title="Schema" href="/concepts/schema" icon="table">
    Where field tags are declared and how they travel with the schema across surfaces.
  </BrandCard>

  <BrandCard eyebrow="SECURITY" title="Multi-tenant isolation" href="/security/multi-tenant-isolation" icon="building-shield">
    The boundaries between workspaces that sit above per-field handling.
  </BrandCard>

  <BrandCard eyebrow="REFERENCE" title="Secrets" href="/api-reference/resources/secrets" icon="key">
    For credentials and signing keys — values the workspace never reads back at all.
  </BrandCard>

  <BrandCard eyebrow="SECURITY" title="Reporting vulnerabilities" href="/security/disclosure" icon="shield-exclamation">
    If you find a way around a tag's enforcement, this is the page to read.
  </BrandCard>
</Columns>
