fix(shortcut): fix projection-data-loss silent-empty returns

Several read shortcuts returned an empty list with exit 0 and no error
envelope even though the underlying MCP tool returned data, so agents misread
"no data" and made wrong decisions.

Root causes:
- Container key mismatch: the resolver probed the wrong key —
  processCodeList / values / wikiSpaces / itemList / groupList / recentItems /
  emailAccounts / deptUserList / labelUserList / roles / report_list, plus
  get_org_labels grouped labels[] needing a descend.
- Item fields nested under a VO wrapper, not unwrapped: shiftVO / entityVO /
  userInfo.
- Param exceeded a backend limit: todo +created-todos sent pageSize=50 while
  the backend silently returns empty for pageSize>20; now uses the shared pager
  (pageSize=20).

Affected: contact/oa/wiki/drive/minutes/calendar/attendance/chat/report/smart
resolvers. Every fix ships a guard test that feeds the real backend response
shape (and, for minutes, both the taskUuid and minutesId item shapes) and
asserts the projection is non-empty with a usable id.

scripts/shortcut_real_result.py now compares the upper (projection) output
against the lower (raw backend) layer, and record_real_shortcut_run.py captures
the lower layer in memory (persisting only derived counts, never raw PII) so an
exit-0 empty projection over a non-empty backend is scored as
projection-data-loss instead of real-ok. The Python self-test runs in CI via
test/scripts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
DennisandClaude Opus 4.8 committed 2026-07-24 10:07:02 +08:00
1 parent 9f76c1844a
commit b1e7b43f85
28 files changed
+1418 -45

No files matched your search

+4 -1
View File
@@ -494,7 +494,10 @@ func roomGroupsProject(data map[string]any) []map[string]any {
// roomGroupsContainer locates the group slice across candidate wrapper keys,
// unwrapping one nested object layer (e.g. result.list) when needed.
func roomGroupsContainer(data map[string]any) []any {
keys := []string{"result", "data", "list", "items", "groups"}
// list_meeting_room_groups nests the groups under result.groupList;
// "groupList" MUST be probed or +room-groups silently returns empty despite
// the backend returning meeting-room groups.
keys := []string{"result", "data", "list", "items", "groupList", "groups"}
for _, k := range keys {
v, ok := data[k]
if !ok {