{"record":{"id":"8470408cc93ba27c","repo":"twentyhq/twenty","slug":"upsertrowlevelpermissionpredicates-returned-fewer","errorCode":null,"errorMessage":"upsertRowLevelPermissionPredicates returned fewer than 2 predicates for opportunity OR group","messagePattern":"upsertRowLevelPermissionPredicates returned fewer than 2 predicates for opportunity OR group","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/twenty-apps/internal/twenty-partners/src/scripts/configure-partner-rls.ts","lineNumber":536,"sourceCode":"            operand: 'IS',\n            workspaceMemberFieldMetadataId: workspaceMemberIdFieldId,\n            rowLevelPermissionPredicateGroupId: OPPORTUNITY_RLS_OR_GROUP_ID,\n            positionInRowLevelPermissionPredicateGroup: 0,\n          },\n          {\n            fieldMetadataId: opportunityIsListedFieldId,\n            operand: 'IS',\n            value: true,\n            rowLevelPermissionPredicateGroupId: OPPORTUNITY_RLS_OR_GROUP_ID,\n            positionInRowLevelPermissionPredicateGroup: 1,\n          },\n        ],\n      } satisfies UpsertPredicatesInput,\n      'opportunity',\n    );\n\n    if (oppPredicates.length < 2) {\n      throw new Error(\n        'upsertRowLevelPermissionPredicates returned fewer than 2 predicates for opportunity OR group',\n      );\n    }\n\n    for (const predicate of oppPredicates) {\n      results.push(predicate);\n    }\n\n    console.log(\n      `[rls:configure] ✓ opportunity: OR group id=${OPPORTUNITY_RLS_OR_GROUP_ID} ` +\n        `(${oppPredicates.length} predicates: partnerUser IS me OR isListed = true)`,\n    );\n  }\n\n  // workspaceMember predicate: \"id IS the current member\", scoping the role's read to the\n  // partner's own record. Other members (e.g. an opportunity's internal owner) resolve to null.\n  {\n    const wmData = await metadataFetch<{","sourceCodeStart":518,"sourceCodeEnd":554,"githubUrl":"https://github.com/twentyhq/twenty/blob/1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6/packages/twenty-apps/internal/twenty-partners/src/scripts/configure-partner-rls.ts#L518-L554","documentation":"Thrown by the partner RLS configuration script after it calls the metadata `upsertRowLevelPermissionPredicates` mutation to create the opportunity OR-group (predicate 0: partnerUser IS me; predicate 1: isListed = true). The mutation is expected to return both persisted predicates; if it returns fewer than two, the script aborts because the row-level security rule would be incomplete (partners would not reliably see listed briefs). It is a hard stop in a one-shot provisioning script, not a runtime user-facing path.","triggerScenarios":"Running `configure-partner-rls` against a workspace where (a) `opportunityPartnerUserFieldId` or `opportunityIsListedFieldId` resolves to a field that does not exist on the opportunity object, (b) the `partnerRole.id` or `opportunityObjectId` is wrong/stale, (c) RLS is not enabled on the opportunity object so the mutation silently drops predicates, or (d) the OR group UUID `OPPORTUNITY_RLS_OR_GROUP_ID` collides with an existing group and the upsert dedupes one predicate away.","commonSituations":"Re-running the script against a workspace whose metadata UUIDs were regenerated (e.g. after a `database:reset` or a fresh seed), pointing the script at the wrong environment, or a partially-applied earlier run that left an orphaned OR group with one predicate. Also seen after renaming the isListed/partnerUser fields without updating the ID constants the script reads.","solutions":["Inspect the actual mutation response: log `oppPredicates` (and the full `upsertRowLevelPermissionPredicates` payload) right before the check to see exactly which predicate is missing and any partial error.","Verify `opportunityPartnerUserFieldId` and `opportunityIsListedFieldId` resolve to live fields on the opportunity object in this workspace (query `fieldMetadata` by the universal identifiers the script uses).","Confirm `partnerRole.id` and `opportunityObjectId` are correct for the target workspace and that RLS/row-level permission groups are enabled on the opportunity object.","If a previous partial run left an OR group with one predicate, delete the existing `OPPORTUNITY_RLS_OR_GROUP_ID` group (or run the script's cleanup/teardown path) so the upsert is not deduping against stale state.","Re-run the script against a clean workspace after `database:reset` so all metadata IDs match the seed."],"exampleFix":"// before\nif (oppPredicates.length < 2) {\n  throw new Error(\n    'upsertRowLevelPermissionPredicates returned fewer than 2 predicates for opportunity OR group',\n  );\n}\n\n// after — surface which predicate is missing before aborting\nconst expectedFieldIds = new Set([\n  opportunityPartnerUserFieldId,\n  opportunityIsListedFieldId,\n]);\nconst returnedFieldIds = new Set(oppPredicates.map((p) => p.fieldMetadataId));\nconst missing = [...expectedFieldIds].filter((id) => !returnedFieldIds.has(id));\nif (oppPredicates.length < 2) {\n  throw new Error(\n    `upsertRowLevelPermissionPredicates returned ${oppPredicates.length} predicates for opportunity OR group; missing fieldMetadataIds: ${missing.join(', ')}`,\n  );\n}","handlingStrategy":"validation","validationCode":"// Before calling upsertPredicates for the opportunity OR group, confirm both\n// fields exist on the opportunity object in this workspace.\nconst fieldIds = await fetchFieldMetadataIds(metadataUrl, apiKey, opportunityObjectId, [\n  opportunityPartnerUserFieldId,\n  opportunityIsListedFieldId,\n]);\nif (fieldIds.size !== 2) {\n  throw new Error(\n    `Cannot build opportunity OR group: missing field metadata ids on opportunity object`,\n  );\n}\n// (then proceed to the upsertPredicates call)","typeGuard":"const isCompletePredicateSet = (\n  predicates: unknown,\n  expectedFieldIds: ReadonlyArray<string>,\n): predicates is { id: string; fieldMetadataId: string }[] =>\n  Array.isArray(predicates) &&\n  predicates.length >= expectedFieldIds.length &&\n  expectedFieldIds.every((id) =>\n    (predicates as Array<{ fieldMetadataId: string }>).some(\n      (p) => p.fieldMetadataId === id,\n    ),\n  );","tryCatchPattern":null,"preventionTips":["Run the configure-rls script only against a freshly reset workspace whose metadata IDs match the seed.","Resolve field/object IDs from universal identifiers at runtime rather than hardcoding, so a regenerated workspace cannot silently mismatch.","Before the upsert, assert all referenced fieldMetadataIds and the roleId exist.","Make the script idempotent and clean up partial OR groups from prior failed runs before re-upserting."],"tags":["rls","metadata-mutation","partner-setup","configuration","script"],"backgroundTag":null,"analyzedSha":"1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6","analyzedAt":"2026-08-12T15:37:27.593Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}