[object Object]

← back to IWasCute

Deploy IWasCute verification with preserved analytics and portable runtime proof

443abe50b945160c4ff1406bff3b3f46bc61dbe7 · 2026-09-10 10:42:25 -0700 · Steve Abrams

Files touched

Diff

commit 443abe50b945160c4ff1406bff3b3f46bc61dbe7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 10 10:42:25 2026 -0700

    Deploy IWasCute verification with preserved analytics and portable runtime proof
---
 src/app/api/admin/route.ts                         |   9 ++-
 src/app/layout.tsx                                 |  11 +++
 verification/TK-11380/PRODUCTION.md                |  35 ++++++++++
 verification/TK-11380/REVIEW.md                    |   2 +-
 verification/TK-11380/canary/login.png             | Bin 0 -> 76365 bytes
 verification/TK-11380/deploy-remote.sh             |  77 +++++++++++++++++++++
 verification/TK-11380/live/3x-e2e.png              | Bin 0 -> 88563 bytes
 verification/TK-11380/live/3x-render.png           | Bin 0 -> 211523 bytes
 verification/TK-11380/live/build-output.txt        |  45 ++++++++++++
 verification/TK-11380/live/login.png               | Bin 0 -> 76462 bytes
 verification/TK-11380/live/normalized-links.json   |  10 +++
 .../TK-11380/live/post-activation-http.json        |   1 +
 verification/TK-11380/live/results.json            |  64 +++++++++++++++++
 verification/TK-11380/normalize-build.cjs          |  20 ++++++
 verification/TK-11380/rollback-test.cjs            |  21 ++++++
 verification/TK-11380/verify-http.cjs              |  21 ++++++
 verification/TK-11380/verify.cjs                   |  13 +++-
 verification/e2e-proof.json                        |  69 ++++++++++++++++++
 18 files changed, 393 insertions(+), 5 deletions(-)

diff --git a/src/app/api/admin/route.ts b/src/app/api/admin/route.ts
index 2a89946..e208e08 100644
--- a/src/app/api/admin/route.ts
+++ b/src/app/api/admin/route.ts
@@ -172,7 +172,14 @@ async function getUploadDetail(params: URLSearchParams) {
     WHERE u.id = $1
   `, [id])
 
-  const photos = await query(`SELECT * FROM photos WHERE upload_id = $1 ORDER BY created_at`, [id])
+  const photos = await query(`
+    SELECT id, upload_id, user_id, filename, file_size, mime_type,
+           storage_path, thumbnail_path, exif_date_taken, exif_year,
+           exif_latitude, exif_longitude, exif_camera_make, exif_camera_model,
+           exif_width, exif_height, clearance_level, view_count, license_count,
+           total_earnings, directory_name, created_at
+    FROM photos WHERE upload_id = $1 ORDER BY created_at
+  `, [id])
 
   return NextResponse.json({
     upload: upload.rows[0] || null,
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index d55a4e6..7d23708 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,4 +1,5 @@
 import type { Metadata } from "next";
+import Script from "next/script";
 import { Geist, Geist_Mono } from "next/font/google";
 import "./globals.css";
 import VersionSwitcher from "@/components/shared/VersionSwitcher";
@@ -31,6 +32,16 @@ export default function RootLayout({
       className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
     >
       <body className="min-h-full flex flex-col">
+        <Script
+          src="https://www.googletagmanager.com/gtag/js?id=G-2DHDBP8R78"
+          strategy="afterInteractive"
+        />
+        <Script id="ga4-init" strategy="afterInteractive">
+          {`window.dataLayer = window.dataLayer || [];
+function gtag(){dataLayer.push(arguments);}
+gtag("js", new Date());
+gtag("config", "G-2DHDBP8R78");`}
+        </Script>
         {children}
         <VersionSwitcher />
       </body>
diff --git a/verification/TK-11380/PRODUCTION.md b/verification/TK-11380/PRODUCTION.md
new file mode 100644
index 0000000..c763c6f
--- /dev/null
+++ b/verification/TK-11380/PRODUCTION.md
@@ -0,0 +1,35 @@
+# IWasCute production deployment — TK-11380
+
+Passed on 2026-09-10. Existing PM2 `gap-iwascute` continues at `/root/Projects/iwascute`, port9636. No nginx, DNS, account or photo records changed.
+
+The reviewed ownership metadata is live and the AdSense executable is removed across public, login and account routes. Existing GA4 `G-2DHDBP8R78` remains installed and its configuration executes. The deployment guard also required replacing an existing broad photo projection with the same22 explicit columns, verified against the live schema. A read-only `LIMIT 0` query confirmed those fields without returning user rows.
+
+## Deployment and verification
+
+- Approved window: 2026-09-10T17:19:10Z through 2026-09-11T09:19:10Z. Activation enforces the expiry; rollback remains available for recovery.
+- Remote baseline snapshot: `1dd9768`. Existing non-secret tracked changes and ads.txt were committed; unrelated legacy files remained untouched/untracked. A private source backup preserves the full original worktree.
+- Built in `/root/Projects/.iwascute-TK11380-20260910T172500Z` using `npm ci --ignore-scripts --no-audit --no-fund` then `NEXT_TELEMETRY_DISABLED=1 npm run build` while production continued running. Build ID: `CDMq148tzzvoWWsHCxsrN`.
+- First activation exposed a Turbopack package-link relocation defect: admin returned500 because the generated pg link still referenced the staging directory. Immediately restored previous build/modules and confirmed admin401. No completion claim was made for that attempt.
+- Normalized the single generated pg link to `../../node_modules/pg`; moved the whole candidate into another directory and verified metadata, analytics, ads.txt and admin GET/POST401. Re-activated with a production HTTP verifier inside the automatic rollback guard.
+- Final production source commit: `d0dc519`. Dependencies and compiled artifacts were swapped together; the existing process identity and runtime environment remained intact.
+- `verify.cjs`: nine public/account-route HTML and hydrated checks, home→v1→login real clicks, both unauthenticated admin methods401, exact ads.txt, GA configuration, zero attempted advertising requests. Third-party requests blocked during this test. See `live/results.json` and `live/login.png`.
+- `/3x`: HTTP, rendered screenshot, browser interaction and Chrome allPASS (4/4 available); Safari and Firefox skipped because their engines are unavailable. See `live/3x-render.png` and `live/3x-e2e.png`.
+- Pre-deploy guard5/5PASS; live nginx syntax valid with existing unrelated duplicate-name warnings. No nginx modification by this task.
+
+## Rollback
+
+Retained state: `/root/.local/state/iwascute-deploy/TK11380-20260910T172500Z/` (private directory).
+
+`previous.next` and `previous.node_modules` contain the pre-deployment runtime. Source backup is `source/`. Build/install and activation logs remain here. Run the saved task script:
+
+```sh
+ssh kamatera 'bash /tmp/TK11380-iwascute-deploy.sh rollback'
+```
+
+The script stops only `gap-iwascute`, retains the rejected artifacts, restores the previous runtime, reverses only the task patch when applicable and restarts the same process. Rollback was actually exercised after the first failed activation. The filesystem recovery function additionally passed full-swap and partial-swap fixture tests (PM2 and Git mocked).
+
+Local tunnel and both remote canary processes were stopped. Staging source and rollback artifacts remain intentionally for recovery; no scheduled process was added.
+
+## Limits
+
+This proves the deployed ownership-verification and advertising-execution boundary. It does not establish AdSense approval or general content/privacy readiness. No authenticated account, photo upload, license purchase or Google account mutation was exercised. Direct-route hydration uses a750ms bounded observation, supplemented by real navigation and request assertions. Analytics installation/configuration is verified; receipt by Google Analytics was not tested because third-party requests were blocked.
diff --git a/verification/TK-11380/REVIEW.md b/verification/TK-11380/REVIEW.md
index 096f51a..20ca794 100644
--- a/verification/TK-11380/REVIEW.md
+++ b/verification/TK-11380/REVIEW.md
@@ -1,6 +1,6 @@
 # TK-11380 — three sites requiring review
 
-Local work is verified. Production changes and Google review submission have not occurred.
+Historical local review. IWasCute production deployment subsequently passed on 2026-09-10; see [PRODUCTION.md](PRODUCTION.md) for the final scope, rollback and evidence. Google review submission is outside this deployment handoff.
 
 ## IWasCute
 
diff --git a/verification/TK-11380/canary/login.png b/verification/TK-11380/canary/login.png
new file mode 100644
index 0000000..b52ceba
Binary files /dev/null and b/verification/TK-11380/canary/login.png differ
diff --git a/verification/TK-11380/deploy-remote.sh b/verification/TK-11380/deploy-remote.sh
new file mode 100644
index 0000000..0dd10c1
--- /dev/null
+++ b/verification/TK-11380/deploy-remote.sh
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+set -euo pipefail
+APP=/root/Projects/iwascute
+RUN=TK11380-20260910T172500Z
+STATE=/root/.local/state/iwascute-deploy/$RUN
+STAGE=/root/Projects/.iwascute-$RUN
+PATCH=/tmp/TK11380-iwascute.patch
+cd "$APP"
+if [[ "${1:-}" == prepare || "${1:-}" == activate ]]; then
+  test "$(date +%s)" -lt 1789118350 || { echo 'AdSense approval window expired' >&2; exit 3; }
+fi
+recover() {
+  trap - ERR
+  pm2 stop gap-iwascute > "$STATE/recovery-stop.log" 2>&1 || true
+  for part in .next node_modules; do
+    local previous="$STATE/previous.${part#.}"
+    if [[ -d "$previous" ]]; then
+      if [[ -e "$APP/$part" ]]; then mv "$APP/$part" "$STATE/failed.${part#.}"; fi
+      mv "$previous" "$APP/$part"
+    fi
+  done
+  if git apply -R --check "$PATCH"; then git apply -R "$PATCH"; fi
+  pm2 restart gap-iwascute > "$STATE/recovery-start.log" 2>&1
+  printf 'ROLLED_BACK %s\n' "$STATE"
+}
+case "${1:-}" in
+  prepare)
+    test ! -e "$STATE"
+    test ! -e "$STAGE"
+    git apply --check "$PATCH"
+    nginx -t
+    install -d -m 700 "$STATE" "$STAGE"
+    git status --short > "$STATE/before-status.txt"
+    git rev-parse HEAD > "$STATE/before-head.txt"
+    sha256sum src/app/layout.tsx src/app/api/admin/route.ts > "$STATE/before-sha256.txt"
+    rsync -a --exclude=.git --exclude=.next --exclude=node_modules ./ "$STATE/source/"
+    # Only reviewed non-secret files enter the existing Git repository.
+    git add -- .gitignore package.json package-lock.json src/app/layout.tsx public/ads.txt
+    git -c user.name='Steve Abrams' -c user.email=steve@designerwallcoverings.com commit -m 'snapshot before TK-11380 IWasCute targeted deployment'
+    git rev-parse HEAD > "$STATE/snapshot-head.txt"
+    rsync -a --exclude=.git --exclude=.next --exclude=node_modules ./ "$STAGE/"
+    cd "$STAGE"
+    git apply --check "$PATCH"
+    git apply "$PATCH"
+    npm ci --ignore-scripts --no-audit --no-fund > "$STATE/install.log" 2>&1
+    NEXT_TELEMETRY_DISABLED=1 npm run build > "$STATE/build.log" 2>&1
+    node /tmp/TK11380-iwascute-normalize.cjs "$STAGE" > "$STATE/normalized-links.json"
+    test -s .next/BUILD_ID
+    cat .next/BUILD_ID > "$STATE/new-build-id.txt"
+    printf 'PREPARED %s\n' "$STATE"
+    ;;
+  activate)
+    test -s "$STATE/new-build-id.txt"
+    test ! -e "$STATE/previous.next"
+    test ! -e "$STATE/previous.node_modules"
+    sha256sum --check "$STATE/before-sha256.txt"
+    git apply --check "$PATCH"
+    git apply "$PATCH"
+    trap 'recover; exit 1' ERR
+    pm2 stop gap-iwascute > "$STATE/activation-stop.log" 2>&1
+    mv .next "$STATE/previous.next"
+    mv node_modules "$STATE/previous.node_modules"
+    mv "$STAGE/.next" .next
+    mv "$STAGE/node_modules" node_modules
+    pm2 restart gap-iwascute > "$STATE/activation-start.log" 2>&1
+    sleep 1
+    node /tmp/TK11380-iwascute-http.cjs http://127.0.0.1:9636 > "$STATE/post-activation-http.json"
+    trap - ERR
+    printf 'ACTIVATED %s\n' "$STATE"
+    ;;
+  rollback)
+    test -d "$STATE/previous.next"
+    test -d "$STATE/previous.node_modules"
+    recover
+    ;;
+  *) echo 'Expected prepare, activate or rollback' >&2; exit 2 ;;
+esac
diff --git a/verification/TK-11380/live/3x-e2e.png b/verification/TK-11380/live/3x-e2e.png
new file mode 100644
index 0000000..ca5af6a
Binary files /dev/null and b/verification/TK-11380/live/3x-e2e.png differ
diff --git a/verification/TK-11380/live/3x-render.png b/verification/TK-11380/live/3x-render.png
new file mode 100644
index 0000000..c7b68d2
Binary files /dev/null and b/verification/TK-11380/live/3x-render.png differ
diff --git a/verification/TK-11380/live/build-output.txt b/verification/TK-11380/live/build-output.txt
new file mode 100644
index 0000000..f74cd97
--- /dev/null
+++ b/verification/TK-11380/live/build-output.txt
@@ -0,0 +1,45 @@
+
+> iwascute@0.1.0 build
+> next build
+
+⚠ Warning: Next.js inferred your workspace root, but it may not be correct.
+ We detected multiple lockfiles and selected the directory of /root/package-lock.json as the root directory.
+ To silence this warning, set `turbopack.root` in your Next.js config, or consider removing one of the lockfiles if it's not needed.
+   See https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#root-directory for more information.
+ Detected additional lockfiles: 
+   * /root/Projects/.iwascute-TK11380-20260910T172500Z/package-lock.json
+
+▲ Next.js 16.2.3 (Turbopack)
+
+  Creating an optimized production build ...
+✓ Compiled successfully in 2.5s
+  Running TypeScript ...
+  Finished TypeScript in 4.2s ...
+  Collecting page data using 15 workers ...
+  Generating static pages using 15 workers (0/14) ...
+  Generating static pages using 15 workers (3/14) 

+  Generating static pages using 15 workers (6/14) 

+Couldn't load fs
+Couldn't load zlib
+  Generating static pages using 15 workers (10/14) 

+✓ Generating static pages using 15 workers (14/14) in 632ms
+  Finalizing page optimization ...
+
+Route (app)
+┌ ○ /
+├ ○ /_not-found
+├ ○ /admin
+├ ƒ /api/admin
+├ ƒ /api/auth
+├ ○ /browse
+├ ○ /dashboard
+├ ○ /licensor
+├ ○ /login
+├ ○ /v1
+├ ○ /v2
+└ ○ /v3
+
+
+○  (Static)   prerendered as static content
+ƒ  (Dynamic)  server-rendered on demand
+
diff --git a/verification/TK-11380/live/login.png b/verification/TK-11380/live/login.png
new file mode 100644
index 0000000..814b0f6
Binary files /dev/null and b/verification/TK-11380/live/login.png differ
diff --git a/verification/TK-11380/live/normalized-links.json b/verification/TK-11380/live/normalized-links.json
new file mode 100644
index 0000000..c32ff46
--- /dev/null
+++ b/verification/TK-11380/live/normalized-links.json
@@ -0,0 +1,10 @@
+{
+  "app": "/root/Projects/.iwascute-TK11380-20260910T172500Z",
+  "links": [
+    {
+      "name": "pg-a59f420146f41206",
+      "before": "../../../../Projects/.iwascute-TK11380-20260910T172500Z/node_modules/pg",
+      "after": "../../node_modules/pg"
+    }
+  ]
+}
diff --git a/verification/TK-11380/live/post-activation-http.json b/verification/TK-11380/live/post-activation-http.json
new file mode 100644
index 0000000..4eddf14
--- /dev/null
+++ b/verification/TK-11380/live/post-activation-http.json
@@ -0,0 +1 @@
+{"timestamp":"2026-09-10T17:38:35.878Z","base":"http://127.0.0.1:9636","metadata":"PASS","analytics":"PASS","adminGetPost":401,"adsTxt":"PASS","noAdLoader":"PASS"}
diff --git a/verification/TK-11380/live/results.json b/verification/TK-11380/live/results.json
new file mode 100644
index 0000000..5127e44
--- /dev/null
+++ b/verification/TK-11380/live/results.json
@@ -0,0 +1,64 @@
+{
+  "timestamp": "2026-09-10T17:38:53.317Z",
+  "base": "https://iwascute.com",
+  "results": [
+    {
+      "path": "/",
+      "html": "PASS",
+      "hydrated": "PASS"
+    },
+    {
+      "path": "/v1",
+      "html": "PASS",
+      "hydrated": "PASS"
+    },
+    {
+      "path": "/v2",
+      "html": "PASS",
+      "hydrated": "PASS"
+    },
+    {
+      "path": "/v3",
+      "html": "PASS",
+      "hydrated": "PASS"
+    },
+    {
+      "path": "/browse",
+      "html": "PASS",
+      "hydrated": "PASS"
+    },
+    {
+      "path": "/login",
+      "html": "PASS",
+      "hydrated": "PASS"
+    },
+    {
+      "path": "/licensor",
+      "html": "PASS",
+      "hydrated": "PASS"
+    },
+    {
+      "path": "/dashboard",
+      "html": "PASS",
+      "hydrated": "PASS"
+    },
+    {
+      "path": "/admin",
+      "html": "PASS",
+      "hydrated": "PASS"
+    },
+    {
+      "journey": "home -> v1 -> login",
+      "adRequests": 0,
+      "verdict": "PASS"
+    },
+    {
+      "adminWithoutCookie": 401,
+      "adminPostWithoutCookie": 401,
+      "analyticsConfig": "G-2DHDBP8R78",
+      "adsTxt": "PASS"
+    }
+  ],
+  "adRequests": [],
+  "thirdPartyRequestsBlocked": true
+}
diff --git a/verification/TK-11380/normalize-build.cjs b/verification/TK-11380/normalize-build.cjs
new file mode 100644
index 0000000..65f94e0
--- /dev/null
+++ b/verification/TK-11380/normalize-build.cjs
@@ -0,0 +1,20 @@
+const fs = require('node:fs');
+const path = require('node:path');
+const assert = require('node:assert/strict');
+const app = fs.realpathSync(process.argv[2]);
+const modules = path.join(app, 'node_modules');
+const links = path.join(app, '.next', 'node_modules');
+const changed = [];
+for (const name of fs.readdirSync(links)) {
+  const link = path.join(links, name);
+  if (!fs.lstatSync(link).isSymbolicLink()) continue;
+  const target = fs.realpathSync(link);
+  assert.ok(target.startsWith(modules + path.sep), 'External package must resolve inside candidate node_modules');
+  const relative = path.relative(path.dirname(link), target);
+  const before = fs.readlinkSync(link);
+  fs.unlinkSync(link);
+  fs.symlinkSync(relative, link);
+  assert.equal(fs.realpathSync(link), target);
+  changed.push({ name, before, after: relative });
+}
+console.log(JSON.stringify({ app, links: changed }, null, 2));
diff --git a/verification/TK-11380/rollback-test.cjs b/verification/TK-11380/rollback-test.cjs
new file mode 100644
index 0000000..0fa2074
--- /dev/null
+++ b/verification/TK-11380/rollback-test.cjs
@@ -0,0 +1,21 @@
+const fs = require('node:fs');
+const os = require('node:os');
+const path = require('node:path');
+const assert = require('node:assert/strict');
+const { execFileSync } = require('node:child_process');
+const script = fs.readFileSync(path.join(__dirname, 'deploy-remote.sh'), 'utf8');
+const recover = script.slice(script.indexOf('recover() {'), script.indexOf('\ncase '));
+for (const partial of [false, true]) {
+  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'iwascute-rollback-'));
+  const app = path.join(dir, 'app'), state = path.join(dir, 'state');
+  fs.mkdirSync(app); fs.mkdirSync(state);
+  for (const item of ['.next', 'node_modules']) {
+    const old = partial && item === 'node_modules' ? path.join(app, item) : path.join(state, 'previous.' + item.replace(/^\./, ''));
+    fs.mkdirSync(old); fs.writeFileSync(path.join(old, 'identity'), 'old');
+    if (!partial) { fs.mkdirSync(path.join(app, item)); fs.writeFileSync(path.join(app, item, 'identity'), 'new'); }
+  }
+  execFileSync('bash', ['-c', 'set -eu\npm2(){ :; }\ngit(){ return 0; }\n' + recover + '\nrecover'], { env: { ...process.env, APP: app, STATE: state, PATCH: '/unused-in-rehearsal' } });
+  for (const item of ['.next', 'node_modules']) assert.equal(fs.readFileSync(path.join(app, item, 'identity'), 'utf8'), 'old');
+  if (!partial) for (const item of ['next', 'node_modules']) assert.equal(fs.readFileSync(path.join(state, 'failed.' + item, 'identity'), 'utf8'), 'new');
+}
+console.log('PASS rollback filesystem rehearsal: complete and partial activation; PM2/Git mocked');
diff --git a/verification/TK-11380/verify-http.cjs b/verification/TK-11380/verify-http.cjs
new file mode 100644
index 0000000..d4cc980
--- /dev/null
+++ b/verification/TK-11380/verify-http.cjs
@@ -0,0 +1,21 @@
+const assert = require('node:assert/strict');
+const base = process.argv[2];
+(async () => {
+  for (const path of ['/', '/login', '/admin']) {
+    const r = await fetch(base + path);
+    assert.equal(r.status, 200);
+    const html = await r.text();
+    assert.ok(html.includes('google-adsense-account'));
+    assert.ok(html.includes('G-2DHDBP8R78'));
+    assert.ok(!html.includes('adsbygoogle'));
+  }
+  for (const method of ['GET', 'POST']) {
+    const r = await fetch(base + '/api/admin', { method });
+    assert.equal(r.status, 401);
+    assert.deepEqual(await r.json(), { error: 'Unauthorized' });
+  }
+  const ads = await fetch(base + '/ads.txt');
+  assert.equal(ads.status, 200);
+  assert.equal((await ads.text()).trim(), 'google.com, pub-5278231299883833, DIRECT, f08c47fec0942fa0');
+  console.log(JSON.stringify({ timestamp: new Date().toISOString(), base, metadata: 'PASS', analytics: 'PASS', adminGetPost: 401, adsTxt: 'PASS', noAdLoader: 'PASS' }));
+})().catch(e => { console.error(e); process.exitCode = 1; });
diff --git a/verification/TK-11380/verify.cjs b/verification/TK-11380/verify.cjs
index 7840a04..b0ef971 100644
--- a/verification/TK-11380/verify.cjs
+++ b/verification/TK-11380/verify.cjs
@@ -2,6 +2,7 @@ const assert = require('node:assert/strict');
 const fs = require('node:fs');
 const { chromium } = require('/Users/macstudio3/Projects/Designer-Wallcoverings/node_modules/playwright');
 const base = process.env.VERIFY_URL || 'http://127.0.0.1:19962';
+const output = process.env.VERIFY_OUTPUT || __dirname;
 const publisher = 'ca-pub-5278231299883833';
 const results = [];
 
@@ -19,12 +20,14 @@ const results = [];
       const html = await response.text();
       assert.match(html, /<meta name="google-adsense-account" content="ca-pub-5278231299883833"/);
       assert.doesNotMatch(html, /adsbygoogle\.js/);
+      assert.ok(html.includes('G-2DHDBP8R78'), 'Preserve existing GA4 identity: ' + path);
       await page.goto(base + path, { waitUntil: 'domcontentloaded' });
       await page.locator('body').waitFor({ state: 'visible' });
       // The uploader has ongoing work; network-idle is not its readiness contract.
       await page.waitForTimeout(750);
       assert.equal(await page.locator('meta[name="google-adsense-account"]').getAttribute('content'), publisher);
       assert.equal(await page.locator('script[src*="adsbygoogle"]').count(), 0);
+      await page.waitForFunction(() => Array.isArray(window.dataLayer) && window.dataLayer.some(entry => entry[0] === 'config' && entry[1] === 'G-2DHDBP8R78'));
       results.push({ path, html: 'PASS', hydrated: 'PASS' });
     }
     await page.goto(base, { waitUntil: 'networkidle' });
@@ -39,12 +42,16 @@ const results = [];
     const admin = await fetch(base + '/api/admin');
     assert.equal(admin.status, 401);
     assert.deepEqual(await admin.json(), { error: 'Unauthorized' });
+    const adminPost = await fetch(base + '/api/admin', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'verification_probe' }) });
+    assert.equal(adminPost.status, 401);
+    assert.deepEqual(await adminPost.json(), { error: 'Unauthorized' });
     const ads = await fetch(base + '/ads.txt');
     assert.equal(ads.status, 200);
     assert.equal((await ads.text()).trim(), 'google.com, pub-5278231299883833, DIRECT, f08c47fec0942fa0');
-    results.push({ adminWithoutCookie: 401, adsTxt: 'PASS' });
-    await page.screenshot({ path: __dirname + '/login.png', fullPage: true });
-    fs.writeFileSync(__dirname + '/results.json', JSON.stringify({ timestamp: new Date().toISOString(), base, results, adRequests, thirdPartyRequestsBlocked: true }, null, 2) + '\n');
+    results.push({ adminWithoutCookie: 401, adminPostWithoutCookie: 401, analyticsConfig: 'G-2DHDBP8R78', adsTxt: 'PASS' });
+    fs.mkdirSync(output, { recursive: true });
+    await page.screenshot({ path: output + '/login.png', fullPage: true });
+    fs.writeFileSync(output + '/results.json', JSON.stringify({ timestamp: new Date().toISOString(), base, results, adRequests, thirdPartyRequestsBlocked: true }, null, 2) + '\n');
     console.log(JSON.stringify({ verdict: 'PASS', checkedRoutes: 9, clientNavigation: 'PASS', adminAuth: 'PASS', adRequests: 0 }));
   } finally { await browser.close(); }
 })().catch(err => { console.error(err); process.exitCode = 1; });
diff --git a/verification/e2e-proof.json b/verification/e2e-proof.json
new file mode 100644
index 0000000..a4da52f
--- /dev/null
+++ b/verification/e2e-proof.json
@@ -0,0 +1,69 @@
+{
+  "intent": "Deploy IWasCute ownership verification without advertising execution while preserving analytics and admin boundaries",
+  "ticket": "TK-11380",
+  "risk_tier": "R4",
+  "timestamp": "2026-09-10T17:42:24.752Z",
+  "environment": "Production https://iwascute.com; PM2 gap-iwascute; /root/Projects/iwascute; port 9636",
+  "approval": {
+    "start": "2026-09-10T17:19:10Z",
+    "expires": "2026-09-11T09:19:10Z",
+    "scope": "Explicit user authorization relayed by root; targeted IWasCute deployment"
+  },
+  "identity": {
+    "remote_snapshot": "1dd9768",
+    "remote_success": "d0dc519",
+    "build_id": "CDMq148tzzvoWWsHCxsrN"
+  },
+  "baseline": "Live HTML contained GA4 and an AdSense preload/RSC loader; no ownership metadata. Remote worktree dirty; preserved by scoped Git snapshot and private source backup.",
+  "commands": [
+    "bash pre-deploy-check.sh /Users/macstudio3/Projects/IWasCute",
+    "ssh kamatera 'bash /tmp/TK11380-iwascute-deploy.sh prepare'",
+    "npm ci --ignore-scripts --no-audit --no-fund (isolated remote stage)",
+    "NEXT_TELEMETRY_DISABLED=1 npm run build (isolated remote stage)",
+    "node verification/TK-11380/rollback-test.cjs",
+    "node /tmp/TK11380-iwascute-normalize.cjs /root/Projects/.iwascute-TK11380-20260910T172500Z",
+    "node /tmp/TK11380-iwascute-http.cjs http://127.0.0.1:19962 (candidate relocated to a different directory)",
+    "ssh kamatera 'bash /tmp/TK11380-iwascute-deploy.sh activate'",
+    "VERIFY_URL=https://iwascute.com VERIFY_OUTPUT=verification/TK-11380/live node verification/TK-11380/verify.cjs",
+    "node /Users/macstudio3/.agents/skills/3x/run.js --url https://iwascute.com --expect google-adsense-account --selector 'a[href=\"/v1\"]' --click 'a[href=\"/v1\"]' --paths /ads.txt,/login --no-open"
+  ],
+  "checks": {
+    "predeploy_guard": "PASS 5/5",
+    "nginx": "PASS syntax; existing unrelated duplicate-name warnings",
+    "isolated_build": "PASS",
+    "relocated_candidate": "PASS",
+    "nine_routes": "PASS metadata and hydrated no-ad boundary",
+    "client_navigation": "PASS home to v1 to login",
+    "analytics": "PASS existing G-2DHDBP8R78 and executed dataLayer config",
+    "admin_unauthenticated": "PASS GET and POST 401",
+    "photo_projection": "PASS 22 fields using read-only LIMIT 0, zero user rows",
+    "ads_txt": "PASS exact DIRECT record",
+    "advertising_requests": "PASS zero attempted requests",
+    "three_x": "PASS 4/4 available, Safari and Firefox SKIP engines absent"
+  },
+  "failure_recovery": {
+    "initial_activation": "FAIL admin 500: generated Turbopack pg symlink still referenced stage",
+    "response": "Rolled back immediately, verified admin 401",
+    "correction": "Normalized one package symlink to ../../node_modules/pg; full candidate relocation rehearsal passed; subsequent activation passed canonical-port and HTTPS checks",
+    "rollback_rehearsal": "Actual production rollback plus full/partial filesystem fixture tests (PM2/Git mocked)"
+  },
+  "artifacts": [
+    "TK-11380/PRODUCTION.md",
+    "TK-11380/live/results.json",
+    "TK-11380/live/post-activation-http.json",
+    "TK-11380/live/normalized-links.json",
+    "TK-11380/live/login.png",
+    "TK-11380/live/3x-render.png",
+    "TK-11380/live/3x-e2e.png",
+    "TK-11380/live/build-output.txt"
+  ],
+  "cleanup": "Temporary SSH tunnel and both remote canary processes stopped. Stage source and private rollback artifacts deliberately retained. No DB writes, account changes, photo submissions or ad clicks.",
+  "rollback": "/root/.local/state/iwascute-deploy/TK11380-20260910T172500Z/{previous.next,previous.node_modules,source}; saved script /tmp/TK11380-iwascute-deploy.sh rollback",
+  "limitations": [
+    "No authenticated account or photo workflows exercised",
+    "No Google Analytics receipt assertion (third-party requests blocked in primary browser test)",
+    "750ms direct-route observation plus real navigation does not prove arbitrary delayed behavior",
+    "No Google review submission or AdSense approval; content/privacy readiness remains separate"
+  ],
+  "verdict": "PASS scoped production deployment; parent independent acceptance pending"
+}

← 9205c24 auto-data-snapshot: 2026-09-10T10:34:55 (2 data files) — ver  ·  back to IWasCute  ·  Add privacy policy page for AdSense 1106eb7 →