Skip to content
cccskills
FreePublished on GitHub

delete-user

Remove / reset a user (and their partition) on a running MeshWeaver portal (AKS). Use when you need to kick out a user — e.g. a throwaway test account — so they re-onboard from scratch. The normal delete API REFUSES a user's home ("user partition root … cannot be deleted"), so deletion is a Postgres-schema drop, not an MCP call. Covers where a user's data actually lives (per-user PG schemas named by BOTH id AND email, e.g. `rbuergi` + `rbuergi@systemorph.com`), how to reach PG (the connection string is inline in the portal's `ConnectionStrings__memex` env — NOT the `POSTGRES_PASSWORD` secret), the substring-match footgun (`buergi` also matches admin `rbuergi`), the in-memory resurrection gotcha (a recycle re-saves the node — a portal restart is what actually clears it), and the half-onboarded case where the user has NO schema at all (in-memory only → restart is the whole fix).

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12 stars(GitHub 12 + cccskills 0)

Run score Not tested

See how to install

Files included (1)

  • SKILL.md8.6 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

The cluster name, resource group and namespace list are deployment identities and live in the PRIVATE Systemorph/Memex repo — deployments/aks/envs.json (cluster + environments) and docs/deployments.md. Export them once per session rather than hard-coding them here:

eval "$(gh api repos/Systemorph/Memex/contents/deployments/aks/envs.json --jq '.content' | base64 -d \
  | jq -r '"AKS_RG=\(.cluster.resourceGroup) AKS_CLUSTER=\(.cluster.name) NAMESPACES=\"\([.environments[].ns]|join(" "))\""')"

Verified to set all three (resource group / cluster name / the namespaces) on 2026-08-19. Reading it from the source of truth also means a new environment shows up here automatically instead of this file going quietly stale.

/delete-user — remove a user + partition from a running MeshWeaver portal

Deleting a user is not an MCP/API operation — the framework guards it. This skill does it via the Postgres layer, safely.

1. Why the API refuses

The MCP delete (and DeleteNodeRequest) guards user partition roots:

Cannot delete '<user>': '<user>' is a user partition root (home) and cannot be deleted — that would remove the entire partition (threads, tokens, settings, access grants) and lock the user out.

So you cannot delete a user via delete @<user>. You drop their Postgres schema(s).

2. Where a user's data lives

public.mesh_nodes is empty by design; each partition is its own PG schema ({id}.mesh_nodes + satellite tables access, threads, activities, …). A user typically has TWO schemas — one by id and one by email:

user-a                      user-a@example.com
user-b                      …

So dropping <id> alone can leave <id>@<domain> behind. Drop both.

⚠️ Half-onboarded users have NO schema. If onboarding never provisioned the partition (the user created no content / a write failed), the User node lives only in the portal's in-memory workspace — nothing in PG at all. Then there is nothing to drop; step 5 (restart) is the whole fix. Verify with the find in step 4.

3. Reach Postgres

🚨 This whole procedure is break-glass by construction (maintainer, 2026-09-08: operations go through the control instance's Hosting API, not the cluster). There is no Hosting/InstanceAction kind for removing a user, so the schema drop below is a cluster write with no record and no audit — do it only on an explicit order, write it up as break-glass, and file the missing action against the Hosting package. The restart in step 5 IS an action kind: file a Restart Hosting/InstanceAction rather than the kubectl rollout restart shown. Policy: OperatingFromThePortal.md.

The private AKS cluster: kubectl only via az aks command invoke -g "$AKS_RG" -n "$AKS_CLUSTER" --command "…".

The DB password is inline in the portal's ConnectionStrings__memex env — NOT the POSTGRES_PASSWORD secret (that secret is a different, unused value; using it gives password authentication failed). Parse the real one out of the portal env and hand it to a postgres:16 client pod (the invoke shell has no sed/tr/python3 — use bash parameter expansion only). Pass SQL to the pod base64-encoded to dodge four levels of quoting.

Connection string shape: Host=<db-host>;Port=5432;Username=<db-admin>;Password=…;Database=<database>;SslMode=Require;…

4. FIND the user's footprint first (never drop blind)

Run this to see the exact schema names + confirm where (if anywhere) the user is persisted. Match on the full id, and beware: buergi matches BOTH roland.buergi and admin rbuergi — use the distinguishing part of the id (roland, not buergi).

# find_user.sh  — pass the user's DISTINCTIVE id fragment as $1 (e.g. "roland", not "buergi")
NEEDLE="${1:?usage: find_user.sh <distinctive-id-fragment>}"
cat > /tmp/find.sql <<SQL
\echo '== schemas matching =='
SELECT schema_name FROM information_schema.schemata WHERE schema_name ILIKE '%${NEEDLE}%';
DO \$\$ DECLARE r record; c int; BEGIN
  FOR r IN SELECT table_schema FROM information_schema.tables WHERE table_name='mesh_nodes' LOOP
    EXECUTE format('SELECT count(*) FROM %I.mesh_nodes WHERE mesh_nodes::text ILIKE %L', r.table_schema, '%${NEEDLE}%') INTO c;
    IF c>0 THEN RAISE NOTICE 'mesh_nodes HIT schema=% rows=%', r.table_schema, c; END IF;
  END LOOP;
END \$\$;
SQL
run_psql_pod /tmp/find.sql     # see the pod helper below

Zero schemas + zero mesh_nodes hits ⇒ half-onboarded (in-memory only) ⇒ skip to step 5.

5. DROP the schema(s), then RESTART the portal

Dropping the schema is not enough on its own: the portal's in-memory workspace resurrects the node on the next partition reactivation (a recycle @<user> disposes the hub but reactivation re-saves it — the version bumps and it's back). The reliable clear is a portal restart (nothing in PG to reload).

# 1) drop every schema whose name contains the DISTINCTIVE fragment (both id and email variants)
DROP SCHEMA "roland.buergi" CASCADE;
DROP SCHEMA "roland.buergi@gmail.com" CASCADE;   -- if it exists

# 2) clear the in-memory copy so it can't resurrect
az aks command invoke -g "$AKS_RG" -n "$AKS_CLUSTER" --command \
  "kubectl -n <namespace> rollout restart deployment/memex-portal-deployment; kubectl -n <namespace> rollout status deployment/memex-portal-deployment --timeout=300s"

After the restart, get @<user> should be Not found, and the user re-onboards on next login.

The psql-client-pod helper (run_psql_pod)

Put this in your script; it pulls the connection from the portal env and runs a base64'd SQL file in a throwaway postgres:16 pod. The password is only ever in the pod env (never printed).

run_psql_pod() {  # $1 = path to a .sql file
  local NS="${NS:?set NS to the portal namespace}" SQLFILE="$1"
  local CS host user pass db B64
  CS=$(kubectl -n $NS exec deploy/memex-portal-deployment -c memex-portal -- printenv ConnectionStrings__memex 2>/dev/null)
  host=${CS#*Host=};     host=${host%%;*}
  user=${CS#*Username=}; user=${user%%;*}
  pass=${CS#*Password=}; pass=${pass%%;*}
  db=${CS#*Database=};   db=${db%%;*}
  B64=$(base64 < "$SQLFILE" | tr -d '\n')
  kubectl -n $NS delete pod pgc --ignore-not-found >/dev/null 2>&1
  kubectl -n $NS run pgc --image=postgres:16 --restart=Never \
    --env="PGPASSWORD=$pass" \
    --env="CONN=sslmode=require host=$host port=5432 user=$user dbname=$db" \
    --env="B64=$B64" \
    --command -- bash -c 'echo "$B64" | base64 -d > /tmp/q.sql; psql "$CONN" -f /tmp/q.sql 2>&1' >/dev/null 2>&1
  for i in $(seq 1 40); do
    ph=$(kubectl -n $NS get pod pgc -o jsonpath='{.status.phase}' 2>/dev/null || true)
    { [ "$ph" = Succeeded ] || [ "$ph" = Failed ]; } && break; sleep 3
  done
  kubectl -n $NS logs pgc 2>&1
  kubectl -n $NS delete pod pgc --ignore-not-found >/dev/null 2>&1
}

Wrap the whole script in a file and invoke it with az aks command invoke … --command "bash script.sh" --file script.sh (the --file upload avoids inline-quoting hell; the invoke shell has kubectl but not sed/tr/python3).

Checklist

  1. Identify the user's distinctive id fragment (not a substring shared with admins).
  2. Find their schemas + mesh_nodes rows (step 4). Confirm you're touching only the right user.
  3. DROP SCHEMA "<id>" CASCADE; and DROP SCHEMA "<id>@<domain>" CASCADE; for each match.
  4. Restart the portal (step 5) — the in-memory workspace resurrects the node otherwise.
  5. Verify get @<user> → Not found; the user re-onboards on next login.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

async

Free

Forwarding AccessContext through async/reactive boundaries, and the no-async rule on the actor-model mesh. Use when writing or reviewing any hub-reachable / Blazor-view / agent-round / IIoPool code that creates, reads, or updates a mesh node, OR when a write "silently does nothing" / "Access denied" appears after a .Subscribe / IIoPool / Observable.Create hop. The async/await/ToTask/.Wait/.Result/.GetAwaiter/FirstAsync family and lost AccessContext are the two ways async boundaries cause wedges and storms. Grounded in AsynchronousCalls.md, SyncedMeshNodeQueries.md, AccessContextPropagation.md.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 29, 2026

ci

Free

What CI proves, what it cannot prove, and how to write a gate that cannot pass on no evidence. Use when authoring or changing a GitHub Actions workflow, adding a required check, wiring a satellite repo CI, or reasoning about whether a green run actually covers your change. Covers the absolute no-skip-trapdoor rule (a gate must never carry continue-on-error on its input step nor an if that asks whether a secret is set — GitHub paints a skipped job the same colour as a passed one), the reusable workflow_call jobs satellite repos must call instead of hand-rolling, why runs on main are deliberately never cancelled, and the large body of in-mesh C# that no dotnet build or test ever type-checks.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 29, 2026

debug

Free

Debug a deadlock / hang / timeout in the mesh — a test that times out, a hub handler whose response never arrives, a GetRemoteStream / GetMeshNodeStream / GetQuery that never emits, an agent round or per-instance-hub activation that parks forever. Use whenever something times out and you need to find WHERE the message flow breaks down. The timeout is the symptom, never the bug — raising it just hangs longer. Grounded in DebuggingMessageFlow.md.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 29, 2026

deploy

Free

Deploy MeshWeaver — the two routes (shared AKS cluster, and Azure Container Apps via the Aspire test/prod modes) and the traps that make a deploy look done when it is not. Use when rolling a code change onto a portal, standing up or patching an environment, or verifying that what is running is what you built. FIRST RULE (maintainer, 2026-09-08): on AKS an operation is a Hosting/InstanceAction on the control instance (Roll, Restart, Suspend, Audit, Reconcile, …) executed by the in-cluster operator — az aks command invoke / kubectl are break-glass, never the procedure. Also covers the bootstrap build-image / set-image / restart sequence the operator runs for you, why an env deploy.sh is first-time setup and not a code-update path, why the database migration is a run-once Job that a helm upgrade runs — never a Deployment to roll — and the DbVersionGate that makes a portal refuse to serve ahead of its schema.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 29, 2026

gui

Free

Build or review ANY MeshWeaver UI — layout areas, editable views, forms, tables, content editing, side-panel composers — by wiring the framework's EXISTING data-binding pieces, NEVER hand-rolling. The one rule is ALWAYS DATA-BIND, especially editable views: the backend layout area declares WHAT to render (a UiControl tree + node PATHS), and every value read / write-back happens on the GUI side through the per-node IMeshNodeStreamCache. Hand-woven binding — replicating a node into a /data/{id} copy + a save subscription, emitting HTML strings, hand-built selects/checkboxes, .Take(1) on a live stream, async/await/QueryAsync in a layout area — is the bug class behind "awaiting first data" / "renders empty" / a stuck editable view. Use when writing or reviewing a layout area, control, editor, form, table, or any data-bound view. Grounded in src/MeshWeaver.Layout/DataBinding/, Doc/GUI/DataBinding.md + Editor.md, the Edit extension (EditorExtensions.cs), and the node-bound editor controls.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 29, 2026

i18n

Free

Localize every user-visible string as you write it — the portal ships English + German, so a hard-coded UI string is a bug that renders English for every viewer. Use whenever you write or review text a human reads on screen: buttons, labels, tooltips, aria-labels, placeholders, page titles, empty states, validation messages, toasts, dialog copy, menu entries, settings tabs, notifications, errors. Covers the two shapes (a Translation attribute on a declaration, a strings.en/de key everywhere else), the second key home in the plugins repo whose drift guard compares VALUES and merges core-first, the CurrentUICulture ban (it covers date and number FORMATTING too), and the list of things deliberately NOT translated.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 29, 2026

See all skills by Systemorph

Report a problem with this skill