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

# Tools and Proposals

> Every write as a tool with a preview, an execute and a policy; proposals on cases approved by the people the policy names; the first checks

A **tool** wraps one service function the console already calls, with
three things the function alone does not have: a **preview** (its dry
run - the plan with the targets it would reach and a hash of them, and
nothing sent), an **execute** that runs the function for real against
that plan, and a **policy** - how many people have to approve. This is
PR 3 of the [agentic process support
design](/setup/agentic-process-support.md): every write is a case (design
rule D2), the bridge makes the plan and the model, from PR 4 on, only
proposes it (D3), and the policy is a ceiling no agent can raise (D6).

## The tools

| Tool | Wraps | Policy | Sends |
| - | - | - | - |
| `acknowledge-received` | the APERAK loop | one person | positive APERAKs to market partners |
| `resend-rejected-partins` | the PARTIN resend | **four eyes** | PARTIN to market partners |
| `nachversand` | the Nachversand | **four eyes** | UTILMD and PARTIN to market partners |
| `korrektur-mail`, `quittungs-nachfass`, `stammdaten-anfrage`, `aktivierungs-nachfass`, `auskunft` | the mail functions | one person | mail to market partners |
| `auto-zuordnung` | the Auto-ZOE | **four eyes** | Zuordnungsermächtigungen to DSOs |
| `zurueckstellung`, `ausnahme`, `nachversand-vormerkung`, `kontakt-adresse` | the ledger setters (set or lift) | one person | nothing - a ledger entry |
| `vertrag` | recording a contract's state | one person | nothing |
| `notiz` | the Protokoll-Notiz | one person | the note to the Slack log with the next Protokoll run |
| `service-stop`, `service-start` | pausing and resuming the orchestrator | one person | nothing; the stop is **refused inside the gate window** while the day ahead is not submitted and accepted |

Never a tool, as the operator-app design lists it: the orchestrator and
the manual pipeline steps, the runtime configuration, mailbox moves,
single or custom Zuordnungen, the PARTIN fan-out, the Bestellungen,
imports, the external writes. They stay with the console and the process
key.

**One person** means one approval, and the proposer's own counts.
**Four eyes** means a second person: a different key name than the
proposer's. The bridge and an agent are not persons, so what they
propose needs the people the policy names - two for four eyes.

> [!NOTE]
>
> **Four eyes is switched off for now** (`FOUR_EYES_ENABLED=false`, the
> default; changeable in the console under the pipeline settings): every
> proposal executes on one person's approval, the proposer's own included.
> The policy stays recorded on each tool and each proposal as the ceiling,
> and `GET /v1/process/tools` says per tool how many approvals it takes
> right now (`approvals`). Switching it on enforces four eyes again from
> the next proposal, with named process keys for the second person.

## Previews and plans

`POST /v1/process/tools/{slug}/preview` with the tool's parameters
returns the plan: `summary`, `targets` (what the dry run says it would
send, or its targets without the skipped ones; identifiers and short
strings only), `count`, `detail`, `plan_hash`, and `refused` with the
reason where the tool will not run now. A preview sends nothing and
stores nothing; the suite proves it for every tool: the mailer, MakoFlow
and sFTP fakes stay empty and the audit share is byte-identical after.

Where the function takes `mp_ids`, the execute is restricted to the
plan's partners, so what runs is what was approved and not what the
world looks like a day later.

## Proposals on cases

`POST /v1/process/cases/{id}/proposals` with `{"tool", "parameters",
"note"}` previews the tool and puts the plan on the case as a proposal;
`POST /v1/process/tools/{slug}/propose` opens a case for it (source
`proposal`). A case with a proposal waiting moves to the inbox's **To
approve** view and back when none is left.

| Endpoint | Does |
| - | - |
| `POST .../proposals/{pid}/approve` | One person's approval. The policy says how many distinct persons it takes; the last one executes: the plan is previewed again and must hash the same, then the tool runs. A changed plan **expires** the proposal (409) - propose it again. |
| `POST .../proposals/{pid}/reject` | Ends it with a note. |

A proposal carries `tool`, `parameters`, `plan`, `plan_hash`, `policy`,
`state` (`proposed`, `approved` - one of two -, `executing`, `executed`,
`rejected`, `expired`, `failed`), who proposed it and when, the
approvals, the result or the error. It lives 24 hours. Events:
`proposal_made`, `proposal_approved`, `proposal_rejected`,
`action_executed`, `action_failed` (warning: expired, refused, or the
tool raised; on the operator-app feed). They stay out of the Slack log,
which carries the interaction the tool itself records.

In the console, every open case offers a tool with its parameters, or
the tool its finding **suggests**, pre-filled; every proposal shows its
state, its policy and *Freigeben* / *Ablehnen*.

## Checks and the first check skills

A **check** is an enumerated, deterministic function that turns what the
bridge knows into findings (design rule D1). The first ones read the dry
runs of the send functions and the partner overview:

| Skill | Check | Finding | Suggests |
| - | - | - | - |
| `de-mako-quittungen` | `standing-rejections` | a partner and stream whose latest word is a rejection | `resend-rejected-partins` for PARTIN and transport |
| | `sent-unanswered` | delivered files without an acknowledgment past the deadline plus Karenz | `quittungs-nachfass` for the partner |
| `de-mako-registrierung` | `stammdaten-needed` | the partner does not know our MP-ID | `stammdaten-anfrage` |
| | `korrektur-needed` | one stream accepted, the other rejected | `korrektur-mail` |
| | `nachversand-due` | a bounced message whose partner knows us now | `nachversand` |

A target the routine itself skips (excluded, deferred, written to
already) is still a finding - the condition stands - with the reason in
its subject and no suggestion. `GET /v1/process/checks` lists the checks,
`POST /v1/process/checks/{slug}/run` returns a check's findings now
without opening anything.

A check skill, carried by an agent (the check skills are not agents of
their own; create one in the Agenten tab, or wait for PR 4's MaKo
Exception Handler), runs every check of the skill in full and keeps one
case per finding: opened while the finding stands, with the suggested
tool on it, closed as *resolved by counterpart* when the check no longer
finds it. A dry run reports without touching the cases. The skill
registry (`GET /v1/process/agents`) now says per skill which tools it
may propose, which checks it runs and which pages its knowledge comes
from.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.