← back to Norma Platform
Record campaign browser proof and explicit provider integration gaps
4de7056e3a0ed47697ad0e7691962d5dcf51569c · 2026-09-09 13:11:16 -0700 · Steve Abrams
Files touched
M components/campaigns/CampaignWorkspace.tsxM verification/e2e-proof.jsonA verification/platform/CTA-REPORT.mdA verification/platform/e2e-proof.jsonA verification/platform/openclaw-workspace.jsonA verification/platform/regression-gate.txtA verification/platform/screenshots/openclaw-riverside.jpg
Diff
commit 4de7056e3a0ed47697ad0e7691962d5dcf51569c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 9 13:11:16 2026 -0700
Record campaign browser proof and explicit provider integration gaps
---
components/campaigns/CampaignWorkspace.tsx | 2 +-
verification/e2e-proof.json | 179 ++++++++++++-
verification/platform/CTA-REPORT.md | 42 +++
verification/platform/e2e-proof.json | 172 ++++++++++++
verification/platform/openclaw-workspace.json | 297 +++++++++++++++++++++
verification/platform/regression-gate.txt | 154 +++++++++++
.../platform/screenshots/openclaw-riverside.jpg | Bin 0 -> 36676 bytes
7 files changed, 831 insertions(+), 15 deletions(-)
diff --git a/components/campaigns/CampaignWorkspace.tsx b/components/campaigns/CampaignWorkspace.tsx
index 8e1f33b..549dd87 100644
--- a/components/campaigns/CampaignWorkspace.tsx
+++ b/components/campaigns/CampaignWorkspace.tsx
@@ -110,7 +110,7 @@ export default function CampaignWorkspace() {
if(editor.type==='task'){path=editor.task?'/tasks/'+editor.task.id:'/tasks';method=editor.task?'PATCH':'POST';}
const res=await (await api(path,{method,body:JSON.stringify(data)})).json();setEditor(null);
if(editor.type==='campaign'&&!editor.campaign)selectCampaign(res.campaign.id);else await reload();
- }}/>}
+ }}/>}
</div>;
function renderTasks(){return <><div className={styles.sectionhead}><div><h2>The next good step.</h2><p className={styles.muted}>Organizer tasks and message drafts. “Ready” never sends a message.</p></div><button className={styles.button} disabled={!workspace?.campaigns.length} onClick={()=>setEditor({type:'task'})}><Plus size={16}/> Add follow-up</button></div>{tasks.length?<div className={styles.grid}>{tasks.map(t=><article className={styles.card} key={t.id}><div className={styles.cardhead}><span className={styles.eyebrow}>{t.channel}</span><Badge value={t.status}/></div><h3>{t.title}</h3>{t.supporter_name&&<p><strong>{t.supporter_name}</strong><br/><span className={styles.muted}>{t.supporter_email}</span></p>}{!selected&&<button className={styles.textbutton} onClick={()=>selectCampaign(t.campaign_id)}>View campaign: {t.campaign_title||'Open campaign'} ↗</button>}<p style={{whiteSpace:'pre-wrap'}}>{t.body}</p><DateChip value={t.created_at}/><div className={styles.cardfoot}><button className={styles.textbutton} onClick={()=>setEditor({type:'task',task:t})}>Edit draft</button><button className={styles.textbutton} disabled={busy} onClick={()=>void mutate('/tasks/'+t.id,{status:t.status==='draft'?'ready':t.status==='ready'?'completed':'draft'})}>{t.status==='draft'?'Mark ready':t.status==='ready'?'Mark task complete':'Reopen draft'}</button></div></article>)}</div>:<Empty title="Keep the momentum going"><p>New participation creates a review task automatically. Add your own email draft, event reminder, or volunteer checklist.</p></Empty>}</>;}
}
diff --git a/verification/e2e-proof.json b/verification/e2e-proof.json
index 7d14396..58fad68 100644
--- a/verification/e2e-proof.json
+++ b/verification/e2e-proof.json
@@ -1,21 +1,172 @@
{
- "intent": "Test Norma UX with browser agents",
+ "intent": "Original unified Norma campaign workspace combining petition, pledge, event, volunteer and advocacy journeys; verified local milestone, not complete ActBlue/MoveOn parity",
+ "ticket": "TK-11327-build-unified-norma-campaign-organizing",
"risk_tier": "R3",
- "environment": "isolated seeded local service http://127.0.0.1:7411 plus live http://127.0.0.1:7400",
- "timestamp": "2026-09-09T16:00:00-07:00",
- "baseline": "isolated seeded instance started with repository test accounts; live norma-email remained healthy",
+ "timestamp": "2026-09-09T20:08:32.082166+00:00",
+ "environment": {
+ "worktree": "/Users/macstudio3/Projects/Norma-platform",
+ "preview": "http://127.0.0.1:7416/campaigns",
+ "database": "sdcc_test",
+ "gate": "http://127.0.0.1:7417",
+ "live_service": "Existing :7400 was not restarted or migrated"
+ },
+ "build": {
+ "source_commit": "841ad4c0bbfa261fe9133cf76382e93c8708cb3b",
+ "next_build_id": "b7vXAy7wsC7WvPgYVxLsg",
+ "note": "Only trailing whitespace and verification documentation changed after tested build."
+ },
+ "baseline": "Existing Norma main 2cd602c; additive migration 026 on sdcc_test only. Seeded admin/staff sessions, no production credentials copied. External send integrations sinked.",
"commands": [
- "NORMA_TEST_URL=http://127.0.0.1:7400 npx playwright test tests/e2e --project=chromium"
+ "env SESSION_SECRET=<known-test-secret> DATABASE_URL=postgresql://127.0.0.1:5432/sdcc_test npm run build",
+ "npx tsc --noEmit",
+ "npx eslint <changed application paths>",
+ "bash scripts/campaign-preview.sh",
+ "node tests/campaign-platform.mjs",
+ "npx playwright test --config verification/platform/browser.config.ts",
+ "env GATE_PORT=7417 npm run gate",
+ "env NORMA_TEST_URL=http://127.0.0.1:7417 node tests/campaign-disabled.mjs",
+ "node scripts/seed-campaign-demo.mjs",
+ "OpenClaw dedicated norma-platform profile: seeded test login and Riverside workspace"
],
"assertions": [
- {"journey":"public /pulse","verdict":"PASS","evidence":"Playwright test 7; no real console errors"},
- {"journey":"public /pulse/petitions","verdict":"PASS","evidence":"Playwright test 8; no real console errors"},
- {"journey":"public /pulse/about","verdict":"PASS","evidence":"Playwright test 9; no real console errors"},
- {"journey":"wrong credentials remain on /login","verdict":"PASS","evidence":"Playwright test 6; visible invalid-credentials error"},
- {"journey":"admin/staff/pulse authenticated UX on isolated :7411","verdict":"PASS","evidence":"Chromium Playwright tests 1-5 passed; all three roles landed and admin navigated core tabs"},
- {"journey":"public and authenticated UX on WebKit :7411","verdict":"PARTIAL","evidence":"All documents returned 200 and admin login landed at /; WebKit emitted fetch/RSC errors during client navigation"}
+ {
+ "journey": "Private API rejects anonymous access and malformed/oversized bodies",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Campaign creation and read-back are persisted",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Staff org spoof and foreign campaign IDs are denied",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Five typed actions save and enforce type-specific validation",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Draft is hidden from public but available in authenticated preview",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Explicit sandbox activation makes campaign and actions available",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Petition intake is transactional and idempotent",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Pledges do not touch donation ledger; monthly intent is explicit",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Concurrent RSVP honors capacity and attendance lifecycle",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Shared supporter identity spans volunteer and advocacy actions",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Follow-up drafts save, transition, and remain unsent",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "CSV is tenant scoped and neutralizes spreadsheet formulas",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Cross-origin requests and duplicate hidden fields are rejected",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Pause immediately stops new public intake",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Chromium and WebKit UI creation, all five public forms, success, joined supporters, calendar/CSV downloads, check-in, follow-up editing, full-event alternative, mobile and staff deep link",
+ "verdict": "PASS",
+ "evidence": "platform/browser-results.json; 6 passed, 0 skipped, 0 flaky; screenshots and video artifacts"
+ },
+ {
+ "journey": "Existing regression battery: route/write sweeps, 20 security checks, 35 CRUD checks, 12 authz checks, 9 Chromium UI tests",
+ "verdict": "PASS",
+ "evidence": "platform/regression-gate.txt"
+ },
+ {
+ "journey": "Default activation and public intake off",
+ "verdict": "PASS",
+ "evidence": "platform/publication-results.json"
+ },
+ {
+ "journey": "Compilation and TypeScript",
+ "verdict": "PASS",
+ "evidence": "production build completed0; tsc completed0"
+ },
+ {
+ "journey": "Scoped application lint",
+ "verdict": "PASS",
+ "reason": "0 errors; 3 warnings (existing Sidebar unused constant and 2 React cleanup advisories)"
+ },
+ {
+ "journey": "Real money, subscription billing, message delivery and verified consent",
+ "verdict": "SKIP",
+ "reason": "Not implemented in local milestone; explicit provider/consent work tracked in follow-up tickets"
+ },
+ {
+ "journey": "Browserbase clean remote environment",
+ "verdict": "SKIP",
+ "reason": "Local-only loopback preview is not reachable from cloud; no public tunnel opened"
+ },
+ {
+ "journey": "Exhaustive testing of legacy settings, every theme and every external provider",
+ "verdict": "SKIP",
+ "reason": "Outside campaign change; no claim that every Norma control or competitor capability is verified"
+ },
+ {
+ "journey": "OpenClaw dedicated browser opens populated five-action Riverside campaign",
+ "verdict": "PASS",
+ "evidence": "platform/screenshots/openclaw-riverside.jpg; authenticated through normal test login endpoint"
+ }
],
- "cleanup": "No application data writes; no code changes; test artifacts retained",
- "verdict": "PARTIAL",
- "blocker": "WebKit client fetch/RSC failures remain to verify on reachable HTTPS staging; Browserbase cannot reach loopback"
+ "artifacts": {
+ "api": "platform/api-results.json",
+ "browser": "platform/browser-results.json",
+ "screenshots": "platform/screenshots",
+ "videos": "platform/browser-artifacts/**/video.webm (local, gitignored)",
+ "regression": "platform/regression-gate.txt",
+ "publication": "platform/publication-results.json",
+ "demo_ids": "platform/demo.json",
+ "runbook": "../docs/platform/RUNBOOK.md"
+ },
+ "review": {
+ "engineer": "Changed duplicate409 correction accepted and verified",
+ "designer": "Select height and receipt state fixed and browser tested; global toolbar advisory retained by prior user preference",
+ "user": "Contextual followups and full/closed event state fixed and tested",
+ "skeptic": "Export acceptance corrected to all-org export; no full-parity claim",
+ "strategist": "Local milestone accepted; full-parity request remains partial"
+ },
+ "cleanup": "API-owned campaign/action/participation/supporter operational fixtures removed; browser fixtures archived and demo retained. Receipt audit evidence retained. Gate and default-check servers stopped. Preview :7416 and dedicated OpenClaw profile retained. No production mutation, sending, remote push or funds moved.",
+ "followup_tickets": [
+ "TK-11334-norma-verified-consent-and-provider-sand",
+ "TK-11335-norma-payment-provider-and-donation-reco",
+ "TK-11336-norma-shared-contact-history-and-field-o"
+ ],
+ "verdict": "PASS for tested local campaign milestone; PARTIAL for full competitor parity"
}
diff --git a/verification/platform/CTA-REPORT.md b/verification/platform/CTA-REPORT.md
new file mode 100644
index 0000000..939c2e3
--- /dev/null
+++ b/verification/platform/CTA-REPORT.md
@@ -0,0 +1,42 @@
+# Campaign interaction verification
+
+Tested build: `b7vXAy7wsC7WvPgYVxLsg`; source commit `841ad4c`.
+
+## Verified interaction matrix
+
+| Journey | Chromium | WebKit | Evidence |
+| --- | --- | --- | --- |
+| Admin creates campaign, activates petition, submits, finds supporter and contextual follow-up | PASS | PASS | `browser-results.json`, campaign/receipt screenshots |
+| Staff narrow-screen workspace, keyboard dialog dismissal, shell deep link, 42px organization control | PASS | PASS | mobile screenshots |
+| Create and activate fundraiser, event, volunteer and advocacy forms | PASS | PASS | recorded all-forms browser journey |
+| Monthly pledge entry; no payment claim | PASS | PASS | fundraiser screenshots; API donation count unchanged |
+| RSVP and valid calendar-file download | PASS | PASS | event journey, downloaded VCALENDAR assertion |
+| Full event disabled upfront with alternatives | PASS | PASS | event journey after second RSVP |
+| Volunteer and advocacy participation | PASS | PASS | respective screenshots |
+| One supporter joins four action records; CSV download | PASS | PASS | persisted response and download assertions |
+| Attendance change, follow-up edit and ready transition, archive | PASS | PASS | recorded journey |
+| Console page errors and API 500 responses on tested campaign journeys | None | None | browser assertions |
+
+Recordings are retained locally under `browser-artifacts/**/video.webm`; screenshots are committed under `screenshots/`. Six tests passed, zero retries/flakes/skips. The broader existing Norma gate separately passed its nine Chromium browser journeys plus API/write/security/CRUD/authorization suites.
+
+## Review fixes independently verified
+
+Five separate critics reviewed through Engineer, Designer, User, Skeptic and Strategist lenses and logged their findings on TK-11327. Parent acceptance followed source inspection and updated real API/browser tests.
+
+- Engineer: changed duplicates with fresh submission keys had silently returned success. They now return 409, while identical retries retain one receipt and one follow-up.
+- Designer: WebKit select sizing and stale receipt totals. Selects now use normalized appearance; mobile engine assertions require at least 42px. Success receipts suppress stale progress and refresh authoritative action data.
+- User: repeated follow-ups lacked participant context; full/closed event forms still solicited input. Joined participant/campaign data is now visible on tasks; availability is exposed before RSVP.
+- Skeptic: plan inaccurately promised filtered export. Corrected to the implemented organization-wide export; UI and test agree.
+- Strategist: intake and drafts are a useful foundation, but payment/delivery/legacy-record integration are substantial remaining work. Explicit follow-up tickets preserve that distinction.
+
+The critics disagreed on milestone readiness, not the existence of full parity: initial FIX FIRST concerns were accepted and fixed, conditional local approval was satisfied by the parent tests, and the full-parity verdict remains REVISE. Highest-leverage next milestone: confirmed-consent follow-up through a provider sandbox to a second action.
+
+## Limits, not blanket certification
+
+- This is a campaign-journey matrix, not proof that every control in all of Norma works. Exhaustive permutations of legacy settings, themes, external connections, sort/density persistence and every field validation were not individually clicked.
+- Browserbase was skipped because the preview is loopback-only. No cloud reachability or public tunnel is claimed.
+- The existing global theme/age toolbar remains as previously requested; its unrelated Gmail label/mobile footprint is advisory UX debt.
+- OpenClaw opened a separate `norma-platform` profile for the populated demo because another task reused the shared browser tab. Its login/workspace opening supplements the recorded Chromium/WebKit journeys.
+- Actual money collection, recurring billing, verified consent, delivery, dialing and full competitor parity are not implemented or tested here.
+
+**Verdict: PASS for the named local journeys; PARTIAL for exhaustive CTA and full competitor parity.**
diff --git a/verification/platform/e2e-proof.json b/verification/platform/e2e-proof.json
new file mode 100644
index 0000000..58fad68
--- /dev/null
+++ b/verification/platform/e2e-proof.json
@@ -0,0 +1,172 @@
+{
+ "intent": "Original unified Norma campaign workspace combining petition, pledge, event, volunteer and advocacy journeys; verified local milestone, not complete ActBlue/MoveOn parity",
+ "ticket": "TK-11327-build-unified-norma-campaign-organizing",
+ "risk_tier": "R3",
+ "timestamp": "2026-09-09T20:08:32.082166+00:00",
+ "environment": {
+ "worktree": "/Users/macstudio3/Projects/Norma-platform",
+ "preview": "http://127.0.0.1:7416/campaigns",
+ "database": "sdcc_test",
+ "gate": "http://127.0.0.1:7417",
+ "live_service": "Existing :7400 was not restarted or migrated"
+ },
+ "build": {
+ "source_commit": "841ad4c0bbfa261fe9133cf76382e93c8708cb3b",
+ "next_build_id": "b7vXAy7wsC7WvPgYVxLsg",
+ "note": "Only trailing whitespace and verification documentation changed after tested build."
+ },
+ "baseline": "Existing Norma main 2cd602c; additive migration 026 on sdcc_test only. Seeded admin/staff sessions, no production credentials copied. External send integrations sinked.",
+ "commands": [
+ "env SESSION_SECRET=<known-test-secret> DATABASE_URL=postgresql://127.0.0.1:5432/sdcc_test npm run build",
+ "npx tsc --noEmit",
+ "npx eslint <changed application paths>",
+ "bash scripts/campaign-preview.sh",
+ "node tests/campaign-platform.mjs",
+ "npx playwright test --config verification/platform/browser.config.ts",
+ "env GATE_PORT=7417 npm run gate",
+ "env NORMA_TEST_URL=http://127.0.0.1:7417 node tests/campaign-disabled.mjs",
+ "node scripts/seed-campaign-demo.mjs",
+ "OpenClaw dedicated norma-platform profile: seeded test login and Riverside workspace"
+ ],
+ "assertions": [
+ {
+ "journey": "Private API rejects anonymous access and malformed/oversized bodies",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Campaign creation and read-back are persisted",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Staff org spoof and foreign campaign IDs are denied",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Five typed actions save and enforce type-specific validation",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Draft is hidden from public but available in authenticated preview",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Explicit sandbox activation makes campaign and actions available",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Petition intake is transactional and idempotent",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Pledges do not touch donation ledger; monthly intent is explicit",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Concurrent RSVP honors capacity and attendance lifecycle",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Shared supporter identity spans volunteer and advocacy actions",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Follow-up drafts save, transition, and remain unsent",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "CSV is tenant scoped and neutralizes spreadsheet formulas",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Cross-origin requests and duplicate hidden fields are rejected",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Pause immediately stops new public intake",
+ "verdict": "PASS",
+ "evidence": "platform/api-results.json"
+ },
+ {
+ "journey": "Chromium and WebKit UI creation, all five public forms, success, joined supporters, calendar/CSV downloads, check-in, follow-up editing, full-event alternative, mobile and staff deep link",
+ "verdict": "PASS",
+ "evidence": "platform/browser-results.json; 6 passed, 0 skipped, 0 flaky; screenshots and video artifacts"
+ },
+ {
+ "journey": "Existing regression battery: route/write sweeps, 20 security checks, 35 CRUD checks, 12 authz checks, 9 Chromium UI tests",
+ "verdict": "PASS",
+ "evidence": "platform/regression-gate.txt"
+ },
+ {
+ "journey": "Default activation and public intake off",
+ "verdict": "PASS",
+ "evidence": "platform/publication-results.json"
+ },
+ {
+ "journey": "Compilation and TypeScript",
+ "verdict": "PASS",
+ "evidence": "production build completed0; tsc completed0"
+ },
+ {
+ "journey": "Scoped application lint",
+ "verdict": "PASS",
+ "reason": "0 errors; 3 warnings (existing Sidebar unused constant and 2 React cleanup advisories)"
+ },
+ {
+ "journey": "Real money, subscription billing, message delivery and verified consent",
+ "verdict": "SKIP",
+ "reason": "Not implemented in local milestone; explicit provider/consent work tracked in follow-up tickets"
+ },
+ {
+ "journey": "Browserbase clean remote environment",
+ "verdict": "SKIP",
+ "reason": "Local-only loopback preview is not reachable from cloud; no public tunnel opened"
+ },
+ {
+ "journey": "Exhaustive testing of legacy settings, every theme and every external provider",
+ "verdict": "SKIP",
+ "reason": "Outside campaign change; no claim that every Norma control or competitor capability is verified"
+ },
+ {
+ "journey": "OpenClaw dedicated browser opens populated five-action Riverside campaign",
+ "verdict": "PASS",
+ "evidence": "platform/screenshots/openclaw-riverside.jpg; authenticated through normal test login endpoint"
+ }
+ ],
+ "artifacts": {
+ "api": "platform/api-results.json",
+ "browser": "platform/browser-results.json",
+ "screenshots": "platform/screenshots",
+ "videos": "platform/browser-artifacts/**/video.webm (local, gitignored)",
+ "regression": "platform/regression-gate.txt",
+ "publication": "platform/publication-results.json",
+ "demo_ids": "platform/demo.json",
+ "runbook": "../docs/platform/RUNBOOK.md"
+ },
+ "review": {
+ "engineer": "Changed duplicate409 correction accepted and verified",
+ "designer": "Select height and receipt state fixed and browser tested; global toolbar advisory retained by prior user preference",
+ "user": "Contextual followups and full/closed event state fixed and tested",
+ "skeptic": "Export acceptance corrected to all-org export; no full-parity claim",
+ "strategist": "Local milestone accepted; full-parity request remains partial"
+ },
+ "cleanup": "API-owned campaign/action/participation/supporter operational fixtures removed; browser fixtures archived and demo retained. Receipt audit evidence retained. Gate and default-check servers stopped. Preview :7416 and dedicated OpenClaw profile retained. No production mutation, sending, remote push or funds moved.",
+ "followup_tickets": [
+ "TK-11334-norma-verified-consent-and-provider-sand",
+ "TK-11335-norma-payment-provider-and-donation-reco",
+ "TK-11336-norma-shared-contact-history-and-field-o"
+ ],
+ "verdict": "PASS for tested local campaign milestone; PARTIAL for full competitor parity"
+}
diff --git a/verification/platform/openclaw-workspace.json b/verification/platform/openclaw-workspace.json
new file mode 100644
index 0000000..2df5d34
--- /dev/null
+++ b/verification/platform/openclaw-workspace.json
@@ -0,0 +1,297 @@
+{
+ "ok": true,
+ "format": "ai",
+ "targetId": "4934F80AB6AE463FC96B7C9226D1D744",
+ "url": "http://127.0.0.1:7416/campaigns",
+ "snapshot": "- button \"Previous theme\" [ref=e1]\n- button \"Next theme\" [ref=e2]\n- button \"Random theme\" [ref=e3]\n- combobox \"Pick inbox theme\" [ref=e4]\n- option \"🎨 1 · Classic Dense\" [ref=e5] [selected]\n- option \"🎨 2 · Notion Cards\" [ref=e6]\n- option \"🎨 3 · Linear Keyboard\" [ref=e7]\n- option \"🎨 4 · Apple Mail\" [ref=e8]\n- option \"🎨 5 · Front Conversation\" [ref=e9]\n- option \"🎨 6 · Hey Zero-Distraction\" [ref=e10]\n- option \"🎨 7 · Spotify Dark\" [ref=e11]\n- option \"🎨 8 · Minimal Mono\" [ref=e12]\n- option \"🎨 9 · Newsprint\" [ref=e13]\n- option \"🎨 10 · Glassmorphism\" [ref=e14]\n- option \"🎨 11 · Terminal\" [ref=e15]\n- option \"🎨 12 · Mochi Pastel\" [ref=e16]\n- option \"🎨 13 · High-Contrast A11y\" [ref=e17]\n- option \"🎨 14 · Material 3\" [ref=e18]\n- option \"🎨 15 · Synthwave\" [ref=e19]\n- option \"🎨 16 · Kanban Lanes\" [ref=e20]\n- option \"🎨 17 · Ottoman Tile\" [ref=e21]\n- option \"🎨 18 · Brutalist Concrete\" [ref=e22]\n- option \"🎨 19 · Library Catalog\" [ref=e23]\n- option \"🎨 20 · Wabi-Sabi\" [ref=e24]\n- option \"🎨 21 · Cyberpunk Rain\" [ref=e25]\n- option \"🎨 22 · Bauhaus\" [ref=e26]\n- option \"🎨 23 · Memphis 80s\" [ref=e27]\n- option \"🎨 24 · Vellum Manuscript\" [ref=e28]\n- option \"🎨 25 · Solarpunk\" [ref=e29]\n- option \"🎨 26 · Frutiger Aero\" [ref=e30]\n- option \"🎨 27 · Crossword Grid\" [ref=e31]\n- option \"🎨 28 · 8-Bit Pixel\" [ref=e32]\n- option \"🎨 29 · Watercolor\" [ref=e33]\n- option \"🎨 30 · Sakura\" [ref=e34]\n- option \"🎨 31 · Forest Walk\" [ref=e35]\n- option \"🎨 32 · Botanical Print\" [ref=e36]\n- link \"Gallery ↗\" [ref=e37]\n- button \"30 · Adult\" [ref=e38]\n- slider \"Adjust theme for viewer age\" [ref=e39]\n- link \"N Norma / Campaigns\" [ref=e40]\n- combobox \"Campaign organization\" [ref=e41]\n- option \"Choose an organization\" [ref=e42]\n- option \"AUTHZ-Tenant-B\" [ref=e43]\n- option \"Hacked Org\" [ref=e44]\n- option \"Riverside Community · Sandbox\" [ref=e45] [selected]\n- button \"Refresh workspace\" [ref=e46]\n- button \"Create campaign\" [ref=e47]\n- tab \"Campaigns\" [ref=e48] [selected]\n- tab \"Supporters\" [ref=e49]\n- tab \"Follow-ups\" [ref=e50]\n- tab \"Insights\" [ref=e51]\n- tab \"Connections\" [ref=e52]\n- searchbox \"Search campaigns\" [ref=e53]\n- combobox \"Status\" [ref=e54]\n- option \"All statuses\" [ref=e55] [selected]\n- option \"draft\" [ref=e56]\n- option \"active\" [ref=e57]\n- option \"paused\" [ref=e58]\n- option \"completed\" [ref=e59]\n- option \"archived\" [ref=e60]\n- combobox \"Sort\" [ref=e61]\n- option \"Newest\" [ref=e62] [selected]\n- option \"Oldest\" [ref=e63]\n- option \"Title A–Z\" [ref=e64]\n- option \"Most supporters\" [ref=e65]\n- slider \"Card size\" [ref=e66]\n- button \"Saturday park crew\" [ref=e67]\n- button \"Open campaign ↗\" [ref=e68]\n- button \"A reading room for everyone\" [ref=e69]\n- button \"Open campaign ↗\" [ref=e70] [nth=1]",
+ "refs": {
+ "e1": {
+ "role": "button",
+ "name": "Previous theme"
+ },
+ "e2": {
+ "role": "button",
+ "name": "Next theme"
+ },
+ "e3": {
+ "role": "button",
+ "name": "Random theme"
+ },
+ "e4": {
+ "role": "combobox",
+ "name": "Pick inbox theme"
+ },
+ "e5": {
+ "role": "option",
+ "name": "🎨 1 · Classic Dense"
+ },
+ "e6": {
+ "role": "option",
+ "name": "🎨 2 · Notion Cards"
+ },
+ "e7": {
+ "role": "option",
+ "name": "🎨 3 · Linear Keyboard"
+ },
+ "e8": {
+ "role": "option",
+ "name": "🎨 4 · Apple Mail"
+ },
+ "e9": {
+ "role": "option",
+ "name": "🎨 5 · Front Conversation"
+ },
+ "e10": {
+ "role": "option",
+ "name": "🎨 6 · Hey Zero-Distraction"
+ },
+ "e11": {
+ "role": "option",
+ "name": "🎨 7 · Spotify Dark"
+ },
+ "e12": {
+ "role": "option",
+ "name": "🎨 8 · Minimal Mono"
+ },
+ "e13": {
+ "role": "option",
+ "name": "🎨 9 · Newsprint"
+ },
+ "e14": {
+ "role": "option",
+ "name": "🎨 10 · Glassmorphism"
+ },
+ "e15": {
+ "role": "option",
+ "name": "🎨 11 · Terminal"
+ },
+ "e16": {
+ "role": "option",
+ "name": "🎨 12 · Mochi Pastel"
+ },
+ "e17": {
+ "role": "option",
+ "name": "🎨 13 · High-Contrast A11y"
+ },
+ "e18": {
+ "role": "option",
+ "name": "🎨 14 · Material 3"
+ },
+ "e19": {
+ "role": "option",
+ "name": "🎨 15 · Synthwave"
+ },
+ "e20": {
+ "role": "option",
+ "name": "🎨 16 · Kanban Lanes"
+ },
+ "e21": {
+ "role": "option",
+ "name": "🎨 17 · Ottoman Tile"
+ },
+ "e22": {
+ "role": "option",
+ "name": "🎨 18 · Brutalist Concrete"
+ },
+ "e23": {
+ "role": "option",
+ "name": "🎨 19 · Library Catalog"
+ },
+ "e24": {
+ "role": "option",
+ "name": "🎨 20 · Wabi-Sabi"
+ },
+ "e25": {
+ "role": "option",
+ "name": "🎨 21 · Cyberpunk Rain"
+ },
+ "e26": {
+ "role": "option",
+ "name": "🎨 22 · Bauhaus"
+ },
+ "e27": {
+ "role": "option",
+ "name": "🎨 23 · Memphis 80s"
+ },
+ "e28": {
+ "role": "option",
+ "name": "🎨 24 · Vellum Manuscript"
+ },
+ "e29": {
+ "role": "option",
+ "name": "🎨 25 · Solarpunk"
+ },
+ "e30": {
+ "role": "option",
+ "name": "🎨 26 · Frutiger Aero"
+ },
+ "e31": {
+ "role": "option",
+ "name": "🎨 27 · Crossword Grid"
+ },
+ "e32": {
+ "role": "option",
+ "name": "🎨 28 · 8-Bit Pixel"
+ },
+ "e33": {
+ "role": "option",
+ "name": "🎨 29 · Watercolor"
+ },
+ "e34": {
+ "role": "option",
+ "name": "🎨 30 · Sakura"
+ },
+ "e35": {
+ "role": "option",
+ "name": "🎨 31 · Forest Walk"
+ },
+ "e36": {
+ "role": "option",
+ "name": "🎨 32 · Botanical Print"
+ },
+ "e37": {
+ "role": "link",
+ "name": "Gallery ↗"
+ },
+ "e38": {
+ "role": "button",
+ "name": "30 · Adult"
+ },
+ "e39": {
+ "role": "slider",
+ "name": "Adjust theme for viewer age"
+ },
+ "e40": {
+ "role": "link",
+ "name": "N Norma / Campaigns"
+ },
+ "e41": {
+ "role": "combobox",
+ "name": "Campaign organization"
+ },
+ "e42": {
+ "role": "option",
+ "name": "Choose an organization"
+ },
+ "e43": {
+ "role": "option",
+ "name": "AUTHZ-Tenant-B"
+ },
+ "e44": {
+ "role": "option",
+ "name": "Hacked Org"
+ },
+ "e45": {
+ "role": "option",
+ "name": "Riverside Community · Sandbox"
+ },
+ "e46": {
+ "role": "button",
+ "name": "Refresh workspace"
+ },
+ "e47": {
+ "role": "button",
+ "name": "Create campaign"
+ },
+ "e48": {
+ "role": "tab",
+ "name": "Campaigns"
+ },
+ "e49": {
+ "role": "tab",
+ "name": "Supporters"
+ },
+ "e50": {
+ "role": "tab",
+ "name": "Follow-ups"
+ },
+ "e51": {
+ "role": "tab",
+ "name": "Insights"
+ },
+ "e52": {
+ "role": "tab",
+ "name": "Connections"
+ },
+ "e53": {
+ "role": "searchbox",
+ "name": "Search campaigns"
+ },
+ "e54": {
+ "role": "combobox",
+ "name": "Status"
+ },
+ "e55": {
+ "role": "option",
+ "name": "All statuses"
+ },
+ "e56": {
+ "role": "option",
+ "name": "draft"
+ },
+ "e57": {
+ "role": "option",
+ "name": "active"
+ },
+ "e58": {
+ "role": "option",
+ "name": "paused"
+ },
+ "e59": {
+ "role": "option",
+ "name": "completed"
+ },
+ "e60": {
+ "role": "option",
+ "name": "archived"
+ },
+ "e61": {
+ "role": "combobox",
+ "name": "Sort"
+ },
+ "e62": {
+ "role": "option",
+ "name": "Newest"
+ },
+ "e63": {
+ "role": "option",
+ "name": "Oldest"
+ },
+ "e64": {
+ "role": "option",
+ "name": "Title A–Z"
+ },
+ "e65": {
+ "role": "option",
+ "name": "Most supporters"
+ },
+ "e66": {
+ "role": "slider",
+ "name": "Card size"
+ },
+ "e67": {
+ "role": "button",
+ "name": "Saturday park crew"
+ },
+ "e68": {
+ "role": "button",
+ "name": "Open campaign ↗",
+ "nth": 0
+ },
+ "e69": {
+ "role": "button",
+ "name": "A reading room for everyone"
+ },
+ "e70": {
+ "role": "button",
+ "name": "Open campaign ↗",
+ "nth": 1
+ }
+ },
+ "stats": {
+ "lines": 70,
+ "chars": 2712,
+ "refs": 70,
+ "interactive": 70
+ }
+}
diff --git a/verification/platform/regression-gate.txt b/verification/platform/regression-gate.txt
new file mode 100644
index 0000000..27e90aa
--- /dev/null
+++ b/verification/platform/regression-gate.txt
@@ -0,0 +1,154 @@
+
+> norma@0.1.4 gate
+> bash scripts/predeploy-gate.sh
+
+[gate] starting isolated instance on :7417 (sdcc_test, sends sinked)…
+[gate] instance ready (Ready marker + /login 200×2) — running durable battery…
+──────── api-smoke ────────
+
+=== Norma API smoke — http://127.0.0.1:7417 ===
+routes: 273 total · 197 GET-able · 76 mutate-only (skipped in GET sweep)
+
+swept 197 GET route(s) × {anon, admin}
+
+VISIBILITY CENSUS — 4 GET route(s) answer 200 unauthenticated (audit, not a fail):
+ · /api/auth/session
+ · /api/pulse/petitions
+ · /api/pulse/stats
+ · /api/pulse/topics
+
+PASS — 0 crashes + 0 swallowed query-errors across 197 GET routes.
+
+✔ api-smoke green (197 swept, 4 public, 0 crashes, 0 swallowed)
+
+[gate] api-smoke: PASS
+──────── security-regression ────────
+
+=== Norma security regression — http://127.0.0.1:7417 ===
+
+ PASS admin login → 200 + role admin + cookie
+ PASS staff login → 200 + role staff
+ PASS pulse login → 200 + role pulse
+ PASS wrong password → 401
+ PASS non-JSON body → 400 (not 500)
+ PASS login page shows no OAuth buttons
+ PASS social /api/social/posts/pending → 403 for pulse
+ PASS social /api/social/analytics/dashboard → 403 for pulse
+ PASS social /api/social/bulk-schedule → 403 for pulse
+ PASS social pending → 200 for admin
+ PASS impersonate DELETE with bogus imp-by → 400 (no escalation)
+ PASS registry → 200 for admin
+ PASS registry → 403 for pulse
+ PASS /api/users → 403 for staff
+ PASS /api/users → 200 for admin
+ PASS petition-sign flood → 429 (rate limited)
+ PASS petition CREATE flood → 429 (rate limited)
+ PASS /api/petitions unauth → not 200 (auth required)
+ PASS brute-forced username → 429 (locked)
+ PASS DIFFERENT username still logs in (no tenant-wide lockout)
+
+=== 20 passed, 0 failed ===
+[gate] security-regression: PASS
+──────── write-smoke ────────
+
+=== Norma write-path smoke — http://127.0.0.1:7417 ===
+219 mutating (route,verb) pair(s) across POST/PUT/PATCH/DELETE
+
+admin org scope: 9e0fca77-3168-4772-bc67-6fbee6901be4
+
+probed 219 unauth · fired 155 as admin (64 external/AI/send routes probed unauth-only)
+
+PASS — every mutation gates unauth (4xx) + no crash on empty body.
+
+✔ write-smoke green (219 pairs, 0 crashes, 0 unauth-writes)
+
+[gate] write-smoke: PASS
+──────── crud-lifecycle ────────
+
+=== Norma CRUD lifecycle — http://127.0.0.1:7417 ===
+
+organizations id: 0ab4542f-8bfc-4a4d-991b-69024620a278 · nonprofit_accounts id: 2b99a35f-c43d-4376-9f57-dd78423226fb
+
+clients
+ PASS clients: create → 2xx + id
+ PASS clients: read-back → 200
+ PASS clients: created first_name persisted
+ PASS clients: update → 2xx
+ PASS clients: update city persisted
+ PASS clients: delete → 2xx
+ PASS clients: gone → 404
+
+donations
+ PASS donations: create → 2xx + id
+ PASS donations: read-back → 200
+ PASS donations: created amount persisted
+ PASS donations: update → 2xx
+ PASS donations: update notes persisted
+ PASS donations: delete → 2xx
+ PASS donations: gone → 404
+
+grants
+ PASS grants: create → 2xx + id
+ PASS grants: read-back → 200
+ PASS grants: created title persisted
+ PASS grants: update → 2xx
+ PASS grants: update notes persisted
+ PASS grants: delete → 2xx
+ PASS grants: gone → 404
+
+library
+ PASS library: create → 2xx + id
+ PASS library: read-back → 200
+ PASS library: created title persisted
+ PASS library: update → 2xx
+ PASS library: update title persisted
+ PASS library: delete → 2xx
+ PASS library: gone → 404
+
+journalists
+ PASS journalists: create → 2xx + id
+ PASS journalists: read-back → 200
+ PASS journalists: created name persisted
+ PASS journalists: update → 2xx
+ PASS journalists: update beat persisted
+ PASS journalists: delete → 2xx
+ PASS journalists: gone → 404
+
+=== 35 passed, 0 failed ===
+
+[gate] crud-lifecycle: PASS
+──────── authz-matrix ────────
+
+=== Norma authz-matrix + tenant-isolation — http://127.0.0.1:7417 ===
+
+tenant A (teststaff): 2b99a35f-c43d-4376-9f57-dd78423226fb
+tenant B (foreign): 485fd4e5-192e-4ff7-bcb0-d3a9a60669c0
+
+ROLE MATRIX — 180 GET + 201 mutation handlers × roles
+ under-restrictions (security): 0 · over-restrictions: 0
+
+TENANT ISOLATION — staff(A) sees A's records, never B's
+
+=== 12 passed, 0 failed ===
+✔ authz + isolation green
+[gate] authz-matrix: PASS
+──────── e2e-playwright ────────
+
+Running 9 tests using 1 worker
+
+(node:45504) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
+(Use `node --trace-warnings ...` to show where the warning was created)
+ ✓ 1 [chromium] › tests/e2e/admin.spec.ts:18:5 › admin loads the shell and navigates core tabs with no console errors (1.8s)
+ ✓ 2 [chromium] › tests/e2e/auth.spec.ts:35:7 › admin logs in, leaves /login, no console errors (691ms)
+ ✓ 3 [chromium] › tests/e2e/auth.spec.ts:35:7 › staff logs in, leaves /login, no console errors (775ms)
+ ✓ 4 [chromium] › tests/e2e/auth.spec.ts:35:7 › pulse logs in, leaves /login, no console errors (597ms)
+ ✓ 5 [chromium] › tests/e2e/auth.spec.ts:46:5 › pulse tier lands on the pulse surface (510ms)
+ ✓ 6 [chromium] › tests/e2e/auth.spec.ts:52:5 › wrong credentials stay on /login and surface an error (2.0s)
+ ✓ 7 [chromium] › tests/e2e/pulse.spec.ts:17:7 › public /pulse renders without a login wall or console errors (416ms)
+ ✓ 8 [chromium] › tests/e2e/pulse.spec.ts:17:7 › public /pulse/petitions renders without a login wall or console errors (387ms)
+ ✓ 9 [chromium] › tests/e2e/pulse.spec.ts:17:7 › public /pulse/about renders without a login wall or console errors (391ms)
+
+ 9 passed (8.3s)
+[gate] e2e-playwright: PASS
+════════════════════════════════════════
+[gate] ✅ all suites green — safe to deploy.
diff --git a/verification/platform/screenshots/openclaw-riverside.jpg b/verification/platform/screenshots/openclaw-riverside.jpg
new file mode 100644
index 0000000..1bf3a1f
Binary files /dev/null and b/verification/platform/screenshots/openclaw-riverside.jpg differ
← c2e108f Build unified campaign workspace with five action journeys a
·
back to Norma Platform
·
Capture populated campaign preview at desktop width 1e82ed2 →