feat(webhooks): add Deck card sync preset with vector indexing#809
Conversation
Nextcloud Deck PR #7910 added IWebhookCompatibleEvent to CardCreated/ Updated/DeletedEvent and BoardUpdatedEvent, so Deck can finally emit real-time webhooks via core's webhook_listeners app. Wire this into the existing preset → parser → DocumentTask pipeline that already backs Notes / Calendar / Tables / Forms / Files sync. - Add deck_sync preset (app=deck, 4 events) and drop the stale "Deck does not support webhooks" comment. - Teach webhook_parser to convert Deck card events into DocumentTask(doc_type=deck_card, operation=index|delete) with stack_id metadata. BoardUpdatedEvent logs delivery at INFO and returns None — the polling scanner reconciles affected cards. - Cover three new unit tests for the deck create/delete/board-update paths plus symmetric fail-open tests for missing card.id / node.id in _parse_deck_event and _parse_file_event. The astrolabe admin UI auto-discovers the new preset via filter_presets_by_installed_apps(); no astrolabe-side wiring is required for it to appear in the Webhook Management card grid. Note: requires Deck ≥1.18.x (where PR #7910 lands); the preset is hidden when the Deck app isn't installed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Astrolabe (and any other PHP-side client) sends a stable User-Agent on
every outbound call to the MCP server. Capture it at the middleware
layer so backend access logs can attribute each request to a specific
client build — e.g. ``Nextcloud-Astrolabe/0.14.1``.
The middleware fires only for /api/v1/* and /webhooks/nextcloud,
which is the surface PHP-side clients hit; /mcp and /health stay
silent. The structured ``extra`` ({user_agent, http_method, http_path})
flows into OTel spans so the field is queryable in Grafana / Loki.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Claude finished @cbcoutinho's task in 3m 0s —— View job PR Review: feat(webhooks): add Deck card sync preset with vector indexing
Overall this is a clean, well-scoped PR. The design is sound, the comments explain the non-obvious decisions, and the test coverage mirrors what the code actually does. A few items worth addressing before merge: Important
In metadata = {"stack_id": stack_id} if stack_id is not None else NoneBut if (
doc_task.metadata
and "board_id" in doc_task.metadata
and "stack_id" in doc_task.metadata
):Since Two options:
logger.info("%s %s from %s", request.method, path, ua, ...)In a busy deployment — many card creates/updates through MCP tools, regular scanner webhooks — this doubles INFO log traffic for those endpoints. The comment says the goal is to attribute calls to a specific Astrolabe build, but Consider NitsMissing test for The five new tests cover create, delete, board-update, and two fail-open cases — but Deck event class strings duplicated across two modules
What's working well
|
…ith posture preservation Selective merge of 10 upstream commits past pi0n00r/master (da23d51) to upstream tip (25eb2e9). Catches up through tag v0.89.0 (bump commit e4497c4) plus 4 follow-on commits (nextcloud-32 digest renovate + internal upstream merge + the PR cbcoutinho#792 merge commit). Conflict resolution (1): - third_party/astrolabe (modify/delete) — git rm (kept Astrolabe strip; upstream submodule pointer bump 904c793 inapplicable, same shape as the v0.88.3 resolution of 39d70cf) Auto-merge handled cleanly: 9 remaining intersection files, including client/contacts.py, client/vcard_parser.py, client/webdav.py, models/contacts.py, server/contacts.py, server/oauth_tools.py — no Class-1 surface required ours-vs-theirs resolution. Upstream additions absorbed (dormant by configuration): - server/webhook_presets.py + vector/webhook_parser.py (PR cbcoutinho#809 Deck card sync preset with vector indexing; preset added to catalog, only fires if registered; vector path inactive — no Qdrant) - feat(api): log inbound User-Agent on management API and webhook receiver (local logging only, no phone-home; management routes dormant in single_user_basic, no webhooks registered against MCP) - tests/unit/test_webhook_endpoint.py + test_webhook_presets.py (new upstream tests for the above) - chore(deps): nextcloud-32 docker tag and digest renovate bumps (upstream compose, not our bridgette overlay) Posture verification (paragraph 6 gauntlet, all PASS): - oauth_tools.py: 0 Astrolabe refs, 4 get_shared_storage, 0 Optional[ - models/contacts.py: 5 typed fields present - vcard_parser.py: present (441 lines) - auth/astrolabe_client.py, third_party/astrolabe, CLA.md, astrolabe.svg, both app-hooks scripts: all absent - .gitmodules: 0 astrolabe entries (only oidc and notes) - AGENTS.md and LICENSE.md (7 CLA/AGPL refs): present - pyproject.toml version: 0.89.0 - py_compile: 9/9 critical files clean (incl. new webhook_presets and vector/webhook_parser) Single_user_basic posture, ENABLE_LOGIN_FLOW=false dormancy, and no-Qdrant runtime all preserved. Multi-user gate: no commit in the range introduces multi-user surfaces. No new Tier-1 Astrolabe landmines. Safety tag: pre-upstream-merge-10-da23d512. Note: commit message amended 2026-05-25 post-push after initial commit used a stale /tmp/merge-msg.txt referencing v0.88.3. The tree content and merge structure were correct; only the commit message string was wrong. Amended via git commit --amend; tag v0.89.0 force-recreated at the new commit SHA; force-pushed master + tag per Lesson 3.
The prior commit b7bd18b is the v0.88.3 → v0.89.0 selective merge but its header line incorrectly reads "Merge upstream cbcoutinho/master at v0.88.3". This was an authoring slip: the commit-message file at /tmp/merge-msg.txt was left over from the prior cycle (v0.86.4 → v0.88.3, da23d51), the scp of the corrected message file failed silently due to a sandbox-side path issue, and git fell back to the stale template. Tree content of b7bd18b is correct (the actual v0.89.0 merge): - merge-base: 25eb2e9 (upstream PR cbcoutinho#792 merge tip) - 10 upstream commits absorbed: Astrolabe submodule pointer bump (resolved git rm), Deck webhook preset + vector indexing PR cbcoutinho#809 (dormant), User-Agent logging feat(api) (dormant management routes), test fixtures, nextcloud-32:32.0.9 docker digest renovate, version bump 0.88.3 → 0.89.0. - §6 grep gauntlet: ALL PASS at the time of merge. - pyproject.toml version: 0.89.0 - py_compile: 9/9 critical files clean. Resolution choice: leave b7bd18b in history with its wrong header string (branch protection blocked the obvious force-push amend), and add this no-op correction commit on top so future readers grepping for "v0.89.0" find both the merge and the explanatory note. The v0.89.0 git tag points at b7bd18b (the merge commit on this branch lineage). Audit trail for the slip is recorded in Documents/Projects/NC/v0.86.3-merge-notes.md Cat L. No tree change. --allow-empty.



Summary
deck_syncwebhook preset and teacheswebhook_parserto convert Deck card events intoDocumentTask(doc_type="deck_card", …)so Deck cards get indexed into Qdrant in real time instead of waiting for the polling scanner.User-Agenton every/api/v1/*and/webhooks/nextcloudrequest, so backend access logs can attribute calls to a specific Astrolabe build (e.g.Nextcloud-Astrolabe/0.14.1).Context
Nextcloud Deck PR #7910 added
IWebhookCompatibleEventtoCardCreated/Updated/DeletedEventandBoardUpdatedEvent, undoing the stale "Deck does not support webhooks" comment inwebhook_presets.py. The preset library, parser, and processor were already designed for this — only the wiring was missing. The astrolabe-side change (paired PR cbcoutinho/astrolabe#TBD) consumes the new preset viafilter_presets_by_installed_appsand stamps theNextcloud-Astrolabe/<version>UA on its outbound calls.BoardUpdatedEventcarries onlyboardId(no card list), so the parser logs delivery at INFO and returnsNone; the polling scanner reconciles the affected cards.Caveats
IWebhookCompatibleEventis inv1.18.0-beta.*but not yet in any stable release. The preset is hidden byfilter_presets_by_installed_appswhen the Deck app isn't installed, but admins enabling it against an older Deck will get a 400 fromwebhook_listeners("not an event class compatible with webhooks").Test plan
uv run pytest tests/unit/test_webhook_endpoint.py tests/unit/test_webhook_presets.py -v— 5 new tests pass (deck create/delete/board-update + symmetric fail-open tests for missing card.id / node.id)uv run ruff check && uv run ruff format --check && uv run ty check -- nextcloud_mcp_servermcp-login-flow:ACardEvent.php+BoardUpdatedEvent.php(PR #7910 source)mcp__nextcloud-stdio__deck_*Webhook queued deck_card_7 (index|delete)+Indexed/Deleted deck_card_7 for adminin MCP logsNextcloud-Astrolabe/0.14.1UA on every astrolabe-originated/api/v1/*requestThis PR was generated with the help of AI, and reviewed by a Human