Sealed forms, hosted pages and sealed views — forms/<id>.json

Sealed forms, hosted pages, sealed views

Raw Markdown the file as the skill ships it All pages ↓

Law 4: sensitive data never touches the model. Choose the path by what the data is:

DataUseWhy
Ordinary data (name, phone, email, address, SKU, amount)Ask in the stream; it is a tool argumentThe model collects and cleans it. No form.
Personal secrets that are not payment credentials (SSN, date of birth, license number, income, PIN)Sealed form (kind: "form")Values go device → the organization's endpoint; the model gets a filtered receipt.
Card number, card not presentHosted page (kind: "hosted"): the processor's own page in a browser the app can't readKeeps card data out of the app and the model.
Card present at a counterA terminal tool (sends the amount to the store's terminal)Not a form.
Showing a secret (full account number, limit)Sealed view (kind: "view")The model is told only that it was shown.

**Card field types (cardNumber, expiry, cvc) exist only for mode: "demo" accounts.** Refuse to add them to an organization or personal account; offer the hosted page or the terminal instead.

Contents

  • Sealed form
  • Hosted page
  • Sealed view
  • Fields
  • How the tool opens it

Sealed form

{
  "id": "financing.apply",
  "kind": "form",
  "title": "Financing application",
  "subtitle": "Goes straight to the credit partner. The assistant only learns the decision.",
  "presentation": "sheet",
  "fields": [
    { "key": "ssn", "label": "Social Security number", "type": "ssn", "required": true },
    { "key": "dob", "label": "Date of birth", "type": "date", "required": true },
    { "key": "income", "label": "Annual income", "type": "money", "required": true },
    { "key": "consent", "label": "Customer agrees to a credit check", "type": "toggle", "required": true }
  ],
  "submit": { "url": "https://credit.meridian.example/sealed/financing", "auth": "oauth", "resource": "https://api.meridian.example/mcp",
              "label": "Submit application", "include": ["session"] },
  "receipt": { "keep": { "status": "^(approved|declined|review|error)$", "ref": "^fin_[A-Za-z0-9]{6,40}$",
                         "limit": "^[0-9]{1,7}$", "message": "^.{0,140}$" } },
  "card": { "pending": "Financing application open", "done": "Financing {{ status }}{{ limit | money | prefix:' · limit ' }}" }
}
  • submit.url: the organization's sensitive-data endpoint, https. Auth is the person's credential for the connection of the MCP server that opened the form. include: ["session"] echoes the tool's one-time session id.
  • receipt.keep: the only keys that ever reach the model, each kept only if its value matches the regex in full. Keep it to status, a reference, last four digits, a short message. Anchor every regex (^…$).
  • card.pending / card.done: the chat card's text before and after (done interpolates the filtered receipt).
  • presentation: sheet (a card that opens a modal) or inline (≤ 3 short fields drawn in the card).

Hosted page

{ "id": "card.hosted", "kind": "hosted", "title": "Card for {{ context.customerName }}",
  "subtitle": "Opens the payment processor's secure page. Neither this app nor the assistant sees the card.",
  "allowedHosts": ["pay.processor.example"], "button": "Open secure card page",
  "receipt": { "keep": { "status": "^(captured|declined|canceled|error)$", "ref": "^tok_[A-Za-z0-9]{6,40}$", "last4": "^[0-9]{4}$" } },
  "card": { "pending": "Card entry open", "done": "Card ···· {{ last4 }} · {{ status }}" } }

The URL comes from the tool at run time (one-time); allowedHosts is the only place it may point. When the page closes, the loop asks the tool for the outcome.

Sealed view

{ "id": "financing.account", "kind": "view", "title": "Financing account",
  "fetch": { "url": "https://credit.meridian.example/sealed/account", "auth": "oauth", "resource": "https://api.meridian.example/mcp", "include": ["session"] },
  "template": "financing.account", "expiresAfterSeconds": 120, "allowCopy": false }

Fields

key, label, type, required, optional value (prefill, interpolated from context.*), readOnly, validate (luhn or a regex), options (choice), camera (image), autocapitalize. Types: text, number, money, date, choice, toggle, phone, email, postalCode, ssn, image, signature (and cardNumber, expiry, cvc for demo accounts only). Sensitive types are masked, never autocorrected, kept out of keyboard learning and pasteboard history.

How the tool opens it

The MCP server returns either standard MCP elicitation (form mode with _meta["com.manolab.streamline/ui"].sealedForm naming the form; URL mode for a hosted page) or the sealedForm directive { "form": "financing.apply", "session": "…", "context": { "customerName": "…" }, "expiresAt": "…" }. context holds only non-sensitive display values. The streamline must list the form id in forms[] (views in views[]). Nothing typed is written to disk, logged, put in a transcript or kept after the sheet closes.