fix(platform): answer the stubbed chat search legs - #3147
Conversation
24b8dc2 to
3da8a6a
Compare
|
Rebased onto current Migration renumbered 0064 → 0070. Verified against a real Postgres and MinIO: 377/382, five failures — two The three added checks all pass, and the mail listing proves the property that mattered: So the admin sees the unassigned triage row, the plain member sees only their team's and their own, and both the non-member and the stranger see nothing. One benign log line to expect in the output: |
Three legs the port left returning nothing, each under a comment saying no
0.5 table existed. All three tables existed.
kind="website" answered []. Migration 0045 created app.websites with every
column 0.4's reader projected, and listWebsites already existed.
kind="mail-attachment" answered {attachments: [], truncated: false}, while
migration 0037 shipped conversation_id and mail_received_at_ms and the email
binder writes both. truncated:false means "the walk reached the end of the
mail index", so the stub was also making a false claim. The new reader gates
every row on conversationAssignmentAllows against the conversation's current
assignment, resolving the caller's role and team ids rather than taking them
as arguments — a listing that skipped that would serve the whole inbox.
Migration 0070 adds the arrival index. The only existing index leads with
conversation_id, so it orders by conversation, which is the shape #3035
recorded as rejected: a by-arrival page reads most of its budget to return
one row, and above the budget reports "most recent" from an arbitrary window.
The task and project legs also lost their zero-hit listing fallback, leaving
listedSource unreachable. Restored in both.
Adds the reachability gate the chat map never had: every internal ref
reachable from the turn now needs a handler, using the walker the sandbox
bridge already had rather than a second copy. That absence is how #3112's
and #3128's missing handlers shipped.
3da8a6a to
a5e7568
Compare
Three chat search legs returned nothing, each under a comment saying no 0.5 table existed. All three tables existed.
Stacked on #3123 — review that first. Refs #3142.
Why
kind="website"app.websiteswith every column 0.4's reader projected, andlistWebsitesalready existedkind="mail-attachment"conversation_idandmail_received_at_ms, and the email binder writes bothBoth kinds are advertised to the model on every turn — the tool description says
kind="web-page" cannot be listed — search it, or list kind="website". The website stub killed the search leg too.The mail stub also made a false claim:
truncated: falseis defined by the 0.4 contract as "the walk reached the end of the mail index", and it was returning that while listing nothing.Separately, the task and project legs lost their zero-hit listing fallback. 0.4, on a text match that returned nothing, fell back to a 15-row listing and stamped
listed: true— "Nothing matched the words. Answer the question the words were describing." #3029's own table promises it stays byte-identical. 0.5 returnedlisted: args.list === true, always false on a search, which leftlistedSourceunreachable code.What changed
A real mail-attachment reader in
domains/file_metadata/mail-attachments.ts. Every row is gated onconversationAssignmentAllowsagainst the conversation's current assignment, with the caller's role and team ids resolved rather than accepted as arguments, anddisableddenied. A listing that skipped that would serve the whole inbox.truncatednow reports the truth.Migration
0064adds(org_id, mail_received_at_ms DESC, id DESC) WHERE mail_received_at_ms IS NOT NULL. The only existing index leads withconversation_id, so it orders by conversation — the shape #3035 recorded as rejected, because a by-arrival page then reads most of its budget to return one row and, above the budget, reports "most recent" from an arbitrary window.idis in the index so the tiebreak inside one millisecond is index-ordered: which rows a bounded listing keeps is part of its answer.Numbered 0064 to clear 0059 (#3130), 0060 (#3131) and 0062 (#3135). The boot runner applies by filename order and needs no contiguity.
The website leg calls the existing
listWebsites. The two work legs get their fallback back.The guard that would have caught all three
backend/domains/chat/had no test file. The sandbox bridge has exactly the right one — a walker over its import graph requiring a handler for every reachableinternal.*name. That absence is how #3112's and #3128's missing handlers shipped.The walker is now extracted to
backend/lib/ctx-shim-reachability.tsand used by both maps, rather than copied.Tests
queryContacts (called from core/chat/assistant_tools.ts)Plus integration coverage for the three legs and the mail listing's privacy: an admin sees an unassigned conversation's attachment, a plain member sees only their own or their team's, a non-member sees nothing.
Scope
Does not index anything.
kind="mail-attachment"lists attachments and reportsindexed: falseper row; making their content searchable is #3121, which is a different mechanism and needs the retrieval gate #3141 lands first.Gate:
typecheck,oxlint --type-aware,oxfmt --checkgreen; the two shim suites 7/7.