From 328442554c2b693bae2c48fda6735a38db1b818b Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Sun, 8 Feb 2026 19:15:56 -0500 Subject: [PATCH] docs: rename DEPLOY-SUBWORKFLOWS.md to ARCHITECTURE.md and rewrite Restructured as a proper technical architecture document: - Added Observability section (correlation IDs, structured errors, debugging) - Reorganized into logical flow: overview, request flow, contracts, internals - Removed stale rollback/backup references - Updated all references in README, CLAUDE.md, PROJECT.md, STATE.md Co-Authored-By: Claude Opus 4.6 --- .planning/PROJECT.md | 2 +- .planning/STATE.md | 6 +- ARCHITECTURE.md | 421 ++++++++++++++++++++++++ CLAUDE.md | 2 +- DEPLOY-SUBWORKFLOWS.md | 725 ----------------------------------------- README.md | 4 +- 6 files changed, 428 insertions(+), 732 deletions(-) create mode 100644 ARCHITECTURE.md delete mode 100644 DEPLOY-SUBWORKFLOWS.md diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md index dc3cd56..77a2fbf 100644 --- a/.planning/PROJECT.md +++ b/.planning/PROJECT.md @@ -62,7 +62,7 @@ When you get a container update notification or notice a service is down, you ca **Shipped:** v1.2 (2026-02-08) **Tech stack:** n8n workflow + Telegram Bot API + Docker socket proxy **Architecture:** 1 main workflow (166 nodes) + 7 sub-workflows (121 nodes) = 287 total nodes -**Files:** 8 workflow JSON files (10,987 LOC), README.md (264 lines), DEPLOY-SUBWORKFLOWS.md (725 lines) +**Files:** 8 workflow JSON files (10,987 LOC), README.md (264 lines), ARCHITECTURE.md (725 lines) **Sub-workflows:** Update, Actions, Logs, Batch UI, Status, Confirmation, Matching ## Context diff --git a/.planning/STATE.md b/.planning/STATE.md index a65f6be..cfb9f6f 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -60,7 +60,7 @@ Phase 13: Documentation Overhaul [**********] 100% COMPLETE (1/1 plan - `n8n-actions.json` -- Container Actions sub-workflow (11 nodes) -- ID: `fYSZS5PkH0VSEaT5` - `n8n-logs.json` -- Container Logs sub-workflow (9 nodes) -- ID: `oE7aO2GhbksXDEIw` - `n8n-matching.json` -- Container Matching sub-workflow (23 nodes) -- ID: `kL4BoI8ITSP9Oxek` -- `DEPLOY-SUBWORKFLOWS.md` -- Full architecture docs, contracts, and node analysis +- `ARCHITECTURE.md` -- Full architecture docs, contracts, and node analysis ## Technical Notes @@ -139,7 +139,7 @@ Phase 13: Documentation Overhaul [**********] 100% COMPLETE (1/1 plan - [Quick 1-1]: Removed 6 orphan callback nodes (no incoming connections after Phase 10 modularization) - [Quick 1-1]: Achieved structural minimum of 166 nodes (per Phase 10.1-07 analysis) - [Phase 12-01]: Document Unraid badge limitation instead of programmatic fix (Unraid API integration adds complexity for cosmetic issue) -- [Phase 13-01]: Remove DEPLOYMENT_GUIDE.md instead of updating (outdated Phase 10-05 content, fully superseded by DEPLOY-SUBWORKFLOWS.md) +- [Phase 13-01]: Remove DEPLOYMENT_GUIDE.md instead of updating (outdated Phase 10-05 content, fully superseded by ARCHITECTURE.md) - [Phase 13-01]: Separate Configuration from Installation in README (installation should be linear and action-only) ## Phase 10.1 Progress @@ -272,7 +272,7 @@ All 7 sub-workflows deployed and operational: - Separated configuration from installation (installation now linear and action-only) - Added 5 common troubleshooting scenarios with fixes - Removed outdated DEPLOYMENT_GUIDE.md (Phase 10-05, 3 sub-workflows, 199 nodes) -- Consolidated to single technical reference: DEPLOY-SUBWORKFLOWS.md (725 lines, 7 sub-workflows, 166 nodes) +- Consolidated to single technical reference: ARCHITECTURE.md (725 lines, 7 sub-workflows, 166 nodes) - All v1.2 features now documented (batch ops, update all, inline keyboard, 7 sub-workflows) ## Quick Tasks Completed diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..c16e1d2 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,421 @@ +# Architecture + +Technical reference for the Unraid Docker Manager workflow system. + +## System Overview + +The bot is an n8n workflow that receives Telegram messages, routes them through authentication and keyword matching, dispatches to domain-specific sub-workflows, and sends responses back to the user. + +``` +Telegram + | + v +n8n-workflow.json (166 nodes) + |-- Auth check (Telegram user ID) + |-- Correlation ID generation + |-- Text path: Keyword Router -> Parse Command -> Match Container -> Execute + |-- Callback path: Parse Callback Data -> Route -> Execute + | + |-- n8n-update.json (34 nodes) Container image pull + recreate + |-- n8n-actions.json (11 nodes) Start / stop / restart + |-- n8n-logs.json (9 nodes) Log retrieval + formatting + |-- n8n-batch-ui.json (17 nodes) Batch selection keyboard UI + |-- n8n-status.json (11 nodes) Container list + status display + |-- n8n-confirmation.json (16 nodes) Confirmation dialogs + |-- n8n-matching.json (23 nodes) Container name matching + | + v +docker-socket-proxy (tecnativa/docker-socket-proxy) + | + v +Docker Engine +``` + +**Total:** 287 nodes (166 main + 121 across 7 sub-workflows) + +## Workflow Files + +| File | n8n ID | Purpose | Nodes | +|------|--------|---------|-------| +| n8n-workflow.json | `HmiXBlJefBRPMS0m4iNYc` | Main orchestrator | 166 | +| n8n-update.json | `7AvTzLtKXM2hZTio92_mC` | Container update (pull, recreate, cleanup) | 34 | +| n8n-actions.json | `fYSZS5PkH0VSEaT5` | Container start/stop/restart | 11 | +| n8n-logs.json | `oE7aO2GhbksXDEIw` | Container log retrieval | 9 | +| n8n-batch-ui.json | `ZJhnGzJT26UUmW45` | Batch selection keyboard | 17 | +| n8n-status.json | `lqpg2CqesnKE2RJQ` | Container list and status | 11 | +| n8n-confirmation.json | `fZ1hu8eiovkCk08G` | Confirmation dialogs | 16 | +| n8n-matching.json | `kL4BoI8ITSP9Oxek` | Container name matching | 23 | + +## Request Flow + +### Text Commands + +1. Telegram Trigger receives message +2. Auth IF node checks user ID +3. Correlation ID generator creates a unique request trace ID (`timestamp-random`) +4. Keyword Router (Switch node) matches command keyword +5. Parse Command (Code node) extracts parameters +6. Matching sub-workflow resolves container name to Docker ID +7. Domain sub-workflow executes the operation +8. Result routed to Telegram response node + +### Callback (Inline Keyboard) + +1. Telegram Trigger receives callback query +2. Auth IF node checks user ID +3. Correlation ID generator creates a unique request trace ID +4. Parse Callback Data (Code node, 441 lines) decodes callback string +5. Route Callback (Switch node) dispatches by prefix +6. Domain sub-workflow executes the operation +7. Result routed to Telegram response node (editMessageText) + +## Observability + +### Correlation IDs + +Every request gets a unique correlation ID generated at the entry point of the main workflow. This ID flows through all sub-workflow calls via Prepare Input nodes, enabling request tracing across workflow boundaries in the n8n execution log. + +**How it works:** +- Two generator nodes in the main workflow: one for the text path, one for the callback path +- Format: `timestamp-randomString` (no external dependencies) +- All 19 Prepare Input nodes include `correlationId: $json.correlationId` in their output +- Sub-workflows receive the ID as an input field and can reference it in their logs + +**Where to find it:** Open any execution in the n8n UI, inspect the output of a Prepare Input node — the `correlationId` field traces back to the original user request. + +**Limitations:** Correlation IDs are only visible in the n8n execution log. There is no persistent storage or user-facing output. n8n's workflow static data is execution-scoped (not workflow-scoped), so ring buffers and cross-execution logging are not possible on this platform. + +### Structured Error Returns + +All 7 sub-workflows return structured error objects on failure: + +```json +{ + "success": false, + "error": "Container not found: foo", + "correlationId": "1707400000-abc123" +} +``` + +This provides a consistent error shape for the main workflow to route and format error messages to the user. + +### Debugging a Request + +1. Open the n8n execution list for the main workflow +2. Find the execution by timestamp or Telegram message content +3. Check the Prepare Input node output for the `correlationId` +4. Search sub-workflow executions for the same `correlationId` +5. Trace the full request path: main workflow -> sub-workflow -> Docker API -> response + +## Sub-workflow Contracts + +Each sub-workflow has a defined input/output contract. The main workflow communicates with sub-workflows through Prepare Input (Code) nodes that build the input object, and Route Result (Code) nodes that interpret the response. + +### n8n-update.json (Container Update) + +**Input:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| containerId | string | yes* | Docker container ID (empty string to resolve by name) | +| containerName | string | yes | Container display name | +| chatId | number | yes | Telegram chat ID | +| messageId | number | yes | Telegram message ID (0 for text mode) | +| responseMode | string | yes | `"text"`, `"inline"`, or `"batch"` | +| correlationId | string | no | Request trace ID | + +*containerId can be empty — the sub-workflow resolves by name via its Resolve Container ID node. + +**Output:** + +| Outcome | Fields | +|---------|--------| +| Updated | `success: true, updated: true, message, oldDigest, newDigest` | +| No update needed | `success: true, updated: false, message` | +| Error | `success: false, updated: false, message` | + +**Callers:** Execute Text Update, Execute Callback Update, Execute Batch Update + +--- + +### n8n-actions.json (Container Actions) + +**Input:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| containerId | string | yes | Docker container ID | +| containerName | string | yes | Container display name | +| action | string | yes | `"start"`, `"stop"`, or `"restart"` | +| chatId | number | yes | Telegram chat ID | +| messageId | number | yes | Telegram message ID (0 for text mode) | +| responseMode | string | yes | `"text"`, `"inline"`, or `"batch"` | +| correlationId | string | no | Request trace ID | + +**Output:** `success, message, action, containerName, containerId, chatId, messageId, responseMode` + +HTTP status codes are checked before message content: 204 = success, 304 = already in state (treated as success), others = error. + +**Callers:** Execute Container Action, Execute Inline Action, Execute Batch Action Sub-workflow + +--- + +### n8n-logs.json (Container Logs) + +**Input:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| containerName | string | yes* | Container name for lookup | +| containerId | string | no | Docker container ID (optional) | +| lineCount | number | no | Lines to retrieve (default: 50, max: 1000) | +| chatId | number | yes | Telegram chat ID | +| messageId | number | no | Telegram message ID (default: 0) | +| responseMode | string | no | `"text"` or `"inline"` (default: "text") | +| correlationId | string | no | Request trace ID | + +*Either containerId or containerName is required. + +**Output:** `success: true, message, containerName, lineCount` + +Errors (container not found, Docker error) throw exceptions handled by n8n's error system. + +**Callers:** Execute Text Logs, Execute Inline Logs + +--- + +### n8n-batch-ui.json (Batch Selection UI) + +**Input:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| chatId | number | yes | Telegram chat ID | +| messageId | number | yes | Telegram message ID | +| callbackData | string | yes | Raw callback data string | +| queryId | string | yes | Telegram callback query ID | +| action | string | yes | `"mode"`, `"toggle"`, `"nav"`, `"exec"`, `"clear"`, `"cancel"` | +| batchPage | number | no | Current page number (default: 0) | +| selectedCsv | string | no | Comma-separated selected container names | +| toggleName | string | no | Container name being toggled | +| batchAction | string | no | Action for batch execution (stop/restart/update) | +| correlationId | string | no | Request trace ID | + +**Output:** + +| action | Description | +|--------|-------------| +| `"keyboard"` | Rendered selection keyboard with checkmarks | +| `"execute"` | User confirmed — includes containerNames and batchAction | +| `"cancel"` | User cancelled batch selection | + +Batch selection uses bitmap encoding (base36 BigInt) to fit container selections within Telegram's 64-byte callback data limit. + +**Callers:** Execute Batch UI + +--- + +### n8n-status.json (Container Status/List) + +**Input:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| chatId | number | yes | Telegram chat ID | +| messageId | number | yes | Telegram message ID | +| action | string | yes | `"list"`, `"status"`, `"paginate"` | +| containerId | string | no | Docker container ID (for status lookup) | +| containerName | string | no | Container name (for status lookup) | +| page | number | no | Page number for pagination (default: 0) | +| queryId | string | no | Telegram callback query ID | +| searchTerm | string | no | Container name search term | +| correlationId | string | no | Request trace ID | + +**Output:** + +| action | Description | +|--------|-------------| +| `"list"` | Container list with pagination keyboard | +| `"status"` | Single container detail with action buttons | +| `"paginate"` | Updated page of container list | + +The container list keyboard includes an "Update All :latest" button after the navigation row. + +**Callers:** Execute Container Status, Execute Select Status, Execute Paginate Status, Execute Batch Cancel Status + +--- + +### n8n-confirmation.json (Confirmation Dialogs) + +**Input:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| chatId | number | yes | Telegram chat ID | +| messageId | number | yes | Telegram message ID | +| action | string | yes | `"show_stop"`, `"show_update"`, `"confirm"`, `"cancel"`, `"expired"` | +| containerId | string | no | Docker container ID | +| containerName | string | yes | Container display name | +| confirmAction | string | no | `"stop"` or `"update"` (for confirm action) | +| confirmationToken | string | no | Timestamp token for 30-second expiry check | +| expired | boolean | no | Whether confirmation has expired | +| responseMode | string | no | `"inline"` (default) | +| correlationId | string | no | Request trace ID | + +This sub-workflow internally calls n8n-actions.json for confirmed stop actions. + +**Output:** + +| action | Description | +|--------|-------------| +| `"show_stop"` | Stop confirmation dialog rendered | +| `"show_update"` | Update confirmation dialog rendered | +| `"confirm_stop_result"` | Stop executed, result returned | +| `"confirm_update"` | Update confirmed, containerId/name returned for update sub-workflow | +| `"cancel"` | Confirmation cancelled | +| `"expired"` | Confirmation token expired (30-second timeout) | + +**Callers:** Execute Confirmation (fed by 3 Prepare Input nodes: Prepare Confirm Input, Prepare Show Stop Input, Prepare Show Update Input) + +--- + +### n8n-matching.json (Container Matching/Disambiguation) + +**Input:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| action | string | yes | `"match_action"`, `"match_update"`, or `"match_batch"` | +| containerList | string | yes | Raw Docker API JSON output (container list) | +| searchTerm | string | yes | Container name query to match | +| selectedContainers | string | no | Comma-separated names for batch matching | +| chatId | number | yes | Telegram chat ID | +| messageId | number | yes | Telegram message ID | +| correlationId | string | no | Request trace ID | + +Matching priority: exact match > single substring match > multiple matches (disambiguation) > no match (suggestion keyboard). + +**Output (action match):** + +| action | Description | +|--------|-------------| +| `"matched"` | Single container matched — includes containerId, containerName | +| `"multiple"` | Multiple matches — includes matches array for disambiguation | +| `"no_match"` | No match found | +| `"suggestion"` | Suggestion keyboard with close matches | +| `"error"` | Matching error | + +**Output (update match):** Same as action match with `_update` suffix on action values. + +**Output (batch match):** + +| action | Description | +|--------|-------------| +| `"batch_matched"` | All names resolved — includes matchedContainers array | +| `"disambiguation"` | Some names ambiguous — disambiguation keyboard | +| `"not_found"` | Some names not found | + +**Callers:** Execute Action Match, Execute Update Match, Execute Batch Match + +## Execute Workflow Node Map + +17 Execute Workflow nodes in the main workflow dispatch to 7 sub-workflows: + +| Target | Execute Nodes | +|--------|---------------| +| n8n-update.json | Execute Text Update, Execute Callback Update, Execute Batch Update | +| n8n-actions.json | Execute Container Action, Execute Inline Action, Execute Batch Action Sub-workflow | +| n8n-logs.json | Execute Text Logs, Execute Inline Logs | +| n8n-batch-ui.json | Execute Batch UI | +| n8n-status.json | Execute Container Status, Execute Select Status, Execute Paginate Status, Execute Batch Cancel Status | +| n8n-confirmation.json | Execute Confirmation | +| n8n-matching.json | Execute Action Match, Execute Update Match, Execute Batch Match | + +## Main Workflow Internals + +### Node Breakdown (166 nodes) + +| Category | Count | Purpose | +|----------|-------|---------| +| Code | 60 | Command parsing, input preparation, result routing, response building, batch orchestration | +| HTTP Request | 40 | Docker API and Telegram API calls | +| Telegram | 23 | User-facing response nodes | +| Execute Workflow | 17 | Sub-workflow dispatch | +| Switch | 13 | Keyword Router, Route Callback, result routing | +| If | 8 | Auth checks, batch completion, expiry, status routing | +| Execute Command | 6 | Docker CLI (container list, exec) | +| Telegram Trigger | 1 | Entry point | + +### Code Node Categories + +The 60 Code nodes break down into orchestration categories. All are infrastructure — none contain extractable domain logic: + +| Category | Count | Role | +|----------|-------|------| +| prepare-input | 27 | Build input objects for sub-workflow calls | +| route-result | 12 | Interpret sub-workflow responses for routing | +| build-response | 8 | Build Telegram messages and keyboards | +| orchestration | 6 | Batch loop control and state management | +| parse-command | 5 | Parse text commands into structured data | +| domain-logic | 2 | Legacy candidates (net-negative extraction) | + +### Callback Data Encoding + +Telegram limits callback data to 64 bytes. The system uses two encoding schemes: + +**Colon-delimited** for single operations: `s:containerId` (status), `stop:containerId` (action), `cfm:stop:containerId:token` (confirmation) + +**Bitmap encoding** for batch selection: `b:0:1a3:5` where the middle segment is a base36 BigInt representing selected container indices. Supports 50+ containers within the 64-byte limit. + +Legacy parsers (`batch:toggle:`, `batch:nav:`, `batch:exec:`) are retained for graceful migration of in-flight messages. + +## Deployment + +### Redeploying After Changes + +1. Import the modified sub-workflow JSON into n8n +2. If main workflow changed, re-import n8n-workflow.json +3. Activate the workflow + +Workflow IDs are stable — n8n preserves them across re-imports of the same workflow. + +### Execute Workflow Node Format + +All Execute Workflow nodes use typeVersion 1.2: + +```json +"workflowId": { "__rl": true, "mode": "list", "value": "" } +``` + +### Testing Checklist + +After deployment, verify: + +- [ ] `status` — Shows container list with pagination +- [ ] Tap container — Shows detail with action buttons +- [ ] `stop ` — Confirmation dialog, confirm executes stop +- [ ] `update ` — Confirmation dialog, confirm pulls image + recreates +- [ ] `restart ` — Immediate restart +- [ ] `logs ` — Shows last 50 lines +- [ ] `stop plex sonarr` — Batch selection keyboard +- [ ] Select multiple, execute — Batch processes all selected +- [ ] `update all` — Lists :latest containers, confirm updates all +- [ ] Update All button in keyboard — Same flow via inline keyboard + +## Known Limitations + +### Unraid Update Badges + +After the bot updates a container, Unraid's Docker tab may show "apply update" on the next check. The bot uses Docker API directly while Unraid tracks containers through its XML template system — it doesn't know the container was updated externally. + +**Resolution:** Click "Apply Update" in Unraid. It completes instantly since the image is already cached. + +**Why not automated:** Clearing the badge would require calling Unraid's internal web API (authentication, template parsing) for a cosmetic issue that takes one click. + +### n8n Static Data + +n8n workflow static data (`$getWorkflowStaticData('global')`) is execution-scoped, not workflow-scoped. Data written in one execution is not available in the next. This prevents persistent cross-execution features like error ring buffers or debug command history. + +### Orphan Nodes + +3 legacy Code nodes remain in the main workflow (Build Action Command, Build Immediate Action Command, Prepare Cancel Return). They are unreachable dead code from pre-modularization inline action paths. They have no incoming connections and do not affect functionality. diff --git a/CLAUDE.md b/CLAUDE.md index db11755..2f5f9e8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -129,7 +129,7 @@ POST /api/v1/workflows/{id}/deactivate — Deactivate workflow - `n8n-workflow.json` — Main n8n workflow (Telegram bot entry point) - `n8n-*.json` — Sub-workflows (7 total, see table above) - `.planning/` — GSD planning directory (STATE.md, ROADMAP.md, phases/) -- `DEPLOY-SUBWORKFLOWS.md` — Architecture docs, contracts, node analysis +- `ARCHITECTURE.md` — Architecture docs, contracts, node analysis - `.env.n8n-api` — n8n API credentials (gitignored) ## n8n Workflow Conventions diff --git a/DEPLOY-SUBWORKFLOWS.md b/DEPLOY-SUBWORKFLOWS.md deleted file mode 100644 index c7f4a90..0000000 --- a/DEPLOY-SUBWORKFLOWS.md +++ /dev/null @@ -1,725 +0,0 @@ -# Sub-workflow Deployment Guide - -## Overview - -Phase 10.1 (Aggressive Workflow Modularization) reduced the main workflow from 192 nodes to 168 nodes by extracting domain-specific functionality into sub-workflows. - -**Final State:** -- Main workflow: 168 nodes (reduced by 24 nodes, -12.5%) -- Total sub-workflows: 7 (3 pre-existing + 4 new) - -## Sub-workflows - -| File | Purpose | Nodes | Status | -|------|---------|-------|--------| -| n8n-update.json | Container update operations | 34 | Deployed (ID: `7AvTzLtKXM2hZTio92_mC`) | -| n8n-actions.json | Container start/stop/restart | 11 | Deployed (ID: `fYSZS5PkH0VSEaT5`) | -| n8n-logs.json | Container log retrieval | 9 | Deployed (ID: `oE7aO2GhbksXDEIw`) | -| n8n-batch-ui.json | Batch selection UI | 16 | Deployed | -| n8n-status.json | Container list/status | 11 | Deployed (ID: `lqpg2CqesnKE2RJQ`) | -| n8n-confirmation.json | Confirmation dialogs | 16 | Deployed (ID: `fZ1hu8eiovkCk08G`) | -| n8n-matching.json | Container matching/disambiguation | 23 | Deployed (ID: `kL4BoI8ITSP9Oxek`) | - -## Deployment Status - -All sub-workflows have been deployed. The main workflow (`n8n-workflow.json`) contains all correct workflow IDs. - -To redeploy after changes: -1. Import the modified sub-workflow JSON to n8n -2. Re-import `n8n-workflow.json` if main workflow changed -3. Activate the workflow - -## Execute Workflow Node Mapping - -| Node Name | Target Sub-workflow | Workflow ID | -|-----------|---------------------|-------------| -| Execute Text Update | n8n-update.json | `7AvTzLtKXM2hZTio92_mC` | -| Execute Callback Update | n8n-update.json | `7AvTzLtKXM2hZTio92_mC` | -| Execute Batch Update | n8n-update.json | `7AvTzLtKXM2hZTio92_mC` | -| Execute Container Action | n8n-actions.json | `fYSZS5PkH0VSEaT5` | -| Execute Inline Action | n8n-actions.json | `fYSZS5PkH0VSEaT5` | -| Execute Batch Action Sub-workflow | n8n-actions.json | `fYSZS5PkH0VSEaT5` | -| Execute Text Logs | n8n-logs.json | `oE7aO2GhbksXDEIw` | -| Execute Inline Logs | n8n-logs.json | `oE7aO2GhbksXDEIw` | -| Execute Batch UI | n8n-batch-ui.json | Deployed | -| Execute Container Status | n8n-status.json | `lqpg2CqesnKE2RJQ` | -| Execute Select Status | n8n-status.json | `lqpg2CqesnKE2RJQ` | -| Execute Paginate Status | n8n-status.json | `lqpg2CqesnKE2RJQ` | -| Execute Batch Cancel Status | n8n-status.json | `lqpg2CqesnKE2RJQ` | -| Execute Confirmation | n8n-confirmation.json | `fZ1hu8eiovkCk08G` | -| Execute Action Match | n8n-matching.json | `kL4BoI8ITSP9Oxek` | -| Execute Update Match | n8n-matching.json | `kL4BoI8ITSP9Oxek` | -| Execute Batch Match | n8n-matching.json | `kL4BoI8ITSP9Oxek` | - -## Rollback - -If issues are encountered, backup files are available: - -```bash -# Restore to before batch UI extraction -cp n8n-workflow.json.backup-batch n8n-workflow.json - -# Restore to before status extraction -cp n8n-workflow.json.backup-status n8n-workflow.json - -# Restore to before confirmation extraction -cp n8n-workflow.json.backup-confirm n8n-workflow.json -``` - -Then re-import the restored `n8n-workflow.json` to n8n. - -## Known Limitations - -### Unraid Update Badges (UNR-01) - -After the bot updates a container, Unraid's Docker tab may show "apply update" on the next update check. This is expected behavior. - -**Why this happens:** The bot uses the Docker API directly to pull images and recreate containers. Unraid manages containers through its own XML template system and doesn't know the container was updated outside of its control. - -**What to do:** Click "Apply Update" in Unraid's Docker tab. It completes instantly because the image is already pulled and cached — Unraid just recreates the container from its template to sync its internal tracking. - -**Important:** "Check for Updates" in Unraid does NOT clear an existing badge. It may actually cause a new badge to appear if the bot updated a container that Unraid hadn't checked yet. - -**Why not fix programmatically:** Clearing the badge would require calling Unraid's internal web API to "apply" the update through its template system. This adds significant complexity (Unraid web UI access, authentication, template parsing) for a cosmetic issue that takes one click to resolve. - ---- - -## Architecture - -``` -n8n-workflow.json (168 nodes - orchestrator) -├── Telegram Trigger (1) -├── Auth + Error Handling -├── Keyword Routing (switch/if nodes) -│ -├── Update Operations -│ ├── Execute Text Update ──────────┐ -│ ├── Execute Callback Update ──────┼── n8n-update.json (34 nodes) -│ └── Execute Batch Update ─────────┘ -│ -├── Action Operations -│ ├── Execute Container Action ─────┐ -│ ├── Execute Inline Action ────────┼── n8n-actions.json (11 nodes) -│ └── Execute Batch Action ─────────┘ -│ -├── Log Operations -│ ├── Execute Text Logs ────────────┐ -│ └── Execute Inline Logs ──────────┴── n8n-logs.json (9 nodes) -│ -├── Batch UI -│ └── Execute Batch UI ─────────────── n8n-batch-ui.json (16 nodes) -│ ├── Returns: keyboard/confirmation/execute/cancel/limit -│ └── Main routes response based on action -│ -├── Container Status -│ ├── Execute Container Status ─────┐ -│ ├── Execute Select Status ────────┼── n8n-status.json (11 nodes) -│ ├── Execute Paginate Status ──────┤ Returns: list/status/paginate -│ └── Execute Batch Cancel Status ──┘ -│ -└── Confirmation Dialogs - └── Execute Confirmation ─────────── n8n-confirmation.json (16 nodes) - ├── Returns: show_stop/show_update/confirm_*/cancel/expired - └── Calls n8n-actions.json for confirmed stop actions -``` - -## Sub-workflow Input/Output Contracts - -### n8n-batch-ui.json - -**Input:** -- `chatId`, `messageId`, `queryId` -- `callbackData`, `action`, `batchPage` -- `selectedCsv`, `toggleName` - -**Output:** -- `action`: keyboard | confirmation | execute | cancel | limit_reached - -### n8n-status.json - -**Input:** -- `chatId`, `messageId`, `queryId` -- `action`, `containerId`, `containerName` -- `page`, `searchTerm` - -**Output:** -- `action`: list | status | paginate | status_direct - -### n8n-confirmation.json - -**Input:** -- `chatId`, `messageId`, `queryId` -- `action`, `containerId`, `containerName` -- `confirmAction`, `confirmationToken` -- `expired`, `responseMode` - -**Output:** -- `action`: show_stop | show_update | confirm_stop_result | confirm_update | cancel | expired - -## Testing Checklist - -After deployment, verify: - -- [ ] `/list` - Shows container list -- [ ] `/status ` - Shows container details -- [ ] `/stop ` - Shows confirmation dialog -- [ ] Confirm stop - Executes stop action -- [ ] Cancel stop - Returns to status view -- [ ] `/update ` - Shows confirmation dialog -- [ ] Confirm update - Executes update flow -- [ ] `/stop` (no args) - Shows batch selection UI -- [ ] Select multiple containers - Batch selection works -- [ ] Execute batch - All selected containers processed -- [ ] `/logs ` - Shows container logs - ---- - -## Code Node Classification - -Analysis of all 60 Code nodes in the main workflow (`n8n-workflow.json`), classifying each as orchestration infrastructure or domain logic. - -### Classification Categories - -| Category | Definition | Must Stay in Main? | -|----------|-----------|-------------------| -| `prepare-input` | Prepares input data for a sub-workflow Execute Workflow call or Execute Command | YES - glue between routing and sub-workflow | -| `route-result` | Processes sub-workflow or command return data for routing/display | YES - glue between sub-workflow and Telegram response | -| `parse-command` | Parses user text input into structured command data | YES - part of keyword routing infrastructure | -| `build-response` | Builds Telegram response text/keyboards from data | YES - tightly coupled to Telegram response nodes | -| `orchestration` | Batch loop control, state management, result aggregation | YES - main workflow orchestration logic | -| `domain-logic` | Pure domain computation that could theoretically live in sub-workflow | CANDIDATE - but assess extraction overhead | - -### Code Node Classification Table - -| Node Name | Category | Lines | Stays in Main? | Reason | -|-----------|----------|-------|---------------|--------| -| Build Action Command | build-response | 22 | YES | Builds curl command for Docker action execution | -| Build Batch Keyboard | build-response | 56 | YES | Builds batch confirmation keyboard for multiple matches | -| Build Batch Stop Confirmation | build-response | 36 | YES | Builds Telegram message for batch stop confirmation | -| Build Batch Stop Expired | build-response | 10 | YES | Builds expired confirmation message | -| Build Batch Summary | build-response | 62 | YES | Builds batch result summary message with success/failure counts | -| Build Callback Action | build-response | 24 | YES | Builds curl command for callback action execution | -| Build Cancel Return Submenu | domain-logic | 72 | CANDIDATE | Container name matching + submenu building (search + normalize + build) | -| Build Immediate Action Command | domain-logic | 43 | CANDIDATE | Container lookup by name + curl command building | -| Build Progress Message | build-response | 30 | YES | Builds progress message for batch loop iteration | -| Build Update All Confirmation | build-response | 35 | YES | Builds Telegram confirmation message for update-all | -| Check Available Updates | orchestration | 44 | YES | Filters containers by :latest tag, orchestrates update-all flow | -| Detect Batch Command | parse-command | 70 | YES | Detects batch commands (multiple container names) from text input | -| Find Container For Callback Update | prepare-input | 38 | YES | Resolves container name to ID for callback update sub-workflow | -| Format Immediate Result | route-result | 49 | YES | Formats action result into Telegram message with inline keyboard | -| Format Inline Logs Result | route-result | 36 | YES | Formats logs result with inline keyboard and refresh button | -| Get Update All Data | prepare-input | 18 | YES | Prepares data for update-all re-fetch | -| Handle Batch Action Result Sub | route-result | 33 | YES | Aggregates batch action sub-workflow result into loop state | -| Handle Batch Update Result | route-result | 33 | YES | Aggregates batch update sub-workflow result into loop state | -| Handle Cancel | route-result | 10 | YES | Prepares cancel callback query response data | -| Handle Expired | route-result | 10 | YES | Prepares expired callback query response data | -| Handle Inline Action Result | route-result | 36 | YES | Routes inline action result to Telegram edit with keyboard | -| Handle Text Action Result | route-result | 13 | YES | Routes text action result to Telegram send | -| Handle Update Multiple | route-result | 14 | YES | Builds error message for ambiguous update match | -| Initialize Batch State | orchestration | 43 | YES | Initializes batch execution loop state from multiple input sources | -| Parse Action Command | parse-command | 49 | YES | Parses /stop, /start, /restart text commands | -| Parse Action Result | route-result | 42 | YES | Parses curl HTTP status code into success/failure message | -| Parse Callback Data | parse-command | 441 | YES | Central callback data parser (all button clicks) | -| Parse Callback Result | route-result | 54 | YES | Parses callback action curl result with keyboard building | -| Parse Logs Command | parse-command | 45 | YES | Parses /logs text command with optional line count | -| Parse Update Command | parse-command | 25 | YES | Parses /update text command | -| Prepare Action Match Input | prepare-input | 17 | YES | Prepares input for matching sub-workflow (action commands) | -| Prepare Batch Action Input | prepare-input | 19 | YES | Prepares input for actions sub-workflow (batch loop) | -| Prepare Batch Cancel Return | prepare-input | 10 | YES | Prepares data for return to container list from batch cancel | -| Prepare Batch Cancel Return Input | prepare-input | 14 | YES | Prepares input for status sub-workflow from batch cancel | -| Prepare Batch Exec | orchestration | 26 | YES | Prepares batch exec data, normalizes container name formats | -| Prepare Batch Execution | prepare-input | 13 | YES | Transforms matching sub-workflow output to batch execution format | -| Prepare Batch Loop | orchestration | 42 | YES | Stores progress message ID, prepares first batch iteration | -| Prepare Batch Match Input | prepare-input | 21 | YES | Prepares input for matching sub-workflow (batch commands) | -| Prepare Batch Stop Exec | prepare-input | 16 | YES | Prepares batch stop data from callback confirmation | -| Prepare Batch UI Input | prepare-input | 27 | YES | Prepares input for batch UI sub-workflow | -| Prepare Batch Update Input | prepare-input | 17 | YES | Prepares input for update sub-workflow (batch loop) | -| Prepare Callback Update Input | prepare-input | 15 | YES | Prepares input for update sub-workflow (inline mode) | -| Prepare Cancel From Confirm | prepare-input | 10 | YES | Prepares cancel return data from confirmation result | -| Prepare Cancel Return | prepare-input | 9 | YES | Prepares cancel data from callback for container submenu | -| Prepare Confirm Input | prepare-input | 26 | YES | Prepares input for confirmation sub-workflow | -| Prepare Immediate Action | prepare-input | 18 | YES | Prepares inline keyboard action data for Docker execution | -| Prepare Inline Action Input | prepare-input | 41 | YES | Prepares input for actions sub-workflow (inline keyboard path) | -| Prepare Inline Logs Input | prepare-input | 12 | YES | Prepares input for logs sub-workflow (inline action) | -| Prepare Next Iteration | orchestration | 18 | YES | Advances batch loop counter, checks completion | -| Prepare Paginate Input | prepare-input | 14 | YES | Prepares input for status sub-workflow (pagination callback) | -| Prepare Select Status Input | prepare-input | 14 | YES | Prepares input for status sub-workflow (container select) | -| Prepare Show Stop Input | prepare-input | 13 | YES | Prepares input for confirmation sub-workflow (show stop) | -| Prepare Show Update Input | prepare-input | 13 | YES | Prepares input for confirmation sub-workflow (show update) | -| Prepare Status Input | prepare-input | 24 | YES | Prepares input for status sub-workflow (/status command) | -| Prepare Text Action Input | prepare-input | 18 | YES | Prepares input for actions sub-workflow (text command) | -| Prepare Text Logs Input | prepare-input | 23 | YES | Prepares input for logs sub-workflow (text command) | -| Prepare Text Update Input | prepare-input | 15 | YES | Prepares input for update sub-workflow (text mode) | -| Prepare Update All Batch | orchestration | 41 | YES | Prepares batch data for update-all from container list | -| Prepare Update Match Input | prepare-input | 16 | YES | Prepares input for matching sub-workflow (update commands) | -| Strip Status Keyboard | route-result | 9 | YES | Strips inline keyboard for text-mode status display | - -### Classification Summary - -``` -Total Code nodes: 60 - - prepare-input: 27 (must stay - sub-workflow integration glue) - route-result: 12 (must stay - sub-workflow result routing) - parse-command: 5 (must stay - keyword routing infrastructure) - build-response: 8 (must stay - Telegram response building) - orchestration: 6 (must stay - batch loop + state management) - domain-logic: 2 (extraction candidates) - -Extractable domain logic: 2 nodes (72 + 43 = 115 lines) - - Build Cancel Return Submenu (72 lines) — container name matching + submenu building - - Build Immediate Action Command (43 lines) — container lookup + curl command building - -Extraction overhead per domain: ~3 nodes (Prepare Input + Execute Workflow + Route Result) -Net reduction potential: 2 extracted - 3 overhead = -1 node (net increase) -``` - -### Extraction Viability Assessment - -The 2 domain-logic candidates (`Build Cancel Return Submenu` and `Build Immediate Action Command`) both perform container name matching followed by response building. However: - -1. **Both are already partially handled by n8n-matching.json** -- the matching sub-workflow handles the primary matching paths. These two nodes handle edge cases (cancel return to submenu, and immediate inline actions like start/restart) where the matching has already been resolved by callback data. - -2. **Extraction would be net-negative** -- extracting 2 nodes but adding 3 integration nodes (Prepare Input, Execute Workflow, Route Result) would increase the node count by 1. - -3. **Complexity is low** -- both nodes are under 75 lines and perform straightforward container name normalization + lookup against Docker API output already fetched. - -**Conclusion:** No further Code node extraction is viable. All 58 non-candidate nodes are demonstrably orchestration infrastructure (input preparation, result routing, command parsing, response building, or loop control). The 2 domain-logic candidates would yield a net-negative extraction. - ---- - -## Sub-workflow Input/Output Contracts (Detailed) - -Formal contracts for all 7 sub-workflows with field-level documentation. Each contract is verified against the Prepare Input Code nodes that feed the Execute Workflow calls. - -### n8n-update.json (Container Update) - -**Workflow ID:** `7AvTzLtKXM2hZTio92_mC` -**Called by:** 3 Execute Workflow nodes - -#### Input Contract - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| containerId | string | yes* | Docker container ID (empty string if resolving by name) | -| containerName | string | yes | Container display name | -| chatId | number | yes | Telegram chat ID | -| messageId | number | yes | Telegram message ID (0 for text mode) | -| responseMode | string | yes | `"text"` or `"inline"` | - -*containerId can be empty string -- the sub-workflow resolves by name via `Resolve Container ID` node. - -#### Output Contract - -| Exit Node | Fields | When | -|-----------|--------|------| -| Return Success | `success: true, updated: true, message, oldDigest, newDigest` | Image pulled, container recreated | -| Return No Update | `success: true, updated: false, message` | Image already up to date | -| Return Error | `success: false, updated: false, message` | Pull or creation failed | - -#### Execute Workflow Node Mapping - -| Execute Node | Source Node | Fields Produced | -|-------------|-------------|-----------------| -| Execute Text Update | Prepare Text Update Input | containerId, containerName, chatId, messageId (0), responseMode ("text") | -| Execute Callback Update | Find Container For Callback Update | containerId, containerName, chatId, messageId, responseMode ("inline") | -| Execute Batch Update | Prepare Batch Update Input | containerId, containerName, chatId, messageId, responseMode ("batch") | - -**Verification:** All 3 source nodes produce all 5 required fields. Confirmed. - ---- - -### n8n-actions.json (Container Actions) - -**Workflow ID:** `fYSZS5PkH0VSEaT5` -**Called by:** 3 Execute Workflow nodes - -#### Input Contract - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| containerId | string | yes | Docker container ID | -| containerName | string | yes | Container display name | -| action | string | yes | `"start"`, `"stop"`, or `"restart"` | -| chatId | number | yes | Telegram chat ID | -| messageId | number | yes | Telegram message ID (0 for text mode) | -| responseMode | string | yes | `"text"`, `"inline"`, or `"batch"` | - -#### Output Contract - -All 3 exit nodes return the same structure (per action type): - -| Exit Node | Fields | -|-----------|--------| -| Format Start Result | `success, message, action, containerName, containerId, chatId, messageId, responseMode` | -| Format Stop Result | `success, message, action, containerName, containerId, chatId, messageId, responseMode` | -| Format Restart Result | `success, message, action, containerName, containerId, chatId, messageId, responseMode` | - -#### Execute Workflow Node Mapping - -| Execute Node | Source Node | Fields Produced | -|-------------|-------------|-----------------| -| Execute Container Action | Prepare Text Action Input | containerId, containerName, action, chatId, messageId, responseMode ("text") | -| Execute Inline Action | Prepare Inline Action Input | containerId, containerName, action, chatId, messageId, responseMode ("inline") | -| Execute Batch Action Sub-workflow | Prepare Batch Action Input | containerId, containerName, action, chatId, messageId, responseMode ("batch") | - -**Verification:** All 3 source nodes produce all 6 required fields. Confirmed. - ---- - -### n8n-logs.json (Container Logs) - -**Workflow ID:** `oE7aO2GhbksXDEIw` -**Called by:** 2 Execute Workflow nodes - -#### Input Contract - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| containerId | string | no | Docker container ID (optional, resolved by name if absent) | -| containerName | string | yes* | Container name for lookup | -| lineCount | number | no | Lines to retrieve (default: 50, max: 1000) | -| chatId | number | yes | Telegram chat ID | -| messageId | number | no | Telegram message ID (default: 0) | -| responseMode | string | no | `"text"` or `"inline"` (default: "text") | - -*Either containerId or containerName required. Sub-workflow's `Parse Input` node validates this. - -#### Output Contract - -| Exit Node | Fields | When | -|-----------|--------|------| -| Format Logs | `success: true, message, containerName, lineCount` | Logs retrieved and formatted | - -Note: Error cases (container not found, Docker error) throw exceptions which n8n handles as workflow errors. - -#### Execute Workflow Node Mapping - -| Execute Node | Source Node | Fields Produced | -|-------------|-------------|-----------------| -| Execute Text Logs | Prepare Text Logs Input | containerName, lineCount, chatId, messageId, responseMode ("text") | -| Execute Inline Logs | Prepare Inline Logs Input | containerName, lineCount (30), chatId, messageId, responseMode ("inline") | - -**Verification:** Both source nodes produce required fields. `Prepare Text Logs Input` has error-path return (error, chatId, text) but this is handled before reaching Execute Workflow node. Confirmed. - ---- - -### n8n-batch-ui.json (Batch Selection UI) - -**Workflow ID:** `ZJhnGzJT26UUmW45` -**Called by:** 1 Execute Workflow node - -#### Input Contract - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| chatId | number | yes | Telegram chat ID | -| messageId | number | yes | Telegram message ID | -| callbackData | string | yes | Raw callback data string | -| queryId | string | yes | Telegram callback query ID | -| action | string | yes | `"mode"`, `"toggle"`, `"nav"`, `"exec"`, `"clear"`, `"cancel"` | -| batchPage | number | no | Current page number (default: 0) | -| selectedCsv | string | no | Comma-separated selected container names | -| toggleName | string | no | Container name being toggled | -| batchAction | string | no | Action for batch execution (stop/restart/update) | - -#### Output Contract - -| Exit Node | action Value | Key Fields | -|-----------|-------------|------------| -| Build Batch Keyboard | `"keyboard"` | queryId, chatId, messageId, text, keyboard, selectedCsv, selectedCount, answerText | -| Rebuild Keyboard After Toggle | `"keyboard"` | queryId, chatId, messageId, text, keyboard, selectedCsv, answerText | -| Rebuild Keyboard For Nav | `"keyboard"` | queryId, chatId, messageId, text, keyboard, selectedCsv, answerText | -| Rebuild Keyboard After Clear | `"keyboard"` | queryId, chatId, messageId, text, keyboard, selectedCsv (""), answerText | -| Handle Exec | `"execute"` | queryId, chatId, messageId, batchAction, containerNames, selectedCsv, count, fromKeyboard, answerText | -| Handle Cancel | `"cancel"` | queryId, chatId, messageId, page, answerText | - -All exit nodes include `success: true`. - -#### Execute Workflow Node Mapping - -| Execute Node | Source Node | Fields Produced | -|-------------|-------------|-----------------| -| Execute Batch UI | Prepare Batch UI Input | chatId, messageId, queryId, callbackData, action, batchAction, batchPage, selectedCsv, toggleName | - -**Verification:** Source node produces all 9 input fields. Confirmed. - ---- - -### n8n-status.json (Container Status/List) - -**Workflow ID:** `lqpg2CqesnKE2RJQ` -**Called by:** 4 Execute Workflow nodes - -#### Input Contract - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| chatId | number | yes | Telegram chat ID | -| messageId | number | yes | Telegram message ID | -| action | string | yes | `"list"`, `"status"`, `"paginate"` | -| containerId | string | no | Docker container ID (for status lookup) | -| containerName | string | no | Container name (for status lookup) | -| page | number | no | Page number for pagination (default: 0) | -| queryId | string | no | Telegram callback query ID | -| searchTerm | string | no | Container name search term | - -#### Output Contract - -| Exit Node | action Value | Key Fields | -|-----------|-------------|------------| -| Build Container List | `"list"` | chatId, messageId, text, reply_markup, totalContainers, currentPage, totalPages | -| Build Container Submenu | `"status"` | chatId, messageId, text, reply_markup, container (id, name, state, status, image) | -| Build Paginated List | `"paginate"` | chatId, messageId, text, reply_markup, totalContainers, currentPage, totalPages | - -All exit nodes include `success: true`. - -Note: The main workflow also routes `status_direct` results (when messageId > 0 goes to edit, messageId == 0 goes to text send via `Strip Status Keyboard`). - -#### Execute Workflow Node Mapping - -| Execute Node | Source Node | Fields Produced | -|-------------|-------------|-----------------| -| Execute Container Status | Prepare Status Input | chatId, messageId, action ("list"/"status"), containerId, containerName, page, queryId, searchTerm | -| Execute Select Status | Prepare Select Status Input | chatId, messageId, action ("status"), containerId, containerName, page, queryId, searchTerm | -| Execute Paginate Status | Prepare Paginate Input | chatId, messageId, action ("paginate"), containerId, containerName, page, queryId, searchTerm | -| Execute Batch Cancel Status | Prepare Batch Cancel Return Input | chatId, messageId, action ("paginate"), containerId, containerName, page, queryId, searchTerm | - -**Verification:** All 4 source nodes produce all 8 fields. Confirmed. - ---- - -### n8n-confirmation.json (Confirmation Dialogs) - -**Workflow ID:** `fZ1hu8eiovkCk08G` -**Called by:** 1 Execute Workflow node (from 3 source nodes) - -#### Input Contract - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| chatId | number | yes | Telegram chat ID | -| messageId | number | yes | Telegram message ID | -| action | string | yes | `"show_stop"`, `"show_update"`, `"confirm"`, `"cancel"`, `"expired"` | -| containerId | string | no | Docker container ID | -| containerName | string | yes | Container display name | -| confirmAction | string | no | `"stop"` or `"update"` (for confirm action) | -| confirmationToken | string | no | Timestamp token for expiry check | -| expired | boolean | no | Whether confirmation has expired | -| responseMode | string | no | `"inline"` (default) | - -Note: This sub-workflow internally calls n8n-actions.json (ID: `fYSZS5PkH0VSEaT5`) for confirmed stop actions. - -#### Output Contract - -| Exit Node | action Value | Key Fields | -|-----------|-------------|------------| -| Build Stop Confirmation | `"show_stop"` | chatId, messageId, text, reply_markup | -| Build Update Confirmation | `"show_update"` | chatId, messageId, text, reply_markup | -| Format Stop Result | `"confirm_stop_result"` | success, chatId, messageId, text, reply_markup | -| Return Update Action | `"confirm_update"` | containerId, containerName, chatId, messageId, responseMode | -| Handle Cancel | `"cancel"` | containerName, chatId, messageId | -| Handle Expired | `"expired"` | chatId, messageId, text, reply_markup | -| Stop Expired Response | `"expired"` | chatId, messageId, text, reply_markup | -| Update Expired Response | `"expired"` | chatId, messageId, text, reply_markup | - -#### Execute Workflow Node Mapping - -| Execute Node | Source Node | Fields Produced | -|-------------|-------------|-----------------| -| Execute Confirmation | Prepare Confirm Input | chatId, messageId, action ("confirm"/"cancel"/"expired"), containerId, containerName, confirmAction, confirmationToken, expired, responseMode | -| Execute Confirmation | Prepare Show Stop Input | chatId, messageId, action ("show_stop"), containerId, containerName, responseMode | -| Execute Confirmation | Prepare Show Update Input | chatId, messageId, action ("show_update"), containerId, containerName, responseMode | - -**Verification:** All 3 source nodes produce required fields for their respective action paths. Confirmed. - ---- - -### n8n-matching.json (Container Matching/Disambiguation) - -**Workflow ID:** `kL4BoI8ITSP9Oxek` -**Called by:** 3 Execute Workflow nodes - -#### Input Contract - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| action | string | yes | `"match_action"`, `"match_update"`, or `"match_batch"` | -| containerList | string | yes | Raw Docker API JSON output (container list) | -| searchTerm | string | yes | Container name query to match | -| selectedContainers | string | no | Comma-separated container names for batch matching | -| chatId | number | yes | Telegram chat ID | -| messageId | number | yes | Telegram message ID | - -#### Output Contract - -**Action match path** (action: "match_action"): - -| Exit Node | action Value | Key Fields | -|-----------|-------------|------------| -| Format Single Match Return | `"matched"` | containerId, containerName, matches, matchCount, actionType, containerQuery, chatId, allContainers | -| Format Multiple Match Return | `"multiple"` | matches, matchCount, actionType, containerQuery, chatId, allContainers | -| Format No Match Return | `"no_match"` | query, chatId | -| Format Error Return | `"error"` | errorMessage, chatId | -| Build Suggestion Keyboard | `"suggestion"` | chat_id, text, parse_mode, reply_markup | - -**Update match path** (action: "match_update"): - -| Exit Node | action Value | Key Fields | -|-----------|-------------|------------| -| Format Update Single Match Return | `"matched_update"` | containerId, containerName, matches, matchCount, containerQuery, chatId | -| Format Update Multiple Match Return | `"multiple_update"` | matches, matchCount, containerQuery, chatId | -| Format Update No Match Return | `"no_match_update"` | containerQuery, chatId | -| Format Update Error Return | `"error"` | errorMessage, chatId | - -**Batch match path** (action: "match_batch"): - -| Exit Node | action Value | Key Fields | -|-----------|-------------|------------| -| Format Batch Matched Return | `"batch_matched"` | matchedContainers, chatId, messageId, originalContainerNames | -| Build Disambiguation Message | `"disambiguation"` | chat_id, text, parse_mode, reply_markup | -| Build Not Found Message | `"not_found"` | chat_id, text, parse_mode, hasMatched (+ optional matchedContainers) | - -#### Execute Workflow Node Mapping - -| Execute Node | Source Node | Fields Produced | -|-------------|-------------|-----------------| -| Execute Action Match | Prepare Action Match Input | action ("match_action"), containerList, searchTerm, selectedContainers (""), chatId, messageId | -| Execute Update Match | Prepare Update Match Input | action ("match_update"), containerList, searchTerm, selectedContainers (""), chatId, messageId | -| Execute Batch Match | Prepare Batch Match Input | action ("match_batch"), containerList, searchTerm (""), selectedContainers, chatId, messageId | - -**Verification:** All 3 source nodes produce all 6 input fields. Confirmed. - ---- - -### Execute Workflow Node Summary - -**Total Execute Workflow nodes:** 17 - -| Target Sub-workflow | Count | Execute Nodes | -|--------------------|-------|---------------| -| n8n-update.json | 3 | Execute Text Update, Execute Callback Update, Execute Batch Update | -| n8n-actions.json | 3 | Execute Container Action, Execute Inline Action, Execute Batch Action Sub-workflow | -| n8n-logs.json | 2 | Execute Text Logs, Execute Inline Logs | -| n8n-batch-ui.json | 1 | Execute Batch UI | -| n8n-status.json | 4 | Execute Container Status, Execute Select Status, Execute Paginate Status, Execute Batch Cancel Status | -| n8n-confirmation.json | 1 | Execute Confirmation (3 source nodes) | -| n8n-matching.json | 3 | Execute Action Match, Execute Update Match, Execute Batch Match | - -**Contract verification result:** All 17 Execute Workflow nodes receive correctly structured input from their source Prepare Input nodes. No mismatches found. - ---- - -## Node Count Analysis - -### Current State - -- **Main workflow:** 168 nodes (after all Phase 10.1 extractions) -- **7 sub-workflows:** 120 nodes total - - n8n-update.json: 34 nodes - - n8n-matching.json: 23 nodes - - n8n-batch-ui.json: 16 nodes - - n8n-confirmation.json: 16 nodes - - n8n-actions.json: 11 nodes - - n8n-status.json: 11 nodes - - n8n-logs.json: 9 nodes - -### Main Workflow Node Breakdown (168 total) - -| Category | Count | Description | -|----------|-------|-------------| -| Code nodes | 60 | Orchestration logic (see Classification above) | -| HTTP Request nodes | 40 | Docker API + Telegram API calls | -| Telegram nodes | 23 | User-facing response nodes (locked to main per design) | -| Execute Workflow nodes | 17 | Sub-workflow dispatch | -| Switch nodes | 13 | Routing logic (Keyword Router, Route Callback, etc.) | -| If nodes | 8 | Conditional routing (auth checks, batch completion, etc.) | -| Execute Command nodes | 6 | Docker CLI calls (list, execute) | -| Telegram Trigger | 1 | Entry point | - -### Why 115-125 Target Is Not Achievable - -Based on the Code node classification above, here is the structural breakdown of what must remain in the main workflow: - -``` -Locked infrastructure (cannot be extracted): - 1 Telegram Trigger - 8 If nodes (auth, batch complete, expired checks, status routing) - 13 Switch nodes (keyword router, callback router, result routers) - ── Total: 22 nodes - -Telegram response nodes (locked per design decision): - 23 Telegram nodes (user-facing messages — must stay in main) - ── Total: 23 nodes - -HTTP Request nodes (Telegram API + Docker API): - 40 HTTP Request nodes (edit message, answer callback, send message, Docker queries) - ── Total: 40 nodes - -Execute Workflow dispatch: - 17 Execute Workflow nodes (sub-workflow calls) - ── Total: 17 nodes - -Execute Command (Docker CLI): - 6 Execute Command nodes (docker list/exec operations) - ── Total: 6 nodes - -Code nodes — orchestration infrastructure (must stay): - 27 prepare-input (sub-workflow glue) - 12 route-result (sub-workflow result routing) - 5 parse-command (keyword routing) - 8 build-response (Telegram message building) - 6 orchestration (batch loop control) - ── Total: 58 nodes - -Code nodes — domain logic candidates: - 2 domain-logic (Build Cancel Return Submenu, Build Immediate Action Command) - ── Total: 2 nodes -``` - -### Revised Realistic Baseline - -**Minimum viable main workflow:** 166 nodes (all categories that must stay) -- 22 infrastructure + 23 Telegram + 40 HTTP + 17 Execute Workflow + 6 Execute Command + 58 Code = 166 - -**Current:** 168 nodes -**Gap:** 2 nodes of domain logic where extraction overhead (~3 nodes per extraction: Prepare Input + Execute Workflow + Route Result) makes extraction net-negative - -### Extraction History - -| Phase | Extraction | Nodes Removed | Nodes Added | Net Change | -|-------|-----------|--------------|-------------|------------| -| 10-02 | Container Update | -13 (inline logic) | +6 (integration) | -7 | -| 10-03 | Container Actions | -6 (inline logic) | +5 (integration) | -1 | -| 10-05 | Batch Loops + Logs | -5 (inline logic) | +4 (integration) | -1 | -| 10.1-02 | Batch UI | -16 (inline logic) | +3 (integration) | -13 | -| 10.1-03 | Container Status | -11 (inline logic) | +10 (integration) | -1 | -| 10.1-04 | Confirmation Dialogs | -16 (inline logic) | +6 (integration) | -10 | -| 10.1-06 | Matching/Disambiguation | -12 (inline logic) | +12 (integration + fixes) | 0 | -| **Total** | **7 sub-workflows** | **-79** | **+46** | **-33** | - -Starting from 192 nodes (post-Phase 10), reduced to 168 nodes in Phase 10.1 (-24 nodes, -12.5%). -Including Phase 10 reductions: 209 -> 168 nodes total (-41 nodes, -19.6%). - -### Diminishing Returns Evidence - -Each extraction adds integration overhead (Prepare Input, Execute Workflow, Route Result nodes). The extraction benefit decreases as remaining logic is more tightly coupled to the orchestration: - -- **Batch UI extraction:** 16 nodes extracted, 3 overhead = 81% efficient -- **Confirmation extraction:** 16 nodes extracted, 6 overhead = 63% efficient -- **Status extraction:** 11 nodes extracted, 10 overhead = 9% efficient -- **Matching extraction:** 12 nodes extracted, 12 overhead = 0% efficient - -Further extraction of the 2 remaining domain-logic nodes (115 total lines) would require 3 overhead nodes, yielding a **-50% efficiency** (net increase of 1 node). - -### Conclusion - -The 168-node main workflow is **2 nodes above the structural minimum** of 166. The 115-125 target was based on incomplete analysis of extraction overhead costs. With full evidence: - -1. **58 Code nodes** are verified orchestration infrastructure (cannot be extracted) -2. **2 Code nodes** are domain logic but extraction is net-negative -3. **108 non-Code nodes** are locked infrastructure (Telegram, HTTP, Switch, If, Execute, Trigger) -4. **All 17 Execute Workflow nodes** pass correct input data to sub-workflows -5. **All 7 sub-workflow contracts** are formally documented and verified - -The modularization objective is achieved: domain complexity is in sub-workflows, the main workflow is a routing/orchestration shell. diff --git a/README.md b/README.md index 42b1b63..50c8e41 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ Docker Engine The main workflow handles authentication and routing, while domain-specific logic lives in sub-workflows. This modular design simplifies debugging and isolates container operations. -For detailed sub-workflow contracts and technical architecture, see [DEPLOY-SUBWORKFLOWS.md](DEPLOY-SUBWORKFLOWS.md). +For detailed sub-workflow contracts and technical architecture, see [ARCHITECTURE.md](ARCHITECTURE.md). ## Configuration @@ -244,7 +244,7 @@ Send `/start` or any unrecognized text to display the persistent keyboard with q **Fix:** Click "Apply Update" in Unraid's Docker tab. It completes instantly because the image is already pulled — Unraid just recreates the container to sync its tracking. -For more details, see Known Limitations in [DEPLOY-SUBWORKFLOWS.md](DEPLOY-SUBWORKFLOWS.md). +For more details, see Known Limitations in [ARCHITECTURE.md](ARCHITECTURE.md). ### Batch operations not working