Commit Graph
73 Commits
Author SHA1 Message Date
gtiandClaude Sonnet 5 40a4d835fe fix: installer starts the service reliably (retry + verify + installer.log), fails loudly; 1.1.6
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 10:54:19 -03:00
gtiandClaude Sonnet 5 7e229f4b84 docs: deploy rule for agents (use deploy_hub.ps1)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 10:30:57 -03:00
gtiandClaude Sonnet 5 59443ffe3e fix: deploy extra smoke uses Gerenciador /login and is non-blocking
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 10:25:45 -03:00
gtiandClaude Sonnet 5 25a8321dd4 fix: deploy verify uses mirrored client, release revoke via Session, remote error tail
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 10:22:18 -03:00
gtiandClaude Sonnet 5 7080ef4d73 fix: deploy health via docker exec (prod port bound to VM IP), safe rollback, error propagation, VPN retry, release default + download verification
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 10:17:47 -03:00
gtiandClaude Sonnet 5 b01e3b230a fix: deploy_hub.ps1 splat variable
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 10:02:42 -03:00
gtiandClaude Sonnet 5 7dc437c01c feat: official operator-run Hub deploy script (backup, migrate, health, release, rollback)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 10:02:28 -03:00
gtiandClaude Sonnet 5 71bbf3e0bd fix: installer stops the service reliably before upgrading and retries locked file copies; 1.1.5
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 09:46:00 -03:00
gtiandClaude Sonnet 5 e40e9500a4 fix: Agent survives slow Hub on large syncs (25-product chunks, 120s timeout, retry) and keeps heartbeating during jobs; 1.1.4
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 09:33:54 -03:00
gti d1104589cc chore: build script embeds release/VERSION; local zero-touch E2E driver 2026-09-24 08:55:30 -03:00
gtiandClaude Sonnet 5 bb663a979c feat: zero-touch Agent — central scheduler, central pause, delta sync, discovery-driven installer
Hub: installations.processing_enabled / sync_interval_seconds / last_sync_* (migration 0026).
The first heartbeat of an installation without coverage queues exactly one INITIAL_SYNC;
afterwards one SYNC_INCREMENTAL per interval (idempotent, backs off on failure). One fiscal
job at a time per Agent (FIFO, stale RUNNING recovery). Job completion now updates the
coverage snapshot, blockers and JOB_STARTED/COMPLETED/FAILED events. Panel: Sincronizar
agora / Reprocessar tudo / Pausar / Retomar + status; pause is enforced Hub-side.
Agent: local AGENT_JOBS_ENABLED is ignored (central control); SYNC_INCREMENTAL re-classifies
only new/changed products via a per-product fingerprint+decision cache and rebuilds coverage
from it; results carry operational summary and blockers; Firebird credentials come from a
DPAPI firebird.bin. Setup: read-only SGV Firebird discovery (auto-select only when
unambiguous, wizard chooser otherwise), starts the service, waits for the first heartbeat,
upgrade-in-place mode. All jobs stay read-only (FIREBIRD_WRITES=0). Version 1.1.3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 08:47:00 -03:00
gtiandClaude Sonnet 5 9f9e06e9e9 fix: searchable client combobox in Nova instalação
Replaces the native list with a combobox (name/code/CNPJ search, accent- and
case-insensitive, keyboard + mouse, custom dropdown, hidden client_id = the
Gerenciador's real CLIENTE.ID). The backend resolves client_id against the
active GTI_GERENCIADOR mirror and rejects free text/unknown ids. Adds
Tenant.external_cnpj (migration 0025) mirrored via the internal endpoint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 08:12:52 -03:00
gtiandClaude Sonnet 5 3ccef36c50 docs: record migration 0024 verified in production
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 22:56:57 -03:00
gtiandClaude Sonnet 5 096f201c84 fix: backup/restore scripts now actually read BACKUP_DIR from .env
--env-file only feeds the containers; a plain shell script never saw
BACKUP_DIR, so it silently fell back to ./backups while the Hub itself used
the real value from .env. Found in production: postgres's /backups bind
mount was still pinned to a directory orphaned by an earlier git-archive
swap (mv gti-fiscal gti-fiscal_old && mv gti-fiscal_new gti-fiscal — Linux
bind mounts follow the original dentry, not the path string), so every
backup.sh run had been silently landing dumps nobody could find at the
live path. Fixed by pointing BACKUP_DIR at a stable absolute path outside
the deploy tree and recreating postgres once to pick it up; verified with
a real ./backup.sh run in production.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 22:52:53 -03:00
gtiandClaude Sonnet 5 d884d41db5 feat: back up and restore the release-storage volume, not just Postgres
backup.sh now also streams the fiscal-hub container's release binaries
(dist tarball, never in Postgres) into BACKUP_DIR alongside the pg_dump,
with the same 14-backup retention; restore_releases.sh is the companion
restore. Verified with a real restore smoke test in production: the
restored dump's agent_releases row and the restored tarball's exe both
match the published sha256 exactly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 22:49:16 -03:00
gtiandClaude Sonnet 5 e7d77beb26 feat: GTI Gerenciador as the single source of truth for clients; bump to 1.1.2
Tenant gains source (LOCAL|GTI_GERENCIADOR), external_client_id, external_code,
active and synced_at (migration 0024). central/app/client_sync.py fetches from
the panel's new GET /internal/fiscal/clients (dedicated bearer token, never
MySQL credentials) and upserts Tenant rows, never touching a LOCAL row.
/agents/new now offers only active GTI_GERENCIADOR tenants, with a client-side
search box; the POST validates the same rule server-side, not just in the
dropdown. central/scripts/sync_clients_from_gerenciador.py is the operator-run
sync + reconciliation report.

GTI_FISCAL_VERSION/AGENT_VERSION bumped to 1.1.2 (release/VERSION single
source of truth), incorporating the installation_id persistence fix. Rebuilt
the EXE installer trio at 1.1.2 (scripts/build_windows_installer.ps1).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 22:38:58 -03:00
gtiandClaude Sonnet 5 ac7b503c2f fix: persist and recover the Agent's real installation_id (heartbeat activation)
POST /v1/agents/enroll now returns installation_id; new installs persist it
to agent.env immediately. Runtime recovery precedence: existing SQLite
installation_identity -> configured INSTALLATION_ID -> authenticated
GET /v1/agents/me/identity. Recovery fails closed on missing/malformed/
mismatched identity rather than inventing a UUID (which the Hub would then
reject as a conflicting Installation on an already-enrolled Agent). An
explicit AGENT_JOBS_ENABLED gate stops the service from claiming a job right
after the first heartbeat.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 22:13:56 -03:00
gti a9aded1e49 installer v1 (C+D): Windows EXE installer trio + build script + local Hub E2E 2026-09-23 17:44:48 -03:00
gti 91c4030b06 installer v1 (A+B): install-code enrollment, panel code UI, EXE publish; DPAPI credential store 2026-09-23 16:38:19 -03:00
gti b058affa29 docs: windows installer EXE V1 implementation plan 2026-09-23 16:02:30 -03:00
gti 701d7dafdd docs: windows installer EXE V1 design (approved with adjustments) 2026-09-23 16:01:44 -03:00
gti 21c926150f fix: stamp published_at on release publish (was NULL) 2026-09-23 15:28:59 -03:00
gti c509d1cfe2 fix: declare static agent routes before dynamic id route 2026-09-23 14:08:17 -03:00
gtiandClaude Sonnet 5 e4c42dd9ff docs: record migration 0022 verified in production
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 13:26:25 -03:00
gtiandClaude Sonnet 5 8d6122c096 feat: Agent distribution with secure enrollment from the control panel
One universal Windows Agent package per version (agent_releases: version/
platform/architecture/sha256/status DRAFT|PUBLISHED|REVOKED; the binary
lives on a local release-storage volume, never in PostgreSQL). An operator
creates a pending installation (agent_enrollments) from Agents / Servidores
-> + Nova instalação, then either downloads it directly or generates a
one-time HTTPS link (short TTL, single use). Either path streams the
package plus a freshly minted enrollment.json whose token is never written
to storage in plaintext, only its SHA-256 hash. On the client host,
gti-agent.ps1 install-auto verifies the bundle's SHA-256, refuses a non-
HTTPS Hub, enrolls via POST /v1/agents/enroll (deleting the enrollment
token immediately after, success or failure), writes an ACL-restricted
.env with the newly issued permanent Agent token, and registers the
Windows Service without starting it (Firebird stays unconfigured on
purpose). Idempotent, replay-proof, rate-limited per IP, fully audited,
never logs a secret.

Migration 0022_agent_releases_enrollment (verified fresh-DB ==
upgrade-from-0021 by pg_dump diff). GTI_FISCAL_VERSION/AGENT_VERSION
bumped to 1.1.0 as the single source of truth across code, packages, the
release manifest and every published AgentRelease row.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 13:19:31 -03:00
gtiandClaude Opus 5.5 b1c496fc1b docs: record migration head 0021 (SSO) in production, TLS via NPM, Caddy removed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 12:30:49 -03:00
gtiandClaude Opus 5.5 64d3fc56e2 feat: SSO from GTI Gerenciador and the Gerenciador visual identity
Collaborators logged into GTI Gerenciador open GTI Fiscal without a
second password. The panel signs a 60 s JWT (Ed25519; the Hub holds only
the public key); the Hub checks signature, iss/aud/exp/nbf, maximum
lifetime, nonce against a one-time state cookie set when the flow starts
at GET /login (login-CSRF protection) and a single-use jti
(sso_used_assertions). Collaborators are provisioned in admin_users with
auth_source=GTI_GERENCIADOR and no password; a CHECK constraint makes a
passwordless local account or a federated account with a password
impossible (migration 0021). Sessions last FEDERATED_SESSION_SECONDS
(1 h) and are renewed silently through the panel.

The local admin stays as break-glass at /login/local, audited. Logout ends
only the Hub session. Every SSO success/rejection (stable reason code),
logout and local login is audited with the real client IP, never the
token. verify_password now fails closed on a missing hash instead of
raising.

Visual identity: gti-theme.css is a verbatim copy of the panel's
design system (tokens, sidebar, light/dark theme) plus its tema.js,
sidebar.js and logo; control-panel.css keeps only Hub components, all
colours from the panel tokens. Page structure and features unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 12:21:26 -03:00
gtiandClaude Opus 5.5 91a2dda969 feat: publish the Hub through a reverse proxy on another host
GTI terminates TLS in its Nginx Proxy Manager (10.0.10.100), not on the
Hub host, so the Hub must be reachable on the LAN — by the proxy only.

- docker-compose.prod.yml passes FORWARDED_ALLOW_IPS (uvicorn then trusts
  X-Forwarded-For/-Proto from the proxy only: real client IPs in audit and
  session records, https scheme) and WEB_COOKIE_SECURE. Defaults keep the
  previous behaviour.
- restrict_hub_port.sh + gti-fiscal-hub-port.service: Docker-published
  ports bypass UFW, so the source restriction lives in DOCKER-USER,
  matched on the conntrack original destination with --ctdir ORIGINAL,
  and is re-applied whenever docker.service starts (the chain is not
  persisted across reboots).
- *.service forced to LF like *.sh; tests cover both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 07:46:11 -03:00
gtiandClaude Opus 5.5 d33149f5ee docs: record migration head 0020 in the release manifest and docs
RELEASE_MANIFEST.json still said head 0011 (repo was at 0018, now 0020)
and RELEASE_CANDIDATE_V1.md said 0010. Updated both, plus
AI_PROJECT_STATE.md and PILOT_DEPLOYMENT.md, to head
0020_not_null_timestamps as applied in production.

scripts/release_manifest.py reported the 4-digit file prefix as the head
("0020" instead of "0020_not_null_timestamps") and always read the
applied revision from the dev container. It now asks Alembic for the real
head and revision count, and the Hub container is configurable
(HUB_CONTAINER); the source is recorded in the manifest.

tests/test_release_candidate.py now fails when the manifest head differs
from Alembic's head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 07:23:13 -03:00
gtiandClaude Opus 5.5 6bcfac6497 fix: enforce NOT NULL on 20 server-defaulted timestamp columns
Audit of models vs fresh DB vs production (all at 0019): 20 columns are
NOT NULL in the models but nullable in the database, all timestamps with
DEFAULT now() created by 0011/0012/0014/0017 without nullable=False.

Classified DB_CONSTRAINT_MISSING: no application path writes NULL to
them (inserts omit them, updates set now; no raw SQL inserts), and there
were zero NULLs in production and in the development database (246,682
rows checked). The upgrade aborts without changing anything if any NULL
is present, instead of inventing a value.

After this revision a fresh database matches the models on nullability,
column types, PKs, FKs, uniqueness and checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 07:20:04 -03:00
gtiandClaude Opus 5.5 b6b45c703b fix: make the Hub backup scripts runnable on the Linux host
Found by running deploy/hub/backup.sh on the production VM:

- With core.autocrlf=true, `git archive` on the Windows release machine
  shipped the .sh files with CRLF and `sh` aborted on line 4
  ("set: Illegal option -"): backup, restore and seed never ran.
  .gitattributes now forces *.sh to LF.
- Docker creates the bind-mounted backups/ directory as root, so the
  host-side retention `rm` would fail once the 15th dump existed.
  Retention now runs inside the postgres container.

tests/test_deploy_scripts.py covers both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 00:35:20 -03:00
gtiandClaude Opus 5.5 a11209d123 fix: make the migration chain runnable from an empty database
0001 called Base.metadata.create_all(), building the schema from the
current models, so on a fresh PostgreSQL 0002 collided on
datasets.raw_sha256 and `alembic upgrade head` could never run from zero.

0001 is now an explicit, frozen baseline: the seven base tables exactly as
the real history created them, before 0002/0006 extended them. Databases
already past 0001 never re-run it. Proof: a fresh `upgrade head` produces a
schema identical (pg_dump --schema-only) to the long-lived dev database.

0019 repairs a latent bug found by that comparison: 0006 used
server_default="now()" as a string, which PostgreSQL froze at the instant
the column was created, so every Agent and Job got the same created_at.
It also adds three indexes the models declare but no migration created.

central/tests/test_migration_baseline.py guards (via AST) against
migrations importing the models or using string timestamp defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 00:30:51 -03:00
gti ce606b04cc fix: reconcile final control panel coverage UX 2026-09-22 23:04:53 -03:00
gti 283749e860 docs: clarify legacy blocker resolution limits 2026-09-22 21:46:44 -03:00
gti 50254772e4 fix: clarify unavailable legacy blocker resolution 2026-09-22 21:42:24 -03:00
gti c23782095c fix: keep migration and blocker SQL postgres-safe 2026-09-22 21:29:09 -03:00
gti c2507aee35 fix: show technical timestamps in tooltips 2026-09-22 21:05:39 -03:00
gti f05e4a10a2 docs: record control panel UX task 5 report 2026-09-22 20:50:04 -03:00
gti 4ba0fa8a53 test: complete control panel fixture UX coverage 2026-09-22 20:49:04 -03:00
gti 0eeb7d159c fix: close operational panel page review findings 2026-09-22 20:34:03 -03:00
gti a4d837f151 feat: refine operational control panel pages 2026-09-22 20:11:18 -03:00
gti 06f6d0f940 fix: enforce utf8 resolution limits 2026-09-22 19:20:45 -03:00
gti 516c71b932 fix: close task 3 resolution findings 2026-09-22 19:11:08 -03:00
gti 5180725238 feat: add safe blocker resolution workflow 2026-09-22 18:47:04 -03:00
gti 25af5cf0f0 docs: append task 2 re-review report 2026-09-22 18:24:08 -03:00
gti 2ee271ca80 fix: close remaining task 2 review findings 2026-09-22 18:23:38 -03:00
gti 041257d254 docs: append task 2 review fix report 2026-09-22 18:01:04 -03:00
gti e35417f044 fix: close task 2 operational review findings 2026-09-22 18:00:28 -03:00
gti 34744ac75a docs: record operational view model task 2 2026-09-22 17:37:46 -03:00
gti d72971047b feat: add bounded operational view models 2026-09-22 17:34:47 -03:00