{
  "protocol_version": 1,
  "article_slug": "vendo-saas-tenant-isolation",
  "reviewed_on": "2026-10-08",
  "evidence_status": "proposed_protocol_not_executed",
  "not_a_vendor_sdk_schema": true,
  "sources": [
    "https://github.com/runvendo/vendo",
    "https://docs.vendo.run/howto/auth",
    "https://docs.vendo.run/howto/approvals",
    "https://docs.vendo.run/production/persistence",
    "https://docs.vendo.run/production/deploying"
  ],
  "locales": {
    "en": {
      "title": "Vendo for SaaS: Tenant Isolation and Approval Boundaries",
      "cases": [
        {
          "Attempt": "A requests B's invoice ID",
          "Required outcome": "Denied without leaking invoice fields",
          "Independent check": "Host API response and access log"
        },
        {
          "Attempt": "A changes active organization",
          "Required outcome": "Only the newly authorized context is visible",
          "Independent check": "Re-query records and reopen the saved app"
        },
        {
          "Attempt": "Read-only member sends a reminder",
          "Required outcome": "Blocked unless the role explicitly permits it",
          "Independent check": "Delivery ledger stays unchanged"
        },
        {
          "Attempt": "Administrator loses the role after approval",
          "Required outcome": "Permission is checked again before execution",
          "Independent check": "No external send after revocation"
        },
        {
          "Attempt": "Two workers resume the same approved action",
          "Required outcome": "One logical side effect",
          "Independent check": "Durable operation key and delivery ledger"
        },
        {
          "Attempt": "Process restarts",
          "Required outcome": "Authorized state survives; expired grants do not revive",
          "Independent check": "Store contents and replayed action"
        }
      ],
      "steps": []
    },
    "de": {
      "title": "Vendo für SaaS: Mandantentrennung und Freigaben",
      "cases": [
        {
          "Versuch": "A fragt B's Rechnungs-ID ab",
          "Erforderliches Ergebnis": "Ablehnung ohne Datenleck",
          "Unabhängige Prüfung": "API-Antwort und Zugriffslog"
        },
        {
          "Versuch": "A wechselt die aktive Organisation",
          "Erforderliches Ergebnis": "Nur der neu berechtigte Kontext ist sichtbar",
          "Unabhängige Prüfung": "Datensätze erneut laden und gespeicherte App öffnen"
        },
        {
          "Versuch": "Lesendes Mitglied sendet eine Erinnerung",
          "Erforderliches Ergebnis": "Ohne ausdrückliches Recht blockiert",
          "Unabhängige Prüfung": "Versandprotokoll bleibt unverändert"
        },
        {
          "Versuch": "Administrator verliert nach Freigabe seine Rolle",
          "Erforderliches Ergebnis": "Recht wird vor Ausführung erneut geprüft",
          "Unabhängige Prüfung": "Kein Versand nach Entzug"
        },
        {
          "Versuch": "Zwei Worker setzen dieselbe Freigabe fort",
          "Erforderliches Ergebnis": "Genau ein logischer Seiteneffekt",
          "Unabhängige Prüfung": "Dauerhafter Aktionsschlüssel und Versandprotokoll"
        },
        {
          "Versuch": "Prozess startet neu",
          "Erforderliches Ergebnis": "Berechtigter Zustand bleibt, abgelaufene Grants bleiben abgelaufen",
          "Unabhängige Prüfung": "Speicherzustand und wiederholte Aktion"
        }
      ],
      "steps": []
    },
    "es": {
      "title": "Vendo para SaaS: aislamiento de clientes y aprobaciones",
      "cases": [
        {
          "Intento": "A solicita la factura de B por ID",
          "Resultado requerido": "Denegación sin revelar campos",
          "Comprobación independiente": "Respuesta API y registro de acceso"
        },
        {
          "Intento": "A cambia de organización activa",
          "Resultado requerido": "Solo aparece el contexto autorizado",
          "Comprobación independiente": "Volver a consultar y abrir la app guardada"
        },
        {
          "Intento": "Miembro de lectura envía recordatorio",
          "Resultado requerido": "Bloqueado salvo permiso explícito",
          "Comprobación independiente": "Registro de envíos sin cambios"
        },
        {
          "Intento": "Administrador pierde rol tras aprobar",
          "Resultado requerido": "Se vuelve a comprobar el permiso",
          "Comprobación independiente": "Ningún envío tras revocar"
        },
        {
          "Intento": "Dos workers reanudan la misma acción",
          "Resultado requerido": "Un solo efecto lógico",
          "Comprobación independiente": "Clave durable y registro de entregas"
        },
        {
          "Intento": "Reinicio del proceso",
          "Resultado requerido": "Persiste estado autorizado; no reviven permisos caducados",
          "Comprobación independiente": "Almacenamiento y repetición de acción"
        }
      ],
      "steps": []
    },
    "zh": {
      "title": "Vendo SaaS 集成：租户隔离与审批边界",
      "cases": [
        {
          "尝试": "A 请求 B 的发票 ID",
          "必须满足的结果": "拒绝且不泄露字段",
          "独立检查": "宿主 API 响应与访问日志"
        },
        {
          "尝试": "A 切换当前组织",
          "必须满足的结果": "只显示新授权上下文",
          "独立检查": "重新查询并打开保存的应用"
        },
        {
          "尝试": "只读成员发送提醒",
          "必须满足的结果": "无明确权限就阻止",
          "独立检查": "投递台账不变"
        },
        {
          "尝试": "管理员审批后被撤销角色",
          "必须满足的结果": "执行前重新检查权限",
          "独立检查": "撤销后没有外部发送"
        },
        {
          "尝试": "两个 worker 恢复同一已审批动作",
          "必须满足的结果": "只有一次逻辑副作用",
          "独立检查": "持久操作键与投递台账"
        },
        {
          "尝试": "进程重启",
          "必须满足的结果": "合法状态保留，过期授权不复活",
          "独立检查": "存储内容与动作重放"
        }
      ],
      "steps": []
    }
  },
  "run_configuration": {
    "product_versions": null,
    "model": null,
    "fixture_revision": null,
    "permissions": null,
    "time_budget_seconds": null,
    "cost_budget": null
  },
  "trial_results": [],
  "report_fields": [
    "attempt_id",
    "case_id",
    "observed_final_state",
    "acceptance_passed",
    "independent_evidence",
    "duration_seconds",
    "total_cost",
    "human_review_minutes",
    "unresolved_effects"
  ]
}
