@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
@@ -7,6 +7,7 @@ function mapMeta(r) {
7
7
  name: r.name,
8
8
  category: r.category,
9
9
  provider: r.provider,
10
+ exposure: r.exposure ?? 'revealable',
10
11
  keyVersion: r.key_version,
11
12
  metadata: r.metadata ?? {},
12
13
  expiresAt: r.expires_at ? r.expires_at.toISOString() : null,
@@ -16,26 +17,43 @@ function mapMeta(r) {
16
17
  };
17
18
  }
18
19
  const META_COLUMNS = `
19
- id, client_id, name, category, provider, key_version, metadata,
20
+ id, client_id, name, category, provider, exposure, key_version, metadata,
20
21
  expires_at, rotated_at, created_at, updated_at
21
22
  `;
23
+ /**
24
+ * A classe de exposição de uma categoria.
25
+ *
26
+ * Material de certificado NUNCA é revelável, e a regra vive nos dois lugares de
27
+ * propósito: aqui, para que a gravação nasça certa, e no CHECK da 0060, para que
28
+ * um caminho que ninguém escreveu ainda também nasça certo.
29
+ *
30
+ * Sem esta função, `createCredential` — a porta genérica do cofre, usada por
31
+ * rotas e seeds — gravaria `revealable` e receberia uma violação de constraint.
32
+ * O comportamento seria seguro e a mensagem, incompreensível: um 500 de
33
+ * constraint onde deveria haver uma gravação correta.
34
+ */
35
+ export function exposureFor(category) {
36
+ return category === 'certificate' ? 'write_only' : 'revealable';
37
+ }
22
38
  export async function createCredential(tx, keyring, tenantId, input) {
23
39
  const enc = encryptSecret(keyring, tenantId, input.secret);
40
+ const category = input.category ?? 'generic';
24
41
  const rows = await tx `
25
42
  INSERT INTO vault_credential (
26
43
  id, tenant_id, client_id, name, category, provider,
27
- ciphertext, iv, auth_tag, key_version, metadata, expires_at, created_by
44
+ ciphertext, iv, auth_tag, key_version, metadata, expires_at, created_by, exposure
28
45
  ) VALUES (
29
46
  ${uuidv7()},
30
47
  current_setting('app.current_tenant')::uuid,
31
48
  ${input.clientId ?? null},
32
49
  ${input.name},
33
- ${input.category ?? 'generic'},
50
+ ${category},
34
51
  ${input.provider ?? null},
35
52
  ${enc.ciphertext}, ${enc.iv}, ${enc.authTag}, ${enc.keyVersion},
36
53
  ${tx.json((input.metadata ?? {}))},
37
54
  ${input.expiresAt ?? null},
38
- ${input.createdBy ?? null}
55
+ ${input.createdBy ?? null},
56
+ ${exposureFor(category)}
39
57
  )
40
58
  RETURNING ${tx.unsafe(META_COLUMNS)}
41
59
  `;
@@ -60,11 +78,76 @@ export async function getCredentialMeta(tx, id) {
60
78
  throw new NotFoundError('Credencial não encontrada');
61
79
  return mapMeta(rows[0]);
62
80
  }
63
- /** Decifra e retorna o segredo em claro. A ROTA deve exigir `vault.credential.decrypt` e auditar. */
81
+ /**
82
+ * Erro de quem pede para ver um segredo que não tem caminho de volta.
83
+ *
84
+ * Nomeado, e não um `NotFoundError`, porque a diferença importa para quem opera:
85
+ * "não existe" manda procurar; "não é revelável" manda substituir. Não carrega
86
+ * nome nem ponteiro — só a classe.
87
+ */
88
+ export class SecretNotRevealableError extends Error {
89
+ constructor() {
90
+ super('Esta credencial é write-only: não existe caminho de leitura para humano.');
91
+ this.name = 'SecretNotRevealableError';
92
+ }
93
+ }
94
+ /**
95
+ * Decifra e retorna o segredo em claro. A ROTA deve exigir
96
+ * `vault.credential.decrypt` e auditar.
97
+ *
98
+ * ## O portão que faltava
99
+ *
100
+ * Esta função era o caminho ÚNICO de saída do cofre, e ela não perguntava que
101
+ * tipo de segredo estava devolvendo. Para uma chave de API isso é o
102
+ * comportamento desejado. Para o envelope de um certificado digital — que
103
+ * carrega a CHAVE PRIVADA do cliente em base64 — era a porta dos fundos: um
104
+ * `POST .../reveal` devolvia o material inteiro para a tela, e o papel
105
+ * `accountant` do catálogo já carregava a permissão.
106
+ *
107
+ * A recusa acontece DEPOIS de ler a linha e ANTES de decifrar: nem o custo da
108
+ * decifração é pago, e o texto em claro nunca chega a existir em memória.
109
+ */
64
110
  export async function revealSecret(tx, keyring, tenantId, id) {
65
111
  const rows = await tx `
66
112
  SELECT ${tx.unsafe(META_COLUMNS)}, ciphertext, iv, auth_tag
67
113
  FROM vault_credential WHERE id = ${id} AND deleted_at IS NULL
114
+ `;
115
+ if (!rows[0])
116
+ throw new NotFoundError('Credencial não encontrada');
117
+ const r = rows[0];
118
+ if ((r.exposure ?? 'revealable') === 'write_only')
119
+ throw new SecretNotRevealableError();
120
+ const secret = decryptSecret(keyring, tenantId, {
121
+ ciphertext: r.ciphertext,
122
+ iv: r.iv,
123
+ authTag: r.auth_tag,
124
+ keyVersion: r.key_version,
125
+ });
126
+ return { meta: mapMeta(r), secret };
127
+ }
128
+ /**
129
+ * O caminho de PROCESSO para um segredo write-only.
130
+ *
131
+ * ## Por que ela precisa existir, e por que não é exportada
132
+ *
133
+ * `revealSecret` passou a recusar `write_only` — o que fecha a rota humana e,
134
+ * sem esta função, quebraria o motor fiscal: `loadFiscalTlsMaterial` também
135
+ * decifra o mesmo envelope, e ele é justamente quem deve conseguir.
136
+ *
137
+ * A diferença entre os dois chamadores não é a permissão de quem pediu; é o
138
+ * DESTINO do texto em claro. Em `revealSecret` ele vira corpo de resposta HTTP e
139
+ * chega a uma tela. Aqui ele vira `Buffer` dentro de um `https.Agent`, é usado
140
+ * num handshake e é zerado — nunca atravessa a fronteira do processo.
141
+ *
142
+ * O nome grita o que a função faz porque é o único aviso que sobrevive a um
143
+ * autocomplete. E ela NÃO é reexportada pelo `index` do pacote: fora de
144
+ * `@baruk/core` não existe caminho para chamá-la, do mesmo modo que
145
+ * `grantFiscalSecretAccess` não existe fora daqui.
146
+ */
147
+ export async function revealWriteOnlySecretForProcess(tx, keyring, tenantId, id) {
148
+ const rows = await tx `
149
+ SELECT ${tx.unsafe(META_COLUMNS)}, ciphertext, iv, auth_tag
150
+ FROM vault_credential WHERE id = ${id} AND deleted_at IS NULL
68
151
  `;
69
152
  if (!rows[0])
70
153
  throw new NotFoundError('Credencial não encontrada');
@@ -1 +1 @@
1
- {"version":3,"file":"repository.js","sourceRoot":"","sources":["../../src/vault/repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAgB,MAAM,aAAa,CAAC;AAiDzE,SAAS,OAAO,CAAC,CAAU;IACzB,OAAO;QACL,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,QAAQ,EAAE,CAAC,CAAC,SAAS;QACrB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,UAAU,EAAE,CAAC,CAAC,WAAW;QACzB,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE;QAC1B,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI;QAC3D,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI;QAC3D,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC,WAAW,EAAE;QACrC,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC,WAAW,EAAE;KACtC,CAAC;AACJ,CAAC;AAED,MAAM,YAAY,GAAG;;;CAGpB,CAAC;AAaF,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,EAAM,EACN,OAAgB,EAChB,QAAgB,EAChB,KAA4B;IAE5B,MAAM,GAAG,GAAG,aAAa,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3D,MAAM,IAAI,GAAG,MAAM,EAAE,CAAW;;;;;QAK1B,MAAM,EAAE;;QAER,KAAK,CAAC,QAAQ,IAAI,IAAI;QACtB,KAAK,CAAC,IAAI;QACV,KAAK,CAAC,QAAQ,IAAI,SAAS;QAC3B,KAAK,CAAC,QAAQ,IAAI,IAAI;QACtB,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,OAAO,KAAK,GAAG,CAAC,UAAU;QAC5D,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAU,CAAC;QACxC,KAAK,CAAC,SAAS,IAAI,IAAI;QACvB,KAAK,CAAC,SAAS,IAAI,IAAI;;gBAEf,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC;GACpC,CAAC;IACF,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EAAM,EAAE,QAAiB;IAC7D,MAAM,IAAI,GAAG,MAAM,EAAE,CAAW;aACrB,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC;;;aAGvB,QAAQ,IAAI,IAAI,gCAAgC,QAAQ,IAAI,IAAI;;GAE1E,CAAC;IACF,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,EAAM,EAAE,EAAU;IACxD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAW;aACrB,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC;uCACG,EAAE;GACtC,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,aAAa,CAAC,2BAA2B,CAAC,CAAC;IACnE,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1B,CAAC;AAED,qGAAqG;AACrG,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,EAAM,EACN,OAAgB,EAChB,QAAgB,EAChB,EAAU;IAEV,MAAM,IAAI,GAAG,MAAM,EAAE,CAAoE;aAC9E,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC;uCACG,EAAE;GACtC,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,aAAa,CAAC,2BAA2B,CAAC,CAAC;IACnE,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,QAAQ,EAAE;QAC9C,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,OAAO,EAAE,CAAC,CAAC,QAAQ;QACnB,UAAU,EAAE,CAAC,CAAC,WAAW;KAC1B,CAAC,CAAC;IACH,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;AACtC,CAAC;AAsBD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,EAAM,EACN,OAAgB,EAChB,QAAgB,EAChB,IAAY;IAEZ,MAAM,IAAI,GAAG,MAAM,EAAE,CAAoE;aAC9E,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC;;sDAEkB,IAAI;GACvD,CAAC;IACF,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpB,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QACzD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACrC,CAAC;IAED,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,QAAQ,EAAE;QAC9C,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,OAAO,EAAE,CAAC,CAAC,QAAQ;QACnB,UAAU,EAAE,CAAC,CAAC,WAAW;KAC1B,CAAC,CAAC;IACH,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACxC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,EAAM,EACN,OAAgB,EAChB,QAAgB,EAChB,EAAU,EACV,SAAiB,EACjB,SAAyB;IAEzB,MAAM,GAAG,GAAG,aAAa,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;IACxD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAA;;qBAEF,GAAG,CAAC,UAAU,UAAU,GAAG,CAAC,EAAE,gBAAgB,GAAG,CAAC,OAAO;sBACxD,GAAG,CAAC,UAAU;qBACf,SAAS,IAAI,IAAI;iBACrB,EAAE;;GAEhB,CAAC;IACF,OAAO,IAAI,CAAC,KAAK,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,EAAM,EAAE,EAAU;IAC3D,MAAM,IAAI,GAAG,MAAM,EAAE,CAAA;;iBAEN,EAAE;;GAEhB,CAAC;IACF,OAAO,IAAI,CAAC,KAAK,CAAC;AACpB,CAAC;AAED,4FAA4F;AAC5F,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,EAAM,EAAE,IAAI,GAAG,EAAE;IAC7D,MAAM,IAAI,GAAG,MAAM,EAAE,CAAW;aACrB,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC;;;mCAGD,IAAI;;GAEpC,CAAC;IACF,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;;;;;GAWG;AACH;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,EAAM,EACN,IAAY;IAEZ,MAAM,IAAI,GAAG,MAAM,EAAE,CAA2C;;uDAEX,IAAI;;GAExD,CAAC;IACF,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACzB,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACnF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,EAAM,EAAE,IAAY;IAC/D,MAAM,IAAI,GAAG,MAAM,EAAE,CAAkB;;uDAEc,IAAI;;GAExD,CAAC;IACF,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC;AAC7B,CAAC"}
1
+ {"version":3,"file":"repository.js","sourceRoot":"","sources":["../../src/vault/repository.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baruk/core",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/index.js",
@@ -68,6 +68,7 @@
68
68
  "seed": "tsx src/db/seed.ts",
69
69
  "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
70
70
  "seed:catalog": "tsx src/db/seed-catalog.ts",
71
- "provision:e2e": "tsx src/db/provision-e2e-identity.ts"
71
+ "provision:e2e": "tsx src/db/provision-e2e-identity.ts",
72
+ "audit:fiscal-onboarding": "node scripts/audit-fiscal-credential-onboarding.mjs"
72
73
  }
73
74
  }
@@ -0,0 +1,321 @@
1
+ -- 0060 — o cofre fiscal ganha classe de exposição, o certificado ganha
2
+ -- identidade, e a primeira consulta real ganha um portão que se consome.
3
+ --
4
+ -- ═══ O defeito que a primeira seção existe para tornar indizível ══════════
5
+ --
6
+ -- `vault_credential` (0012) guarda todo segredo do escritório sob uma única
7
+ -- regra de leitura: quem tem `vault.credential.decrypt` chama
8
+ -- `POST /v1/vault/credentials/:id/reveal` e recebe o texto em claro. Para uma
9
+ -- chave de API isso é o comportamento desejado — o operador precisa colá-la
10
+ -- noutro sistema.
11
+ --
12
+ -- Para o material de um certificado digital, é a porta dos fundos do cofre. O
13
+ -- envelope que a Fase 2 definiu carrega a CHAVE PRIVADA do cliente em base64;
14
+ -- revelá-lo numa tela é entregá-la, e "não faça isso" não é um controle. Pior:
15
+ -- o papel `accountant` do catálogo já carrega `vault.credential.decrypt`, de
16
+ -- modo que a porta estava aberta para todo contador do escritório.
17
+ --
18
+ -- A correção não é tirar o botão da tela. É dizer no BANCO que existem duas
19
+ -- classes de segredo, e que a segunda não tem caminho de saída:
20
+ --
21
+ -- revealable pode voltar ao humano que o guardou (chave de API, token).
22
+ -- write_only entra, é usado pelo processo, e NUNCA volta.
23
+ --
24
+ -- E é uma RESTRIÇÃO, não uma convenção, que amarra `certificate` à segunda: um
25
+ -- certificado gravado por qualquer caminho — rota nova, script, seed — nasce
26
+ -- irrecuperável, inclusive num caminho que ainda não foi escrito.
27
+
28
+ ALTER TABLE vault_credential ADD COLUMN IF NOT EXISTS exposure text NOT NULL DEFAULT 'revealable';
29
+
30
+ DO $$
31
+ BEGIN
32
+ IF NOT EXISTS (SELECT FROM pg_constraint WHERE conname = 'vault_credential_exposure_values') THEN
33
+ ALTER TABLE vault_credential ADD CONSTRAINT vault_credential_exposure_values
34
+ CHECK (exposure IN ('revealable', 'write_only'));
35
+ END IF;
36
+ END $$;
37
+
38
+ -- Backfill ANTES da restrição de coerência: uma credencial de certificado já
39
+ -- gravada sob a regra antiga tornaria o CHECK inválido, e um `NOT VALID`
40
+ -- silencioso deixaria exatamente as linhas antigas — as que já correram risco —
41
+ -- fora da garantia.
42
+ UPDATE vault_credential SET exposure = 'write_only'
43
+ WHERE category = 'certificate' AND exposure <> 'write_only';
44
+
45
+ DO $$
46
+ BEGIN
47
+ IF NOT EXISTS (SELECT FROM pg_constraint WHERE conname = 'vault_credential_certificate_is_write_only') THEN
48
+ ALTER TABLE vault_credential ADD CONSTRAINT vault_credential_certificate_is_write_only
49
+ CHECK (category <> 'certificate' OR exposure = 'write_only');
50
+ END IF;
51
+ END $$;
52
+
53
+ COMMENT ON COLUMN vault_credential.exposure IS
54
+ 'write_only = não existe caminho de leitura para humano, por nenhuma rota. Material de certificado é write_only por RESTRIÇÃO, não por disciplina da camada de rota.';
55
+ COMMENT ON CONSTRAINT vault_credential_certificate_is_write_only ON vault_credential IS
56
+ 'Remover esta restrição é a decisão consciente de permitir que a chave privada de um cliente volte para uma tela.';
57
+
58
+ -- ═══ 2 · A autorização de execução única ═════════════════════════════════
59
+ --
60
+ -- O motor da Fase 2 tem três desligamentos (flag de ambiente, elegibilidade,
61
+ -- cadência) e todos são ESTADO: ligados, ficam ligados. Para a primeira
62
+ -- consulta real contra um ambiente oficial, em nome de um contribuinte que não
63
+ -- pediu nada, estado permanente é a forma errada de autorização — o titular
64
+ -- autorizou UMA leitura, não um regime.
65
+ --
66
+ -- Esta tabela é um portão que se gasta. Quatro propriedades, todas no schema:
67
+ --
68
+ -- `max_runs = 1` CHECK literal. "Uma execução" não é configuração.
69
+ -- `operation = 'READ_ONLY'` idem. Mutação é INGRAVÁVEL nesta fase.
70
+ -- `expires_at` uma autorização esquecida morre sozinha.
71
+ -- `runs_consumed` nunca ultrapassa `max_runs`, e o CHECK é quem diz.
72
+ --
73
+ -- O período vem por EXTENSO — `period_start` e `period_end` além do par
74
+ -- ano/mês. O relato humano que originou esta frente continha exemplos de datas
75
+ -- inconsistentes entre si, e transformar "mês anterior" em regra implícita
76
+ -- produziria uma coleta cuja janela ninguém confirmou. Aqui a janela é dado
77
+ -- gravado, e a tela mostra as duas datas antes de qualquer execução.
78
+ CREATE TABLE IF NOT EXISTS fiscal_run_authorization (
79
+ id uuid PRIMARY KEY,
80
+ tenant_id uuid NOT NULL REFERENCES tenant(id),
81
+ client_id uuid NOT NULL,
82
+
83
+ document_type text NOT NULL CHECK (document_type IN ('nfse', 'nfe', 'cte', 'nfce')),
84
+ environment text NOT NULL CHECK (environment IN ('synthetic', 'homologation', 'production')),
85
+ -- Slug da fonte, o mesmo vocabulário que governa cursor, disjuntor e
86
+ -- prontidão (`fiscal_sync_cursor.source`). Uma autorização emitida para uma
87
+ -- fonte não vale para outra.
88
+ source text NOT NULL,
89
+ credential_kind text NOT NULL DEFAULT 'certificate_a1'
90
+ CHECK (credential_kind IN ('certificate_a1', 'certificate_a3', 'municipal_portal')),
91
+
92
+ operation text NOT NULL DEFAULT 'READ_ONLY' CHECK (operation IN ('READ_ONLY', 'MUTATING')),
93
+
94
+ competence_year int NOT NULL CHECK (competence_year BETWEEN 2000 AND 2100),
95
+ competence_month int NOT NULL CHECK (competence_month BETWEEN 1 AND 12),
96
+ period_start date NOT NULL,
97
+ period_end date NOT NULL,
98
+
99
+ max_runs int NOT NULL DEFAULT 1,
100
+ runs_consumed int NOT NULL DEFAULT 0,
101
+
102
+ authorized_by uuid NOT NULL REFERENCES "user"(id),
103
+ authorized_at timestamptz NOT NULL DEFAULT now(),
104
+ -- Responsável contábil DECLARADO para esta execução. Texto, e não FK para
105
+ -- `user`: o profissional responsável pode não ter login no produto, e exigir
106
+ -- que tivesse forçaria a criação de uma identidade que ninguém pediu.
107
+ responsible_name text,
108
+ expires_at timestamptz NOT NULL,
109
+
110
+ consumed_at timestamptz,
111
+ consumed_by_run_id uuid,
112
+ revoked_at timestamptz,
113
+ revoked_by uuid REFERENCES "user"(id),
114
+ revoke_reason text,
115
+
116
+ created_at timestamptz NOT NULL DEFAULT now(),
117
+ updated_at timestamptz NOT NULL DEFAULT now(),
118
+
119
+ CONSTRAINT fiscal_run_authorization_tenant_id_uq UNIQUE (tenant_id, id),
120
+ FOREIGN KEY (tenant_id, client_id) REFERENCES client (tenant_id, id),
121
+
122
+ CONSTRAINT fiscal_run_authorization_one_shot CHECK (max_runs = 1),
123
+ CONSTRAINT fiscal_run_authorization_read_only CHECK (operation = 'READ_ONLY'),
124
+ CONSTRAINT fiscal_run_authorization_budget CHECK (runs_consumed >= 0 AND runs_consumed <= max_runs),
125
+ CONSTRAINT fiscal_run_authorization_consumption_coherent
126
+ CHECK ((consumed_at IS NULL) = (runs_consumed = 0)),
127
+ CONSTRAINT fiscal_run_authorization_window CHECK (period_end >= period_start),
128
+ CONSTRAINT fiscal_run_authorization_expiry CHECK (expires_at > authorized_at),
129
+ CONSTRAINT fiscal_run_authorization_revocation_coherent
130
+ CHECK ((revoked_at IS NULL) = (revoked_by IS NULL))
131
+ );
132
+
133
+ COMMENT ON CONSTRAINT fiscal_run_authorization_one_shot ON fiscal_run_authorization IS
134
+ 'Uma execução. Não é configuração: é restrição. Um segundo run real exige uma segunda autorização.';
135
+ COMMENT ON CONSTRAINT fiscal_run_authorization_read_only ON fiscal_run_authorization IS
136
+ 'Mutação em sistema fiscal externo é ingravável nesta fase. Remover esta restrição é a decisão consciente de permitir escrita.';
137
+
138
+ -- Uma autorização VIVA por (cliente, fonte, ambiente, competência). Sem isto,
139
+ -- emitir duas e consumir as duas devolve na prática o regime permanente que a
140
+ -- tabela existe para impedir.
141
+ CREATE UNIQUE INDEX IF NOT EXISTS fiscal_run_authorization_live_uq
142
+ ON fiscal_run_authorization (tenant_id, client_id, source, environment,
143
+ competence_year, competence_month)
144
+ WHERE consumed_at IS NULL AND revoked_at IS NULL;
145
+
146
+ CREATE INDEX IF NOT EXISTS fiscal_run_authorization_client_idx
147
+ ON fiscal_run_authorization (tenant_id, client_id, authorized_at DESC);
148
+
149
+ ALTER TABLE fiscal_run_authorization ENABLE ROW LEVEL SECURITY;
150
+ ALTER TABLE fiscal_run_authorization FORCE ROW LEVEL SECURITY;
151
+ CREATE POLICY fiscal_run_authorization_tenant_isolation ON fiscal_run_authorization
152
+ USING (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid)
153
+ WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid);
154
+
155
+ -- ═══ 3 · A cobrança da PLATAFORMA, separada da cobrança do escritório ═════
156
+ --
157
+ -- Há duas cobranças neste produto e confundi-las custa caro nas duas direções:
158
+ --
159
+ -- plataforma Baruk → escritório (assinatura do Office). `saas_subscription`.
160
+ -- escritório escritório → clientes dele (honorários). `receivable`.
161
+ --
162
+ -- O escritório fundador não paga a primeira. A forma ERRADA de implementar isso
163
+ -- é desligar o financeiro dele — que apagaria a segunda, que é o produto
164
+ -- inteiro. A forma errada seguinte é `if (tenant.code === 'sc')`, que faz o
165
+ -- segundo escritório fundador virar um deploy.
166
+ --
167
+ -- Esta tabela é a forma certa: um fato do tenant, com quem concedeu, quando, e
168
+ -- por quê. O motor de billing lê a linha; ele não conhece nenhum código de
169
+ -- escritório.
170
+ --
171
+ -- ═══ Quem escreve não é quem é cobrado ═══════════════════════════════════
172
+ --
173
+ -- Um controle escrito pelo controlado não é controle. Se o tenant pudesse
174
+ -- gravar a própria isenção, todo escritório seria fundador na primeira tarde.
175
+ -- As políticas abaixo são POR COMANDO: o tenant LÊ a própria linha; só o
176
+ -- operador da plataforma (a GUC `app.platform_operator`, que apenas as rotas de
177
+ -- backoffice setam) INSERE, ATUALIZA ou APAGA.
178
+ CREATE TABLE IF NOT EXISTS tenant_platform_billing (
179
+ tenant_id uuid PRIMARY KEY REFERENCES tenant(id),
180
+ billing_mode text NOT NULL DEFAULT 'standard' CHECK (billing_mode IN ('standard', 'exempt')),
181
+ exemption_reason text CHECK (exemption_reason IS NULL OR
182
+ exemption_reason IN ('founding_tenant', 'internal', 'partner')),
183
+ founding_tenant boolean NOT NULL DEFAULT false,
184
+ granted_by uuid REFERENCES "user"(id),
185
+ granted_at timestamptz,
186
+ -- Isenção com prazo é possível; a do fundador não tem, e `NULL` diz isso.
187
+ expires_on date,
188
+ notes text,
189
+ created_at timestamptz NOT NULL DEFAULT now(),
190
+ updated_at timestamptz NOT NULL DEFAULT now(),
191
+
192
+ -- Isentar custa motivo e data. `standard` é o estado gratuito — e o correto
193
+ -- para todo tenant sobre o qual ninguém decidiu nada.
194
+ CONSTRAINT tenant_platform_billing_exemption_needs_reason
195
+ CHECK (billing_mode = 'standard' OR (exemption_reason IS NOT NULL AND granted_at IS NOT NULL))
196
+ );
197
+
198
+ COMMENT ON TABLE tenant_platform_billing IS
199
+ 'Cobrança da PLATAFORMA (Baruk → escritório). Não governa a cobrança do escritório aos clientes dele, que é outro domínio e continua ativa.';
200
+ COMMENT ON CONSTRAINT tenant_platform_billing_exemption_needs_reason ON tenant_platform_billing IS
201
+ 'Isentar exige motivo e data. Ausência de linha significa cobrança padrão, nunca isenção por omissão.';
202
+
203
+ ALTER TABLE tenant_platform_billing ENABLE ROW LEVEL SECURITY;
204
+ ALTER TABLE tenant_platform_billing FORCE ROW LEVEL SECURITY;
205
+
206
+ -- Leitura: o próprio tenant, ou o operador.
207
+ CREATE POLICY tenant_platform_billing_read ON tenant_platform_billing
208
+ FOR SELECT
209
+ USING (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid
210
+ OR current_setting('app.platform_operator', true) = 'true');
211
+
212
+ -- Escrita: SÓ o operador. Uma política por comando — `WITH CHECK` não governa
213
+ -- DELETE, e uma política única deixaria o apagamento da própria isenção (ou da
214
+ -- de outro) fora de qualquer regra.
215
+ CREATE POLICY tenant_platform_billing_insert ON tenant_platform_billing
216
+ FOR INSERT WITH CHECK (current_setting('app.platform_operator', true) = 'true');
217
+ CREATE POLICY tenant_platform_billing_update ON tenant_platform_billing
218
+ FOR UPDATE
219
+ USING (current_setting('app.platform_operator', true) = 'true')
220
+ WITH CHECK (current_setting('app.platform_operator', true) = 'true');
221
+ CREATE POLICY tenant_platform_billing_delete ON tenant_platform_billing
222
+ FOR DELETE USING (current_setting('app.platform_operator', true) = 'true');
223
+
224
+ -- ═══ 4 · O registry municipal deixa de responder só "não sabemos" ════════
225
+ --
226
+ -- `fiscal_coverage` (0058) já traz status, método, credencial exigida, fallback,
227
+ -- fonte e data. Faltavam duas respostas que a política API-first exige ANTES de
228
+ -- alguém cogitar automação de navegador:
229
+ --
230
+ -- `official_api_available` existe caminho estruturado oficial?
231
+ -- `supported_operations` o que ele faz — listar, baixar, consultar evento?
232
+ --
233
+ -- Sem a primeira, "não achei API" e "não procurei" são o mesmo registro, e o
234
+ -- segundo é o que costuma virar um Playwright logando num portal de prefeitura.
235
+ ALTER TABLE fiscal_coverage ADD COLUMN IF NOT EXISTS official_api_available text NOT NULL DEFAULT 'unknown';
236
+ ALTER TABLE fiscal_coverage ADD COLUMN IF NOT EXISTS supported_operations text[] NOT NULL DEFAULT '{}';
237
+
238
+ DO $$
239
+ BEGIN
240
+ IF NOT EXISTS (SELECT FROM pg_constraint WHERE conname = 'fiscal_coverage_official_api_values') THEN
241
+ ALTER TABLE fiscal_coverage ADD CONSTRAINT fiscal_coverage_official_api_values
242
+ CHECK (official_api_available IN ('yes', 'no', 'unknown'));
243
+ END IF;
244
+ END $$;
245
+
246
+ COMMENT ON COLUMN fiscal_coverage.official_api_available IS
247
+ '"no" é uma medição; "unknown" é a ausência dela. Só a primeira autoriza sequer discutir fallback por navegador.';
248
+
249
+ -- ═══ Backfill: as linhas que já afirmavam cobertura ══════════════════════
250
+ --
251
+ -- A 0058 criou quatro linhas `SUPPORTED_OFFICIAL` — NF-e, CT-e e NFS-e nacional
252
+ -- —, todas com a norma citada em `source_reference`. Com a coluna nova nascendo
253
+ -- `unknown`, elas passariam a AFIRMAR cobertura sem ter respondido "existe
254
+ -- caminho oficial?", que é justamente a pergunta que a coluna existe para fazer.
255
+ --
256
+ -- Uma coluna nova sem backfill deixa de fora exatamente as linhas antigas — as
257
+ -- que já estavam em uso. O mesmo raciocínio do `exposure` acima.
258
+ UPDATE fiscal_coverage SET
259
+ official_api_available = 'yes',
260
+ supported_operations = CASE
261
+ WHEN automation_method = 'dfe_nsu_soap' THEN ARRAY['dist_nsu', 'cons_nsu', 'cons_chave']
262
+ WHEN automation_method = 'dfe_nsu_rest' THEN ARRAY['list_by_nsu', 'fetch_document', 'fetch_events']
263
+ ELSE supported_operations
264
+ END
265
+ WHERE coverage_status LIKE 'SUPPORTED%'
266
+ AND official_api_available = 'unknown'
267
+ AND automation_method IN ('dfe_nsu_soap', 'dfe_nsu_rest');
268
+
269
+ -- As linhas dos três municípios do piloto. `SUPPORTED_OFFICIAL` aqui significa
270
+ -- o que já significava na linha nacional do ADN: existe caminho oficial
271
+ -- documentado — e não "já funcionou". Quem responde "já funcionou" é
272
+ -- `digital_certificate.verified_at` somado a um `fiscal_sync_run` completo, e
273
+ -- ambos continuam vazios.
274
+ INSERT INTO fiscal_coverage (
275
+ id, document_type, jurisdiction_scope, jurisdiction_code, provider, coverage_status,
276
+ automation_method, credential_requirement, environments, fallback_type,
277
+ official_api_available, supported_operations,
278
+ last_verified_at, source_reference, notes
279
+ ) VALUES
280
+ (gen_random_uuid(), 'nfse', 'municipal', '5208707', 'nfse_adn', 'SUPPORTED_OFFICIAL',
281
+ 'dfe_nsu_rest', 'icp_brasil_mtls', ARRAY['homologation', 'production'], 'manual_upload',
282
+ 'yes', ARRAY['list_by_nsu', 'fetch_document', 'fetch_events'],
283
+ DATE '2026-09-08',
284
+ 'Prefeitura de Goiânia — comunicado oficial de ajuste da NFS-e à Reforma Tributária (vigência 01/01/2026); LC 214/2025 art. 62; Manual dos Contribuintes — APIs do ADN v1.0',
285
+ 'Goiânia MANTÉM emissor próprio (Sistema ISS Goiânia, provedor ISSNet) e o compartilhamento com o ADN é feito pela própria administração tributária. Logo o caminho de COLETA é o ADN nacional, por certificado — não o portal municipal. Nenhuma chamada autenticada foi feita a partir deste produto.'),
286
+
287
+ (gen_random_uuid(), 'nfse', 'municipal', '5201405', 'nfse_adn', 'SUPPORTED_OFFICIAL',
288
+ 'dfe_nsu_rest', 'icp_brasil_mtls', ARRAY['homologation', 'production'], 'manual_upload',
289
+ 'yes', ARRAY['list_by_nsu', 'fetch_document', 'fetch_events'],
290
+ DATE '2026-09-08',
291
+ 'Portal oficial da Prefeitura de Aparecida de Goiânia (sistema NFS-e em issnetonline.com.br/aparecida); LC 214/2025 art. 62; Manual dos Contribuintes — APIs do ADN v1.0',
292
+ 'Emissor municipal é ISSNet (mesmo provedor de Goiânia e Anápolis). Nenhuma API pública do ISSNet foi verificada; a via oficial de coleta é o ADN. Adesão específica do município ao compartilhamento NÃO foi confirmada em fonte municipal — apenas pela obrigação legal geral.'),
293
+
294
+ (gen_random_uuid(), 'nfse', 'municipal', '5201108', 'nfse_adn', 'SUPPORTED_OFFICIAL',
295
+ 'dfe_nsu_rest', 'icp_brasil_mtls', ARRAY['homologation', 'production'], 'manual_upload',
296
+ 'yes', ARRAY['list_by_nsu', 'fetch_document', 'fetch_events'],
297
+ DATE '2026-09-08',
298
+ 'Prefeitura de Anápolis — Decreto nº 52.525 (03/03/2026, efeitos retroativos a 01/01/2026); LC 214/2025 art. 62; Manual dos Contribuintes — APIs do ADN v1.0',
299
+ 'Anápolis mantém emissor municipal próprio (ISS.NET) com webservice local; o comunicado NÃO menciona o ADN. Simples Nacional migra ao emissor nacional a partir de 01/11/2026. A coleta pelo ADN depende do compartilhamento obrigatório, que este produto não verificou.'),
300
+
301
+ -- Estadual GO. A pergunta humana era "notas de entrada e saída"; a resposta
302
+ -- oficial separa as duas, e é essa separação que decide se um conector novo é
303
+ -- necessário — ou se o que falta não tem conector nenhum.
304
+ (gen_random_uuid(), 'nfe', 'state', 'GO', 'sefaz_dfe', 'SUPPORTED_OFFICIAL',
305
+ 'dfe_nsu_soap', 'icp_brasil_mtls', ARRAY['homologation', 'production'], 'issuer_export',
306
+ 'yes', ARRAY['dist_nsu', 'cons_nsu', 'cons_chave'],
307
+ DATE '2026-09-08',
308
+ 'Secretaria da Economia de Goiás — Documentos Fiscais (nfeweb.sefaz.go.gov.br, arquivo XML por certificado digital); NT 2014.002 v1.02c',
309
+ 'ENTRADAS: cobertas pela distribuição nacional NFeDistribuicaoDFe, autenticada por certificado ICP-Brasil — nenhuma credencial humana. SAÍDAS: o DF-e não distribui o que a própria empresa emitiu; a origem é o emissor do cliente. O login CPF/senha do portal da SEFAZ-GO NÃO é caminho de coleta estruturada e por isso não é modelado como credencial deste produto.')
310
+ ON CONFLICT DO NOTHING;
311
+
312
+ -- ═══ 5 · A capacidade ════════════════════════════════════════════════════
313
+ --
314
+ -- Chave NOVA, e não bump de `contract_version` — a versão do contrato é global
315
+ -- (0055) e subi-la para exigir schema novo obrigaria todo worker a reconhecer a
316
+ -- versão maior, inclusive os que nada têm a ver com cofre fiscal.
317
+ INSERT INTO platform_capability (key, label, contract_version, declared_by_migration) VALUES
318
+ ('office.fiscal-credential-vault.v1',
319
+ 'Cofre fiscal: certificado write-only, identidade conferida e autorização de execução única',
320
+ 1, '0060')
321
+ ON CONFLICT (key) DO NOTHING;