ponch-mcp-server 1.0.72 → 1.0.74
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/dist/index.js +142 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3129,7 +3129,38 @@ var MartinContractSchema = import_zod28.z.object({
|
|
|
3129
3129
|
extractChanges: ExtractChangesSchema.optional(),
|
|
3130
3130
|
// Governance
|
|
3131
3131
|
quotasConsumed: import_zod28.z.array(import_zod28.z.string()),
|
|
3132
|
-
|
|
3132
|
+
// ── PermissionScope (HITO 6 A6) ────────────────────────────
|
|
3133
|
+
// Cada contract declara EXPLÍCITAMENTE su scope de acceso.
|
|
3134
|
+
// Sin paths legacy. Sin opcionales. Un solo modelo.
|
|
3135
|
+
//
|
|
3136
|
+
// 'module' — Tool de un módulo del negocio (marketing, compras, etc.)
|
|
3137
|
+
// Requiere permissionKey + permissionAction. El wrapper
|
|
3138
|
+
// consulta permisosPorModulo del rol del usuario.
|
|
3139
|
+
//
|
|
3140
|
+
// 'self' — Tool sobre datos del propio usuario (memorias, rutinas
|
|
3141
|
+
// personales con Martin). El wrapper solo verifica que
|
|
3142
|
+
// el usuario esté logueado en el tenant. El helper
|
|
3143
|
+
// individual valida que el doc pertenezca al uid del
|
|
3144
|
+
// usuario (doc.uid === ctx.user.uid).
|
|
3145
|
+
//
|
|
3146
|
+
// 'saas' — Tool del admin del SaaS (deleteTenant, suspendTenant).
|
|
3147
|
+
// Solo super_admin pasa. Validación tenant-ownership la
|
|
3148
|
+
// hace el helper internamente.
|
|
3149
|
+
permissionScope: import_zod28.z.enum(["module", "self", "saas"]),
|
|
3150
|
+
/**
|
|
3151
|
+
* Key del módulo. Solo si scope === 'module'.
|
|
3152
|
+
* Ejemplos: 'marketing', 'mural', 'analytics', 'compras'.
|
|
3153
|
+
* Coincide con keys que el sistema ya usa en
|
|
3154
|
+
* `configuracion/{tenantId}_roles.{roleId}.permisosPorModulo`.
|
|
3155
|
+
*/
|
|
3156
|
+
permissionKey: import_zod28.z.string().min(1).optional(),
|
|
3157
|
+
/**
|
|
3158
|
+
* Nivel mínimo requerido. Solo si scope === 'module'.
|
|
3159
|
+
* - 'ver' → lectura
|
|
3160
|
+
* - 'editar' → mutación reversible
|
|
3161
|
+
* - 'completo' → destructivos / publicación externa
|
|
3162
|
+
*/
|
|
3163
|
+
permissionAction: import_zod28.z.enum(["ver", "editar", "completo"]).optional(),
|
|
3133
3164
|
sideEffects: import_zod28.z.array(SideEffectEnum),
|
|
3134
3165
|
// Disabled state declaration (HITO 6).
|
|
3135
3166
|
// Si el helper PUEDE retornar { disabled: true, code }, el author
|
|
@@ -3174,6 +3205,37 @@ var MartinContractSchema = import_zod28.z.object({
|
|
|
3174
3205
|
path: ["requiresConfirmation"]
|
|
3175
3206
|
});
|
|
3176
3207
|
}
|
|
3208
|
+
if (contract.permissionScope === "module") {
|
|
3209
|
+
if (!contract.permissionKey) {
|
|
3210
|
+
ctx.addIssue({
|
|
3211
|
+
code: "custom",
|
|
3212
|
+
message: `Contract "${contract.name}" scope='module' requiere permissionKey.`,
|
|
3213
|
+
path: ["permissionKey"]
|
|
3214
|
+
});
|
|
3215
|
+
}
|
|
3216
|
+
if (!contract.permissionAction) {
|
|
3217
|
+
ctx.addIssue({
|
|
3218
|
+
code: "custom",
|
|
3219
|
+
message: `Contract "${contract.name}" scope='module' requiere permissionAction.`,
|
|
3220
|
+
path: ["permissionAction"]
|
|
3221
|
+
});
|
|
3222
|
+
}
|
|
3223
|
+
} else {
|
|
3224
|
+
if (contract.permissionKey) {
|
|
3225
|
+
ctx.addIssue({
|
|
3226
|
+
code: "custom",
|
|
3227
|
+
message: `Contract "${contract.name}" scope='${contract.permissionScope}' no debe declarar permissionKey (solo aplica a scope='module').`,
|
|
3228
|
+
path: ["permissionKey"]
|
|
3229
|
+
});
|
|
3230
|
+
}
|
|
3231
|
+
if (contract.permissionAction) {
|
|
3232
|
+
ctx.addIssue({
|
|
3233
|
+
code: "custom",
|
|
3234
|
+
message: `Contract "${contract.name}" scope='${contract.permissionScope}' no debe declarar permissionAction (solo aplica a scope='module').`,
|
|
3235
|
+
path: ["permissionAction"]
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3177
3239
|
});
|
|
3178
3240
|
var ParamsSchema = import_zod27.z.object({
|
|
3179
3241
|
tenantId: import_zod27.z.string().min(1),
|
|
@@ -3224,7 +3286,9 @@ var rawContract = {
|
|
|
3224
3286
|
after: output.ok ? { plan: input.plan } : null
|
|
3225
3287
|
}),
|
|
3226
3288
|
quotasConsumed: [],
|
|
3227
|
-
|
|
3289
|
+
permissionScope: "module",
|
|
3290
|
+
permissionKey: "marketing",
|
|
3291
|
+
permissionAction: "editar",
|
|
3228
3292
|
sideEffects: ["writes_firestore", "updates_brand_config"]
|
|
3229
3293
|
};
|
|
3230
3294
|
var planWriterSaveContract = MartinContractSchema.parse(
|
|
@@ -3798,7 +3862,9 @@ var rawContract2 = {
|
|
|
3798
3862
|
} : null
|
|
3799
3863
|
}),
|
|
3800
3864
|
quotasConsumed: [],
|
|
3801
|
-
|
|
3865
|
+
permissionScope: "module",
|
|
3866
|
+
permissionKey: "marketing",
|
|
3867
|
+
permissionAction: "editar",
|
|
3802
3868
|
sideEffects: ["writes_firestore", "updates_calendar_slot"]
|
|
3803
3869
|
};
|
|
3804
3870
|
var calendarSlotUpdaterContract = MartinContractSchema.parse(
|
|
@@ -3856,7 +3922,9 @@ var rawContract3 = {
|
|
|
3856
3922
|
// AUDITA SIEMPRE — regla A5. Lectura no muta, changes son null/null.
|
|
3857
3923
|
auditAction: "marketing.calendario.leer",
|
|
3858
3924
|
quotasConsumed: [],
|
|
3859
|
-
|
|
3925
|
+
permissionScope: "module",
|
|
3926
|
+
permissionKey: "marketing",
|
|
3927
|
+
permissionAction: "ver",
|
|
3860
3928
|
sideEffects: ["reads_firestore"]
|
|
3861
3929
|
};
|
|
3862
3930
|
var getCalendarContract = MartinContractSchema.parse(
|
|
@@ -4191,7 +4259,9 @@ var rawContract4 = {
|
|
|
4191
4259
|
auditAction: "marketing.brand_brief.preparar",
|
|
4192
4260
|
// No extractTargetPath/extractChanges — no es writer.
|
|
4193
4261
|
quotasConsumed: [],
|
|
4194
|
-
|
|
4262
|
+
permissionScope: "module",
|
|
4263
|
+
permissionKey: "marketing",
|
|
4264
|
+
permissionAction: "ver",
|
|
4195
4265
|
sideEffects: ["reads_firestore"]
|
|
4196
4266
|
};
|
|
4197
4267
|
var brandBriefBuilderContract = MartinContractSchema.parse(
|
|
@@ -6259,24 +6329,68 @@ function getWrapperMessage(key, locale) {
|
|
|
6259
6329
|
}
|
|
6260
6330
|
return msg;
|
|
6261
6331
|
}
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
return
|
|
6332
|
+
var NIVELES_ORDER = ["ninguno", "ver", "editar", "completo"];
|
|
6333
|
+
function nivelAlcanza(nivel, accion) {
|
|
6334
|
+
return NIVELES_ORDER.indexOf(nivel) >= NIVELES_ORDER.indexOf(accion);
|
|
6265
6335
|
}
|
|
6266
|
-
function wrapWithContract(contract, helper) {
|
|
6336
|
+
function wrapWithContract(contract, helper, options = {}) {
|
|
6267
6337
|
return async function wrappedTool(input, ctx) {
|
|
6268
6338
|
const startMs = Date.now();
|
|
6269
6339
|
const locale = ctx.user.idiomaPreferido;
|
|
6270
|
-
|
|
6340
|
+
let accesoOk = false;
|
|
6341
|
+
let reqDesc = "";
|
|
6342
|
+
switch (contract.permissionScope) {
|
|
6343
|
+
case "module": {
|
|
6344
|
+
if (!contract.permissionKey || !contract.permissionAction) {
|
|
6345
|
+
throw new Error(
|
|
6346
|
+
`Wrapper: contract "${contract.name}" scope='module' sin permissionKey/permissionAction. Schema debi\xF3 validar esto.`
|
|
6347
|
+
);
|
|
6348
|
+
}
|
|
6349
|
+
if (!options.permissionResolver) {
|
|
6350
|
+
throw new Error(
|
|
6351
|
+
`Wrapper: contract "${contract.name}" scope='module' pero el caller no inyect\xF3 permissionResolver. Pasarlo en options.`
|
|
6352
|
+
);
|
|
6353
|
+
}
|
|
6354
|
+
reqDesc = `module:${contract.permissionKey}:${contract.permissionAction}`;
|
|
6355
|
+
if (ctx.user.rol === "super_admin") {
|
|
6356
|
+
accesoOk = true;
|
|
6357
|
+
break;
|
|
6358
|
+
}
|
|
6359
|
+
const nivel = await options.permissionResolver({
|
|
6360
|
+
tenantId: ctx.tenantId,
|
|
6361
|
+
userRol: ctx.user.rol,
|
|
6362
|
+
modulo: contract.permissionKey
|
|
6363
|
+
});
|
|
6364
|
+
accesoOk = nivelAlcanza(nivel, contract.permissionAction);
|
|
6365
|
+
break;
|
|
6366
|
+
}
|
|
6367
|
+
case "self": {
|
|
6368
|
+
reqDesc = "self";
|
|
6369
|
+
accesoOk = !!ctx.user.uid && ctx.user.uid !== "";
|
|
6370
|
+
break;
|
|
6371
|
+
}
|
|
6372
|
+
case "saas": {
|
|
6373
|
+
reqDesc = "saas";
|
|
6374
|
+
accesoOk = ctx.user.rol === "super_admin";
|
|
6375
|
+
break;
|
|
6376
|
+
}
|
|
6377
|
+
default: {
|
|
6378
|
+
const _exhaustive = contract.permissionScope;
|
|
6379
|
+
throw new Error(
|
|
6380
|
+
`Wrapper: contract "${contract.name}" permissionScope inv\xE1lido: ${String(_exhaustive)}.`
|
|
6381
|
+
);
|
|
6382
|
+
}
|
|
6383
|
+
}
|
|
6384
|
+
if (!accesoOk) {
|
|
6271
6385
|
await writeAuditLog({
|
|
6272
6386
|
tenantId: ctx.tenantId,
|
|
6273
6387
|
brandId: ctx.brandId ?? null,
|
|
6274
6388
|
actor: { type: "martin", uid: ctx.user.uid, nombre: ctx.user.nombre },
|
|
6275
6389
|
action: contract.auditAction,
|
|
6276
|
-
motivo: `Acceso denegado \u2014 rol '${ctx.user.rol}'
|
|
6390
|
+
motivo: `Acceso denegado \u2014 rol '${ctx.user.rol}' sin permiso para ${reqDesc}`,
|
|
6277
6391
|
conversacionId: ctx.conversacionId ?? null,
|
|
6278
6392
|
status: "error",
|
|
6279
|
-
errorMessage: `Required: ${
|
|
6393
|
+
errorMessage: `Required: ${reqDesc}, got rol: ${ctx.user.rol}`,
|
|
6280
6394
|
durationMs: Date.now() - startMs
|
|
6281
6395
|
});
|
|
6282
6396
|
return {
|
|
@@ -6501,9 +6615,24 @@ async function dispatchWithContract(args) {
|
|
|
6501
6615
|
const { contract, helper, callable, input, ctx } = args;
|
|
6502
6616
|
if (getMode() === "admin") {
|
|
6503
6617
|
const db = getAdminDb();
|
|
6618
|
+
const permissionResolver = async (args2) => {
|
|
6619
|
+
if (args2.userRol === "super_admin") return "completo";
|
|
6620
|
+
const ref = db.collection("configuracion").doc(`${args2.tenantId}_roles`);
|
|
6621
|
+
const snap = await ref.get();
|
|
6622
|
+
if (!snap.exists) return "ninguno";
|
|
6623
|
+
const rolesData = snap.data();
|
|
6624
|
+
const rolData = rolesData[args2.userRol];
|
|
6625
|
+
if (!rolData || rolData.activo === false) return "ninguno";
|
|
6626
|
+
const nivel = rolData.permisosPorModulo?.[args2.modulo];
|
|
6627
|
+
if (nivel === "completo" || nivel === "editar" || nivel === "ver" || nivel === "ninguno") {
|
|
6628
|
+
return nivel;
|
|
6629
|
+
}
|
|
6630
|
+
return "ninguno";
|
|
6631
|
+
};
|
|
6504
6632
|
const wrapped = wrapWithContract(
|
|
6505
6633
|
contract,
|
|
6506
|
-
async (i) => helper({ ...i, db })
|
|
6634
|
+
async (i) => helper({ ...i, db }),
|
|
6635
|
+
{ permissionResolver }
|
|
6507
6636
|
);
|
|
6508
6637
|
return wrapped(input, ctx);
|
|
6509
6638
|
}
|