---
title: "AI proposals and the export check"
description: "Show unchecked AI results as „KI-Vorschlag“ in the Freigabestatus, list what to look at before a Typed block is exported, and register your own check source."
canonical_url: "https://skaile-ai.github.io/forge-editor/guides/export-check"
---
# AI proposals and the export check

> Show unchecked AI results as „KI-Vorschlag“ in the Freigabestatus, list what to look at before a Typed block is exported, and register your own check source.

An AI result is a Typed block that the model wrote. Two things tell that a block still waits for a person: the block has a Derivation made by the model, and nobody has changed or checked it since. The editor derives both facts on every read. Nothing new is stored: the decision of a person is the [Prüfung](https://skaile-ai.github.io/forge-editor/guides/pruefung) (a record pinned to a content hash), and the status is the derived **Freigabestatus**.

## `vorgeschlagen`

A block is an **AI proposal** when all of these hold (`isAiProposal(editor, node)`, `@skaile/forge-editor/composites`):

- its Derivation names the model as author: `method` starts with `ai:` (as `derive` and `composite-fill` write it), or `activity.agent` is set;
- `outputHash` is not empty and equals the SHA-256 of the block's body Markdown now, so nobody has edited the body since the model wrote it.

The Freigabestatus of a block, per kind, gains one value:

<table>
<thead>
  <tr>
    <th>
      Per kind
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        geprueft
      </code>
    </td>
    
    <td>
      a record of this kind exists at the current hash
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        veraltet
      </code>
    </td>
    
    <td>
      records of this kind exist, none at the current hash
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        vorgeschlagen
      </code>
    </td>
    
    <td>
      no record of this kind, and the block is an AI proposal
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        offen
      </code>
    </td>
    
    <td>
      no record of this kind, and the block is not an AI proposal
    </td>
  </tr>
</tbody>
</table>

The order of the cases is fixed: a record decides first. An edit of the body ends the proposal, so the kind reads `offen`, not `vorgeschlagen`: the person who edits has taken the text over, and the Prüfung that follows pins their version. A block that is `vorgeschlagen` counts as open in `freigabeOf`: the document is `freigegeben` only after every such block has a Prüfung.

Where it shows:

<table>
<thead>
  <tr>
    <th>
      Where
    </th>
    
    <th>
      What
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Chip after the title
    </td>
    
    <td>
      „KI-Vorschlag“ (warning) while a kind is <code>
        vorgeschlagen
      </code>
      
       and none is <code>
        veraltet
      </code>
      
      ; „Prüfung veraltet“ and „Geprüft“ keep their rules
    </td>
  </tr>
  
  <tr>
    <td>
      Section „Prüfstand“
    </td>
    
    <td>
      the kind line reads „KI-Vorschlag“
    </td>
  </tr>
</tbody>
</table>

Only blocks whose Block type lists the `pruefung` capability have a Freigabestatus. `PruefungKindStatus` is `'geprueft' | 'veraltet' | 'offen' | 'vorgeschlagen'`; a host that switches over it adds the case.

## The export check

Before a Typed block is exported as Markdown the editor looks for what a reader of the file could trip over. `exportIssuesOf(editor, node)` (`@skaile/forge-editor/composites`) returns one `ExportIssue` per finding, for `node` and every Typed block inside it, in document order:

```ts
interface ExportIssue {
  /** The Typed block the finding concerns (for a reference chip, the block that holds the chip). */
  blockId: string
  code: 'ai-unchecked' | 'stale-derived' | 'source-missing' | 'unresolved-reference'
  /** Title of that block. */
  title: string
}
```

<table>
<thead>
  <tr>
    <th>
      Code
    </th>
    
    <th>
      When
    </th>
    
    <th>
      Label
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        ai-unchecked
      </code>
    </td>
    
    <td>
      the block is an AI proposal and still waits for a Prüfung (see the check probe below)
    </td>
    
    <td>
      „KI-Vorschlag ungeprüft“
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        stale-derived
      </code>
    </td>
    
    <td>
      the block is Derived and its original changed since it was made
    </td>
    
    <td>
      „Quelle geändert“
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        source-missing
      </code>
    </td>
    
    <td>
      the block is Derived and its original is gone
    </td>
    
    <td>
      „Quelle fehlt“
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        unresolved-reference
      </code>
    </td>
    
    <td>
      a reference chip points at a Block that does not exist
    </td>
    
    <td>
      „Verweis ohne Ziel“
    </td>
  </tr>
</tbody>
</table>

The check hashes and serialises bodies. It runs when someone exports or opens the inspector section, not for every row.

### The export never waits

The block action „Als Markdown exportieren“ (`document-export`) runs the check first. With one or more findings it shows a warning notice („1 Hinweis“, „n Hinweise“) that points at the inspector, and it exports anyway. The check informs, it never blocks.

### The inspector section

The composites bindings add the section **„Export-Hinweise“** (id `export-check`) to the Typed block inspector and the details card, for every Block type that lists the `export` capability. It lists the findings with their label and the block title, or „Keine Hinweise“. It follows document changes and Prüfung records. `ExportCheckSection` is exported from `@skaile/forge-editor/composites/vue` and `@skaile/forge-editor/composites/react`. A host that builds `compositeFeatureCore` itself passes the component as `CompositesUi.exportCheck`; without it the Feature adds no section (the notice stays).

## Who tells that a block is checked

`ai-unchecked` needs to know whether a Prüfung exists, and the composites Feature does not know the `pruefung` Feature. A Feature that records checks provides a **check probe** for its editor:

```ts
import { provideCheckProbe } from '@skaile/forge-editor/composites'

const unprovide = provideCheckProbe(editor, {
  // true while the block still waits for a check
  awaitsCheck: (_editor, node) => !myChecks.has(node.attrs.id),
  // optional: the answer changed without a document change (the inspector section draws again)
  subscribe: listener => myChecks.subscribe(listener),
})
```

The `pruefung` Feature does this for its model: a block awaits a check while any configured kind is not `geprueft`. A block whose Block type has no kind awaits nothing, so it is never listed as `ai-unchecked`. Without any probe every AI proposal is `ai-unchecked`. `provideCheckProbe` returns the function that removes the probe; call it when the editor goes away.


## Sitemap

See the full [sitemap](https://skaile-ai.github.io/forge-editor/sitemap.md) for all pages.
