> ## 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.

# Predictable by design

> Why arbitrary code can't be verified to be safe and why gavAI compresses an app's surface down to typed JSON that can be.

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 PullQuote = ({children}) => <p className="gv-pull-quote">
    <span className="gv-pull-quote__mark" aria-hidden="true">“</span>
    <span className="gv-pull-quote__body">{children}</span>
  </p>;

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 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 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="CONCEPT" />

<PageMeta audience="Developers, technical buyers, security reviewers" readingTime="7 min" level="Concepts" />

The promise of AI-assisted development is that anyone can ship a working app. The reality of running one in production is that "working in the demo" and "safe to charge a credit card on" are not the same thing. The gap between them is the central problem this page is about. By the end you will know why arbitrary code resists deterministic safety checks, why that gap widens when an AI writes the code, and how gavAI's design — strongly-typed JSON with per-component validation — collapses an app's surface to something exhaustive checks can actually cover.

## The verification problem with arbitrary code

A program written as code — even a small one — has an effectively unbounded state space. Consider what determines whether a single endpoint behaves correctly:

<ArrowList>
  <ArrowItem>**Input space.** Every string field has an infinity of possible values. Every numeric field has a range plus the edge cases at its boundaries. Every optional field is the cross product of present and absent.</ArrowItem>
  <ArrowItem>**Code paths.** Each branch, each retry, each error handler, each library call doubles the number of paths through the program. A function with twenty branches has over a million possible traces.</ArrowItem>
  <ArrowItem>**Runtime conditions.** Time, concurrency, dependency versions, environment variables, partial network failures — all of these change which path the program actually takes.</ArrowItem>
  <ArrowItem>**Dependencies.** Every imported library is itself an unbounded state space, and your program's correctness composes with theirs.</ArrowItem>
</ArrowList>

You cannot exhaustively check this. Static analysis covers some classes of bug. Tests cover specific examples. Type systems prune the tree to a smaller set. Code review catches things a reader can hold in their head. None of these are exhaustive; together they sample the state space. The places they don't sample are where production incidents come from.

<PullQuote>
  "Did we check this is safe?" and "Did we check every reachable state?" are not the same question. Most engineering effort is the first; the second is structurally impossible at the scale a typical app contains.
</PullQuote>

The discipline of senior engineering is largely about *narrowing the state space until the questions you can't answer don't matter*. It is hard, it takes years, and most production incidents come from the cases that slipped past the narrowing.

## What AI assistance changes

Generating code with an LLM does not change the verification math. It changes two things downstream of it:

<ArrowList>
  <ArrowItem>**Volume of code goes up faster than reviewer attention does.** A model can produce a hundred lines of plausible code per minute; a human reviewing that code is still a human. The slop-to-signal ratio in the diff degrades the more code there is to read.</ArrowItem>
  <ArrowItem>**Edge cases that "look fine" pass.** A model trained on a corpus of working code will produce code that *looks* like working code on the happy path. The unhandled timeout, the SSRF in the redirect parameter, the race condition between two writes — these don't make the code look broken until they fire.</ArrowItem>
</ArrowList>

A model can write a payment integration that ships and clears card payments correctly all day. It can also leave a token in a log line, leak a customer email in an error message, or retry a non-idempotent write under load. These failures are not in the foreground of "does it work in the demo." They are in the long tail of "does it stay safe when something goes sideways," and that tail is exactly the part exhaustive checks cannot reach when the surface is arbitrary code.

This is the gap between vibe-coded demos and production-grade apps. It is not a question of effort. It is a structural property of how much state space the artifact carries.

## The platform's bet: shrink the surface

gavAI's design is the consequence of taking that gap seriously. Instead of producing arbitrary code that has to be verified after the fact, the platform produces a **typed JSON document** that is verified at every boundary by construction. The artifact is not code; it is data with a known shape.

The shape is fixed. Each block is one of a known set of types, each with a declared schema for its inputs. Each capability call is a named, typed action with declared inputs and outputs. Each write to a workspace's data passes through a schema validation before the database sees it. There is no arbitrary code path inside an app because there is no arbitrary code.

```json a-payment-integration.json theme={null}
{
  "block": "Cta",
  "props": {
    "headline": "Start your Pro trial",
    "cta_label": "Subscribe",
    "on_click": {
      "capability": "collectPayment",
      "args": {
        "plan_id":     "plan_pro_monthly",
        "success_url": "https://app.acme.com/welcome",
        "cancel_url":  "https://app.acme.com/pricing"
      }
    }
  }
}
```

This is a payment integration. The set of things that can go wrong with it is bounded by the schema of `collectPayment` — `plan_id` must be a known plan id, `success_url` must pass the platform's URL validator, and so on. The runtime checks every one of those constraints before any provider code runs. There is no log statement that could leak a token because the page does not hold one. There is no SSRF in `success_url` because the URL is validated against the workspace's allow-list before redirect. There is no retry storm because retry semantics live in the platform, not in this declaration.

## Per-component validation, all the way down

The bounded surface only delivers if validation is exhaustive at every layer. The platform validates four boundaries on every operation.

<FeatureTable
  caption="What gets checked, where, on every write"
  columns={[
{ key: "boundary", label: "Boundary" },
{ key: "check", label: "What's enforced" }
]}
  rows={[
{
  boundary: "Block input",
  check: "Each block has a strongly-typed schema for its props. A block with wrong props is rejected at publish time — the page does not deploy."
},
{
  boundary: "Capability input",
  check: "Each capability declares typed input and output schemas. Arguments are validated before the capability runs; outputs are validated before the runtime hands them back."
},
{
  boundary: "Schema write",
  check: "Every database write passes through the workspace's declared schema. Wrong types, missing required fields, broken foreign keys, duplicate uniques — all rejected at the boundary."
},
{
  boundary: "Outbound dispatch",
  check: "Capability calls to external providers cross the platform's perimeter — URL allow-lists, sensitivity tags, SSRF classification, scope checks — before the network sees them."
}
]}
/>

Each layer's check is exhaustive *over its own surface*. The schema declares every legal shape. The validator rejects everything outside that shape. There is no path through the platform that bypasses these checks because the path is the platform.

## What "exhaustive" actually means

Calling a check "exhaustive" is a strong claim. The precise version of it is narrower than it sounds.

<ArrowList>
  <ArrowItem>**Exhaustive over the declared shape.** Given the schema for a block or a capability, every input field is checked against every constraint that schema declares. Nothing slips past because the validator runs on the request before the handler does.</ArrowItem>
  <ArrowItem>**Not exhaustive over the world.** The platform cannot guarantee that the payment provider will not have an outage. It can guarantee that when it does, the result returns through a typed error envelope rather than as a thrown exception leaking a stack trace.</ArrowItem>
  <ArrowItem>**Not exhaustive over intent.** A workspace owner can build an app that does the wrong thing on purpose. The platform validates that the app's components compose correctly; it does not read minds.</ArrowItem>
</ArrowList>

The bargain is that the part you can verify is *exactly* the part that historically produces the long-tail production bugs: input validation, retry safety, type coercion, secret handling, provider-API misuse. The part the platform cannot verify — your business logic's intent — is also the part a human reviewer is actually able to read in one sitting and reason about, because the artifact is small.

## What you give up

Trade-offs are not free. The bounded surface costs you arbitrary expressiveness.

<ArrowList>
  <ArrowItem>**You cannot ship a custom database driver, a forked block, or a hand-rolled webhook handler.** The block catalogue is closed. The capability list is the list. New types ship with the runtime, not with your app.</ArrowItem>
  <ArrowItem>**You cannot reach into the runtime's internals.** If a capability does not support a flag you want, you cannot patch it locally; you have to wait for the platform to add it or use the API to compose the behavior at a higher level.</ArrowItem>
  <ArrowItem>**You cannot bypass validation "just this once."** The schema is the contract. A field that should be a UUID stays a UUID; you do not get to slip a free-form string through for an emergency.</ArrowItem>
</ArrowList>

These are real costs. If your app needs arbitrary code — a research prototype, a one-off integration with an exotic protocol, a deeply custom rendering pipeline — gavAI is not the right tool. If your app fits the shape that 95% of production SaaS fits — pages, forms, data, payments, emails, workflows, agents — the trade is a good one.

## The result, stated plainly

A page on gavAI is a JSON document. The document's shape is checked by a schema. Every operation it performs is a typed capability call with its own schema. Every write to data is a schema-validated transaction. Every outbound call is a perimeter-checked dispatch. There is no path through the app that skips any of these checks because they are not optional decorators — they are the spine the app runs on.

This is what "predictable by design" means. Not that nothing can go wrong, but that the set of things that *can* go wrong is bounded by declarations the platform can exhaustively check. The remaining surface — the part where a human still needs to think — is small enough that a human can actually think about it.

## Related

<Columns cols={2}>
  <BrandCard eyebrow="CONCEPT" title="Capabilities" href="/concepts/capabilities" icon="zap">
    Typed actions with Zod-validated I/O — the unit the platform's exhaustive checks cover.
  </BrandCard>

  <BrandCard eyebrow="CONCEPT" title="Schema" href="/concepts/schema" icon="table">
    The declarative shape every write is validated against, including sensitivity tags.
  </BrandCard>

  <BrandCard eyebrow="CONCEPT" title="PageBlocks" href="/concepts/pageblocks" icon="layout-grid">
    The closed set of block types every page is composed from.
  </BrandCard>

  <BrandCard eyebrow="SECURITY" title="Data sensitivity" href="/security/data-sensitivity" icon="shield-check">
    How field-level tags propagate through validation so sensitive values cannot leak across surfaces.
  </BrandCard>
</Columns>
