feat(signals): project-scoped Zopu routing loop with secure tools
Add the complete project-scoped Zopu chat, Signal extraction, and work-routing loop: Backend (signalRouting.ts): - Agent-token-gated functions for the full routing cycle: list evidence, create signal, list signals, list active issues, attach signal to issue, create issue from signal, begin issue, get project context, list projects - attachSignalToIssue requires signal.projectId to exist and equal issue.projectId (org equality alone is insufficient) - createIssueFromSignal is idempotent: queries existing attachments before creating, returns created/reused flag - Duplicate attach retries return existing relation without emitting a duplicate event - All functions deny-by-default with agent token validation Schema: - Add signalIssueAttachments table with indexes by signal, issue, and the composite (signalId, issueId) for idempotent lookups Agent tools (tools/signals.ts): - Nine Flue tool definitions bound to the organization-scoped agent instance ID, calling the agent-gated Convex functions - Organization ID never accepted as free input; always from the bound instance Zopu agent (zopu.ts): - Comprehensive routing-loop instructions: assess actionability, identify project, create signal only when actionable, route via attach or create, explain outcome, optionally begin - Wiring of routing tools into the agent definition Tests (signalRouting.test.ts): - Authentication: invalid token rejected on every function - Project scoping: evidence, signals, issues scoped correctly - Idempotency: duplicate attach, duplicate create-from-signal, duplicate signal creation - Attach vs create: both paths verified - Cross-project rejection: signal and issue must be in same project - Org-scoped signal attach rejected (no projectId) - Provenance: exact raw text preserved through routing loop - Begin issue: transition and auth verification
This commit is contained in:
@@ -282,6 +282,23 @@ export default defineSchema({
|
||||
"messageId",
|
||||
]),
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Signal-to-issue attachments. A proper relation that links one Signal
|
||||
// to one ProjectIssue, idempotent by (signalId, issueId). Multiple
|
||||
// Signals may attach to the same ProjectIssue. The relation is scoped
|
||||
// by organization and project to prevent cross-tenant leakage.
|
||||
// -----------------------------------------------------------------
|
||||
signalIssueAttachments: defineTable({
|
||||
signalId: v.id("signals"),
|
||||
issueId: v.id("projectIssues"),
|
||||
organizationId: v.id("organizations"),
|
||||
projectId: v.id("projects"),
|
||||
createdAt: v.number(),
|
||||
})
|
||||
.index("by_signal", ["signalId"])
|
||||
.index("by_issue", ["issueId"])
|
||||
.index("by_signal_and_issue", ["signalId", "issueId"]),
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Flue persistence stores (schema/format version 4).
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user