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:
-Puter
2026-07-24 20:48:18 +05:30
parent 1324316fee
commit 03fc32bd6d
5 changed files with 1658 additions and 6 deletions

View File

@@ -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).
// -----------------------------------------------------------------