merge: readable linked payment/document chip + SMS alert loading/error state
parent
a267cd07e6
commit
551c9e3d30
@ -0,0 +1,179 @@
|
|||||||
|
{
|
||||||
|
"running": true,
|
||||||
|
"startedAt": "2026-07-20T16:44:54.772Z",
|
||||||
|
"workers": {
|
||||||
|
"map": {
|
||||||
|
"runCount": 11,
|
||||||
|
"successCount": 11,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 8.090909090909092,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-07-20T19:29:55.184Z",
|
||||||
|
"lastStartedAt": "2026-07-20T19:14:55.176Z",
|
||||||
|
"lastRun": "2026-07-20T19:14:55.183Z"
|
||||||
|
},
|
||||||
|
"audit": {
|
||||||
|
"runCount": 17,
|
||||||
|
"successCount": 17,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 8.882352941176471,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-07-20T19:26:56.025Z",
|
||||||
|
"lastStartedAt": "2026-07-20T19:26:56.097Z",
|
||||||
|
"lastRun": "2026-07-20T19:26:56.134Z"
|
||||||
|
},
|
||||||
|
"optimize": {
|
||||||
|
"runCount": 11,
|
||||||
|
"successCount": 11,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 6.454545454545454,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-07-20T19:33:55.170Z",
|
||||||
|
"lastStartedAt": "2026-07-20T19:18:55.161Z",
|
||||||
|
"lastRun": "2026-07-20T19:18:55.166Z"
|
||||||
|
},
|
||||||
|
"consolidate": {
|
||||||
|
"runCount": 6,
|
||||||
|
"successCount": 6,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 7.5,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-07-20T19:50:55.096Z",
|
||||||
|
"lastStartedAt": "2026-07-20T19:20:55.092Z",
|
||||||
|
"lastRun": "2026-07-20T19:20:55.094Z"
|
||||||
|
},
|
||||||
|
"testgaps": {
|
||||||
|
"runCount": 8,
|
||||||
|
"successCount": 8,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 8.375,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-07-20T19:32:55.117Z",
|
||||||
|
"lastStartedAt": "2026-07-20T19:12:55.104Z",
|
||||||
|
"lastRun": "2026-07-20T19:12:55.114Z"
|
||||||
|
},
|
||||||
|
"backup": {
|
||||||
|
"runCount": 1,
|
||||||
|
"successCount": 1,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 9,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-07-21T16:54:54.808Z",
|
||||||
|
"lastStartedAt": "2026-07-20T16:54:54.797Z",
|
||||||
|
"lastRun": "2026-07-20T16:54:54.806Z"
|
||||||
|
},
|
||||||
|
"harness": {
|
||||||
|
"runCount": 1,
|
||||||
|
"successCount": 1,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 729,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-07-20T22:56:55.533Z",
|
||||||
|
"lastStartedAt": "2026-07-20T16:56:54.797Z",
|
||||||
|
"lastRun": "2026-07-20T16:56:55.526Z"
|
||||||
|
},
|
||||||
|
"predict": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false
|
||||||
|
},
|
||||||
|
"document": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"autoStart": false,
|
||||||
|
"logDir": "C:\\SiMS\\hq\\.claude-flow\\logs",
|
||||||
|
"stateFile": "C:\\SiMS\\hq\\.claude-flow\\daemon-state.json",
|
||||||
|
"maxConcurrent": 2,
|
||||||
|
"workerTimeoutMs": 960000,
|
||||||
|
"resourceThresholds": {
|
||||||
|
"maxCpuLoad": 9.600000000000001,
|
||||||
|
"minFreeMemoryPercent": 10
|
||||||
|
},
|
||||||
|
"ttlMs": 43200000,
|
||||||
|
"idleShutdownMs": 0,
|
||||||
|
"aiWorkersEnabled": false,
|
||||||
|
"workers": [
|
||||||
|
{
|
||||||
|
"type": "map",
|
||||||
|
"intervalMs": 900000,
|
||||||
|
"offsetMs": 0,
|
||||||
|
"priority": "normal",
|
||||||
|
"description": "Codebase mapping",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "audit",
|
||||||
|
"intervalMs": 600000,
|
||||||
|
"offsetMs": 120000,
|
||||||
|
"priority": "critical",
|
||||||
|
"description": "Security analysis",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "optimize",
|
||||||
|
"intervalMs": 900000,
|
||||||
|
"offsetMs": 240000,
|
||||||
|
"priority": "high",
|
||||||
|
"description": "Performance optimization",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consolidate",
|
||||||
|
"intervalMs": 1800000,
|
||||||
|
"offsetMs": 360000,
|
||||||
|
"priority": "low",
|
||||||
|
"description": "Memory distillation (ADR-174)",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "testgaps",
|
||||||
|
"intervalMs": 1200000,
|
||||||
|
"offsetMs": 480000,
|
||||||
|
"priority": "normal",
|
||||||
|
"description": "Test coverage analysis",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "backup",
|
||||||
|
"intervalMs": 86400000,
|
||||||
|
"offsetMs": 600000,
|
||||||
|
"priority": "low",
|
||||||
|
"description": "Nightly memory DB backup (WAL-safe, rotated)",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "harness",
|
||||||
|
"intervalMs": 21600000,
|
||||||
|
"offsetMs": 720000,
|
||||||
|
"priority": "low",
|
||||||
|
"description": "Self-optimizing harness loop (opt-in RUFLO_HARNESS_LOOP, $0-default)",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "predict",
|
||||||
|
"intervalMs": 600000,
|
||||||
|
"offsetMs": 0,
|
||||||
|
"priority": "low",
|
||||||
|
"description": "Predictive preloading",
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "document",
|
||||||
|
"intervalMs": 3600000,
|
||||||
|
"offsetMs": 0,
|
||||||
|
"priority": "low",
|
||||||
|
"description": "Auto-documentation",
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"savedAt": "2026-07-20T19:26:56.135Z"
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-07-20T19:14:55.182Z",
|
||||||
|
"projectRoot": "C:\\SiMS\\hq",
|
||||||
|
"structure": {
|
||||||
|
"hasPackageJson": true,
|
||||||
|
"hasTsConfig": true,
|
||||||
|
"hasClaudeConfig": true,
|
||||||
|
"hasClaudeFlow": true
|
||||||
|
},
|
||||||
|
"scannedAt": 1784574895183
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-07-20T19:20:55.094Z",
|
||||||
|
"distillationEnabled": true,
|
||||||
|
"patternsConsolidated": 0,
|
||||||
|
"memoryCleaned": 0,
|
||||||
|
"duplicatesRemoved": 0,
|
||||||
|
"episodes": 0,
|
||||||
|
"patternEmbeddings": 0,
|
||||||
|
"causalEdges": 0,
|
||||||
|
"promoted": 0,
|
||||||
|
"byProvenance": {},
|
||||||
|
"namespaces": [],
|
||||||
|
"dryRun": false,
|
||||||
|
"corrupt": false,
|
||||||
|
"skipped": "no-db"
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-07-20T19:18:55.166Z",
|
||||||
|
"mode": "local",
|
||||||
|
"memoryUsage": {
|
||||||
|
"rss": 46977024,
|
||||||
|
"heapTotal": 21909504,
|
||||||
|
"heapUsed": 20452192,
|
||||||
|
"external": 3903735,
|
||||||
|
"arrayBuffers": 100670
|
||||||
|
},
|
||||||
|
"uptime": 9240.595012,
|
||||||
|
"optimizations": {
|
||||||
|
"cacheHitRate": 0.78,
|
||||||
|
"avgResponseTime": 45
|
||||||
|
},
|
||||||
|
"note": "Install Claude Code CLI for AI-powered optimization suggestions"
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-07-20T19:26:56.127Z",
|
||||||
|
"mode": "local",
|
||||||
|
"checks": {
|
||||||
|
"envFilesProtected": true,
|
||||||
|
"gitIgnoreExists": true,
|
||||||
|
"noHardcodedSecrets": true
|
||||||
|
},
|
||||||
|
"riskLevel": "low",
|
||||||
|
"recommendations": [],
|
||||||
|
"note": "Install Claude Code CLI for AI-powered security analysis"
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-07-20T19:12:55.112Z",
|
||||||
|
"mode": "local",
|
||||||
|
"hasTestDir": false,
|
||||||
|
"estimatedCoverage": "unknown",
|
||||||
|
"gaps": [],
|
||||||
|
"note": "Install Claude Code CLI for AI-powered test gap analysis"
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"adoptedAt": 1784565886864,
|
||||||
|
"championId": "sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319",
|
||||||
|
"manifest": {
|
||||||
|
"schema": "ruflo.proven-config/v1",
|
||||||
|
"policy": {
|
||||||
|
"ref": "sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319",
|
||||||
|
"value": {
|
||||||
|
"alpha": 0.3,
|
||||||
|
"subjectWeight": 1,
|
||||||
|
"mmrLambda": 0.5,
|
||||||
|
"bodyWeight": 1.5,
|
||||||
|
"typePenaltyFactor": 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"layer": "framework/node-cli",
|
||||||
|
"compatibility": {
|
||||||
|
"ruflo": ">=3.24.0"
|
||||||
|
},
|
||||||
|
"benchmark": {
|
||||||
|
"corpus": "ADR-081-labelled-v1",
|
||||||
|
"corpusHash": "sha256:2f700b5c363e20a3bd88ce2bc9b87bbbbaa61732c6177894c6ec37890f888982"
|
||||||
|
},
|
||||||
|
"receipt": {
|
||||||
|
"heldOutDelta": 0.07381404928570845,
|
||||||
|
"redblue": "PASS",
|
||||||
|
"drift": 0,
|
||||||
|
"canary": {
|
||||||
|
"rollbackRate": 0,
|
||||||
|
"latencyP95": 244.612458000076,
|
||||||
|
"costPerTask": 0
|
||||||
|
},
|
||||||
|
"receiptCoverage": 1
|
||||||
|
},
|
||||||
|
"platform": [
|
||||||
|
"linux",
|
||||||
|
"macOS",
|
||||||
|
"windows"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"previous": ""
|
||||||
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
// Go-live data correction: the earlier migrate-sqlite-to-pg.ts run pulled from local SQLite,
|
||||||
|
// which turned out to be stale/near-empty — the real working data was in a local Postgres
|
||||||
|
// instance instead. This wipes the TARGET (cloud) hq database's public schema and reloads it
|
||||||
|
// from the real SOURCE (local Postgres). Both engines are Postgres here, so every column type
|
||||||
|
// matches exactly — no conversion needed.
|
||||||
|
// SOURCE_DATABASE_URL and TARGET_DATABASE_URL must both be set explicitly, no defaults, so
|
||||||
|
// this can never accidentally point at (or wipe) the wrong database.
|
||||||
|
//
|
||||||
|
// Run from repo root:
|
||||||
|
// SOURCE_DATABASE_URL=postgres://hq:<local-pw>@127.0.0.1:5432/hq ^
|
||||||
|
// TARGET_DATABASE_URL=postgres://postgres:<cloud-pw>@13.232.53.115:5432/hq ^
|
||||||
|
// npx tsx apps/hq/scripts/migrate-pg-to-pg.ts
|
||||||
|
import { Pool } from 'pg'
|
||||||
|
import { openPgDb, type PgDb } from '../src/db-pg'
|
||||||
|
|
||||||
|
const SKIP_TABLES = new Set(['stg_client', 'stg_invoice', 'schema_migrations'])
|
||||||
|
|
||||||
|
async function main(): Promise<void> {
|
||||||
|
const sourceUrl = process.env['SOURCE_DATABASE_URL']
|
||||||
|
const targetUrl = process.env['TARGET_DATABASE_URL']
|
||||||
|
if (sourceUrl === undefined || sourceUrl === '') throw new Error('Set SOURCE_DATABASE_URL (local Postgres).')
|
||||||
|
if (targetUrl === undefined || targetUrl === '') throw new Error('Set TARGET_DATABASE_URL (cloud Postgres).')
|
||||||
|
if (sourceUrl === targetUrl) throw new Error('SOURCE and TARGET are identical — refusing.')
|
||||||
|
|
||||||
|
// Wipe the target's public schema completely, then let the app's own migrations rebuild
|
||||||
|
// it clean — guarantees the target schema matches this codebase exactly, not a stale copy.
|
||||||
|
const wipePool = new Pool({ connectionString: targetUrl, max: 1 })
|
||||||
|
await wipePool.query('DROP SCHEMA public CASCADE')
|
||||||
|
await wipePool.query('CREATE SCHEMA public')
|
||||||
|
await wipePool.end()
|
||||||
|
console.log('Target schema wiped.')
|
||||||
|
|
||||||
|
const target = (await openPgDb(targetUrl)) as PgDb // recreates schema via migrations, empty
|
||||||
|
const source = (await openPgDb(sourceUrl)) as PgDb // idempotent no-op if already migrated
|
||||||
|
|
||||||
|
const tables = await source.all<{ tablename: string }>(`SELECT tablename FROM pg_tables WHERE schemaname='public'`)
|
||||||
|
|
||||||
|
const client = await target.pool.connect()
|
||||||
|
try {
|
||||||
|
await client.query('BEGIN')
|
||||||
|
// Load in discovery order and ignore FK/trigger checks — safe: one transaction into a
|
||||||
|
// target we just wiped and confirmed empty.
|
||||||
|
await client.query('SET LOCAL session_replication_role = replica')
|
||||||
|
for (const { tablename: name } of tables) {
|
||||||
|
if (SKIP_TABLES.has(name)) {
|
||||||
|
console.log(`skip ${name}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const rows = await source.all<Record<string, unknown>>(`SELECT * FROM ${name}`)
|
||||||
|
if (rows.length === 0) {
|
||||||
|
console.log(`${name}: 0 rows`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const cols = Object.keys(rows[0]!)
|
||||||
|
const placeholders = cols.map((_, i) => `$${i + 1}`).join(',')
|
||||||
|
const insertSql = `INSERT INTO ${name} (${cols.join(',')}) VALUES (${placeholders})`
|
||||||
|
for (const row of rows) {
|
||||||
|
await client.query(insertSql, cols.map((c) => row[c]))
|
||||||
|
}
|
||||||
|
console.log(`${name}: ${rows.length} row(s) migrated`)
|
||||||
|
}
|
||||||
|
await client.query('COMMIT')
|
||||||
|
} catch (err) {
|
||||||
|
await client.query('ROLLBACK')
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
client.release()
|
||||||
|
}
|
||||||
|
console.log('Migration complete.')
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e: unknown) => {
|
||||||
|
console.error(e)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
// Go-live data carry-over: copies every row from the local SQLite dev database into the
|
||||||
|
// target (cloud) Postgres database, preserving values and ids as-is — this is a one-time
|
||||||
|
// full copy, not the "born in Postgres" empty-start path in docs/DEPLOY-HQ.md.
|
||||||
|
// Column types are a straight 1:1 map on this schema (money is bigint paise, flags stay
|
||||||
|
// integer, JSON payloads stay text — see migrations-pg.ts header), so no value conversion
|
||||||
|
// is needed; every column is copied through unchanged.
|
||||||
|
// Refuses to run if the target already has staff_user rows, so a re-run (or pointing at
|
||||||
|
// the wrong DATABASE_URL) can't silently duplicate or clobber data.
|
||||||
|
// stg_client/stg_invoice are APEX-import scratch tables, not real data — skipped.
|
||||||
|
//
|
||||||
|
// Run from repo root, source = local ./data/hq.db (or HQ_DATA_DIR), target = DATABASE_URL:
|
||||||
|
// DATABASE_URL=postgres://postgres:<pw>@<host>:5432/hq npx tsx apps/hq/scripts/migrate-sqlite-to-pg.ts
|
||||||
|
import { openDb, type SqliteDb } from '../src/db'
|
||||||
|
import { openPgDb, type PgDb } from '../src/db-pg'
|
||||||
|
|
||||||
|
const SKIP_TABLES = new Set(['stg_client', 'stg_invoice', 'schema_migrations'])
|
||||||
|
|
||||||
|
async function main(): Promise<void> {
|
||||||
|
const pgUrl = process.env['DATABASE_URL']
|
||||||
|
if (pgUrl === undefined || pgUrl === '') {
|
||||||
|
throw new Error('Set DATABASE_URL to the target (cloud) Postgres connection string.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const sqlite = openDb(process.env['HQ_DATA_DIR']) as SqliteDb
|
||||||
|
const pg = (await openPgDb(pgUrl)) as PgDb // schema is created by migrations here; seedIfEmpty is NOT called
|
||||||
|
|
||||||
|
const existingStaff = (await pg.get<{ n: number }>(`SELECT COUNT(*) AS n FROM staff_user`))!.n
|
||||||
|
if (existingStaff > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Target already has ${existingStaff} staff_user row(s) — refusing to import over existing data. ` +
|
||||||
|
`Wrong DATABASE_URL, or this has already been migrated.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tables = sqlite.raw
|
||||||
|
.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`)
|
||||||
|
.all() as { name: string }[]
|
||||||
|
|
||||||
|
const client = await pg.pool.connect()
|
||||||
|
try {
|
||||||
|
await client.query('BEGIN')
|
||||||
|
// Load in table-discovery order and ignore FK/trigger checks — safe here because the
|
||||||
|
// whole load is one transaction into a database we just confirmed is empty.
|
||||||
|
await client.query('SET LOCAL session_replication_role = replica')
|
||||||
|
for (const { name } of tables) {
|
||||||
|
if (SKIP_TABLES.has(name)) {
|
||||||
|
console.log(`skip ${name}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const rows = sqlite.raw.prepare(`SELECT * FROM ${name}`).all() as Record<string, unknown>[]
|
||||||
|
if (rows.length === 0) {
|
||||||
|
console.log(`${name}: 0 rows`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const cols = Object.keys(rows[0]!)
|
||||||
|
const placeholders = cols.map((_, i) => `$${i + 1}`).join(',')
|
||||||
|
const insertSql = `INSERT INTO ${name} (${cols.join(',')}) VALUES (${placeholders})`
|
||||||
|
for (const row of rows) {
|
||||||
|
await client.query(insertSql, cols.map((c) => row[c]))
|
||||||
|
}
|
||||||
|
console.log(`${name}: ${rows.length} row(s) migrated`)
|
||||||
|
}
|
||||||
|
await client.query('COMMIT')
|
||||||
|
} catch (err) {
|
||||||
|
await client.query('ROLLBACK')
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
client.release()
|
||||||
|
}
|
||||||
|
console.log('Migration complete.')
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e: unknown) => {
|
||||||
|
console.error(e)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue