@baruk/core 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/.baruk-provenance.json +4 -4
  2. package/dist/cjs/clients/ibge.js +14 -0
  3. package/dist/cjs/clients/ibge.js.map +1 -1
  4. package/dist/cjs/clients/registry/http.js +157 -0
  5. package/dist/cjs/clients/registry/http.js.map +1 -0
  6. package/dist/cjs/clients/registry/ibge-municipios.js +28 -0
  7. package/dist/cjs/clients/registry/ibge-municipios.js.map +1 -0
  8. package/dist/cjs/clients/registry/ibge.js +63 -0
  9. package/dist/cjs/clients/registry/ibge.js.map +1 -0
  10. package/dist/cjs/clients/registry/lookup.js +191 -0
  11. package/dist/cjs/clients/registry/lookup.js.map +1 -0
  12. package/dist/cjs/clients/registry/provider.js +34 -0
  13. package/dist/cjs/clients/registry/provider.js.map +1 -0
  14. package/dist/cjs/clients/registry/resolve.js +47 -0
  15. package/dist/cjs/clients/registry/resolve.js.map +1 -0
  16. package/dist/cjs/clients/registry/serpro.js +149 -0
  17. package/dist/cjs/clients/registry/serpro.js.map +1 -0
  18. package/dist/cjs/clients/registry/service.js +230 -0
  19. package/dist/cjs/clients/registry/service.js.map +1 -0
  20. package/dist/cjs/clients/registry/synthetic.js +165 -0
  21. package/dist/cjs/clients/registry/synthetic.js.map +1 -0
  22. package/dist/cjs/clients/repository.js +6 -2
  23. package/dist/cjs/clients/repository.js.map +1 -1
  24. package/dist/cjs/db/provision-e2e-identity.js +8 -3
  25. package/dist/cjs/db/provision-e2e-identity.js.map +1 -1
  26. package/dist/cjs/fiscal/collection/authorization.js +186 -0
  27. package/dist/cjs/fiscal/collection/authorization.js.map +1 -0
  28. package/dist/cjs/fiscal/collection/competence.js +97 -0
  29. package/dist/cjs/fiscal/collection/competence.js.map +1 -0
  30. package/dist/cjs/fiscal/collection/first-contact.js +204 -0
  31. package/dist/cjs/fiscal/collection/first-contact.js.map +1 -0
  32. package/dist/cjs/fiscal/collection/plan.js +122 -0
  33. package/dist/cjs/fiscal/collection/plan.js.map +1 -0
  34. package/dist/cjs/fiscal/coverage/model.js +11 -1
  35. package/dist/cjs/fiscal/coverage/model.js.map +1 -1
  36. package/dist/cjs/fiscal/credentials/inspect.js +182 -0
  37. package/dist/cjs/fiscal/credentials/inspect.js.map +1 -0
  38. package/dist/cjs/fiscal/credentials/material.js +1 -1
  39. package/dist/cjs/fiscal/credentials/material.js.map +1 -1
  40. package/dist/cjs/fiscal/credentials/onboarding.js +266 -0
  41. package/dist/cjs/fiscal/credentials/onboarding.js.map +1 -0
  42. package/dist/cjs/fiscal/credentials/readiness.js +29 -2
  43. package/dist/cjs/fiscal/credentials/readiness.js.map +1 -1
  44. package/dist/cjs/fiscal/enrollment.js +134 -0
  45. package/dist/cjs/fiscal/enrollment.js.map +1 -0
  46. package/dist/cjs/fiscal/transport/synthetic-tls.js +52 -0
  47. package/dist/cjs/fiscal/transport/synthetic-tls.js.map +1 -1
  48. package/dist/cjs/index.js +196 -113
  49. package/dist/cjs/index.js.map +1 -1
  50. package/dist/cjs/legal/documents.js +64 -4
  51. package/dist/cjs/legal/documents.js.map +1 -1
  52. package/dist/cjs/legal/types.js.map +1 -1
  53. package/dist/cjs/platform/capabilities.js +29 -0
  54. package/dist/cjs/platform/capabilities.js.map +1 -1
  55. package/dist/cjs/rbac/catalog.js +78 -2
  56. package/dist/cjs/rbac/catalog.js.map +1 -1
  57. package/dist/cjs/subscription-billing/index.js +8 -1
  58. package/dist/cjs/subscription-billing/index.js.map +1 -1
  59. package/dist/cjs/subscription-billing/platform-exemption.js +121 -0
  60. package/dist/cjs/subscription-billing/platform-exemption.js.map +1 -0
  61. package/dist/cjs/subscription-billing/service.js +93 -1
  62. package/dist/cjs/subscription-billing/service.js.map +1 -1
  63. package/dist/cjs/vault/repository.js +92 -5
  64. package/dist/cjs/vault/repository.js.map +1 -1
  65. package/dist/clients/ibge.d.ts.map +1 -1
  66. package/dist/clients/ibge.js +14 -0
  67. package/dist/clients/ibge.js.map +1 -1
  68. package/dist/clients/registry/http.d.ts +61 -0
  69. package/dist/clients/registry/http.d.ts.map +1 -0
  70. package/dist/clients/registry/http.js +151 -0
  71. package/dist/clients/registry/http.js.map +1 -0
  72. package/dist/clients/registry/ibge-municipios.d.ts +25 -0
  73. package/dist/clients/registry/ibge-municipios.d.ts.map +1 -0
  74. package/dist/clients/registry/ibge-municipios.js +25 -0
  75. package/dist/clients/registry/ibge-municipios.js.map +1 -0
  76. package/dist/clients/registry/ibge.d.ts +62 -0
  77. package/dist/clients/registry/ibge.d.ts.map +1 -0
  78. package/dist/clients/registry/ibge.js +57 -0
  79. package/dist/clients/registry/ibge.js.map +1 -0
  80. package/dist/clients/registry/lookup.d.ts +94 -0
  81. package/dist/clients/registry/lookup.d.ts.map +1 -0
  82. package/dist/clients/registry/lookup.js +184 -0
  83. package/dist/clients/registry/lookup.js.map +1 -0
  84. package/dist/clients/registry/provider.d.ts +124 -0
  85. package/dist/clients/registry/provider.d.ts.map +1 -0
  86. package/dist/clients/registry/provider.js +39 -0
  87. package/dist/clients/registry/provider.js.map +1 -0
  88. package/dist/clients/registry/resolve.d.ts +36 -0
  89. package/dist/clients/registry/resolve.d.ts.map +1 -0
  90. package/dist/clients/registry/resolve.js +44 -0
  91. package/dist/clients/registry/resolve.js.map +1 -0
  92. package/dist/clients/registry/serpro.d.ts +57 -0
  93. package/dist/clients/registry/serpro.d.ts.map +1 -0
  94. package/dist/clients/registry/serpro.js +143 -0
  95. package/dist/clients/registry/serpro.js.map +1 -0
  96. package/dist/clients/registry/service.d.ts +59 -0
  97. package/dist/clients/registry/service.d.ts.map +1 -0
  98. package/dist/clients/registry/service.js +225 -0
  99. package/dist/clients/registry/service.js.map +1 -0
  100. package/dist/clients/registry/synthetic.d.ts +30 -0
  101. package/dist/clients/registry/synthetic.d.ts.map +1 -0
  102. package/dist/clients/registry/synthetic.js +161 -0
  103. package/dist/clients/registry/synthetic.js.map +1 -0
  104. package/dist/clients/repository.d.ts +4 -0
  105. package/dist/clients/repository.d.ts.map +1 -1
  106. package/dist/clients/repository.js +6 -2
  107. package/dist/clients/repository.js.map +1 -1
  108. package/dist/db/provision-e2e-identity.js +8 -3
  109. package/dist/db/provision-e2e-identity.js.map +1 -1
  110. package/dist/fiscal/collection/authorization.d.ts +152 -0
  111. package/dist/fiscal/collection/authorization.d.ts.map +1 -0
  112. package/dist/fiscal/collection/authorization.js +178 -0
  113. package/dist/fiscal/collection/authorization.js.map +1 -0
  114. package/dist/fiscal/collection/competence.d.ts +66 -0
  115. package/dist/fiscal/collection/competence.d.ts.map +1 -0
  116. package/dist/fiscal/collection/competence.js +90 -0
  117. package/dist/fiscal/collection/competence.js.map +1 -0
  118. package/dist/fiscal/collection/first-contact.d.ts +103 -0
  119. package/dist/fiscal/collection/first-contact.d.ts.map +1 -0
  120. package/dist/fiscal/collection/first-contact.js +201 -0
  121. package/dist/fiscal/collection/first-contact.js.map +1 -0
  122. package/dist/fiscal/collection/plan.d.ts +74 -0
  123. package/dist/fiscal/collection/plan.d.ts.map +1 -0
  124. package/dist/fiscal/collection/plan.js +119 -0
  125. package/dist/fiscal/collection/plan.js.map +1 -0
  126. package/dist/fiscal/coverage/model.d.ts +28 -0
  127. package/dist/fiscal/coverage/model.d.ts.map +1 -1
  128. package/dist/fiscal/coverage/model.js +11 -1
  129. package/dist/fiscal/coverage/model.js.map +1 -1
  130. package/dist/fiscal/credentials/inspect.d.ts +114 -0
  131. package/dist/fiscal/credentials/inspect.d.ts.map +1 -0
  132. package/dist/fiscal/credentials/inspect.js +178 -0
  133. package/dist/fiscal/credentials/inspect.js.map +1 -0
  134. package/dist/fiscal/credentials/material.js +2 -2
  135. package/dist/fiscal/credentials/material.js.map +1 -1
  136. package/dist/fiscal/credentials/onboarding.d.ts +119 -0
  137. package/dist/fiscal/credentials/onboarding.d.ts.map +1 -0
  138. package/dist/fiscal/credentials/onboarding.js +259 -0
  139. package/dist/fiscal/credentials/onboarding.js.map +1 -0
  140. package/dist/fiscal/credentials/readiness.d.ts +30 -3
  141. package/dist/fiscal/credentials/readiness.d.ts.map +1 -1
  142. package/dist/fiscal/credentials/readiness.js +29 -2
  143. package/dist/fiscal/credentials/readiness.js.map +1 -1
  144. package/dist/fiscal/enrollment.d.ts +91 -0
  145. package/dist/fiscal/enrollment.d.ts.map +1 -0
  146. package/dist/fiscal/enrollment.js +126 -0
  147. package/dist/fiscal/enrollment.js.map +1 -0
  148. package/dist/fiscal/transport/synthetic-tls.d.ts +19 -0
  149. package/dist/fiscal/transport/synthetic-tls.d.ts.map +1 -1
  150. package/dist/fiscal/transport/synthetic-tls.js +51 -0
  151. package/dist/fiscal/transport/synthetic-tls.js.map +1 -1
  152. package/dist/index.d.ts +32 -4
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.js +22 -2
  155. package/dist/index.js.map +1 -1
  156. package/dist/legal/documents.d.ts.map +1 -1
  157. package/dist/legal/documents.js +64 -4
  158. package/dist/legal/documents.js.map +1 -1
  159. package/dist/legal/index.d.ts +1 -1
  160. package/dist/legal/index.d.ts.map +1 -1
  161. package/dist/legal/types.d.ts +77 -0
  162. package/dist/legal/types.d.ts.map +1 -1
  163. package/dist/legal/types.js.map +1 -1
  164. package/dist/platform/capabilities.d.ts.map +1 -1
  165. package/dist/platform/capabilities.js +29 -0
  166. package/dist/platform/capabilities.js.map +1 -1
  167. package/dist/rbac/catalog.d.ts.map +1 -1
  168. package/dist/rbac/catalog.js +78 -2
  169. package/dist/rbac/catalog.js.map +1 -1
  170. package/dist/subscription-billing/index.d.ts +3 -1
  171. package/dist/subscription-billing/index.d.ts.map +1 -1
  172. package/dist/subscription-billing/index.js +2 -1
  173. package/dist/subscription-billing/index.js.map +1 -1
  174. package/dist/subscription-billing/platform-exemption.d.ts +89 -0
  175. package/dist/subscription-billing/platform-exemption.d.ts.map +1 -0
  176. package/dist/subscription-billing/platform-exemption.js +113 -0
  177. package/dist/subscription-billing/platform-exemption.js.map +1 -0
  178. package/dist/subscription-billing/service.d.ts +53 -0
  179. package/dist/subscription-billing/service.d.ts.map +1 -1
  180. package/dist/subscription-billing/service.js +92 -1
  181. package/dist/subscription-billing/service.js.map +1 -1
  182. package/dist/vault/repository.d.ts +69 -1
  183. package/dist/vault/repository.d.ts.map +1 -1
  184. package/dist/vault/repository.js +88 -5
  185. package/dist/vault/repository.js.map +1 -1
  186. package/package.json +3 -2
  187. package/src/db/migrations/0060_fiscal_credential_vault_and_run_authorization.sql +321 -0
  188. package/src/db/migrations/0061_client_official_registry_provenance.sql +193 -0
  189. package/src/db/migrations/0062_first_contact_and_capability_truth.sql +119 -0
@@ -0,0 +1,193 @@
1
+ -- 0061 — de onde veio cada dado cadastral, e quem mandou nele.
2
+ --
3
+ -- ═══ O defeito que a proveniência existe para tornar indizível ═════════════
4
+ --
5
+ -- Sem ela, um cadastro pré-preenchido por fonte oficial e um cadastro digitado
6
+ -- à mão são a MESMA linha. E aí duas coisas ficam impossíveis:
7
+ --
8
+ -- 1. atualizar sem destruir. Uma segunda consulta traria o valor oficial, e
9
+ -- sobrescrever apagaria a correção que uma pessoa fez de propósito — o
10
+ -- telefone que o escritório sabe estar certo, o e-mail que mudou e a
11
+ -- Receita ainda não sabe.
12
+ -- 2. explicar. "Por que o município deste cliente é Goiânia?" tem respostas
13
+ -- diferentes conforme alguém tenha digitado ou a Receita tenha dito, e a
14
+ -- diferença decide quanto se confia no dado.
15
+ --
16
+ -- A tabela guarda o retrato OFICIAL, ao lado do cadastro — nunca por cima dele.
17
+ -- O cadastro continua sendo o que o escritório afirma; esta linha é o que a
18
+ -- fonte disse, quando disse, e por qual provedor.
19
+ --
20
+ -- ═══ Por que uma tabela, e não colunas em `client` ════════════════════════
21
+ --
22
+ -- Colunas `*_source` em `client` dobrariam a largura da tabela e ainda assim não
23
+ -- guardariam o valor oficial DIVERGENTE — só a etiqueta. É justamente o valor
24
+ -- divergente que a tela de conferência precisa mostrar lado a lado.
25
+
26
+ CREATE TABLE IF NOT EXISTS client_registry_snapshot (
27
+ id uuid PRIMARY KEY,
28
+ tenant_id uuid NOT NULL REFERENCES tenant(id),
29
+ client_id uuid NOT NULL,
30
+
31
+ -- Quem respondeu, e se é fonte oficial. `official = false` NÃO é proibido —
32
+ -- é o que permite registrar um provedor de teste sem que ele se disfarce de
33
+ -- Receita Federal na trilha.
34
+ provider text NOT NULL,
35
+ provider_official boolean NOT NULL DEFAULT false,
36
+ fetched_at timestamptz NOT NULL DEFAULT now(),
37
+
38
+ -- O documento consultado, em dígitos. Guardado aqui porque o `client` pode ser
39
+ -- corrigido depois, e o retrato tem de dizer sobre QUAL número ele fala.
40
+ document_digits text NOT NULL CHECK (document_digits ~ '^[0-9]{14}$'),
41
+
42
+ -- O retrato normalizado. Todo campo é opcional: toda fonte tem lacuna, e uma
43
+ -- lacuna precisa ser dizível como ausência — não como string vazia.
44
+ legal_name text,
45
+ trade_name text,
46
+ registration_status text,
47
+ opened_on date,
48
+ legal_nature text,
49
+ company_size text,
50
+ primary_activity_code text,
51
+ primary_activity_text text,
52
+ secondary_activities jsonb NOT NULL DEFAULT '[]'::jsonb,
53
+ street text,
54
+ street_number text,
55
+ complement text,
56
+ district text,
57
+ postal_code text,
58
+ municipality text,
59
+ state_uf text,
60
+ phone text,
61
+ email text,
62
+ share_capital_cents bigint,
63
+
64
+ -- ═══ O silêncio da fonte NÃO é "não" ════════════════════════════════════
65
+ -- `NULL` aqui significa "a fonte não informou", e é diferente de `false`.
66
+ -- Colapsar os dois é como um optante do Simples vira Lucro Presumido por
67
+ -- omissão — e o regime decide quais obrigações o motor aplica.
68
+ simples_optant boolean,
69
+ mei_optant boolean,
70
+
71
+ created_at timestamptz NOT NULL DEFAULT now(),
72
+
73
+ CONSTRAINT client_registry_snapshot_tenant_id_uq UNIQUE (tenant_id, id),
74
+ FOREIGN KEY (tenant_id, client_id) REFERENCES client (tenant_id, id)
75
+ );
76
+
77
+ COMMENT ON COLUMN client_registry_snapshot.simples_optant IS
78
+ 'NULL = a fonte não informou. NÃO é false. O regime tributário depende disso, e silêncio não é negativa.';
79
+
80
+ -- Um retrato por (cliente, provedor, momento). O índice serve à pergunta real:
81
+ -- "qual foi o último retrato deste cliente?".
82
+ CREATE INDEX IF NOT EXISTS client_registry_snapshot_latest_idx
83
+ ON client_registry_snapshot (tenant_id, client_id, fetched_at DESC);
84
+
85
+ ALTER TABLE client_registry_snapshot ENABLE ROW LEVEL SECURITY;
86
+ ALTER TABLE client_registry_snapshot FORCE ROW LEVEL SECURITY;
87
+ CREATE POLICY client_registry_snapshot_tenant_isolation ON client_registry_snapshot
88
+ USING (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid)
89
+ WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid);
90
+
91
+ -- ═══ A etiqueta por ATRIBUTO ══════════════════════════════════════════════
92
+ --
93
+ -- O retrato acima diz o que a fonte falou. Esta tabela diz o que aconteceu com
94
+ -- cada campo DO CADASTRO: veio da fonte, ou uma pessoa o escreveu?
95
+ --
96
+ -- É ela que faz `HUMAN_OVERRIDE_PRESERVED` ser verificável: um campo marcado
97
+ -- `human` nunca é sobrescrito por atualização automática, e isso é uma consulta
98
+ -- — não uma convenção que alguém precisa lembrar.
99
+ CREATE TABLE IF NOT EXISTS client_field_provenance (
100
+ id uuid PRIMARY KEY,
101
+ tenant_id uuid NOT NULL REFERENCES tenant(id),
102
+ client_id uuid NOT NULL,
103
+ field text NOT NULL,
104
+ origin text NOT NULL CHECK (origin IN ('official_lookup', 'human', 'import')),
105
+ provider text,
106
+ verified_at timestamptz,
107
+ updated_at timestamptz NOT NULL DEFAULT now(),
108
+ updated_by uuid REFERENCES "user"(id),
109
+
110
+ CONSTRAINT client_field_provenance_tenant_id_uq UNIQUE (tenant_id, id),
111
+ FOREIGN KEY (tenant_id, client_id) REFERENCES client (tenant_id, id),
112
+ -- Origem oficial sem provedor é afirmação sem autor.
113
+ CONSTRAINT client_field_provenance_official_needs_provider
114
+ CHECK (origin <> 'official_lookup' OR (provider IS NOT NULL AND verified_at IS NOT NULL))
115
+ );
116
+
117
+ CREATE UNIQUE INDEX IF NOT EXISTS client_field_provenance_uq
118
+ ON client_field_provenance (tenant_id, client_id, field);
119
+
120
+ ALTER TABLE client_field_provenance ENABLE ROW LEVEL SECURITY;
121
+ ALTER TABLE client_field_provenance FORCE ROW LEVEL SECURITY;
122
+ CREATE POLICY client_field_provenance_tenant_isolation ON client_field_provenance
123
+ USING (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid)
124
+ WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid);
125
+
126
+ -- ═══ O nome fantasia ══════════════════════════════════════════════════════
127
+ --
128
+ -- `client` guarda `display_name` e `legal_name`, e não tinha onde pôr o nome
129
+ -- fantasia — que é o que a fonte oficial devolve e o que a equipe usa para
130
+ -- falar do cliente ("HIPER FESTA", não "OLIVEIRA PRODUTOS PARA FESTAS LTDA").
131
+ ALTER TABLE client ADD COLUMN IF NOT EXISTS trade_name text;
132
+
133
+ COMMENT ON COLUMN client.trade_name IS
134
+ 'Nome fantasia. Distinto de display_name (como o escritório chama) e de legal_name (razão social).';
135
+
136
+ INSERT INTO platform_capability (key, label, contract_version, declared_by_migration) VALUES
137
+ ('office.client-registry-provenance.v1',
138
+ 'Retrato de fonte oficial e proveniência por atributo do cadastro do cliente',
139
+ 1, '0061')
140
+ ON CONFLICT (key) DO NOTHING;
141
+
142
+ -- ═══ A consulta em si — cota, cache e trilha na MESMA linha ═══════════════
143
+ --
144
+ -- Esta tabela existe por três perguntas que não têm outra fonte:
145
+ --
146
+ -- 1. **quantas consultas este escritório já fez na janela?** Sem contagem
147
+ -- persistente, um contador em memória zera a cada redeploy e some quando
148
+ -- há duas instâncias — que é exatamente quando o limite importa.
149
+ -- 2. **já perguntamos este CNPJ há pouco?** Uma segunda consulta idêntica em
150
+ -- cinco minutos gasta cota de um contrato pago para reobter o que já se
151
+ -- sabe. O `payload` é o cache; o TTL vive no código.
152
+ -- 3. **quem perguntou por qual empresa?** Consulta de CNPJ é rastreável por
153
+ -- dever próprio: é a pergunta "esse escritório andou varrendo documentos?".
154
+ --
155
+ -- **Sem FK para `client`, e é de propósito.** A consulta acontece ANTES de o
156
+ -- cliente existir — o operador digita o CNPJ numa tela de cadastro vazia. Uma FK
157
+ -- aqui tornaria impossível registrar justamente a consulta mais comum.
158
+ CREATE TABLE IF NOT EXISTS client_registry_lookup (
159
+ id uuid PRIMARY KEY,
160
+ tenant_id uuid NOT NULL REFERENCES tenant(id),
161
+ document_digits text NOT NULL CHECK (document_digits ~ '^[0-9]{14}$'),
162
+ provider text NOT NULL,
163
+ outcome text NOT NULL CHECK (outcome IN ('ok', 'failed')),
164
+ -- O motivo da recusa, quando houve. Vocabulário fechado no código
165
+ -- (`CompanyLookupFailure`); aqui fica aberto para não travar a migration num
166
+ -- vocabulário que ainda vai crescer com o primeiro provedor real.
167
+ failure_reason text,
168
+ -- O retrato devolvido, quando houve. É o cache: uma repetição dentro do TTL
169
+ -- é respondida daqui sem tocar a rede. NULL em toda linha `failed` — falha
170
+ -- não se cacheia, senão uma indisponibilidade de um minuto ficaria colada
171
+ -- no CNPJ pelo resto do TTL.
172
+ payload jsonb,
173
+ requested_by uuid REFERENCES "user"(id),
174
+ created_at timestamptz NOT NULL DEFAULT now(),
175
+
176
+ CONSTRAINT client_registry_lookup_tenant_id_uq UNIQUE (tenant_id, id),
177
+ CONSTRAINT client_registry_lookup_failure_has_no_payload
178
+ CHECK (outcome = 'ok' OR payload IS NULL)
179
+ );
180
+
181
+ -- A cota pergunta "quantas linhas deste tenant desde X"; o cache pergunta
182
+ -- "qual a última linha `ok` deste documento". Um índice serve às duas.
183
+ CREATE INDEX IF NOT EXISTS client_registry_lookup_quota_idx
184
+ ON client_registry_lookup (tenant_id, created_at DESC);
185
+ CREATE INDEX IF NOT EXISTS client_registry_lookup_cache_idx
186
+ ON client_registry_lookup (tenant_id, document_digits, created_at DESC)
187
+ WHERE outcome = 'ok';
188
+
189
+ ALTER TABLE client_registry_lookup ENABLE ROW LEVEL SECURITY;
190
+ ALTER TABLE client_registry_lookup FORCE ROW LEVEL SECURITY;
191
+ CREATE POLICY client_registry_lookup_tenant_isolation ON client_registry_lookup
192
+ USING (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid)
193
+ WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid);
@@ -0,0 +1,119 @@
1
+ -- 0062 — o primeiro contato, e a diferença entre "existe" e "nós fizemos".
2
+ --
3
+ -- ═══ Dois defeitos, e por que eles são o mesmo defeito ════════════════════
4
+ --
5
+ -- **1. O impasse.** A execução exigia `verified_at`; `verified_at` significa "o
6
+ -- ambiente oficial já aceitou"; e só uma execução produz esse aceite. A primeira
7
+ -- consulta real era inalcançável por construção — não por configuração, não por
8
+ -- falta de certificado. A auditoria da C2 encontrou o ciclo fechado.
9
+ --
10
+ -- **2. A cobertura mentindo por omissão.** `coverage_status = 'SUPPORTED_OFFICIAL'`
11
+ -- responde "a norma oferece um caminho oficial?". A tela mostrava isso ao lado do
12
+ -- botão de autorizar, e o operador lia "o Baruk já fala com esse serviço". Para
13
+ -- `sefaz_dfe` isso era falso: não existe adaptador, só a string do conector.
14
+ --
15
+ -- Os dois são a mesma confusão — **tratar o que o mundo oferece como o que este
16
+ -- produto construiu**. A correção é a mesma nos dois: separar os fatos em
17
+ -- colunas próprias e obrigar cada portão a citar a sua.
18
+
19
+ -- ═══ 1 · A verdade da capacidade, por camada ══════════════════════════════
20
+ --
21
+ -- `official_api_available` já respondia "existe API oficial?". Faltavam as duas
22
+ -- perguntas seguintes, e é a ausência delas que produzia a leitura errada.
23
+ ALTER TABLE fiscal_coverage
24
+ ADD COLUMN IF NOT EXISTS connector_implemented boolean NOT NULL DEFAULT false;
25
+ ALTER TABLE fiscal_coverage
26
+ ADD COLUMN IF NOT EXISTS protocol_verified boolean NOT NULL DEFAULT false;
27
+
28
+ COMMENT ON COLUMN fiscal_coverage.official_api_available IS
29
+ 'A NORMA oferece um caminho oficial? Fato do mundo, independente deste produto.';
30
+ COMMENT ON COLUMN fiscal_coverage.connector_implemented IS
31
+ 'ESTE produto tem adaptador para o caminho? Fato do repositório. `sefaz_dfe` era SUPPORTED_OFFICIAL sem uma linha de adaptador — foi o que esta coluna passou a tornar dizível.';
32
+ COMMENT ON COLUMN fiscal_coverage.protocol_verified IS
33
+ 'O envelope/contrato do serviço foi conferido contra o serviço REAL? Ler documentação não conta; para o ADN, os nomes dos campos do envelope não estão publicados em lugar acessível.';
34
+
35
+ -- Backfill explícito, e não default silencioso.
36
+ --
37
+ -- `DEFAULT false` deixaria toda linha existente afirmando "não implementado", o
38
+ -- que por acaso está certo para `sefaz_dfe` e errado para `nfse_adn`. Um acerto
39
+ -- por acaso é o que some no primeiro conector novo.
40
+ UPDATE fiscal_coverage SET connector_implemented = true
41
+ WHERE provider = 'nfse_adn';
42
+ UPDATE fiscal_coverage SET connector_implemented = false
43
+ WHERE provider = 'sefaz_dfe';
44
+ -- Nenhum conector tem o protocolo conferido contra o serviço real hoje. O do
45
+ -- ADN depende de um Swagger servido pelo próprio host que exige mTLS.
46
+ UPDATE fiscal_coverage SET protocol_verified = false;
47
+
48
+ -- ═══ 2 · A autorização passa a apontar para UM certificado ════════════════
49
+ --
50
+ -- Sem isto, trocar o certificado depois de autorizar mantinha a autorização de
51
+ -- pé — e a execução aconteceria com material que ninguém aprovou. O caso não é
52
+ -- teórico: substituir certificado é a operação normal quando um vence.
53
+ ALTER TABLE fiscal_run_authorization
54
+ ADD COLUMN IF NOT EXISTS credential_fingerprint text;
55
+
56
+ COMMENT ON COLUMN fiscal_run_authorization.credential_fingerprint IS
57
+ 'sha256 do DER do certificado vigente quando a autorização foi emitida. O executor recusa quando o certificado atual difere: autorizar é aprovar UM material, não uma intenção.';
58
+
59
+ -- ═══ 3 · O primeiro contato é um ATO, e ele tem registro próprio ══════════
60
+ --
61
+ -- Por que uma coluna e não um tipo de autorização separado: a autorização já é
62
+ -- one-shot, read-only, escopada e expirável. O primeiro contato não precisa de
63
+ -- outra tabela — precisa de uma DECLARAÇÃO de que quem autorizou sabia que a
64
+ -- credencial ainda não fora aceita por ninguém.
65
+ --
66
+ -- `false` como padrão importa: uma autorização comum continua exigindo
67
+ -- credencial verificada. Só quem marca este campo aceita o risco do bootstrap.
68
+ ALTER TABLE fiscal_run_authorization
69
+ ADD COLUMN IF NOT EXISTS first_contact boolean NOT NULL DEFAULT false;
70
+
71
+ COMMENT ON COLUMN fiscal_run_authorization.first_contact IS
72
+ 'Autorização de PRIMEIRO CONTATO: aceita credencial válida localmente e ainda não aceita por nenhum ambiente oficial. É o único caminho que resolve o impasse do verified_at, e nenhum agendador pode emiti-la.';
73
+
74
+ -- Quem executou, e por qual porta.
75
+ --
76
+ -- `manual` é o único valor que o primeiro contato admite, e o CHECK abaixo é
77
+ -- quem garante isso — não um `if` em TypeScript que alguém remove.
78
+ ALTER TABLE fiscal_run_authorization
79
+ ADD COLUMN IF NOT EXISTS trigger_kind text NOT NULL DEFAULT 'manual';
80
+
81
+ ALTER TABLE fiscal_run_authorization
82
+ DROP CONSTRAINT IF EXISTS fiscal_run_authorization_trigger_kind_check;
83
+ ALTER TABLE fiscal_run_authorization
84
+ ADD CONSTRAINT fiscal_run_authorization_trigger_kind_check
85
+ CHECK (trigger_kind IN ('manual', 'scheduled'));
86
+
87
+ -- ═══ O CHECK que impede o agendador de fazer o bootstrap ══════════════════
88
+ --
89
+ -- Está no banco, e não no orquestrador, porque a regra tem de sobreviver a um
90
+ -- refactor do orquestrador. Um cron que tentasse emitir autorização de primeiro
91
+ -- contato viola a linha abaixo e a transação morre.
92
+ ALTER TABLE fiscal_run_authorization
93
+ DROP CONSTRAINT IF EXISTS fiscal_run_authorization_first_contact_is_manual;
94
+ ALTER TABLE fiscal_run_authorization
95
+ ADD CONSTRAINT fiscal_run_authorization_first_contact_is_manual
96
+ CHECK (first_contact = false OR trigger_kind = 'manual');
97
+
98
+ COMMENT ON CONSTRAINT fiscal_run_authorization_first_contact_is_manual ON fiscal_run_authorization IS
99
+ 'Primeiro contato é ato de gente. Um agendador que o tentasse estaria decidindo, sozinho, gastar o certificado de um terceiro contra a Receita pela primeira vez.';
100
+
101
+ -- ═══ 4 · A habilitação fiscal do tenant, com granularidade ════════════════
102
+ --
103
+ -- `tenant_fiscal_automation` já existe (0058) e responde por MODO — qual
104
+ -- ambiente o escritório declarou operar. Ela continua sendo a chave-geral.
105
+ --
106
+ -- O que faltava era o outro lado do par: a matrícula POR CLIENTE já existia em
107
+ -- `client_fiscal_enrollment` (0058), também por `document_type`. Nada disso
108
+ -- precisa de tabela nova — precisa de superfície e de auditoria, que vêm no
109
+ -- código desta mesma frente.
110
+ --
111
+ -- Registrado aqui para quem ler a migration procurando as tabelas e não as
112
+ -- encontrar: elas existem desde a 0058, e esta frente as LIGA, não as cria.
113
+
114
+ -- ═══ 5 · A capacidade ═════════════════════════════════════════════════════
115
+ INSERT INTO platform_capability (key, label, contract_version, declared_by_migration) VALUES
116
+ ('office.fiscal-first-contact.v1',
117
+ 'Primeiro contato fiscal: autorização one-shot que aceita credencial válida só localmente',
118
+ 1, '0062')
119
+ ON CONFLICT (key) DO NOTHING;