@smartkomm/plexus-cli 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +863 -2
- package/dist/cli.mjs +5432 -0
- package/dist/skill/.managed-by-plexus-cli +1 -0
- package/dist/skill/SKILL.md +224 -0
- package/dist/skill/references/accounts/email-changes.md +46 -0
- package/dist/skill/references/agents/use-shared-tools.md +52 -0
- package/dist/skill/references/applications/inspect-process-store.md +61 -0
- package/dist/skill/references/applications/start-a-test-application.md +66 -0
- package/dist/skill/references/audit/answer-access-request.md +115 -0
- package/dist/skill/references/config/attach-a-template.md +117 -0
- package/dist/skill/references/config/init-tenant.md +121 -0
- package/dist/skill/references/config/onboard-tenant.md +71 -0
- package/dist/skill/references/config/rename-program-stage.md +99 -0
- package/dist/skill/references/config/set-tenant-branding.md +150 -0
- package/dist/skill/references/config/set-tenant-terms.md +50 -0
- package/dist/skill/references/config/set-up-billing-catalog.md +73 -0
- package/dist/skill/references/config/update-tenant-config.md +174 -0
- package/dist/skill/references/config/wipe-tenant-config.md +107 -0
- package/dist/skill/references/export/create-named-export.md +47 -0
- package/dist/skill/references/feedback/create-and-triage-feedback.md +69 -0
- package/dist/skill/references/feedback/manage-feedback-tags.md +57 -0
- package/dist/skill/references/finance/change-contract-grant.md +36 -0
- package/dist/skill/references/finance/record-submission-date.md +33 -0
- package/dist/skill/references/interessenten/list-interessenten.md +54 -0
- package/dist/skill/references/marketing/demo-video-links.md +101 -0
- package/dist/skill/references/notifications/debug-mail-delivery.md +77 -0
- package/dist/skill/references/notifications/mail-level.md +90 -0
- package/dist/skill/references/notifications/reword-a-letter.md +155 -0
- package/dist/skill/references/notifications/trace-a-login.md +50 -0
- package/dist/skill/references/offer/add-workflow-node.md +268 -0
- package/dist/skill/references/offer/addresses.md +129 -0
- package/dist/skill/references/offer/appointment-task.md +79 -0
- package/dist/skill/references/offer/assignment-rule.md +195 -0
- package/dist/skill/references/offer/attach-form-to-workflow.md +79 -0
- package/dist/skill/references/offer/author-form-scenarios.md +97 -0
- package/dist/skill/references/offer/author-manifest-meta.md +94 -0
- package/dist/skill/references/offer/author-smartform.md +102 -0
- package/dist/skill/references/offer/bind-node-input.md +76 -0
- package/dist/skill/references/offer/completeness-check.md +137 -0
- package/dist/skill/references/offer/countersign-node.md +91 -0
- package/dist/skill/references/offer/create-pedagogy-unit.md +55 -0
- package/dist/skill/references/offer/declare-node-reads.md +99 -0
- package/dist/skill/references/offer/edit-live-tenant-offer.md +144 -0
- package/dist/skill/references/offer/edit-smartform.md +121 -0
- package/dist/skill/references/offer/edit-workflow-node.md +126 -0
- package/dist/skill/references/offer/engage-push-node.md +147 -0
- package/dist/skill/references/offer/insert-and-rewire-node.md +67 -0
- package/dist/skill/references/offer/node-tasks.md +162 -0
- package/dist/skill/references/offer/optional-assessment.md +65 -0
- package/dist/skill/references/offer/publish-offer-bundle.md +117 -0
- package/dist/skill/references/offer/set-offer-capacities.md +78 -0
- package/dist/skill/references/offer/staff-input-if-node.md +112 -0
- package/dist/skill/references/offer/task-appointment-result.md +69 -0
- package/dist/skill/references/offer/task-family-request.md +69 -0
- package/dist/skill/references/offer/task-result-files.md +52 -0
- package/dist/skill/references/offer/template-document-node.md +113 -0
- package/dist/skill/references/offer/workflow-default-channels.md +69 -0
- package/dist/skill/references/resolver/manage-contract-resolver.md +161 -0
- package/dist/skill/references/retention/erase-due-applications.md +99 -0
- package/dist/skill/references/retention/erase-due-parties.md +107 -0
- package/dist/skill/references/roles/migrate-parent-assignments.md +31 -0
- package/dist/skill/references/signing/README.md +113 -0
- package/dist/skill/references/signing/connect-a-school.md +90 -0
- package/dist/skill/references/signing/rotate-the-key.md +41 -0
- package/dist/skill/references/stammdaten/purge-deleted-person.md +68 -0
- package/dist/skill/references/stripe/connect-a-school.md +107 -0
- package/dist/skill/references/stripe/request-and-verify-a-payment.md +78 -0
- package/dist/skill/references/test-actions/drive-a-test-application.md +69 -0
- package/dist/skill/references/users/add-platform-admin.md +59 -0
- package/dist/skill/references/users/add-platform-user.md +59 -0
- package/dist/skill/references/users/add-tenant-admin.md +75 -0
- package/dist/skill/references/users/change-login-email.md +55 -0
- package/dist/skill/references/users/cli-access-for-tenant-admin.md +117 -0
- package/dist/skill/references/users/delete-permission-group.md +76 -0
- package/dist/skill/references/users/grant-cross-tenant-access.md +57 -0
- package/dist/skill/references/users/grant-feedback-assignable.md +43 -0
- package/dist/skill/references/users/set-password.md +57 -0
- package/dist/skill/references/users/use-test-account.md +79 -0
- package/dist/skill/references/worker-ops/debug-worker-jobs.md +90 -0
- package/dist/skill/references/worker-ops/verify-migrations.md +51 -0
- package/dist/spec.snapshot.json +156821 -0
- package/package.json +53 -4
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Add a workflow node to an offer bundle
|
|
2
|
+
|
|
3
|
+
**When to use:** an offer's admission workflow needs another step (an approval
|
|
4
|
+
gate, a waitlist holding node, a selection terminus, another form, …) and you want
|
|
5
|
+
the graph + progress board kept in sync automatically — instead of hand-editing
|
|
6
|
+
`workflow.json` (graph + `progressWiring.mappings`) and `manifest.json`
|
|
7
|
+
(`progressLayout` states + columns) in lockstep.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
- **Env / scope:** none for authoring — `offer workflow node add|rm|list` and
|
|
11
|
+
`offer workflow show` are **offline** (read/write the on-disk bundle, no
|
|
12
|
+
deployment). Only the final `offer apply --publish` is online and needs
|
|
13
|
+
`--env <env>` + a token with offer config rights (`plexus-cli --env <env> whoami`).
|
|
14
|
+
- **A bundle on disk:** scaffold one with `offer init`, or `offer export` an
|
|
15
|
+
existing offer into a directory.
|
|
16
|
+
- **Tooling:** an installed, current `plexus-cli` (`plexus-cli offer workflow node add --help`).
|
|
17
|
+
- **Both languages:** a node's label is ONE `{ de, en }` field — `--label` AND
|
|
18
|
+
`--label-en` are required (as `--reason` + `--reason-en` on a collectPayment
|
|
19
|
+
node and `<id>:<Deutsch>:<English>` per `--decision-outcomes` entry).
|
|
20
|
+
- **Supported types:** `form`, `decision`, `review`, `setFlag`, `approval`, `waitlist`,
|
|
21
|
+
`roleAssign`, `join`, `collectPayment`, `completenessCheck`, `countersign`, `engagePush`, `if`, `end`
|
|
22
|
+
(the list `offer workflow node add --help` prints). `decision` is the
|
|
23
|
+
configurable-decision builder (ATS primitives — assign-decision, composed
|
|
24
|
+
tasks, artifacts, decision-as-task); `approval` is a **deprecated alias** for
|
|
25
|
+
`decision` (maps `--decision-outcomes` → outcomes). `setFlag` flips an external
|
|
26
|
+
flag on the form instance it names (`--flag-key` + `--target '$node.<formNode>.admission'`).
|
|
27
|
+
`countersign` („Unterschrift (intern)") is the
|
|
28
|
+
school's countersignature of a checked contract — see
|
|
29
|
+
[Countersign the contract](./countersign-node.md).
|
|
30
|
+
`if` („Wenn") routes on one DSL expression over `$env` (`--condition
|
|
31
|
+
'$env.staffInput.<key>'`, outcomes `true` / `false` — route both with
|
|
32
|
+
`--outcomes`) — see [Branch on a staff input](./staff-input-if-node.md).
|
|
33
|
+
`completenessCheck` waits for outstanding documents (`--require`,
|
|
34
|
+
`--deadline-days`|`--deadline-until`, `--notification requested=…`, optional
|
|
35
|
+
`--guest-role` + `--person`) — see
|
|
36
|
+
[Wait for outstanding documents](./completeness-check.md). The folded
|
|
37
|
+
`governedDocument` (offer letter / contract) cannot be added by the CLI yet.
|
|
38
|
+
A `form` node binds a SmartForm via `--form` / `--new-form` (see
|
|
39
|
+
[Attach a form to a workflow](./attach-form-to-workflow.md)).
|
|
40
|
+
- **Letters with the node:** `--notification <event>=<templateId>` (repeatable)
|
|
41
|
+
binds a letter in the same call — required when the type declares the event
|
|
42
|
+
required (completenessCheck `requested`, countersign `countersigned`), since
|
|
43
|
+
`add` validates before writing.
|
|
44
|
+
- **Addresses with the node**: every flag that reads another node's data
|
|
45
|
+
takes a full address naming its producer, in single quotes — `--reads` /
|
|
46
|
+
`--require` (`'$node.<formNode>.admission.<field>'`), `--person`
|
|
47
|
+
(`'$node.<formNode>.admission.beneficiary'`), `--target` / `--adopt-form`
|
|
48
|
+
(`'$node.<formNode>.admission'`), `--contract`, engagePush `--form` / `--payment`.
|
|
49
|
+
A bare key is refused with the addresses visible at the new node; `add` binds
|
|
50
|
+
NOTHING implicitly. Bundles exported before addresses were introduced (with `wires`) are refused —
|
|
51
|
+
export again. See [Name what a node reads](./addresses.md).
|
|
52
|
+
|
|
53
|
+
### Payment gate (`collectPayment`)
|
|
54
|
+
`--article <key>` and `--settlement online|separate` are **required** (no
|
|
55
|
+
default); both are written as the node's parameters (`inputs.article`,
|
|
56
|
+
`inputs.settlement` — the editor lists them under „Parameter"). The article is
|
|
57
|
+
a LINK: the key (`billing_item_definitions.configKey`) of a `one_time`
|
|
58
|
+
article assigned to the offer's program (Finanzen → Artikel, directly or through
|
|
59
|
+
an offered package). Amount, currency and the Stripe line name are read from the
|
|
60
|
+
article at runtime — the node stores no amount, and publish refuses `amount` /
|
|
61
|
+
`currency` on it (`[collectPayment/retiredField]`) as well as an unknown,
|
|
62
|
+
non-one-time or out-of-program article. A price change in Finanzen applies to
|
|
63
|
+
the next payment without a republish. `online`: the family gets a „Zahlung leisten" request row and pays
|
|
64
|
+
through Stripe Checkout; the process responsible can also close it
|
|
65
|
+
„Ohne Online-Zahlung abschließen" with a required reason (e.g. on an
|
|
66
|
+
installation without Stripe). `separate`: the school bills
|
|
67
|
+
the fee outside Plexus — nothing is asked of the family (no row, no letter; the
|
|
68
|
+
portal says „Die Anmeldegebühr wird separat abgerechnet."), and the process
|
|
69
|
+
responsible closes the step in the cockpit with **„Zahlung überspringen"**
|
|
70
|
+
(optional comment, feed entry „Zahlung übersprungen", nothing booked). The
|
|
71
|
+
routing edge is `paid` either way. Switch an existing node with
|
|
72
|
+
`offer workflow node set --id <node> --settlement separate` (or
|
|
73
|
+
`--article <key>` to charge another article); a `requested`
|
|
74
|
+
letter bound on a `separate` node is refused (`[notification/unknownEvent]`).
|
|
75
|
+
Applications already parked on the node stay on the version they are pinned
|
|
76
|
+
to — moving them onto the new version after `apply --publish` needs a platform
|
|
77
|
+
operator.
|
|
78
|
+
|
|
79
|
+
### Terminal, fan-in, and role nodes
|
|
80
|
+
- **`end --outcome <outcome> --data-decision <commit|discard>`** authors a
|
|
81
|
+
terminal node's application outcome (`approved` | `rejected` | `withdrawn` |
|
|
82
|
+
`waitlist` | `expired`; default `approved`) and — required, no default —
|
|
83
|
+
whether the application's data is kept (`commit`) or discarded (`discard`)
|
|
84
|
+
at this end (the outcome is only a label and never decides it). It
|
|
85
|
+
writes the `terminalStatus` / `applicationOutcome` / `dataDecision` /
|
|
86
|
+
`terminationKind` literal inputs the end executor reads (fixing the old gap
|
|
87
|
+
where every CLI-authored end recorded `approved`), and contributes **one**
|
|
88
|
+
outcome-aware terminal column (e.g. a rejected end adds a red *Abgesagt*
|
|
89
|
+
column, not a misleading green *Eingeschrieben*). A second end is inserted
|
|
90
|
+
isolated — wire predecessors to it with `join --from` or `node set --outcomes`.
|
|
91
|
+
Override the ledger label with `--terminal-status`, the kind with
|
|
92
|
+
`--termination-kind`.
|
|
93
|
+
- **`join --from <nodeId>:<outcome>,…`** authors an explicit XOR fan-in
|
|
94
|
+
convergence. Each `--from` pair reroutes that source node's outcome onto the
|
|
95
|
+
join; the join's own onward edge defaults to the end node (override with
|
|
96
|
+
`--outcomes done:<target>`). Multi-input into a join is the one legal
|
|
97
|
+
many-to-one shape (the publish gate rejects multi-input on non-join nodes).
|
|
98
|
+
- **`roleAssign --role <key>`** grants a person-lifecycle role. `--role` is
|
|
99
|
+
validated **offline** against the manifest's declared `roleInstances` (fail
|
|
100
|
+
loud if absent — declare it first with `offer role add`). `--mode`
|
|
101
|
+
is `add` (default) | `remove` | `replace`; `--person '<address>'` is required —
|
|
102
|
+
the party that gets the role, a party leaf of a form
|
|
103
|
+
(`--person '$node.form.admission.beneficiary'`). Never defaulted.
|
|
104
|
+
|
|
105
|
+
### Configurable decision node (`decision`)
|
|
106
|
+
The `decision` node is the **one configurable decision node** (it replaced the old
|
|
107
|
+
`approval` node; `--type approval` is refused as an unknown type). By default it
|
|
108
|
+
declares the type outcomes `approved` / `rejected` / `waitlisted`. To author a
|
|
109
|
+
**custom** outcome set (with your own ids + labels), pass `--decision-outcomes
|
|
110
|
+
<id>:<Deutsch>:<English>,…` (an outcome's label is `{ de, en }` — both languages
|
|
111
|
+
are required) and route each with `--outcomes`:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Inquiry decision: direkt aufnehmen / zum Interview / absage.
|
|
115
|
+
plexus-cli offer workflow node add --dir ./demo \
|
|
116
|
+
--type decision --id decisionInquiry --label "Erstentscheidung" --label-en "Initial decision" --after form \
|
|
117
|
+
--decision-outcomes 'direkt:"Direkt aufnehmen":"Admit directly",interview:"Zum Interview":"To the interview",absage:"Absage":"Rejection"' \
|
|
118
|
+
--outcomes 'direkt:formApplication,interview:interview,absage:joinAbsage' \
|
|
119
|
+
--assign-decision zulassung --decision-as-task 'Erstentscheidung treffen'
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Each entry becomes a `NodeOutcome {id,label}` on `node.outcomes`
|
|
123
|
+
(first = `isDefault`); the backend executor + publish gate + cockpit UI all validate
|
|
124
|
+
and render decisions against whatever set the node declares. `--decision-outcomes` is
|
|
125
|
+
decision-only (fails loud on any other type). Outcomes whose id maps to a known
|
|
126
|
+
semantic (`direkt`/`accepted`→granted, `absage`/`rejected`→rejected,
|
|
127
|
+
`warteliste`/`waitlisted`→waitlisted) drive the roster chip + annotation mirror; any
|
|
128
|
+
other id (e.g. `interview`) is a real routing decision with no typed annotation.
|
|
129
|
+
Route each declared outcome to a target that exists — the publish gate checks coverage.
|
|
130
|
+
|
|
131
|
+
#### ATS lifecycle primitives on a decision node
|
|
132
|
+
A decision node carries the full offer-lifecycle authoring surface:
|
|
133
|
+
|
|
134
|
+
- **`--assign-decision <groupKey>`** — the permission group whose members hold the
|
|
135
|
+
synthesized decision Work-Item (= may pick the routing outcome, in ONE act).
|
|
136
|
+
Pairs with `--decision-as-task` (each requires the other — fails loud). Absent
|
|
137
|
+
= super-user-only decide (the instance's responsible).
|
|
138
|
+
- **`--comment-required <id>[,<id>]`** — authored outcome ids that require a decider comment.
|
|
139
|
+
- **`--task …`** (repeatable) — a composed task the node spawns on enter,
|
|
140
|
+
instantiating a code-owned kind (the kind carries the behaviour: actions,
|
|
141
|
+
close protocol, respawn, gate default):
|
|
142
|
+
- `key:task:title:group[:dueInDays]` — an internal check task (trivial `complete`).
|
|
143
|
+
- `key:approval:title:group[:revisionGroup]` — an approval; gates the node by
|
|
144
|
+
default. With **`--task-on-reject spawnRevision`** a rejecting verdict
|
|
145
|
+
spawns a revision back to `revisionGroup` (then REQUIRED — fails loud).
|
|
146
|
+
(`binds` is DELETED — the offer arc is node-internal; use the
|
|
147
|
+
`offer` node type for the prepare/approve arc.)
|
|
148
|
+
- **`--artifact key:type:visibility:label`** (repeatable) — a versioned artifact SLOT
|
|
149
|
+
(`visibility` = `internal` | `external`). Each slot exposes a `<key>Uploaded` node-export.
|
|
150
|
+
- **Assignment rule** (GRAPH-level, no flag) — who becomes responsible and who owns
|
|
151
|
+
each team is `graph.assignmentRules` in the same `workflow.json`; `node add/set` do
|
|
152
|
+
not touch it. A node that gives work to a team the rule has no slot for makes
|
|
153
|
+
`offer validate` / publish refuse with `[assignment/slotMissing]` — add a
|
|
154
|
+
`team:<key>` list in the bundle or in the workflow editor („Team-Verantwortliche").
|
|
155
|
+
Recipe: `playbooks/offer/assignment-rule.md`.
|
|
156
|
+
- **`--decision-as-task title[:dueInDays]`** — surface the decision as a
|
|
157
|
+
first-class queue Work-Item. Requires `--assign-decision` (and vice versa).
|
|
158
|
+
Combining with gating approval tasks is fine: the HARD node gate blocks a
|
|
159
|
+
non-rejecting advance server-side while gate-effective rows are open.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Offer create-phase (mirrors the grundschule createOffer node): external artifact,
|
|
163
|
+
# a prepare task, a finance approval gate with a ping-pong revision on reject, and
|
|
164
|
+
# the Schulleitung send-offer decision row (hard-gated on the finance approval).
|
|
165
|
+
plexus-cli offer workflow node add --dir ./demo \
|
|
166
|
+
--type decision --id createOffer --label "Angebot erstellen" --label-en "Prepare offer" --after form \
|
|
167
|
+
--decision-outcomes 'offerReady:"Angebot freigegeben",absage:"Angebot abgebrochen"' \
|
|
168
|
+
--assign-decision schulleitung --decision-as-task 'Angebot versenden:3' \
|
|
169
|
+
--artifact 'offer:offer:external:Angebotsschreiben' \
|
|
170
|
+
--task 'prepareOffer:checkTask:Angebotsschreiben vorbereiten:schulleitung:5' \
|
|
171
|
+
--task 'offerApproval:approval:Angebot freigeben:finanzen:offer:offerUploaded:schulleitung' \
|
|
172
|
+
--task-on-reject spawnRevision
|
|
173
|
+
|
|
174
|
+
# Task-driven selection decision (grundschule approvalSchulleitung), gate-less:
|
|
175
|
+
plexus-cli offer workflow node add --dir ./demo \
|
|
176
|
+
--type decision --id freigabe --label "Freigabe" --label-en "Approval" --after form \
|
|
177
|
+
--decision-outcomes 'approved:Freigegeben,rejected:Abgelehnt,waitlisted:Warteliste' \
|
|
178
|
+
--assign-decision schulleitung \
|
|
179
|
+
--comment-required rejected \
|
|
180
|
+
--decision-as-task 'Über Freigabe entscheiden:5'
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
All of the above is validated by the REAL publish gate on `offer validate`; the CLI adds
|
|
184
|
+
fail-loud cross-checks at the flag boundary (undeclared bound artifact, `spawnRevision`
|
|
185
|
+
without `revisionGroup`, `decision-as-task` ⇔ `assign-decision` pairing).
|
|
186
|
+
|
|
187
|
+
## Steps
|
|
188
|
+
```bash
|
|
189
|
+
# 1. Scaffold a minimal linear offer (start → form → end).
|
|
190
|
+
plexus-cli offer init --slug demo --title "Demo Schule" --title-en "Demo School" --tenant test --responsible sekretariat --out ./demo
|
|
191
|
+
|
|
192
|
+
# 2. Inspect the graph.
|
|
193
|
+
plexus-cli offer workflow node list --dir ./demo
|
|
194
|
+
plexus-cli offer workflow show --dir ./demo
|
|
195
|
+
|
|
196
|
+
# 3. Insert a decision gate between the form and the end node.
|
|
197
|
+
# --outcomes routes each declared outcome; any omitted outcome defaults to
|
|
198
|
+
# where --after currently points (here end), splicing into the chain.
|
|
199
|
+
plexus-cli offer workflow node add --dir ./demo \
|
|
200
|
+
--type decision --id approval --label "Freigabe" --label-en "Approval" --after form \
|
|
201
|
+
--assign-decision freigeber --decision-as-task 'Freigabe entscheiden' \
|
|
202
|
+
--outcomes approved:end,rejected:end
|
|
203
|
+
|
|
204
|
+
# 4. Validate offline against the REAL publish gate (add already ran it; this is
|
|
205
|
+
# the explicit re-check / CI gate).
|
|
206
|
+
plexus-cli offer validate --dir ./demo
|
|
207
|
+
|
|
208
|
+
# 5. Apply + publish (online). Needs --env + a token; cuts new versions on diff.
|
|
209
|
+
plexus-cli --env <env> --tenant test offer apply --dir ./demo --publish
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
- **`--id`** is optional — defaults to the type, numbered from 2 when taken
|
|
213
|
+
(`approval`, `approval2`, …; `makeNodeId`). An id you pass must be a dot-free
|
|
214
|
+
identifier (`^[a-z][A-Za-z0-9]*$`, e.g. `decisionInquiry`) — `offer validate`
|
|
215
|
+
refuses anything else as `[graph/nodeIdFormat]`.
|
|
216
|
+
- **`--after <nodeId>`** inserts after that node and rewires its onward edge(s)
|
|
217
|
+
to the new node. Omit it to insert immediately before the end node.
|
|
218
|
+
- **`--type form`** binds a SmartForm — `--form <slug>` (existing) or
|
|
219
|
+
`--new-form --form <slug>` (scaffold + bind). See the dedicated playbook.
|
|
220
|
+
- **Remove** a node (rewires predecessors onto its single successor, strips its
|
|
221
|
+
progress entries, re-validates; refused while an address elsewhere names it):
|
|
222
|
+
```bash
|
|
223
|
+
plexus-cli offer workflow node rm --dir ./demo --id approval
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Verify
|
|
227
|
+
```bash
|
|
228
|
+
plexus-cli offer workflow node list --dir ./demo # the node appears, routing is wired
|
|
229
|
+
plexus-cli offer validate --dir ./demo # exit 0 = publish gate clean
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
For the approval example, `offer validate` exits 0 with **no manual manifest
|
|
233
|
+
edit**: `offer workflow node add` materialised the `approval.released` process
|
|
234
|
+
state, its board column, and the `(approval, released) → process/approval.released`
|
|
235
|
+
wiring mapping. A decision that reads nothing of the form names no address
|
|
236
|
+
(`offer workflow node reads --dir ./demo --node approval` lists nothing). After `offer workflow node rm`, the graph returns to the linear
|
|
237
|
+
`form → end` shape and validation passes again.
|
|
238
|
+
|
|
239
|
+
## Idempotency / gotchas
|
|
240
|
+
- **`add` validates before writing.** Any error-severity issue from the real
|
|
241
|
+
publish gate (`runWorkflowValidators` etc.) blocks the write and exits 1 — the
|
|
242
|
+
bundle is left untouched. A failed `add` never leaves a half-spliced bundle.
|
|
243
|
+
- **Splice defaults can over-route.** Every declared outcome you DON'T name in
|
|
244
|
+
`--outcomes` defaults to the anchor's current onward target. Inserting a
|
|
245
|
+
multi-outcome node (endauswahl, approval) after a node that points at the end
|
|
246
|
+
node routes *all* its outcomes to the end node — pass `--outcomes` to fan them
|
|
247
|
+
out (e.g. `warteliste:waitlist`).
|
|
248
|
+
- **Nothing is bound implicitly.** A node reads a form only at the address it
|
|
249
|
+
names (`$node.<formNode>.admission.<field>`), and the producer must run on every
|
|
250
|
+
path before it (`[address/notVisible]` otherwise). A `form` node's `smartForm` is
|
|
251
|
+
written as a `{ kind: "literal", value: <slug> }`; with
|
|
252
|
+
`--adopt-form '$node.<formNode>.admission'` it continues that instance instead
|
|
253
|
+
(`adoptForm` holds the address; the adopting node produces nothing — later
|
|
254
|
+
nodes still read the creator's address). Explicit-by-default, matching `offer init`.
|
|
255
|
+
- **`rm` refuses** to remove the start (`eventTrigger`) or `end` node, a node with
|
|
256
|
+
multiple distinct successors (ambiguous splice), and a node any address elsewhere
|
|
257
|
+
still names (its form instance, document, contract, payment, or a value one of
|
|
258
|
+
its tasks writes) — the refusal lists every naming place (config path +
|
|
259
|
+
address); edit those first. Orphans surface at re-validate and block the write.
|
|
260
|
+
- **Progress labels/tones** come from one built-in default table, shared with the
|
|
261
|
+
`offer init` scaffold. An event with no default falls back to its name +
|
|
262
|
+
neutral tone and prints a warning.
|
|
263
|
+
|
|
264
|
+
## Related
|
|
265
|
+
- [Attach a form to a workflow](./attach-form-to-workflow.md) — the `--type form` flow.
|
|
266
|
+
- [Author a SmartForm as code](./author-smartform.md) — build the form itself.
|
|
267
|
+
- `plexus-cli offer --help` — the full `offer …` command reference; `offer validate`
|
|
268
|
+
runs the same publish-gate validators as `offer workflow node add`.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Name what a node reads (addresses)
|
|
2
|
+
|
|
3
|
+
**When to use:** a node must read data another node produces — the form's answers in a
|
|
4
|
+
contract, a value a review task records, a derived price, the contract a countersignature books
|
|
5
|
+
on — or the gate refuses a bundle with an `[address/*]` code. A read **names its
|
|
6
|
+
producer** in the field that reads it: a full address. There are no data edges any more
|
|
7
|
+
(`node.wires` is gone), no form implied by the node and no bare keys. Control edges (`next`) only
|
|
8
|
+
route. What a node may read is what is produced on every path before it.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
- An offer bundle on disk (`offer init`, or `offer export --slug <slug> --out <dir>`; a live
|
|
12
|
+
tenant's bundle only into a private scratch dir — see
|
|
13
|
+
[Edit a live tenant's offer](./edit-live-tenant-offer.md)). Fully offline: every verb runs the
|
|
14
|
+
publish gate on the result and writes nothing when the edit introduces an error.
|
|
15
|
+
- **A bundle exported before addresses were introduced is refused** (`[graph/retiredField]` for `wires`, a binding
|
|
16
|
+
with `path`, `adoptForm: true`; `[address/bareIdentifier]` for a bare key). Do not patch it by
|
|
17
|
+
hand — export it again (the server migration folded the live graph onto addresses).
|
|
18
|
+
- The spelling — **always in single quotes on the command line**, or the shell expands `$node`:
|
|
19
|
+
|
|
20
|
+
| Address | Names | Example (demo tenant `test`) |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `$node.<formNode>.admission` | the form instance a form node creates | `$node.form.admission` |
|
|
23
|
+
| `$node.<formNode>.admission.<field>` | one field (or export, or party leaf `beneficiary` / `signers`) of it | `$node.form.admission.kind.geburtsurkunde` |
|
|
24
|
+
| `$node.<node>.document` | the newest revision of a document node's slot | `$node.platzangebot.document` |
|
|
25
|
+
| `$node.<node>.contract` | a machine-produced contract (`production: resolver`) | `$node.contract.contract` |
|
|
26
|
+
| `$node.<node>.payment` | a payment node's result | `$node.zahlung.payment` |
|
|
27
|
+
| `$task.<task>.<key>` | the value the ONE task `<task>` writes under store key `<key>` | `$task.reducedFee.reducedFee` |
|
|
28
|
+
| `$derived.<key>` | a computed price (`processStore[].derive`) | `$derived.schulgeld` |
|
|
29
|
+
| `$env.applicant.language` | a fact about the application nobody produces | |
|
|
30
|
+
|
|
31
|
+
An adopting form node (`adoptForm`, e.g. `formZusage` in the demo tenant `test`) **produces nothing**: the
|
|
32
|
+
instance stays its creator's, so every later node still reads `$node.form.admission.…`.
|
|
33
|
+
|
|
34
|
+
## Steps
|
|
35
|
+
```bash
|
|
36
|
+
# What arrives at a node: every address it reads, where it is written, who produces it —
|
|
37
|
+
# including what it reads by carrying (the evidence of a task whose value it reads, the
|
|
38
|
+
# sources of a derived price, the price check):
|
|
39
|
+
plexus-cli offer workflow node reads --dir <dir> --node contract
|
|
40
|
+
|
|
41
|
+
# A bare key is refused with the list of what that node can read — use it to pick:
|
|
42
|
+
plexus-cli offer workflow node set-reads --dir <dir> --node contract --address kind.vorname
|
|
43
|
+
# → --address (reads) "kind.vorname": ein nackter Schlüssel …
|
|
44
|
+
# An „Vertrag" (contract) lesbar:
|
|
45
|
+
# Bewerbung Grundschule Havelland › Anmeldung ($node.form.admission)
|
|
46
|
+
# Prüfung › Schulgeld mit Ermäßigung › Einkommensermäßigung ($task.reducedFee.reducedFee)
|
|
47
|
+
# …
|
|
48
|
+
|
|
49
|
+
# The flags that read data (each takes full addresses):
|
|
50
|
+
plexus-cli offer workflow node set-input --dir <dir> --node contract --input schulgeld --address '$derived.schulgeld'
|
|
51
|
+
plexus-cli offer workflow node set-reads --dir <dir> --node contract --from form --field kind.vorname,kind.nachname
|
|
52
|
+
plexus-cli offer workflow node set-requires --dir <dir> --node nachreichen --address '$node.form.admission.kind.masernschutz'
|
|
53
|
+
plexus-cli offer workflow node task set --dir <dir> --node reviewEingereicht --key reducedFee \
|
|
54
|
+
--when '$node.form.admission.ermaessigungBeantragt' --reads '$node.form.admission.gebuehren.einkommensnachweise'
|
|
55
|
+
plexus-cli offer workflow node add --dir <dir> --type setFlag ... --target '$node.form.admission'
|
|
56
|
+
plexus-cli offer workflow node add --dir <dir> --type form ... --adopt-form '$node.form.admission'
|
|
57
|
+
plexus-cli offer workflow node add --dir <dir> --type countersign ... --contract '$node.contract.contract'
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The short form `--from <formNode> --field <path>` (set-input, `*-reads`, `*-requires`) only
|
|
61
|
+
composes `$node.<formNode>.admission.<path>` — it names the node, it never picks one.
|
|
62
|
+
|
|
63
|
+
Which flag sets which field:
|
|
64
|
+
|
|
65
|
+
| Field | Verb / flag |
|
|
66
|
+
|---|---|
|
|
67
|
+
| an input binding | `node set-input --address` (or `--from/--field`) |
|
|
68
|
+
| `reads` (decision / review / governedDocument) | `node set-reads|add-reads|remove-reads --address`; review at add: `node add --reads` |
|
|
69
|
+
| `requires` (completenessCheck) | `node set-requires|add-requires|remove-requires --address`; at add: `--require` |
|
|
70
|
+
| setFlag `target` | `node add|set --target` |
|
|
71
|
+
| form `adoptForm` | `node add --adopt-form` |
|
|
72
|
+
| resolver governedDocument / engagePush `form` | `node set --form '<instance address>'` (engagePush also at add) |
|
|
73
|
+
| countersign / engagePush `contract`, engagePush `payment` | `node add|set --contract|--payment` |
|
|
74
|
+
| roleAssign / completenessCheck `person` | `node add|set --person` |
|
|
75
|
+
| task `when`, `reads`, `documents` | `node task add|set --when|--reads|--documents` (`field:<address>`, `document:$node.<node>.document`; an own slot stays `artifact:<key>`) |
|
|
76
|
+
| task family request | `--family-request-fields <addresses>` or `--family-request-from '$node.<form>.admission'` |
|
|
77
|
+
| record field suggestion | `--field '{…,"suggest":{"kind":"form","address":"$node.form.admission.alleinerziehend"}}'` |
|
|
78
|
+
| `pricingRules[].rows[].when`, `processStore[].derive.reduction` | **no CLI verb** — edit the bundle JSON; a condition's identifiers are canonical addresses (`$task.geschwisterrang.geschwisterRangSchule >= 3`), a reduction is `{ "scope": "task", "owner": "reducedFee", "key": "reducedFee" }` |
|
|
79
|
+
|
|
80
|
+
## In the IDE instead
|
|
81
|
+
The Workflow-IDE offers exactly what the gate accepts — the same rules
|
|
82
|
+
(`addressFieldOptions`), one picker in every editor, never a typed key:
|
|
83
|
+
- **What arrives at a node:** select it — the bottom bar's „Eingang" column lists
|
|
84
|
+
its reads and what it may read (Formular · Dokumente · Zahlungen · Aufgaben ·
|
|
85
|
+
Abgeleitet · Umgebung), each with producer, address and how it is read; a rose
|
|
86
|
+
entry is a read publish refuses (`[address/notVisible]`). Drag an entry onto a
|
|
87
|
+
parameter to bind it.
|
|
88
|
+
- **Node fields** (`reads`, `requires`, setFlag `target`, `adoptForm`, `form` /
|
|
89
|
+
`contract` / `payment`): node → „Details" → „Einstellungen".
|
|
90
|
+
- **Task fields** (`when`, `reads`, `documents`, `familyRequest`, a record's
|
|
91
|
+
suggestion): node → „Details" → „Team".
|
|
92
|
+
- **Pricing-rule conditions and reductions:** header → „Preisregeln" („Adresse
|
|
93
|
+
einfügen" puts the `$task…` identifier into the condition).
|
|
94
|
+
|
|
95
|
+
## Verify
|
|
96
|
+
```bash
|
|
97
|
+
plexus-cli offer validate --dir <dir> # exit 0, no [address/*] error
|
|
98
|
+
plexus-cli offer workflow node reads --dir <dir> --node <id> --json
|
|
99
|
+
plexus-cli --env staging --tenant <t> offer plan --dir <dir> # ~ workflow; address:/reads: rows against the live head (see edit-live-tenant-offer.md)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Idempotency / gotchas
|
|
103
|
+
- **The gate judges every address** before writing: `[address/shape]`, `[address/bareIdentifier]`,
|
|
104
|
+
`[address/unknownOwner]` (no such node / task), `[address/unknownKey]` (the node does not
|
|
105
|
+
produce it — a form produces `admission`, a resolver contract `contract`, not `document`),
|
|
106
|
+
`[address/ownerNotWriter]` (the task does not write that store key), `[address/notVisible]`
|
|
107
|
+
(the producer is not on EVERY path before the reader — on a detour the reader would wait
|
|
108
|
+
forever), `[address/pathNotInTarget]` (the form has no such field), `[address/typeMismatch]`
|
|
109
|
+
(a `when` must be a yes/no export, a binding must fit its slot), `[address/notFormInstance]`
|
|
110
|
+
(`target`, `adoptForm`, `form`, `familyRequest.from` name `$node.<creating form>.admission`
|
|
111
|
+
without a path), `[address/mixedForms]` (one list — node `reads`, `requires`, a task's
|
|
112
|
+
`when` + `reads`, `familyRequest.fields` — names ONE form), `[address/missing]` (setFlag
|
|
113
|
+
`target`, countersign `contract`, a resolver's / engagePush's `form`). Only errors the edit
|
|
114
|
+
INTRODUCES block it; errors the bundle already had are printed as „vorbestehend".
|
|
115
|
+
- **A node reads its own results** (a value one of its tasks writes, its own document slot)
|
|
116
|
+
without dominance; it is still in its read list.
|
|
117
|
+
- **`node rm` refuses while an address names the node** — its form instance, document,
|
|
118
|
+
contract, payment, or a value one of its tasks writes — and lists every place (config path +
|
|
119
|
+
address). Edit those first; nothing is dropped silently.
|
|
120
|
+
- **Renaming a task key or moving a writing task to another node** changes the address
|
|
121
|
+
(`$task.<task>.<key>`): every reader must be edited, and running applications cannot re-pin
|
|
122
|
+
across it.
|
|
123
|
+
- Nothing reaches a live process until `offer apply --publish`; running applications keep their
|
|
124
|
+
pinned version.
|
|
125
|
+
|
|
126
|
+
## Related
|
|
127
|
+
- [Bind a node input](./bind-node-input.md) · [Declare what a document node reads](./declare-node-reads.md)
|
|
128
|
+
- [Add a workflow node](./add-workflow-node.md) · [Countersign the contract](./countersign-node.md) · [Node tasks](./node-tasks.md)
|
|
129
|
+
- [Edit a live tenant's offer](./edit-live-tenant-offer.md) — export, plan and apply against a live tenant
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Give an appointment its Terminaufgabe („Taster Day durchführen")
|
|
2
|
+
|
|
3
|
+
**When to use:** a „Termin vereinbaren" task books an appointment (see
|
|
4
|
+
[Make a task close with an appointment](./task-appointment-result.md)), and the work
|
|
5
|
+
done AT that appointment — hold the interview, run the taster day, give feedback with
|
|
6
|
+
attachments — belongs to whoever hosts it, due a few days after it ends.
|
|
7
|
+
|
|
8
|
+
## The model in three sentences
|
|
9
|
+
|
|
10
|
+
The **Terminaufgabe** is an ordinary task of the same node with
|
|
11
|
+
`appointment: { of: '<booking task>', dueDaysAfterEnd: N }`. It spawns with its round
|
|
12
|
+
and waits („Wartet auf: <booking task>") until the booking binds it: its holder is the
|
|
13
|
+
appointment's **host** (a person of the task's team, or that team's owner on the
|
|
14
|
+
application), its due day the appointment's end + N. It closes with its result — the
|
|
15
|
+
feedback, a recommendation / choice / free text with `files: true` — and holds whatever
|
|
16
|
+
it `blocks` until then.
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
- The booking task exists with `result.kind: appointment`.
|
|
21
|
+
- The Terminaufgabe's team (`--team`) is the team the hosts come from: the booking
|
|
22
|
+
dialog offers only that team and its members, and the server refuses others.
|
|
23
|
+
Teachers who host need cockpit access (`bewerbungsmanagement.applications.read`,
|
|
24
|
+
`aufgaben.items.read/write`).
|
|
25
|
+
- The target environment runs a platform version that knows Terminaufgaben (`offer apply`
|
|
26
|
+
publishes against the deployed gate; if publish does not know `appointment`, ask the
|
|
27
|
+
platform operator).
|
|
28
|
+
|
|
29
|
+
## Steps
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# 1. The booking task no longer blocks the follow-up — the hold is derived.
|
|
33
|
+
plexus-cli offer workflow node task set --dir $WORK/$S --node decision --key tasterDayTermin --clear-blocks
|
|
34
|
+
# 2. The Terminaufgabe: team, link, due rule, feedback with files.
|
|
35
|
+
plexus-cli offer workflow node task add --dir $WORK/$S --node decision --key tasterDayDurchfuehren \
|
|
36
|
+
--title 'Taster Day durchführen' --title-en 'Hold the taster day' --team teachers \
|
|
37
|
+
--appointment-of tasterDayTermin --due-days-after-end 2 \
|
|
38
|
+
--result recommendation --chat all --files
|
|
39
|
+
# 3. If the booking task is part of an optional action, the Terminaufgabe joins it —
|
|
40
|
+
# and the action is repeatable: a cancelled appointment ends the round.
|
|
41
|
+
plexus-cli offer workflow node optional-action set --dir $WORK/$S --node decision --key tasterDay \
|
|
42
|
+
--tasks tasterDayTermin,tasterDayDurchfuehren --repeatable
|
|
43
|
+
plexus-cli offer validate --dir $WORK/$S
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then the live path ([Edit a live tenant's offer](./edit-live-tenant-offer.md)):
|
|
47
|
+
`--env prod offer plan` → `--env prod offer apply --allow-prod --publish` → plan again
|
|
48
|
+
until every row is `same`.
|
|
49
|
+
|
|
50
|
+
## Verify
|
|
51
|
+
|
|
52
|
+
- `offer validate` shows no `[workItems/appointment*]` error.
|
|
53
|
+
- In the cockpit: trigger the action → the Terminaufgabe reads „Wartet auf: …";
|
|
54
|
+
book with a host of the team → the card shows the appointment line
|
|
55
|
+
(„Taster Day · Di., 21.10. 09:00–12:00 · Raum 2 · Gastgeber: …") and the host holds it.
|
|
56
|
+
|
|
57
|
+
## Gotchas
|
|
58
|
+
|
|
59
|
+
- Publish refuses: `of` naming no appointment task of the same node
|
|
60
|
+
(`appointmentOfUnknown`), two Terminaufgaben for one booking task (`appointmentOfTwice`),
|
|
61
|
+
`--due-in-days` beside the end rule or N outside 0–60 (`appointmentTaskDue`), any
|
|
62
|
+
`blocks` aimed at it (`appointmentTaskBlocked`), a different optional action than the
|
|
63
|
+
booking task (`appointmentTaskBinding`), an optional action spawning the booking task
|
|
64
|
+
without `repeatable: true` (`appointmentActionRepeatable` — a cancellation ends the
|
|
65
|
+
round, and without a second one a decision waiting for the action via `requiresOneOf`
|
|
66
|
+
would hang for good), an approval / `when` / `optional` /
|
|
67
|
+
family request / missing result (`appointmentTaskShape`). Warning
|
|
68
|
+
`appointmentChatUnreadable`: the feedback chat is another team's — the host may not
|
|
69
|
+
read it; prefer `all` or the task's own team.
|
|
70
|
+
- Holder and due day have ONE writer, the appointment: „Zuweisen" refuses a bound
|
|
71
|
+
Terminaufgabe, and so does „Wiederöffnen" (edit the feedback text instead).
|
|
72
|
+
- A staff member who hosts an appointment with an open Terminaufgabe cannot be
|
|
73
|
+
withdrawn (`[appointment/hostInUse]`) — change the appointment's host first.
|
|
74
|
+
- Running applications keep their pinned version.
|
|
75
|
+
|
|
76
|
+
## Related
|
|
77
|
+
|
|
78
|
+
- [Make a task close with an appointment](./task-appointment-result.md)
|
|
79
|
+
- [Files on a task result](./task-result-files.md)
|