File size: 17,048 Bytes
46252cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
/**
 * Committed engine capability matrix: for each `IWhatsAppEngine` method, the REAL availability on
 * each adapter — `wwjs` = whatsapp-web.js (the default engine), `baileys` = the browser-free
 * alternative.
 *
 * Two fields tell the story:
 *  - `status`: 'supported' (the capability genuinely works end-to-end) or 'not-available' (the method
 *    either throws `EngineNotSupportedError`/`ChannelMediaNotSupportedError` at the adapter boundary
 *    → HTTP 501, OR the adapter claims support but the underlying library cannot deliver — a
 *    phantom-support case surfaced by source verification, e.g. wwjs catalog methods that log
 *    "not implemented" and return null/[] without throwing).
 *  - `rootCause` (present only when `not-available`): WHY it is not available, so a contributor knows
 *    exactly where to start. Three values:
 *      'adapter-gap'        — the underlying library HAS the capability; only the OpenWA adapter
 *                             wiring is missing. FIXABLE in this repo (a PR that calls the library
 *                             symbol the evidence points at).
 *      'library-limitation' — the underlying library exposes NO first-class symbol for this op. Not
 *                             fixable without a raw-proto/fork effort or an event-cache hack.
 *      'uncertain'          — source trace was inconclusive; needs a live spike.
 *
 * `evidence` cites the library symbol(s) that were inspected, so an engineer can open the exact file
 * and start wiring immediately. REQUIRED when at least one adapter is `not-available`; may also
 * annotate a newly-wired `supported` row with the symbols it now calls.
 *
 * This is a SNAPSHOT. `engine-parity.spec.ts` enforces exact matrix-key↔interface-method correspondence
 * and the throw-invariants it can observe in live adapter method bodies. It does not read the operator
 * documentation and cannot classify non-throwing phantom stubs. The `status`, `rootCause`, and
 * `evidence` fields therefore remain hand-curated, source-traced annotations that must be reviewed as
 * adapters are wired or libraries change.
 *
 * NOTE on phantom support: the drift gate's throw-heuristic cannot see adapter methods that silently
 * stub (return null/[] + a warn log) without throwing. The matrix BELOW is the source-of-truth: three
 * wwjs entries (getCatalog/getProducts/getProduct) are marked `not-available` here even though their
 * adapter bodies do not throw — the library has no API for them, so the adapter stubs. If the drift
 * gate is extended to assert against this matrix, it must consult `status`, not just the throw
 * pattern, for these rows (or the adapter stubs must start throwing). getContactStatus/
 * getContactStatuses were on this list until #714 wired them on whatsapp-web.js; their rows say
 * `supported` and the adapter really does read stories, so they no longer belong here.
 */
export type CapabilityStatus = 'supported' | 'not-available';
export type RootCause = 'adapter-gap' | 'library-limitation' | 'uncertain';

export interface AdapterCapability {
  status: CapabilityStatus;
  /** Present only when `status === 'not-available'`. */
  rootCause?: RootCause;
}

export interface MethodCapability {
  wwjs: AdapterCapability;
  baileys: AdapterCapability;
  /** Cited library symbols (baileys; wwjs). Required when at least one adapter is not-available. */
  evidence?: string;
}

export const ENGINE_CAPABILITY_MATRIX: Record<string, MethodCapability> = {
  addLabelToChat: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  addParticipants: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  blockContact: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  checkNumberExists: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  createGroup: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  deleteChat: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  deleteMessage: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  deleteStatus: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  demoteParticipants: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  destroy: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  disconnect: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  editMessage: {
    wwjs: { status: 'supported' },
    baileys: { status: 'supported' },
    evidence:
      'wwjs Message.edit(content,options?) (index.d.ts:1362; MessageEditOptions:1600); baileys Editable.edit?: WAMessageKey on the text content variant (Types/Message.d.ts:86, AnyRegularMessageContent:168) via sendMessage(jid,{text,edit:key})',
  },
  forceDestroy: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  forwardMessage: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  getCatalog: {
    wwjs: { status: 'not-available', rootCause: 'library-limitation' },
    baileys: { status: 'not-available', rootCause: 'adapter-gap' },
    evidence:
      'baileys Socket/business.d.ts:7 getCatalog({jid,limit,cursor}) + getCollections (business.d.ts:11) — adapter unwired (returns Product[]+cursor, not Catalog metadata; medium-confidence shape synthesis); wwjs index.d.ts has NO Client.getCatalog (0 hits), adapter stubs to null @WhatsAppWebJsAdapter.getCatalog',
  },
  getChannelById: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  getChannelMessages: {
    wwjs: { status: 'supported' },
    baileys: { status: 'not-available', rootCause: 'adapter-gap' },
    evidence:
      'baileys Socket/newsletter.d.ts:19 newsletterFetchMessages(jid,count,since,after) returns RAW BinaryNode of <message_updates> (newsletter.js:149) — adapter unwired AND no exposed library parser (BinaryNode→ChannelMessage mapping is the work); wwjs Channel.fetchMessages (Channel.js:327)',
  },
  getChatHistory: {
    wwjs: { status: 'supported' },
    baileys: { status: 'not-available', rootCause: 'library-limitation' },
    evidence:
      'baileys only fetchMessageHistory(count,oldestKey,oldestTs) (Socket/business.d.ts:25) returns a sync-token string; messages arrive later via messaging-history.set event — no synchronous per-chat fetchMessages; wwjs Chat.fetchMessages (Chat.js)',
  },
  getChatLabels: {
    wwjs: { status: 'supported' },
    baileys: { status: 'not-available', rootCause: 'library-limitation' },
    evidence:
      'baileys no getChatLabels in lib/**/*.d.ts; Types/LabelAssociation.d.ts defines ChatLabelAssociation but no query fn (only addChatLabel/removeChatLabel writes @chats.d.ts:70-71); wwjs Client.getChatLabels (Client.js:2838)',
  },
  getChats: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  getContactById: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  getContactStatus: {
    wwjs: { status: 'supported' },
    baileys: { status: 'not-available', rootCause: 'library-limitation' },
    evidence:
      'baileys fetchStatus (Socket/chats.d.ts:42 via USyncStatusProtocol) = about/profile text only, NOT 24h stories — no story getter in lib',
  },
  getContactStatuses: {
    wwjs: { status: 'supported' },
    baileys: { status: 'not-available', rootCause: 'library-limitation' },
    evidence: 'baileys fetchStatus = about text only; no story enumerate in lib',
  },
  getContacts: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  getGroupInfo: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  getGroupInviteCode: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  getGroups: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  getLabelById: {
    wwjs: { status: 'supported' },
    baileys: { status: 'not-available', rootCause: 'library-limitation' },
    evidence:
      'baileys no getLabel/getLabelById in lib/**/*.d.ts (Types/Label.d.ts has only Label interface + LabelColor enum + LabelActionBody); derivable only from an app-state-sync label cache; wwjs Client.getLabelById (Client.js:2825)',
  },
  getLabels: {
    wwjs: { status: 'supported' },
    baileys: { status: 'not-available', rootCause: 'library-limitation' },
    evidence:
      'baileys no getLabel/fetchLabel in lib/**/*.d.ts; chats.d.ts:69-73 + business.d.ts:162-166 expose ONLY writes; derivable only from an app-state-sync event cache; wwjs Client.getLabels (Client.js:2747)',
  },
  getMessageReactions: {
    wwjs: { status: 'supported' },
    baileys: { status: 'not-available', rootCause: 'library-limitation' },
    evidence:
      'baileys no getReactions/fetchReactions; reactions exist only as event-augmented WAMessage.reactions (proto.IReaction @WAProto/index.d.ts:10623) via messages.reaction event; adapter does not persist them into its store; wwjs Message.getReactions (Message.js)',
  },
  getNumberId: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  getPhoneNumber: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  getProduct: {
    wwjs: { status: 'not-available', rootCause: 'library-limitation' },
    baileys: { status: 'not-available', rootCause: 'adapter-gap' },
    evidence:
      'baileys only getCatalog (Socket/business.d.ts:7); getProduct = getCatalog then find-by-id (compose-and-filter, loads whole page; medium-confidence); wwjs no Client.getProduct — only page-internal getProductMetadata (Utils.js:1253), not a public Client fn; adapter stubs to null @WhatsAppWebJsAdapter.getProduct',
  },
  getProducts: {
    wwjs: { status: 'not-available', rootCause: 'library-limitation' },
    baileys: { status: 'not-available', rootCause: 'adapter-gap' },
    evidence:
      'baileys Socket/business.d.ts:7 getCatalog({jid,limit,cursor}) → {products, nextPageCursor} — adapter unwired; wwjs no Client.getProducts in index.d.ts (0 hits); adapter stubs to empty @WhatsAppWebJsAdapter.getProducts',
  },
  getProfilePicture: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  getPushName: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  getQRCode: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  getStatus: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  getSubscribedChannels: {
    wwjs: { status: 'supported' },
    baileys: { status: 'not-available', rootCause: 'library-limitation' },
    evidence:
      'baileys no enumerate-newsletters fn; all 23 Socket/newsletter.d.ts exports are per-jid (newsletterMetadata requires a key; newsletterSubscribers returns the count of ONE). Only the newsletter EVENT surfaces jids opportunistically (incremental, not list-all); wwjs Client.getChannels (Client.js:1680)',
  },
  initialize: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  joinGroupViaInviteCode: {
    wwjs: { status: 'supported' },
    baileys: { status: 'supported' },
    evidence:
      'wwjs Client.acceptInvite(inviteCode) → res.gid._serialized (index.d.ts:23; Client.js:1836-1844); baileys groupAcceptInvite(code) → string|undefined (Socket/groups.d.ts:25) — undefined mapped to a thrown error',
  },
  leaveGroup: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  logout: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  markUnread: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  postImageStatus: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  postTextStatus: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  postVideoStatus: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  promoteParticipants: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  reactToMessage: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  removeLabelFromChat: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  removeParticipants: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  rejectCall: {
    wwjs: { status: 'supported' },
    baileys: { status: 'supported' },
    evidence:
      "wwjs Call.reject() (index.d.ts:2417) on the live Call cached from the client 'call' event (index.d.ts:643); baileys rejectCall(callId, callFrom) (Socket/messages-recv.d.ts:10) with the raw `from` JID cached from the 'offer' call event (Types/Call.d.ts)",
  },
  replyToMessage: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  requestPairingCode: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  resolveContactPhone: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  revokeGroupInviteCode: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  sendAudioMessage: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  sendCatalog: {
    wwjs: { status: 'not-available', rootCause: 'library-limitation' },
    baileys: { status: 'not-available', rootCause: 'library-limitation' },
    evidence:
      'baileys AnyMessageContent (Types/Message.d.ts:166-210) has no catalog key — only {product} single-product + product_catalog_edit/add/delete CRUD (Socket/business.js:294-362); wwjs no Client.sendCatalog in index.d.ts (0 hits)',
  },
  sendChatState: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  sendContactMessage: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  sendDocumentMessage: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  sendImageMessage: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  sendLocationMessage: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  sendPollMessage: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  sendProduct: {
    wwjs: { status: 'not-available', rootCause: 'library-limitation' },
    baileys: { status: 'not-available', rootCause: 'adapter-gap' },
    evidence:
      'baileys AnyRegularMessageContent {product: WASendableProduct} (Types/Message.d.ts:203) built in messages.js:397 — adapter unwired (2-step: getCatalog lookup for image/title/price THEN sendMessage); wwjs no Client.sendProduct — Product/Order are inbound-only parsers',
  },
  sendSeen: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  sendStickerMessage: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  sendTextMessage: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  sendVideoMessage: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  setGroupDescription: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  setGroupEphemeral: {
    wwjs: { status: 'not-available', rootCause: 'library-limitation' },
    baileys: { status: 'supported' },
    evidence:
      'wwjs 1.34.7 exposes NO ephemeral setter — 0 hits for ephemeral in index.d.ts; only a create-time messageTimer option (Client.js:2371); adapter throws EngineNotSupportedError; baileys groupToggleEphemeral(jid, ephemeralExpiration) (Socket/groups.d.ts:40)',
  },
  setGroupInfoAdminsOnly: {
    wwjs: { status: 'supported' },
    baileys: { status: 'supported' },
    evidence:
      "wwjs GroupChat.setInfoAdminsOnly(adminsOnly?) (index.d.ts:2216; sets groupMetadata.restrict, GroupChat.js:544); baileys groupSettingUpdate(jid, 'locked'|'unlocked') (Socket/groups.d.ts:41)",
  },
  setGroupMessagesAdminsOnly: {
    wwjs: { status: 'supported' },
    baileys: { status: 'supported' },
    evidence:
      "wwjs GroupChat.setMessagesAdminsOnly(adminsOnly?) (index.d.ts:2210; sets groupMetadata.announce, GroupChat.js:513); baileys groupSettingUpdate(jid, 'announcement'|'not_announcement') (Socket/groups.d.ts:41)",
  },
  setGroupSubject: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  setProfileName: {
    wwjs: { status: 'supported' },
    baileys: { status: 'supported' },
    evidence:
      'wwjs Client.setDisplayName(displayName) → boolean (index.d.ts:251; false → adapter throws); baileys updateProfileName(name) (Socket/chats.d.ts:50)',
  },
  setProfilePicture: {
    wwjs: { status: 'supported' },
    baileys: { status: 'supported' },
    evidence:
      'wwjs Client.setProfilePicture(MessageMedia) → boolean (index.d.ts:336; false → adapter throws); baileys updateProfilePicture(ownJid, WAMediaUpload) (Socket/chats.d.ts:44)',
  },
  setProfileStatus: {
    wwjs: { status: 'supported' },
    baileys: { status: 'supported' },
    evidence:
      'wwjs Client.setStatus(status) (index.d.ts:245); baileys updateProfileStatus(status) (Socket/chats.d.ts:49)',
  },
  subscribeToChannel: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  unblockContact: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
  unsubscribeFromChannel: { wwjs: { status: 'supported' }, baileys: { status: 'supported' } },
};