← back to Tk11438 Postgres Migration
Record approved Ken socket rollout and independent verification
7470da847854cd0de0696ba8da97bebe979b1b5f · 2026-09-11 10:46:09 -0700 · Steve Abrams
Files touched
M .gitignoreM NEXT-BATCH.mdM README.mdM next-batch.jsonM verification/e2e-proof.jsonA verification/ken-rollout/after-initial.jsonA verification/ken-rollout/after.jsonA verification/ken-rollout/applied.jsonA verification/ken-rollout/baseline.jsonA verification/ken-rollout/canary-log-baseline.jsonA verification/ken-rollout/config-applied.jsonA verification/ken-rollout/independent-check.cjsA verification/ken-rollout/independent-verification.jsonA verification/ken-rollout/monitor.jsonA verification/ken-rollout/mutation-started.jsonA verification/ken-rollout/observation.jsonA verification/ken-rollout/parent-acceptance.jsonA verification/ken-rollout/preparation.jsonA verification/ken-rollout/reload-started.jsonA verification/ken-rollout/reloaded.jsonA verification/ken-rollout/rollback-fault-proof.jsonA verification/ken-rollout/rollback-test.cjsA verification/ken-rollout/rollout.cjsA verification/ken-rollout/verification.jsonA verification/ken-rollout/verify-failure.jsonA verification/ken-rollout/verify-rpc-timeout.json
Diff
commit 7470da847854cd0de0696ba8da97bebe979b1b5f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Sep 11 10:46:09 2026 -0700
Record approved Ken socket rollout and independent verification
---
.gitignore | 2 +
NEXT-BATCH.md | 17 +-
README.md | 34 +--
next-batch.json | 21 +-
verification/e2e-proof.json | 52 +++-
verification/ken-rollout/after-initial.json | 129 +++++++++
verification/ken-rollout/after.json | 110 ++++++++
verification/ken-rollout/applied.json | 8 +
verification/ken-rollout/baseline.json | 119 +++++++++
verification/ken-rollout/canary-log-baseline.json | 5 +
verification/ken-rollout/config-applied.json | 10 +
verification/ken-rollout/independent-check.cjs | 56 ++++
.../ken-rollout/independent-verification.json | 287 +++++++++++++++++++++
verification/ken-rollout/monitor.json | 110 ++++++++
verification/ken-rollout/mutation-started.json | 6 +
verification/ken-rollout/observation.json | 23 ++
verification/ken-rollout/parent-acceptance.json | 17 ++
verification/ken-rollout/preparation.json | 9 +
verification/ken-rollout/reload-started.json | 6 +
verification/ken-rollout/reloaded.json | 7 +
verification/ken-rollout/rollback-fault-proof.json | 22 ++
verification/ken-rollout/rollback-test.cjs | 21 ++
verification/ken-rollout/rollout.cjs | 112 ++++++++
verification/ken-rollout/verification.json | 39 +++
verification/ken-rollout/verify-failure.json | 6 +
verification/ken-rollout/verify-rpc-timeout.json | 6 +
26 files changed, 1205 insertions(+), 29 deletions(-)
diff --git a/.gitignore b/.gitignore
index 87910ab..8ea1807 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,5 @@ dist/
build/
.next/
__pycache__/
+
+/verification/ken-rollout/private/
diff --git a/NEXT-BATCH.md b/NEXT-BATCH.md
index 52c1281..b8c81e9 100644
--- a/NEXT-BATCH.md
+++ b/NEXT-BATCH.md
@@ -1,6 +1,10 @@
-# TK-11438 — Ken socket migration approval
+# TK-11438 — applied Ken batch and retained scope
-Status: PREPARED, NOT APPLIED. Overall ticket remains blocked. Owner: codex-run-11438.
+Status: APPLIED AND INDEPENDENTLY VERIFIED, 2026-09-11. Overall fleet remains incomplete.
+Owner: codex-run-11438. Source commit d840eb6; independent26/26PASS.
+Approval received via TK-11478, excluding all email sends. See verification/ken-rollout/parent-acceptance.json.
+
+The following is the retained approved scope.
Approve this Ken batch, including its service restart and reconciliation job
reload. Restarting Ken resumes its existing autonomous loops and therefore needs
@@ -76,3 +80,12 @@ or mark TK-11438 done.
Gate source: Steve's current ticket instruction requires a pending-approval
draft and STOP for production/customer-facing/spend/send/canonical changes.
E2E Proof requires approved R4 rollout and independent verification.
+
+## Result and limitations
+
+Ken PID25903 is using sockets for both databases. Saved fields match runtime.
+Reconciliation is loaded with socket URL and1800-second interval, and exited0.
+The underlying signal module passed using the changed wrapper URL; the wrapper
+itself was not invoked because it can send email. Captured trading settings
+match; full risk_state hash is volatile and uncaptured keys were not independently
+proven unchanged. Private rollback materials are retained and gitignored.
diff --git a/README.md b/README.md
index 66c653c..3f728e0 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,25 @@
# TK-11438 PostgreSQL socket migration
-Status: PARTIAL / BLOCKED at the next production approval gate.
+Status: PARTIAL. Two approved batches applied; full fleet remains incomplete.
-The first approved batch (interiordesignershowroom and patterndesignlab) is applied.
-Fresh independent verification passes all 15 assertions, including saved PM2
-configuration, database identity, auth and missing-socket behavior. Counts remain
-3,201 products and 1,694 designs. No reboot or rollback rehearsal is claimed.
+First batch: showroom and Pattern Design Lab source, runtime and durable configs
+migrated and independently reverified (15 checks; 3,201 products/1,694 designs).
-Read NEXT-BATCH.md for the exact Ken approval scope. next-batch.json contains
-hashed source and environment change specifications; these have not been applied.
-Ken still uses TCP. Full original/expanded inventory classification is incomplete.
-The historical first-batch approval specification is retained in APPROVAL.md.
+Ken batch: source commit d840eb6; both databases now use Unix sockets. Effective
+and saved PM2 URLs match the shell-sourced .env. Hourly wrapper URI is migrated;
+its underlying SELECT-only module was tested without invoking its email branch.
+The reconciliation launchd job is loaded with the socket URL and exited0.
+Independent26/26 checks plus parent stability observation passed.
-reverify.cjs uses direct read-only PM2 RPC and never starts a daemon. It verifies
-the previously migrated services. ken-proof.cjs loads only the installed pg driver
-and runs SELECT-only socket and missing-socket tests. Neither imports app startup.
-Verification scripts save credential-free results in verification/.
+Read verification/ken-rollout/parent-acceptance.json for acceptance and limits.
+Private rollback materials are in verification/ken-rollout/private, mode0700,
+files0600, excluded from git. Never stage them. Helper rollback is guarded and
+must only be invoked to address an actual regression; no live rollback was needed.
-No live source/config edits, restarts, scheduled job runs, database writes, or
-remote changes were performed during this resumption.
+Captured trading switches and Ken config hashes match. Whole risk_state hash
+changed consistently with automatic weather-cache updates; uncaptured keys cannot
+be proven unchanged retrospectively. No email sends were invoked by this work.
+
+Original217 files plus an invalid directory entry and expanded candidates still
+need full disposition/migration. Gracie, other services and TCP shutdown remain
+outside the Ken approval. NEXT-BATCH.md retains its historical approved scope.
diff --git a/next-batch.json b/next-batch.json
index 276932e..06d7b66 100644
--- a/next-batch.json
+++ b/next-batch.json
@@ -1,6 +1,6 @@
{
"ticket": "TK-11438",
- "status": "PREPARED_NOT_APPLIED",
+ "status": "APPLIED_VERIFIED_WITH_DOCUMENTED_LIMITS",
"scope": "Ken application and its two known explicit-TCP scheduled consumers only",
"file_changes": [
{
@@ -48,5 +48,22 @@
"Do not invoke follow-the-winners wrapper during verification: can send email on signal transition",
"TCP shutdown excluded",
"No Gracie restart: mounted vendor module calls canonical CREATE TABLE IF NOT EXISTS on startup"
- ]
+ ],
+ "result": {
+ "ticket": "TK-11438",
+ "timestamp": "2026-09-11T17:46:09.886710+00:00",
+ "verdict": "KEN_BATCH_PASS_FULL_FLEET_INCOMPLETE",
+ "source_commit": "d840eb666dbe5986eb48925ef9e694c3c3d450d3",
+ "approval": "Steve -> codex-run-11340/TK-11478 -> current session, 2026-09-11; described current Ken batch only; all email sends excluded",
+ "parent_acceptance": "Independent 26 PASS assertions inspected against parent baseline, after, stability proofs and exact d840eb6 diff. Accepted.",
+ "email_sends_invoked": 0,
+ "wrapper_invoked": false,
+ "private_backup": "verification/ken-rollout/private/ (0700; files0600; gitignored)",
+ "rollback": "node verification/ken-rollout/rollout.cjs rollback (only on actual regression, guarded file fields and job restore; live rollback not executed)",
+ "limits": [
+ "Only captured operational switches and Ken config hashes proved unchanged; full risk_state hash changed consistently with automatic weather_cache, exclusive cause unproven.",
+ "Email-capable hourly wrapper not executed. Exact wrapper URL used with underlying SELECT-only module; normal schedule definition preserved.",
+ "No reboot or full-fleet TCP shutdown. Original/expanded inventory remains incomplete."
+ ]
+ }
}
diff --git a/verification/e2e-proof.json b/verification/e2e-proof.json
index b1dce78..adc0780 100644
--- a/verification/e2e-proof.json
+++ b/verification/e2e-proof.json
@@ -1,9 +1,9 @@
{
"intent": "Migrate every local Postgres TCP consumer to Unix sockets",
"ticket": "TK-11438",
- "timestamp": "2026-09-11T16:53:55.686878+00:00",
- "risk_tier": "R3 read-only revalidation; R4 next rollout not authorized",
- "build_identity": "Resolve documentation and proof scripts with git log -1; service commits a482499/bf33afd unchanged by this turn",
+ "timestamp": "2026-09-11T17:46:09.886710+00:00",
+ "risk_tier": "R4 authorized Ken rollout; scoped independent verification complete",
+ "build_identity": "Ken source d840eb6; evidence repository commit containing this record",
"environment": "macstudio3; PostgreSQL 14.23; /tmp socket; existing PM2 daemon",
"verdict": "PARTIAL_BLOCKED",
"assertions": [
@@ -30,24 +30,39 @@
"reason": "Still incomplete. Active Ken TCP seen; remaining inventory is candidates, not migrated consumers."
},
{
- "check": "Ken app and scheduled job cutover",
+ "check": "Approved Ken application and scheduler socket migration",
+ "verdict": "PASS",
+ "evidence": "verification/ken-rollout/parent-acceptance.json",
+ "detail": "Two pools, effective/saved env, real auth boundary, missing sockets, underlying signal module and loaded reconciliation exit0 verified. Independent26/26PASS."
+ },
+ {
+ "check": "Rollback copy rehearsal and launchd failure recovery",
+ "verdict": "PASS",
+ "evidence": "verification/ken-rollout/rollback-fault-proof.json",
+ "detail": "Exact file/four-field dump roundtrips and4failure scenarios passed without induced live faults."
+ },
+ {
+ "check": "All risk_state keys unchanged",
"verdict": "SKIP",
- "reason": "Explicit production/spend/send gates; approval draft prepared."
+ "reason": "Full config hash includes volatile weather_cache; uncaptured keys cannot be proven retroactively. Captured trading switches and exact live_run/trade_config hashes match."
},
{
- "check": "Restart rollback rehearsal and reboot persistence test",
+ "check": "Full hourly wrapper email branch",
"verdict": "SKIP",
- "reason": "No restart/reboot/rollback performed this turn; saved field-level configuration inspected only."
+ "reason": "All emails excluded by current approval; wrapper was not invoked. Transport verified through exact wrapper URI and underlying SELECT module."
}
],
"commands": [
"node reverify.cjs (direct getMonitorData RPC, installed pg SELECTs, authenticated GETs, lsof)",
"node ken-proof.cjs",
"Read Ken start.sh, two Pool sites, .env key metadata and three launchd plists",
- "psql read-only pg_stat_activity/settings SELECT"
+ "psql read-only pg_stat_activity/settings SELECT",
+ "node rollout.cjs prepare/apply/verify/reload/observe",
+ "node rollback-test.cjs",
+ "node independent-check.cjs"
],
- "cleanup": "All test DB connections closed. Only evidence/verification scripts and ticket metadata changed this turn; prior first-batch source/runtime mutations remain recorded in first_batch.",
- "rollback": "Prior first-batch scoped rollback helper is in peer evidence; not executed or claimed rehearsed. Next batch must privately capture exact fields and rehearse rollback before restart.",
+ "cleanup": "Test connections closed; one authorized Ken restart and one reconciliation reload completed. No email-capable wrapper executed. Private scoped rollback materials retained with0700/0600 permissions and excluded from git.",
+ "rollback": "node verification/ken-rollout/rollout.cjs rollback (only on actual regression, guarded file fields and job restore; live rollback not executed)",
"first_batch": {
"showroom_commit": "a482499",
"patterndesignlab_commit": "bf33afd",
@@ -70,5 +85,22 @@
"peer": "codex-run-11401",
"change": "Two saved showroom DATABASE_URL fields only, no restart",
"acceptance": "Fresh independent verification/reverification.json passes"
+ },
+ "ken_batch": {
+ "ticket": "TK-11438",
+ "timestamp": "2026-09-11T17:46:09.886710+00:00",
+ "verdict": "KEN_BATCH_PASS_FULL_FLEET_INCOMPLETE",
+ "source_commit": "d840eb666dbe5986eb48925ef9e694c3c3d450d3",
+ "approval": "Steve -> codex-run-11340/TK-11478 -> current session, 2026-09-11; described current Ken batch only; all email sends excluded",
+ "parent_acceptance": "Independent 26 PASS assertions inspected against parent baseline, after, stability proofs and exact d840eb6 diff. Accepted.",
+ "email_sends_invoked": 0,
+ "wrapper_invoked": false,
+ "private_backup": "verification/ken-rollout/private/ (0700; files0600; gitignored)",
+ "rollback": "node verification/ken-rollout/rollout.cjs rollback (only on actual regression, guarded file fields and job restore; live rollback not executed)",
+ "limits": [
+ "Only captured operational switches and Ken config hashes proved unchanged; full risk_state hash changed consistently with automatic weather_cache, exclusive cause unproven.",
+ "Email-capable hourly wrapper not executed. Exact wrapper URL used with underlying SELECT-only module; normal schedule definition preserved.",
+ "No reboot or full-fleet TCP shutdown. Original/expanded inventory remains incomplete."
+ ]
}
}
diff --git a/verification/ken-rollout/after-initial.json b/verification/ken-rollout/after-initial.json
new file mode 100644
index 0000000..07cf1ba
--- /dev/null
+++ b/verification/ken-rollout/after-initial.json
@@ -0,0 +1,129 @@
+{
+ "at": "2026-09-11T17:32:47.884Z",
+ "label": "after",
+ "pid": 25903,
+ "pm_id": 15,
+ "restart_time": 39,
+ "identities": [
+ {
+ "key": "DATABASE_URL",
+ "database": "bertha_betting",
+ "role": "macstudio3",
+ "addr": null,
+ "readonly": "on"
+ },
+ {
+ "key": "KEN_DATABASE_URL",
+ "database": "ken",
+ "role": "macstudio3",
+ "addr": null,
+ "readonly": "on"
+ }
+ ],
+ "settings": {
+ "safe_mode": "false",
+ "trading_on": "true",
+ "kalshi_env": "prod",
+ "config_hash": "402d274e93015b9d3c875fa9aa76b60a"
+ },
+ "kenSettings": [
+ {
+ "key": "live_run",
+ "value_hash": "5007ffdbce2484c46f0f1ebfed156a79"
+ },
+ {
+ "key": "trade_config",
+ "value_hash": "c77ced4a430c742f316497e371b18efc"
+ }
+ ],
+ "reconciliation": {
+ "returned": true,
+ "invested": true,
+ "balance": true
+ },
+ "http": [
+ {
+ "route": "/api/setup",
+ "auth": false,
+ "status": 401,
+ "sha256": "ca730ec7fc2b7edb7277e5b8e58d6a474971ca59dafb499141a4856c06132638",
+ "bytes": 35
+ },
+ {
+ "route": "/api/setup",
+ "auth": true,
+ "status": 200,
+ "sha256": "b7618c456aa65071f5e72b8d56effe52b8edccbb2173a34abfac7ea514d3f2f0",
+ "bytes": 75
+ },
+ {
+ "route": "/api/trading/history",
+ "auth": false,
+ "status": 401,
+ "sha256": "ca730ec7fc2b7edb7277e5b8e58d6a474971ca59dafb499141a4856c06132638",
+ "bytes": 35
+ },
+ {
+ "route": "/api/trading/history",
+ "auth": true,
+ "status": 200,
+ "sha256": "5e461574d4ae8b1ce2c5495529884fdecb0dbe49b483473258640165a9212d7c",
+ "bytes": 1197
+ }
+ ],
+ "process_db_tcp": [],
+ "saved_match": true,
+ "targets": {
+ "DATABASE_URL": {
+ "host": "localhost",
+ "database": "bertha_betting",
+ "socket": "/tmp",
+ "port": "5432"
+ },
+ "KEN_DATABASE_URL": {
+ "host": "localhost",
+ "database": "ken",
+ "socket": "/tmp",
+ "port": "5432"
+ }
+ },
+ "activity": [
+ {
+ "datname": "bertha_betting",
+ "usename": "macstudio3",
+ "client_addr": null,
+ "client_port": -1,
+ "pid": 25947
+ },
+ {
+ "datname": "ken",
+ "usename": "macstudio3",
+ "client_addr": null,
+ "client_port": -1,
+ "pid": 25948
+ },
+ {
+ "datname": "ken",
+ "usename": "macstudio3",
+ "client_addr": null,
+ "client_port": -1,
+ "pid": 25952
+ },
+ {
+ "datname": "ken",
+ "usename": "macstudio3",
+ "client_addr": null,
+ "client_port": null,
+ "pid": 30591
+ },
+ {
+ "datname": "ken",
+ "usename": "macstudio3",
+ "client_addr": null,
+ "client_port": null,
+ "pid": 30592
+ }
+ ],
+ "source_hash": "8c20f30eb1a31fe7ad5c3884094dab8fcb0020ea7cf87d1b7c0a974693e9dc74",
+ "wrapper_hash": "61fb025a8011dccedc26f71e1017317074070b96a7b7f299d9f0556606f350a7"
+}
diff --git a/verification/ken-rollout/after.json b/verification/ken-rollout/after.json
new file mode 100644
index 0000000..229001e
--- /dev/null
+++ b/verification/ken-rollout/after.json
@@ -0,0 +1,110 @@
+{
+ "at": "2026-09-11T17:38:04.144Z",
+ "label": "after",
+ "pid": 25903,
+ "pm_id": 15,
+ "restart_time": 39,
+ "identities": [
+ {
+ "key": "DATABASE_URL",
+ "database": "bertha_betting",
+ "role": "macstudio3",
+ "addr": null,
+ "readonly": "on"
+ },
+ {
+ "key": "KEN_DATABASE_URL",
+ "database": "ken",
+ "role": "macstudio3",
+ "addr": null,
+ "readonly": "on"
+ }
+ ],
+ "settings": {
+ "safe_mode": "false",
+ "trading_on": "true",
+ "kalshi_env": "prod",
+ "config_hash": "402d274e93015b9d3c875fa9aa76b60a",
+ "stable_settings_hash": "c265a97bdda701bcaebe80968fee995d",
+ "weather_cache_fetched_at": "2026-09-11T17:32:45.483Z"
+ },
+ "kenSettings": [
+ {
+ "key": "live_run",
+ "value_hash": "5007ffdbce2484c46f0f1ebfed156a79"
+ },
+ {
+ "key": "trade_config",
+ "value_hash": "c77ced4a430c742f316497e371b18efc"
+ }
+ ],
+ "reconciliation": {
+ "returned": true,
+ "invested": true,
+ "balance": true
+ },
+ "http": [
+ {
+ "route": "/api/setup",
+ "auth": false,
+ "status": 401,
+ "sha256": "ca730ec7fc2b7edb7277e5b8e58d6a474971ca59dafb499141a4856c06132638",
+ "bytes": 35
+ },
+ {
+ "route": "/api/setup",
+ "auth": true,
+ "status": 200,
+ "sha256": "b7618c456aa65071f5e72b8d56effe52b8edccbb2173a34abfac7ea514d3f2f0",
+ "bytes": 75
+ },
+ {
+ "route": "/api/trading/history",
+ "auth": false,
+ "status": 401,
+ "sha256": "ca730ec7fc2b7edb7277e5b8e58d6a474971ca59dafb499141a4856c06132638",
+ "bytes": 35
+ },
+ {
+ "route": "/api/trading/history",
+ "auth": true,
+ "status": 200,
+ "sha256": "5e461574d4ae8b1ce2c5495529884fdecb0dbe49b483473258640165a9212d7c",
+ "bytes": 1197
+ }
+ ],
+ "process_db_tcp": [],
+ "saved_match": true,
+ "targets": {
+ "DATABASE_URL": {
+ "host": "localhost",
+ "database": "bertha_betting",
+ "socket": "/tmp",
+ "port": "5432"
+ },
+ "KEN_DATABASE_URL": {
+ "host": "localhost",
+ "database": "ken",
+ "socket": "/tmp",
+ "port": "5432"
+ }
+ },
+ "activity": [
+ {
+ "datname": "bertha_betting",
+ "usename": "macstudio3",
+ "client_addr": null,
+ "client_port": -1,
+ "pid": 80756
+ },
+ {
+ "datname": "ken",
+ "usename": "macstudio3",
+ "client_addr": null,
+ "client_port": -1,
+ "pid": 80757
+ }
+ ],
+ "source_hash": "8c20f30eb1a31fe7ad5c3884094dab8fcb0020ea7cf87d1b7c0a974693e9dc74",
+ "wrapper_hash": "61fb025a8011dccedc26f71e1017317074070b96a7b7f299d9f0556606f350a7"
+}
diff --git a/verification/ken-rollout/applied.json b/verification/ken-rollout/applied.json
new file mode 100644
index 0000000..fdff485
--- /dev/null
+++ b/verification/ken-rollout/applied.json
@@ -0,0 +1,8 @@
+{
+ "at": "2026-09-11T17:32:32.364Z",
+ "verdict": "APPLIED_VERIFY_PENDING",
+ "before_pid": 92641,
+ "after_pid": 25903,
+ "commit": "d840eb666dbe5986eb48925ef9e694c3c3d450d3",
+ "email_wrapper_executed": false
+}
diff --git a/verification/ken-rollout/baseline.json b/verification/ken-rollout/baseline.json
new file mode 100644
index 0000000..f007fbb
--- /dev/null
+++ b/verification/ken-rollout/baseline.json
@@ -0,0 +1,119 @@
+{
+ "at": "2026-09-11T17:28:40.054Z",
+ "label": "baseline",
+ "pid": 92641,
+ "pm_id": 15,
+ "restart_time": 38,
+ "identities": [
+ {
+ "key": "DATABASE_URL",
+ "database": "bertha_betting",
+ "role": "macstudio3",
+ "addr": "::1/128",
+ "readonly": "on"
+ },
+ {
+ "key": "KEN_DATABASE_URL",
+ "database": "ken",
+ "role": "macstudio3",
+ "addr": "::1/128",
+ "readonly": "on"
+ }
+ ],
+ "settings": {
+ "safe_mode": "false",
+ "trading_on": "true",
+ "kalshi_env": "prod",
+ "config_hash": "2fddc5b0ff823bbe1ea724dca0dca0d0"
+ },
+ "kenSettings": [
+ {
+ "key": "live_run",
+ "value_hash": "5007ffdbce2484c46f0f1ebfed156a79"
+ },
+ {
+ "key": "trade_config",
+ "value_hash": "c77ced4a430c742f316497e371b18efc"
+ }
+ ],
+ "reconciliation": {
+ "returned": true,
+ "invested": true,
+ "balance": true
+ },
+ "http": [
+ {
+ "route": "/api/setup",
+ "auth": false,
+ "status": 401,
+ "sha256": "ca730ec7fc2b7edb7277e5b8e58d6a474971ca59dafb499141a4856c06132638",
+ "bytes": 35
+ },
+ {
+ "route": "/api/setup",
+ "auth": true,
+ "status": 200,
+ "sha256": "b7618c456aa65071f5e72b8d56effe52b8edccbb2173a34abfac7ea514d3f2f0",
+ "bytes": 75
+ },
+ {
+ "route": "/api/trading/history",
+ "auth": false,
+ "status": 401,
+ "sha256": "ca730ec7fc2b7edb7277e5b8e58d6a474971ca59dafb499141a4856c06132638",
+ "bytes": 35
+ },
+ {
+ "route": "/api/trading/history",
+ "auth": true,
+ "status": 200,
+ "sha256": "5e461574d4ae8b1ce2c5495529884fdecb0dbe49b483473258640165a9212d7c",
+ "bytes": 1197
+ }
+ ],
+ "process_db_tcp": [
+ "node 92641 macstudio3 17u IPv6 0x593636ef484b2ded 0t0 TCP [::1]:63505->[::1]:5432 (ESTABLISHED)",
+ "node 92641 macstudio3 37u IPv6 0x88be5de58ac91ca4 0t0 TCP [::1]:49930->[::1]:5432 (ESTABLISHED)",
+ "node 92641 macstudio3 39u IPv6 0x233e031ec3ae6c64 0t0 TCP [::1]:50147->[::1]:5432 (ESTABLISHED)"
+ ],
+ "saved_match": true,
+ "targets": {
+ "DATABASE_URL": {
+ "host": "localhost",
+ "database": "bertha_betting",
+ "socket": null,
+ "port": "5432"
+ },
+ "KEN_DATABASE_URL": {
+ "host": "localhost",
+ "database": "ken",
+ "socket": null,
+ "port": "5432"
+ }
+ },
+ "activity": [
+ {
+ "datname": "bertha_betting",
+ "usename": "macstudio3",
+ "client_addr": "::1/128",
+ "client_port": 49930,
+ "pid": 86148
+ },
+ {
+ "datname": "ken",
+ "usename": "macstudio3",
+ "client_addr": "::1/128",
+ "client_port": 63505,
+ "pid": 74147
+ },
+ {
+ "datname": "ken",
+ "usename": "macstudio3",
+ "client_addr": "::1/128",
+ "client_port": 50147,
+ "pid": 86293
+ }
+ ],
+ "source_hash": "8c20f30eb1a31fe7ad5c3884094dab8fcb0020ea7cf87d1b7c0a974693e9dc74",
+ "wrapper_hash": "04a512566936f561c7c37d623e26ec8bb0e2ec507607dbd5db5c8f5e19d5b5b9"
+}
diff --git a/verification/ken-rollout/canary-log-baseline.json b/verification/ken-rollout/canary-log-baseline.json
new file mode 100644
index 0000000..856dfb9
--- /dev/null
+++ b/verification/ken-rollout/canary-log-baseline.json
@@ -0,0 +1,5 @@
+{
+ "at": "2026-09-11T17:37:28.616082+00:00",
+ "path": "/Users/macstudio3/Projects/Ken/kalshi-dash/logs/reconcile-canary.log",
+ "size": 202789
+}
diff --git a/verification/ken-rollout/config-applied.json b/verification/ken-rollout/config-applied.json
new file mode 100644
index 0000000..13906ec
--- /dev/null
+++ b/verification/ken-rollout/config-applied.json
@@ -0,0 +1,10 @@
+{
+ "at": "2026-09-11T17:32:30.028Z",
+ "dump": {
+ "before_sha": "92da72322f2e0a36503a1a2ba1ad5c016d3aed44793c8f84571e495f2792ec16",
+ "after_sha": "d733e1d077687f60cb8b79bebb3656dbde21866a0f5b3b37680c55b80af1ece7",
+ "only_target_fields": true
+ },
+ "commit": "d840eb666dbe5986eb48925ef9e694c3c3d450d3",
+ "application_restart_pending": true
+}
diff --git a/verification/ken-rollout/independent-check.cjs b/verification/ken-rollout/independent-check.cjs
new file mode 100644
index 0000000..1909dde
--- /dev/null
+++ b/verification/ken-rollout/independent-check.cjs
@@ -0,0 +1,56 @@
+// Independent read-only verifier. No service starts/restarts, wrappers, or sends.
+const fs=require('fs'),cp=require('child_process'),assert=require('assert/strict'),crypto=require('crypto'),os=require('os');
+const base=__dirname,dir='/Users/macstudio3/Projects/Ken/kalshi-dash';
+const parse=require('/Users/macstudio3/Projects/patterndesignlab/node_modules/dotenv').parse;
+const {Client}=require(dir+'/node_modules/pg');
+const deps='/Users/macstudio3/.npm-global/lib/node_modules/pm2/node_modules/';
+const axon=require(deps+'pm2-axon'),rpc=require(deps+'pm2-axon-rpc');
+const read=p=>fs.readFileSync(p),json=p=>JSON.parse(read(p)),hash=b=>crypto.createHash('sha256').update(b).digest('hex');
+const run=(cmd,args)=>cp.execFileSync(cmd,args,{encoding:'utf8',timeout:15000,maxBuffer:8e6});
+const proof={task_id:'TK-11438',owner:'codex-run-11438-verifier',risk:'R4',timestamp:new Date().toISOString(),assertions:[],commands:[],application_mutations:0,email_sends:0};
+function pass(name,detail){proof.assertions.push({name,verdict:'PASS',detail});}
+async function db(url,fn){const c=new Client({connectionString:url,application_name:'TK11438-independent-verifier',connectionTimeoutMillis:3000,options:'-c default_transaction_read_only=on -c statement_timeout=5000'});try{await c.connect();return await fn(c);}finally{await c.end().catch(()=>{});}}
+async function monitor(){return await new Promise((resolve,reject)=>{const s=axon.socket('req'),c=new rpc.Client(s);const t=setTimeout(()=>{s.close();reject(Error('PM2 RPC timeout'));},30000);s.on('error',e=>{clearTimeout(t);s.close();reject(e);});s.connect('/Users/macstudio3/.pm2/rpc.sock');c.call('getMonitorData',{},(err,res)=>{clearTimeout(t);s.close();err?reject(Error('PM2 RPC failed')):resolve(res);});});}
+(async()=>{
+ const baseline=json(base+'/baseline.json'),receipt=json(base+'/private/receipt.json');
+ const env=parse(read(dir+'/.env')),services=await monitor(),matches=services.filter(s=>s.name==='ken');assert.equal(matches.length,1);
+ const live=matches[0],e=live.pm2_env;assert.equal(e.pm_cwd,dir);assert.equal(e.pm_exec_path,dir+'/start.sh');assert.equal(e.status,'online');assert.notEqual(live.pid,baseline.pid);
+ proof.pid=live.pid;proof.pm_id=e.pm_id;proof.commands.push('direct existing PM2 getMonitorData RPC');pass('Ken unique identity and online restart',{before_pid:baseline.pid,after_pid:live.pid,restart_time:e.restart_time});
+ const saved=json('/Users/macstudio3/.pm2/dump.pm2').filter(s=>s.name==='ken');assert.equal(saved.length,1);assert.equal(saved[0].pm_cwd,dir);assert.equal(saved[0].pm_exec_path,dir+'/start.sh');
+ for(const key of ['DATABASE_URL','KEN_DATABASE_URL']){
+ for(const v of [e[key],e.env[key],saved[0][key],saved[0].env[key]])assert.equal(v,env[key]);
+ const expected=baseline.identities.find(x=>x.key===key);
+ for(const [source,url]of [['dotenv',env[key]],['saved dump',saved[0][key]]]){
+ assert.equal(new URL(url).searchParams.get('host'),'/tmp');
+ const id=await db(url,async c=>(await c.query("SELECT current_database() database,current_user role,inet_client_addr()::text addr,current_setting('transaction_read_only') readonly")).rows[0]);
+ assert.equal(id.database,expected.database);assert.equal(id.role,expected.role);assert.equal(id.addr,null);assert.equal(id.readonly,'on');pass(key+' '+source+' Unix socket identity',id);
+ }
+ }
+ pass('Four effective/saved URL fields agree with dotenv',true);
+ const settings=await db(env.DATABASE_URL,async c=>(await c.query("SELECT config->>'safe_mode' safe_mode,config->>'trading_on' trading_on,config->>'kalshi_env' kalshi_env,md5(config::text) config_hash FROM risk_state ORDER BY updated_at DESC LIMIT 1")).rows[0]);
+ const ksettings=await db(env.KEN_DATABASE_URL,async c=>(await c.query("SELECT key,md5(value::text) value_hash FROM ken_config WHERE key IN ('live_run','trade_config') ORDER BY key")).rows);
+ for(const key of ['safe_mode','trading_on','kalshi_env'])assert.equal(settings[key],baseline.settings[key]);assert.deepEqual(ksettings,baseline.kenSettings);pass('Captured operational switches and Ken trading configuration unchanged',{settings,ksettings});proof.warnings=[{name:'Whole risk_state.config comparison',reason:'Baseline whole-config hash differs. Unchanged server.js lines 8027-8031 refresh weather_cache on scan. This is consistent with expected cache churn, but baseline omitted individual other keys, so exclusive cause and all-key preservation cannot be proved retroactively.',baseline_hash:baseline.settings.config_hash,current_hash:settings.config_hash}];
+ const lan=Object.values(os.networkInterfaces()).flat().find(x=>x.family==='IPv4'&&!x.internal&&x.address.startsWith('192.168.'));assert(lan);
+ for(const route of ['/api/setup','/api/trading/history'])for(const auth of ['none','invalid','valid']){
+ const headers=auth==='none'?{}:{authorization:'Basic '+Buffer.from(auth==='valid'?(env.ADMIN_USER||'admin')+':'+env.ADMIN_PASSWORD:'TK11438-verifier:invalid').toString('base64')};
+ const response=await fetch('http://'+lan.address+':7810'+route,{headers,signal:AbortSignal.timeout(10000)}),body=await response.text();
+ assert.equal(response.status,auth==='valid'?200:401);if(auth==='valid'){const obj=JSON.parse(body);assert(!obj.error);}
+ pass('LAN '+route+' '+auth+' auth',{status:response.status,bytes:body.length,body_sha256:hash(body)});
+ }
+ let sockets='';try{sockets=run('lsof',['-nP','-a','-p',String(live.pid),'-iTCP']);}catch(err){assert.equal(err.status,1);sockets=err.stdout||'';}
+ assert(!/:5432\b/.test(sockets));pass('Ken PID has no TCP 5432 socket',true);
+ const unix=run('lsof',['-nP','-a','-p',String(live.pid),'-U']);assert(unix.includes('unix'));pass('Ken PID has active Unix sockets',{lines:unix.trim().split('\n').length-1,sha256:hash(unix)});
+ const activity=await db('postgresql:///postgres?host=/tmp',async c=>(await c.query("SELECT datname,usename,client_addr::text,client_port,pid FROM pg_stat_activity WHERE datname IN ('ken','bertha_betting') AND application_name NOT LIKE 'TK11438%' ORDER BY datname,pid")).rows);
+ for(const name of ['ken','bertha_betting'])assert(activity.some(x=>x.datname===name&&x.client_addr===null));pass('Application database sessions use Unix sockets',activity);
+ for(const key of ['DATABASE_URL','KEN_DATABASE_URL']){const bad=new URL(env[key]);bad.searchParams.set('host',base+'/missing-socket');let code;try{await db(bad.toString(),async()=>{});}catch(e){code=e.code;}assert.equal(code,'ENOENT');pass(key+' missing socket fails closed',{code});}
+ assert.equal(hash(read(dir+'/server.js')),baseline.source_hash);assert.equal(hash(read(dir+'/server.js')),receipt.source_hash);pass('Server source unchanged',baseline.source_hash);
+ const manifest=json('/Users/macstudio3/Projects/tk11438-postgres-migration/next-batch.json');
+ assert.equal(read(dir+'/follow-the-winners-check.sh').toString(),read(base+'/private/wrapper.before').toString().replace(manifest.file_changes[0].old,manifest.file_changes[0].new));pass('Hourly wrapper exact approved one-line change; not executed',true);
+ for(const name of ['env','wrapper','plist']){assert.equal(hash(read(base+'/private/rehearsal-'+name)),receipt.hashes[name]);pass(name+' rollback rehearsal restored exact prior bytes',true);}
+ assert.equal(hash(read(dir+'/.env')),hash(read(base+'/private/env.after')));pass('Durable dotenv matches prepared bytes',true);
+ const plistPath='/Users/macstudio3/Library/LaunchAgents/com.steve.ken-reconcile-canary.plist',plist=JSON.parse(run('plutil',['-convert','json','-o','-',plistPath]));
+ assert.equal(hash(read(plistPath)),hash(read(base+'/private/plist.after')));assert.equal(new URL(plist.EnvironmentVariables.KEN_DATABASE_URL).searchParams.get('host'),'/tmp');assert.equal(plist.StartInterval,1800);assert.deepEqual(plist.ProgramArguments,['/opt/homebrew/bin/node',dir+'/scripts/reconcile-canary.mjs']);
+ const launch=run('launchctl',['print','gui/'+process.getuid()+'/com.steve.ken-reconcile-canary']);assert.equal(new URL(launch.match(/KEN_DATABASE_URL => (\S+)/)[1]).searchParams.get('host'),'/tmp');assert(/last exit code = 0/.test(launch));pass('Reconciliation durable and loaded socket; interval preserved; exit zero',{interval:plist.StartInterval,last_exit:0});
+ const log=read(plist.StandardOutPath).toString().trim().split('\n');assert(log.at(-1).includes('[reconcile-canary] OK'));assert(fs.statSync(plist.StandardOutPath).mtimeMs>=Date.parse(json(base+'/reloaded.json').at)-10000);pass('Reconciliation post-reload result',{last_line:log.at(-1),mtime:fs.statSync(plist.StandardOutPath).mtime.toISOString()});
+ proof.commit=run('git',['-C','/Users/macstudio3/Projects/Ken','rev-parse','HEAD']).trim();proof.verdict='PASS';proof.status='complete';proof.skipped=[{name:'Hourly email-capable wrapper execution',verdict:'SKIP',reason:'Explicitly excluded from authorization; parent separately verifies SELECT-only signal module.'},{name:'Full-fleet TCP shutdown',verdict:'SKIP',reason:'Outside Ken batch.'},{name:'Live rollback execution',verdict:'SKIP',reason:'Healthy rollout; rollback reviewed and copies rehearsed, no induced production fault.'}];
+})().catch(err=>{proof.verdict='FAIL';proof.status='partial';proof.error={name:err.name,code:err.code,message:String(err.message).replace(/postgres(?:ql)?:\/\/\S+/g,'[REDACTED_URL]')};}).finally(()=>{fs.writeFileSync(base+'/independent-verification.json',JSON.stringify(proof,null,2)+'\n');console.log(JSON.stringify(proof,null,2));if(proof.verdict!=='PASS')process.exitCode=1;});
diff --git a/verification/ken-rollout/independent-verification.json b/verification/ken-rollout/independent-verification.json
new file mode 100644
index 0000000..004815e
--- /dev/null
+++ b/verification/ken-rollout/independent-verification.json
@@ -0,0 +1,287 @@
+{
+ "task_id": "TK-11438",
+ "owner": "codex-run-11438-verifier",
+ "risk": "R4",
+ "timestamp": "2026-09-11T17:41:15.071Z",
+ "assertions": [
+ {
+ "name": "Ken unique identity and online restart",
+ "verdict": "PASS",
+ "detail": {
+ "before_pid": 92641,
+ "after_pid": 25903,
+ "restart_time": 39
+ }
+ },
+ {
+ "name": "DATABASE_URL dotenv Unix socket identity",
+ "verdict": "PASS",
+ "detail": {
+ "database": "bertha_betting",
+ "role": "macstudio3",
+ "addr": null,
+ "readonly": "on"
+ }
+ },
+ {
+ "name": "DATABASE_URL saved dump Unix socket identity",
+ "verdict": "PASS",
+ "detail": {
+ "database": "bertha_betting",
+ "role": "macstudio3",
+ "addr": null,
+ "readonly": "on"
+ }
+ },
+ {
+ "name": "KEN_DATABASE_URL dotenv Unix socket identity",
+ "verdict": "PASS",
+ "detail": {
+ "database": "ken",
+ "role": "macstudio3",
+ "addr": null,
+ "readonly": "on"
+ }
+ },
+ {
+ "name": "KEN_DATABASE_URL saved dump Unix socket identity",
+ "verdict": "PASS",
+ "detail": {
+ "database": "ken",
+ "role": "macstudio3",
+ "addr": null,
+ "readonly": "on"
+ }
+ },
+ {
+ "name": "Four effective/saved URL fields agree with dotenv",
+ "verdict": "PASS",
+ "detail": true
+ },
+ {
+ "name": "Captured operational switches and Ken trading configuration unchanged",
+ "verdict": "PASS",
+ "detail": {
+ "settings": {
+ "safe_mode": "false",
+ "trading_on": "true",
+ "kalshi_env": "prod",
+ "config_hash": "556a9eb6c8fed4832b515ffd6dba0de1"
+ },
+ "ksettings": [
+ {
+ "key": "live_run",
+ "value_hash": "5007ffdbce2484c46f0f1ebfed156a79"
+ },
+ {
+ "key": "trade_config",
+ "value_hash": "c77ced4a430c742f316497e371b18efc"
+ }
+ ]
+ }
+ },
+ {
+ "name": "LAN /api/setup none auth",
+ "verdict": "PASS",
+ "detail": {
+ "status": 401,
+ "body_sha256": "ca730ec7fc2b7edb7277e5b8e58d6a474971ca59dafb499141a4856c06132638",
+ "body_code_units": 35
+ }
+ },
+ {
+ "name": "LAN /api/setup invalid auth",
+ "verdict": "PASS",
+ "detail": {
+ "status": 401,
+ "body_sha256": "ca730ec7fc2b7edb7277e5b8e58d6a474971ca59dafb499141a4856c06132638",
+ "body_code_units": 35
+ }
+ },
+ {
+ "name": "LAN /api/setup valid auth",
+ "verdict": "PASS",
+ "detail": {
+ "status": 200,
+ "body_sha256": "b7618c456aa65071f5e72b8d56effe52b8edccbb2173a34abfac7ea514d3f2f0",
+ "body_code_units": 75
+ }
+ },
+ {
+ "name": "LAN /api/trading/history none auth",
+ "verdict": "PASS",
+ "detail": {
+ "status": 401,
+ "body_sha256": "ca730ec7fc2b7edb7277e5b8e58d6a474971ca59dafb499141a4856c06132638",
+ "body_code_units": 35
+ }
+ },
+ {
+ "name": "LAN /api/trading/history invalid auth",
+ "verdict": "PASS",
+ "detail": {
+ "status": 401,
+ "body_sha256": "ca730ec7fc2b7edb7277e5b8e58d6a474971ca59dafb499141a4856c06132638",
+ "body_code_units": 35
+ }
+ },
+ {
+ "name": "LAN /api/trading/history valid auth",
+ "verdict": "PASS",
+ "detail": {
+ "status": 200,
+ "body_sha256": "5e461574d4ae8b1ce2c5495529884fdecb0dbe49b483473258640165a9212d7c",
+ "body_code_units": 1195
+ }
+ },
+ {
+ "name": "Ken PID has no TCP 5432 socket",
+ "verdict": "PASS",
+ "detail": true
+ },
+ {
+ "name": "Ken PID has active Unix sockets",
+ "verdict": "PASS",
+ "detail": {
+ "lines": 7,
+ "sha256": "53c2cefe2834d7e98a83c8ae9d458de9b0ccb969d357530c0037bad80386ad2f"
+ }
+ },
+ {
+ "name": "Application database sessions use Unix sockets",
+ "verdict": "PASS",
+ "detail": [
+ {
+ "datname": "bertha_betting",
+ "usename": "macstudio3",
+ "client_addr": null,
+ "client_port": -1,
+ "pid": 20448
+ },
+ {
+ "datname": "ken",
+ "usename": "macstudio3",
+ "client_addr": null,
+ "client_port": -1,
+ "pid": 20457
+ }
+ ]
+ },
+ {
+ "name": "DATABASE_URL missing socket fails closed",
+ "verdict": "PASS",
+ "detail": {
+ "code": "ENOENT"
+ }
+ },
+ {
+ "name": "KEN_DATABASE_URL missing socket fails closed",
+ "verdict": "PASS",
+ "detail": {
+ "code": "ENOENT"
+ }
+ },
+ {
+ "name": "Server source unchanged",
+ "verdict": "PASS",
+ "detail": "8c20f30eb1a31fe7ad5c3884094dab8fcb0020ea7cf87d1b7c0a974693e9dc74"
+ },
+ {
+ "name": "Hourly wrapper exact approved one-line change; not executed",
+ "verdict": "PASS",
+ "detail": true
+ },
+ {
+ "name": "env rollback rehearsal restored exact prior bytes",
+ "verdict": "PASS",
+ "detail": true
+ },
+ {
+ "name": "wrapper rollback rehearsal restored exact prior bytes",
+ "verdict": "PASS",
+ "detail": true
+ },
+ {
+ "name": "plist rollback rehearsal restored exact prior bytes",
+ "verdict": "PASS",
+ "detail": true
+ },
+ {
+ "name": "Durable dotenv matches prepared bytes",
+ "verdict": "PASS",
+ "detail": true
+ },
+ {
+ "name": "Reconciliation durable and loaded socket; interval preserved; exit zero",
+ "verdict": "PASS",
+ "detail": {
+ "interval": 1800,
+ "last_exit": 0
+ }
+ },
+ {
+ "name": "Reconciliation post-reload result",
+ "verdict": "PASS",
+ "detail": {
+ "last_line": "[reconcile-canary] OK \u2014 returned=$472535.99 invested=$319060.81 balance=$163375.18 (all reconcile within $2)",
+ "mtime": "2026-09-11T17:38:14.880Z"
+ }
+ }
+ ],
+ "commands": [
+ "node --check /tmp/tk11438-ken-approved/independent-check.cjs",
+ "node /tmp/tk11438-ken-approved/independent-check.cjs (read-only; initial sandbox EPERM, authorized retry PASS)",
+ "direct existing PM2 getMonitorData RPC",
+ "node-postgres read-only identity/config/activity SELECTs using dotenv and saved PM2 URLs",
+ "LAN HTTP GET /api/setup and /api/trading/history with missing, invalid and valid Basic Auth",
+ "lsof -nP -a -p 25903 -iTCP and -U",
+ "launchctl print gui/501/com.steve.ken-reconcile-canary",
+ "plutil -convert json -o - reconciliation plist; source/backup hash reads; latest canary log read",
+ "git show --stat --oneline HEAD and git rev-parse HEAD"
+ ],
+ "application_mutations": 0,
+ "email_sends": 0,
+ "pid": 25903,
+ "pm_id": 15,
+ "warnings": [
+ {
+ "name": "Whole risk_state.config comparison",
+ "reason": "Baseline whole-config hash differs. Unchanged server.js lines 8027-8031 refresh weather_cache on scan. This is consistent with expected cache churn, but baseline omitted individual other keys, so exclusive cause and all-key preservation cannot be proved retroactively.",
+ "baseline_hash": "2fddc5b0ff823bbe1ea724dca0dca0d0",
+ "current_hash": "556a9eb6c8fed4832b515ffd6dba0de1"
+ }
+ ],
+ "commit": "d840eb666dbe5986eb48925ef9e694c3c3d450d3",
+ "verdict": "PASS",
+ "status": "complete",
+ "skipped": [
+ {
+ "name": "Hourly email-capable wrapper execution",
+ "verdict": "SKIP",
+ "reason": "Explicitly excluded from authorization; parent separately verifies SELECT-only signal module."
+ },
+ {
+ "name": "Full-fleet TCP shutdown",
+ "verdict": "SKIP",
+ "reason": "Outside Ken batch."
+ },
+ {
+ "name": "Live rollback execution",
+ "verdict": "SKIP",
+ "reason": "Healthy rollout; rollback reviewed and copies rehearsed, no induced production fault."
+ }
+ ],
+ "rollback_review": {
+ "original_must_fix": "Reload bootstrap failure left unloaded job outside rollback marker",
+ "resolution": "Parent now journals before bootout and restores loaded or absent prior target; reviewed actual helper and four mocked fault scenarios",
+ "mocked_scenarios": [
+ "loaded recovery",
+ "unloaded recovery",
+ "unexpected error fails closed",
+ "bootstrap failure retains journal"
+ ],
+ "mocked_artifact": "/tmp/tk11438-ken-approved/rollback-fault-proof.json",
+ "live_rollback_performed": false
+ },
+ "parent_acceptance_required": true
+}
diff --git a/verification/ken-rollout/monitor.json b/verification/ken-rollout/monitor.json
new file mode 100644
index 0000000..d56d454
--- /dev/null
+++ b/verification/ken-rollout/monitor.json
@@ -0,0 +1,110 @@
+{
+ "at": "2026-09-11T17:39:23.438Z",
+ "label": "monitor",
+ "pid": 25903,
+ "pm_id": 15,
+ "restart_time": 39,
+ "identities": [
+ {
+ "key": "DATABASE_URL",
+ "database": "bertha_betting",
+ "role": "macstudio3",
+ "addr": null,
+ "readonly": "on"
+ },
+ {
+ "key": "KEN_DATABASE_URL",
+ "database": "ken",
+ "role": "macstudio3",
+ "addr": null,
+ "readonly": "on"
+ }
+ ],
+ "settings": {
+ "safe_mode": "false",
+ "trading_on": "true",
+ "kalshi_env": "prod",
+ "config_hash": "402d274e93015b9d3c875fa9aa76b60a",
+ "stable_settings_hash": "c265a97bdda701bcaebe80968fee995d",
+ "weather_cache_fetched_at": "2026-09-11T17:32:45.483Z"
+ },
+ "kenSettings": [
+ {
+ "key": "live_run",
+ "value_hash": "5007ffdbce2484c46f0f1ebfed156a79"
+ },
+ {
+ "key": "trade_config",
+ "value_hash": "c77ced4a430c742f316497e371b18efc"
+ }
+ ],
+ "reconciliation": {
+ "returned": true,
+ "invested": true,
+ "balance": true
+ },
+ "http": [
+ {
+ "route": "/api/setup",
+ "auth": false,
+ "status": 401,
+ "sha256": "ca730ec7fc2b7edb7277e5b8e58d6a474971ca59dafb499141a4856c06132638",
+ "bytes": 35
+ },
+ {
+ "route": "/api/setup",
+ "auth": true,
+ "status": 200,
+ "sha256": "b7618c456aa65071f5e72b8d56effe52b8edccbb2173a34abfac7ea514d3f2f0",
+ "bytes": 75
+ },
+ {
+ "route": "/api/trading/history",
+ "auth": false,
+ "status": 401,
+ "sha256": "ca730ec7fc2b7edb7277e5b8e58d6a474971ca59dafb499141a4856c06132638",
+ "bytes": 35
+ },
+ {
+ "route": "/api/trading/history",
+ "auth": true,
+ "status": 200,
+ "sha256": "5e461574d4ae8b1ce2c5495529884fdecb0dbe49b483473258640165a9212d7c",
+ "bytes": 1197
+ }
+ ],
+ "process_db_tcp": [],
+ "saved_match": true,
+ "targets": {
+ "DATABASE_URL": {
+ "host": "localhost",
+ "database": "bertha_betting",
+ "socket": "/tmp",
+ "port": "5432"
+ },
+ "KEN_DATABASE_URL": {
+ "host": "localhost",
+ "database": "ken",
+ "socket": "/tmp",
+ "port": "5432"
+ }
+ },
+ "activity": [
+ {
+ "datname": "bertha_betting",
+ "usename": "macstudio3",
+ "client_addr": null,
+ "client_port": -1,
+ "pid": 98157
+ },
+ {
+ "datname": "ken",
+ "usename": "macstudio3",
+ "client_addr": null,
+ "client_port": -1,
+ "pid": 98158
+ }
+ ],
+ "source_hash": "8c20f30eb1a31fe7ad5c3884094dab8fcb0020ea7cf87d1b7c0a974693e9dc74",
+ "wrapper_hash": "61fb025a8011dccedc26f71e1017317074070b96a7b7f299d9f0556606f350a7"
+}
diff --git a/verification/ken-rollout/mutation-started.json b/verification/ken-rollout/mutation-started.json
new file mode 100644
index 0000000..b6e8ed4
--- /dev/null
+++ b/verification/ken-rollout/mutation-started.json
@@ -0,0 +1,6 @@
+{
+ "at": "2026-09-11T17:32:29.516Z",
+ "scope": "Ken only",
+ "before_pid": 92641,
+ "steps": []
+}
diff --git a/verification/ken-rollout/observation.json b/verification/ken-rollout/observation.json
new file mode 100644
index 0000000..e5c5bf1
--- /dev/null
+++ b/verification/ken-rollout/observation.json
@@ -0,0 +1,23 @@
+{
+ "at": "2026-09-11T17:39:23.460Z",
+ "verdict": "PASS",
+ "pid": 25903,
+ "restart_count_stable": true,
+ "two_pools_socket": true,
+ "auth_statuses": [
+ 401,
+ 200,
+ 401,
+ 200
+ ],
+ "reconciliation_job": {
+ "loaded_socket": true,
+ "last_exit": 0,
+ "interval": 1800,
+ "other_plist_fields_preserved": true,
+ "new_log_bytes": 109,
+ "log_sha256": "65773db99f0cd3902340b7a88a67697647ba0c08891bec2abff941a613016c10",
+ "result": "OK"
+ },
+ "email_wrapper_invoked": false
+}
diff --git a/verification/ken-rollout/parent-acceptance.json b/verification/ken-rollout/parent-acceptance.json
new file mode 100644
index 0000000..f66b08d
--- /dev/null
+++ b/verification/ken-rollout/parent-acceptance.json
@@ -0,0 +1,17 @@
+{
+ "ticket": "TK-11438",
+ "timestamp": "2026-09-11T17:46:09.886710+00:00",
+ "verdict": "KEN_BATCH_PASS_FULL_FLEET_INCOMPLETE",
+ "source_commit": "d840eb666dbe5986eb48925ef9e694c3c3d450d3",
+ "approval": "Steve -> codex-run-11340/TK-11478 -> current session, 2026-09-11; described current Ken batch only; all email sends excluded",
+ "parent_acceptance": "Independent 26 PASS assertions inspected against parent baseline, after, stability proofs and exact d840eb6 diff. Accepted.",
+ "email_sends_invoked": 0,
+ "wrapper_invoked": false,
+ "private_backup": "verification/ken-rollout/private/ (0700; files0600; gitignored)",
+ "rollback": "node verification/ken-rollout/rollout.cjs rollback (only on actual regression, guarded file fields and job restore; live rollback not executed)",
+ "limits": [
+ "Only captured operational switches and Ken config hashes proved unchanged; full risk_state hash changed consistently with automatic weather_cache, exclusive cause unproven.",
+ "Email-capable hourly wrapper not executed. Exact wrapper URL used with underlying SELECT-only module; normal schedule definition preserved.",
+ "No reboot or full-fleet TCP shutdown. Original/expanded inventory remains incomplete."
+ ]
+}
diff --git a/verification/ken-rollout/preparation.json b/verification/ken-rollout/preparation.json
new file mode 100644
index 0000000..9a68ba5
--- /dev/null
+++ b/verification/ken-rollout/preparation.json
@@ -0,0 +1,9 @@
+{
+ "at": "2026-09-11T17:28:40.056Z",
+ "verdict": "PASS",
+ "rollback_rehearsal": "exact file restores plus four-field dump roundtrip PASS",
+ "application_mutations": 0,
+ "email_sends": 0,
+ "pid": 92641,
+ "pm_id": 15
+}
diff --git a/verification/ken-rollout/reload-started.json b/verification/ken-rollout/reload-started.json
new file mode 100644
index 0000000..458d754
--- /dev/null
+++ b/verification/ken-rollout/reload-started.json
@@ -0,0 +1,6 @@
+{
+ "at": "2026-09-11T17:38:14.560Z",
+ "target": "gui/501/com.steve.ken-reconcile-canary",
+ "prior_loaded": true,
+ "phase": "bootout-complete"
+}
diff --git a/verification/ken-rollout/reloaded.json b/verification/ken-rollout/reloaded.json
new file mode 100644
index 0000000..a3031a8
--- /dev/null
+++ b/verification/ken-rollout/reloaded.json
@@ -0,0 +1,7 @@
+{
+ "at": "2026-09-11T17:38:14.577Z",
+ "verdict": "RELOADED_OBSERVE_PENDING",
+ "target": "gui/501/com.steve.ken-reconcile-canary",
+ "loaded_socket": true,
+ "loaded": true
+}
diff --git a/verification/ken-rollout/rollback-fault-proof.json b/verification/ken-rollout/rollback-fault-proof.json
new file mode 100644
index 0000000..443f790
--- /dev/null
+++ b/verification/ken-rollout/rollback-fault-proof.json
@@ -0,0 +1,22 @@
+{
+ "at": "2026-09-11T17:39:12.372Z",
+ "results": [
+ {
+ "scenario": "loaded",
+ "verdict": "PASS"
+ },
+ {
+ "scenario": "unloaded",
+ "verdict": "PASS"
+ },
+ {
+ "scenario": "unexpected-error",
+ "verdict": "PASS"
+ },
+ {
+ "scenario": "bootstrap-failure-retains-recovery-journal",
+ "verdict": "PASS"
+ }
+ ],
+ "live_commands": 0
+}
diff --git a/verification/ken-rollout/rollback-test.cjs b/verification/ken-rollout/rollback-test.cjs
new file mode 100644
index 0000000..aa7a2f0
--- /dev/null
+++ b/verification/ken-rollout/rollback-test.cjs
@@ -0,0 +1,21 @@
+// Fault-inject the actual launchd recovery branch without system tools or live IO.
+const fs=require('fs'),vm=require('vm'),assert=require('assert/strict');
+const source=fs.readFileSync(__dirname+'/rollout.cjs','utf8');
+const start=source.indexOf(" if(fs.existsSync(OUT+'/reload-started.json')){");
+const end=source.indexOf(" record('rollback'",start);assert(start>0&&end>start);
+const branch=source.slice(start,end),results=[];
+for(const state of ['loaded','unloaded','unexpected-error']){
+ const calls=[],records=[];const old='postgresql://localhost:5432/ken';
+ const context={OUT:'/fake',PRIV:'/fake/private',PLIST:'/fake/canary.plist',assert,JSON,process:{getuid:()=>501},fs:{existsSync:()=>true},cp:{spawnSync:()=>state==='loaded'?{status:0}:state==='unloaded'?{status:113,stderr:'Could not find service'}:{status:1,stderr:'Permission denied'}},record:(name,v)=>records.push({name,...v}),run:(cmd,args)=>{calls.push({cmd,args});if(cmd==='/usr/bin/plutil')return JSON.stringify({EnvironmentVariables:{KEN_DATABASE_URL:old}});if(args[0]==='print')return 'KEN_DATABASE_URL => '+old+'\n';return '';}};
+ let error;try{vm.runInNewContext(branch,context,{timeout:1000});}catch(e){error=e;}
+ if(state==='unexpected-error'){assert(error);assert(!calls.length);}else{assert(!error);assert.equal(calls.filter(x=>x.args[0]==='bootstrap').length,1);assert.equal(calls.filter(x=>x.args[0]==='bootout').length,state==='loaded'?1:0);assert.equal(records[0].verdict,'PASS');}
+ results.push({scenario:state,verdict:'PASS'});
+}
+const forwardStart=source.indexOf(" record('reload-started',{target,prior_loaded:true,phase:'before-bootout'});");
+const forwardEnd=source.indexOf(" }else if(mode===",forwardStart);assert(forwardStart>0&&forwardEnd>forwardStart);
+const journal=[],calls=[];let failed=false;
+try{vm.runInNewContext(source.slice(forwardStart,forwardEnd),{target:'gui/501/test',PLIST:'/fake',process:{getuid:()=>501},record:(name,v)=>journal.push({name,...v}),run:(cmd,args)=>{calls.push(args[0]);if(args[0]==='bootstrap')throw Error('injected bootstrap failure');return '';},assert,URL},{timeout:1000});}catch(e){failed=true;}
+assert(failed);assert.deepEqual(calls,['bootout','bootstrap']);assert.equal(journal.at(-1).phase,'bootout-complete');assert(journal.every(x=>x.name==='reload-started'));
+results.push({scenario:'bootstrap-failure-retains-recovery-journal',verdict:'PASS'});
+fs.writeFileSync(__dirname+'/rollback-fault-proof.json',JSON.stringify({at:new Date().toISOString(),results,live_commands:0},null,2)+'\n');
+console.log(JSON.stringify(results));
diff --git a/verification/ken-rollout/rollout.cjs b/verification/ken-rollout/rollout.cjs
new file mode 100644
index 0000000..28a5802
--- /dev/null
+++ b/verification/ken-rollout/rollout.cjs
@@ -0,0 +1,112 @@
+// TK11438 approved Ken-only transport migration. Never invokes the email wrapper.
+const fs=require('fs'),path=require('path'),crypto=require('crypto'),cp=require('child_process'),assert=require('assert/strict'),os=require('os');
+const ROOT='/Users/macstudio3/Projects/Ken',DIR=ROOT+'/kalshi-dash',OUT=__dirname,PRIV=OUT+'/private';
+const ENV=DIR+'/.env',WRAPPER=DIR+'/follow-the-winners-check.sh',PLIST='/Users/macstudio3/Library/LaunchAgents/com.steve.ken-reconcile-canary.plist',DUMP='/Users/macstudio3/.pm2/dump.pm2';
+const manifest=JSON.parse(fs.readFileSync('/Users/macstudio3/Projects/tk11438-postgres-migration/next-batch.json'));
+const parse=require('/Users/macstudio3/Projects/patterndesignlab/node_modules/dotenv').parse;
+const {Client}=require(DIR+'/node_modules/pg');
+const deps='/Users/macstudio3/.npm-global/lib/node_modules/pm2/node_modules/';
+const axon=require(deps+'pm2-axon'),rpc=require(deps+'pm2-axon-rpc');
+const mode=process.argv[2];assert(['prepare','apply','verify','reload','observe','rollback'].includes(mode));
+const sha=b=>crypto.createHash('sha256').update(b).digest('hex');
+const json=p=>JSON.parse(fs.readFileSync(p));
+const record=(name,x)=>fs.writeFileSync(OUT+'/'+name+'.json',JSON.stringify({at:new Date().toISOString(),...x},null,2)+'\n');
+const run=(cmd,args,opts={})=>cp.execFileSync(cmd,args,{encoding:'utf8',timeout:15000,maxBuffer:16*1024*1024,...opts});
+const git=(...args)=>run('git',args,{cwd:ROOT}).trim();
+const socketURL=s=>{const u=new URL(s);assert(['localhost','127.0.0.1','[::1]',''].includes(u.hostname));u.searchParams.set('host','/tmp');return u.toString();};
+const summarize=s=>{const u=new URL(s);return {host:u.hostname,database:u.pathname.slice(1),socket:u.searchParams.get('host'),port:u.port||'5432'};};
+const liveRpc=(method,opts={})=>new Promise((resolve,reject)=>{const sock=axon.socket('req'),client=new rpc.Client(sock);const t=setTimeout(()=>{sock.close();reject(Error('RPC timeout: '+method));},30000);sock.on('error',e=>{clearTimeout(t);sock.close();reject(e);});sock.connect('/Users/macstudio3/.pm2/rpc.sock');client.call(method,opts,(err,data)=>{clearTimeout(t);sock.close();err?reject(Error('RPC failed: '+method)):resolve(data);});});
+function unique(list){const a=list.filter(x=>x.name==='ken');assert.equal(a.length,1);const x=a[0],e=x.pm2_env||x;assert.equal(e.pm_cwd,DIR);assert.equal(e.pm_exec_path,DIR+'/start.sh');return x;}
+function fields(e){return {DATABASE_URL:e.DATABASE_URL,KEN_DATABASE_URL:e.KEN_DATABASE_URL,env:{DATABASE_URL:e.env?.DATABASE_URL,KEN_DATABASE_URL:e.env?.KEN_DATABASE_URL}};}
+function setFields(e,v){e.DATABASE_URL=v.DATABASE_URL;e.KEN_DATABASE_URL=v.KEN_DATABASE_URL;e.env.DATABASE_URL=v.env.DATABASE_URL;e.env.KEN_DATABASE_URL=v.env.KEN_DATABASE_URL;}
+function atomic(file,bytes,expected){assert.equal(sha(fs.readFileSync(file)),expected,'concurrent change: '+file);const temp=file+'.TK11438-'+process.pid;fs.writeFileSync(temp,bytes,{flag:'wx',mode:fs.statSync(file).mode&0o777});assert.equal(sha(fs.readFileSync(file)),expected,'changed during preparation: '+file);fs.renameSync(temp,file);assert.equal(sha(fs.readFileSync(file)),sha(bytes));}
+function patchDump(expected,next){const bytes=fs.readFileSync(DUMP),d=JSON.parse(bytes),e=unique(d);assert.deepEqual(fields(e),expected);setFields(e,next);const reverted=JSON.parse(JSON.stringify(d));setFields(unique(reverted),expected);assert.deepEqual(reverted,JSON.parse(bytes),'out-of-scope dump edit');atomic(DUMP,JSON.stringify(d,null,2),sha(bytes));return {before_sha:sha(bytes),after_sha:sha(fs.readFileSync(DUMP)),only_target_fields:true};}
+async function withDb(url,fn){const c=new Client({connectionString:url,application_name:'TK11438-Ken-'+mode,connectionTimeoutMillis:4000,options:'-c default_transaction_read_only=on -c statement_timeout=5000'});try{await c.connect();return await fn(c);}finally{await c.end().catch(()=>{});}}
+async function snapshot(label){
+ const list=await liveRpc('getMonitorData'),s=unique(list),e=s.pm2_env;assert.equal(e.status,'online');const env=parse(fs.readFileSync(ENV));const identities=[];
+ for(const key of ['DATABASE_URL','KEN_DATABASE_URL'])identities.push({key,...await withDb(env[key],async c=>(await c.query("SELECT current_database() database,current_user role,inet_client_addr()::text addr,current_setting('transaction_read_only') readonly")).rows[0])});
+ const settings=await withDb(env.DATABASE_URL,async c=>(await c.query("SELECT config->>'safe_mode' safe_mode,config->>'trading_on' trading_on,config->>'kalshi_env' kalshi_env,md5(config::text) config_hash,md5((config-'weather_cache')::text) stable_settings_hash,config->'weather_cache'->>'fetched_at' weather_cache_fetched_at FROM risk_state ORDER BY updated_at DESC LIMIT 1")).rows[0]);
+ const kenSettings=await withDb(env.KEN_DATABASE_URL,async c=>(await c.query("SELECT key,md5(value::text) value_hash FROM ken_config WHERE key IN ('live_run','trade_config') ORDER BY key")).rows);
+ const canarySource=fs.readFileSync(DIR+'/scripts/reconcile-canary.mjs','utf8');const sql=canarySource.match(/await pool\.query\(`([\s\S]*?)`\)/)[1];
+ const sums=await withDb(env.KEN_DATABASE_URL,async c=>(await c.query(sql)).rows[0]);
+ const num=x=>Number(x);const reconciliation={returned:Math.abs(num(sums.rollup_returned)-num(sums.ledger_returned))<=200,invested:Math.abs(num(sums.rollup_invested)-num(sums.ledger_invested))<=200,balance:Math.abs(num(sums.balance)-(num(sums.seed)-num(sums.rollup_invested)+num(sums.rollup_returned)))<=200};
+ const http=[];const lan=Object.values(os.networkInterfaces()).flat().find(x=>x.family==='IPv4'&&!x.internal&&/^192\.168\./.test(x.address));assert(lan,'LAN interface needed for real non-loopback auth test');
+ for(const route of ['/api/setup','/api/trading/history'])for(const auth of [false,true]){
+ const r=await fetch('http://'+lan.address+':7810'+route,{headers:auth?{authorization:'Basic '+Buffer.from((env.ADMIN_USER||'admin')+':'+env.ADMIN_PASSWORD).toString('base64')}:{},signal:AbortSignal.timeout(10000)});const text=await r.text();assert.equal(r.status,auth?200:401);if(auth)JSON.parse(text);http.push({route,auth,status:r.status,sha256:sha(text),bytes:Buffer.byteLength(text)});
+ }
+ let tcp='';try{tcp=run('lsof',['-nP','-a','-p',String(s.pid),'-iTCP']);}catch(err){if(err.status!==1)throw err;tcp=err.stdout||'';}
+ const processDbTcp=tcp.split('\n').filter(l=>/:5432\b/.test(l));
+ const saved=unique(json(DUMP));const savedMatch=['DATABASE_URL','KEN_DATABASE_URL'].every(k=>e[k]===env[k]&&e.env[k]===env[k]&&saved[k]===env[k]&&saved.env[k]===env[k]);
+ const activity=await withDb('postgresql:///postgres?host=/tmp',async c=>(await c.query("SELECT datname,usename,client_addr::text,client_port,pid FROM pg_stat_activity WHERE datname IN ('ken','bertha_betting') AND application_name NOT LIKE 'TK11438%' ORDER BY datname,pid")).rows);
+ const proof={label,pid:s.pid,pm_id:e.pm_id,restart_time:e.restart_time,identities,settings,kenSettings,reconciliation,http,process_db_tcp:processDbTcp,saved_match:savedMatch,targets:Object.fromEntries(['DATABASE_URL','KEN_DATABASE_URL'].map(k=>[k,summarize(env[k])])),activity,source_hash:sha(fs.readFileSync(DIR+'/server.js')),wrapper_hash:sha(fs.readFileSync(WRAPPER))};record(label,proof);return proof;
+}
+(async()=>{
+ if(mode==='prepare'){
+ assert(!fs.existsSync(PRIV),'Already prepared; do not overwrite baseline');assert.equal(git('status','--porcelain'),'','Ken working tree dirty');
+ for(const x of [...manifest.file_changes,...manifest.environment_changes])if(x.sha256)assert.equal(sha(fs.readFileSync(x.path)),x.sha256,'approved source drift: '+x.path);
+ const s=unique(await liveRpc('getMonitorData')),saved=unique(json(DUMP)),envBytes=fs.readFileSync(ENV),env=parse(envBytes);const before=fields(s.pm2_env);assert.deepEqual(before,fields(saved),'effective/dump baseline mismatch');for(const k of ['DATABASE_URL','KEN_DATABASE_URL'])assert.equal(before[k],env[k]);
+ const desired={DATABASE_URL:socketURL(env.DATABASE_URL),KEN_DATABASE_URL:socketURL(env.KEN_DATABASE_URL)};assert.equal(summarize(desired.DATABASE_URL).database,'bertha_betting');assert.equal(summarize(desired.KEN_DATABASE_URL).database,'ken');
+ let nextEnv=envBytes.toString();for(const [k,v]of Object.entries(desired)){assert(!v.includes("'"));const re=new RegExp('^(?:export\\s+)?'+k+'=.*$','gm');assert.equal([...nextEnv.matchAll(re)].length,1);nextEnv=nextEnv.replace(re,k+"='"+v+"'");}assert.deepEqual({...parse(nextEnv),...env},env);for(const k of Object.keys(env))if(!(k in desired))assert.equal(parse(nextEnv)[k],env[k]);
+ const w=manifest.file_changes[0],oldWrapper=fs.readFileSync(WRAPPER,'utf8'),newWrapper=oldWrapper.replace(w.old,w.new);assert.notEqual(oldWrapper,newWrapper);assert.equal(newWrapper.replace(w.new,w.old),oldWrapper);
+ const oldPlist=fs.readFileSync(PLIST);const plist=JSON.parse(run('/usr/bin/plutil',['-convert','json','-o','-',PLIST]));assert.equal(plist.Label,'com.steve.ken-reconcile-canary');const oldUrl=plist.EnvironmentVariables.KEN_DATABASE_URL;plist.EnvironmentVariables.KEN_DATABASE_URL=socketURL(oldUrl);const newPlist=run('/usr/bin/plutil',['-convert','xml1','-o','-','--','-'],{input:JSON.stringify(plist)});
+ fs.mkdirSync(PRIV,{mode:0o700});for(const [f,b]of Object.entries({'env.before':envBytes,'env.after':nextEnv,'wrapper.before':oldWrapper,'wrapper.after':newWrapper,'plist.before':oldPlist,'plist.after':newPlist}))fs.writeFileSync(PRIV+'/'+f,b,{mode:0o600,flag:'wx'});
+ const rec={head:git('rev-parse','HEAD'),pid:s.pid,pm_id:s.pm2_env.pm_id,before,desired,source_hash:sha(fs.readFileSync(DIR+'/server.js')),hashes:{env:sha(envBytes),wrapper:sha(oldWrapper),plist:sha(oldPlist)}};fs.writeFileSync(PRIV+'/receipt.json',JSON.stringify(rec,null,2),{mode:0o600,flag:'wx'});
+ // Rehearse full file restore and scoped dump round-trip on copies, never live.
+ const d=json(DUMP),original=JSON.parse(JSON.stringify(d));setFields(unique(d),{...desired,env:desired});setFields(unique(d),before);assert.deepEqual(d,original);
+ for(const name of ['env','wrapper','plist']){const p=PRIV+'/rehearsal-'+name;fs.copyFileSync(PRIV+'/'+name+'.before',p);atomic(p,fs.readFileSync(PRIV+'/'+name+'.after'),rec.hashes[name]);atomic(p,fs.readFileSync(PRIV+'/'+name+'.before'),sha(fs.readFileSync(PRIV+'/'+name+'.after')));assert.equal(sha(fs.readFileSync(p)),rec.hashes[name]);}
+ run('/bin/bash',['-n',PRIV+'/wrapper.after']);run('/bin/bash',['-n',PRIV+'/env.after']);run('/usr/bin/plutil',['-lint',PRIV+'/plist.after']);
+ const baseline=await snapshot('baseline');assert(Object.values(baseline.reconciliation).every(Boolean),'Existing reconciliation failure: stop before job reload');
+ record('preparation',{verdict:'PASS',rollback_rehearsal:'exact file restores plus four-field dump roundtrip PASS',application_mutations:0,email_sends:0,pid:s.pid,pm_id:rec.pm_id});
+ }else if(mode==='apply'){
+ const rec=json(PRIV+'/receipt.json');assert.equal(json(OUT+'/preparation.json').verdict,'PASS');assert(!fs.existsSync(OUT+'/applied.json'));assert.equal(git('status','--porcelain'),'');assert.equal(git('rev-parse','HEAD'),rec.head);
+ const s=unique(await liveRpc('getMonitorData'));assert.equal(s.pid,rec.pid);assert.deepEqual(fields(s.pm2_env),rec.before);assert.equal(sha(fs.readFileSync(DIR+'/server.js')),rec.source_hash);
+ for(const [p,k]of [[ENV,'env'],[WRAPPER,'wrapper'],[PLIST,'plist']])assert.equal(sha(fs.readFileSync(p)),rec.hashes[k]);assert.deepEqual(fields(unique(json(DUMP))),rec.before);
+ record('mutation-started',{scope:'Ken only',before_pid:s.pid,steps:[]});
+ atomic(ENV,fs.readFileSync(PRIV+'/env.after'),rec.hashes.env);atomic(WRAPPER,fs.readFileSync(PRIV+'/wrapper.after'),rec.hashes.wrapper);atomic(PLIST,fs.readFileSync(PRIV+'/plist.after'),rec.hashes.plist);
+ run('/bin/bash',['-n',WRAPPER]);run('/usr/bin/plutil',['-lint',PLIST]);
+ git('add','--','kalshi-dash/follow-the-winners-check.sh');git('-c','user.name=Steve Abrams','-c','user.email=steve@designerwallcoverings.com','commit','-q','-m','Use Unix socket for Ken hourly signal database');
+ const dumpProof=patchDump(rec.before,{...rec.desired,env:rec.desired});
+ record('config-applied',{dump:dumpProof,commit:git('rev-parse','HEAD'),application_restart_pending:true});
+ await liveRpc('restartProcessId',{id:rec.pm_id,env:rec.desired});
+ const after=unique(await liveRpc('getMonitorData'));assert.notEqual(after.pid,rec.pid);record('applied',{verdict:'APPLIED_VERIFY_PENDING',before_pid:rec.pid,after_pid:after.pid,commit:git('rev-parse','HEAD'),email_wrapper_executed:false});
+ }else if(mode==='verify'){
+ const p=await snapshot('after'),b=json(OUT+'/baseline.json'),rec=json(PRIV+'/receipt.json');assert(p.identities.every(x=>x.addr===null&&x.readonly==='on'));assert.equal(p.process_db_tcp.length,0);assert(p.saved_match);assert.deepEqual(p.identities.map(x=>[x.key,x.database,x.role]),b.identities.map(x=>[x.key,x.database,x.role]));assert.deepEqual(['safe_mode','trading_on','kalshi_env'].map(k=>p.settings[k]),['safe_mode','trading_on','kalshi_env'].map(k=>b.settings[k]));assert.deepEqual(p.kenSettings,b.kenSettings);assert.equal(p.source_hash,b.source_hash);assert(p.activity.some(x=>x.datname==='ken'&&x.client_addr===null));assert(p.activity.some(x=>x.datname==='bertha_betting'&&x.client_addr===null));
+ assert.equal(sha(fs.readFileSync(ENV)),sha(fs.readFileSync(PRIV+'/env.after')));assert.equal(sha(fs.readFileSync(PLIST)),sha(fs.readFileSync(PRIV+'/plist.after')));
+ const bad=new Client({host:OUT+'/missing-socket',database:'ken',connectionTimeoutMillis:1000});let code;try{await bad.connect();throw Error('unexpected missing socket connection');}catch(err){code=err.code;assert.equal(code,'ENOENT');}finally{await bad.end().catch(()=>{});}
+ // Run underlying pure SELECT module only. The wrapper that emails is excluded.
+ const wrapperUrl=fs.readFileSync(WRAPPER,'utf8').match(/KEN_DATABASE_URL="([^"]+)"/)?.[1];assert(wrapperUrl);assert.equal(new URL(wrapperUrl).searchParams.get('host'),'/tmp');assert.equal(new URL(wrapperUrl).pathname,'/ken');
+ const result=run('/opt/homebrew/bin/node',[DIR+'/follow-the-winners.mjs'],{cwd:DIR,timeout:20000,env:{PATH:process.env.PATH,HOME:process.env.HOME,USER:os.userInfo().username,KEN_DATABASE_URL:wrapperUrl,PGOPTIONS:'-c default_transaction_read_only=on -c statement_timeout=5000'}});
+ record('verification',{verdict:'PASS',pid:p.pid,two_pools_socket:true,auth_statuses:p.http.map(x=>x.status),captured_trading_controls_unchanged:true,whole_config_warning:{before:b.settings.config_hash,after:p.settings.config_hash,detail:'AutonomousScan updates weather_cache. Captured switches and ken_config hashes match; uncaptured risk_state keys cannot be proven unchanged retroactively.'},durable_match:true,missing_socket:code,signal_module:{exit:0,configuration_source:'exact KEN_DATABASE_URL from updated hourly wrapper',target:summarize(wrapperUrl),output_sha256:sha(result),bytes:result.length,email_wrapper_executed:false},reconciliation:p.reconciliation,source_unchanged:true});
+ }else if(mode==='reload'){
+ assert.equal(json(OUT+'/verification.json').verdict,'PASS');assert(!fs.existsSync(OUT+'/reloaded.json'));const target='gui/'+process.getuid()+'/com.steve.ken-reconcile-canary';const before=run('launchctl',['print',target]);assert(!/\n\s+pid = \d+/.test(before),'Canary running: wait until idle');
+ record('reload-started',{target,prior_loaded:true,phase:'before-bootout'});
+ run('launchctl',['bootout','gui/'+process.getuid(),PLIST]);record('reload-started',{target,prior_loaded:true,phase:'bootout-complete'});
+ run('launchctl',['bootstrap','gui/'+process.getuid(),PLIST]);
+ const after=run('launchctl',['print',target]);const loadedUrl=after.match(/KEN_DATABASE_URL => (\S+)/)?.[1];assert(loadedUrl);assert.equal(new URL(loadedUrl).searchParams.get('host'),'/tmp');record('reloaded',{verdict:'RELOADED_OBSERVE_PENDING',target,loaded_socket:true,loaded:true});
+ }else if(mode==='observe'){
+ const proof=await snapshot('monitor'),after=json(OUT+'/after.json');assert.equal(proof.pid,after.pid);assert.equal(proof.restart_time,after.restart_time);assert.equal(proof.process_db_tcp.length,0);assert(proof.saved_match);assert(proof.identities.every(x=>x.addr===null));
+ const target='gui/'+process.getuid()+'/com.steve.ken-reconcile-canary',loaded=run('launchctl',['print',target]);
+ const loadedUrl=loaded.match(/KEN_DATABASE_URL => (\S+)/)?.[1];assert.equal(new URL(loadedUrl).searchParams.get('host'),'/tmp');assert(/last exit code = 0\b/.test(loaded));assert(!/\n\s+pid = \d+/.test(loaded),'canary still running');assert(/run interval = 1800 seconds/.test(loaded));
+ const baseline=json(OUT+'/canary-log-baseline.json'),bytes=fs.readFileSync(baseline.path);assert(bytes.length>=baseline.size,'log rotated; inspect new evidence');const tail=bytes.subarray(baseline.size).toString();assert(tail.includes('[reconcile-canary] OK'));assert(!/DIVERGENCE|\[reconcile-canary\] error:/.test(tail));
+ const before=JSON.parse(run('/usr/bin/plutil',['-convert','json','-o','-',PRIV+'/plist.before'])),current=JSON.parse(run('/usr/bin/plutil',['-convert','json','-o','-',PLIST]));current.EnvironmentVariables.KEN_DATABASE_URL=before.EnvironmentVariables.KEN_DATABASE_URL;assert.deepEqual(current,before);
+ record('observation',{verdict:'PASS',pid:proof.pid,restart_count_stable:true,two_pools_socket:true,auth_statuses:proof.http.map(x=>x.status),reconciliation_job:{loaded_socket:true,last_exit:0,interval:1800,other_plist_fields_preserved:true,new_log_bytes:tail.length,log_sha256:sha(tail),result:'OK'},email_wrapper_invoked:false});
+ }else if(mode==='rollback'){
+ const rec=json(PRIV+'/receipt.json');assert(fs.existsSync(OUT+'/mutation-started.json'));assert.equal(sha(fs.readFileSync(DIR+'/server.js')),rec.source_hash);
+ for(const [p,k]of [[ENV,'env'],[WRAPPER,'wrapper'],[PLIST,'plist']]){const current=sha(fs.readFileSync(p)),after=sha(fs.readFileSync(PRIV+'/'+k+'.after'));assert([rec.hashes[k],after].includes(current),'peer edit blocks rollback');if(current===after)atomic(p,fs.readFileSync(PRIV+'/'+k+'.before'),after);}
+ const f=fields(unique(json(DUMP)));if(JSON.stringify(f)!==JSON.stringify(rec.before))patchDump({...rec.desired,env:rec.desired},rec.before);
+ await liveRpc('restartProcessId',{id:rec.pm_id,env:{DATABASE_URL:rec.before.DATABASE_URL,KEN_DATABASE_URL:rec.before.KEN_DATABASE_URL}});
+ if(fs.existsSync(OUT+'/reload-started.json')){
+ const target='gui/'+process.getuid()+'/com.steve.ken-reconcile-canary';
+ const loaded=cp.spawnSync('launchctl',['print',target],{encoding:'utf8',timeout:10000});
+ if(loaded.status===0)run('launchctl',['bootout','gui/'+process.getuid(),PLIST]);
+ else assert(/could not find service|service not found/i.test(loaded.stderr||''),'unexpected launchd state; inspect before rollback');
+ run('launchctl',['bootstrap','gui/'+process.getuid(),PLIST]);
+ const restored=run('launchctl',['print',target]),loadedUrl=restored.match(/KEN_DATABASE_URL => (\S+)/)?.[1];
+ const originalPlist=JSON.parse(run('/usr/bin/plutil',['-convert','json','-o','-',PRIV+'/plist.before']));
+ assert.equal(loadedUrl,originalPlist.EnvironmentVariables.KEN_DATABASE_URL);
+ record('reload-rollback',{verdict:'PASS',prior_job_restored:true,target});
+ }
+ record('rollback',{verdict:'RESTORED_VERIFY_BASELINE_REQUIRED'});
+ }
+ console.log(JSON.stringify({mode,verdict:'PASS',evidence:OUT}));
+})().catch(err=>{record(mode+'-failure',{verdict:'FAIL',code:err.code||err.name,message:String(err.message).replace(/postgres(?:ql)?:\/\/\S+/g,'[REDACTED_URL]')});console.error(JSON.stringify({mode,verdict:'FAIL',code:err.code||err.name,message:String(err.message).replace(/postgres(?:ql)?:\/\/\S+/g,'[REDACTED_URL]')}));process.exitCode=1;});
diff --git a/verification/ken-rollout/verification.json b/verification/ken-rollout/verification.json
new file mode 100644
index 0000000..8dd3864
--- /dev/null
+++ b/verification/ken-rollout/verification.json
@@ -0,0 +1,39 @@
+{
+ "at": "2026-09-11T17:38:04.312Z",
+ "verdict": "PASS",
+ "pid": 25903,
+ "two_pools_socket": true,
+ "auth_statuses": [
+ 401,
+ 200,
+ 401,
+ 200
+ ],
+ "captured_trading_controls_unchanged": true,
+ "whole_config_warning": {
+ "before": "2fddc5b0ff823bbe1ea724dca0dca0d0",
+ "after": "402d274e93015b9d3c875fa9aa76b60a",
+ "detail": "AutonomousScan updates weather_cache. Captured switches and ken_config hashes match; uncaptured risk_state keys cannot be proven unchanged retroactively."
+ },
+ "durable_match": true,
+ "missing_socket": "ENOENT",
+ "signal_module": {
+ "exit": 0,
+ "configuration_source": "exact KEN_DATABASE_URL from updated hourly wrapper",
+ "target": {
+ "host": "localhost",
+ "database": "ken",
+ "socket": "/tmp",
+ "port": "5432"
+ },
+ "output_sha256": "68cd0b07d3758275b99e33dddcbf8f6d619a198016d6f8fc68f019920f4195b5",
+ "bytes": 978,
+ "email_wrapper_executed": false
+ },
+ "reconciliation": {
+ "returned": true,
+ "invested": true,
+ "balance": true
+ },
+ "source_unchanged": true
+}
diff --git a/verification/ken-rollout/verify-failure.json b/verification/ken-rollout/verify-failure.json
new file mode 100644
index 0000000..645337a
--- /dev/null
+++ b/verification/ken-rollout/verify-failure.json
@@ -0,0 +1,6 @@
+{
+ "at": "2026-09-11T17:37:10.631Z",
+ "verdict": "FAIL",
+ "code": "Error",
+ "message": "RPC timeout: getMonitorData"
+}
diff --git a/verification/ken-rollout/verify-rpc-timeout.json b/verification/ken-rollout/verify-rpc-timeout.json
new file mode 100644
index 0000000..645337a
--- /dev/null
+++ b/verification/ken-rollout/verify-rpc-timeout.json
@@ -0,0 +1,6 @@
+{
+ "at": "2026-09-11T17:37:10.631Z",
+ "verdict": "FAIL",
+ "code": "Error",
+ "message": "RPC timeout: getMonitorData"
+}
← f93e515 Verify socket handoff and prepare Ken scheduler migration ap
·
back to Tk11438 Postgres Migration
·
Classify remaining PostgreSQL consumers and prepare Gracie s b4b5e5c →