Skip to content

fix: track campaign source freshness - #54

Merged
Ankit6149 merged 13 commits into
masterfrom
stabilization/campaign-state-integrity
Jul 27, 2026
Merged

fix: track campaign source freshness#54
Ankit6149 merged 13 commits into
masterfrom
stabilization/campaign-state-integrity

Conversation

@Ankit6149

@Ankit6149 Ankit6149 commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Closes #16.

What changed

  • Added a versioned, normalized generation source snapshot and deterministic fingerprint.
  • Added a minimal GenerationRun contract that records the source snapshot and fingerprint used for generation.
  • Captured the source snapshot at request start, so edits made while generation is running are detected correctly when the response returns.
  • Recomputed freshness from campaign name, brief, audience, links, repository, destinations, provider, model, endpoint, extracted document text, and submitted media metadata.
  • Excluded temporary API keys and browser-only extraction flags from the fingerprint.
  • Kept old drafts visible when stale, with a persistent accessible warning describing what changed.
  • Blocked copy, copy-and-open, Markdown export, JSON export, and direct publishing while stale.
  • Persisted freshness metadata in the local campaign record and restored it deterministically, including legacy campaign migration and tamper-safe rebuilding.
  • Added scoped responsive styling for the stale state.

Acceptance evidence

  • Source field regression coverage includes every generation-relevant field.
  • Equivalent whitespace, line endings, destination ordering, endpoint trailing slashes, and the Untitled campaign default remain stable.
  • Temporary key and browser-only metadata changes do not create false stale states.
  • Matching, stale, untracked, saved/reopened, legacy, and tampered metadata paths are covered.
  • UI integration assertions verify the persistent alert, freshness state, blocked outbound actions, persistence wiring, and stylesheet loading.
  • CI run 201 passed:
    • frontend regression tests;
    • production dependency audit;
    • production build;
    • MCP tests;
    • existing Python tests.
  • Vercel preview deployment succeeded for the final head commit.
  • Final diff contains only five product/test files; all temporary integration files were removed.

Deliberately not included

Issue #17 remains separate. Atomic rejection of fallback or malformed responses requires its own response-validation boundary and broader regression suite; it was not partially claimed as complete here.

@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
signal-flow-studio Ready Ready Preview, Comment Jul 27, 2026 6:03pm

@github-actions

Copy link
Copy Markdown
Contributor

Campaign freshness integration diagnostic

file:///home/runner/work/SignalFlow-Studio/SignalFlow-Studio/scripts/apply-campaign-freshness.mjs:90
    "replacement": "                <div className={`review-workspace ${isCampaignStale ? \"has-stale-campaign\" : \"\"}`}>\n                  {isCampaignStale && (\n                    <div className=\"campaign-stale-banner\" role=\"alert\" aria-live=\"assertive\">\n                      <div className=\"campaign-stale-banner__copy\">\n                        <span className=\"campaign-stale-banner__label\">Source changed</span>\n                        <strong>These drafts belong to an earlier campaign snapshot.</strong>\n                      </div>\n                      <p>\n                        Review remains available, but SignalFlow blocks copy, export, and publishing until the\n                        campaign is regenerated from the current source.\n                      </p>\n                      {sourceChangeLabels.length > 0 && (\n                        <small>Changed: {sourceChangeLabels.join(\", ")}.</small>\n                      )}\n                      <button type=\"button\" onClick={() => navigateStudioFlow(\"destinations\")}>\n                        Review changes\n                      </button>\n                    </div>\n                  )}\n                  <div className=\"review-tabs\" aria-label=\"Campaign channels\">"
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  ^

SyntaxError: Unexpected token ')'
    at compileSourceTextModule (node:internal/modules/esm/utils:346:16)
    at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:110:18)
    at #translate (node:internal/modules/esm/loader:559:20)
    at afterLoad (node:internal/modules/esm/loader:612:29)
    at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:617:12)
    at #createModuleJob (node:internal/modules/esm/loader:640:36)
    at #getJobFromResolveResult (node:internal/modules/esm/loader:353:34)
    at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:321:41)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:680:25)

Node.js v22.23.1

@github-actions

Copy link
Copy Markdown
Contributor

Campaign freshness integration diagnostic

file:///home/runner/work/SignalFlow-Studio/SignalFlow-Studio/scripts/apply-campaign-freshness.mjs:90
    "replacement": "                <div className={`review-workspace ${isCampaignStale ? \"has-stale-campaign\" : \"\"}`}>\n                  {isCampaignStale && (\n                    <div className=\"campaign-stale-banner\" role=\"alert\" aria-live=\"assertive\">\n                      <div className=\"campaign-stale-banner__copy\">\n                        <span className=\"campaign-stale-banner__label\">Source changed</span>\n                        <strong>These drafts belong to an earlier campaign snapshot.</strong>\n                      </div>\n                      <p>\n                        Review remains available, but SignalFlow blocks copy, export, and publishing until the\n                        campaign is regenerated from the current source.\n                      </p>\n                      {sourceChangeLabels.length > 0 && (\n                        <small>Changed: {sourceChangeLabels.join(\", ")}.</small>\n                      )}\n                      <button type=\"button\" onClick={() => navigateStudioFlow(\"destinations\")}>\n                        Review changes\n                      </button>\n                    </div>\n                  )}\n                  <div className=\"review-tabs\" aria-label=\"Campaign channels\">"
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  ^

SyntaxError: Unexpected token ')'
    at compileSourceTextModule (node:internal/modules/esm/utils:346:16)
    at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:110:18)
    at #translate (node:internal/modules/esm/loader:559:20)
    at afterLoad (node:internal/modules/esm/loader:612:29)
    at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:617:12)
    at #createModuleJob (node:internal/modules/esm/loader:640:36)
    at #getJobFromResolveResult (node:internal/modules/esm/loader:353:34)
    at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:321:41)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:680:25)

Node.js v22.23.1

@Ankit6149
Ankit6149 marked this pull request as ready for review July 27, 2026 18:05
@Ankit6149
Ankit6149 merged commit f644854 into master Jul 27, 2026
5 checks passed

@Ankit6149 Ankit6149 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post-merge branch audit: this task is already integrated into master through this merged PR. The source branch may still exist and its commit graph can appear diverged after squash merge; it must not be merged a second time. Any future work for #17 or another issue should start from current master on a new issue-specific branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P0][Plan 1] SF-002 — Mark review stale when generation inputs change

1 participant