feat(iios): generic opaque metadata + manual ticket assign (glue prereqs)
Generic kernel primitives so app backends (e.g. a CRM support glue) can link
their domain to interactions WITHOUT the kernel learning any app vocabulary:
- thread: accept an opaque `metadata` bag on create (merged with membership),
echo it in listThreads, and filter listThreads by `?metadata[key]=value`
(equality on the opaque bag — the kernel never interprets the keys).
- support: accept opaque `metadata` on createTicket/escalate (thread bag
inherited); add SupportService.assignTo — a policy-gated manual assignment
of a ticket to a target actor (POST /v1/support/tickets/:id/assignee).
- SDK @insignia/iios-kernel-client 0.1.2: createThread(metadata),
listThreads({metadata}) filter, createTicket(metadata), escalate(metadata),
assignTicket; ThreadSummary/Ticket expose the opaque bag.
Generic-safety gate holds: no crm/customer/lead in kernel code (opaque values
only). Tests: +2 (metadata create/filter, ticket metadata + assignTo); 31 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -143,6 +143,23 @@ describe('Governed membership + replies (v1.1, policy-enforced)', () => {
|
||||
expect((await s.listThreads(bob))[0]?.unread).toBe(1);
|
||||
});
|
||||
|
||||
it('opaque metadata: stored on create, echoed in listThreads, and a metadata filter narrows the list', async () => {
|
||||
const s = svc();
|
||||
// Two threads with different opaque app attributes — the kernel never interprets these values.
|
||||
await s.openThread(null, alice, { metadata: { source: 'crm-support', crmCustomerId: 'cust_1' } });
|
||||
await s.openThread(null, alice, { metadata: { source: 'other' } });
|
||||
|
||||
const all = await s.listThreads(alice);
|
||||
expect(all).toHaveLength(2);
|
||||
const support = all.find((t) => (t.metadata as { source?: string } | null)?.source === 'crm-support');
|
||||
expect(support?.metadata).toMatchObject({ source: 'crm-support', crmCustomerId: 'cust_1' });
|
||||
|
||||
// Equality filter on the opaque bag returns only the matching thread.
|
||||
const filtered = await s.listThreads(alice, { metadata: { source: 'crm-support' } });
|
||||
expect(filtered).toHaveLength(1);
|
||||
expect(filtered[0]?.metadata).toMatchObject({ crmCustomerId: 'cust_1' });
|
||||
});
|
||||
|
||||
it('a reply stores + returns parentInteractionId; a cross-thread parent is ignored', async () => {
|
||||
const s = gov();
|
||||
const { threadId } = await s.openThread(null, alice, { membership: 'dm' });
|
||||
|
||||
Reference in New Issue
Block a user