# OPC Gateway — Design (SIENTIAPDE-1972) - **Épico:** [SIENTIAPDE-1972](https://aignosi-wiki.atlassian.net/browse/SIENTIAPDE-1972) — OPC Gateway derivado do OPC Ingestor, com ajustes no Orchestrator e no Laborious. - **Sub-tasks:** [1973](https://aignosi-wiki.atlassian.net/browse/SIENTIAPDE-1973) (Gateway), [1974](https://aignosi-wiki.atlassian.net/browse/SIENTIAPDE-1974) (Orchestrator), [1975](https://aignosi-wiki.atlassian.net/browse/SIENTIAPDE-1975) (Laborious). - **Referências:** [1879](https://aignosi-wiki.atlassian.net/browse/SIENTIAPDE-1879) (boas práticas OPC), [1569](https://aignosi-wiki.atlassian.net/browse/SIENTIAPDE-1569) (banco de tags / CRUD conectores). - **Branch:** `feature/SIENTIAPDE-1972-opc-gateway-...` (repo `sientia-dataops-opc-ingestor`). - **Data:** 2026-07-13. --- ## 1. Contexto e objetivo Evoluir o `opc-ingestor` (hoje read-only, leitura por subscription) para um **OPC Gateway** completo: - Gestão de certificados via MinIO + MongoDB (reaproveitando o épico 1569). - Leitura por **subscription** usando o sistema de slots já existente. - **Leitura ativa**: agendada (frequência múltipla do ciclo de operação) e **on-demand via GRPC**. - **Escrita ativa** via **NATS** como broker intermediário. - **Métricas detalhadas** de todas as operações. - Boas práticas / correções do card 1879. O trabalho atravessa 3 repositórios. Este spec detalha o **Gateway** (repo atual) e define os **contratos** para Orchestrator (1974) e Laborious (1975); cada um ganha spec próprio no seu repo. ## 2. Escopo **Dentro:** arquitetura interna do Gateway e componentes; consumo do banco de tags (1569) e do slot v2 no Redis; escrita OPC via NATS; leitura ativa agendada e on-demand via GRPC; métricas/observabilidade; checklist 1879; contratos C1–C4; integração de deploy (Helm/air-gapped) do serviço. **Fora (specs próprios):** implementação do 1974 (`orchestrator_temporal`) e do 1975 (`laborious_temporal`); CRUD/UI do banco de tags (1569, já feito); provisionamento do broker NATS na plataforma (dependência, ver §8). ## 3. Documentos-fonte autoritativos (aig-docs) - **Brief oficial do serviço:** `07 - Projects & R&D/sientia/opc-gateway/topics.md` — escopo canônico (confirma tudo acima; define a coluna de status de escrita na tabela de predições — ver C3). - **DataOps OPC (contrato de dados):** `.../technical-documentation/model-lifecycle/data-ingestion-opc-scouter.md` — coleções Mongo `opc-servers`, `pipelines` (`read_tags[]`: `tag_name/server_id/tag_address/aggr_func/frequency/data_range`, filtros, `tag_retention_minutes`), `orchestrated_schedules`; **gotcha:** `updated_at` deve ser BSON `Date` (não string ISO). Orchestrator sincroniza pipelines→Temporal em cron `*/30`. - **Helm/air-gapped:** `.../helm-chart-air-gapped/**` — chart genérico `sientia-module`, namespace `sientia-core`, `values/.yaml` + perfis, init-containers, probes, netpol default-deny, image signing. Ver §10. - **Observabilidade:** `.../helm-chart-air-gapped/07-observability-monitoring/**`. Ver §9. - **Onboarding/dev:** `01 - Welcome & Onboarding/technical-onboarding.md`, `06 - Engineering & DevOps/**`. Ver §11. - **Incidente OPC real (Samarco):** `04 - Clients & Deployments/Samarco/reports/2026-06-opc-maintance/report-samarco-2026-06-opc-timeout-kepserver.md` — `TimeoutError` em escrita `asyncua`; restrições do servidor (block size 512 vs 255; chars especiais em endereços `- _ / .`; degradação de sessão ao longo do tempo; publishing interval 2000ms; write timeout 30000ms). Motiva broker + retry + métricas de erro. ## 4. Estado atual (fatos do código) ### opc-ingestor (base) - `asyncua` 1.1.5. Leitura **só por subscription**. Sem escrita, NATS ou GRPC. - Slots no **Redis** (`slot:opc_tags:{id}`), com lease/heartbeat para balanceamento. - Managers: `opc_manager`, `ingestor_manager`, `data_manager`, `resource_manager`. Egress: Mongo (obrigatório) + Kafka legado opcional (**removido no Gateway** para simplificar — decisão Vitor 2026-07-14). Métricas Prometheus em `:9090/metrics`. - Conexão OPC hoje é **por slot arrematado** (só conecta nos servidores dos slots que possui). ### dataops-library - `MongoDBRepository` (async: `find/find_one/distinct/aggregate/insert/insert_many/update_one/update_many/delete_one/delete_many`), `MinioRepository`, `opc_ua_client`, `Postgres` (`sientia_do.temporal.activities.postgres`), helpers de métricas (`sientia_do.observability`). Métodos genéricos em `feature/SIENTIAPDE-1590`. **Sem** NATS/GRPC/modelo de slot. ### Banco de tags (1569) — `sientia-projects-templates @ rc/SIENTIAPDE-1588` Mongo `sientia`: | Coleção | Campos | |---|---| | `connectors` (opc_ua) | `connector_id`(int), `type`, `name`, `url`, `server_uri`, `subscription_period_ms`, `use_security`(bool), `status`, `description` | | `connector_tags` | `tag_id`(int), `connector_id`(FK), `address`(NodeId), `tag_name`, `category`(`Read`/`Write`/`Prediction Confidence`), `data_type`, `unit`, `status`, `description` + atributos custom top-level | | `platform_certificates` | doc único: `client_cert{minio_object_name,sha256_hash,uploaded_at,original_filename,expiration_date}`, `private_key{...}` | - **Segurança já é `Basic256Sha256`**; cert de plataforma **único**, baixado do MinIO (bucket `streamlit-connectors`, prefixo `platform/certificates/`) para temp e passado ao `asyncua`. - **Multi-nome por endereço já é permitido** (unicidade em `(connector_id, tag_name)`, nunca em `address`). - **Não existem** `model_id/model_name`, `collection`, `frequency` em `connector_tags` (vêm do lado modelo/pipeline). O subtask 1600 (pendente) adiciona `Category` + atributos custom como chaves flat na raiz. **Decisão Vitor:** cada tag também tem um campo **`metadata`** (objeto), deixado **vazio** por ora; tags de teste criadas com `metadata: {}` (para documentação). - Fix aplicado: `projects-templates/app/src/operations/db_infra.py:16` corrigido para o caminho local correto da lib (import validado). ### Escrita hoje (Laborious) - Escrita OPC **direta** via `asyncua` (`laborious/activities/opc.py` → `opc_repository.write_data` → `node.write_value`). - Status de escrita é gambiarra: sobrescreve `prediction_confidence` com sentinelas (`12`=erro escrita, `14`=sessão ruim; PI=`13`) em `process_confidence`. Predições persistidas em Postgres (`model_id, prediction, prediction_confidence, response_time, prediction_status, timestamp, created_at, comments`) via **pandas `to_sql`** (schema/tabela por-modelo, criados por inferência do DataFrame; `predictions_schema.predictions` é só fixture de teste). - Payload de escrita já contém: `server_id`, `addr`, `value`, `data_type`, `tag_type`, `model_id`, `model_name`, `timestamp`. ## 5. Arquitetura do Gateway (Abordagem B — engines por capacidade) ``` Orchestrator ─(Redis slot v2)─▶ SubscriptionEngine ─┐ Laborious ────(NATS write)────▶ NatsWriteConsumer ──▶ WriteEngine ────┐ caller ───────(GRPC read)─────▶ GrpcServer ─────────▶ ActiveReadEngine┤ scheduler ────────────────────────────────────────▶ ActiveReadEngine┤ ▼ OpcConnectionPool (por servidor) ─▶ servidores OPC │ DataSink ◀────────────────────┘ ├─▶ Mongo (dados) └─▶ Postgres predictions.write_status ``` Decisão-chave: **conexões OPC deixam de ser atreladas a slot** (escrita e leitura on-demand miram qualquer servidor) → **pool por servidor** compartilhado. ### Componentes 1. **`OpcConnectionPool`** *(novo)* — conexões `asyncua` por conector/servidor, reuso + *idle eviction*; `Basic256Sha256`; `secure_channel_timeout = int(session_timeout*1.25)`; certs do MinIO/Mongo (para `emptyDir`/tmpfs, ver §12); referência única `time.monotonic()`; opção NTP; reconexão + métricas de conexão e tipos de erro OPC. Tolera restrições do incidente Samarco (block size, timeouts, degradação de sessão). 2. **`SubscriptionEngine`** *(evolui `ingestor_manager`+`opc_manager`)* — arremata slots (lease/heartbeat), subscription por (slot, servidor) via pool; `datachange` faz **fan-out multi-nome** para cada `{collection, tag_name, model_name, model_id}`. 3. **`ActiveReadEngine`** *(nova)* — agendada (freq. múltipla do ciclo) e on-demand (GRPC: lê, **persiste E devolve**); mede *lag* de leitura (ts recebido − ts máquina). 4. **`WriteEngine`** *(nova)* — escrita OPC com `DataValue`+`Variant`+`status_code` Good+`source_timestamp`; flags timestamp/quality (`Good_Local_Override 0xD8`); **converter seguro** (BUG-1 bool, BUG-2 try/except); **rate-limit**; diferencia erros OPC; grava `write_status` na tabela de predições (C3). 5. **Adapters** — `NatsWriteConsumer` (subject de escrita, **queue group** `gateway-writers`, Core NATS fire-and-forget; JetStream depois) e `GrpcServer` (leitura on-demand). 6. **`DataSink`** *(evolui `data_manager`)* — persiste leituras (**Mongo**, fan-out; `updated_at` como BSON Date) e `update_write_status(model_id, timestamp, status,...)` na tabela de predições (Postgres). Kafka removido. 7. **`app`** — instancia pool+engines+adapters, sobe Prometheus/health, trata sinais e shutdown. Coordenação: lease/heartbeat governam SubscriptionEngine e leitura agendada; escrita/on-demand não dependem de lease. Meta 1879: ≤ ~500 linhas/arquivo, ≤ ~20/função. ## 6. Contratos ### C1 — Slot v2 no Redis (`slot:opc_tags:{id}`) Orchestrator escreve, Gateway lê. Substitui `topics:[str]` por lista multi-nome por endereço + config de leitura ativa: ```json { "server1": { "server_id": "1", "name": "server1", "url": "opc.tcp://host:4840", "server_uri": "urn:...", "use_security": true, "subscription_period_ms": 1000, "tags": { "ns=2;i=2": { "frequency": "15000", "active_read": { "enabled": true, "cycle_multiple": 3 }, "names": [ {"collection": "raw_x", "tag_name": "Counter", "model_name": "m1", "model_id": "1"}, {"collection": "raw_y", "tag_name": "Contador", "model_name": "m2", "model_id": "2"} ] } } } } ``` Orchestrator monta via **JOIN**: identidade da tag (`connector_tags` + `connectors`, do banco 1569) × config de modelo (`model_id/model_name/collection/frequency`, lado `models`/pipeline). Multi-nome = múltiplas entradas em `names`. ### C2 — Mensagem NATS de escrita Laborious publica, Gateway consome. Core NATS, subject `opc.write` (avaliar `opc.write.{server_id}`), queue group `gateway-writers`, fire-and-forget. Deve carregar `timestamp`+`model_id` (correlação com C3). ```json { "msg_id":"uuid","server_id":"server1","addr":"ns=2;i=5","value":42.0, "data_type":"float","tag_type":"prediction","model_id":"1","model_name":"m1", "schema":"sientia_data","table_name":"predictions_", "timestamp":"2026-07-13T12:00:00Z","write_timestamp":true,"write_quality":false } ``` `schema`+`table_name` são necessários porque a tabela de predição é **por-modelo** — o Gateway usa-os para o `UPDATE` da coluna `write_status` (ver C3). ### C3 — Coluna de status de escrita (Postgres, tabela de predições) ⭐ autoritativo em topics.md Nova coluna no DDL padrão de predições (ex.: `write_status` + opcional `write_status_detail`/`write_status_at`). Em **escrita OPC**, o **Gateway** grava o status vinculando por **`timestamp` + `model_id`**. Em **escrita PI Web API**, o **Laborious** grava. Substitui as sentinelas `12/13/14` em `prediction_confidence`. - **Onde:** schema/tabela de predição são **config por modelo** (`input_data['schema']`/`['table_name']` no Laborious) — não há tabela única fixa. A coluna faz parte do **DDL padrão** de predições; homologar no schema gerenciado pelo Flyway (**`sientia_data`**). Migração via `Aignosi/sientia-dataops-database-migrations` (`.sql` versionado, Job Helm hook), backward-compatible. - **Provisionamento (LOCALIZADO):** **não há DDL/template** — as tabelas são criadas por **pandas `DataFrame.to_sql(if_exists='append')`** em `sientia_do/temporal/activities/postgres.py:439-464`, com o **schema inferido das colunas do DataFrame**. Logo: (a) **tabelas novas** ganham `write_status` automaticamente quando o DataFrame de predições do Laborious passar a incluir a coluna (trabalho do 1975); (b) **tabelas existentes** precisam de **backfill** `ALTER TABLE . ADD COLUMN IF NOT EXISTS write_status ...` — como são dinâmicas/por-modelo, a migração Flyway enumera as tabelas existentes (ex.: via `information_schema`) ou usa migração repetível/callback. - **Fluxo de gravação:** o Laborious grava a linha de predição (via `to_sql`) com `write_status` default (ex.: `pending`/null); depois o Gateway faz `UPDATE . SET write_status=... WHERE timestamp=... AND model_id=...`. Por isso a mensagem NATS (C2) carrega `schema`+`table_name` (a tabela é por-modelo). ### C4 — GRPC leitura on-demand ``` service OpcGateway { rpc ReadTag(ReadTagRequest) returns (ReadTagResponse); } ReadTagRequest { string server_id; string addr; optional string data_type; } ReadTagResponse { string value; string opc_status_code; string source_timestamp; string server_timestamp; bool persisted; } ``` Lê agora, persiste no pipeline e devolve. **Sem consumidor hoje** (talvez o frontend no futuro — Vitor, 2026-07-14): endpoint **interno ao cluster, sem auth** por ora; fase de menor prioridade, adiável sem afetar leitura/escrita. ## 7. Gestão de certificados Reaproveita o mecanismo do 1569: metadados em Mongo `platform_certificates` + arquivos no MinIO. O `OpcConnectionPool` baixa `client_cert`/`private_key` para **`emptyDir`/tmpfs** (root FS é read-only, §12) e passa ao `asyncua` com `Basic256Sha256`, espelhando `_create_opc_ua_client`. Cert de plataforma é único/compartilhado. **Bucket (DECIDIDO): `sientia-dataops`.** Padrão de MinIO (`03-database-storage/minio-strategy.md`, Bruno Domingues) é bucket + user scoped por serviço; DataOps/`sientia-core` usa **`sientia-dataops`**. O Gateway lê os certs de `sientia-dataops`. **A escrita dos certs do CRUD 1569 passa a ser em `sientia-dataops`** (hoje grava no bucket do frontend `streamlit-connectors`) — ajuste no app 1569 + policy do user do frontend para o bucket DataOps. ## 8. NATS como nova dependência de plataforma **Decisão: NATS** (definido pelo brief oficial `opc-gateway/topics.md` e confirmado pelo dono da feature, Vitor Pimentel — fonte mais atualizada). O padrão de subjects/queue groups do NATS casa com o comando de escrita ponto-a-ponto de baixa latência. Nota de plataforma: os docs air-gapped diziam *"do not include RabbitMQ/NATS unless a clear requirement arises from a new component"* (`helm-chart-air-gapped/03-database-storage/other-data-stores.md`; `requirements-checklist.md:106`). O **OPC Gateway é exatamente esse novo componente** que cria o requisito — logo o NATS entra na plataforma como dependência de primeira classe. Ônus de onboarding a cumprir: - Subchart Helm + versão pinada (sem `latest`); imagem no bundle air-gapped (Trivy/Cosign). - Entrada nos perfis de sizing; `NetworkPolicy` de egress no `sientia-core`. - Credencial via `sientia-secrets-chart`. - HA/persistência: **Core NATS** agora (fire-and-forget, sem storage); JetStream depois exige storage/persistência. ## 9. Métricas e observabilidade - **Métricas (1973+1879):** `app_up`; sucesso/erro de escrita e leitura ativa; latência (histograma) de escrita e leitura ativa; tags na subscription; slots possuídos; conexões/desconexões; **status de escrita por modelo**; **valor por tag/modelo**; ciclos sem dado em subscription; **tipos de erro OPC**; **lag de leitura**. Prefixo sugerido `opc_gateway_*`; manter `app_up`. - **Exposição:** `/metrics` em `:9090`; **ServiceMonitor** com label `release: kube-prometheus-stack`. - **Health probe (padrão DataOps):** exec `curl -sf http://localhost:9090/metrics | grep -q '^app_up{.*} 1'`. - **Logs:** JSON estruturado (Loki+Promtail; indexado por namespace/pod/container). - **Alerting:** AlertManager (já existe `SientiaAppDown`, `KubePodCrashLooping`, `DatabaseConnectionFailure`). Dashboard Grafana "OPC Ingestor" existe — estender/criar um para o Gateway. ## 10. Deploy e integração de plataforma (Helm / air-gapped) - **Chart:** consumidor do chart genérico **`sientia-module`** (app carrega só `values/opc-gateway.yaml`; sem templates). Namespace **`sientia-core`**, Phase 5. - **Perfis de sizing:** adicionar entrada por perfil (`profiles/{small,medium,large}.yaml`) com requests/limits do Gateway (e do NATS). YAML estático, sem Go template em values. - **Código:** workers clonam o app do **Gitea** via SSH no startup (`PYTHON_APP`); libs compartilhadas via Library Distribution Server (PyPI em MinIO). Base image `sientia-module`, Python 3.11+. - **Init-containers:** `busybox nc -z` em loop sobre `waitForDependencies.services[]` — Gateway espera **Gitea, MongoDB, Redis, Postgres, MinIO, NATS**. - **Segurança do pod (herdada do library chart):** `runAsNonRoot`, `readOnlyRootFilesystem: true`, `drop ALL`, writable só `/tmp` e `/.cache` → certs OPC em `emptyDir`/tmpfs. - **NetworkPolicy default-deny:** liberar egress p/ PG/Mongo/Redis/MinIO/**NATS**/servidores OPC (4840) e ingress do Prometheus (9090); GRPC exposto é **net-new** (hoje o único gRPC da plataforma é o do Temporal) → adicionar regra. - **Migração de banco (C3):** **Flyway** (repo `Aignosi/sientia-dataops-database-migrations`, schema `sientia_data`; substituiu Alembic/SQLAlchemy). Coluna nova = `.sql` versionado (V2+), imutável, aplicado por Job Helm hook `pre-upgrade`; backward-compatible. - **Imagem:** assinada (Cosign) e escaneada (Trivy) no bundle; tags imutáveis (SemVer/SHA), nunca `latest`; `Chart.lock` commitado. - **MinIO:** padrão é bucket + user scoped por serviço; o bucket do `sientia-core`/DataOps é **`sientia-dataops`**. Ver fronteira de certs em §7/§17. ## 11. Convenções de desenvolvimento - **Nome de branch dirige versionamento e é checado no CI:** `feature/`→minor, `fix/|hotfix/|bugfix/`→patch, `release/`→major, `rc/`, etc.; descrição em kebab-case; **branch fora do padrão falha o CI**. ✅ Branch já renomeada para `feature/SIENTIAPDE-1972-...`. - **Ticket-first:** referência do Jira no branch e no commit (ex.: `feat(gateway): ... (SIENTIAPDE-1973)`). - **Qualidade:** SonarQube (quality gate bloqueia merge — coverage, smells, security hotspots, duplicação, complexidade). Sem config numérica de tamanho de arquivo/função documentada além do 1879; qualidade delegada ao Sonar. Dockerfiles via `.hadolint.yaml`, centralizados em `sientia-container-images`, sem `latest`. - **Testes:** unit + integration (pytest) + E2E (Playwright quando aplicável). - **Docs-first, em inglês, um tópico por arquivo, com frontmatter** (regra de `aig-docs`). ## 12. Segurança / secrets - Certs OPC não podem ir para o root FS (read-only) → baixar para `emptyDir`/tmpfs em runtime. - NetworkPolicy default-deny em `sientia-core`; adicionar allow-list para NATS e GRPC exposto + ServiceMonitor. - ServiceAccount dedicada por app (default basta se não precisar de API do k8s). - Secrets via `sientia-secrets-chart` (Sealed Secrets / `global.secrets.useExisting`); credencial NATS e certs seguem esse padrão (precedente: `pi-web-api-auth-token`). - TLS via cert-manager (`global.ingress.tls.mode`); relevante só se o GRPC for exposto por ingress (interno fica cluster-DNS + NetworkPolicy). ## 13. Checklist boas práticas (1879) - [ ] `Basic256Sha256` (não `Basic256`) — já é padrão do conector; garantir no pool. - [ ] `time.monotonic()` único para tempo/latência. - [ ] Escrita com `status_code` Good + `source_timestamp`. - [ ] Flag liga/desliga escrita de timestamp; flag liga/desliga quality (`Good_Local_Override 0xD8`). - [ ] Opção NTP; rate-limit / pausa mínima entre escritas. - [ ] BUG-1: coerção de bool via string JSON (`str.lower() in ('false','0','no','off','')`). - [ ] BUG-2: try/except na conversão de tipo (retornar "não escrito" + log). - [ ] `secure_channel_timeout = int(session_timeout*1.25)`. - [ ] Diferenciar tipos de erro OPC nas métricas. - [ ] (Flag/futuro) escrita via método OPC em vez de tag direta. - [ ] ≤ ~500 linhas/arquivo, ≤ ~20/função. ## 14. Faseamento e dependências - **F0 — Fundações:** scaffolding; `OpcConnectionPool` (cert MinIO+Mongo→emptyDir, `Basic256Sha256`, monotonic, channel>session); base de métricas + health `app_up`. - **F1 — SubscriptionEngine:** slot v2 + fan-out multi-nome + `DataSink`. *(C1)* - **F2 — WriteEngine + NatsWriteConsumer + coluna de write_status (Postgres).** *(C2, C3 + migração)* - **F3 — ActiveReadEngine (agendada) + GrpcServer (on-demand).** *(C4)* - **F4 — Métricas completas + hardening 1879 + testes + Dockerfile + values/opc-gateway.yaml + ServiceMonitor/NetworkPolicy.** **Dependências externas:** banco de tags 1569 no Mongo alvo; broker NATS provisionado (§8); bucket de certs no MinIO; `dataops-library` com métodos genéricos (1590) publicada/pinada; migração da tabela de predições (C3). ## 15. Trilhas paralelas (nível de contrato) - **Orchestrator (1974):** montar slot v2 do banco de tags, propagar `model_id/model_name/collection`, multi-nome. **Integração é net-new** (o orchestrator hoje só usa `connectors_config` de conexão e `pipelines.read_tags` embutidas; não toca `connector_tags`). Alvos: `orchestrator/utils/orchestrator_functions.py` (`gather_read_tags`, `build_tag_config`), `orchestrator/activities/formatters.py` (`process_slots`), `orchestrator/activities/slot_manager.py`. - **Mecânica do JOIN (DECIDIDO — chave = `tag_id`):** `connector_tags` **não tem** `model_id`/`collection`/`frequency`. Então: (a) **identidade da tag + conexão** (address, data_type, `tag_name`, `category`, url/server_uri/security) vêm de `connector_tags`+`connectors` — fonte única; (b) **associação modelo→tag + `collection` + `frequency`** vêm do lado modelo/pipeline. O pipeline passa a **referenciar `tag_id`** (não `(server+address)`, que é ambíguo sob multi-nome, já que `address` não é único no banco de tags). Cada `tag_id` = 1 nome → mapeia direto para uma entrada de `names` no slot v2. Requer migrar o schema do pipeline para guardar `tag_id`(s). - **Laborious (1975):** publicar no NATS (C2) em vez de `node.write_value`; gravar `write_status` (C3) nas escritas próprias (PI/simples); remover sentinelas `12/13/14` de `process_confidence`/`api.py`. ## 16. Testes - Unit por engine/pool (padrão `tests/unit/managers`): pool (reconexão, cert, timeouts Samarco), subscription (fan-out multi-nome), write (converter bool/erro, status_code/timestamp, rate-limit, mapa de erros), active read (lag), adapters (NATS queue group, GRPC read+persist), DataSink (write_status por timestamp+model_id). - Cada lógica não trivial deixa ≥1 teste executável. - Funcional single-node contra o `opc_simulator`. ## 17. Questões em aberto **Decididas:** - **Broker = NATS** (Core NATS agora; §8). Definido pelo brief/Vitor. - **Nome da branch:** renomeada para `feature/SIENTIAPDE-1972-...` ✅. **Resolvidas (docs/código + Vitor + Bruno):** - **Migração = Flyway** — repo `Aignosi/sientia-dataops-database-migrations`, schema `sientia_data`. Coluna = `.sql` versionado, Job Helm hook. - **Bucket de certs = `sientia-dataops`** (aprovado por Bruno). Ação: migrar a escrita dos certs do 1569 p/ esse bucket + policy (impl, não decisão). (§7) - **JOIN do 1974 = chave `tag_id`** (o pipeline referencia `tag_id`; `(server+address)` é ambíguo sob multi-nome). (§15) - **`metadata` = campo (objeto) na tag, default vazio** (decisão Vitor); tags de teste com `metadata: {}`. (§4) - **Tabelas de predição = pandas `to_sql` (sem DDL/template)** — coluna nova via DataFrame do Laborious (tabelas novas) + backfill `ALTER TABLE` (existentes). (§6 C3) - **Chamador do GRPC on-demand (Vitor, 2026-07-14):** ninguém hoje; talvez o frontend depois. Endpoint interno, sem auth por ora; fase adiável. (§6 C4) **Em aberto (precisam de humano):** 1. **Provisionamento do NATS no air-gapped:** subchart/versão/sizing/network policy (a *escolha* já é NATS). (§8) — **plataforma/Bruno** **Adiada por design (nós):** - **Durabilidade da escrita:** Core NATS agora; reavaliar JetStream (TTL curto) se houver perda real em restart. ## 18. Riscos - **Introdução de NATS** (decidida): a diretriz antiga "não adicionar NATS sem requisito" é atendida — o Gateway é o requisito; resta cumprir o onboarding de plataforma (§8/§10). - **Escrita OPC não idempotente:** se migrar p/ JetStream (at-least-once), dedupe por `msg_id`. - **Relógio/OPC:** timestamps/NTP são fonte recorrente de bug (1879) — priorizar `monotonic` e flags de quality. - **Restrições reais do servidor OPC (Samarco):** block size 512/255, chars especiais em endereços, degradação de sessão, timeouts (publishing 2000ms, write 30000ms) — o pool/WriteEngine devem tolerar e medir. - **Pool multi-instância:** escrita/on-demand conectam sob demanda a qualquer servidor; vigiar limite de conexões/idle eviction para não estourar sessões. - **Schema do 1569 ainda em `rc/`:** pode mudar antes do merge; revalidar C1 no merge. - **GRPC como servidor é net-new na plataforma:** exige regra de rede e revisão (hoje só Temporal usa gRPC).