[794-5][@ant] feat: локальный бутстрап-стек (parser2 Parser + verifier-api + pg/redis) против цепи mono-ai-N — раннер SHiP→Redis, compose с host-gateway и скрипт авто-chain_id, чтобы индексировать permissions с genesis и проверять C6 на живых документах
CI / build (push) Failing after 7s

This commit is contained in:
coopops
2026-06-03 08:14:43 +00:00
parent 25269a9cb1
commit d9bf1e8e91
7 changed files with 235 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
# Единый образ монорепы для бутстрапа: собирает shared + api + parser.
# Команду (api или parser) задаёт docker-compose.
FROM node:22-alpine
RUN corepack enable
WORKDIR /repo
COPY . .
RUN pnpm install --frozen-lockfile \
&& pnpm --filter @verifier/shared build \
&& pnpm --filter @verifier/api build \
&& pnpm --filter @verifier/parser build
ENV NODE_ENV=production
CMD ["node", "apps/api/dist/index.js"]
+22
View File
@@ -40,6 +40,28 @@ pnpm dev:spa # :5173
(бренд кооперативной экономики). Бордо только в chrome; вердикт — отдельная семантика
(✅ зелёный / ❌ красный / ⚠ amber). Reka UI + Tailwind v4 + Radix Colors. Эталоны: Linear, Vercel, Raycast, Resend.
## Локальный бутстрап против цепи mono-ai-N
Индексация permissions с genesis для проверки C6 на живых документах.
**Предусловие:** поднята цепь mono (`docker compose up` в `~/mono-ai-N`) — nodeos отдаёт
chain API на `:8888` и state-history (SHiP) на `:8070`.
```bash
bash deploy/bootstrap/bootstrap-local.sh
```
Скрипт достаёт `chain_id` из `get_info`, поднимает изолированные postgres+redis,
parser2 Parser (SHiP→Redis) и verifier-api (consumer+REST, миграции авто), стримит логи.
- Parser читает SHiP с genesis → Redis Stream; verifier-api потребляет native-delta `permission`.
- Прогресс: `curl http://localhost:3030/v1/health` (`current_block`/`head_block`/`status`).
- **Перезапуск безопасен** — parser2 резюмится по позиции в Redis, upsert идемпотентен
(натуральный ключ), `sync_progress` хранит прогресс. Репарсинга с нуля при рестарте нет.
- Полный реиндекс (осознанно): `docker compose -f deploy/bootstrap/docker-compose.bootstrap.yaml down -v`.
SPA направить на этот api: `VITE_API_BASE=http://localhost:3030`.
## Деплой
Автономные пайплайны из одной репы: `deploy-api.yml` (docker image → ssh) и `deploy-spa.yml`
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@verifier/parser",
"version": "0.0.0",
"private": true,
"type": "module",
"description": "Раннер parser2 Parser (SHiP → Redis) для бутстрапа индекса permissions против локальной цепи Coopenomics",
"scripts": {
"build": "tsc -p tsconfig.json",
"typecheck": "tsc -p tsconfig.json --noEmit",
"start": "node dist/index.js",
"dev": "node --watch --experimental-strip-types src/index.ts"
},
"dependencies": {
"@coopenomics/parser2": "^1.1.0"
},
"devDependencies": {
"typescript": "^5.7.2"
}
}
+52
View File
@@ -0,0 +1,52 @@
/**
* Раннер parser2 Parser (SHiP → Redis) для бутстрапа.
*
* parser2 разделён на две роли: ЭТОТ процесс читает SHiP цепи и пишет унифицированный
* поток событий в Redis Stream; verifier-api (ParserClient) — потребляет из Redis.
* Обе роли должны смотреть на ОДИН Redis и ОДИН chain_id.
*
* Запуск программный (в CLI parser2 команды только ops: validate/list/reset/...).
*/
import { Parser, parseConfig, type ParserOptions } from '@coopenomics/parser2';
function req(name: string): string {
const v = process.env[name];
if (!v) throw new Error(`Не задана обязательная env-переменная ${name}`);
return v;
}
const raw: ParserOptions = {
ship: { url: req('SHIP_URL') }, // ws://<host>:8070 (state_history_plugin)
chain: {
url: process.env.CHAIN_API_URL ?? undefined, // http://<host>:8888 — ABI fallback
id: process.env.CHAIN_ID ?? undefined,
},
redis: { url: req('REDIS_URL') },
// permission — нативная таблица, ABI не нужен; rpc-current для контрактных ABI на всякий.
abiFallback: 'rpc-current',
deserializer: 'wharfkit',
logger: { level: process.env.LOG_LEVEL ?? 'info', pretty: process.env.LOG_PRETTY !== '0' },
health: { enabled: true, port: Number(process.env.PARSER_HEALTH_PORT ?? '4100') },
metrics: { enabled: true, port: Number(process.env.PARSER_METRICS_PORT ?? '4101') },
};
async function main(): Promise<void> {
const parser = new Parser(parseConfig(raw));
const shutdown = async (sig: string) => {
console.log(`[parser] получен ${sig}, останавливаюсь`);
await parser.stop();
process.exit(0);
};
process.on('SIGTERM', () => void shutdown('SIGTERM'));
process.on('SIGINT', () => void shutdown('SIGINT'));
console.log(
`[parser] старт: ship=${raw.ship.url} redis=${raw.redis.url} chain_id=${raw.chain?.id ?? '(авто)'} `,
);
await parser.start();
}
main().catch((e) => {
console.error('[parser] фатальная ошибка', e);
process.exit(1);
});
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"lib": ["ES2022"],
"types": ["node"]
},
"include": ["src"]
}
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# Локальный бутстрап индекса permissions против запущенной цепи mono-ai-N.
#
# Предусловие: цепь mono поднята (docker compose up в ~/mono-ai-N), nodeos отдаёт
# chain API на :8888 и state-history (SHiP) на :8070 (биндятся на 0.0.0.0 хоста).
#
# Делает: достаёт chain_id из get_info → пишет .env → поднимает parser+api+pg+redis
# → стримит логи. Индексация идёт с genesis (START_BLOCK=1). Перезапуск безопасен
# (parser2 резюмится по позиции в Redis, upsert идемпотентен — репарсинга с нуля нет).
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
NODE_HTTP_PORT="${NODE_HTTP_PORT:-8888}"
NODE_HISTORY_PORT="${NODE_HISTORY_PORT:-8070}"
API="http://localhost:${NODE_HTTP_PORT}"
echo "[bootstrap] запрашиваю chain_id у ${API} ..."
INFO="$(curl -fsS "${API}/v1/chain/get_info" || true)"
if [ -z "${INFO}" ]; then
echo "[bootstrap] ОШИБКА: нет ответа от ${API}/v1/chain/get_info — цепь mono поднята?" >&2
exit 1
fi
CHAIN_ID="$(printf '%s' "${INFO}" | sed -n 's/.*"chain_id":"\([0-9a-f]\{64\}\)".*/\1/p')"
HEAD="$(printf '%s' "${INFO}" | sed -n 's/.*"head_block_num":\([0-9]\{1,\}\).*/\1/p')"
if [ -z "${CHAIN_ID}" ]; then
echo "[bootstrap] ОШИБКА: не распарсил chain_id из get_info" >&2
exit 1
fi
echo "[bootstrap] chain_id=${CHAIN_ID} head_block=${HEAD:-?}"
cat > "${HERE}/.env" <<EOF
CHAIN_ID=${CHAIN_ID}
NODE_HTTP_PORT=${NODE_HTTP_PORT}
NODE_HISTORY_PORT=${NODE_HISTORY_PORT}
EOF
cd "${HERE}"
echo "[bootstrap] поднимаю стек (build при первом запуске может занять пару минут)..."
docker compose --env-file .env -f docker-compose.bootstrap.yaml up --build -d
echo "[bootstrap] готово. verifier-api → http://localhost:3030/v1/health"
echo "[bootstrap] логи (Ctrl-C выйдет из логов, стек продолжит работать):"
exec docker compose -f docker-compose.bootstrap.yaml logs -f parser api
@@ -0,0 +1,77 @@
# Локальный бутстрап индекса permissions против запущенной цепи mono-ai-N.
#
# Стек: своя postgres + redis (изолированы от mono) + parser2 Parser (SHiP→Redis)
# + verifier-api (ParserClient consumer + REST + миграции). Цепь mono достаётся
# через host-gateway (nodeos SHiP :8070 и chain API :8888 биндятся на 0.0.0.0 хоста).
#
# CHAIN_ID берётся из --env-file (его пишет bootstrap-local.sh из get_info живой цепи).
# Запуск: bash deploy/bootstrap/bootstrap-local.sh
services:
vpostgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: verifier
POSTGRES_PASSWORD: verifier
POSTGRES_DB: verifier
ports:
- '5433:5432'
volumes:
- vpgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U verifier']
interval: 5s
timeout: 3s
retries: 20
vredis:
image: redis:7-alpine
ports:
- '6380:6379'
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 3s
retries: 20
parser:
build:
context: ../..
dockerfile: Dockerfile
command: node apps/parser/dist/index.js
environment:
SHIP_URL: ws://host.docker.internal:${NODE_HISTORY_PORT:-8070}
CHAIN_API_URL: http://host.docker.internal:${NODE_HTTP_PORT:-8888}
CHAIN_ID: ${CHAIN_ID}
REDIS_URL: redis://vredis:6379
LOG_PRETTY: '1'
extra_hosts:
- 'host.docker.internal:host-gateway'
depends_on:
vredis:
condition: service_healthy
restart: unless-stopped
api:
build:
context: ../..
dockerfile: Dockerfile
command: node apps/api/dist/index.js
environment:
DATABASE_URL: postgres://verifier:verifier@vpostgres:5432/verifier
REDIS_URL: redis://vredis:6379
CHAIN_ID: ${CHAIN_ID}
START_BLOCK: '1'
RUN_INDEXER: '1'
API_PORT: '3030'
ports:
- '3030:3030'
depends_on:
vpostgres:
condition: service_healthy
vredis:
condition: service_healthy
restart: unless-stopped
volumes:
vpgdata: