Diagnose completed/executed contracts blocked from download because a PRE_SEND approval is still pending, often after the approver was deleted or an executed version was uploaded later. Use when support says: 'download disabled on executed contract', 'pending pre-send approval on completed contract', 'executed contract cannot be downloaded', 'deleted approver left contract stuck', or 'many executed contracts in one workspace need bulk skip + resync'.
This runbook covers a silent data-state issue where a contract is already Completed / Executed, but download remains blocked because a PRE_SEND required approval is still in a non-terminal state.
Common production pattern:
ContractRequiredApprovalV2 still has sent_for_approval=Trueactive_approval.status is neither APPROVED nor SKIPPEDThis incident class is usually not a runtime failure. The backend is enforcing the stored approval state exactly as written.
Use when:
Do not use for:
approvals-reset-on-upload-v4-triagecontract-search-stale-assignee-manual-reviewapproval-v5-bulk-reassign-download-restriction-backend{workspace_id}{cluster}{contract_id} for one impacted example{required_approval_id} / {active_approval_id} once queried{org_user_email} for the support actor if a Django-shell mitigation is needed{request_id} if correlating upload/download requestsKnown historical example only: Jira SPD-42470, workspace 179471, cluster IN, sample contract 436143, dry-run count 332.
Use the same production mapping used in other approval / contract skills:
| Cluster | BQ project | Dataset | Table prefix |
|---|---|---|---|
| EU | spotdraft-prod | prod_eu_db | public_ |
| IN | spotdraft-prod | prod_india_db | public_ |
| US | spotdraft-prod | prod_usa_db | public_ |
| ME | spotdraft-prod | prod_mea_db | (none) |
Replace {project}, {dataset}, and {prefix} in the queries below.
Expected symptom bundle:
COMPLETED / executedexecution_date is populatedSlack / Jira clues from the reference incident:
Run sequentially.
SELECT
cra.id AS required_approval_id,
cra.approval_name,
cra.contract_id,
cra.org_user_id,
cra.role_id,
cra.sent_for_approval,
c.status AS contract_status,
c.execution_date,
c.created_by_workspace_id,
ca.id AS active_approval_id,
ca.status AS active_approval_status
FROM `{project}.{dataset}.{prefix}contracts_v3_contractrequiredapprovalv2` AS cra
JOIN `{project}.{dataset}.{prefix}contracts_v3_contractv3` AS c
ON c.id = cra.contract_id
LEFT JOIN `{project}.{dataset}.{prefix}contracts_v3_contractapprovalv2` AS ca
ON ca.id = cra.active_approval_id
WHERE c.created_by_workspace_id = {workspace_id}
AND c.status = 'COMPLETED'
AND c.execution_date IS NOT NULL
AND cra.approval_type = 'PRE_SEND'
AND cra.is_deleted = FALSE
AND cra.sent_for_approval = TRUE
AND (ca.status IS NULL OR ca.status NOT IN ('APPROVED', 'SKIPPED'))
ORDER BY cra.contract_id, cra.id;
If this returns rows, the issue class is confirmed.
Use a single-contract drilldown when validating a customer-reported sample:
SELECT
cra.id AS required_approval_id,
cra.contract_id,
cra.approval_name,
cra.org_user_id,
cra.role_id,
ca.id AS active_approval_id,
ca.status AS active_approval_status,
c.status AS contract_status,
c.execution_date
FROM `{project}.{dataset}.{prefix}contracts_v3_contractrequiredapprovalv2` AS cra
JOIN `{project}.{dataset}.{prefix}contracts_v3_contractv3` AS c
ON c.id = cra.contract_id
LEFT JOIN `{project}.{dataset}.{prefix}contracts_v3_contractapprovalv2` AS ca
ON ca.id = cra.active_approval_id
WHERE cra.contract_id = {contract_id}
AND c.created_by_workspace_id = {workspace_id}
AND cra.approval_type = 'PRE_SEND'
AND cra.is_deleted = FALSE
ORDER BY cra.id;
The block is expected from current code behavior:
contracts_v3/contract_approvals/data/db_repo.py
are_approvals_in_progress(...) only checks for non-deleted, sent_for_approval=True, non-terminal approvals for the contract/workspace.COMPLETED contracts.sd_auth/permissions.py
IsActionRestrictedDueToActiveApproval maps both download and upload views to restricted actions.compute_restricted_actions_use_case returns DOWNLOAD, the user sees the action blocked.sd_organizations_v2/org_user/domain/org_user_can_be_deleted_or_deactivated_check_use_case.py
This means a deleted approver can leave behind orphaned PRE_SEND approvals that still gate completed contracts.
Do not assume the stock reassign_contract_approvals managerie fixes this issue.
Why:
contracts_v3/management/commands/reassign_contract_approvals.py filters ContractRequiredApprovalV2 with contract__status=ContractStatus.ACTIVEThis is the main operational trap. If the contract is already completed, reassignment is usually the wrong end state anyway; the approval should be terminalized so download is unblocked.
If logs are required, use them only to correlate the upload/download timeline or identify a request_id.
The reference incident was resolved with a one-off Django-shell script, not with the stock managerie command.
Your mitigation should be workspace-scoped and should:
required_approval_id / contract_id rowsContractApprovalV2 row with status SKIPPED for each affected PRE_SEND approvalContractRequiredApprovalV2.active_approval to the new skipped approvalOperational notes from the incident:
If no reusable script already exists for the workspace, ask for a Django-shell script that is explicitly scoped to:
workspace_id={workspace_id}contract__status=COMPLETEDexecution_date IS NOT NULLapproval_type='PRE_SEND'sent_for_approval=TrueAPPROVED/SKIPPEDExpected script behavior from the incident:
Re-run the workspace blast-radius query above. Success condition:
| Observation | Interpretation |
|---|---|
| Completed contract + pending PRE_SEND row returned by query | Confirms this issue class |
| Completed contract + no matching PRE_SEND rows | Use another skill |
| Reassign managerie finds nothing for the same contract | Expected if contract is completed; not a disproof |
| Rows drop to zero after skip script + resync | Mitigation succeeded |
| Script cannot create skipped approvals or cannot resync | Data integrity / script issue; escalate |
are_approvals_in_progress(...)contracts_v3/contract_approvals/data/db_repo.pysd_auth/permissions.pysd_organizations_v2/org_user/domain/org_user_can_be_deleted_or_deactivated_check_use_case.pycontracts_v3/management/commands/reassign_contract_approvals.pySPD-424703005 (Probo)C0AMYDFLLBAreassign_contract_approvals is intended for active approvals before deletion / role changes, not for already completed contractscontract-lookup for baseline contract / workspace investigationapprovals-reset-on-upload-v4-triage when upload behavior itself is disputedapproval-v5-bulk-reassign-download-restriction-backend for active approval reassignment and V5 restriction edits