@smartkomm/plexus-cli 0.0.0-stage → 0.1.0
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 +826 -2
- package/dist/cli.mjs +483 -0
- package/dist/skill/.managed-by-plexus-cli +1 -0
- package/dist/skill/SKILL.md +185 -0
- package/dist/skill/references/accounts/email-changes.md +46 -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 +170 -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/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 +159 -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 +59 -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 +145815 -0
- package/package.json +53 -4
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Author the manifest "meta" layer
|
|
2
|
+
|
|
3
|
+
**When to use:** configure an offer's manifest settings that aren't the form or the
|
|
4
|
+
workflow — the applicant mode, signature level, application window, total capacity,
|
|
5
|
+
cohort-selection strategy, the `roleInstances` (legal roles like Erziehungsberechtigte),
|
|
6
|
+
and the `leistungen` — entirely from the CLI instead of hand-editing `manifest.json`.
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
- An offer bundle on disk (`offer init …` or `offer export …`).
|
|
10
|
+
- Authoring is **fully offline**; only `offer apply --publish` needs an API token of a
|
|
11
|
+
user who may configure the tenant's offers (a tenant admin — see
|
|
12
|
+
[Add a tenant admin](../users/add-tenant-admin.md)).
|
|
13
|
+
- An installed, current `plexus-cli` (`plexus-cli offer manifest set --help`).
|
|
14
|
+
|
|
15
|
+
## Steps
|
|
16
|
+
```bash
|
|
17
|
+
# Top-level settings. Enum values are validated fail-loud (self|proxy|both,
|
|
18
|
+
# wet_ink|FES|QES, immediate|batch, arrival|manual).
|
|
19
|
+
plexus-cli offer manifest set --dir <bundle> \
|
|
20
|
+
--applicant-mode both --signature-level FES \
|
|
21
|
+
--window-start 2026-02-01 --window-end 2026-05-31 --capacity 30
|
|
22
|
+
|
|
23
|
+
# Selection config (FCFS/committee/Stichtag are emergent from these knobs).
|
|
24
|
+
# Set both required knobs the first time.
|
|
25
|
+
plexus-cli offer manifest set --dir <bundle> \
|
|
26
|
+
--selection-release batch --selection-waitlist-order manual \
|
|
27
|
+
--selection-auto-promote --selection-deadline 2026-06-01
|
|
28
|
+
|
|
29
|
+
# roleInstances — legal/contract roles. --custom-role-id binds the tenant custom-role.
|
|
30
|
+
# A role's (and a service's) label is { de, en }: --label AND --label-en on add.
|
|
31
|
+
plexus-cli offer role add --dir <bundle> --key erzb1 --label "Erziehungsberechtigte:r 1" --label-en "Guardian 1" \
|
|
32
|
+
--custom-role-id role_eb --min 1 --max 2 --legally-relevant --can-sign
|
|
33
|
+
plexus-cli offer role set --dir <bundle> --key erzb1 --max 3 --requires-evidence
|
|
34
|
+
plexus-cli offer role list --dir <bundle>
|
|
35
|
+
|
|
36
|
+
# leistungen — selectable/assigned services.
|
|
37
|
+
plexus-cli offer leistung add --dir <bundle> --key betreuung --label "Nachmittagsbetreuung" --label-en "Afternoon care" --selectable-by-applicant
|
|
38
|
+
|
|
39
|
+
# Display order among the unit's offers (lower = first; "" clears). Handle-level
|
|
40
|
+
# property like the title — lives on bundle.yaml, applied via createManifest.
|
|
41
|
+
plexus-cli offer manifest set --dir <bundle> --sort-order 20
|
|
42
|
+
|
|
43
|
+
# Validate + publish.
|
|
44
|
+
plexus-cli offer validate --dir <bundle>
|
|
45
|
+
plexus-cli --env <env> offer apply --publish --dir <bundle>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Verify
|
|
49
|
+
```bash
|
|
50
|
+
plexus-cli offer role list --dir <bundle> # roleInstances reflect the edits
|
|
51
|
+
plexus-cli offer validate --dir <bundle> # "local validation clean"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Idempotency / gotchas
|
|
55
|
+
- **Offer title is a BARE name — never embed the run/year or levels.** The offer
|
|
56
|
+
`label` (set via `offer init/set --title`) is just the program name, e.g.
|
|
57
|
+
"Grundschule" / "Primary School". The school-year cycle is a separate entity
|
|
58
|
+
(`program_runs.name`, e.g. "2026/27") and grade levels are `program_stages` — the UI
|
|
59
|
+
shows runs nested under the offer (internal sidebar) and as a separate dropdown
|
|
60
|
+
(external portal), so it **never composes** them into the title. Putting
|
|
61
|
+
"· Aufnahme 2026/27" in the title makes the cycle show twice. Levels/runs are managed
|
|
62
|
+
with `offer level …` / `offer run …`, not the title.
|
|
63
|
+
- **`--title` (offer label) is the offer-title SoT — `--program-name` is separate.**
|
|
64
|
+
`offer set --title` (+ `--title-en`) writes the offer title (`offer_versions.label`), the single source the staff Angebote
|
|
65
|
+
tree, external portal, and applicant "Meine Bewerbungen" all read. The underlying
|
|
66
|
+
master-data `programs.name` is a *copy* made at authoring time that only shows in the
|
|
67
|
+
staff master-data admin; it can drift from the label. Use `offer set --program-name
|
|
68
|
+
"<bare name>" --program-name-en "<English>"` (→ `programAdmin.updateProgram`) to bring a drifted program name back in
|
|
69
|
+
line — e.g. after fixing a label that had been authored with a run/year baked in. You
|
|
70
|
+
almost never need `--program-name`; reach for it only to clean up master-data drift.
|
|
71
|
+
- **`--sort-order` orders offers within a unit** (lower = first); the sibling unit
|
|
72
|
+
order is set as code via tenant config (`TenantConfigUnitDefinition.sortOrder`). Both
|
|
73
|
+
apply to the internal Angebote sidebar AND the external `/e` portal; offers/units
|
|
74
|
+
without a sortOrder fall back to creation-time / alphabetical respectively.
|
|
75
|
+
- **Validate-before-write.** Every mutator runs the real publish gate first; on any
|
|
76
|
+
error it writes **nothing**.
|
|
77
|
+
- **Enum values are checked client-side, fail-loud.** The manifest publish gate
|
|
78
|
+
(`validateManifestBlob`) enforces required fields + roleInstances/leistungen *structure*
|
|
79
|
+
(unique keys, customRoleId, cardinality) but NOT the enum *values* of
|
|
80
|
+
applicantMode/signatureLevel/selection — so the CLI validates those at the boundary.
|
|
81
|
+
- **`customRoleId` resolution is server-side.** The CLI authors the binding; whether
|
|
82
|
+
`--custom-role-id` resolves to a real tenant custom-role is checked at runtime, not
|
|
83
|
+
offline (the bundle has no tenant role catalog).
|
|
84
|
+
- **No graph↔role cross-check.** The static graph carries no reference to a
|
|
85
|
+
`roleInstance.key` (those are referenced only by runtime persons; approval approvers
|
|
86
|
+
reference tenant permission *groups*, validated server-side) — so there is nothing
|
|
87
|
+
offline to cross-validate beyond the structure the manifest gate already enforces.
|
|
88
|
+
- **`manifest set` is additive per-flag.** Pass only the flags you want to change;
|
|
89
|
+
selection needs both `--selection-release` and `--selection-waitlist-order` present
|
|
90
|
+
the first time so a half-config is never written.
|
|
91
|
+
|
|
92
|
+
## Related
|
|
93
|
+
- [Author a SmartForm as code](./author-smartform.md) · [Edit a workflow node](./edit-workflow-node.md).
|
|
94
|
+
- Command reference: `plexus-cli offer manifest --help`, `plexus-cli offer role --help`, `plexus-cli offer leistung --help`.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Author a SmartForm as code
|
|
2
|
+
|
|
3
|
+
**When to use:** build or change a customer's application form (sections, fields,
|
|
4
|
+
validation/visibility rules, outcomes) entirely from the CLI — then publish a new
|
|
5
|
+
version — without hand-editing `smartforms/<slug>.json`.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
- An offer bundle on disk (`offer init …` or `offer export …`).
|
|
9
|
+
- **Authoring is fully offline** — no deployment, no auth. `offer apply --publish`
|
|
10
|
+
(the publish step) needs an API token of a user who may configure the tenant's offers
|
|
11
|
+
(a tenant admin — see [Add a tenant admin](../users/add-tenant-admin.md)).
|
|
12
|
+
- An installed, current `plexus-cli` (`plexus-cli offer form --help`).
|
|
13
|
+
|
|
14
|
+
## Steps
|
|
15
|
+
```bash
|
|
16
|
+
# 0. (If the bundle has no form yet, or you want a second form) scaffold one.
|
|
17
|
+
# A new form starts with one empty section + a default `standard` outcome.
|
|
18
|
+
plexus-cli offer form add --dir <bundle> --key <form-slug> --label "<Label>" --label-en "<English>"
|
|
19
|
+
|
|
20
|
+
# 1. Add sections (nest with --parent; gate with --visible-if).
|
|
21
|
+
plexus-cli offer form section add --dir <bundle> [--form <slug>] \
|
|
22
|
+
--id section.kind --label "Kind" --label-en "Child" [--visible-if "<expr>"]
|
|
23
|
+
|
|
24
|
+
# 2. Add fields. Types: string|boolean|date|enum|email|tel|address|docRef.
|
|
25
|
+
# enum options come from exactly ONE source (mutually exclusive):
|
|
26
|
+
# --enum a,b,c inline options
|
|
27
|
+
# --enum-ref <catalog> static central catalog (konfession, geschlecht, …)
|
|
28
|
+
# --enum-list-key <list> tenant custom_field_lists key (runtime-resolved;
|
|
29
|
+
# central management — nationalities, languages,
|
|
30
|
+
# countries, …). Add --multiple for multi-select.
|
|
31
|
+
plexus-cli offer form field add --dir <bundle> [--form <slug>] \
|
|
32
|
+
--section section.kind --key kind.vorname --type string --required --bucket kind --label "Vorname" --label-en "First name"
|
|
33
|
+
plexus-cli offer form field add --dir <bundle> --section section.kind \
|
|
34
|
+
--key kind.geschlecht --type enum --enum-list-key gender --label "Geschlecht" --label-en "Gender"
|
|
35
|
+
plexus-cli offer form field add --dir <bundle> --section section.kind \
|
|
36
|
+
--key kind.nationalitaeten --type enum --enum-list-key nationalities --multiple --label "Staatsangehörigkeit(en)" --label-en "Nationality(ies)"
|
|
37
|
+
# NB: --enum-list-key requires the list to exist on the tenant BEFORE publish
|
|
38
|
+
# (config apply the list first) — else the publish gate throws form.unknown-enum-list.
|
|
39
|
+
|
|
40
|
+
# 3. Attach validation / visibility rules to a field.
|
|
41
|
+
# "Dieses Feld soll Pflicht sein, wenn …" → --required-if.
|
|
42
|
+
# "Dieses Feld soll sich ändern" → --visible-if / --disabled-if.
|
|
43
|
+
plexus-cli offer form field set --dir <bundle> --key kind.nachweis \
|
|
44
|
+
--required-if "kind.vorname != null"
|
|
45
|
+
|
|
46
|
+
# 4. Add outcomes (the branch the form emits to the workflow).
|
|
47
|
+
plexus-cli offer form outcome add --dir <bundle> --id sonderfall \
|
|
48
|
+
--when "kind.konfession == 'sonstige'" --fields kind.vorname
|
|
49
|
+
|
|
50
|
+
# 4b. (Full depth) effects, repeated (arrays-of-records), and computed fields.
|
|
51
|
+
# person/organization/relationship/fieldMap materialize parties + links.
|
|
52
|
+
plexus-cli offer form effect add --dir <bundle> --id p.kind --kind person \
|
|
53
|
+
--bucket kind --policy auto-on-any-write --name-from "kind.vorname,kind.nachname" --role applicant
|
|
54
|
+
plexus-cli offer form repeated add --dir <bundle> --section section.kind \
|
|
55
|
+
--key geschwister --item-fields "name:string,alter:string" --min 0 --max 5
|
|
56
|
+
plexus-cli offer form computed add --dir <bundle> --section section.kind \
|
|
57
|
+
--key kind.hasName --expression "kind.vorname != null"
|
|
58
|
+
|
|
59
|
+
# 5. Validate offline against the REAL publish gate, then publish a new version.
|
|
60
|
+
plexus-cli offer validate --dir <bundle>
|
|
61
|
+
plexus-cli --env <env> offer apply --publish --dir <bundle>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`--form` is optional when the bundle carries exactly one form; with several forms
|
|
65
|
+
it is required (the CLI fails loud listing the choices).
|
|
66
|
+
|
|
67
|
+
## Verify
|
|
68
|
+
```bash
|
|
69
|
+
plexus-cli offer form list --dir <bundle> # forms + field/outcome counts + bound?
|
|
70
|
+
plexus-cli offer form show --dir <bundle> --form <s> # per-field type/required/flags
|
|
71
|
+
plexus-cli offer validate --dir <bundle> # "local validation clean"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Idempotency / gotchas
|
|
75
|
+
- **Every mutator validates before writing.** On any publish-gate error (or a
|
|
76
|
+
malformed `--visible-if`/`--when` expression) the command writes **nothing** and
|
|
77
|
+
prints the issues — the bundle is never left half-edited.
|
|
78
|
+
- **Field-key stability (publish invariant).** `publishSmartForm` lets you **add**
|
|
79
|
+
fields across versions but **refuses to remove** a field key that a prior published
|
|
80
|
+
version had. `offer form field rm` / `section rm` warn about this — a later
|
|
81
|
+
`apply --publish` over an already-published form will be rejected by the server.
|
|
82
|
+
- **Re-publishing = new version.** Each `offer apply --publish` with a changed form
|
|
83
|
+
definition creates a new `smart_form_versions` row (never edits in place).
|
|
84
|
+
- **Workflows pin the form version.** A published workflow is pinned to the form
|
|
85
|
+
version that was current at *workflow*-publish time (`smartFormPins`). A new form
|
|
86
|
+
version does **not** propagate automatically — the workflow must be re-published.
|
|
87
|
+
`offer apply --publish` over the **whole bundle** re-publishes the workflow too, so
|
|
88
|
+
the full-bundle path picks up the new form; a standalone form publish would leave
|
|
89
|
+
existing workflows on the old version.
|
|
90
|
+
- **Effects / repeated / computed fields** are CLI-authorable (`offer form effect|repeated|computed`).
|
|
91
|
+
The offline gate checks structure + refs + effect-graph + `nameFrom`/`fieldMap.from`
|
|
92
|
+
resolution; the **catalog-dependent** checks (effect `role` → tenant role, `relKind` →
|
|
93
|
+
relationship field, fieldMap `to` → custom field) run **server-side** at `apply --publish`
|
|
94
|
+
and fail loud there if the tenant catalog doesn't have them.
|
|
95
|
+
- An unbound form (no workflow form node binds it yet) is a **warning**, not an error
|
|
96
|
+
— bind it with `offer workflow node add --type form --form <slug>`.
|
|
97
|
+
|
|
98
|
+
## Related
|
|
99
|
+
- [Attach a form to a workflow](./attach-form-to-workflow.md) — bind the form to a workflow
|
|
100
|
+
form node (`offer workflow node add --type form`).
|
|
101
|
+
- [Add a tenant admin](../users/add-tenant-admin.md) — the token `offer apply --publish` needs.
|
|
102
|
+
- Command reference: `plexus-cli offer form --help`.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Bind a node input to an address (form field, task value, derived price, applicant language)
|
|
2
|
+
|
|
3
|
+
**When to use:** a node's input slot must read a value produced elsewhere — the applicant's
|
|
4
|
+
language for a template letter (`$env.applicant.language`), the tuition a rule set derives
|
|
5
|
+
(`$derived.schulgeld`), a value a task records (`$task.reducedFee.reducedFee`), a form field
|
|
6
|
+
(`$node.form.admission.kind.vorname`) — or carry a fixed value, or go back to its default.
|
|
7
|
+
Replaces hand edits of `inputs` in the bundle's `workflow.json`.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
- An offer bundle on disk (`offer export --slug <slug> --out <dir>`). Fully offline: the command
|
|
11
|
+
runs the publish gate on the result and writes nothing when the edit introduces an error. A
|
|
12
|
+
bundle exported before addresses were introduced is refused (`[graph/retiredField]`: a binding with `path`, `wires`)
|
|
13
|
+
— export it again.
|
|
14
|
+
- The input is a **slot** the node declares. For a template document (`production: 'template'`)
|
|
15
|
+
the slots are its `template.fields` — add the field first (bundle or workflow IDE), then bind.
|
|
16
|
+
`offer workflow node list --dir <dir>` shows the nodes; an unknown slot is refused with the
|
|
17
|
+
node's slots.
|
|
18
|
+
- The **address** names its producer, which must run on every path before the node — the model
|
|
19
|
+
and the spelling: [Name what a node reads](./addresses.md). Quote it with single quotes.
|
|
20
|
+
|
|
21
|
+
## Steps
|
|
22
|
+
```bash
|
|
23
|
+
# The offer letter in the applicant's language (demo tenant `test`: platzangebot, field `sprache`):
|
|
24
|
+
plexus-cli offer workflow node set-input --dir <dir> --node platzangebot --input sprache \
|
|
25
|
+
--address '$env.applicant.language'
|
|
26
|
+
|
|
27
|
+
# Tuition: the derived price (sibling discount, then the reduction `reducedFee` records):
|
|
28
|
+
plexus-cli offer workflow node set-input --dir <dir> --node contract --input schulgeld \
|
|
29
|
+
--address '$derived.schulgeld'
|
|
30
|
+
|
|
31
|
+
# A form field — full address, or the short form that composes the same address:
|
|
32
|
+
plexus-cli offer workflow node set-input --dir <dir> --node contract --input startDate \
|
|
33
|
+
--address '$node.form.admission.kind.einschulung'
|
|
34
|
+
plexus-cli offer workflow node set-input --dir <dir> --node contract --input startDate \
|
|
35
|
+
--from form --field kind.einschulung
|
|
36
|
+
|
|
37
|
+
# A fixed value (JSON: a text needs its quotes):
|
|
38
|
+
plexus-cli offer workflow node set-input --dir <dir> --node platzangebot --input sprache --literal '"de"'
|
|
39
|
+
|
|
40
|
+
# Back to the slot's default:
|
|
41
|
+
plexus-cli offer workflow node set-input --dir <dir> --node platzangebot --input sprache --clear
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The command prints the cell before and after (`binding $env.applicant.language`, `literal "de"`,
|
|
45
|
+
`— (default)`).
|
|
46
|
+
|
|
47
|
+
## Verify
|
|
48
|
+
```bash
|
|
49
|
+
plexus-cli offer validate --dir <dir>
|
|
50
|
+
plexus-cli offer workflow node reads --dir <dir> --node contract # the binding is listed, origin „geschrieben"
|
|
51
|
+
plexus-cli --env staging --tenant <t> offer plan --dir <dir> # expect: ~ workflow
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Idempotency / gotchas
|
|
55
|
+
- Exactly one of `--address`, `--from`+`--field`, `--literal`, `--clear`. A bare key
|
|
56
|
+
(`--address applicant.language`, `--address schulgeld`) is refused with the addresses visible at
|
|
57
|
+
the node — there is no default source and no guessing.
|
|
58
|
+
- A literal is JSON: `"de"`, `3`, `true`, `null`, or a priced item `{"itemKey":…,"grantKeys":[…]}`
|
|
59
|
+
— and must fit the slot's value type: an option field takes one
|
|
60
|
+
of its options, a party slot (a roleAssign's `person`) only a binding. This check runs in the
|
|
61
|
+
verb; the publish gate does not (yet) check literals against their slot type.
|
|
62
|
+
- An end node's outcome cells are set with `offer workflow node set --outcome|--terminal-status|--termination-kind`.
|
|
63
|
+
- `--clear` also removes a cell no slot declares (a dead cell from an old edit).
|
|
64
|
+
- The gate decides whether the address can be read here, before writing: `[address/unknownOwner]`,
|
|
65
|
+
`[address/unknownKey]`, `[address/pathNotInTarget]` (the form has no such field / the stored value no
|
|
66
|
+
such leaf), `[address/notVisible]` (the producer does not run on every path before the node),
|
|
67
|
+
`[address/typeMismatch]` (the value does not fit the slot), `[governedDocument/envNotCovered]`
|
|
68
|
+
(a language field needs an option per language `$env.applicant.language` can deliver), the
|
|
69
|
+
uncovered-input contract. An error the bundle already had is named as „vorbestehend" and does
|
|
70
|
+
not block this edit.
|
|
71
|
+
- Re-running with the same value rewrites the same cell (no change).
|
|
72
|
+
- Nothing reaches a live process until `offer apply --publish`.
|
|
73
|
+
|
|
74
|
+
## Related
|
|
75
|
+
- [Name what a node reads](./addresses.md) · [Generate a letter from a Word template](./template-document-node.md)
|
|
76
|
+
- [Declare what a document node reads](./declare-node-reads.md) · [Edit a live tenant's offer](./edit-live-tenant-offer.md)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Wait for outstanding documents (completeness check)
|
|
2
|
+
|
|
3
|
+
**When to use:** a school lets the process run on while documents are still
|
|
4
|
+
missing — the Masernschutz-Nachweis, the birth certificate — and wants it to end
|
|
5
|
+
only once they are there, with a deadline („erst wenn wirklich alle Unterlagen da
|
|
6
|
+
sind, ist es vorbei"). The `completenessCheck` node names SmartForm fields
|
|
7
|
+
(`requires`) and a deadline. Entered with nothing missing it completes at once;
|
|
8
|
+
otherwise the family gets its fields in **one** task „Offene Felder beantworten" due by the
|
|
9
|
+
deadline, the optional guest role is granted, and the node parks until the last
|
|
10
|
+
field arrives (`complete`) or a person decides: „Ohne Nachreichung abschließen"
|
|
11
|
+
(`acceptedIncomplete`) or „Bewerbung ablehnen" (`rejected`). The family's
|
|
12
|
+
withdrawal arrives as `absage`.
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
- An offer bundle on disk: `offer init` for a new one, or a **fresh export** of
|
|
16
|
+
the target environment into a private scratch dir (a live tenant's bundle is
|
|
17
|
+
never kept in a shared or versioned place — the server is its source of truth).
|
|
18
|
+
- The fields exist in the form the bundle's form node pins, as **entered,
|
|
19
|
+
non-repeated** fields (a computed key or a field inside a repeated group
|
|
20
|
+
cannot be handed in). Each is named by its full address,
|
|
21
|
+
`$node.<formNode>.admission.<field>` — the form node that CREATES the
|
|
22
|
+
instance, in single quotes. A bundle exported before addresses were introduced is
|
|
23
|
+
refused — export again.
|
|
24
|
+
- For a guest role: a **leaf** role in the tenant's role catalog (e.g.
|
|
25
|
+
`gasthoerer`) — checked at `offer apply --publish`, not offline.
|
|
26
|
+
- The graph has somewhere to send `rejected` and `absage` (the paths to the
|
|
27
|
+
end of a rejection and of a withdrawal — an end with `dataDecision: discard`
|
|
28
|
+
takes the application's records back itself, no discard node needed).
|
|
29
|
+
|
|
30
|
+
## Steps
|
|
31
|
+
```bash
|
|
32
|
+
# 1. Export what is live (read-only).
|
|
33
|
+
plexus-cli --env staging --tenant <tenant> offer export --slug <offer> --out ./bundle
|
|
34
|
+
|
|
35
|
+
# 2. Add the check. Every flag below except --guest-role/--person is required:
|
|
36
|
+
# - at least one --require address (repeatable, comma-separated),
|
|
37
|
+
# - exactly one deadline: --deadline-days <n> (calendar days after entering)
|
|
38
|
+
# OR --deadline-until <YYYY-MM-DD> (a fixed due day, tenant zone),
|
|
39
|
+
# - the `requested` letter (the node type declares it REQUIRED),
|
|
40
|
+
# - rejected/absage routed to the rejection/withdrawal paths.
|
|
41
|
+
# `requires` and the guest role's person name the form they read; nothing is
|
|
42
|
+
# bound implicitly (a bare key is refused with the addresses the node can read).
|
|
43
|
+
plexus-cli offer workflow node add --dir ./bundle --type completenessCheck \
|
|
44
|
+
--id nachreichen --label "Vollständigkeitsprüfung" --label-en "Completeness check" \
|
|
45
|
+
--require '$node.form.admission.kind.masernschutz,$node.form.admission.kind.geburtsurkunde' \
|
|
46
|
+
--deadline-days 14 \
|
|
47
|
+
--guest-role gasthoerer --person '$node.form.admission.beneficiary' \
|
|
48
|
+
--notification requested=admission.documentsRequested \
|
|
49
|
+
--outcomes complete:end,acceptedIncomplete:end,rejected:joinAbsage,absage:endAbsage
|
|
50
|
+
|
|
51
|
+
# Later edits:
|
|
52
|
+
plexus-cli offer workflow node add-requires --dir ./bundle --node nachreichen --address '$node.form.admission.kind.impfpass'
|
|
53
|
+
plexus-cli offer workflow node remove-requires --dir ./bundle --node nachreichen --from form --field kind.impfpass
|
|
54
|
+
plexus-cli offer workflow node set-requires --dir ./bundle --node nachreichen --from form --field kind.masernschutz
|
|
55
|
+
plexus-cli offer workflow node set --dir ./bundle --id nachreichen --deadline-until 2026-10-15
|
|
56
|
+
plexus-cli offer workflow node set --dir ./bundle --id nachreichen --guest-role "" # drops role AND person
|
|
57
|
+
plexus-cli offer workflow node set-notification --dir ./bundle \
|
|
58
|
+
--node nachreichen --event requested --template admission.documentsRequested
|
|
59
|
+
|
|
60
|
+
# 3. Validate offline, see the difference, publish.
|
|
61
|
+
plexus-cli offer validate --dir ./bundle
|
|
62
|
+
plexus-cli --env staging offer plan --dir ./bundle
|
|
63
|
+
plexus-cli --env staging offer apply --publish --dir ./bundle
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Verify
|
|
67
|
+
```bash
|
|
68
|
+
plexus-cli offer workflow node list --dir ./bundle
|
|
69
|
+
# nachreichen completenessCheck … complete→…, acceptedIncomplete→…, rejected→…, absage→…
|
|
70
|
+
# letters: requested→admission.documentsRequested
|
|
71
|
+
jq '.graph.nodes[] | select(.id=="nachreichen") | {requires, deadline, guestRole, inputs}' ./bundle/workflow.json
|
|
72
|
+
plexus-cli offer workflow node reads --dir ./bundle --node nachreichen
|
|
73
|
+
```
|
|
74
|
+
After publish, a fresh application that reaches the node with a field empty
|
|
75
|
+
shows the family task „Offene Felder beantworten" and the board column of the same
|
|
76
|
+
name; a deployment that does not send mail records the letter in Dev Rail
|
|
77
|
+
→ „Mail" instead.
|
|
78
|
+
|
|
79
|
+
## Idempotency / gotchas
|
|
80
|
+
- **The `requested` letter is required.** Without a binding the family would get
|
|
81
|
+
no mail about the request, so every write refuses it
|
|
82
|
+
(`[notification/missingBinding]`) — bind it in the `add` call with
|
|
83
|
+
`--notification requested=<templateId>`. `--disabled` counts as missing.
|
|
84
|
+
Offline validation cannot see the letter catalog: a misspelled template id is
|
|
85
|
+
caught only at `offer apply --publish`.
|
|
86
|
+
- **Deadline rules:** exactly one form. `--deadline-days` is a whole number 0–366
|
|
87
|
+
(`[completenessCheck/deadlineDays]`); `--deadline-until` must be a real
|
|
88
|
+
calendar day (`2026-02-30` is refused, `[completenessCheck/deadlineDate]`).
|
|
89
|
+
Passing both, or neither on `add`, fails before anything is written. Past the
|
|
90
|
+
deadline nothing happens by itself: the family and the responsible person are
|
|
91
|
+
told, and a person decides.
|
|
92
|
+
- **Guest role:** `--guest-role` needs `--person`, and `--person` needs
|
|
93
|
+
`--guest-role` — nothing is defaulted. The person is a party of a form,
|
|
94
|
+
`$node.form.admission.beneficiary` (the beneficiary the form's contract parties
|
|
95
|
+
name), read live at that address whenever the role is granted or ended; a form
|
|
96
|
+
that does not run before the check is `[address/notVisible]`. The role must be a **leaf** tenant
|
|
97
|
+
role (`[completenessCheck/unknownRole]`, `[completenessCheck/nonLeafRole]`,
|
|
98
|
+
both at publish). It is granted only when something is missing, ended on
|
|
99
|
+
`complete` / `rejected` / `absage`, and **kept on `acceptedIncomplete`** — the
|
|
100
|
+
school still has to know whose documents are outstanding. A role the person
|
|
101
|
+
already held is never ended.
|
|
102
|
+
- **Entering with nothing missing completes automatically** — no request, no
|
|
103
|
+
letter, no role; the node passes straight through on `complete`.
|
|
104
|
+
- **Where it lands:** without `--after` the check is spliced in front of the
|
|
105
|
+
FIRST `end` node in `workflow.json` (every edge into that end now enters the
|
|
106
|
+
check) — make sure that is the positive end. With `--after <node>` the
|
|
107
|
+
anchor's FIRST onward edge is the one rerouted; on an anchor with several
|
|
108
|
+
outcomes (a contract: `signed` + `absage`) that may be the wrong one. Check
|
|
109
|
+
with `node list` and fix with `node set --outcomes`.
|
|
110
|
+
- **Routing:** `complete`, `rejected` and `absage` must be routed
|
|
111
|
+
(`[completenessCheck/unroutedOutcome]`); the staff buttons appear only for the
|
|
112
|
+
outcomes that are wired. Left to the splice default, `rejected`/`absage` run
|
|
113
|
+
into whatever follows (usually the positive end) — `add` warns about that; route
|
|
114
|
+
them with `--outcomes`.
|
|
115
|
+
- **`requires` edits:** only a completenessCheck carries `requires`
|
|
116
|
+
(`*-requires` on another node fails; for fields a decider reads use
|
|
117
|
+
`*-reads`). Removing the last address is refused (`[completenessCheck/missingRequires]`);
|
|
118
|
+
two forms in one list are `[address/mixedForms]`, a field the form lacks is
|
|
119
|
+
`[address/pathNotInTarget]`.
|
|
120
|
+
Only errors an edit introduces block it.
|
|
121
|
+
- **Hand-authored boards (known CLI gap):** `node add` derives the progress
|
|
122
|
+
states and appends the check's column „Unterlagen nachreichen"; its
|
|
123
|
+
`rejected`/`withdrawn` states go into the global `col.abgelehnt` /
|
|
124
|
+
`col.abgesagt` columns. A bundle whose board already names its own exit
|
|
125
|
+
columns (e.g. a `c.absage` column labelled „Abgelehnt")
|
|
126
|
+
then carries two columns with one label, and `node add` refuses the write —
|
|
127
|
+
for ANY node type with such an event (`waitlist`, `decision`, …), not only this
|
|
128
|
+
one. Until `node add` can map events onto existing states, author the node in
|
|
129
|
+
`workflow.json` + the states/columns/wiring in `manifest.json`/`workflow.json`
|
|
130
|
+
by hand (shape: the Verify `jq` output above) and gate it with `offer validate`
|
|
131
|
+
/ `offer apply --publish`. Scaffolded bundles (`offer init`) are not affected.
|
|
132
|
+
- **Running applications keep their pinned version** — see
|
|
133
|
+
[Declare what a document node reads](./declare-node-reads.md) for re-pin / wipe.
|
|
134
|
+
|
|
135
|
+
## Related
|
|
136
|
+
- [Add a workflow node](./add-workflow-node.md) · [Edit a workflow node](./edit-workflow-node.md) · [Release an offer bundle](./publish-offer-bundle.md)
|
|
137
|
+
- [Reword a letter](../notifications/reword-a-letter.md)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Countersign the contract („Unterschrift (intern)")
|
|
2
|
+
|
|
3
|
+
**When to use:** the school countersigns the admission contract the family signed. A `countersign` node after the contract opens one row „Vertrag gegenzeichnen" for a
|
|
4
|
+
team: the clerk downloads the family's signed copy, signs it for the Träger, uploads the
|
|
5
|
+
scan with the date. That opens the Träger's own Signaturvorgang on the revision the family
|
|
6
|
+
signed and books the Träger („vertreten durch <Mitarbeiter/in>") on it, so the contract is
|
|
7
|
+
signed and the family's portal shows the countersigned paper; it sends the node's
|
|
8
|
+
`countersigned` letter, and the contract's event moves the node along `countersigned`. It replaces
|
|
9
|
+
the retired task result `countersignature` (`--result countersignature` is now an unknown
|
|
10
|
+
kind) and its review host node.
|
|
11
|
+
|
|
12
|
+
## Prerequisites
|
|
13
|
+
- A bundle on disk (`offer export`, a live tenant's only into a private scratch dir —
|
|
14
|
+
[Edit a live tenant's offer](./edit-live-tenant-offer.md)). Authoring is offline.
|
|
15
|
+
- The contract node is a `governedDocument` with `production: "resolver"` and
|
|
16
|
+
`responseStrategy: "signedUploadChecked"` (the school checks the signed return; only that
|
|
17
|
+
freezes a revision with declared signers) — `[countersign/sourceNotCheckedContract]`
|
|
18
|
+
otherwise.
|
|
19
|
+
- A team (permission-group key) that countersigns, e.g. `schulverwaltung`.
|
|
20
|
+
|
|
21
|
+
## Steps
|
|
22
|
+
```bash
|
|
23
|
+
# 1. The contract's accepting edge (`signed`) and what follows it:
|
|
24
|
+
plexus-cli offer workflow node list --dir <dir>
|
|
25
|
+
|
|
26
|
+
# 2. Splice the countersignature onto that edge. Required: the team, the contract's
|
|
27
|
+
# address (a resolver contract is `$node.<contractNode>.contract`) and the
|
|
28
|
+
# letter (the node type declares it REQUIRED).
|
|
29
|
+
plexus-cli offer workflow node add --dir <dir> --type countersign \
|
|
30
|
+
--id gegenzeichnung --label "Gegenzeichnung" --label-en "Countersignature" \
|
|
31
|
+
--on-edge contract:signed \
|
|
32
|
+
--assign-team schulverwaltung \
|
|
33
|
+
--title "Vertrag gegenzeichnen" --title-en "Countersign the contract" \
|
|
34
|
+
--due-in-days 5 \
|
|
35
|
+
--contract '$node.contract.contract' \
|
|
36
|
+
--notification countersigned=admission.contractCountersigned
|
|
37
|
+
|
|
38
|
+
# Optional: put its board event on an existing state instead of a new
|
|
39
|
+
# „Gegenzeichnung" column: --progress countersigning=<stateId>
|
|
40
|
+
|
|
41
|
+
# 3. Validate, plan, publish.
|
|
42
|
+
plexus-cli offer validate --dir <dir>
|
|
43
|
+
plexus-cli --env staging --tenant <t> offer plan --dir <dir>
|
|
44
|
+
plexus-cli --env staging --tenant <t> offer apply --publish --dir <dir>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Verify
|
|
48
|
+
```bash
|
|
49
|
+
plexus-cli offer workflow node list --dir <dir>
|
|
50
|
+
# contract governedDocument … signed→gegenzeichnung
|
|
51
|
+
# gegenzeichnung countersign … countersigned→<old target>
|
|
52
|
+
# letters: countersigned→admission.contractCountersigned
|
|
53
|
+
plexus-cli offer workflow node reads --dir <dir> --node gegenzeichnung # $node.contract.contract, written in nodes.gegenzeichnung.contract
|
|
54
|
+
jq '.graph.nodes[] | select(.type=="countersign")' <dir>/workflow.json
|
|
55
|
+
```
|
|
56
|
+
After publish, an application whose signed return the school accepted parks on the node;
|
|
57
|
+
the team's queue shows „Vertrag gegenzeichnen", the family's portal „Die Schule zeichnet
|
|
58
|
+
Ihren Vertrag gegen". After the upload the revision reads „3 von 3" and the family gets the
|
|
59
|
+
letter (on a deployment that does not send mail: Dev Rail → „Mail").
|
|
60
|
+
|
|
61
|
+
## Idempotency / gotchas
|
|
62
|
+
- **The publish rules:** `[countersign/assign]` (no team), `[countersign/dueInDays]` (a whole
|
|
63
|
+
number ≥ 1), `[countersign/sourceNotCheckedContract]`, `[countersign/twice]` (one
|
|
64
|
+
countersignature per contract), `[address/missing]` (no `contract`),
|
|
65
|
+
`[address/unknownKey]` (a resolver contract produces `contract`, not `document`),
|
|
66
|
+
`[address/notVisible]` (the contract must lie on every path to the node),
|
|
67
|
+
`[notification/missingBinding]` (no `countersigned` letter; `--disabled` counts as missing).
|
|
68
|
+
Offline the letter catalog is not loaded: a misspelled template id fails only at
|
|
69
|
+
`offer apply --publish`.
|
|
70
|
+
- **The letter moved:** `countersigned` is bound on the countersign node, not on the contract.
|
|
71
|
+
A bundle that still binds it on the contract is refused there (`[notification/unknownEvent]`)
|
|
72
|
+
— clear it with `node clear-notification --node contract --event countersigned`.
|
|
73
|
+
- **`--title` needs `--title-en`** (every config text is `{ de, en }`); without both the row
|
|
74
|
+
is titled „Vertrag gegenzeichnen" / „Countersign the contract". `--assign-team`, `--title`,
|
|
75
|
+
`--title-en` and `--due-in-days` are refused on any other node type.
|
|
76
|
+
- **The row closes only through the countersignature dialog**, never by a bare dispatch: the
|
|
77
|
+
engine moves the node only on the contract's `signed` event and refuses it while the contract
|
|
78
|
+
is not signed by every side („Der Vertrag ist noch nicht von allen Seiten unterschrieben."). Withdrawal and „Bewerbung ablehnen" sweep
|
|
79
|
+
the open row; nothing of the school is booked.
|
|
80
|
+
- **The representative is whoever completes the row** — active staff of the tenant with a
|
|
81
|
+
name; nobody can book a colleague's signature.
|
|
82
|
+
- **Migrating a bundle with the old shape** (a review „Gegenzeichnung" + task
|
|
83
|
+
`result: { kind: 'countersignature' }`): remove the task and the review
|
|
84
|
+
(`node task rm`, `node rm`), add the countersign node as above (team from the old
|
|
85
|
+
`assign.decision`, title / SLA from the task), move the letter, and re-key the progress
|
|
86
|
+
wiring from `reviewing` to `countersigning`.
|
|
87
|
+
|
|
88
|
+
## Related
|
|
89
|
+
- [Name what a node reads](./addresses.md) · [Add a workflow node](./add-workflow-node.md)
|
|
90
|
+
- [Insert a node on one edge and rewire it](./insert-and-rewire-node.md) (`--on-edge`, `--progress`)
|
|
91
|
+
- [Edit a live tenant's offer](./edit-live-tenant-offer.md)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Make a new school applyable (create a pedagogy unit)
|
|
2
|
+
|
|
3
|
+
**When to use:** a brand-new school (or an existing plain unit) needs to host an
|
|
4
|
+
admission offer, but `offer create --unit <unit>` fails because the unit isn't a
|
|
5
|
+
*pedagogy unit* yet (no `pedagogy_units` bridge row). This creates the unit + bridge
|
|
6
|
+
so the external "bewerben" portal can resolve it.
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
- A configured `plexus-cli` targeting the env (`--env local|staging|prod`).
|
|
10
|
+
- A token with tenant-wide master-data access (creating units) +
|
|
11
|
+
tenant access (the bridge writer is tenant-gated).
|
|
12
|
+
- An installed, current `plexus-cli` (`plexus-cli offer unit create --help`).
|
|
13
|
+
|
|
14
|
+
## Steps
|
|
15
|
+
```bash
|
|
16
|
+
# 1a. Brand-new school: create the unit AND its pedagogy bridge in one step.
|
|
17
|
+
# The name is German AND English.
|
|
18
|
+
plexus-cli --env <env> --tenant <slug> offer unit create --name "Grundschule Neustadt" --name-en "Neustadt Primary School"
|
|
19
|
+
# (--kind school by default; --parent <unit key> to nest; output prints the unitId)
|
|
20
|
+
|
|
21
|
+
# --unit takes the unit's config key (`plexus-cli units` lists it) — never its
|
|
22
|
+
# name. A unit made by `offer unit create` has no config key until config-as-code
|
|
23
|
+
# adopts it: address it by its URL slug meanwhile (here: grundschule-neustadt).
|
|
24
|
+
|
|
25
|
+
# 1b. OR upgrade an EXISTING plain unit to a pedagogy unit (idempotent).
|
|
26
|
+
plexus-cli --env <env> --tenant <slug> offer unit ensure-pedagogy --unit grundschule-neustadt
|
|
27
|
+
|
|
28
|
+
# 2. Now the offer can link it. (offer create/set --unit also ensures the bridge,
|
|
29
|
+
# so an existing non-pedagogy unit is upgraded automatically on link.)
|
|
30
|
+
plexus-cli --env <env> --tenant <slug> offer create --slug gs-neustadt --title "Grundschule Neustadt" --title-en "Neustadt Primary School" --unit grundschule-neustadt
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Verify
|
|
34
|
+
```bash
|
|
35
|
+
plexus-cli --env <env> --tenant <slug> units # the unit shows pedagogic ✓
|
|
36
|
+
plexus-cli --env <env> --tenant <slug> offer status --slug gs-neustadt # program/pedagogy links resolve
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Idempotency / gotchas
|
|
40
|
+
- **`ensure-pedagogy` is idempotent** — re-running returns the existing bridge
|
|
41
|
+
(`created: false`); it never inserts a duplicate.
|
|
42
|
+
- **`offer create/set --unit` ensures-then-links** — you don't strictly need a
|
|
43
|
+
separate `ensure-pedagogy` step for an existing unit; the link path upgrades it.
|
|
44
|
+
- **A unit needs a tenant legal entity.** `offer unit create` auto-resolves the
|
|
45
|
+
tenant's first legal entity; a tenant with none will fail loud (set one up first).
|
|
46
|
+
- **Tenant-scoped, fail-loud.** The bridge writer (`programAdmin.ensurePedagogyUnit`)
|
|
47
|
+
verifies the unit belongs to the tenant; it never bridges a foreign unit.
|
|
48
|
+
- **Config is authoritative for the flag.** Also set it in the tenant config
|
|
49
|
+
(`plexus-cli config set --tenant <slug> --target units --key <unitKey> --field pedagogyUnit --value true`),
|
|
50
|
+
or the next `config apply` of a config without it removes the bridge again — or,
|
|
51
|
+
once an offer is linked, fails with `[unit/pedagogyUnitInUse]`.
|
|
52
|
+
|
|
53
|
+
## Related
|
|
54
|
+
- [Author the manifest meta layer](./author-manifest-meta.md) · [Author a SmartForm as code](./author-smartform.md).
|
|
55
|
+
- Command reference: `plexus-cli offer unit --help`.
|