pumuki 6.3.311 → 6.3.312
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +23 -0
- package/integrations/sdd/evidenceScaffold.ts +8 -0
- package/integrations/tdd/contract.ts +1 -0
- package/integrations/tdd/enforcement.ts +53 -11
- package/integrations/tdd/scope.ts +5 -2
- package/integrations/tdd/types.ts +6 -0
- package/package.json +1 -1
- package/scripts/framework-menu-system-notifications-macos-dialog-payload.ts +40 -2
- package/scripts/framework-menu-system-notifications-payloads-blocked.ts +52 -5
package/AGENTS.md
CHANGED
|
@@ -159,6 +159,10 @@ Antes de realizar cualquier accion:
|
|
|
159
159
|
- crear commits atomicos separados o stashes con nombre explicito antes de continuar,
|
|
160
160
|
- no usar un commit masivo para "limpiar" el estado.
|
|
161
161
|
- Antes de cada commit:
|
|
162
|
+
- confirmar RED registrado o justificar que es cambio documental puro;
|
|
163
|
+
- confirmar GREEN con test focal ejecutado;
|
|
164
|
+
- confirmar REFACTOR acotado y gobernado por las skills aplicables;
|
|
165
|
+
- confirmar VERIFY con typecheck/compilacion/build aplicable;
|
|
162
166
|
- revisar `git diff --cached --name-status`,
|
|
163
167
|
- confirmar que todos los archivos staged pertenecen a la misma intencion,
|
|
164
168
|
- dejar fuera del stage cualquier archivo no relacionado,
|
|
@@ -174,9 +178,28 @@ Antes de realizar cualquier accion:
|
|
|
174
178
|
- preservar cambios en commit o stash con nombre explicito,
|
|
175
179
|
- y reportar la evidencia.
|
|
176
180
|
|
|
181
|
+
## Golden Flow TDD / Red-Green-Refactor (flujo principal no negociable)
|
|
182
|
+
- Pumuki es primero un orquestador de flujo seguro de ingenieria: las skills son reglas de calidad dentro de este flujo, no sustituyen el flujo.
|
|
183
|
+
- Todo cambio funcional, bugfix de gate/lifecycle, detector AST/nodal, notificacion, hook, policy o codigo de producto debe seguir obligatoriamente:
|
|
184
|
+
1. `RED`: crear o actualizar primero un test/regresion que falle por el bug o comportamiento esperado.
|
|
185
|
+
2. `GREEN`: tocar el minimo codigo de produccion necesario para hacer pasar ese test.
|
|
186
|
+
3. `REFACTOR`: limpiar solo dentro del alcance de la slice, sin cambiar comportamiento.
|
|
187
|
+
4. `VERIFY`: ejecutar test focal y compilacion/typecheck/build aplicable.
|
|
188
|
+
5. `COMMIT ATOMICO`: cerrar una unica intencion funcional o tecnica.
|
|
189
|
+
- La fase `REFACTOR` debe aplicar las skills de plataforma correspondientes:
|
|
190
|
+
- iOS/Swift/SwiftUI: `ios-enterprise-rules`, `swift-concurrency`, `swiftui-expert-skill`; y `swift-testing-expert`, `core-data-expert` o skills Apple adicionales cuando aplique.
|
|
191
|
+
- Android: `android-enterprise-rules`.
|
|
192
|
+
- Frontend: `frontend-enterprise-rules`.
|
|
193
|
+
- Backend: `backend-enterprise-rules`.
|
|
194
|
+
- Esta prohibido commitear codigo funcional sin test focal ejecutado y verde.
|
|
195
|
+
- Esta prohibido publicar release o repinear consumers si el test focal, typecheck o build aplicable fallan.
|
|
196
|
+
- Si no es tecnicamente posible escribir RED antes de tocar produccion, declarar `STATUS: BLOCKED` o documentar una excepcion explicita y trazable antes de continuar.
|
|
197
|
+
- Cambios documentales puros pueden omitir RED, pero deben mantener diff acotado, trazabilidad y commit atomico.
|
|
198
|
+
|
|
177
199
|
## Gate operativo obligatorio (antes de editar codigo)
|
|
178
200
|
- Declarar internamente las skills aplicables y tratarlas como activas durante TODO el turno.
|
|
179
201
|
- Verificar cumplimiento minimo previo:
|
|
202
|
+
- Golden Flow TDD `RED -> GREEN -> REFACTOR -> VERIFY -> COMMIT ATOMICO` requerido para todo cambio funcional.
|
|
180
203
|
- BDD/TDD requerido por la skill correspondiente.
|
|
181
204
|
- Concurrencia y aislamiento segun `swift-concurrency` cuando haya codigo Swift.
|
|
182
205
|
- Estado/arquitectura/UI segun `swiftui-expert-skill` e `ios-enterprise-rules` cuando aplique iOS/SwiftUI.
|
|
@@ -39,6 +39,10 @@ export type SddEvidenceScaffoldResult = {
|
|
|
39
39
|
status: SddEvidenceScaffoldTestStatus;
|
|
40
40
|
timestamp: string;
|
|
41
41
|
};
|
|
42
|
+
verify: {
|
|
43
|
+
status: SddEvidenceScaffoldTestStatus;
|
|
44
|
+
timestamp: string;
|
|
45
|
+
};
|
|
42
46
|
}>;
|
|
43
47
|
metadata: {
|
|
44
48
|
source: 'pumuki-sdd-evidence';
|
|
@@ -226,6 +230,10 @@ export const runSddEvidenceScaffold = (params?: {
|
|
|
226
230
|
status: testStatus,
|
|
227
231
|
timestamp: generatedAt,
|
|
228
232
|
},
|
|
233
|
+
verify: {
|
|
234
|
+
status: testStatus,
|
|
235
|
+
timestamp: generatedAt,
|
|
236
|
+
},
|
|
229
237
|
},
|
|
230
238
|
],
|
|
231
239
|
metadata: {
|
|
@@ -96,15 +96,21 @@ export const enforceTddBddPolicy = (params: {
|
|
|
96
96
|
public_interface_files: scope.metrics.publicInterfaceFiles,
|
|
97
97
|
},
|
|
98
98
|
},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
99
|
+
evidence: {
|
|
100
|
+
path: '',
|
|
101
|
+
state: 'not_required',
|
|
102
|
+
slices_total: 0,
|
|
103
|
+
slices_valid: 0,
|
|
104
|
+
slices_invalid: 0,
|
|
105
|
+
integrity_ok: true,
|
|
106
|
+
errors: [],
|
|
107
|
+
phases: {
|
|
108
|
+
red: 'missing',
|
|
109
|
+
green: 'missing',
|
|
110
|
+
refactor: 'missing',
|
|
111
|
+
verify: 'missing',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
108
114
|
waiver: {
|
|
109
115
|
applied: false,
|
|
110
116
|
},
|
|
@@ -163,7 +169,7 @@ export const enforceTddBddPolicy = (params: {
|
|
|
163
169
|
ruleId: 'generic_evidence_integrity_required',
|
|
164
170
|
code: 'TDD_BDD_EVIDENCE_MISSING',
|
|
165
171
|
message:
|
|
166
|
-
'
|
|
172
|
+
'Golden Flow evidence is required for functional code changes and was not found. Required phases: RED failed, GREEN passed, REFACTOR passed, VERIFY passed.',
|
|
167
173
|
filePath: evidenceRead.path,
|
|
168
174
|
});
|
|
169
175
|
return {
|
|
@@ -207,6 +213,12 @@ export const enforceTddBddPolicy = (params: {
|
|
|
207
213
|
const sliceFindings: Finding[] = [];
|
|
208
214
|
const seenSliceIds = new Set<string>();
|
|
209
215
|
let validSlices = 0;
|
|
216
|
+
const phaseState: NonNullable<TddBddSnapshot['evidence']['phases']> = {
|
|
217
|
+
red: 'missing',
|
|
218
|
+
green: 'missing',
|
|
219
|
+
refactor: 'missing',
|
|
220
|
+
verify: 'missing',
|
|
221
|
+
};
|
|
210
222
|
|
|
211
223
|
if (evidenceRead.evidence.slices.length === 0) {
|
|
212
224
|
sliceFindings.push(
|
|
@@ -258,6 +270,7 @@ export const enforceTddBddPolicy = (params: {
|
|
|
258
270
|
}
|
|
259
271
|
|
|
260
272
|
if (slice.red.status !== 'failed') {
|
|
273
|
+
phaseState.red = slice.red.status;
|
|
261
274
|
sliceFindings.push(
|
|
262
275
|
buildFinding({
|
|
263
276
|
ruleId: 'generic_tdd_vertical_required',
|
|
@@ -266,8 +279,12 @@ export const enforceTddBddPolicy = (params: {
|
|
|
266
279
|
filePath: evidenceRead.path,
|
|
267
280
|
})
|
|
268
281
|
);
|
|
282
|
+
} else {
|
|
283
|
+
phaseState.red = 'failed';
|
|
269
284
|
}
|
|
270
285
|
|
|
286
|
+
phaseState.green = slice.green.status;
|
|
287
|
+
phaseState.refactor = slice.refactor.status;
|
|
271
288
|
if (slice.green.status !== 'passed' || slice.refactor.status !== 'passed') {
|
|
272
289
|
sliceFindings.push(
|
|
273
290
|
buildFinding({
|
|
@@ -279,18 +296,42 @@ export const enforceTddBddPolicy = (params: {
|
|
|
279
296
|
);
|
|
280
297
|
}
|
|
281
298
|
|
|
299
|
+
if (!slice.verify) {
|
|
300
|
+
sliceFindings.push(
|
|
301
|
+
buildFinding({
|
|
302
|
+
ruleId: 'generic_golden_flow_verify_required',
|
|
303
|
+
code: 'GOLDEN_FLOW_VERIFY_EVIDENCE_MISSING',
|
|
304
|
+
message: `Slice ${slice.id} must include VERIFY passing evidence after REFACTOR.`,
|
|
305
|
+
filePath: evidenceRead.path,
|
|
306
|
+
})
|
|
307
|
+
);
|
|
308
|
+
} else {
|
|
309
|
+
phaseState.verify = slice.verify.status;
|
|
310
|
+
if (slice.verify.status !== 'passed') {
|
|
311
|
+
sliceFindings.push(
|
|
312
|
+
buildFinding({
|
|
313
|
+
ruleId: 'generic_golden_flow_verify_required',
|
|
314
|
+
code: 'GOLDEN_FLOW_VERIFY_MUST_PASS',
|
|
315
|
+
message: `Slice ${slice.id} must finish with VERIFY passing evidence.`,
|
|
316
|
+
filePath: evidenceRead.path,
|
|
317
|
+
})
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
282
322
|
if (
|
|
283
323
|
!isTimelineOrdered([
|
|
284
324
|
slice.red.timestamp,
|
|
285
325
|
slice.green.timestamp,
|
|
286
326
|
slice.refactor.timestamp,
|
|
327
|
+
slice.verify?.timestamp,
|
|
287
328
|
])
|
|
288
329
|
) {
|
|
289
330
|
sliceFindings.push(
|
|
290
331
|
buildFinding({
|
|
291
332
|
ruleId: 'generic_red_green_refactor_enforced',
|
|
292
333
|
code: 'TDD_PHASE_TIMELINE_INVALID',
|
|
293
|
-
message: `Slice ${slice.id} has invalid RED->GREEN->REFACTOR timestamp ordering.`,
|
|
334
|
+
message: `Slice ${slice.id} has invalid RED->GREEN->REFACTOR->VERIFY timestamp ordering.`,
|
|
294
335
|
filePath: evidenceRead.path,
|
|
295
336
|
})
|
|
296
337
|
);
|
|
@@ -320,6 +361,7 @@ export const enforceTddBddPolicy = (params: {
|
|
|
320
361
|
slices_invalid: invalidSlices,
|
|
321
362
|
integrity_ok: evidenceRead.integrity.valid,
|
|
322
363
|
errors: sliceFindings.map((finding) => finding.code),
|
|
364
|
+
phases: phaseState,
|
|
323
365
|
},
|
|
324
366
|
waiver: {
|
|
325
367
|
applied: false,
|
|
@@ -154,6 +154,9 @@ export const classifyTddBddScope = (
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
const reasons: string[] = [];
|
|
157
|
+
if (changedImplementationPaths.size > 0) {
|
|
158
|
+
reasons.push('functional.implementation_changed');
|
|
159
|
+
}
|
|
157
160
|
if (changedImplementationPaths.size > thresholds.maxChangedFiles) {
|
|
158
161
|
reasons.push('complex.changed_files_threshold');
|
|
159
162
|
}
|
|
@@ -168,9 +171,9 @@ export const classifyTddBddScope = (
|
|
|
168
171
|
}
|
|
169
172
|
|
|
170
173
|
const isNewFeature = addedImplementationPaths.size > 0;
|
|
171
|
-
const isComplexChange = reasons.
|
|
174
|
+
const isComplexChange = reasons.some((reason) => reason.startsWith('complex.'));
|
|
172
175
|
return {
|
|
173
|
-
inScope:
|
|
176
|
+
inScope: changedImplementationPaths.size > 0,
|
|
174
177
|
isNewFeature,
|
|
175
178
|
isComplexChange,
|
|
176
179
|
reasons,
|
|
@@ -21,6 +21,12 @@ export type TddBddSnapshot = {
|
|
|
21
21
|
slices_invalid: number;
|
|
22
22
|
integrity_ok: boolean;
|
|
23
23
|
errors: string[];
|
|
24
|
+
phases?: {
|
|
25
|
+
red: 'missing' | 'passed' | 'failed';
|
|
26
|
+
green: 'missing' | 'passed' | 'failed';
|
|
27
|
+
refactor: 'missing' | 'passed' | 'failed';
|
|
28
|
+
verify: 'missing' | 'passed' | 'failed';
|
|
29
|
+
};
|
|
24
30
|
};
|
|
25
31
|
waiver: {
|
|
26
32
|
applied: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.312",
|
|
4
4
|
"description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,10 @@ const buildBlockingCausesDetails = (
|
|
|
27
27
|
? 'Causas bloqueantes: 1'
|
|
28
28
|
: `Causas bloqueantes: ${total}`;
|
|
29
29
|
return resolvePrioritizedBlockingCauses(causes)
|
|
30
|
-
.flatMap((cause, index) =>
|
|
30
|
+
.flatMap((cause, index) => {
|
|
31
|
+
const formatted = formatBlockingCauseForDialog(cause, index);
|
|
32
|
+
return index === 0 ? formatted : ['', ...formatted];
|
|
33
|
+
})
|
|
31
34
|
.reduce((lines, line) => [...lines, line], [header])
|
|
32
35
|
.join('\n');
|
|
33
36
|
};
|
|
@@ -53,6 +56,40 @@ const stripTechnicalFieldsFromMessage = (message: string): string => {
|
|
|
53
56
|
.trim();
|
|
54
57
|
};
|
|
55
58
|
|
|
59
|
+
const extractFieldFromCauseMessage = (message: string, field: string): string | null => {
|
|
60
|
+
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
61
|
+
const match = message.match(new RegExp(`\\b${escapedField}=([^\\n]+?)(?=\\s(?:severity|code|rule|file|lines?|message|remediation|snippet|node|primary_node|missing)=|$)`, 'i'));
|
|
62
|
+
const value = match?.[1]?.trim();
|
|
63
|
+
return value && value.length > 0 ? normalizeNotificationText(value) : null;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const extractSnippetFromCauseMessage = (message: string): string | null =>
|
|
67
|
+
extractFieldFromCauseMessage(message, 'snippet') ??
|
|
68
|
+
extractFieldFromCauseMessage(message, 'node') ??
|
|
69
|
+
extractFieldFromCauseMessage(message, 'primary_node');
|
|
70
|
+
|
|
71
|
+
const extractMissingContractFromCauseMessage = (message: string): string | null => {
|
|
72
|
+
const missing = extractFieldFromCauseMessage(message, 'missing')?.replace(/\]\]$/u, ']');
|
|
73
|
+
if (!missing) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return `Falta: ${missing}`;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const buildEvidenceLine = (
|
|
80
|
+
cause: NonNullable<Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']>[number]
|
|
81
|
+
): string | null => {
|
|
82
|
+
const snippet = extractSnippetFromCauseMessage(cause.message);
|
|
83
|
+
if (snippet) {
|
|
84
|
+
return ` Evidencia: ${truncateNotificationText(snippet, 160)}`;
|
|
85
|
+
}
|
|
86
|
+
const missing = extractMissingContractFromCauseMessage(cause.message);
|
|
87
|
+
if (missing) {
|
|
88
|
+
return ` Evidencia: ${truncateNotificationText(missing, 160)}`;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
};
|
|
92
|
+
|
|
56
93
|
const formatLocation = (cause: NonNullable<Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']>[number]): string => {
|
|
57
94
|
if (!cause.file) {
|
|
58
95
|
return 'sin fichero';
|
|
@@ -95,8 +132,9 @@ const formatBlockingCauseForDialog = (
|
|
|
95
132
|
`${index + 1}. Regla: ${truncateNotificationText(rule, 96)}`,
|
|
96
133
|
` Fichero: ${truncateNotificationText(formatLocation(cause), 120)}`,
|
|
97
134
|
` Viola: ${truncateNotificationText(problem, 160)}`,
|
|
135
|
+
buildEvidenceLine(cause),
|
|
98
136
|
` Solución: ${truncateNotificationText(remediation, 180)}`,
|
|
99
|
-
];
|
|
137
|
+
].filter((line): line is string => typeof line === 'string');
|
|
100
138
|
};
|
|
101
139
|
|
|
102
140
|
const hasOnlyWorktreeHygieneCauses = (
|
|
@@ -4,7 +4,10 @@ import type {
|
|
|
4
4
|
} from './framework-menu-system-notifications-types';
|
|
5
5
|
import { resolveBlockedCauseSummary } from './framework-menu-system-notifications-cause';
|
|
6
6
|
import { resolveBlockedRemediation } from './framework-menu-system-notifications-remediation';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
normalizeNotificationText,
|
|
9
|
+
truncateNotificationText,
|
|
10
|
+
} from './framework-menu-system-notifications-text';
|
|
8
11
|
|
|
9
12
|
export {
|
|
10
13
|
resolveBlockedCauseSummary,
|
|
@@ -37,6 +40,39 @@ const formatCauseLocation = (cause: BlockedCause): string => {
|
|
|
37
40
|
|
|
38
41
|
const formatCauseRule = (cause: BlockedCause): string => cause.ruleId ?? cause.code;
|
|
39
42
|
|
|
43
|
+
const extractMessageField = (message: string, field: string): string | null => {
|
|
44
|
+
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
45
|
+
const match = message.match(new RegExp(`\\b${escapedField}=([^\\n]+?)(?=\\s(?:severity|code|rule|file|lines?|message|remediation|snippet|node|primary_node|missing)=|$)`, 'i'));
|
|
46
|
+
const value = match?.[1]?.trim();
|
|
47
|
+
return value && value.length > 0 ? normalizeNotificationText(value) : null;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const stripTechnicalFieldsFromMessage = (message: string): string =>
|
|
51
|
+
normalizeNotificationText(message)
|
|
52
|
+
.replace(/\bseverity=[A-Z]+\b/gi, '')
|
|
53
|
+
.replace(/\bcode=[A-Z0-9_]+\b/gi, '')
|
|
54
|
+
.replace(/\brule=[^\s]+/gi, '')
|
|
55
|
+
.replace(/\bfile=[^\s]+/gi, '')
|
|
56
|
+
.replace(/\blines?=[0-9][0-9,\-\s]*/gi, '')
|
|
57
|
+
.replace(/\bmessage=/gi, '')
|
|
58
|
+
.replace(/\bremediation=/gi, '')
|
|
59
|
+
.replace(/\s+/g, ' ')
|
|
60
|
+
.trim();
|
|
61
|
+
|
|
62
|
+
const formatCauseProblem = (cause: BlockedCause): string => {
|
|
63
|
+
const messageField = extractMessageField(cause.message, 'message');
|
|
64
|
+
if (messageField) {
|
|
65
|
+
return messageField;
|
|
66
|
+
}
|
|
67
|
+
return stripTechnicalFieldsFromMessage(cause.message) || cause.code;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const formatCauseFix = (cause: BlockedCause): string =>
|
|
71
|
+
normalizeNotificationText(
|
|
72
|
+
cause.remediation ??
|
|
73
|
+
'Corrige la violación indicada con regla, fichero y línea, y vuelve a ejecutar el gate.'
|
|
74
|
+
);
|
|
75
|
+
|
|
40
76
|
export const resolvePrioritizedBlockingCauses = (
|
|
41
77
|
causes: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']
|
|
42
78
|
): ReadonlyArray<BlockedCause> => {
|
|
@@ -66,6 +102,9 @@ const buildBlockingCausesSummary = (
|
|
|
66
102
|
}
|
|
67
103
|
const prefix = isSkillCause(first) ? 'Skill violada' : 'Causa bloqueante';
|
|
68
104
|
const overflow = prioritized.length > 1 ? ` (+${prioritized.length - 1} más)` : '';
|
|
105
|
+
if (isSkillCause(first)) {
|
|
106
|
+
return `${prefix}: ${formatCauseLocation(first)} · ${formatCauseRule(first)}${overflow}`;
|
|
107
|
+
}
|
|
69
108
|
return `${prefix}: ${formatCauseRule(first)} · ${formatCauseLocation(first)}${overflow}`;
|
|
70
109
|
};
|
|
71
110
|
|
|
@@ -80,10 +119,18 @@ const buildBlockingCausesRemediation = (
|
|
|
80
119
|
if (!first) {
|
|
81
120
|
return null;
|
|
82
121
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
122
|
+
if (isSkillCause(first)) {
|
|
123
|
+
return [
|
|
124
|
+
`Regla: ${formatCauseRule(first)}.`,
|
|
125
|
+
`Fichero: ${formatCauseLocation(first)}.`,
|
|
126
|
+
`Viola: ${formatCauseProblem(first)}.`,
|
|
127
|
+
`Solución: ${formatCauseFix(first)}.`,
|
|
128
|
+
prioritized.length > 1 ? `Quedan ${prioritized.length - 1} causa(s) más en el reporte completo.` : '',
|
|
129
|
+
]
|
|
130
|
+
.filter((line) => line.length > 0)
|
|
131
|
+
.join(' ');
|
|
132
|
+
}
|
|
133
|
+
return `${formatCauseFix(first)} Revisa el reporte completo para el resto de causas bloqueantes.`;
|
|
87
134
|
};
|
|
88
135
|
|
|
89
136
|
export const buildGateBlockedPayload = (
|