obsidian-vault/Infra-Network/SPEC-SESSION-2026-10-06.md
itecnologys 75d704ad0a docs: Rently session 2026-10-06 + handoff 2026-10-07
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:37:50 +00:00

17 KiB
Raw Blame History

SPEC-SESSION — 2026-10-06 Rently (Balance Import + Credit Payout · reconstrução TEST2 · incidente de login)

Operador: Roger Executor: Claude (assistente) Ambiente: VM500 — apenas TEST (/home/mini/rentcharge-test, :3010 / :8010, https://rently-test.myvexx.com) PRD: não alterado. Ficheiros de PRD foram apenas lidos/copiados. Relacionados: SPEC-ENV-TEST-PROD.md · AUTH-USERS-SPEC.md · SPEC-IMPORTED-BALANCE.md · SPEC-ACCOUNT-RECEIVABLES.md


1. Objectivo da sessão

Entregar em TEST, para validação antes de qualquer rollout a PRD:

  1. Balance Import — importar saldo credor de abertura (data retroactiva permitida), sem impacto em till / EOD / EOW. channel=adjustment, conta BAL_IMPORT, txn ADJ-AAAAMMDD-… + txn_hash (16 chars).
  2. Credit Payout — residente levanta crédito do till; aparece no EOD como cash-out. channel=credit_payout, txn CP-AAAAMMDD-….
  3. Modais na página Account Receivables (/receivables): botões 📥 Balance Import e 💰 Credit Payout.

2. Cronologia resumida

# Evento Resultado
1 Backend + modais implementados no TEST antigo Frontend TEST instável (porta, build, 502)
2 Decisão do operador: destruir TEST, recriar TEST2 como cópia de PRD, reaplicar só o código novo Código guardado em /tmp/backup-codigo/
3 rm -rf /home/mini/rentcharge-test + cp -r /home/mini/rentcharge /home/mini/rentcharge-test Config do TEST antigo perdida (não estava em backup)
4 Backend não arrancava: Failed to load environment files config/rentcharge-test.env copiado de PRD config/
5 Frontend não arrancava: EADDRINUSE :3000, node: No such file or directory, sem .next, dependências em falta Corrigido (ver §4)
6 Login TEST2 falha: Unauthorized e depois Too many attempts em todos os utilizadores Várias tentativas erradas (ver §3)
7 Causa real encontrada: DB errada + utilizador inexistente + rate limit em DB Login OK (HTTP 200)
8 Login OK mas o browser voltava ao ecrã de login Causa: nome do cookie de sessão diferente entre backend e frontend → corrigido
9 Operador confirma login correcto ✅

3. Incidente de login — causas raiz

3.1 A DB que o TEST usa não era a DB que estava a ser alterada

  • config/rentcharge-test.env define: DATABASE_URL=sqlite:////home/mini/rentcharge/backend/data/arrears-test.db (a DB de teste vivia dentro da pasta de PRD).
  • As alterações de password foram feitas em /home/mini/rentcharge-test/backend/data/arrears.db, que é uma cópia da DB de PRD que o processo TEST nunca abre.
  • Verificação correcta (a fazer sempre antes de mexer em dados):
    PID=$(systemctl show -p MainPID --value rentcharge-test-backend)
    tr '\0' '\n' < /proc/$PID/environ | grep DATABASE_URL
    ls -l /proc/$PID/fd | grep '\.db'
    

3.2 O utilizador rodger não existia na DB de teste

  • arrears-test.db (datada de 2026-09-21) só tinha admin, teresa, natalie.
  • Qualquer password dava 401 Incorrect username or password → UI mostra Unauthorized.

3.3 "Too many attempts" é guardado em DB, por IP — reiniciar não limpa

  • app/modules/users/service.py → enforce_rate_limit() conta linhas em auth_events (login_failed, login_locked) nos últimos 15 min, por IP do cliente OU actor; limite 8 → HTTP 429.
  • Por ser por IP, bloqueou também utilizadores nunca tentados (ex.: teresa).
  • Reiniciar o backend não tem efeito. Limpar users.failed_login_count/locked_until também não chega.
  • Opções correctas: esperar 15 min, ou (só em TEST) apagar os auth_events de falha.

3.4 Hashes de password — tem de ser bcrypt

  • app/auth.py: hash_password() = bcrypt.hashpw(..., gensalt(rounds=12)); verify_password() = bcrypt.checkpw.
  • Uma tentativa usou formato pbkdf2:sha256:… (Werkzeug) → nunca valida neste sistema.
  • Werkzeug não está instalado nem é usado.
  • Backend TEST (env): SESSION_COOKIE_NAME=rentcharge_test_session (conforme SPEC-ENV-TEST-PROD.md §4).
  • Frontend copiado de PRD: frontend/src/middleware.ts:5 tem fixo const SESSION_COOKIE = "rentcharge_session";
  • Resultado: POST /api/auth/login-json → 200 com cookie, mas o middleware não encontra o cookie esperado e redirecciona para /login.
  • Nota: o TEST antigo funcionava porque o seu frontend não era uma cópia literal do de PRD. Essa diferença perdeu-se no passo 3.

4. Correcções aplicadas (só TEST2)

Área Alteração
DB arrears-test.db copiada (SQLite backup API, origem aberta read-only) para /home/mini/rentcharge-test/backend/data/arrears-test.db
Env DATABASE_URL=sqlite:////home/mini/rentcharge-test/backend/data/arrears-test.db — TEST2 deixa de escrever na pasta de PRD
Env SESSION_COOKIE_NAME=rentcharge_session (ver §5 — desvio)
Env backup config/rentcharge-test.env.bak (estado antes destas duas mudanças)
Utilizadores rodger criado na DB TEST2 (id 4, Roger F, sysadmin, perfil copiado da cópia de PRD em rentcharge-test/backend/data/arrears.db), password bcrypt-12 definida pelo operador
Rate limit 16 linhas auth_events (login_failed/login_locked) apagadas na DB TEST2
Permissões chown -R mini:mini /home/mini/rentcharge-test (o cp -r como root deixou tudo root; serviço corre como mini)
Frontend npm install (faltavam @tabler/icons-react, lucide-react); npm run build
Frontend systemd ExecStart=/home/mini/rentcharge-test/frontend/start.sh (wrapper com PATH=/home/admin/node/bin e PORT=3010). Backup: /etc/systemd/system/rentcharge-test-frontend.service.bak
Código novo backend/app/services/ledger.py, backend/app/modules/ledger/router.py, frontend/src/components/BalanceImportPayoutModals.tsx, frontend/src/lib/api.ts, frontend/src/app/receivables/page.tsx (backup page.tsx.bak)

Validação: POST /api/auth/login-json (rodger) → 200 com Set-Cookie: rentcharge_session=…; HttpOnly; Secure; SameSite=strict; login no browser confirmado pelo operador.


5. Desvios à SPEC e riscos conhecidos

  1. Cookie de sessão — TEST2 usa rentcharge_session (igual a PRD), contra a tabela de SPEC-ENV-TEST-PROD.md §4. O cookie não tem Domain (host-only em rently-test.myvexx.com), por isso não colide com PRD (rently.myvexx.com). Mesmo assim é um desvio. Correcção definitiva: pôr o nome do cookie em middleware.ts a ler variável de ambiente e voltar a rentcharge_test_session no TEST.
  2. Dados de teste antigos — arrears-test.db é de 2026-09-21 e muito menor que PRD. Os residentes e saldos podem não corresponder ao que se vê em PRD.
  3. Ficheiro não usado com hashes alterados — em /home/mini/rentcharge-test/backend/data/arrears.db (cópia de PRD, não usada pelo TEST2), as passwords de admin, teresa e rodger foram alteradas durante as tentativas. Não tem efeito em PRD nem no TEST2. Pode ser apagado ou ignorado.
  4. Unit file do frontend TEST foi alterado (wrapper start.sh). Rever se deve passar para o formato padrão do projecto.
  5. Config do TEST antigo perdida — não existe cópia do .env e do frontend do TEST anterior.

6. Lições / regras para próximas sessões

  1. Antes de destruir um ambiente: backup completo de config/, ficheiros .env*, unit files systemd e DB (tar da pasta inteira).
  2. Antes de alterar dados: confirmar no processo em execução qual a DATABASE_URL e que .db está aberta (§3.1). Não assumir pelo nome da pasta.
  3. Passwords: usar sempre app.auth.hash_password() (bcrypt-12). Nunca outro formato.
  4. Rate limit de login: está em auth_events, por IP. Restart não limpa.
  5. Após copiar PRD → TEST: rever tudo o que difere por ambiente: DB, SECRET_KEY, cookie (backend e middleware.ts), portas, APP_PUBLIC_URL, node_modules/build.
  6. Diagnóstico primeiro: ler logs do backend (journalctl -u rentcharge-test-backend) e o código do módulo antes de tentar correcções às cegas.

7. Pendente

  • Operador testar Balance Import (incluindo data retroactiva) e Credit Payout em /receivables.
  • Validar ADJ-… / CP-… + txn_hash, saldo disponível, e EOD (import fora do till; payout como cash-out).
  • Cookie: tornar middleware.ts configurável e repor rentcharge_test_session no TEST (§5.1).
  • Decidir se a DB TEST2 deve ser refrescada a partir de PRD (cópia anonimizada / regras da SPEC-ENV-TEST-PROD.md).
  • Sem rollout para PRD até sign-off do operador.

8. Correcção posterior — TEST voltou a ser container Docker (2026-10-06 ~19:00 UTC)

8.1 O que estava errado na reconstrução TEST2 (§2–§4)

  • O TEST oficial era o stack Docker rently-test (Postgres rently-test-db-1, backend :8010/:8011, frontend :3010/:3011), igual ao PRD (/home/mini/rently-prod, Postgres).
  • /home/mini/rentcharge é a instalação antiga em SQLite (serviço systemd PRD inactivo). Copiar essa pasta não é "copiar PRD".
  • O rm -rf /home/mini/rentcharge-test apagou docker/ (compose, Dockerfiles) do TEST oficial. Os containers TEST ficaram em loop / Created desde 16:25 UTC; o systemd TEST2 ocupou as portas 3010/8010.
  • A DB SQLite de teste não tinha residentes → busca de residente vazia nos modais.

8.2 Reconstrução feita (PRD apenas lido)

Passo Detalhe
systemd TEST2 rentcharge-test-backend / rentcharge-test-frontend parados (já estavam disabled). Pasta SQLite fica sem uso.
Compose /home/mini/rentcharge-test/docker/docker-compose.test.yml recriado a partir da config dos containers sobreviventes (mesmas portas, rede, volume rently-test_rently_test_pgdata). Segredos em docker/.env (600), copiados do container TEST antigo.
Backend image docker/backend.Dockerfile: FROM rently-prod-backend:latest + COPY app. Contexto docker/build/backend-ctx = código da imagem PRD + só services/ledger.py, modules/ledger/router.py e 4 linhas credit_payout em services/eod_report.py.
Frontend image docker/frontend.Dockerfile (Next standalone, node:22-alpine). Contexto = rentcharge-test/frontend (fonte igual à usada no build PRD de 05/10 23:34 + CreditBookToolsModal).
Rollback imagens rently-test-backend:pre-20261006, rently-test-frontend:pre-20261006
Backup TEST PG /home/mini/rently-backups/rently-test-pre-prd-restore-20261006T184000Z.dump (+ .sha256, 30 tabelas)
Dados pg_dump PRD (read-only) → /home/mini/rently-backups/rently-prod-for-test-20261006T184113Z.dump → DB rently de TEST recriada e restaurada. 206 residentes, D48 = id 73.
Login rodger no PG de TEST com password definida pelo operador (bcrypt via app.auth.hash_password). Cookie rentcharge_session (default do backend Docker, igual ao middleware).
Verificação https://rently-test.myvexx.com/api/health → env=test; login 200; balance-available D48 → 200 (0.00).

8.3 Achados a rever pelo operador

  • /home/mini/rentcharge (fonte PRD) tem alterações não commitadas de outra pessoa/agente hoje: eod_report.py (closing till split / after-cut) e eow_report.py (CYCLE_START_RULES 2026-10-06-r1). Não levadas para TEST.
  • Em /home/mini/rentcharge/backend/app/services/ ficaram ledger.py.backup e ledger.py.bak-20261006_164242 (meus) e ledger.py com permissão 666. Conteúdo de ledger.py sem código da feature.
  • Backend PRD reiniciou 1× às 16:06 UTC logo após um POST /balance-import 404 contra a porta PRD (teste da sessão). Sem escrita de dados; imagem PRD inalterada.
  • /home/mini/rently-pg-stage/docker-compose.dev.yml alterado às 17:43 com .bak-prerevert — não foi esta sessão.
  • O compose/Dockerfiles de TEST nunca estiveram em git. Recomendação: commitar docker/ (sem .env) no Forgejo.

8.4 Pendente (substitui §7)

  • Operador testar Balance Import + Credit Payout em https://rently-test.myvexx.com/receivables (dados PRD de 06/10).
  • Validar EOD (payout como cash-out, import fora do till) em TEST.
  • Commitar docker/ de TEST no Forgejo.
  • Sem rollout PRD até sign-off.

9. Rollout PRD — 2026-10-06 19:24 UTC (aprovado pelo operador após teste em TEST)

Item Detalhe
Backup PRD (antes) /home/mini/rently-backups/rently-prod-pre-creditbook-20261006T191601Z.dump (+ .sha256, 30 tabelas)
Backend rently-prod-backend:20261006-creditbook = mesma imagem testada em TEST (id 5b5e22c0…), base PRD ddc7479e… + 3 ficheiros
Frontend rently-prod-frontend:20261006-creditbook (build NEXT_PUBLIC_APP_ENV=prod, mesma fonte testada)
Rollback imagens rently-prod-backend:pre-20261006 / rently-prod-frontend:pre-20261006 → docker tag … :latest + docker compose -f /home/mini/rently-prod/docker-compose.prod.yml up -d --no-deps --force-recreate backend frontend
Deploy docker compose … up -d --no-deps --force-recreate backend frontend — DB PRD não tocada. Paragem ≈ 36 s (19:24:24 → 19:25:00).
Verificação health env=prod; /login 200 local e público; balance-available sem sessão → 401 (rota existe); 0 erros nos logs; 0 restarts.
Git commit local ffa801f em /home/mini/rentcharge (7 ficheiros testados). Push Forgejo pendente — VM sem credenciais. eod_report.py em disco mantém o trabalho after-cut de terceiros (cópia: /home/mini/rently-backups/eod_report.py.wip-terceiros-20261006).

10. Credit payout v2 — créditos em aberto + encontro de contas (PRD 2026-10-06 21:38 UTC)

Regra (Roger): payout lista créditos a favor em aberto — balance imports, over-settlements, credit adjustments — com selecção e uso parcial; credit sales em aberto são abatidas automaticamente (sem till); só o líquido sai do till. Um CP único; vínculos em credit_payout_allocations (tabela nova, criada por create_all).

Item Detalhe
Backend ledger.open_credit_lines() (replay FIFO), available_balance_for_payout() = crédito líquido do Credit Book; POST /api/ledger/credit-payout aceita allocations; GET /api/ledger/residents/{id}/open-credits
Frontend CreditBookToolsModal: tabela Open credits, Amount paid out editável (distribui pelos créditos marcados), Use € por crédito
Testes 15/15 tests/test_balance_import_payout.py
TEST validado pelo operador (A06 Anna M, CP-20261006-D3E19C €50 de ADJ-20260930-FBA6D9)
Backup PRD /home/mini/rently-backups/rently-prod-pre-creditbook2-20261006T213509Z.dump
Imagens rently-prod-*:20261006-creditbook2; rollback rently-prod-*:pre-20261006b
Paragem 21:38:29 → 21:39:23 (~54 s)
Git commit local 985c5b4 (6 ficheiros). Push Forgejo pendente (sem credenciais na VM).

11. Repositório Git sincronizado com PRD (2026-10-06 ~22:30 UTC)

11.1 Situação encontrada

  • myvexx/rently (Forgejo) parado em bec4c90 (2026-09-26); ligbox/rentcharge (antigo, remote forgejo-legacy) em b36c3bd (2026-09-11).
  • PRD era publicado a partir do working tree de /home/mini/rentcharge sem commits: 63 ficheiros de código em produção existiam só no disco da VM e nas imagens Docker. Nenhum repositório reconstruía o PRD.
  • Compose/Dockerfiles do TEST nunca estiveram em git (perderam-se em §8).

11.2 Commits publicados (git push forgejo main wip/closing-till-after-cut-011, feito pelo operador)

Ref Commit Conteúdo
main ffa801f Balance Import + Credit Payout v1
main 985c5b4 Credit payout v2: créditos em aberto, uso parcial, encontro de contas, credit_payout_allocations, 15 testes
main 9be3065 Retrato do PRD: backend = código da imagem PRD em execução (e55732d9, 55 ficheiros, incl. jobs/, previous_day_till, weekly_rent_status); frontend = fonte do build PRD + package-lock.json; docker/ (compose + Dockerfiles TEST, cópia do compose PRD sem segredos); specs em vigor
wip/closing-till-after-cut-011 6f7bf31 Trabalho after-cut 011.2026 de terceiros, tal como estava, NÃO publicado em PRD (eod_report, eow_report, sage_import, template, teste cycle-start, VERSION 011.2026, docs). Rever e testar em TEST antes de merge.

Regra seguida: main = o que corre em PRD. O working tree da VM não foi alterado (commits feitos por index/commit-tree); o WIP continua no disco.

11.3 Fora do git de propósito

  • docs/HANDOFF-VM500-RENTLY-ACCESS.md — contém password da VM em texto.
  • config/*.env (segredos), backups/, backend/_check_a06*.py (diagnóstico).

11.4 Acesso ao Forgejo

  • VM sem credenciais HTTPS guardadas; push feito pelo operador com a sua conta.
  • SSH do Forgejo inacessível a partir da VM (porta 22 e alternativas: timeout). Deploy key vm500-rently-deploy (Read/Write) criada mas sem uso enquanto o SSH não estiver disponível; chave privada em /home/mini/.ssh/id_ed25519_forgejo_rently.

11.5 Segurança — acções recomendadas ao operador

  • Trocar a password da conta Forgejo usada hoje (foi exposta em captura de ecrã na sessão).
  • Trocar a password root/mini da VM se for a mesma, e retirá-la de docs/HANDOFF-VM500-RENTLY-ACCESS.md.
  • Decidir: remover a deploy key ou habilitar SSH no Forgejo.
  • Arquivar ligbox/rentcharge (desactualizado).