fix(10.1): revise plans based on checker feedback

This commit is contained in:
Lucas Berger
2026-02-08 08:19:12 -05:00
parent c437aa534f
commit 18f8976a7f
2 changed files with 165 additions and 85 deletions
@@ -5,7 +5,7 @@ type: execute
wave: 1
depends_on: []
files_modified: [n8n-matching.json, n8n-workflow.json]
autonomous: true
autonomous: false
gap_closure: true
must_haves:
@@ -14,7 +14,7 @@ must_haves:
- "Container name matching works for text commands (action, update)"
- "Batch container matching works for batch operations"
- "Disambiguation, suggestion, and not-found flows work correctly"
- "Main workflow node count is reduced after extraction"
- "All 6 Telegram/HTTP response nodes remain in main workflow (sub-workflow returns data only)"
artifacts:
- path: "n8n-matching.json"
provides: "Container matching and disambiguation sub-workflow"
@@ -26,11 +26,11 @@ must_haves:
key_links:
- from: "n8n-workflow.json"
to: "n8n-matching.json"
via: "Execute Workflow nodes"
via: "3 Execute Workflow nodes (Action, Update, Batch)"
pattern: "Execute.*Match"
- from: "n8n-matching.json"
to: "main workflow routing"
via: "action return field"
to: "main workflow Route *Match Result switches"
via: "action return field from last node in each exit path"
pattern: "action"
---
@@ -78,32 +78,53 @@ Create a new n8n-matching.json sub-workflow that encapsulates container matching
3. **Route Action** (Switch node) — Routes by action field to appropriate matching logic
4. **Logic nodes to include (extract from main workflow):**
- `Match Container` (code) — Fuzzy matches container name against list for action commands
- `Match Update Container` (code) — Fuzzy matches for update commands
- `Match Batch Containers` (code) — Matches batch-selected containers against Docker list
- `Check Match Count` (switch) — Routes by match count (0, 1, 2+)
- `Check Update Match Count` (switch) — Routes by update match count
- `Find Closest Match` (code) — Finds closest match for suggestions
- `Check Suggestion` (if) — Checks if suggestion is close enough
- `Build Suggestion Keyboard` (code) — Builds inline keyboard for suggestion
- `Build Disambiguation Message` (code) — Builds disambiguation text
- `Build Not Found Message` (code) — Builds not-found message text
- `Needs Disambiguation` (if) — Checks if batch has ambiguous matches
- `Has Not Found` (if) — Checks if batch has not-found containers
4. **12 logic nodes to extract from main workflow:**
5. **Return pattern (action-based, like other sub-workflows):**
Each exit path returns a JSON object with an `action` field that the main workflow routes on:
- `action: "matched"` + `containerId`, `containerName`Single match found
- `action: "matched_update"` + `containerId`, `containerName` — Single update match found
- `action: "multiple_update"` + `matches` — Multiple update matches (for Handle Update Multiple)
- `action: "suggestion"` + `keyboard`, `text` — Close match suggestion to display
- `action: "no_match"` — No match and no suggestion
- `action: "disambiguation"` + `text` — Disambiguation message to display
- `action: "not_found"` + `text`, `keyboard` — Batch not-found message
- `action: "batch_matched"` + `matchedContainers` — Batch containers matched successfully
- `action: "error"` + `errorMessage` — Docker list error
- `action: "no_match_update"` — No update match found
Matching nodes (Code):
- `Match Container` — Fuzzy matches container name against list for action commands
- `Match Update Container`Fuzzy matches for update commands
- `Match Batch Containers` — Matches batch-selected containers against Docker list
Routing nodes (Switch/If):
- `Check Match Count` — Routes by match count (0, 1, 2+)
- `Check Update Match Count` — Routes by update match count
- `Check Suggestion` — Checks if suggestion is close enough
- `Needs Disambiguation` — Checks if batch has ambiguous matches
- `Has Not Found` — Checks if batch has not-found containers
Result-building nodes (Code):
- `Find Closest Match` — Finds closest match for suggestions
- `Build Suggestion Keyboard` — Builds inline keyboard for suggestion
- `Build Disambiguation Message` — Builds disambiguation text
- `Build Not Found Message` — Builds not-found message text
5. **6 response nodes that MUST NOT be extracted (stay in main workflow per locked decision):**
- `Send Disambiguation` (n8n-nodes-base.httpRequest)
- `Send Suggestion` (n8n-nodes-base.httpRequest)
- `Send No Match` (n8n-nodes-base.telegram)
- `Send Not Found Message` (n8n-nodes-base.httpRequest)
- `Send Update No Match` (n8n-nodes-base.telegram)
- `Delete Suggestion Message` (n8n-nodes-base.httpRequest)
Note: 4 of these 6 are HTTP Request type (not Telegram type). Only Send No Match and Send Update No Match are Telegram nodes.
6. **Return mechanism (how data gets back to main workflow):**
In n8n sub-workflows, the last node executed in each code path automatically becomes the return data. There is no explicit "return" node. Each exit path must end with a Code node (or Set node) that outputs a JSON object with an `action` field.
Add a `Format Return` Code node at each exit path that produces the return object. For paths that already end with a Code node (e.g., Build Suggestion Keyboard), modify that node to include the `action` field in its output. For paths that end with Switch/If nodes, add a small Code node after them.
Exit path return values:
- Single action match found: `{ action: "matched", containerId, containerName }`
- Single update match found: `{ action: "matched_update", containerId, containerName }`
- Multiple update matches: `{ action: "multiple_update", matches }`
- Close match suggestion: `{ action: "suggestion", keyboard, text }`
- No match, no suggestion: `{ action: "no_match" }`
- Disambiguation needed: `{ action: "disambiguation", text }`
- Batch not-found: `{ action: "not_found", text, keyboard }`
- Batch matched OK: `{ action: "batch_matched", matchedContainers }`
- Docker list error: `{ action: "error", errorMessage }`
- No update match: `{ action: "no_match_update" }`
**CRITICAL:** Copy the actual JavaScript code from each Code node in n8n-workflow.json. Do NOT rewrite or approximate the logic. Read the `jsCode` property from each node's parameters and transplant it exactly, adjusting only input references (change `$json` or `$('Node Name').item.json` references to use the sub-workflow's input data).
@@ -111,19 +132,24 @@ Create a new n8n-matching.json sub-workflow that encapsulates container matching
- Use typeVersion that matches existing sub-workflows (check n8n-confirmation.json for reference)
- Include proper metadata (name: "Container Matching", tags, etc.)
- Position nodes in a readable layout (increment x/y coordinates)
**Do NOT include Telegram response nodes** — per locked decision, Send Disambiguation, Send Suggestion, Send No Match, Send Not Found Message, Send Update No Match, and Delete Suggestion Message stay in main workflow.
</action>
<verify>
Verify with Python script:
1. n8n-matching.json is valid JSON
2. Has Execute Workflow Trigger node
3. Has Switch/Route node for action routing
4. Contains all 12 logic nodes (Match Container, Match Update Container, Match Batch Containers, Check Match Count, Check Update Match Count, Find Closest Match, Check Suggestion, Build Suggestion Keyboard, Build Disambiguation Message, Build Not Found Message, Needs Disambiguation, Has Not Found)
4. Contains all 12 logic nodes by name: Match Container, Match Update Container, Match Batch Containers, Check Match Count, Check Update Match Count, Find Closest Match, Check Suggestion, Build Suggestion Keyboard, Build Disambiguation Message, Build Not Found Message, Needs Disambiguation, Has Not Found
5. All Code nodes have non-empty jsCode
6. No Telegram/HTTP Send nodes in sub-workflow
6. None of these 6 response nodes appear in sub-workflow:
- "Send Disambiguation" (httpRequest)
- "Send Suggestion" (httpRequest)
- "Send No Match" (telegram)
- "Send Not Found Message" (httpRequest)
- "Send Update No Match" (telegram)
- "Delete Suggestion Message" (httpRequest)
7. At least one node in each exit path outputs a JSON object with an `action` field (check Code node jsCode for `action:` or `"action"`)
</verify>
<done>n8n-matching.json exists with 14-16 nodes (12 logic + trigger + router + any merge nodes), valid JSON, all matching logic transplanted from main workflow</done>
<done>n8n-matching.json exists with 14-16 nodes (12 logic + trigger + router + any Format Return nodes), valid JSON, all matching logic transplanted, every exit path returns an action-typed JSON object</done>
</task>
<task type="auto">
@@ -133,15 +159,23 @@ Verify with Python script:
**Step 1: Create backup**
Copy n8n-workflow.json to n8n-workflow.json.backup-matching before any modifications.
**Step 2: Remove extracted nodes**
Remove the 12 matching logic nodes from n8n-workflow.json:
**Step 2: Remove the 12 extracted logic nodes**
Remove ONLY these 12 nodes from n8n-workflow.json (the logic nodes now living in n8n-matching.json):
- Match Container, Match Update Container, Match Batch Containers
- Check Match Count, Check Update Match Count
- Find Closest Match, Check Suggestion
- Build Suggestion Keyboard, Build Disambiguation Message, Build Not Found Message
- Needs Disambiguation, Has Not Found
**Step 3: Add integration nodes**
Do NOT remove these 6 response nodes (they stay in main per locked decision):
- Send Disambiguation (httpRequest)
- Send Suggestion (httpRequest)
- Send No Match (telegram)
- Send Not Found Message (httpRequest)
- Send Update No Match (telegram)
- Delete Suggestion Message (httpRequest)
**Step 3: Add 9 integration nodes**
The matching domain has 3 distinct entry points. Create integration for each:
@@ -150,9 +184,9 @@ The matching domain has 3 distinct entry points. Create integration for each:
- Add `Execute Action Match` (Execute Workflow node) — Calls n8n-matching.json with TODO_DEPLOY_MATCHING_WORKFLOW placeholder. Use typeVersion 1.2 with `workflowId: { "__rl": true, "mode": "list", "value": "TODO_DEPLOY_MATCHING_WORKFLOW" }`
- Add `Route Action Match Result` (Switch node) — Routes by returned action field:
- "matched" -> `Prepare Text Action Input` (existing)
- "suggestion" -> `Send Suggestion` (existing Telegram node)
- "no_match" -> `Send No Match` (existing Telegram node)
- "disambiguation" -> `Send Disambiguation` (existing HTTP node)
- "suggestion" -> `Send Suggestion` (existing httpRequest node, stays in main)
- "no_match" -> `Send No Match` (existing telegram node, stays in main)
- "disambiguation" -> `Send Disambiguation` (existing httpRequest node, stays in main)
- "error" -> `Send Docker Error` (existing)
**Entry 2: Update matching (text command like "update nginx")**
@@ -161,7 +195,7 @@ The matching domain has 3 distinct entry points. Create integration for each:
- Add `Route Update Match Result` (Switch node) — Routes by returned action:
- "matched_update" -> `Prepare Text Update Input` (existing)
- "multiple_update" -> `Handle Update Multiple` (existing)
- "no_match_update" -> `Send Update No Match` (existing)
- "no_match_update" -> `Send Update No Match` (existing telegram node, stays in main)
- "error" -> `Send Update Error` (existing)
**Entry 3: Batch matching**
@@ -169,24 +203,25 @@ The matching domain has 3 distinct entry points. Create integration for each:
- Add `Execute Batch Match` (Execute Workflow node) — Calls n8n-matching.json
- Add `Route Batch Match Result` (Switch node) — Routes by returned action:
- "batch_matched" -> `Build Batch Keyboard` (existing) or equivalent downstream
- "disambiguation" -> `Send Disambiguation` (existing)
- "not_found" -> `Send Not Found Message` (existing) then -> `Route Batch Action` (existing)
- "disambiguation" -> `Send Disambiguation` (existing httpRequest node, stays in main)
- "not_found" -> `Send Not Found Message` (existing httpRequest node, stays in main) then -> `Route Batch Action` (existing)
**Step 4: Rewire connections**
- `Docker List for Action` -> `Prepare Action Match Input` -> `Execute Action Match` -> `Route Action Match Result` -> (existing targets)
- `Docker List for Update` -> `Prepare Update Match Input` -> `Execute Update Match` -> `Route Update Match Result` -> (existing targets)
- `Get Containers for Batch` -> `Prepare Batch Match Input` -> `Execute Batch Match` -> `Route Batch Match Result` -> (existing targets)
- Remove old connections from Docker List nodes to removed matching nodes
- Preserve all connections from Telegram response nodes (Send Disambiguation, etc.) to their downstream targets
- Preserve all connections from the 6 response nodes (Send Disambiguation, etc.) to their downstream targets
**Step 5: Update Answer Action Query connection**
Currently `Answer Action Query` -> `Delete Suggestion Message`. This connection stays as-is since Delete Suggestion Message remains in main workflow.
**Node count verification:**
- Removed: 12 nodes
- Added: 9 nodes (3 Prepare + 3 Execute + 3 Route)
- Net reduction: 3 nodes (168 -> ~165)
- 6 Telegram response nodes remain in main (as required by locked decision)
**Node count math:**
- Starting count: 168 nodes
- Removed: 12 logic nodes
- Added: 9 integration nodes (3 Prepare + 3 Execute + 3 Route)
- Remaining in main: 6 response nodes (not removed, per locked decision)
- Net reduction: 3 nodes (168 -> 165)
**IMPORTANT:** Maintain exact connection format used in existing workflow. Check existing Execute Workflow nodes (e.g., Execute Confirmation, Execute Batch UI) for correct connection structure, parameter format, and typeVersion.
</action>
@@ -195,13 +230,36 @@ Verify with Python script:
1. n8n-workflow.json is valid JSON
2. Backup file exists at n8n-workflow.json.backup-matching
3. Node count is 165 or fewer (168 - 12 removed + 9 added = 165)
4. None of the 12 removed nodes exist in main workflow
4. None of the 12 extracted logic nodes exist in main workflow: Match Container, Match Update Container, Match Batch Containers, Check Match Count, Check Update Match Count, Find Closest Match, Check Suggestion, Build Suggestion Keyboard, Build Disambiguation Message, Build Not Found Message, Needs Disambiguation, Has Not Found
5. 3 Execute Workflow nodes reference TODO_DEPLOY_MATCHING_WORKFLOW (or actual ID)
6. All 6 Telegram response nodes (Send Disambiguation, Send Suggestion, Send No Match, Send Not Found Message, Send Update No Match, Delete Suggestion Message) still exist
6. All 6 response nodes still exist in main workflow:
- "Send Disambiguation" (httpRequest)
- "Send Suggestion" (httpRequest)
- "Send No Match" (telegram)
- "Send Not Found Message" (httpRequest)
- "Send Update No Match" (telegram)
- "Delete Suggestion Message" (httpRequest)
7. No orphan nodes (every node has at least one connection in or out, except trigger)
8. All existing Execute Workflow nodes for other sub-workflows still present (14 original)
</verify>
<done>Main workflow updated with matching sub-workflow calls, 12 logic nodes removed, 9 integration nodes added, 6 Telegram response nodes preserved, backup created, all connections properly wired</done>
<done>Main workflow updated: 12 logic nodes removed, 9 integration nodes added (net -3, 168 -> 165), 6 response nodes preserved in main, backup created, all connections properly wired</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 3: Verify matching sub-workflow runtime behavior</name>
<what-built>Matching/disambiguation domain extracted into n8n-matching.json sub-workflow. Main workflow now calls it via 3 Execute Workflow nodes (action match, update match, batch match). All 6 response nodes remain in main workflow.</what-built>
<how-to-verify>
1. Import n8n-matching.json into n8n (note the workflow ID)
2. Update the 3 TODO_DEPLOY_MATCHING_WORKFLOW placeholders in n8n-workflow.json with the actual workflow ID
3. Import updated n8n-workflow.json into n8n
4. Test action matching: Send a text command like "stop nginx" — should match container and show action buttons
5. Test no match: Send "stop nonexistent" — should show "not found" message
6. Test disambiguation: Send a partial name that matches multiple containers — should show disambiguation options
7. Test update matching: Send "update nginx" — should match and show update confirmation
8. Test batch matching: Enter batch mode, select containers, confirm — batch should execute correctly
9. Verify all response messages appear correctly in Telegram (proves response nodes stayed in main)
</how-to-verify>
<resume-signal>Type "approved" if all matching flows work, or describe which flow(s) failed</resume-signal>
</task>
</tasks>
@@ -211,17 +269,20 @@ Verify with Python script:
2. `python3 -c "import json; wf=json.load(open('n8n-workflow.json')); print(f'Main nodes: {len(wf[\"nodes\"])}')"` shows ~165 nodes
3. Both files are valid JSON
4. No matching logic nodes remain in main workflow
5. All Telegram response nodes preserved in main workflow
5. All 6 response nodes preserved in main workflow (4 httpRequest + 2 telegram)
6. Execute Workflow connections properly structured
7. Sub-workflow exit paths return action-typed JSON objects
</verification>
<success_criteria>
- n8n-matching.json exists with all 12 matching logic nodes transplanted
- Main workflow reduced from 168 to ~165 nodes
- Every exit path in sub-workflow returns a JSON object with an `action` field
- Main workflow reduced from 168 to ~165 nodes (12 removed, 9 added)
- All 3 matching entry paths (action, update, batch) routed through sub-workflow
- 6 Telegram response nodes remain in main workflow per locked decision
- 6 response nodes remain in main workflow per locked decision (4 httpRequest + 2 telegram)
- Backup file created before modification
- Both workflow files are valid JSON with no orphan nodes
- Runtime verification confirms matching flows work end-to-end
</success_criteria>
<output>
@@ -2,17 +2,17 @@
phase: 10.1-aggressive-workflow-modularization
plan: 07
type: execute
wave: 1
depends_on: []
wave: 2
depends_on: ["10.1-06"]
files_modified: [DEPLOY-SUBWORKFLOWS.md]
autonomous: true
gap_closure: true
must_haves:
truths:
- "All 62+ Code nodes in main workflow are classified as orchestration or domain logic"
- "All Code nodes in main workflow are classified as orchestration or domain logic with evidence"
- "All 7 sub-workflow input/output contracts are formally documented"
- "Node count target is updated with evidence-based rationale"
- "Node count target is updated with evidence-based rationale showing current count is near-minimal"
- "Each Execute Workflow node's input mapping is verified against sub-workflow expectations"
artifacts:
- path: "DEPLOY-SUBWORKFLOWS.md"
@@ -31,6 +31,8 @@ Close Gaps 2 and 3 from VERIFICATION.md: (1) Classify all Code nodes in main wor
Purpose: The verification found that 62 Code nodes were unanalyzed (Gap 2) and sub-workflow contracts were undocumented (Gap 3). This plan provides the evidence needed to close both gaps and formally documents the architecture.
Output: Updated DEPLOY-SUBWORKFLOWS.md with Code node classification, I/O contracts, and revised node count rationale
Note: This plan depends on 10.1-06 because it documents the n8n-matching.json sub-workflow contract. If 10.1-06 is not yet complete, document the matching sub-workflow contract as "pending extraction" and fill in after 10.1-06 finishes.
</objective>
<execution_context>
@@ -44,6 +46,7 @@ Output: Updated DEPLOY-SUBWORKFLOWS.md with Code node classification, I/O contra
@.planning/STATE.md
@.planning/phases/10.1-aggressive-workflow-modularization/10.1-VERIFICATION.md
@.planning/phases/10.1-aggressive-workflow-modularization/10.1-01-domain-analysis.md
@.planning/phases/10.1-aggressive-workflow-modularization/10.1-06-SUMMARY.md
@DEPLOY-SUBWORKFLOWS.md
@n8n-workflow.json
@n8n-batch-ui.json
@@ -52,19 +55,18 @@ Output: Updated DEPLOY-SUBWORKFLOWS.md with Code node classification, I/O contra
@n8n-update.json
@n8n-actions.json
@n8n-logs.json
@n8n-matching.json
</context>
<tasks>
<task type="auto">
<name>Task 1: Classify all Code nodes and document contracts</name>
<name>Task 1: Classify all Code nodes in main workflow</name>
<files>DEPLOY-SUBWORKFLOWS.md</files>
<action>
Read the existing DEPLOY-SUBWORKFLOWS.md and significantly expand it with two new sections.
Read the existing DEPLOY-SUBWORKFLOWS.md and add a new "Code Node Classification" section.
**Section 1: Code Node Classification**
Analyze every Code node in n8n-workflow.json (62 nodes currently, may be ~50 after plan 06 extraction). For each Code node, read its `jsCode` parameter content and classify it into one of these categories:
Analyze every Code node in n8n-workflow.json (should be ~50 after plan 06 extraction removes some). For each Code node, read its `jsCode` parameter content and classify it into one of these categories:
| Category | Definition | Should Stay in Main? |
|----------|-----------|---------------------|
@@ -95,11 +97,26 @@ Extraction overhead per domain: ~3 nodes (Prepare + Execute + Route)
Net reduction potential: NN nodes
```
This proves whether any further extraction is viable beyond matching domain.
This proves whether any further extraction is viable beyond the matching domain.
</action>
<verify>
1. DEPLOY-SUBWORKFLOWS.md contains "Code Node Classification" section with table
2. Table row count matches actual Code node count in n8n-workflow.json (verify with: `python3 -c "import json; wf=json.load(open('n8n-workflow.json')); print(sum(1 for n in wf['nodes'] if n['type']=='n8n-nodes-base.code'))"`)
3. All 6 categories are represented in the summary
4. Category counts sum to total Code node count
</verify>
<done>Every Code node in main workflow classified with category, line count, and rationale. Summary shows breakdown by category and quantifies remaining extraction potential.</done>
</task>
**Section 2: Sub-workflow Input/Output Contracts**
<task type="auto">
<name>Task 2: Document sub-workflow contracts and node count analysis</name>
<files>DEPLOY-SUBWORKFLOWS.md</files>
<action>
Add two more sections to DEPLOY-SUBWORKFLOWS.md (after the Code Node Classification from Task 1).
For each of the 6 (or 7 after plan 06) sub-workflows, document:
**Section: Sub-workflow Input/Output Contracts**
For each of the 7 sub-workflows, document:
1. **Input contract** - Read the Execute Workflow Trigger node and the Prepare *Input Code nodes that feed it. Document every field:
```
@@ -125,15 +142,17 @@ For each of the 6 (or 7 after plan 06) sub-workflows, document:
3. **Verification** - For each Execute Workflow node in main workflow, verify that the preceding Prepare *Input node produces all required fields. Document any mismatches.
Sub-workflows to document:
- n8n-update.json (Container Update) called by 3 Execute nodes
- n8n-actions.json (Container Actions) called by 3 Execute nodes
- n8n-logs.json (Container Logs) called by 2 Execute nodes
- n8n-batch-ui.json (Batch UI) called by 1 Execute node
- n8n-status.json (Container Status) called by 4 Execute nodes
- n8n-confirmation.json (Confirmation Dialogs) called by 1 Execute node
- n8n-matching.json (Container Matching) called by 3 Execute nodes (if plan 06 complete, otherwise note as pending)
- n8n-update.json (Container Update) -- called by 3 Execute nodes
- n8n-actions.json (Container Actions) -- called by 3 Execute nodes
- n8n-logs.json (Container Logs) -- called by 2 Execute nodes
- n8n-batch-ui.json (Batch UI) -- called by 1 Execute node
- n8n-status.json (Container Status) -- called by 4 Execute nodes
- n8n-confirmation.json (Confirmation Dialogs) -- called by 1 Execute node
- n8n-matching.json (Container Matching) -- called by 3 Execute nodes (use 10.1-06-SUMMARY.md for reference)
**Section 3: Revised Node Count Rationale**
Also verify the total Execute Workflow node count: should be 17 (14 original + 3 from matching extraction).
**Section: Node Count Analysis**
Add a section that explains the final node count with evidence:
@@ -167,22 +186,22 @@ Gap: NN nodes of domain logic where extraction overhead (~3 nodes per domain) ma
- Add the new sections after the existing content
</action>
<verify>
1. DEPLOY-SUBWORKFLOWS.md contains "Code Node Classification" section with table covering all Code nodes
2. DEPLOY-SUBWORKFLOWS.md contains "Input/Output Contract" section for each sub-workflow
3. Each sub-workflow contract lists all input fields with types
4. Each sub-workflow contract lists all output actions with fields
5. DEPLOY-SUBWORKFLOWS.md contains "Node Count Analysis" section with revised rationale
6. All Code node categories sum to total Code node count in main workflow
1. DEPLOY-SUBWORKFLOWS.md contains "Input/Output Contract" section for each of the 7 sub-workflows
2. Each sub-workflow contract lists all input fields with types
3. Each sub-workflow contract lists all output actions with fields
4. DEPLOY-SUBWORKFLOWS.md contains "Node Count Analysis" section with revised rationale
5. Execute Workflow node count documented (should be 17 total)
6. Spot-check: For n8n-status.json, verify the input contract fields match what `Prepare Status Input` Code nodes actually produce (read the jsCode)
</verify>
<done>All Code nodes classified with evidence, all sub-workflow contracts documented with field-level detail, node count rationale updated with structural analysis proving current count is near-minimal</done>
<done>All 7 sub-workflow contracts documented with field-level input/output detail. Execute Workflow node mappings verified. Node count analysis provides structural evidence that current count is near-minimal.</done>
</task>
</tasks>
<verification>
1. DEPLOY-SUBWORKFLOWS.md exists and contains all three new sections
1. DEPLOY-SUBWORKFLOWS.md exists and contains all three new sections (Code Classification, Contracts, Node Count)
2. Code node classification covers every Code node in main workflow (verify count matches)
3. Sub-workflow contracts cover all 6-7 sub-workflows
3. Sub-workflow contracts cover all 7 sub-workflows
4. Each contract has input fields table and output actions table
5. Node count analysis shows evidence-based rationale
6. No factual errors in classification (spot-check 5 random Code nodes)