docs(10): create UAT gap closure plan 10-07
Phase 10 UAT revealed 5 gaps: - Text update race condition (parallel message send) - Batch update/action data chain broken ($json vs Build Progress Message) - Logs fuzzy matching missing - Logs refresh "message not modified" error 10-07-PLAN.md addresses all gaps in 5 tasks. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ Modularize the workflow for maintainability, add "update all" functionality, fix
|
|||||||
|
|
||||||
**Requirements:** MOD-01, MOD-02, DEBT-03
|
**Requirements:** MOD-01, MOD-02, DEBT-03
|
||||||
|
|
||||||
**Plans:** 6 plans
|
**Plans:** 7 plans
|
||||||
|
|
||||||
Plans:
|
Plans:
|
||||||
- [x] 10-01-PLAN.md — Orphan node cleanup (removed 2 orphan nodes)
|
- [x] 10-01-PLAN.md — Orphan node cleanup (removed 2 orphan nodes)
|
||||||
@@ -29,6 +29,7 @@ Plans:
|
|||||||
- [x] 10-04-PLAN.md — Integration verification and user checkpoint
|
- [x] 10-04-PLAN.md — Integration verification and user checkpoint
|
||||||
- [x] 10-05-PLAN.md — Complete modularization (batch operations, logs sub-workflow)
|
- [x] 10-05-PLAN.md — Complete modularization (batch operations, logs sub-workflow)
|
||||||
- [x] 10-06-PLAN.md — Remediation: fix routing gaps, wire logs, cleanup Python scripts
|
- [x] 10-06-PLAN.md — Remediation: fix routing gaps, wire logs, cleanup Python scripts
|
||||||
|
- [ ] 10-07-PLAN.md — UAT gap closure: race conditions, data chains, fuzzy matching, error handling
|
||||||
|
|
||||||
**Success Criteria:**
|
**Success Criteria:**
|
||||||
1. ✓ Workflow split into logical sub-workflows (update, actions, logs)
|
1. ✓ Workflow split into logical sub-workflows (update, actions, logs)
|
||||||
@@ -154,7 +155,7 @@ Plans:
|
|||||||
| 7 | Socket Security | v1.1 | Complete |
|
| 7 | Socket Security | v1.1 | Complete |
|
||||||
| 8 | Inline Keyboard Infrastructure | v1.1 | Complete |
|
| 8 | Inline Keyboard Infrastructure | v1.1 | Complete |
|
||||||
| 9 | Batch Operations | v1.1 | Complete |
|
| 9 | Batch Operations | v1.1 | Complete |
|
||||||
| 10 | Workflow Modularization | v1.2 | Complete |
|
| 10 | Workflow Modularization | v1.2 | UAT Gaps |
|
||||||
| 10.1 | Aggressive Workflow Modularization | v1.2 | Pending (INSERTED) |
|
| 10.1 | Aggressive Workflow Modularization | v1.2 | Pending (INSERTED) |
|
||||||
| 10.2 | Better Logging & Log Management | v1.2 | Pending (INSERTED) |
|
| 10.2 | Better Logging & Log Management | v1.2 | Pending (INSERTED) |
|
||||||
| 11 | Update All & Callback Limits | v1.2 | Pending |
|
| 11 | Update All & Callback Limits | v1.2 | Pending |
|
||||||
@@ -164,4 +165,4 @@ Plans:
|
|||||||
**v1.2 Coverage:** 12+ requirements mapped across 7 phases
|
**v1.2 Coverage:** 12+ requirements mapped across 7 phases
|
||||||
|
|
||||||
---
|
---
|
||||||
*Updated: 2026-02-04 after Phase 10 planning*
|
*Updated: 2026-02-04 after Phase 10 UAT gap planning*
|
||||||
|
|||||||
@@ -0,0 +1,345 @@
|
|||||||
|
---
|
||||||
|
phase: 10-workflow-modularization
|
||||||
|
plan: 07
|
||||||
|
type: remediation
|
||||||
|
wave: 6
|
||||||
|
depends_on: [10-06]
|
||||||
|
files_modified: [n8n-workflow.json]
|
||||||
|
autonomous: true
|
||||||
|
gap_closure: true
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Single text update sends only one result message (no race condition)"
|
||||||
|
- "Batch update executes via Container Update sub-workflow"
|
||||||
|
- "Batch actions execute via Container Actions sub-workflow"
|
||||||
|
- "Container logs text command works with fuzzy matching"
|
||||||
|
- "Refresh Logs button handles 'message not modified' gracefully"
|
||||||
|
artifacts:
|
||||||
|
- path: "n8n-workflow.json"
|
||||||
|
provides: "Fixed main workflow with all UAT gaps closed"
|
||||||
|
contains: "$('Build Progress Message').item.json"
|
||||||
|
key_links:
|
||||||
|
- from: "Prepare Text Update Input"
|
||||||
|
to: "Execute Text Update"
|
||||||
|
via: "Sequential connection (not parallel with Send Text Update Started)"
|
||||||
|
- from: "Prepare Batch Update Input"
|
||||||
|
to: "Execute Batch Update"
|
||||||
|
via: "Uses $('Build Progress Message').item.json.container"
|
||||||
|
- from: "Prepare Batch Action Input"
|
||||||
|
to: "Execute Batch Action Sub-workflow"
|
||||||
|
via: "Uses $('Build Progress Message').item.json"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Close 5 UAT gaps discovered during user testing of Phase 10 modularization.
|
||||||
|
|
||||||
|
Purpose: UAT revealed race conditions, broken data chains, missing fuzzy matching, and unhandled Telegram API errors that prevent the modularized workflow from functioning correctly.
|
||||||
|
|
||||||
|
Output: Fully functional modularized workflow with all user-reported issues fixed.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/home/luc/.claude/get-shit-done/workflows/execute-plan.md
|
||||||
|
@/home/luc/.claude/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/phases/10-workflow-modularization/10-06-SUMMARY.md
|
||||||
|
@n8n-workflow.json
|
||||||
|
@n8n-container-logs.json
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Fix text update race condition (remove parallel message)</name>
|
||||||
|
<files>n8n-workflow.json</files>
|
||||||
|
<action>
|
||||||
|
**Problem:** User reported two messages sent for single text update - "already up to date" AND "Updating..." in race condition. Root cause: `Prepare Text Update Input` connects in PARALLEL to both `Send Text Update Started` AND `Execute Text Update`. The sub-workflow also sends a result message.
|
||||||
|
|
||||||
|
**Current connection (lines 6616-6629):**
|
||||||
|
```json
|
||||||
|
"Prepare Text Update Input": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{ "node": "Send Text Update Started", ... },
|
||||||
|
{ "node": "Execute Text Update", ... }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Both nodes are in the same array = parallel execution.
|
||||||
|
|
||||||
|
**Fix:** Remove `Send Text Update Started` from the connection array. The sub-workflow handles all messaging, so the "Updating..." message is redundant.
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Find the "Prepare Text Update Input" entry in the `connections` object
|
||||||
|
2. Remove the object `{ "node": "Send Text Update Started", "type": "main", "index": 0 }` from the array
|
||||||
|
3. Keep only `{ "node": "Execute Text Update", "type": "main", "index": 0 }`
|
||||||
|
|
||||||
|
**Result connection:**
|
||||||
|
```json
|
||||||
|
"Prepare Text Update Input": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{ "node": "Execute Text Update", "type": "main", "index": 0 }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- Grep for "Prepare Text Update Input" in connections shows only Execute Text Update
|
||||||
|
- No reference to Send Text Update Started in parallel
|
||||||
|
</verify>
|
||||||
|
<done>Text update sends only one message (from sub-workflow)</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Fix batch update data chain (use Build Progress Message reference)</name>
|
||||||
|
<files>n8n-workflow.json</files>
|
||||||
|
<action>
|
||||||
|
**Problem:** User reported "Cannot read properties of undefined (reading 'id')" in Prepare Batch Update Input. Root cause: The node uses `$json.container` but receives Telegram API response from `Edit Progress Message`, not the batch data.
|
||||||
|
|
||||||
|
**Current code (node id: caeae5d6-f9ec-4aa3-83d3-198b6b55be65, lines 4688-4699):**
|
||||||
|
```javascript
|
||||||
|
const data = $json;
|
||||||
|
const container = data.container;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Data flow:**
|
||||||
|
- `Build Progress Message` outputs: `{ container: {...}, chatId, progressMessageId, action, ... }`
|
||||||
|
- `Edit Progress Message` sends Telegram API call, outputs Telegram response (no container data)
|
||||||
|
- `Prepare Batch Update Input` receives Telegram response (wrong data!)
|
||||||
|
|
||||||
|
**Fix:** Change to reference `Build Progress Message` directly instead of relying on `$json`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Prepare input for Container Update sub-workflow
|
||||||
|
const data = $('Build Progress Message').item.json;
|
||||||
|
const container = data.container;
|
||||||
|
|
||||||
|
// Extract container info
|
||||||
|
const containerId = container.id || container.Id || '';
|
||||||
|
const containerName = container.name || container.Name || '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
json: {
|
||||||
|
containerId: containerId,
|
||||||
|
containerName: containerName,
|
||||||
|
chatId: data.chatId,
|
||||||
|
messageId: data.progressMessageId || 0,
|
||||||
|
responseMode: "inline"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Find node with id "caeae5d6-f9ec-4aa3-83d3-198b6b55be65" (Prepare Batch Update Input)
|
||||||
|
2. Update the `jsCode` parameter to use `$('Build Progress Message').item.json` instead of `$json`
|
||||||
|
3. Keep all other logic the same
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- Grep for "Prepare Batch Update Input" shows `$('Build Progress Message').item.json`
|
||||||
|
- No `const data = $json` in that node
|
||||||
|
</verify>
|
||||||
|
<done>Batch update correctly reads container data from Build Progress Message</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Fix batch action data chain (use Build Progress Message reference)</name>
|
||||||
|
<files>n8n-workflow.json</files>
|
||||||
|
<action>
|
||||||
|
**Problem:** Same as Task 2 - "Cannot read properties of undefined (reading 'id')" in Prepare Batch Action Input. Uses `$json.container` but receives Telegram response.
|
||||||
|
|
||||||
|
**Current code (node id: 958f19ef-249b-42ca-8a29-ecb91548f1dd, lines 4732-4743):**
|
||||||
|
```javascript
|
||||||
|
const data = $json;
|
||||||
|
const container = data.container;
|
||||||
|
const action = data.action;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fix:** Change to reference `Build Progress Message` directly:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Prepare input for Container Actions sub-workflow
|
||||||
|
const data = $('Build Progress Message').item.json;
|
||||||
|
const container = data.container;
|
||||||
|
const action = data.action;
|
||||||
|
|
||||||
|
// Extract container info
|
||||||
|
const containerId = container.id || container.Id || '';
|
||||||
|
const containerName = container.name || container.Name || '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
json: {
|
||||||
|
containerId: containerId,
|
||||||
|
containerName: containerName,
|
||||||
|
action: action,
|
||||||
|
chatId: data.chatId,
|
||||||
|
messageId: data.progressMessageId || 0,
|
||||||
|
responseMode: "inline"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Find node with id "958f19ef-249b-42ca-8a29-ecb91548f1dd" (Prepare Batch Action Input)
|
||||||
|
2. Update the `jsCode` parameter to use `$('Build Progress Message').item.json` instead of `$json`
|
||||||
|
3. Keep all other logic the same
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- Grep for "Prepare Batch Action Input" shows `$('Build Progress Message').item.json`
|
||||||
|
- No `const data = $json` in that node
|
||||||
|
</verify>
|
||||||
|
<done>Batch action correctly reads container data from Build Progress Message</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 4: Add fuzzy matching to logs text command and fix Send Logs Response chatId</name>
|
||||||
|
<files>n8n-workflow.json</files>
|
||||||
|
<action>
|
||||||
|
**Problem:** Two issues with logs text command:
|
||||||
|
1. Fuzzy matching not working - exact name required (unlike update/start/stop)
|
||||||
|
2. Send Logs Response uses `$json.chatId` but sub-workflow returns `{success, message, containerName}` without chatId
|
||||||
|
|
||||||
|
**Current flow:**
|
||||||
|
```
|
||||||
|
Parse Logs Command -> Prepare Text Logs Input -> Execute Text Logs -> Send Logs Response
|
||||||
|
```
|
||||||
|
|
||||||
|
**Issue 1 - No fuzzy matching:** The logs flow passes `containerQuery` directly to sub-workflow which does exact match only. Other commands (update/start/stop) have Docker query + fuzzy matching nodes BEFORE calling sub-workflow.
|
||||||
|
|
||||||
|
**Issue 2 - Missing chatId:** Send Logs Response (node id: telegram-send-logs) uses:
|
||||||
|
```json
|
||||||
|
"chatId": "={{ $json.chatId }}"
|
||||||
|
```
|
||||||
|
But `$json` is the sub-workflow output which doesn't include chatId.
|
||||||
|
|
||||||
|
**Fix for Send Logs Response:** Change chatId to reference the input node that HAS chatId:
|
||||||
|
```json
|
||||||
|
"chatId": "={{ $('Prepare Text Logs Input').item.json.chatId }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note on fuzzy matching:** Adding full fuzzy matching infrastructure (Docker query + Match Container node + Check Match Count switch) would require significant changes. For now, the sub-workflow already does name lookup via Docker API. The issue is it requires EXACT normalized name.
|
||||||
|
|
||||||
|
**Alternative simpler fix:** Update the logs sub-workflow's "Find Container" node to use `.includes()` instead of exact `===` match. This provides fuzzy matching without restructuring main workflow.
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. In n8n-workflow.json, find node id "telegram-send-logs" (Send Logs Response)
|
||||||
|
2. Change `chatId` from `={{ $json.chatId }}` to `={{ $('Prepare Text Logs Input').item.json.chatId }}`
|
||||||
|
|
||||||
|
3. In n8n-container-logs.json, find "Find Container" node (id: 52dd705b-dd3b-4fdc-8484-276845857ad0)
|
||||||
|
4. Change the filter logic from exact match to includes:
|
||||||
|
- FROM: `normalizeName(c.Names[0]) === containerName`
|
||||||
|
- TO: `normalizeName(c.Names[0]).includes(containerName)`
|
||||||
|
5. Add handling for multiple matches (throw error suggesting which containers match)
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- Send Logs Response uses `$('Prepare Text Logs Input').item.json.chatId`
|
||||||
|
- n8n-container-logs.json Find Container uses `.includes()` for matching
|
||||||
|
</verify>
|
||||||
|
<done>Logs command works with fuzzy matching and Send Logs Response has valid chatId</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 5: Handle "message not modified" error in logs refresh</name>
|
||||||
|
<files>n8n-workflow.json</files>
|
||||||
|
<action>
|
||||||
|
**Problem:** User reported "message is not modified" error when refreshing logs that haven't changed. Telegram API rejects editMessageText when content is identical.
|
||||||
|
|
||||||
|
**Current flow:**
|
||||||
|
```
|
||||||
|
Prepare Inline Logs Input -> Execute Inline Logs -> Format Inline Logs Result -> Send Logs Result
|
||||||
|
```
|
||||||
|
|
||||||
|
**Send Logs Result (node id: http-send-logs-result):** Uses httpRequest to call editMessageText API.
|
||||||
|
|
||||||
|
**Fix options:**
|
||||||
|
A. Add timestamp to force content difference (changes UX - shows timestamp)
|
||||||
|
B. Add error handling node after Send Logs Result to catch this specific error
|
||||||
|
C. Use Telegram node with "Continue On Fail" and handle error in next node
|
||||||
|
|
||||||
|
**Recommended: Option A - Add timestamp to logs display**
|
||||||
|
|
||||||
|
This is the cleanest solution because:
|
||||||
|
- Logs refresh SHOULD show when they were fetched
|
||||||
|
- Always succeeds (no error handling needed)
|
||||||
|
- Better UX - user knows logs are fresh
|
||||||
|
|
||||||
|
**Implementation in Format Inline Logs Result (node id: b1800598-1ff6-4da3-8506-4e4e8127f902):**
|
||||||
|
|
||||||
|
Update the jsCode to add timestamp to the message:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Format logs result for inline keyboard display
|
||||||
|
const result = $json;
|
||||||
|
const data = $('Prepare Inline Logs Input').item.json;
|
||||||
|
|
||||||
|
const containerName = result.containerName;
|
||||||
|
|
||||||
|
// Add timestamp to prevent "message not modified" error on refresh
|
||||||
|
const timestamp = new Date().toLocaleTimeString('en-US', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build inline keyboard
|
||||||
|
const keyboard = [
|
||||||
|
[
|
||||||
|
{ text: '\ud83d\udd04 Refresh Logs', callback_data: `action:logs:${containerName}` },
|
||||||
|
{ text: '\u2b06\ufe0f Update', callback_data: `action:update:${containerName}` }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ text: '\u25c0\ufe0f Back to List', callback_data: 'list:0' }
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Append timestamp to message
|
||||||
|
const messageWithTimestamp = result.message + `\n\n<i>Updated: ${timestamp}</i>`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
json: {
|
||||||
|
chatId: data.chatId,
|
||||||
|
messageId: data.messageId,
|
||||||
|
text: messageWithTimestamp,
|
||||||
|
reply_markup: { inline_keyboard: keyboard }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Find node with id "b1800598-1ff6-4da3-8506-4e4e8127f902" (Format Inline Logs Result)
|
||||||
|
2. Update jsCode to add timestamp generation
|
||||||
|
3. Append timestamp to the message text before returning
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- Format Inline Logs Result includes timestamp generation code
|
||||||
|
- Output text includes "Updated: HH:MM:SS" suffix
|
||||||
|
</verify>
|
||||||
|
<done>Logs refresh always succeeds (timestamp ensures content is different)</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
1. `grep -A5 '"Prepare Text Update Input"' n8n-workflow.json` shows only Execute Text Update connection
|
||||||
|
2. `grep 'Build Progress Message' n8n-workflow.json | grep -c 'Prepare Batch'` returns 2 (update + action)
|
||||||
|
3. `grep 'Prepare Text Logs Input' n8n-workflow.json` shows chatId reference in Send Logs Response
|
||||||
|
4. `grep 'includes' n8n-container-logs.json` shows fuzzy matching in Find Container
|
||||||
|
5. `grep 'timestamp' n8n-workflow.json` shows timestamp in Format Inline Logs Result
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Text update sends single message (no race condition)
|
||||||
|
- Batch update and batch actions execute without "undefined" errors
|
||||||
|
- Logs command works with partial container names
|
||||||
|
- Logs refresh never fails with "message not modified" error
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/10-workflow-modularization/10-07-SUMMARY.md`
|
||||||
|
</output>
|
||||||
Reference in New Issue
Block a user