@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,170 @@
|
|
|
1
|
+
# Update a tenant's config
|
|
2
|
+
|
|
3
|
+
**When to use:** change a real tenant's live config-as-code — add or remove a search
|
|
4
|
+
filter, search target, field, list, display group, role binding, result icon, etc. — on
|
|
5
|
+
staging and/or prod, without a reseed and without breaking unrelated config.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- `plexus-cli` installed and current (`plexus-cli --help`).
|
|
10
|
+
- A token configured for each target env: a platform-admin token
|
|
11
|
+
(`_platform.config.manage` to commit), or — for your OWN tenant only — a tenant admin's
|
|
12
|
+
token holding `administration.config.manage` (see
|
|
13
|
+
[CLI access for a tenant admin](../users/cli-access-for-tenant-admin.md)). The tenant
|
|
14
|
+
token cannot change most `tenant.*` settings (modules, `mailLevel`, …), the import
|
|
15
|
+
source, or add new accounts; that section lists every refusal. A local admin key
|
|
16
|
+
without a user identity does **not** satisfy the gate.
|
|
17
|
+
- The tenant **slug** (e.g. `test`). `plexus-cli config show` prints the resolved connection.
|
|
18
|
+
- Always **stage on staging first**, verify, then promote to prod.
|
|
19
|
+
|
|
20
|
+
## Steps
|
|
21
|
+
|
|
22
|
+
The mechanism is **export → edit the snapshot → dry-run → confirm**. `export` snapshots the
|
|
23
|
+
live config *exactly* (round-trip is identity), so re-applying with one thing changed yields a
|
|
24
|
+
diff of only that one change.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 1. Snapshot the live config (writes config/.dev/<slug>/config.json below the current
|
|
28
|
+
# directory; keep it private — it holds the tenant's whole config and user roster).
|
|
29
|
+
plexus-cli config export --env staging --tenant <slug>
|
|
30
|
+
|
|
31
|
+
# 2. Edit config/.dev/<slug>/config.json — make the ONE change (e.g. remove the
|
|
32
|
+
# searchFilters entry with "key":"nachname"). Leave everything else untouched.
|
|
33
|
+
|
|
34
|
+
# 3. Dry-run (no --confirm → transaction rolls back, prints the plan).
|
|
35
|
+
plexus-cli config apply --env staging --tenant <slug> \
|
|
36
|
+
--from snapshot:config/.dev/<slug>/config.json
|
|
37
|
+
|
|
38
|
+
# 4. Commit once the dry-run shows only your intended change.
|
|
39
|
+
plexus-cli config apply --env staging --tenant <slug> \
|
|
40
|
+
--from snapshot:config/.dev/<slug>/config.json --confirm
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Then repeat 1-4 with `--env prod` (re-export from prod first — never assume prod == staging).
|
|
44
|
+
|
|
45
|
+
`config set` (surgical single-field edit) is the alternative for changing **one field of one
|
|
46
|
+
existing row**, but it **cannot delete a row** — removing an array element (a filter, a target,
|
|
47
|
+
an option) must go through export → edit → apply.
|
|
48
|
+
|
|
49
|
+
## Verify
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Re-export and confirm the change landed; nothing else moved.
|
|
53
|
+
plexus-cli config export --env staging --tenant <slug>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Read the dry-run plan carefully — it is the safety gate:
|
|
57
|
+
|
|
58
|
+
- `+ create (all) N` / `~ update (all) N` / `- delete (all) N` / `· unchanged (all) N` aggregate counts.
|
|
59
|
+
- `~ update <target> <key>` — **one row per patched config row, by key** (e.g.
|
|
60
|
+
`~ update lists salutation`, `~ update admission.fields geburtsurkunde`). An idempotent
|
|
61
|
+
row is `· unchanged`, never an update — admission custom fields and derived attributes
|
|
62
|
+
included, in the dry run too. A deployment older than this row list prints one
|
|
63
|
+
`~ update (unnamed)` line instead of guessing.
|
|
64
|
+
- `- delete <target> <key>` — **one row per pruned config row, by key** (e.g.
|
|
65
|
+
`- delete searchFilters nachname`). This is exactly what you removed; confirm it matches.
|
|
66
|
+
- `- orphan <target>/<table> <key>` — rows the reconciler will NOT auto-delete (see gotchas).
|
|
67
|
+
- `! no account <email>` — a `foundation.users` entry with no login account. On `--confirm`
|
|
68
|
+
each of these is **minted a password, printed once** (`+ account <email> password: …`), so
|
|
69
|
+
read this line before committing in a shared terminal — add `--output creds.csv --quiet` to
|
|
70
|
+
steer the plaintexts into a file instead. A steady-state tenant usually shows none: an
|
|
71
|
+
existing account is linked, never re-minted.
|
|
72
|
+
|
|
73
|
+
- `~ slug units/<key> <from> → <to>` — an existing unit's URL moves (see
|
|
74
|
+
[Rename a unit's URL](#rename-a-units-url-slug) below). Expect none unless you changed a
|
|
75
|
+
`slug`.
|
|
76
|
+
|
|
77
|
+
**Control-run technique** (isolate baseline drift): a fresh *unedited* export re-applied as a
|
|
78
|
+
dry-run should show no `~ update` row; if it does, the named rows are the ones the export
|
|
79
|
+
does not round-trip. To prove your edit added only what you intended, dry-run the unedited export too and
|
|
80
|
+
diff the counts:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
plexus-cli config export --env staging --tenant <slug> --out /tmp/control.json
|
|
84
|
+
plexus-cli config apply --env staging --tenant <slug> --from snapshot:/tmp/control.json # baseline
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The difference between the control plan and your edited plan is your change.
|
|
88
|
+
|
|
89
|
+
## Rename a unit's URL (slug)
|
|
90
|
+
|
|
91
|
+
A unit's **identity** is its config `key` (stored as `units.configKey`); its **slug** is only
|
|
92
|
+
the URL projection — `/{tenant}/e/units/<slug>` (portal landing + `/bewerben`) and the staff
|
|
93
|
+
`?unit=<slug>` scope. Renaming the unit's `name` never moves the slug; setting `slug` does.
|
|
94
|
+
|
|
95
|
+
- `slug` absent in config → an existing unit keeps its slug, a new unit derives one from its
|
|
96
|
+
name. `config export` always emits the live `slug`, so an exported snapshot pins it.
|
|
97
|
+
- `slug` present → create sets it, an existing unit is patched to it.
|
|
98
|
+
- Shape: lowercase `a-z0-9` words joined by single dashes (exactly what the name derivation
|
|
99
|
+
produces); `tenant` is reserved. Two units in one config may not share a slug (validator),
|
|
100
|
+
and a slug any other unit of the tenant holds — including a row no longer in config — fails
|
|
101
|
+
the apply with `[unit/slugTaken]`. Two units may swap slugs in one apply. To take the slug
|
|
102
|
+
of a unit no longer in config, list that unit again under its key with a new `slug` in
|
|
103
|
+
the same apply — it moves off like any config unit, no data lost. Only if the old unit
|
|
104
|
+
should go anyway: remove it first (`apply --confirm --allow-destructive`, deletes its
|
|
105
|
+
data), then set the slug in a second apply — orphans are deleted only after the
|
|
106
|
+
reconcile has planned the slugs.
|
|
107
|
+
- A new unit in the same apply never derives a slug another unit just vacated. A unit created
|
|
108
|
+
in a *later* apply (or via the UI) may — from then on old links open that unit.
|
|
109
|
+
|
|
110
|
+
Example — the school unit `school` → `school-primary` (**do not run on prod without the
|
|
111
|
+
go of whoever is responsible for production**; this is the recipe, not a pending change):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Unit key from the export (it is the config key, not the slug — may differ).
|
|
115
|
+
plexus-cli config export --env staging --tenant <slug>
|
|
116
|
+
# Dry-run the one-field change; the plan must show exactly one slug row:
|
|
117
|
+
# ~ slug units/<unitKey> school → school-primary (old URL /e/units/school no longer reaches this unit)
|
|
118
|
+
plexus-cli config set --env staging --tenant <slug> --target units --key <unitKey> \
|
|
119
|
+
--field slug --value school-primary
|
|
120
|
+
# Commit.
|
|
121
|
+
plexus-cli config set --env staging --tenant <slug> --target units --key <unitKey> \
|
|
122
|
+
--field slug --value school-primary --confirm
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**The old URL dies with no redirect.** Before committing, update everything outside the DB
|
|
126
|
+
that links to it: the school's own website "Bewerben" button, QR codes on printed flyers
|
|
127
|
+
(regenerate from the offer's QR dialog), links in mails already sent (a magic link mailed
|
|
128
|
+
before the change points at the old slug and 404s — send a fresh one).
|
|
129
|
+
|
|
130
|
+
Also held by value, and **not** rewritten by a slug change: a custom field whose automatic
|
|
131
|
+
string has a `unit`/`slug` segment stores the generated text once, so existing records keep
|
|
132
|
+
the old slug (no committed config uses that segment today).
|
|
133
|
+
|
|
134
|
+
**Cross-env promotes carry slugs.** `export` always emits `slug`, so `apply --from
|
|
135
|
+
env:staging` onto prod moves any prod unit whose slug differs from staging's (e.g. a deduped
|
|
136
|
+
`x-2` vs `x`). The dry run lists each as a `~ slug` row — read them before `--confirm`.
|
|
137
|
+
|
|
138
|
+
## Idempotency / gotchas
|
|
139
|
+
- The writing run (`--confirm`) needs `--env` on the command line, and a production target
|
|
140
|
+
`--allow-prod` — decided on the resolved deployment URL, so an alias pointing at prod is prod.
|
|
141
|
+
The dry-run is unaffected.
|
|
142
|
+
|
|
143
|
+
- **Removing an array element does NOT need `--allow-destructive`.** The reconciler
|
|
144
|
+
prunes any config row absent from your snapshot on a plain
|
|
145
|
+
`apply --confirm`. This covers **search targets, search filters, filter options, result icons,
|
|
146
|
+
role-field bindings, target requirements, template placeholders** — they show as `- delete`
|
|
147
|
+
rows. It is **not** a migration.
|
|
148
|
+
- **`--allow-destructive` is only for `orphans`.** Exactly five upsert-only tables —
|
|
149
|
+
**display-groups, lists, custom_fields, roles, permission_groups** — are *not* auto-pruned;
|
|
150
|
+
removing one surfaces it as an `orphan` (labelled *would destroy*) and is deleted only with
|
|
151
|
+
`--confirm --allow-destructive`. This split is deliberate: dropping a field/role/group can
|
|
152
|
+
orphan dependent data, so it requires the explicit flag.
|
|
153
|
+
- **Changing the *shape* of existing rows (rename a key, change a field type) is a migration,
|
|
154
|
+
not an apply** — it needs a platform operator, who ships and runs the migration. `apply`
|
|
155
|
+
only moves config *values* into the shape that already exists.
|
|
156
|
+
- **Removing a filter never touches the underlying data.** E.g. dropping the `nachname` search
|
|
157
|
+
*filter* leaves `last_name` (a system field) and the free-text search bar (`searchTargets`)
|
|
158
|
+
intact — only the dedicated filter input disappears.
|
|
159
|
+
- **An export carries `foundation.users`**, so the export → edit → apply loop can mint
|
|
160
|
+
credentials for anyone in that roster who still lacks an account — see the `! no account`
|
|
161
|
+
line in the dry-run plan above, and `--output`/`--quiet` in `plexus-cli config apply --help`.
|
|
162
|
+
- This is a live-DB operation; nothing else records it. The `config/.dev/<slug>/config.json`
|
|
163
|
+
snapshot is your only copy — keep it out of any shared or public place.
|
|
164
|
+
- Cross-env shortcut: `--from env:staging` promotes staging's live config directly to prod
|
|
165
|
+
without a snapshot file (re-plans against prod's live state).
|
|
166
|
+
|
|
167
|
+
## Related
|
|
168
|
+
|
|
169
|
+
- [Onboard / establish a tenant](./onboard-tenant.md) · [Wipe a tenant's config](./wipe-tenant-config.md)
|
|
170
|
+
- Command semantics: `plexus-cli config <verb> --help`.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Wipe a tenant's config
|
|
2
|
+
|
|
3
|
+
**When to use:** clear a tenant's config target (and optionally its dependent data) — e.g. to
|
|
4
|
+
rebuild a config section from scratch, or to tear down a test tenant. Reprovision afterwards with
|
|
5
|
+
`config apply`.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- `plexus-cli` installed; platform-admin token for the target env (`_platform.config.manage`).
|
|
10
|
+
- Know the **target id** from the registry: `plexus-cli config targets --env <env> --tenant <slug>`.
|
|
11
|
+
- Wipe is destructive — **rehearse on staging**, never improvise on prod.
|
|
12
|
+
|
|
13
|
+
## Steps
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Clear-only by default; --confirm required to actually wipe.
|
|
17
|
+
plexus-cli config wipe --env <env> --tenant <slug> --target <id> --confirm
|
|
18
|
+
|
|
19
|
+
# Heavy tier (the `masterdata` data target, the `foundation` config target, or --target all)
|
|
20
|
+
# additionally requires --allow-destructive and echoes the affected row count.
|
|
21
|
+
plexus-cli config wipe --env <env> --tenant <slug> --target all --confirm --allow-destructive
|
|
22
|
+
|
|
23
|
+
# Config target with dependent live data → blocked unless --cascade (which also clears the data).
|
|
24
|
+
plexus-cli config wipe --env <env> --tenant <slug> --target masterdata-fields --cascade --confirm
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Verify
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
plexus-cli config targets --env <env> --tenant <slug> # the wiped target's count should be 0
|
|
31
|
+
# Reprovision exactly those rows:
|
|
32
|
+
plexus-cli config apply --env <env> --tenant <slug> --from snapshot:<path> --confirm
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Idempotency / gotchas
|
|
36
|
+
- The writing run (`--confirm`) needs `--env` on the command line, and a production target
|
|
37
|
+
`--allow-prod` — decided on the resolved deployment URL, so an alias pointing at prod is prod.
|
|
38
|
+
The dry-run is unaffected.
|
|
39
|
+
|
|
40
|
+
- **Dry run first.** Without `--confirm`, `config wipe` prints per table what it would clear
|
|
41
|
+
(`applications.contracts would clear 3`) and any block it would hit, and deletes nothing.
|
|
42
|
+
Each table is counted in its own execution, so it works where `config targets` hits the read cap.
|
|
43
|
+
- **Class memberships (`enrollments`) and leads (`program_interests`) are NOT application data.**
|
|
44
|
+
Memberships come from master data (manual or import) and live in `contract-setup` with their
|
|
45
|
+
classes; leads (Interessenten) are recorded before any application and live in `masterdata`.
|
|
46
|
+
`--target applications` leaves both alone; the dry run lists them under their own target.
|
|
47
|
+
- **Contracts without an application block the applications wipe.** The finance runtime tables
|
|
48
|
+
are cleared tenant-wide, so a contract created on a party (not by an admission) would go with
|
|
49
|
+
them. The wipe (and its dry run) refuses with `Blocked: N of M contracts … belong to no
|
|
50
|
+
application` and deletes nothing.
|
|
51
|
+
- **Two tiers.** Normal target = `--confirm`. **Heavy** (`masterdata` data target, `foundation`
|
|
52
|
+
config target, or `--target all`) additionally needs `--allow-destructive`.
|
|
53
|
+
- **`--target all` is the teardown.** It deletes every file the tenant's forms name (also the
|
|
54
|
+
uploads the ledger cannot attribute to one form — every form goes anyway), purges every
|
|
55
|
+
application, drains every target's
|
|
56
|
+
tables (manual tasks and contracts without an application included — the applications
|
|
57
|
+
target's blocks do not apply), then the remaining tenant tables, the tenant's open upload
|
|
58
|
+
tickets and the tenant row. Nothing of the tenant is left in storage but the **logo**
|
|
59
|
+
(letters already sent link it).
|
|
60
|
+
- **Files go with their rows.** Every wipe deletes the stored files only its rows held: fonts,
|
|
61
|
+
template files (once no template, library file, resolver pin or template run still names
|
|
62
|
+
them), import and export files, party files, comment attachments, signature scans. Only the
|
|
63
|
+
**logo** stays.
|
|
64
|
+
- **Master data waits for the applications.** `--target masterdata` is blocked
|
|
65
|
+
(`Blocked: … still has applications`) while the tenant has any application: a form may name
|
|
66
|
+
the same file as a party value, which cannot be proven otherwise, and once the value is gone
|
|
67
|
+
no wipe could find the file again. Pass `--cascade` (the applications go first) or wipe
|
|
68
|
+
`--target applications` before.
|
|
69
|
+
- **Interlock.** Wiping a CONFIG target while dependent DATA exists (e.g. `masterdata-fields`
|
|
70
|
+
while `values` rows reference those fields) is **blocked** with a bilingual message listing the
|
|
71
|
+
dependent counts — unless `--cascade`, which then also clears the data.
|
|
72
|
+
- **Reprovision is exact.** After a scoped config wipe, `config apply` recreates exactly those
|
|
73
|
+
rows (`wipe search` → `apply` round-trips). The gates sit in the backend
|
|
74
|
+
(fail loud); the CLI adds the UX warnings.
|
|
75
|
+
- **`--target applications` hard-deletes every application of the tenant and everything it
|
|
76
|
+
owns** — work items (+ comments, assignees, parties), team-owner bindings, feed (events,
|
|
77
|
+
comments, chat channels, read marks), waitlist entries, notifications about it (+ deliveries,
|
|
78
|
+
previews), its access-audit rows, the party-timeline activities that point at it, party-role
|
|
79
|
+
buckets, its form instances (`smart_form_instances`) and file artifacts, its process store, the workflow cursor, and the finance rows that flow from admissions (contracts, lines, snapshots,
|
|
80
|
+
signatures, payments, invoices, postings) — **and their files**: docRef uploads (single and
|
|
81
|
+
multi-file) whose upload-ledger row names the form, generated/uploaded file artifacts, chat
|
|
82
|
+
attachments, contract snapshot PDFs, signed documents. Draft party shells the forms created
|
|
83
|
+
go too (the soft-delete GC rule); **real master data stays**, as do **manual tasks** (no
|
|
84
|
+
application) and the **school setup**.
|
|
85
|
+
- **School setup is its own heavy target, `contract-setup`:** contract templates,
|
|
86
|
+
billing definitions, grants, generators and their article dependencies, ledger accounts, payment methods, classes
|
|
87
|
+
(`learning_groups`), portal info events and consultation slots. It is maintained in the app,
|
|
88
|
+
not by config-as-code — **no `config apply` brings it back**. Needs `--allow-destructive`.
|
|
89
|
+
- **How it runs:** a few applications per mutation through one cascade, then the target's
|
|
90
|
+
tables page by page — never one tenant-sized transaction.
|
|
91
|
+
- **What it cannot prove, so it leaves:** a file referenced by a docRef value WITHOUT an
|
|
92
|
+
upload-ledger row for that form (dev-seed placeholders, uploads older than the ledger) — it
|
|
93
|
+
might be shared. Only `--target all` takes those. Pending (never persisted) uploads are
|
|
94
|
+
reclaimed by the nightly orphan sweep within 7 days.
|
|
95
|
+
- **Verify an applications wipe:** `config targets` shows 0 for every `applications` table
|
|
96
|
+
(`work_items` counts only application tasks). Tables only the cascade reaches
|
|
97
|
+
(`notifications*`, `access_audit`, `activities`, `storage_uploads`) are not in that listing —
|
|
98
|
+
check them with `plexus-cli call` if needed.
|
|
99
|
+
- **Reprovisioning `foundation` mints new passwords.** The wipe took the tenant's users with
|
|
100
|
+
it, so the re-apply creates their login accounts again and prints each generated password
|
|
101
|
+
once. Run that re-apply with `--output creds.csv --quiet` if the terminal is shared — see
|
|
102
|
+
`plexus-cli config apply --help`.
|
|
103
|
+
|
|
104
|
+
## Related
|
|
105
|
+
|
|
106
|
+
- [Update a tenant's config](./update-tenant-config.md) · [Onboard / establish a tenant](./onboard-tenant.md)
|
|
107
|
+
- Command semantics: `plexus-cli config wipe --help`.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Create a named person export
|
|
2
|
+
|
|
3
|
+
**When to use:** A tenant needs a reusable XLSX export with selected person fields and the shared search filters.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- An operator token with export access to the target tenant in each environment.
|
|
8
|
+
- Review the readable field IDs in `exports:metadata` separately for staging and production; IDs differ between deployments.
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. List existing templates before creating a new name:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
plexus-cli call dataExportTemplates:list --env staging client=<tenant> --json
|
|
16
|
+
plexus-cli call exports:metadata --env staging client=<tenant> --json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
2. Choose explicit `dataExportTemplateColumnInput` paths from the metadata. Prefer a small set of ordinary fields and core role/unit columns. Leave `filters=[]` to let a user choose filters at export time.
|
|
20
|
+
|
|
21
|
+
3. Create on staging first, passing the column array as one JSON argument:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
plexus-cli call dataExportTemplates:create --kind mutation --env staging client=<tenant> name='<name>' completenessMode=all 'columns=[{"path":{"kind":"core","key":"party.roles"},"headerLabel":"Rollen","pathLabel":"Rollen"}]' --json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
4. After verification, repeat against `--env prod` with production field IDs. Core paths do not need environment-specific IDs.
|
|
28
|
+
|
|
29
|
+
## Verify
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
plexus-cli call dataExportTemplates:get --env staging client=<tenant> templateId=<id> --json
|
|
33
|
+
plexus-cli call exports:previewSample --env staging client=<tenant> page=0 'columns=[{"path":{"kind":"core","key":"party.roles"},"headerLabel":"Rollen","pathLabel":"Rollen"}]' --json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Confirm the name, `formatDefault: "xlsx"`, filters, and column paths. Select the named configuration in the browser to check that the preview and XLSX action render. Repeat the read checks on production.
|
|
37
|
+
|
|
38
|
+
## Idempotency / gotchas
|
|
39
|
+
|
|
40
|
+
- `create` always inserts a new template. Check `list` first; update an existing template when it has the intended identity.
|
|
41
|
+
- `previewSample` pages through underlying parties, so a rare filter can return zero rows on one page even if later pages contain matches. A full XLSX job scans the whole tenant.
|
|
42
|
+
- The template is independent of tenant config snapshots; `config export/apply` does not carry it between environments.
|
|
43
|
+
|
|
44
|
+
## Related
|
|
45
|
+
|
|
46
|
+
- `plexus-cli call --help` and `plexus-cli describe <function>` for the arguments of each function
|
|
47
|
+
- [Update tenant config](../config/update-tenant-config.md)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Create + triage feedback from the CLI
|
|
2
|
+
|
|
3
|
+
**When to use:** you want to file platform-level feedback without the browser
|
|
4
|
+
(e.g. an agent capturing an issue), assign it to a triage admin, pull the list
|
|
5
|
+
of items assigned to you, and turn an item into a GitHub issue — all from `plexus-cli`.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
- `--env local|staging|prod` configured with a platform-operator token
|
|
9
|
+
(creating/listing need `_platform.feedback.read`; assigning needs
|
|
10
|
+
`_platform.feedback.manage`).
|
|
11
|
+
- The assignee must be feedback-assignable — see
|
|
12
|
+
[Make a platform admin assignable to feedback](../users/grant-feedback-assignable.md).
|
|
13
|
+
|
|
14
|
+
## Steps
|
|
15
|
+
```bash
|
|
16
|
+
# 1. File platform-level feedback (no screenshot — attachments are browser-only).
|
|
17
|
+
plexus-cli --env <env> feedback create \
|
|
18
|
+
--summary "Board: Spalte 'Klärung' lädt langsam" \
|
|
19
|
+
--tag <feedbackTagId> \
|
|
20
|
+
--situation "Beim Öffnen der Klärung-Spalte mit vielen Karten" \
|
|
21
|
+
--reason "Triage wird ausgebremst"
|
|
22
|
+
# → prints the new feedbackId
|
|
23
|
+
|
|
24
|
+
# 2. Assign it to a triage admin (userId, email, or @me for yourself).
|
|
25
|
+
plexus-cli --env <env> feedback assign <feedbackId> ada@example.com
|
|
26
|
+
|
|
27
|
+
# 3. List everything assigned to me.
|
|
28
|
+
plexus-cli --env <env> feedback list --all-tenants --assignee @me
|
|
29
|
+
|
|
30
|
+
# 4. File an item as a GitHub issue. If it has a screenshot, save it, host it
|
|
31
|
+
# yourself as an EXPIRING link, and pass the URL (the CLI uploads nothing).
|
|
32
|
+
plexus-cli --env <env> feedback screenshot <feedbackId> --out ./feedback.webp
|
|
33
|
+
# → upload ./feedback.webp with your environment's upload tool, presigned,
|
|
34
|
+
# e.g. 7 days; screenshots can show customer data — never a permanent public URL
|
|
35
|
+
plexus-cli --env <env> feedback to-issue <feedbackId> --type bug \
|
|
36
|
+
--screenshot-url '<presigned-url>' # or: --no-screenshot
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Verify
|
|
40
|
+
```bash
|
|
41
|
+
# The item shows the assignee and appears under the @me filter:
|
|
42
|
+
plexus-cli --env <env> feedback list --all-tenants --assignee @me --json
|
|
43
|
+
# Eligible assignees (must include your target):
|
|
44
|
+
plexus-cli --env <env> feedback admins
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Idempotency / gotchas
|
|
48
|
+
- **`feedback create` is platform-level only** — the item is owned by the reserved
|
|
49
|
+
`_platform` "Plattform" tenant, not a client tenant. Use the in-app feedback
|
|
50
|
+
button to file *tenant-scoped* feedback.
|
|
51
|
+
- **No screenshot/attachments on `feedback create`** — they require a browser upload;
|
|
52
|
+
both are optional backend-side, so omitting them is a valid submission.
|
|
53
|
+
- **`feedback to-issue` never drops a screenshot silently:** an item with one is refused
|
|
54
|
+
until you pass `--screenshot-url <https-url>` or `--no-screenshot`; a URL for an item
|
|
55
|
+
without a screenshot is refused too. `--dry-run` applies the same rule.
|
|
56
|
+
- **The issue embeds the link you pass.** A presigned link expires, and the image in the
|
|
57
|
+
issue goes with it — that is the point for customer data; the item itself keeps the
|
|
58
|
+
original (`feedback screenshot` fetches it again any time).
|
|
59
|
+
- **`feedback screenshot`** refuses an existing `--out` file (`--force` overwrites) and an
|
|
60
|
+
item without a screenshot; it prints the content type (staging stored WebP when checked).
|
|
61
|
+
- Tags replace the old fixed category field. Use repeatable `--tag <feedbackTagId>`
|
|
62
|
+
on create, or `feedback tag <feedbackId> <feedbackTagId>` afterwards.
|
|
63
|
+
- `--assignee @me` (and `me`) resolves to the calling operator via `whoAmI`.
|
|
64
|
+
`feedback assign` fails loud if the target lacks `_platform.feedback.assign`.
|
|
65
|
+
- `--assignee` on `feedback list` requires `--all-tenants` (assignee is platform-only).
|
|
66
|
+
|
|
67
|
+
## Related
|
|
68
|
+
- [Make a platform admin assignable to feedback](../users/grant-feedback-assignable.md)
|
|
69
|
+
- [Manage feedback tags + bulk-tag items](./manage-feedback-tags.md)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Manage feedback tags (incl. visibility) and bulk-tag items
|
|
2
|
+
|
|
3
|
+
**When to use:** curate the global feedback tag catalog (create / rename / recolor /
|
|
4
|
+
set public-vs-platform visibility / delete), or attach a tag across many feedback
|
|
5
|
+
items at once.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
- `--env local|staging|prod` selected (the environment as configured for your CLI, see `plexus-cli --help`).
|
|
9
|
+
- Catalog edits need platform `_platform.feedback.tags`; attach/detach needs board
|
|
10
|
+
access to the item (the CLI authenticates as a platform admin, so both are covered).
|
|
11
|
+
- `jq` for the bulk loop.
|
|
12
|
+
|
|
13
|
+
## Steps
|
|
14
|
+
```bash
|
|
15
|
+
# Inspect the catalog (id, name, color, scope). Shows platform-only tags too.
|
|
16
|
+
plexus-cli --env prod feedback tags list
|
|
17
|
+
|
|
18
|
+
# Create — public (default) or platform-only (triage tags like "Unklar"/"Fehlt").
|
|
19
|
+
plexus-cli --env prod feedback tags create --allow-prod "Rechte" --color 5319e7
|
|
20
|
+
plexus-cli --env prod feedback tags create --allow-prod "Unklar" --color d4c5f9 --visibility platform
|
|
21
|
+
|
|
22
|
+
# Rename / recolor / re-scope / delete (delete cascades the item links).
|
|
23
|
+
plexus-cli --env prod feedback tags rename --allow-prod <tagId> "Konfiguration"
|
|
24
|
+
plexus-cli --env prod feedback tags recolor --allow-prod <tagId> fbca04
|
|
25
|
+
plexus-cli --env prod feedback tags visibility --allow-prod <tagId> platform # or: public
|
|
26
|
+
plexus-cli --env prod feedback tags delete --allow-prod <tagId>
|
|
27
|
+
|
|
28
|
+
# Attach / detach a tag on one item.
|
|
29
|
+
plexus-cli --env prod feedback tag --allow-prod <feedbackId> <tagId>
|
|
30
|
+
plexus-cli --env prod feedback untag --allow-prod <feedbackId> <tagId>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Bulk-tag: attach one tag to every matching item
|
|
34
|
+
```bash
|
|
35
|
+
TAG=<tagId>
|
|
36
|
+
plexus-cli --env prod feedback list --all-tenants --text "import" --json \
|
|
37
|
+
| jq -r '.[].id' \
|
|
38
|
+
| while read -r id; do plexus-cli --env prod feedback tag --allow-prod "$id" "$TAG"; done
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Verify
|
|
42
|
+
```bash
|
|
43
|
+
plexus-cli --env prod feedback tags list # scope column reflects the change
|
|
44
|
+
plexus-cli --env prod feedback list --all-tenants --tag <tagId> --json | jq length
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Idempotency / gotchas
|
|
48
|
+
- `feedback tag`/`untag` are idempotent (re-running is a no-op).
|
|
49
|
+
- A non-platform user can attach only **public** tags; the CLI is platform-authed so
|
|
50
|
+
it can attach platform-only ones too.
|
|
51
|
+
- `feedback list` filters in-memory over rank-paginated pages, so a heavily filtered
|
|
52
|
+
query may need `--limit` raised to reach older items (iterate to exhaustion).
|
|
53
|
+
- `tags delete` is destructive: it detaches the tag from every item first.
|
|
54
|
+
|
|
55
|
+
## Related
|
|
56
|
+
- [Create + triage feedback](./create-and-triage-feedback.md) (incl. `feedback to-issue`) ·
|
|
57
|
+
`feedback link/unlink/links` to cross-reference two feedback items · the board UI's "Tags verwalten" dialog does the same edits.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Change a contract grant
|
|
2
|
+
|
|
3
|
+
Use a line grant for a concession on one contract line. Use an aggregate grant for
|
|
4
|
+
an agreed reduction across eligible contract lines with one combined floor. Plans
|
|
5
|
+
only assemble articles. Prices selected through pricing attributes stay in the tree.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
plexus-cli --env staging --tenant test contract grant add \
|
|
9
|
+
--line <lineId> --template <templateKey> --from 2026-11-01 \
|
|
10
|
+
--evidence '{"decision":"Finance approved"}'
|
|
11
|
+
|
|
12
|
+
plexus-cli --env staging --tenant test contract grant add-aggregate \
|
|
13
|
+
--contract <contractId> --template <templateKey> --amount 380 --floor 150 \
|
|
14
|
+
--period month --from 2026-11-01 \
|
|
15
|
+
--evidence '{"decision":"Income assessment approved"}'
|
|
16
|
+
|
|
17
|
+
plexus-cli --env staging --tenant test contract grant end <grantId> --to 2027-07-31
|
|
18
|
+
|
|
19
|
+
plexus-cli --env staging --tenant test contract grant replace <lineGrantId> \
|
|
20
|
+
--template <newTemplateKey> --from 2026-12-01 \
|
|
21
|
+
--evidence '{"decision":"Updated concession"}'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`add` also accepts negotiated line terms: `--name`, `--reason`, and exactly one of
|
|
25
|
+
`--percent` or `--amount`. `replace` replaces line grants. To change an aggregate
|
|
26
|
+
agreement, end it and apply the new explicit terms from the following period.
|
|
27
|
+
|
|
28
|
+
Finance enters income reductions as absolute amounts. The engine caps the granted
|
|
29
|
+
reduction so the eligible total cannot fall below the agreed floor. Annual amounts
|
|
30
|
+
and floors are entered explicitly; monthly terms are never multiplied by twelve.
|
|
31
|
+
Only eligible lines with the matching price period participate. Supporting evidence
|
|
32
|
+
is required. Backdated changes to already booked periods are refused.
|
|
33
|
+
|
|
34
|
+
Template edits do not change existing contract grants. Their target lines, rule,
|
|
35
|
+
floor, period and evidence were agreed when applied. Contract snapshots preserve
|
|
36
|
+
signed evidence; booked charges preserve their pricing explanation and accounting.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Record a missing historical SEPA submission date
|
|
2
|
+
|
|
3
|
+
**When to use:** The dashboard reports a legacy debit batch without a recorded
|
|
4
|
+
submission date. Use the original transmission receipt, not the execution date.
|
|
5
|
+
|
|
6
|
+
## Prerequisites
|
|
7
|
+
|
|
8
|
+
- Tenant-wide finance permission and the original bank transmission evidence.
|
|
9
|
+
- Inspect the target environment before writing. Staging/prod changes require
|
|
10
|
+
the operator's explicit approval; this recipe is not authorization.
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
plexus-cli --env local --tenant test config show
|
|
16
|
+
plexus-cli --env local --tenant test call paymentBatches:recordSubmissionDate --kind mutation client=test batchId=<batch-id> submittedDate=2026-08-31
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Use the actual receipt's date and intended tenant/environment. Ordinary new
|
|
20
|
+
submissions already record this date and need no repair.
|
|
21
|
+
|
|
22
|
+
## Verify
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
plexus-cli --env local --tenant test call financeDashboard:overview client=test
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The return count and denominator appear in the original submission month.
|
|
29
|
+
|
|
30
|
+
## Idempotency / gotchas
|
|
31
|
+
|
|
32
|
+
An identical date can be replayed. A conflicting date, another tenant's batch,
|
|
33
|
+
a draft batch or a date after execution is refused. Nothing books money.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# List a tenant's Interessenten (open leads)
|
|
2
|
+
|
|
3
|
+
Inspect the CRM funnel's `interesse` stage from the CLI — every OPEN lead
|
|
4
|
+
(program interest) for a tenant, newest first, with the person, Angebot, Stufe
|
|
5
|
+
and Lauf. Read-only; backs ops/debug ("why isn't this lead showing /
|
|
6
|
+
converting?").
|
|
7
|
+
|
|
8
|
+
There is no stored lead status: a lead is CONVERTED iff an application
|
|
9
|
+
references it (`applications.leadId`) — converted leads appear as Bewerbungen,
|
|
10
|
+
not here — and withdrawn/declined leads are deleted outright.
|
|
11
|
+
|
|
12
|
+
## Command
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# All open leads for the active tenant (table)
|
|
16
|
+
plexus-cli interessenten list --env local --tenant <slug>
|
|
17
|
+
|
|
18
|
+
# Rich JSON
|
|
19
|
+
plexus-cli interessenten list --env local --tenant <slug> --json
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- `--tenant <slug>`: overrides the config default. The local dev seed's tenant
|
|
23
|
+
slug is **`test`** (the committed `test` template); `grundschule-havelland` is
|
|
24
|
+
a *unit*, not the tenant.
|
|
25
|
+
- `--json`: rich shape (`{ lead, personDisplayName, programName, runName,
|
|
26
|
+
stageName }`); default is a compact table.
|
|
27
|
+
|
|
28
|
+
## Example output (table)
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
name angebot stufe lauf
|
|
32
|
+
Lia Busch Grundschule Klassenstufe 6 Schuljahr 2026/27
|
|
33
|
+
Noah Kaiser Grundschule Klassenstufe 5 Schuljahr 2026/27
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## How it works
|
|
37
|
+
|
|
38
|
+
Wraps the gated query `api.leads.listForManagement(client)`, which requires the
|
|
39
|
+
**`bewerbungsmanagement.applications.read`** permission (unlike the
|
|
40
|
+
membership-stub `leads.listForTenant`). Leads are real Stammdaten persons with
|
|
41
|
+
the tenant's configured Interessent (lead) role — the Bewerber role is attached
|
|
42
|
+
only at conversion — so a lead here is the same person you'll find in the
|
|
43
|
+
Personen catalog. The free-text note lives on the person as the `notiz` custom
|
|
44
|
+
field, not on the lead row.
|
|
45
|
+
|
|
46
|
+
## Verify / gotchas
|
|
47
|
+
|
|
48
|
+
- **`Unknown tenant "<slug>"`** → wrong `--tenant`. Use the tenant slug (`test`
|
|
49
|
+
locally), not a unit slug.
|
|
50
|
+
- **`Missing required permission: bewerbungsmanagement.applications.read`** →
|
|
51
|
+
the CLI token's user lacks admission read in that tenant. Grant it (or use a
|
|
52
|
+
platform-admin token).
|
|
53
|
+
- Empty list on a fresh deployment → seed hasn't run, or every lead was
|
|
54
|
+
converted (now listed as Bewerbungen) or withdrawn (deleted).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Debug a mail delivery
|
|
2
|
+
|
|
3
|
+
**When:** "no mail arrived", a red `deadLastHour` badge in the platform
|
|
4
|
+
console, or a `backend:convex` GlitchTip issue tagged `area=notifications`.
|
|
5
|
+
|
|
6
|
+
## 1. Is the sweeper alive, and is anything dead?
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
plexus-cli --env staging notifications sweeper-status
|
|
10
|
+
plexus-cli --env staging notifications list --status dead
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`ranAt` older than a few minutes ⇒ the cron is not firing (deploy broke?).
|
|
14
|
+
`deadLastHour > 0` ⇒ go to step 3 with an id from the list.
|
|
15
|
+
|
|
16
|
+
## 2. Find the delivery for a person
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
plexus-cli --env staging notifications list --to parent@example.org --since 2d
|
|
20
|
+
plexus-cli --env staging notifications list --template account.loginLink --since 24h
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For a sign-in problem start with `users login-trail` instead — see
|
|
24
|
+
[Trace why someone could not sign in](./trace-a-login.md).
|
|
25
|
+
|
|
26
|
+
Statuses: `queued` (waiting / backoff), `sending` (in flight), `sent` (relay
|
|
27
|
+
accepted — NOT "in the inbox"), `skipped` (a successful non-send: recipient
|
|
28
|
+
gone, precondition no longer true, reserved TLD — in `live` every RFC 2606
|
|
29
|
+
address, in `redirect` only `.invalid`), `dead` (gave up).
|
|
30
|
+
|
|
31
|
+
## 3. Read the evidence
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
plexus-cli --env staging notifications show <deliveryId>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| `errorCode` | Means | Do |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| `delivery_mode_invalid` / `config_missing` | deployment env is incomplete | needs a platform operator: complete the deployment's mail settings, then `retry` |
|
|
40
|
+
| `smtp_auth` | relay rejected the credentials | needs a platform operator: rotate the relay password in the deployment's settings, then `retry` |
|
|
41
|
+
| `smtp_policy` | relay rejected OUR sender at `MAIL FROM` | `MAIL_FROM_ADDRESS` not allowed for this account — ask the mail admin |
|
|
42
|
+
| `smtp_content` with 4xx at `DATA` | milter (DKIM) hiccup — retried automatically | if it stays: the relay's DKIM key/permissions — ask the mail admin |
|
|
43
|
+
| `smtp_recipient_rejected` | 5xx at `RCPT TO` | the address does not exist — nothing to retry |
|
|
44
|
+
| `recipient_revoked` / `recipient_changed` / `recipient_deleted` | identity changed after enqueue | expected; re-raise the event if still wanted |
|
|
45
|
+
| `never_claimed` | the dispatch action fails BEFORE it can claim | bundle/import problem — needs a platform operator (deployment logs of the action), fix, `retry` |
|
|
46
|
+
| `unknown_outcome` | action died after SMTP began | the recipient MAY have received it; decide, then `retry` |
|
|
47
|
+
|
|
48
|
+
`lastSmtpCommand` / `lastSmtpResponseCode` / `lastSmtpResponse` carry the raw
|
|
49
|
+
relay line. `possibleDuplicate: true` means attempt N may already have landed.
|
|
50
|
+
|
|
51
|
+
## 4. Prove the configuration end to end
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
plexus-cli --env staging notifications self-test --to <your-address>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Verifies the connection + credentials, sends one mail, returns the SMTP
|
|
58
|
+
transcript. On staging the mail is redirected like every other; on prod it
|
|
59
|
+
really goes to `--to`.
|
|
60
|
+
|
|
61
|
+
## 5. Retry
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
plexus-cli --env staging notifications retry <deliveryId>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Only `dead` rows. It is a fresh attempt through the normal claim path — the
|
|
68
|
+
retry budget is widened by exactly one.
|
|
69
|
+
|
|
70
|
+
## Gotchas
|
|
71
|
+
|
|
72
|
+
- `sent` = accepted by the relay. The last hop (relay → GMX) is invisible; on
|
|
73
|
+
staging the platform operator reads the catch-all mailbox.
|
|
74
|
+
- A `451` is not always transient: the relay says `451 4.7.1` for a
|
|
75
|
+
sender-policy violation and for a broken DKIM key. Look at the COMMAND.
|
|
76
|
+
- Locally nothing is ever sent (`dry`): the mail is in the Dev Rail →
|
|
77
|
+
`notifications:listDevPreviews`.
|