ponch-mcp-server 1.0.73 → 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 -14
- 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,25 +6329,68 @@ function getWrapperMessage(key, locale) {
|
|
|
6259
6329
|
}
|
|
6260
6330
|
return msg;
|
|
6261
6331
|
}
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
return required.includes(userRol);
|
|
6332
|
+
var NIVELES_ORDER = ["ninguno", "ver", "editar", "completo"];
|
|
6333
|
+
function nivelAlcanza(nivel, accion) {
|
|
6334
|
+
return NIVELES_ORDER.indexOf(nivel) >= NIVELES_ORDER.indexOf(accion);
|
|
6266
6335
|
}
|
|
6267
|
-
function wrapWithContract(contract, helper) {
|
|
6336
|
+
function wrapWithContract(contract, helper, options = {}) {
|
|
6268
6337
|
return async function wrappedTool(input, ctx) {
|
|
6269
6338
|
const startMs = Date.now();
|
|
6270
6339
|
const locale = ctx.user.idiomaPreferido;
|
|
6271
|
-
|
|
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) {
|
|
6272
6385
|
await writeAuditLog({
|
|
6273
6386
|
tenantId: ctx.tenantId,
|
|
6274
6387
|
brandId: ctx.brandId ?? null,
|
|
6275
6388
|
actor: { type: "martin", uid: ctx.user.uid, nombre: ctx.user.nombre },
|
|
6276
6389
|
action: contract.auditAction,
|
|
6277
|
-
motivo: `Acceso denegado \u2014 rol '${ctx.user.rol}'
|
|
6390
|
+
motivo: `Acceso denegado \u2014 rol '${ctx.user.rol}' sin permiso para ${reqDesc}`,
|
|
6278
6391
|
conversacionId: ctx.conversacionId ?? null,
|
|
6279
6392
|
status: "error",
|
|
6280
|
-
errorMessage: `Required: ${
|
|
6393
|
+
errorMessage: `Required: ${reqDesc}, got rol: ${ctx.user.rol}`,
|
|
6281
6394
|
durationMs: Date.now() - startMs
|
|
6282
6395
|
});
|
|
6283
6396
|
return {
|
|
@@ -6502,9 +6615,24 @@ async function dispatchWithContract(args) {
|
|
|
6502
6615
|
const { contract, helper, callable, input, ctx } = args;
|
|
6503
6616
|
if (getMode() === "admin") {
|
|
6504
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
|
+
};
|
|
6505
6632
|
const wrapped = wrapWithContract(
|
|
6506
6633
|
contract,
|
|
6507
|
-
async (i) => helper({ ...i, db })
|
|
6634
|
+
async (i) => helper({ ...i, db }),
|
|
6635
|
+
{ permissionResolver }
|
|
6508
6636
|
);
|
|
6509
6637
|
return wrapped(input, ctx);
|
|
6510
6638
|
}
|