feat: scope file-event triggers to a specific oCIS space - #31
Conversation
6b07942 to
e9ec23f
Compare
dj4oC
left a comment
There was a problem hiding this comment.
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
idmatches the SSE event'sspaceidin 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 viaGET /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: personalagainst 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 ''forspace_id) follows the exact same pattern already established forpath_prefix/extension, so existing rows correctly default to "any space" (no accidental over-restriction of pre-existing triggers on upgrade). - Traced the frontend's
eventSpaceIdcomputed setter — spreading...filtersbefore overwritingspaceIdcorrectly preservespathPrefix/extensionwhen 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/DriveListerfollows the same thin-interface, dependency-injected-for-testing pattern already used byWorkflowsHandler/TriggerIndexer— consistent, no new pattern introduced.- Explicitly calling out in the PR body that pre-existing workflows with a raw-API-set
spaceIdwill 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
e9ec23f to
39079e5
Compare
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>
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>
Signed-off-by: Lukas Hirt <info@hirt.cz>
Signed-off-by: Lukas Hirt <info@hirt.cz>
39079e5 to
f0e1b11
Compare
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.spaceIdwas already declared in both the frontend type and the backend'sEventFiltersmodel, but was silently dropped before persistence and never matched — setting it via the raw API looked like it worked while doing nothing.ocisclient.ListDrivesproxies oCIS's Graph API (GET /graph/v1.0/me/drives), exposed through a newGET /me/spacesendpoint that filters outdriveType == "virtual"(the aggregate "Shares" pseudo-space).SpaceIDnow correctly persists through the trigger-index storage and is matched in the SSE handler alongside the existing path-prefix/extension filters.Full design rationale:
docs/superpowers/specs/2026-07-24-event-trigger-space-scope-design.mdImplementation plan:
docs/superpowers/plans/2026-07-24-event-trigger-space-scope.mdNote for anyone with an existing workflow that already had
spaceIdset 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
go build ./... && go vet ./... && go test ./...(frombackend/) — all green, including new unit tests for the trigger-index persistence fix and the SSE space-matching logic.go test -tags=e2e ./tests/e2e/..., real oCIS instance):GET /me/spacesreturns 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'sspaceidare the same value in practice, not just in theory.pnpm test:unit && pnpm check:types && pnpm lint— all green.pnpm test:e2e) — all 4 specs green, including an extendedevent-trigger.spec.tsthat picks a space, saves, reloads, and confirms it persisted.🤖 Generated with Claude Code