fix(policies): create draft version on policy regenerate instead of overwriting published - #3471
Conversation
…verwriting published ## Problem When regenerating a policy, the published version's content is overwritten immediately. This bypasses the approval workflow that should gate any content changes. Regenerating a published policy currently mutates its live content, signedBy metadata, and currentVersionId directly without creating a draft for review. ## Root cause updatePolicyInDatabase in update-policy-helpers.ts rewrites policy.content, currentVersionId, and signedBy on the live published policy record without changing status or requiring approval. Every other content-change path (createVersion, submitForApproval, acceptChanges) creates a draft version first and only publishes on explicit approval; regenerate was the sole exception. ## Fix For published or needs_review policies, regenerate now creates a new draft version (incrementing the version number) with the regenerated content, leaving the published version's content, currentVersionId, signedBy, and status unchanged. This mirrors the existing approval workflow: draft versions are created, then approved, then published with the signing flow triggered for all employees. ## Explicitly NOT touched - Policies not yet published (status: draft) - The signing flow (runs only on explicit publish, unchanged) - Approval/rejection workflows - Version history or deletion logic ## Verification Added regression test asserting that regenerating a published policy creates a new draft version without modifying the published content or status. Existing policy regenerate and versioning unit tests pass locally ✅
There was a problem hiding this comment.
cubic analysis
All reported issues were addressed across 3 files
Confidence score: 5/5
- Safe to merge after the addressed issues were fixed.
Linked issue analysis
Linked issue: CS-766: [Bug] - Policy Regeneration, changes the published version
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Regenerating a policy creates a new version in draft status (does not overwrite the published policy's content, currentVersionId, signedBy, or PDFs) | update-policy-helpers.ts now creates a new policyVersion in a transaction and intentionally avoids updating the live policy row. The regression test update-policy-helpers.spec.ts asserts a new version is appended and that the policy row is not mutated; UI copy and toast were updated to reflect 'draft created for review'. |
| The new draft version can be approved through the normal approval workflow | The PR preserves the approval workflow by creating a draft version and explicitly states it does not change approval/rejection logic, but there is no explicit test in this PR that exercises approving the newly created draft version. | |
| Publishing the new version triggers the policy signing flow for all employees in the org | The PR states the signing flow is unchanged and that publishing the draft will re-trigger signing, and it avoids touching publishing/signing code here. However, this PR does not include a test that publishes the new draft and asserts the signing flow runs, so verification is indirect (claim + code avoidance) rather than explicit. |
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
Address review findings.
|
@cubic-dev-ai review it |
@tofikwest I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
cubic analysis
1 issue found across 3 files
Confidence score: 5/5
- In
apps/api/src/trigger/policies/update-policy-helpers.ts,POLICY_VERSION_CREATE_RETRIESduplicatesPoliciesService.versionCreateRetries, so these values can drift and cause inconsistent retry behavior between trigger and service paths after future tuning; centralize this constant in a shared source (or reference the service value directly) before merging to prevent subtle reliability mismatches.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/api/src/trigger/policies/update-policy-helpers.ts">
<violation number="1" location="apps/api/src/trigger/policies/update-policy-helpers.ts:72">
P3: The retry constant `POLICY_VERSION_CREATE_RETRIES` (line 72) duplicates `PoliciesService.versionCreateRetries` with a mirror comment but no shared source. If the service-layer retry count is tuned, the trigger path's count won't follow. Consider importing or referencing the value from `PoliciesService`, or extracting a shared constant, to prevent silent drift.</violation>
</file>
Linked issue analysis
Linked issue: CS-766: [Bug] - Policy Regeneration, changes the published version
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Regenerating a PUBLISHED policy creates a new DRAFT version instead of overwriting the published version | update-policy-helpers.ts implements a published-path that creates a new policyVersion (incrementing the version) and leaves the published row unchanged; the spec contains a test that asserts a new version is created and the published row is not mutated. |
| ✅ | Published policy live fields (content, currentVersionId, signedBy, PDFs) are preserved and existing versions are not deleted when regenerating | tests assert deleteMany is not called and policy update calls do not include signedBy/currentVersionId/content; code no longer deletes PDFs or overwrites the live policy for published status. |
| ✅ | Regenerating a DRAFT policy overwrites the current draft version in place and syncs policy.content/draftContent (no unattached extra version) | the code path for PolicyStatus.draft updates the current version and policy.content/draftContent in a transaction, and the spec contains a test asserting version update, no new create, and policy content sync. |
| The new draft version can be approved (follows the normal approval workflow) | PR states regeneration now mirrors the existing approval workflow and creates a draft for review, but there is no direct test in this diff that exercises approving the newly created draft or end-to-end approval behavior. | |
| Publishing the new version triggers the policy signing flow for all employees in the org | PR explicitly states the signing flow is unchanged and runs only on explicit publish, but this change does not include a test or code exercising the signing flow on publish to prove end-to-end behavior in this PR. |
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| // Mirror PoliciesService.versionCreateRetries: retry version creation on a | ||
| // unique-constraint race so two near-simultaneous regenerations don't collide | ||
| // on the [policyId, version] key. | ||
| const POLICY_VERSION_CREATE_RETRIES = 3; |
There was a problem hiding this comment.
P3: The retry constant POLICY_VERSION_CREATE_RETRIES (line 72) duplicates PoliciesService.versionCreateRetries with a mirror comment but no shared source. If the service-layer retry count is tuned, the trigger path's count won't follow. Consider importing or referencing the value from PoliciesService, or extracting a shared constant, to prevent silent drift.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/trigger/policies/update-policy-helpers.ts, line 72:
<comment>The retry constant `POLICY_VERSION_CREATE_RETRIES` (line 72) duplicates `PoliciesService.versionCreateRetries` with a mirror comment but no shared source. If the service-layer retry count is tuned, the trigger path's count won't follow. Consider importing or referencing the value from `PoliciesService`, or extracting a shared constant, to prevent silent drift.</comment>
<file context>
@@ -67,6 +66,11 @@ export async function fetchOrganizationAndPolicy(
+// Mirror PoliciesService.versionCreateRetries: retry version creation on a
+// unique-constraint race so two near-simultaneous regenerations don't collide
+// on the [policyId, version] key.
+const POLICY_VERSION_CREATE_RETRIES = 3;
+
export async function updatePolicyInDatabase(
</file context>
|
re: POLICY_VERSION_CREATE_RETRIES duplicates the service-layer retry setting and can drift, so centralize it to a shared source. |
Address review findings.
|
@cubic-dev-ai review it |
@tofikwest I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
cubic analysis
1 issue found across 3 files
Confidence score: 2/5
apps/api/src/trigger/policies/update-policy-helpers.tswas fixed, but the duplicated helper inapps/app/src/trigger/policies/update-policy-helpers.tsstill appears to run during app Trigger policy regeneration, which can overwrite the published policy version and clear signatures for users. Update the app-side copy (or consolidate to a shared helper) and verify Trigger-driven regeneration preserves published versions/signatures before merging.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/api/src/trigger/policies/update-policy-helpers.ts">
<violation number="1" location="apps/api/src/trigger/policies/update-policy-helpers.ts:153">
P1: Policy regeneration invoked through the app Trigger task still overwrites the published version and clears signatures, because only the API copy of this duplicated helper changed. Update or consolidate `apps/app/src/trigger/tasks/onboarding/update-policies-helpers.ts` too; according to linked Linear issue CS-766, regeneration must create a draft for approval rather than mutate the published policy.</violation>
</file>
Linked issue analysis
Linked issue: CS-766: [Bug] - Policy Regeneration, changes the published version
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Regenerating a published policy creates a new draft version instead of overwriting the published version | updatePolicyInDatabase now appends a new policyVersion with the next version number for published/needs_review policies; tests assert a new version is created and its content/changelog are correct. |
| ✅ | Regenerating a published policy does not mutate the live published policy row (content, currentVersionId, signedBy, pdfs, existing versions) | The published path no longer updates the policy row to overwrite content/currentVersionId/signedBy; tests explicitly assert the tx.policy.update calls do not contain those properties. |
| ✅ | Regenerating a DRAFT policy overwrites the current draft version in-place and clears stale PDF/displayFormat so the editor shows regenerated content | For draft policies the code updates the current version and policy content/draftContent, clears pdfUrl and sets displayFormat to 'EDITOR'; tests verify version update, no extra version creation, and clearing of PDF/displayFormat. |
| Publishing the new draft triggers the policy signing flow for all employees in the org | The PR states the signing flow is unchanged and that publishing continues to trigger signing, but this diff does not add/tests the publish-to-signing behavior itself, so evidence is limited to the author’s statement and unchanged signing flow comment. | |
| ✅ | User-facing messaging updated to indicate regeneration creates a draft for review | UI toast and dialog copy were updated to inform users that regeneration creates a new draft version for review and preserves published signatures. |
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| }); | ||
| const nextVersion = (latestVersion?.version ?? 0) + 1; | ||
|
|
||
| await tx.policyVersion.create({ |
There was a problem hiding this comment.
P1: Policy regeneration invoked through the app Trigger task still overwrites the published version and clears signatures, because only the API copy of this duplicated helper changed. Update or consolidate apps/app/src/trigger/tasks/onboarding/update-policies-helpers.ts too; according to linked Linear issue CS-766, regeneration must create a draft for approval rather than mutate the published policy.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/trigger/policies/update-policy-helpers.ts, line 153:
<comment>Policy regeneration invoked through the app Trigger task still overwrites the published version and clears signatures, because only the API copy of this duplicated helper changed. Update or consolidate `apps/app/src/trigger/tasks/onboarding/update-policies-helpers.ts` too; according to linked Linear issue CS-766, regeneration must create a draft for approval rather than mutate the published policy.</comment>
<file context>
@@ -75,79 +79,99 @@ export async function updatePolicyInDatabase(
+ });
+ const nextVersion = (latestVersion?.version ?? 0) + 1;
+
+ await tx.policyVersion.create({
+ data: {
+ policyId,
</file context>
|
re: The duplicated helper in apps/app/src/trigger/tasks/onboarding/update-policies-helpers.ts still runs during app Trigger policy regeneration and can overwrite the published policy version / clear signatures for users. |
|
@cubic-dev-ai review it |
@tofikwest I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
cubic analysis
No issues found across 3 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Linked issue analysis
Linked issue: CS-766: [Bug] - Policy Regeneration, changes the published version
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Regenerating a published policy creates a new draft version instead of overwriting the published version | updatePolicyInDatabase now appends a new policyVersion with the next version number for published/needs_review policies; tests assert a new version is created and its content/changelog are correct. |
| ✅ | Regenerating a published policy does not mutate the live published policy row (content, currentVersionId, signedBy, pdfs, existing versions) | The published path no longer updates the policy row to overwrite content/currentVersionId/signedBy; tests explicitly assert the tx.policy.update calls do not contain those properties. |
| ✅ | Regenerating a DRAFT policy overwrites the current draft version in-place and clears stale PDF/displayFormat so the editor shows regenerated content | For draft policies the code updates the current version and policy content/draftContent, clears pdfUrl and sets displayFormat to 'EDITOR'; tests verify version update, no extra version creation, and clearing of PDF/displayFormat. |
| Publishing the new draft triggers the policy signing flow for all employees in the org | The PR states the signing flow is unchanged and that publishing continues to trigger signing, but this diff does not add/tests the publish-to-signing behavior itself, so evidence is limited to the author’s statement and unchanged signing flow comment. | |
| ✅ | User-facing messaging updated to indicate regeneration creates a draft for review | UI toast and dialog copy were updated to inform users that regeneration creates a new draft version for review and preserves published signatures. |
# [3.106.0](v3.105.0...v3.106.0) (2026-07-22) ### Bug Fixes * **auth:** attribute API-key mutations to the key's creator, not the org owner ([#3472](#3472)) ([206ed96](206ed96)), closes [hi#risk](https://github.com/hi/issues/risk) * **deps:** bump adm-zip 0.5.18 -> 0.6.0 in apps/api (Dependabot [#88](https://github.com/trycompai/comp/issues/88)/[#89](https://github.com/trycompai/comp/issues/89)) ([#3462](#3462)) ([300f2a1](300f2a1)), closes [#3451](#3451) * **deps:** override tar to ^7.5.19 to clear node-tar Dependabot alerts ([#94](https://github.com/trycompai/comp/issues/94)-[#104](https://github.com/trycompai/comp/issues/104)) ([#3466](#3466)) ([8ab5709](8ab5709)) * **deps:** patch engine.io ([#93](#93)) and body-parser ([#92](#92)) Dependabot alerts ([#3464](#3464)) ([94c33b1](94c33b1)) * **isms:** harden internal-audit validation and edge cases from deploy review ([#3473](#3473)) ([c6c7379](c6c7379)) * **policies:** create draft version on policy regenerate instead of overwriting published ([#3471](#3471)) ([ff31dbd](ff31dbd)) * **policies:** delete detached PDF objects when regenerating a draft ([#3474](#3474)) ([ecd1bd0](ecd1bd0)) * **policies:** rename CreateVersionDto to avoid swagger collision with automations ([#3469](#3469)) ([2d5290a](2d5290a)) ### Features * **isms:** internal audit programme, plan and report — clause 9.2 (CS-724) ([#3468](#3468)) ([42e5ebd](42e5ebd)), closes [hi#impact](https://github.com/hi/issues/impact)
|
🎉 This PR is included in version 3.106.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Problem
When regenerating a policy, the published version's content is overwritten immediately. This bypasses the approval workflow that should gate any content changes. Regenerating a published policy currently mutates its live content, signedBy metadata, and currentVersionId directly without creating a draft for review.
Root cause
updatePolicyInDatabase in update-policy-helpers.ts rewrites policy.content, currentVersionId, and signedBy on the live published policy record without changing status or requiring approval. Every other content-change path (createVersion, submitForApproval, acceptChanges) creates a draft version first and only publishes on explicit approval; regenerate was the sole exception.
Fix
For published or needs_review policies, regenerate now creates a new draft version (incrementing the version number) with the regenerated content, leaving the published version's content, currentVersionId, signedBy, and status unchanged. This mirrors the existing approval workflow: draft versions are created, then approved, then published with the signing flow triggered for all employees.
Explicitly NOT touched
Verification
Added regression test asserting that regenerating a published policy creates a new draft version without modifying the published content or status. Existing policy regenerate and versioning unit tests pass locally ✅
Fixes CS-766
Summary by cubic
Regenerating a policy now creates a draft version for published/needs_review policies and preserves live content and signatures; draft policies are updated in place so the editor shows the new content immediately. Aligns with CS-766 by restoring the approval flow; publishing the draft triggers the signing flow as expected.
currentVersionId,signedBy, PDFs, and existing versions unchanged.contentanddraftContent; do not append a new version; clear stale PDF references and switchdisplayFormattoEDITORif the draft was a PDF.Written for commit cadbbe1. Summary will update on new commits.