# NuQloud Scrum Implementation Plan Plan date: 2026-07-03 ## 1. Direction NuQloud Scrum is a Talk-first scrum layer for NuQloud deployments. Canonical model: | Nextcloud surface | NuQloud Scrum meaning | |---|---| | Talk conversation | Scrum workspace | | Talk thread / reply chain | Task | | Talk messages in thread | Task discussion/activity | | Files shared in conversation | Workspace files | | Files shared in thread/reply chain | Task files | | Deck board | Workspace board mirror | | Deck card | Task mirror | | NuQloud Scrum DB | Mapping, index, sync state, audit | Product rule for v1: 1. Talk is the primary UX, especially on mobile. 2. Threads/reply chains are tasks. 3. Files and Deck are mirrors. 4. NuQloud Scrum stores indexes and sync state only. 5. No custom mobile client is required. ## 2. App Boundary Create a new app: ```text nextcloud/custom_apps/nuqloud_scrum/ ``` Do not merge this into: 1. `qortal_integration` - account, broker, OIDC, publishing controls. 2. `qortal_files_bridge` - QDN/files bridge. 3. `qortal_talk_bridge` - temporary Talk/Q-Chat bridge. 4. `chd_admin` - internal CHD/admin-only work. Suggested app metadata: ```text id: nuqloud_scrum name: NuQloud Scrum namespace: NuqloudScrum category: integration dependencies: nextcloud min-version: 32 nextcloud max-version: 34 initially optional apps: spreed, deck ``` Use Powered Mode language by default. Avoid crypto-native labels in this app UI. ## 3. Compatibility Baseline Verified documentation assumptions on 2026-07-03: 1. Talk chat API base endpoint is `/ocs/v2.php/apps/spreed/api/v1`. 2. Talk chat fetch supports `lastKnownMessageId`, pagination headers, parent message data, and reaction fields. 3. Talk send-message supports `replyTo` for replies. 4. Talk file share uses Files Sharing OCS with `shareType=10` for conversation shares. 5. Talk shared item listings support `lastKnownMessageId` pagination when the relevant capability exists. 6. Deck REST API is authenticated and exposes boards, stacks, cards, assignments, labels, comments, and attachments. 7. Nextcloud app schema changes belong in `lib/Migration`. 8. Background jobs belong in the app bootstrap/job registration path. Implementation rule: ```text Native Talk thread metadata available? Use Talk thread identifiers directly. Only parent/reply chain available? Treat root message ID + parent relationship as task identity. No stable thread creation API available? Bot posts an anchor task message and users reply to that message. ``` ## 4. Target App Structure ```text nextcloud/custom_apps/nuqloud_scrum/ appinfo/ info.xml routes.php lib/ AppInfo/ Application.php BackgroundJob/ SyncConversationsJob.php SyncConversationMessagesJob.php SyncDeckJob.php SyncFilesJob.php SummarizeWorkspacesJob.php Controller/ PageController.php SettingsController.php WorkspaceController.php TaskController.php Db/ Workspace.php WorkspaceMapper.php ThreadTask.php ThreadTaskMapper.php ThreadMessage.php ThreadMessageMapper.php MirroredFile.php MirroredFileMapper.php DeckSync.php DeckSyncMapper.php SyncCursor.php SyncCursorMapper.php Migration/ Version000001DateYYYYMMDDHHMMSS.php Service/ ConfigService.php TalkApiClient.php TalkCapabilityService.php WorkspaceService.php ThreadTaskService.php FolderMirrorService.php FileIndexService.php DeckApiClient.php DeckMirrorService.php ReactionWorkflowService.php BotCommandService.php SummaryService.php SlugService.php AuditService.php templates/ index.php admin.php js/ index.js admin.js css/ style.css admin.css img/ app.svg l10n/ .gitkeep ``` Keep the first UI thin: 1. Admin settings: enable/disable, service user config, sync intervals, workspace filters. 2. User page: workspace index, task links, sync state, Deck links, folder links. 3. No replacement scrum board in v1. ## 5. Data Model ### `nuqloud_scrum_workspaces` ```text id bigint primary key talk_token varchar(128) not null unique talk_room_name varchar(255) not null talk_room_type varchar(64) null deck_board_id bigint null root_folder_path varchar(1024) not null sync_enabled smallint default 1 last_message_id bigint default 0 last_share_message_id bigint default 0 created_by varchar(128) null created_at bigint not null updated_at bigint not null ``` Indexes: ```text uniq talk_token idx sync_enabled idx updated_at ``` ### `nuqloud_scrum_threads` ```text id bigint primary key workspace_id bigint not null talk_thread_id varchar(128) null talk_root_message_id bigint not null title varchar(255) not null slug varchar(255) not null folder_path varchar(1024) not null deck_card_id bigint null deck_stack_id bigint null status varchar(32) default 'inbox' priority varchar(32) default 'normal' created_by varchar(128) null assigned_to varchar(128) null due_at bigint null last_activity_message_id bigint default 0 created_at bigint not null updated_at bigint not null ``` Indexes: ```text uniq workspace_id + talk_root_message_id uniq workspace_id + talk_thread_id when available idx workspace_id + status idx deck_card_id ``` ### `nuqloud_scrum_thread_messages` ```text id bigint primary key thread_id bigint not null talk_message_id bigint not null actor_type varchar(64) not null actor_id varchar(128) not null actor_display_name varchar(255) null message_text clob null message_hash varchar(128) not null raw_payload clob null created_at bigint not null updated_at bigint not null ``` Indexes: ```text uniq thread_id + talk_message_id idx actor_id idx created_at ``` ### `nuqloud_scrum_files` ```text id bigint primary key workspace_id bigint not null thread_id bigint null talk_message_id bigint null nc_file_id bigint null original_path varchar(1024) not null workspace_path varchar(1024) not null checksum varchar(128) null size bigint default 0 mime_type varchar(255) null mirror_state varchar(32) default 'pending' sync_error clob null created_at bigint not null updated_at bigint not null ``` Indexes: ```text idx workspace_id + thread_id idx nc_file_id idx mirror_state ``` ### `nuqloud_scrum_deck_sync` ```text id bigint primary key workspace_id bigint not null thread_id bigint null deck_board_id bigint null deck_stack_id bigint null deck_card_id bigint null last_sync_at bigint null sync_state varchar(32) default 'pending' sync_error clob null created_at bigint not null updated_at bigint not null ``` ### `nuqloud_scrum_sync_cursors` ```text id bigint primary key cursor_type varchar(64) not null scope_key varchar(255) not null cursor_value varchar(255) not null locked_until bigint null updated_at bigint not null ``` Indexes: ```text uniq cursor_type + scope_key idx locked_until ``` ## 6. Folder Layout Root folder: ```text /NuQloud Scrum/ ``` Workspace folder: ```text /NuQloud Scrum/{workspace-name}/ _workspace.md _manifest.json _open-tasks.md _decisions.md Files/ Threads/ Summaries/ ``` Task folder: ```text /NuQloud Scrum/{workspace-name}/Threads/ {root-message-id}-{slug}/ _task.md _activity.jsonl _summary.md Files/ Decisions/ ``` Rules: 1. Prefix folders with immutable IDs. 2. Slugs are display-only and may be regenerated. 3. DB identity always wins over path names. 4. Folder moves are detected and healed through `file_id` where possible. 5. Generated markdown must escape raw user text. ## 7. Sync Flow ### Conversation discovery `SyncConversationsJob`: 1. Fetch configured Talk conversations visible to the automation/service user. 2. Apply workspace filters: - all conversations - named allowlist - group/member filter 3. `ensureWorkspace(token)`. 4. Create root folder and manifest. 5. Ensure Deck board when Deck is enabled. ### Message sync `SyncConversationMessagesJob`: 1. For each enabled workspace, fetch Talk messages after `last_message_id`. 2. Store raw payload only server-side. 3. Detect task root: - native `threadId` root when available - parent/reply root message ID fallback - bot anchor message fallback 4. `ensureThreadTask`. 5. Append message record if it belongs to a task. 6. Apply reactions. 7. Queue Deck and file sync. 8. Advance cursor only after successful persistence. ### File sync `SyncFilesJob`: 1. Fetch shared items from Talk where capability allows it. 2. Resolve target: - thread/task folder when message has thread/root context - workspace `Files/YYYY-MM/` otherwise 3. Copy or link according to admin setting: - v1 default: copy into NuQloud Scrum folder - future option: retain original and write folder link only 4. Record file ID, checksum, size, MIME type. 5. Avoid duplicate copies by `nc_file_id + target_path`. ### Deck sync `SyncDeckJob`: 1. Ensure one Deck board per workspace. 2. Ensure default stacks: - Inbox - Ready - In Progress - Blocked - Review - Done - Archived 3. Ensure one Deck card per thread task. 4. Mirror status to stack. 5. Update card description with: - Talk room link - thread/task folder link - latest summary - source IDs 6. Avoid syncing every Talk reply as a Deck comment in v1 unless explicitly enabled. ## 8. Reaction Workflow Default mappings: | Reaction | Status/action | |---|---| | check mark | Done | | construction | Blocked | | eyes | Review | | fire | Priority | | pushpin | Decision | | folder | File/context marker | Implementation notes: 1. Store reaction-derived status changes in DB. 2. Prefer latest trusted actor reaction if conflicts exist. 3. Allow admins to disable reaction mappings. 4. Do not rely on client-only state. 5. Reaction values must be normalized before matching. ## 9. Bot Commands Use bot-style messages, not legacy slash commands. Supported v1 commands: ```text @NuQloud status blocked @NuQloud status done @NuQloud assign @username @NuQloud due 2026-07-10 @NuQloud summarize @NuQloud help ``` Rules: 1. Commands only work in authenticated Talk conversations. 2. Only room participants can affect that workspace. 3. Admin-configured moderators can override any task. 4. User input is parsed with strict allowlists. 5. Bot responses must not expose raw payloads, secrets, or stack traces. ## 10. Security Requirements Public endpoints: 1. None in v1. Authenticated endpoints: 1. Workspace/task index pages. 2. Per-user workspace/task JSON APIs. Admin endpoints: 1. App enable/disable. 2. Service user/app-password settings. 3. Workspace sync filters. 4. Reaction/Deck/file mirroring settings. 5. Manual resync/repair actions. Required controls: 1. Use Nextcloud controller auth for all routes. 2. Use CSRF checks for state-changing browser routes. 3. Validate all IDs, paths, statuses, dates, and command arguments. 4. Escape user message text in generated markdown and UI. 5. Never log app passwords, raw auth headers, invite tokens, or raw message bodies. 6. Store service credentials in app config with admin-only access. 7. Enforce participant/admin checks server-side. 8. Treat Deck and folder mirrors as derived state, not authority. ## 11. Admin Settings Initial settings: ```text nuqloud_scrum_enabled: 0|1 talk_service_user: string talk_service_app_password: secret workspace_filter_mode: all|allowlist|disabled workspace_allowlist: JSON array of Talk tokens/names deck_sync_enabled: 0|1 file_mirror_enabled: 0|1 file_mirror_mode: copy|link reaction_workflow_enabled: 0|1 bot_commands_enabled: 0|1 summary_enabled: 0|1 sync_interval_minutes: integer ``` Admin UI should use theme variables, l10n strings, and existing Nextcloud settings patterns. ## 12. Implementation Phases ### Phase 0 - API validation spike Goal: prove target Talk/Deck behavior on devcloud before committing to event semantics. Tasks: 1. Confirm Talk capabilities for target Nextcloud/Talk version. 2. Confirm whether native thread identifiers appear in fetched message payloads. 3. Confirm reply-chain fallback using `parent` and `replyTo`. 4. Confirm reaction payload shape. 5. Confirm file share metadata includes usable thread/root context. 6. Confirm Deck card create/update/comment/attachment endpoints. 7. Decide service account pattern for background jobs. Deliverable: ```text docs/engineering/nuqloud-scrum-api-validation.md ``` ### Phase 1 - app skeleton and settings Tasks: 1. Add `nuqloud_scrum` app scaffold. 2. Add `info.xml`, bootstrap, routes, admin settings page. 3. Add config service. 4. Add app icon and minimal themed CSS. 5. Add l10n-ready strings. 6. Register background jobs disabled until config is valid. Acceptance: 1. App installs/enables. 2. Admin can save settings. 3. Missing Talk/Deck apps show clear admin warnings. 4. No public routes exist. ### Phase 2 - migrations and persistence Tasks: 1. Add migration for workspace/thread/message/file/deck/cursor tables. 2. Add entities/mappers. 3. Add repository-level unit tests where practical. 4. Add idempotent upsert methods. 5. Add sync cursor locking. Acceptance: 1. Install creates tables. 2. Upgrade path is repeatable. 3. Duplicate Talk messages do not create duplicate tasks. ### Phase 3 - Talk workspace discovery Tasks: 1. Implement `TalkApiClient`. 2. Implement `TalkCapabilityService`. 3. Implement `WorkspaceService`. 4. Implement `SyncConversationsJob`. 5. Create `/NuQloud Scrum/{conversation}/` folders. 6. Write `_workspace.md` and `_manifest.json`. Acceptance: 1. Allowed Talk conversations become workspaces. 2. Re-running sync is idempotent. 3. Folder names are sanitized. 4. Immutable Talk token remains the workspace key. ### Phase 4 - thread/task indexing Tasks: 1. Implement `ThreadTaskService`. 2. Implement task root detection. 3. Implement title extraction: - explicit thread title when available - first sentence/message summary fallback - safe length cap 4. Create task folders. 5. Write `_task.md` and `_activity.jsonl`. 6. Track `last_message_id` per workspace. Acceptance: 1. Talk replies create/update task records. 2. Existing reply chains backfill correctly. 3. Generated files escape raw Talk content. 4. Task folders use `{root-message-id}-{slug}`. ### Phase 5 - file mirroring Tasks: 1. Implement `FileIndexService`. 2. Implement `FolderMirrorService`. 3. Resolve root conversation files vs task files. 4. Copy files into workspace/task folders. 5. Deduplicate by source file ID/checksum. 6. Record mirror errors without exposing secrets. Acceptance: 1. Conversation file shares appear under `Files/YYYY-MM/`. 2. Task file shares appear under task `Files/`. 3. Duplicate shares do not create uncontrolled copies. 4. Failed copies remain retryable. ### Phase 6 - Deck board/card mirror Tasks: 1. Implement `DeckApiClient`. 2. Implement `DeckMirrorService`. 3. Create workspace board. 4. Create default stacks. 5. Create/update one card per task. 6. Sync status to stack. 7. Add card links back to Talk/folder. Acceptance: 1. One board exists per workspace. 2. One card exists per task. 3. Moving Deck card updates local task status. 4. Talk remains canonical for task identity. ### Phase 7 - mobile status controls Tasks: 1. Implement `ReactionWorkflowService`. 2. Implement admin-configurable mappings. 3. Implement conflict resolution. 4. Implement bot command parser. 5. Add concise bot replies. Acceptance: 1. Mobile Talk reactions update task status. 2. Bot commands update status/assignee/due date. 3. Invalid commands fail safely. 4. Status changes are mirrored to Deck. ### Phase 8 - summaries Tasks: 1. Implement `SummaryService`. 2. Generate `_summary.md` per task. 3. Generate daily summaries under `Summaries/`. 4. Generate `_open-tasks.md` and `_decisions.md`. 5. Keep AI extraction as optional suggestions only. Acceptance: 1. Summaries are regenerated idempotently. 2. No raw secrets or private auth data are included. 3. Summaries link back to source task folders. ### Phase 9 - hardening and release Tasks: 1. Add repair command/job for missing folders/cards. 2. Add admin sync status dashboard. 3. Add logging with redaction. 4. Add docs for admin setup and user workflow. 5. Add compatibility notes for Nextcloud 32-34 and Talk thread differences. 6. Add rollback notes. Acceptance: 1. Failed syncs are visible and retryable. 2. App degrades gracefully without Deck. 3. App degrades gracefully without native Talk thread metadata. 4. Operators have devcloud verification commands. ## 13. Verification Commands Do not run or simulate Docker/Nextcloud locally. Use devcloud. ```bash docker compose -f docker-compose.devprod.yml --env-file .env.devprod ps docker compose -f docker-compose.devprod.yml --env-file .env.devprod exec app php occ app:list docker compose -f docker-compose.devprod.yml --env-file .env.devprod exec app php occ app:enable nuqloud_scrum docker compose -f docker-compose.devprod.yml --env-file .env.devprod exec app php occ migrations:status nuqloud_scrum docker compose -f docker-compose.devprod.yml --env-file .env.devprod exec app php occ background-job:list | grep -i nuqloud docker compose -f docker-compose.devprod.yml --env-file .env.devprod logs --tail=200 app ``` If using the no-SSL devprod stack: ```bash docker compose -f docker-compose.devprod.nossl.yml --env-file .env.devprod ps docker compose -f docker-compose.devprod.nossl.yml --env-file .env.devprod exec app php occ app:enable nuqloud_scrum docker compose -f docker-compose.devprod.nossl.yml --env-file .env.devprod exec app php occ migrations:status nuqloud_scrum ``` ## 14. Risks 1. Talk native thread payloads may differ by Talk version. - Mitigation: implement reply-chain fallback and API validation spike. 2. Deck API authentication from background jobs needs a clear service-account pattern. - Mitigation: configure a dedicated automation user/app password. 3. Mirroring files can duplicate storage. - Mitigation: admin-selectable `copy|link`, dedupe, size limits. 4. Reaction status conflicts can be ambiguous. - Mitigation: deterministic precedence and admin-configurable mappings. 5. App-to-app internals may change. - Mitigation: isolate Talk/Deck clients behind service interfaces. ## 15. External References 1. Nextcloud Talk chat API: https://nextcloud-talk.readthedocs.io/en/latest/chat/ 2. Nextcloud Deck REST API: https://deck.readthedocs.io/en/latest/API/ 3. Nextcloud migrations: https://docs.nextcloud.com/server/stable/developer_manual/basics/storage/migrations.html 4. Nextcloud developer manual: https://docs.nextcloud.com/server/stable/developer_manual/