Skip to content

feat: scope file-event triggers to a specific oCIS space - #31

Open
LukasHirt wants to merge 10 commits into
mainfrom
feature/event-trigger-space-scope
Open

feat: scope file-event triggers to a specific oCIS space#31
LukasHirt wants to merge 10 commits into
mainfrom
feature/event-trigger-space-scope

Conversation

@LukasHirt

Copy link
Copy Markdown
Collaborator

Summary

Lets a user optionally restrict a file-event trigger (upload/move/share/lock) to fire only for events originating in one specific oCIS space, instead of matching across every space they have access to.

This closes a real pre-existing bug: event.filters.spaceId was already declared in both the frontend type and the backend's EventFilters model, but was silently dropped before persistence and never matched — setting it via the raw API looked like it worked while doing nothing.

  • Backend: ocisclient.ListDrives proxies oCIS's Graph API (GET /graph/v1.0/me/drives), exposed through a new GET /me/spaces endpoint that filters out driveType == "virtual" (the aggregate "Shares" pseudo-space). SpaceID now correctly persists through the trigger-index storage and is matched in the SSE handler alongside the existing path-prefix/extension filters.
  • Frontend: the event-trigger config panel gets a "Space (optional)" dropdown, populated from the real endpoint, defaulting to "Any space."

Full design rationale: docs/superpowers/specs/2026-07-24-event-trigger-space-scope-design.md
Implementation plan: docs/superpowers/plans/2026-07-24-event-trigger-space-scope.md

Note for anyone with an existing workflow that already had spaceId set via the raw API: it was previously silently ignored (matching all spaces); after this merges, it will start being enforced the next time that workflow is saved. This is the intended fix, not a regression.

Test plan

  • Backend: go build ./... && go vet ./... && go test ./... (from backend/) — all green, including new unit tests for the trigger-index persistence fix and the SSE space-matching logic.
  • Backend e2e (go test -tags=e2e ./tests/e2e/..., real oCIS instance): GET /me/spaces returns real space data, and a real upload correctly fires a workflow scoped to the exact space it landed in — proving the Graph API's drive id and the SSE event's spaceid are the same value in practice, not just in theory.
  • Frontend: pnpm test:unit && pnpm check:types && pnpm lint — all green.
  • Frontend e2e (pnpm test:e2e) — all 4 specs green, including an extended event-trigger.spec.ts that picks a space, saves, reloads, and confirms it persisted.

🤖 Generated with Claude Code

@LukasHirt
LukasHirt requested a review from a team as a code owner July 24, 2026 16:50
@LukasHirt
LukasHirt force-pushed the feature/event-trigger-space-scope branch 2 times, most recently from 6b07942 to e9ec23f Compare July 27, 2026 14:13

@dj4oC dj4oC left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: feat: scope file-event triggers to a specific oCIS space

Stats: +1938/-17 across 20 files (most of the line count is two docs/plan files; the actual code diff is modest)

Overview

Closes a real, verifiable pre-existing bug: EventFilters.SpaceID/spaceId and the SSE payload's spaceid both already existed in the model before this PR, but syncTriggerIndex never copied SpaceID into the trigger index, and sse/manager.go's filter loop never checked it — so setting it via the raw API silently did nothing. I confirmed this directly against main: model.EventFilters already declares SpaceID string \json:"spaceId,omitempty"`andsse.eventPayloadalready parsesSpaceIDfrom the SSE JSON (and already uses it for path resolution viaItemPath`) — so the "declared but dropped" framing in the PR body checks out, this isn't just the PR's own characterization of its own bug.

What I verified

  • The core claim that Graph's drive id matches the SSE event's spaceid in practice is the one thing that could quietly not work despite compiling fine (format/casing mismatch between the two). The PR backs this with a real e2e test (TestEventTriggeredWorkflowRespectsMatchingSpaceScope) that fetches a real space id via GET /me/spaces, scopes a trigger to it, uploads a real file, and confirms the workflow fires — against a live oCIS instance, not a mock. That's exactly the right way to de-risk this specific claim, and it's a meaningfully more rigorous verification step than some other recent PRs in this batch have done for comparable "does this actually match the real API" questions.
  • Cross-checked driveType: personal against the public libre-graph OpenAPI spec — consistent with what this PR assumes; I couldn't independently corroborate the "virtual" value the same way (it doesn't appear in that spec's static examples), but combined with the PR's own live-instance verification and its dedicated e2e test, I'm not flagging this as a real risk.
  • The SQLite migration (ALTER TABLE ... DEFAULT '' for space_id) follows the exact same pattern already established for path_prefix/extension, so existing rows correctly default to "any space" (no accidental over-restriction of pre-existing triggers on upgrade).
  • Traced the frontend's eventSpaceId computed setter — spreading ...filters before overwriting spaceId correctly preserves pathPrefix/extension when a space is selected/cleared, and setting "" for "Any space" matches the backend's "empty means unfiltered" convention exactly.

Minor observation

WorkflowBuilder.vue's loadSpaces() fails completely silently (empty catch {}, deliberately, per its own comment) if GET /me/spaces errors. I traced through what actually happens to an existing workflow whose trigger already has a specific spaceId set, opened while spaces fail to load: the <select> won't have an <option> for that id, so it'll display as if "Any space" were selected — but since eventSpaceId is a computed with an explicit getter/setter, the underlying data.event.filters.spaceId value isn't actually touched unless the user interacts with the control (Vue doesn't fire the setter just because the visual selection couldn't match). So this isn't silent data loss, just a confusing "your setting looks reset when it isn't" moment if oCIS hiccups at exactly the wrong time — low severity, but a one-line inline error would be more honest than looking identical to "you have no space filter."

Cross-PR note — this is the one worth planning around

NodeDetailsPanel.vue is now being modified by six different currently-open PRs in this batch: #22 (adds required nodes/edges props + a warning banner), #23 (output-hint section), #26 (share/role fields), #27 (condition fields, plus hides the legacy condition field on condition nodes), #29 (extractText output-variable field), and now #31 (the space dropdown). That's a meaningfully higher collision surface than the usual two-PRs-touch-the-same-file case I've flagged elsewhere in this batch — six independent sets of new template blocks and new computeds all landing in roughly the same few dozen lines. Worth specifically noting: #22 adds nodes/edges as required (non-optional) props, which means every other PR's test file that mounts NodeDetailsPanel with just { node } (or { node, spaces: [] } as this PR's own test does) will need updating once #22 merges — so merging #22 first, then rebasing the rest, would avoid the most disruptive part of an eventual six-way reconciliation.

Code quality & style

  • SpacesHandler/DriveLister follows the same thin-interface, dependency-injected-for-testing pattern already used by WorkflowsHandler/TriggerIndexer — consistent, no new pattern introduced.
  • Explicitly calling out in the PR body that pre-existing workflows with a raw-API-set spaceId will go from "silently ignored" to "enforced" on next save is exactly the right thing to surface before merge — a real behavior change for anyone who used the undocumented field, called out rather than left for someone to discover the hard way.
  • The large embedded plan/design docs (docs/superpowers/plans/..., docs/superpowers/specs/...) account for the bulk of this diff's line count but aren't code — didn't review them as implementation, just confirmed the actual code matches what they describe.

Test coverage

Strong and appropriately layered: unit tests for the new SSE space-matching (TestHandleEventSkipsNonMatchingSpace/TestHandleEventMatchesSpecificSpace) cover the matching logic in isolation with fakes, while the e2e suite (both backend and frontend) proves the real id-format compatibility question and the UI's persist-reload round-trip. The one gap — no e2e case uploading to a different space and confirming the trigger does not fire — is reasonably explained by the dev stack only having one real space per test user; the unit test already covers that negative case with fakes, so this isn't a meaningful hole.

Summary

Solid bug fix, well-verified against the one claim that mattered most (Graph drive id ≡ SSE spaceid), with good migration hygiene and an honest callout of the behavior change for existing raw-API users. Nothing here blocks merge; the NodeDetailsPanel.vue six-way collision is worth a quick look at merge order across this batch, not a fix to this PR itself.


🤖 Generated with Claude Code

LukasHirt and others added 10 commits July 29, 2026 11:19
Wires up the existing but dead event.filters.spaceId field end to
end: a new /me/spaces endpoint backed by oCIS's Graph API, persisting
SpaceID through the trigger index, matching it in the SSE handler,
and a space picker in the trigger config UI.

Signed-off-by: Lukas Hirt <info@hirt.cz>
Eight-task plan: oCIS client + API model, the /me/spaces endpoint,
persisting SpaceID through the trigger index (closing an existing
bug where it's silently dropped), SSE-handler matching, backend and
frontend e2e coverage, and the space picker UI, per the approved
design spec.

Signed-off-by: Lukas Hirt <info@hirt.cz>
Signed-off-by: Lukas Hirt <info@hirt.cz>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Lukas Hirt <info@hirt.cz>
Signed-off-by: Lukas Hirt <info@hirt.cz>
… triggers

Signed-off-by: Lukas Hirt <info@hirt.cz>
Signed-off-by: Lukas Hirt <info@hirt.cz>
@LukasHirt
LukasHirt force-pushed the feature/event-trigger-space-scope branch from 39079e5 to f0e1b11 Compare July 29, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants