feat(threads): channel backend — discover (browse), public self-join, leave
The generic primitives channels need beyond dm/group, kept policy-governed and scope-fenced (kernel never interprets 'channel'/'public'): - discoverThreads(principal, filter): scope-wide browse (NOT membership-scoped) matching an opaque metadata filter, each result flagged joined; governed by iios.thread.discover (scope fence in the query). REST: GET /v1/threads/discover - open self-join for PUBLIC channels: openThread now passes visibility into the join decision; dev-OPA iios.thread.join allows membership=channel+visibility= public (dm/group/private-channel stay invite-only) - leaveThread + iios.thread.leave + REST DELETE /v1/threads/:id/me - tests: public self-join vs private denied, discover joined-flags + group excluded + leave; dev-opa join-public/discover/leave rules (29 green) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,18 @@ export class ThreadsController {
|
||||
return this.messages.listMyAnnotated(this.principal(auth), type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover threads across the caller's scope (not just ones they're in) matching an opaque
|
||||
* metadata filter — e.g. `?metadata[membership]=channel&metadata[visibility]=public` to browse
|
||||
* public channels. Each result is flagged `joined`. Declared before `:id` routes so the static
|
||||
* "discover" segment wins.
|
||||
*/
|
||||
@Get('discover')
|
||||
async discover(@Headers('authorization') auth?: string, @Query('metadata') metadata?: Record<string, string>) {
|
||||
const metaFilter = metadata && typeof metadata === 'object' ? metadata : undefined;
|
||||
return this.messages.discoverThreads(this.principal(auth), metaFilter ? { metadata: metaFilter } : undefined);
|
||||
}
|
||||
|
||||
/** Create a thread; `membership`/`creatorRole`/`subject`/`metadata` are opaque, app-supplied attributes the kernel stores but never interprets. */
|
||||
@Post()
|
||||
@HttpCode(201)
|
||||
@@ -79,6 +91,13 @@ export class ThreadsController {
|
||||
return this.messages.renameThread(id, this.principal(auth), body.subject);
|
||||
}
|
||||
|
||||
/** Self-leave: remove the caller from a thread (e.g. leave a channel). */
|
||||
@Delete(':id/me')
|
||||
@HttpCode(200)
|
||||
async leave(@Param('id') id: string, @Headers('authorization') auth?: string) {
|
||||
return this.messages.leaveThread(id, this.principal(auth));
|
||||
}
|
||||
|
||||
@Get(':id/messages')
|
||||
async listMessages(@Param('id') id: string) {
|
||||
return this.threads.getMessages(id);
|
||||
|
||||
Reference in New Issue
Block a user