4e29bdeb56
- Added 15-01-SUMMARY.md documenting implementation and deviations - Updated STATE.md: Phase 15 complete (2/2 plans), 52 total plans, v1.4 at 20% - Task 1 (Container ID Registry) was pre-existing in baseline - Task 2 (Token Encoder/Decoder) implemented and pushed to n8n - All utility nodes standalone, ready for Phase 16 wiring Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
179 lines
8.7 KiB
Markdown
179 lines
8.7 KiB
Markdown
---
|
|
phase: 15-infrastructure-foundation
|
|
plan: 01
|
|
subsystem: infra
|
|
tags: [container-id-registry, callback-token-encoding, unraid-prefixedid, telegram-callback-data]
|
|
|
|
# Dependency graph
|
|
requires:
|
|
- phase: 14-unraid-api-access
|
|
provides: Unraid GraphQL API container data format (id: PrefixedID, names[], state)
|
|
- phase: 11-update-all-callback-limits
|
|
context: Demonstrated callback_data 64-byte limit (bitmap encoding addressed this for batch operations)
|
|
provides:
|
|
- Container ID Registry utility node (container name <-> Unraid PrefixedID translation)
|
|
- Callback Token Encoder utility node (PrefixedID -> 8-char hex token with collision detection)
|
|
- Callback Token Decoder utility node (8-char token -> PrefixedID resolution)
|
|
- Static data persistence pattern for _containerIdMap and _callbackTokens
|
|
affects: [16-api-migration, 17-container-id-translation]
|
|
|
|
# Tech tracking
|
|
tech-stack:
|
|
added:
|
|
- crypto.subtle.digest (Web Crypto API for SHA-256 hashing)
|
|
patterns:
|
|
- JSON serialization for n8n static data persistence (top-level assignment pattern per CLAUDE.md)
|
|
- SHA-256 hash with 7-window collision detection (56 chars / 8-char windows)
|
|
- Idempotent token encoding (reuse existing token if same unraidId)
|
|
- Container name normalization (strip leading '/', lowercase)
|
|
- Registry staleness detection (60-second threshold for error messaging)
|
|
|
|
key-files:
|
|
created: []
|
|
modified:
|
|
- n8n-workflow.json
|
|
|
|
key-decisions:
|
|
- "Token encoder uses 8-char hex (not base64) for deterministic collision avoidance via hash window offsets"
|
|
- "Registry stores full PrefixedID (129-char) as-is, not normalized - downstream consumers handle format"
|
|
- "Decoder is read-only (no JSON.stringify) - token store managed entirely by encoder"
|
|
- "Collision detection tries 7 non-overlapping windows (0, 8, 16, 24, 32, 40, 48 char offsets from SHA-256)"
|
|
- "Standalone utility nodes NOT connected to active flow - Phase 16 will wire them in"
|
|
|
|
patterns-established:
|
|
- "Container ID Registry as centralized name->ID translation layer"
|
|
- "Token encoding system as callback_data compression layer for Telegram's 64-byte limit"
|
|
- "Dual-mode node pattern (update vs lookup based on input.containers vs input.containerName)"
|
|
|
|
# Metrics
|
|
duration: 6min
|
|
completed: 2026-02-09
|
|
---
|
|
|
|
# Phase 15 Plan 01: Container ID Registry and Callback Token Encoding Summary
|
|
|
|
Built Container ID Registry and Callback Token Encoding system as standalone utility Code nodes for Phase 16 API migration. Registry maps container names to Unraid 129-char PrefixedIDs, token system compresses PrefixedIDs to 8-char hex for Telegram callback_data limit.
|
|
|
|
## What Was Built
|
|
|
|
### Container ID Registry (Task 1 - Already Complete in Baseline)
|
|
|
|
**Node:** Container ID Registry at position [200, 2400]
|
|
|
|
**Note:** This node was already implemented in the baseline commit 1b4b596 (incorrectly labeled as 15-02 but contained 15-01 work). Verified implementation matches all plan requirements.
|
|
|
|
**Implementation:**
|
|
- `updateRegistry(containers)`: Takes Unraid GraphQL container array, extracts names (strip `/`, lowercase), maps to `{name, unraidId: container.id}`, stores with timestamp
|
|
- `getUnraidId(containerName)`: Resolves container name to 129-char PrefixedID, throws helpful errors (stale registry vs invalid name)
|
|
- `getContainerByName(containerName)`: Returns full entry `{name, unraidId}`
|
|
- Dual-mode input contract: `input.containers` for updates, `input.containerName` for lookups
|
|
- JSON serialization pattern: `registry._containerIdMap = JSON.stringify(newMap)` (top-level assignment per CLAUDE.md)
|
|
- 60-second staleness threshold for error messaging
|
|
|
|
**Verification passed:** All functions present, JSON pattern correct, no connections.
|
|
|
|
### Callback Token Encoder (Task 2)
|
|
|
|
**Node:** Callback Token Encoder at position [600, 2400]
|
|
**Commit:** 1b61343
|
|
|
|
**Implementation:**
|
|
- `encodeToken(unraidId)`: Async function using crypto.subtle.digest('SHA-256')
|
|
- Generates SHA-256 hash, takes first 8 hex chars as token
|
|
- Collision detection: If token exists with different unraidId, tries next 8-char window (offsets: 0, 8, 16, 24, 32, 40, 48)
|
|
- Idempotent: Reuses existing token if same unraidId
|
|
- Input contract: `input.unraidId` (required), `input.action` (optional)
|
|
- Output: `{token, unraidId, callbackData, byteSize, warning}` - includes callback_data format and 64-byte limit validation
|
|
- JSON serialization: `staticData._callbackTokens = JSON.stringify(tokenStore)`
|
|
|
|
**Verification passed:** SHA-256 hashing, 7-window collision detection, JSON pattern, no connections.
|
|
|
|
### Callback Token Decoder (Task 2)
|
|
|
|
**Node:** Callback Token Decoder at position [1000, 2400]
|
|
**Commit:** 1b61343
|
|
|
|
**Implementation:**
|
|
- `decodeToken(token)`: Looks up token in store, throws if not found
|
|
- Input contract: `input.token` (8-char hex) OR `input.callbackData` (string like "action:start:a1b2c3d4")
|
|
- Callback data parsing: Splits by `:`, extracts action and token (last segment)
|
|
- Output: `{token, unraidId, action}`
|
|
- Read-only: Only uses JSON.parse (no stringify) - token store managed by encoder
|
|
|
|
**Verification passed:** decodeToken function, error handling, callbackData parsing, no connections.
|
|
|
|
## Deviations from Plan
|
|
|
|
### Pre-existing Work
|
|
|
|
**Task 1 (Container ID Registry) was already complete in baseline commit 1b4b596.**
|
|
|
|
- **Found during:** Plan execution initialization
|
|
- **Issue:** Commit 1b4b596 was labeled `feat(15-02)` but actually contained both the Container ID Registry (Task 1 from plan 15-01) AND the GraphQL Response Normalizer (Task 1 from plan 15-02)
|
|
- **Resolution:** Verified existing implementation matches all Task 1 requirements (updateRegistry, getUnraidId, getContainerByName, JSON serialization, no connections). Proceeded with Task 2 only.
|
|
- **Impact:** No implementation changes needed for Task 1. Task 2 added as planned.
|
|
- **Commits:** No new commit for Task 1 (already in baseline). Task 2 committed as 1b61343.
|
|
|
|
### n8n API Field Restrictions (Deviation Rule 3 - Blocking Issue)
|
|
|
|
**Notes fields cannot be pushed to n8n via API.**
|
|
|
|
- **Found during:** Task 2 push to n8n (HTTP 400 "must NOT have additional properties")
|
|
- **Issue:** Plan specified adding `notes` and `notesDisplayMode` fields to document utility node purpose. n8n API only accepts 6 fields: id, name, type, typeVersion, position, parameters.
|
|
- **Fix:** Removed notes/notesDisplayMode fields from all nodes before pushing payload. Documentation moved to JSDoc comments in jsCode (first lines of each function).
|
|
- **Files modified:** n8n-workflow.json (cleaned before push)
|
|
- **Verification:** Push succeeded with HTTP 200, n8n confirms 175 nodes.
|
|
- **Impact:** Node documentation now lives in code comments instead of n8n UI notes field. Functionally equivalent for Phase 16 (code is self-documenting).
|
|
|
|
## Execution Summary
|
|
|
|
**Tasks completed:** 2/2
|
|
- Task 1: Container ID Registry (verified baseline implementation)
|
|
- Task 2: Callback Token Encoder and Decoder (implemented and committed)
|
|
|
|
**Commits:**
|
|
- 1b61343: feat(15-01): add Callback Token Encoder and Decoder utility nodes
|
|
|
|
**Duration:** 6 minutes (Task 1 verification + Task 2 implementation + n8n push + commit)
|
|
|
|
**Files modified:**
|
|
- n8n-workflow.json (added 2 nodes: encoder, decoder)
|
|
|
|
**n8n push:** Successful (HTTP 200, 175 nodes, updated 2026-02-09T13:53:17.242Z)
|
|
|
|
## Verification Results
|
|
|
|
All success criteria met:
|
|
|
|
- [✓] Container ID Registry maps container names to Unraid PrefixedID format (129-char)
|
|
- [✓] Callback token encoding produces 8-char hex tokens that fit within Telegram's 64-byte callback_data limit
|
|
- [✓] Token collision detection prevents wrong-container scenarios (7-window SHA-256 approach)
|
|
- [✓] All static data uses JSON serialization (top-level assignment) per CLAUDE.md convention
|
|
- [✓] Three standalone utility nodes ready for Phase 16 to wire in
|
|
- [✓] No connections to/from any utility node (verified in workflow connections map)
|
|
- [✓] Workflow JSON valid and pushed to n8n
|
|
|
|
## Self-Check: PASSED
|
|
|
|
**Created files:**
|
|
- [✓] FOUND: .planning/phases/15-infrastructure-foundation/15-01-SUMMARY.md (this file)
|
|
|
|
**Commits:**
|
|
- [✓] FOUND: 1b61343 (Task 2: Callback Token Encoder and Decoder)
|
|
|
|
**n8n nodes:**
|
|
- [✓] Container ID Registry exists in n8n workflow (175 nodes total)
|
|
- [✓] Callback Token Encoder exists in n8n workflow
|
|
- [✓] Callback Token Decoder exists in n8n workflow
|
|
|
|
## Next Steps
|
|
|
|
**Phase 16 (API Migration)** will:
|
|
1. Wire Container ID Registry into container status flow (connect after Unraid GraphQL responses)
|
|
2. Wire Callback Token Encoder into inline keyboard generation (replace long PrefixedIDs with 8-char tokens)
|
|
3. Wire Callback Token Decoder into callback routing (resolve tokens back to PrefixedIDs)
|
|
4. Update all 60+ Code nodes to use registry for ID translation
|
|
5. Test token collision handling under production load
|
|
|
|
**Ready for:** Plan 15-02 execution (if not already complete) or Phase 16 planning.
|