252 lines
25 KiB
Markdown
252 lines
25 KiB
Markdown
# 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/<app>.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_<model>",
|
||
"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 <schema>.<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 <schema>.<table_name> 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).
|