@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,259 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { uuidv7 } from '@baruk/shared';
3
+ import { encryptSecret } from '../../vault/crypto.js';
4
+ import { inspectPkcs12, matchIdentity } from './inspect.js';
5
+ /** Impressão digital truncada. 16 hex identificam sem virar um segundo identificador. */
6
+ function shortFingerprint(full) {
7
+ return full.slice(0, 16);
8
+ }
9
+ /**
10
+ * Nome determinístico da credencial no cofre.
11
+ *
12
+ * Determinístico porque a substituição precisa ENCONTRAR o que já existe: o
13
+ * índice único do cofre é `(tenant, cliente, lower(nome))`, e um nome novo a
14
+ * cada upload deixaria uma trilha de chaves privadas órfãs, todas ainda
15
+ * decifráveis, nenhuma referenciada.
16
+ */
17
+ export function fiscalCertificateCredentialName(clientId, certType) {
18
+ return `fiscal:certificate:${certType}:${clientId}`;
19
+ }
20
+ export async function onboardFiscalCertificate(tx, keyring, tenantId, input) {
21
+ const certType = input.certType ?? 'A1';
22
+ const clients = await tx `
23
+ SELECT id, document_number FROM client
24
+ WHERE id = ${input.clientId} AND deleted_at IS NULL
25
+ `;
26
+ const client = clients[0];
27
+ if (!client)
28
+ return { status: 'rejected', reason: 'client_not_found' };
29
+ const inspection = await inspectPkcs12(input.material, input.passphrase);
30
+ if (inspection.status === 'failed') {
31
+ return { status: 'rejected', reason: inspection.reason };
32
+ }
33
+ const facts = inspection.facts;
34
+ if (facts.expired)
35
+ return { status: 'rejected', reason: 'expired' };
36
+ const identity = matchIdentity(facts.document, client.document_number);
37
+ if (identity === 'mismatch' && !input.identityOverrideReason) {
38
+ return { status: 'rejected', reason: 'identity_mismatch', identityMatch: identity };
39
+ }
40
+ // O envelope é o MESMO que `material.ts` sabe abrir. Um segundo formato aqui
41
+ // significaria que o certificado que entra não é o que o transporte usa.
42
+ const envelope = JSON.stringify({
43
+ format: 'pkcs12',
44
+ material: input.material.toString('base64'),
45
+ ...(input.passphrase ? { passphrase: input.passphrase } : {}),
46
+ });
47
+ const credentialName = fiscalCertificateCredentialName(input.clientId, certType);
48
+ const credentialId = await upsertWriteOnlyCredential(tx, keyring, tenantId, {
49
+ name: credentialName,
50
+ clientId: input.clientId,
51
+ secret: envelope,
52
+ expiresAt: facts.validTo,
53
+ actorId: input.actorId,
54
+ metadata: {
55
+ // Metadata do cofre é lida por telas e por trilha: só o que é público no
56
+ // certificado entra aqui.
57
+ fingerprint: shortFingerprint(facts.fingerprintSha256),
58
+ certType,
59
+ subject: facts.subjectCommonName,
60
+ },
61
+ });
62
+ const expiresOn = facts.validTo.slice(0, 10);
63
+ const issuedAt = facts.validFrom.slice(0, 10);
64
+ const holderName = facts.subjectCommonName ?? 'Titular não identificado no certificado';
65
+ const existing = await tx `
66
+ SELECT id FROM digital_certificate
67
+ WHERE client_id = ${input.clientId} AND cert_type = ${certType} AND deleted_at IS NULL
68
+ ORDER BY created_at DESC LIMIT 1
69
+ `;
70
+ const override = identity === 'mismatch' ? (input.identityOverrideReason ?? null) : null;
71
+ const overrideBy = override ? input.actorId : null;
72
+ let rows;
73
+ if (existing[0]) {
74
+ rows = await tx `
75
+ UPDATE digital_certificate SET
76
+ holder_name = ${holderName},
77
+ holder_document = ${facts.document ?? client.document_number},
78
+ issued_at = ${issuedAt},
79
+ expires_at = ${expiresOn},
80
+ vault_credential_id = ${credentialId},
81
+ status = 'active',
82
+ fingerprint_sha256 = ${facts.fingerprintSha256},
83
+ subject_common_name = ${facts.subjectCommonName},
84
+ issuer_common_name = ${facts.issuerCommonName},
85
+ serial_tail = ${facts.serialTail},
86
+ certificate_document = ${facts.document},
87
+ identity_match = ${identity},
88
+ identity_override_by = ${overrideBy},
89
+ identity_override_reason = ${override},
90
+ uploaded_by = ${input.actorId},
91
+ uploaded_at = now(),
92
+ -- O material acabou de ser aberto pela inspeção — o mesmo OpenSSL do
93
+ -- handshake real. Isso é a validação LOCAL, e ela é fato deste instante:
94
+ -- senha correta, chave casando com o certificado, titular e datas
95
+ -- conferidos. Ver 1006.
96
+ local_validated_at = now(),
97
+ -- Material novo é uma afirmação nova: o que o ambiente oficial aceitou
98
+ -- foi o certificado ANTERIOR, e herdar a verificação diria que o novo
99
+ -- já funcionou. Ver 1004.
100
+ verified_at = NULL,
101
+ verified_by = NULL,
102
+ remote_verification_provider = NULL,
103
+ remote_verification_last_attempt_at = NULL,
104
+ last_verification_error = NULL,
105
+ updated_at = now()
106
+ WHERE id = ${existing[0].id}
107
+ RETURNING ${tx.unsafe(CERTIFICATE_COLUMNS)}
108
+ `;
109
+ }
110
+ else {
111
+ rows = await tx `
112
+ INSERT INTO digital_certificate (
113
+ id, tenant_id, client_id, cert_type, holder_name, holder_document,
114
+ issued_at, expires_at, vault_credential_id, status,
115
+ fingerprint_sha256, subject_common_name, issuer_common_name, serial_tail,
116
+ certificate_document, identity_match, identity_override_by, identity_override_reason,
117
+ uploaded_by, uploaded_at, local_validated_at
118
+ ) VALUES (
119
+ ${uuidv7()}, current_setting('app.current_tenant')::uuid, ${input.clientId},
120
+ ${certType}, ${holderName}, ${facts.document ?? client.document_number},
121
+ ${issuedAt}, ${expiresOn}, ${credentialId}, 'active',
122
+ ${facts.fingerprintSha256}, ${facts.subjectCommonName}, ${facts.issuerCommonName},
123
+ ${facts.serialTail}, ${facts.document}, ${identity}, ${overrideBy}, ${override},
124
+ -- As duas datas coincidem aqui, e é correto: o material só chega a
125
+ -- esta linha porque a inspeção passou.
126
+ ${input.actorId}, now(), now()
127
+ )
128
+ RETURNING ${tx.unsafe(CERTIFICATE_COLUMNS)}
129
+ `;
130
+ }
131
+ return {
132
+ status: 'stored',
133
+ certificate: mapCertificate(rows[0], { replaced: Boolean(existing[0]) }),
134
+ };
135
+ }
136
+ /**
137
+ * Grava (ou rotaciona) o segredo no cofre com `exposure = 'write_only'`.
138
+ *
139
+ * Não reutiliza `createCredential`/`rotateSecret` de `vault/repository.ts` por
140
+ * um motivo específico: aqueles não conhecem `exposure`, e um certificado
141
+ * gravado por eles nasceria `revealable` — a 0060 recusaria o INSERT, o que
142
+ * está correto, mas a mensagem de erro seria uma violação de CHECK em vez de
143
+ * uma decisão explícita. Aqui a classe do segredo é dita na escrita.
144
+ */
145
+ async function upsertWriteOnlyCredential(tx, keyring, tenantId, input) {
146
+ const enc = encryptSecret(keyring, tenantId, input.secret);
147
+ const existing = await tx `
148
+ SELECT id FROM vault_credential
149
+ WHERE client_id = ${input.clientId}
150
+ AND lower(name) = lower(${input.name})
151
+ AND deleted_at IS NULL
152
+ LIMIT 1
153
+ `;
154
+ if (existing[0]) {
155
+ await tx `
156
+ UPDATE vault_credential SET
157
+ ciphertext = ${enc.ciphertext}, iv = ${enc.iv}, auth_tag = ${enc.authTag},
158
+ key_version = ${enc.keyVersion}, rotated_at = now(), updated_at = now(),
159
+ updated_by = ${input.actorId}, version = version + 1,
160
+ expires_at = ${input.expiresAt},
161
+ metadata = ${tx.json(input.metadata)},
162
+ exposure = 'write_only'
163
+ WHERE id = ${existing[0].id}
164
+ `;
165
+ return existing[0].id;
166
+ }
167
+ const rows = await tx `
168
+ INSERT INTO vault_credential (
169
+ id, tenant_id, client_id, name, category, provider,
170
+ ciphertext, iv, auth_tag, key_version, metadata, expires_at, created_by, exposure
171
+ ) VALUES (
172
+ ${uuidv7()}, current_setting('app.current_tenant')::uuid, ${input.clientId},
173
+ ${input.name}, 'certificate', 'icp_brasil',
174
+ ${enc.ciphertext}, ${enc.iv}, ${enc.authTag}, ${enc.keyVersion},
175
+ ${tx.json(input.metadata)}, ${input.expiresAt}, ${input.actorId}, 'write_only'
176
+ )
177
+ RETURNING id
178
+ `;
179
+ return rows[0].id;
180
+ }
181
+ const CERTIFICATE_COLUMNS = `
182
+ id, client_id, cert_type, status, subject_common_name, issuer_common_name,
183
+ serial_tail, fingerprint_sha256, certificate_document, identity_match,
184
+ identity_override_reason, issued_at, expires_at, uploaded_at, verified_at,
185
+ local_validated_at, remote_verification_provider
186
+ `;
187
+ function mapCertificate(r, extra) {
188
+ return {
189
+ id: r.id,
190
+ clientId: r.client_id,
191
+ certType: r.cert_type === 'A3' ? 'A3' : 'A1',
192
+ status: r.status,
193
+ subjectCommonName: r.subject_common_name,
194
+ issuerCommonName: r.issuer_common_name,
195
+ serialTail: r.serial_tail,
196
+ fingerprint: shortFingerprint(r.fingerprint_sha256 ?? ''),
197
+ certificateDocument: r.certificate_document,
198
+ identityMatch: r.identity_match ?? 'indeterminate',
199
+ identityOverrideReason: r.identity_override_reason,
200
+ issuedAt: r.issued_at ? isoDate(r.issued_at) : null,
201
+ expiresOn: isoDate(r.expires_at),
202
+ uploadedAt: r.uploaded_at?.toISOString() ?? null,
203
+ verifiedAt: r.verified_at?.toISOString() ?? null,
204
+ replaced: extra.replaced,
205
+ };
206
+ }
207
+ function isoDate(d) {
208
+ return d.toISOString().slice(0, 10);
209
+ }
210
+ /** Os certificados de um cliente, só metadata. É o que a tela lê. */
211
+ export async function listFiscalCertificates(tx, clientId) {
212
+ const rows = await tx `
213
+ SELECT ${tx.unsafe(CERTIFICATE_COLUMNS)}
214
+ FROM digital_certificate
215
+ WHERE client_id = ${clientId} AND deleted_at IS NULL
216
+ ORDER BY expires_at DESC
217
+ `;
218
+ return rows.map((r) => mapCertificate(r, { replaced: false }));
219
+ }
220
+ /**
221
+ * Revoga o uso do certificado DENTRO deste produto.
222
+ *
223
+ * Não toca a ICP-Brasil, e é por isso que a função se chama assim. Revogar de
224
+ * verdade um certificado é um ato junto à Autoridade Certificadora que o emitiu;
225
+ * apagar a linha daqui apenas garante que este produto pare de apresentá-lo. A
226
+ * tela repete a distinção, porque um operador que acredite ter revogado o
227
+ * certificado do cliente não vai à AC — e o certificado segue válido no mundo.
228
+ *
229
+ * O material sai do cofre no mesmo movimento (`deleted_at`), de modo que a
230
+ * revogação não deixa chave privada decifrável para trás.
231
+ */
232
+ export async function revokeFiscalCertificate(tx, clientId, certificateId) {
233
+ const rows = await tx `
234
+ UPDATE digital_certificate SET status = 'revoked', deleted_at = now(), updated_at = now()
235
+ WHERE id = ${certificateId} AND client_id = ${clientId} AND deleted_at IS NULL
236
+ RETURNING vault_credential_id
237
+ `;
238
+ if (rows.length === 0)
239
+ return { revoked: false };
240
+ const pointer = rows[0]?.vault_credential_id;
241
+ if (pointer) {
242
+ await tx `
243
+ UPDATE vault_credential SET deleted_at = now()
244
+ WHERE id = ${pointer} AND deleted_at IS NULL
245
+ `;
246
+ }
247
+ return { revoked: true };
248
+ }
249
+ /**
250
+ * Impressão digital do material recebido, antes de qualquer decisão.
251
+ *
252
+ * Serve à trilha do upload recusado: registrar "tentaram subir algo" sem
253
+ * registrar o quê. É o mesmo cálculo de `material.ts`, e é seguro por
254
+ * construção — um hash não volta a ser um certificado.
255
+ */
256
+ export function materialFingerprint(material) {
257
+ return shortFingerprint(createHash('sha256').update(material).digest('hex'));
258
+ }
259
+ //# sourceMappingURL=onboarding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onboarding.js","sourceRoot":"","sources":["../../../src/fiscal/credentials/onboarding.ts"],"names":[],"mappings":"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"}
@@ -36,15 +36,39 @@ import type { FiscalCredentialKind, FiscalEnvironment } from '../eligibility.js'
36
36
  * `EXPIRED` venceu. Só a renovação resolve.
37
37
  * `INVALID` revogada, ou o ponteiro para o cofre não resolve.
38
38
  * `ACCESS_DENIED` o material serve; o ambiente recusou o TITULAR.
39
- * `REQUIRES_REVIEW` cadastrada e nunca exercida ou de um tipo que um cron
40
- * não consegue usar (A3 vive em token com PIN presencial).
39
+ * `LOCAL_VALID` o material abre, a chave casa, o titular confere e as
40
+ * datas estão na janela e NENHUM ambiente oficial o
41
+ * aceitou ainda. É o estado em que todo certificado novo
42
+ * nasce, e o único que o primeiro contato admite.
43
+ * `REQUIRES_REVIEW` cadastrada e sem validação local registrada — ou de um
44
+ * tipo que um cron não consegue usar (A3 vive em token com
45
+ * PIN presencial).
41
46
  */
42
- export type FiscalCredentialState = 'NOT_CONFIGURED' | 'READY' | 'EXPIRED' | 'INVALID' | 'ACCESS_DENIED' | 'REQUIRES_REVIEW';
47
+ export type FiscalCredentialState = 'NOT_CONFIGURED' | 'READY' | 'LOCAL_VALID' | 'EXPIRED' | 'INVALID' | 'ACCESS_DENIED' | 'REQUIRES_REVIEW';
43
48
  export interface FiscalCredentialReadiness {
44
49
  state: FiscalCredentialState;
45
50
  kind: FiscalCredentialKind | null;
46
51
  /** Só `READY` autoriza o worker a tentar. Nenhum outro estado, por motivo nenhum. */
47
52
  usableByWorker: boolean;
53
+ /**
54
+ * O material passou na verificação LOCAL: abriu com a senha, a chave casa com
55
+ * o certificado, o titular confere e as datas estão na janela.
56
+ *
57
+ * Independente de `usableByWorker` de propósito. Foi a fusão dos dois que
58
+ * produziu o impasse: exigir aceite remoto para executar, quando só executar
59
+ * produz o aceite.
60
+ */
61
+ localValid: boolean;
62
+ /**
63
+ * Esta credencial pode ser usada num PRIMEIRO CONTATO — a execução única,
64
+ * manual e autorizada que existe justamente para obter o aceite remoto.
65
+ *
66
+ * `true` exatamente quando o material é válido localmente e nenhum ambiente o
67
+ * aceitou ainda. Um cron jamais consulta este campo: ele existe para o
68
+ * executor de primeiro contato, e o banco recusa autorização de primeiro
69
+ * contato disparada por agendador.
70
+ */
71
+ eligibleForFirstContact: boolean;
48
72
  /**
49
73
  * `true` quando um retry futuro pode mudar o desfecho sem intervenção humana.
50
74
  * `EXPIRED`, `INVALID` e `ACCESS_DENIED` são `false` — e é isso que decide se
@@ -52,7 +76,10 @@ export interface FiscalCredentialReadiness {
52
76
  */
53
77
  selfHealing: boolean;
54
78
  expiresOn: string | null;
79
+ /** Quando o ambiente OFICIAL aceitou. Nunca sai do cadastro. */
55
80
  verifiedAt: string | null;
81
+ /** Quando o material passou na verificação local. */
82
+ localValidatedAt: string | null;
56
83
  /** sha256 truncado do material, quando registrado. Seguro para tela e trilha. */
57
84
  fingerprint: string | null;
58
85
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../../../src/fiscal/credentials/readiness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,MAAM,qBAAqB,GAC/B,gBAAgB,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,eAAe,GAAG,iBAAiB,CAAC;AAE3F,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,qBAAqB,CAAC;IAC7B,IAAI,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClC,qFAAqF;IACrF,cAAc,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iFAAiF;IACjF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;OAGG;IACH,qBAAqB,EAAE,SAAS,GAAG,aAAa,CAAC;IACjD,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AAoBD;;;;;;;;;;GAUG;AACH,wBAAsB,6BAA6B,CACjD,EAAE,EAAE,EAAE,EACN,KAAK,EAAE;IACL,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,iBAAiB,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,oBAAoB,CAAC;CAC7B,GACA,OAAO,CAAC,yBAAyB,CAAC,CAuHpC;AAWD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,qBAAqB,GAC3B,qBAAqB,GAAG,qBAAqB,GAAG,eAAe,GAAG,IAAI,CAWxE"}
1
+ {"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../../../src/fiscal/credentials/readiness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,qBAAqB,GAC7B,gBAAgB,GAChB,OAAO,GACP,aAAa,GACb,SAAS,GACT,SAAS,GACT,eAAe,GACf,iBAAiB,CAAC;AAEtB,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,qBAAqB,CAAC;IAC7B,IAAI,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClC,qFAAqF;IACrF,cAAc,EAAE,OAAO,CAAC;IACxB;;;;;;;OAOG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;;;;OAQG;IACH,uBAAuB,EAAE,OAAO,CAAC;IACjC;;;;OAIG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gEAAgE;IAChE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,qDAAqD;IACrD,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,iFAAiF;IACjF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;OAGG;IACH,qBAAqB,EAAE,SAAS,GAAG,aAAa,CAAC;IACjD,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AAsBD;;;;;;;;;;GAUG;AACH,wBAAsB,6BAA6B,CACjD,EAAE,EAAE,EAAE,EACN,KAAK,EAAE;IACL,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,iBAAiB,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,oBAAoB,CAAC;CAC7B,GACA,OAAO,CAAC,yBAAyB,CAAC,CAkJpC;AAWD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,qBAAqB,GAC3B,qBAAqB,GAAG,qBAAqB,GAAG,eAAe,GAAG,IAAI,CAWxE"}
@@ -21,9 +21,12 @@ export async function readFiscalCredentialReadiness(tx, input) {
21
21
  const base = {
22
22
  kind,
23
23
  usableByWorker: false,
24
+ localValid: false,
25
+ eligibleForFirstContact: false,
24
26
  selfHealing: false,
25
27
  expiresOn: null,
26
28
  verifiedAt: null,
29
+ localValidatedAt: null,
27
30
  fingerprint: null,
28
31
  providerAuthorization: 'unknown',
29
32
  };
@@ -40,6 +43,7 @@ export async function readFiscalCredentialReadiness(tx, input) {
40
43
  try {
41
44
  rows = await tx `
42
45
  SELECT id, cert_type, status, expires_at, verified_at,
46
+ local_validated_at, fingerprint_sha256,
43
47
  last_verification_error, vault_credential_id
44
48
  FROM digital_certificate
45
49
  WHERE client_id = ${input.clientId}
@@ -62,7 +66,14 @@ export async function readFiscalCredentialReadiness(tx, input) {
62
66
  }
63
67
  const expiresOn = cert.expires_at ? isoDate(cert.expires_at) : null;
64
68
  const verifiedAt = cert.verified_at?.toISOString() ?? null;
65
- const withDates = { ...base, expiresOn, verifiedAt };
69
+ const localValidatedAt = cert.local_validated_at?.toISOString() ?? null;
70
+ const withDates = {
71
+ ...base,
72
+ expiresOn,
73
+ verifiedAt,
74
+ localValidatedAt,
75
+ fingerprint: cert.fingerprint_sha256 ?? null,
76
+ };
66
77
  if (cert.status === 'revoked') {
67
78
  return { ...withDates, state: 'INVALID', detail: 'certificado revogado' };
68
79
  }
@@ -101,10 +112,23 @@ export async function readFiscalCredentialReadiness(tx, input) {
101
112
  };
102
113
  }
103
114
  if (!cert.verified_at) {
115
+ // Aqui estava o impasse. Antes, tudo que não fora aceito remotamente caía
116
+ // num balde só, e o balde reprovava a execução — inclusive a execução que
117
+ // produziria o aceite. Agora o material conferido localmente tem estado
118
+ // próprio, e é ele que o primeiro contato aceita.
119
+ if (cert.local_validated_at) {
120
+ return {
121
+ ...withDates,
122
+ state: 'LOCAL_VALID',
123
+ localValid: true,
124
+ eligibleForFirstContact: true,
125
+ detail: 'válida localmente; nenhum ambiente oficial a aceitou ainda',
126
+ };
127
+ }
104
128
  return {
105
129
  ...withDates,
106
130
  state: 'REQUIRES_REVIEW',
107
- detail: 'cadastrado e nunca exercido contra o ambiente oficial',
131
+ detail: 'cadastrada sem validação local registrada',
108
132
  };
109
133
  }
110
134
  // A autorização do provedor sai de execução OBSERVADA. Um `SELECT` sobre a
@@ -122,6 +146,9 @@ export async function readFiscalCredentialReadiness(tx, input) {
122
146
  ...withDates,
123
147
  state: 'READY',
124
148
  usableByWorker: true,
149
+ localValid: true,
150
+ // Já aceita remotamente: não há primeiro contato a fazer.
151
+ eligibleForFirstContact: false,
125
152
  selfHealing: true,
126
153
  providerAuthorization: established ? 'established' : 'unknown',
127
154
  detail: established
@@ -1 +1 @@
1
- {"version":3,"file":"readiness.js","sourceRoot":"","sources":["../../../src/fiscal/credentials/readiness.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"readiness.js","sourceRoot":"","sources":["../../../src/fiscal/credentials/readiness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ import type { Tx } from '../db/client.js';
2
+ import { type FiscalDocumentType, type FiscalEnvironment, type TenantFiscalAutomation } from './eligibility.js';
3
+ /**
4
+ * Quem entra na automação fiscal — o escritório, e depois cada cliente.
5
+ *
6
+ * ## Duas chaves, e por que não bastava uma
7
+ *
8
+ * `tenant_fiscal_automation` diz em QUE AMBIENTE o escritório declarou operar.
9
+ * É a chave-geral: enquanto ela estiver desligada, nenhum cliente é consultado,
10
+ * por mais matriculado que esteja.
11
+ *
12
+ * `client_fiscal_enrollment` diz QUAIS clientes, e para QUAIS documentos. É a
13
+ * chave por porta: um cliente pode ter NFS-e habilitada e NF-e não, porque os
14
+ * dois conectores têm maturidades diferentes e o escritório pode querer
15
+ * começar por um.
16
+ *
17
+ * As duas tabelas existem desde a 0058 e nunca tiveram escritor. Este módulo é
18
+ * o escritor — e o motivo de ele existir agora é que, sem ele, habilitar
19
+ * automação fiscal exigia um `INSERT` manual em produção.
20
+ *
21
+ * ## Nada aqui liga nada sozinho
22
+ *
23
+ * Um certificado cadastrado NÃO habilita ninguém. São atos distintos, com
24
+ * autores distintos e trilhas distintas: enviar material é uma coisa, declarar
25
+ * que o escritório opera automação fiscal em produção é outra, e dizer que
26
+ * ESTE cliente entra é uma terceira.
27
+ */
28
+ /**
29
+ * O modo declarado pelo escritório.
30
+ *
31
+ * Deriva de `TenantFiscalAutomation` (eligibility.ts) em vez de repetir a lista:
32
+ * duas cópias do mesmo vocabulário divergem, e a divergência decide quem entra
33
+ * na automação.
34
+ */
35
+ export type FiscalAutomationMode = TenantFiscalAutomation['mode'];
36
+ export interface ClientFiscalEnrollment {
37
+ documentType: FiscalDocumentType;
38
+ enrolledAt: string;
39
+ enrolledBy: string | null;
40
+ revokedAt: string | null;
41
+ note: string | null;
42
+ /** Vale agora? Uma matrícula revogada continua no registro, e deve. */
43
+ active: boolean;
44
+ }
45
+ /** O estado das duas chaves, para a tela e para o portão. */
46
+ export interface FiscalEnrollmentView {
47
+ automation: TenantFiscalAutomation;
48
+ enrollments: readonly ClientFiscalEnrollment[];
49
+ }
50
+ /**
51
+ * Declara em que ambiente o escritório opera automação fiscal.
52
+ *
53
+ * `production` é a fronteira entre ensaio e operação real: a partir dela, o
54
+ * material de um contribuinte pode ser apresentado a um servidor da Receita. Por
55
+ * isso o ato é auditado com o modo anterior e o novo — "passou a ser produção"
56
+ * não responde a pergunta que se faz meses depois.
57
+ */
58
+ export declare function setTenantFiscalAutomation(tx: Tx, input: {
59
+ mode: FiscalAutomationMode;
60
+ actorId: string;
61
+ note?: string | null;
62
+ }): Promise<TenantFiscalAutomation>;
63
+ export declare function listClientFiscalEnrollments(tx: Tx, clientId: string): Promise<ClientFiscalEnrollment[]>;
64
+ /**
65
+ * Matricula um cliente para UM tipo de documento.
66
+ *
67
+ * A granularidade é o ponto: NFS-e e NF-e são conectores diferentes, com
68
+ * maturidades diferentes. Uma matrícula que valesse "para tudo" habilitaria o
69
+ * cliente num conector que talvez nem exista.
70
+ *
71
+ * Rematricular um cliente revogado o reativa — e o registro guarda as duas
72
+ * datas, porque quem revogou tinha um motivo e ele não deve sumir.
73
+ */
74
+ export declare function enrollClientFiscal(tx: Tx, input: {
75
+ clientId: string;
76
+ documentType: FiscalDocumentType;
77
+ actorId: string;
78
+ note?: string | null;
79
+ }): Promise<ClientFiscalEnrollment[]>;
80
+ /** Tira o cliente da automação. Não apaga o histórico. */
81
+ export declare function revokeClientFiscalEnrollment(tx: Tx, input: {
82
+ clientId: string;
83
+ documentType: FiscalDocumentType;
84
+ actorId: string;
85
+ reason?: string | null;
86
+ }): Promise<ClientFiscalEnrollment[]>;
87
+ /** As duas chaves numa leitura só, para a tela não fazer duas viagens. */
88
+ export declare function readFiscalEnrollmentView(tx: Tx, clientId: string): Promise<FiscalEnrollmentView>;
89
+ /** O ambiente declarado casa com o pedido? Chave-geral do portão. */
90
+ export declare function automationCovers(automation: TenantFiscalAutomation, environment: FiscalEnvironment): boolean;
91
+ //# sourceMappingURL=enrollment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enrollment.d.ts","sourceRoot":"","sources":["../../src/fiscal/enrollment.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAElE,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,kBAAkB,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,uEAAuE;IACvE,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,6DAA6D;AAC7D,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,sBAAsB,CAAC;IACnC,WAAW,EAAE,SAAS,sBAAsB,EAAE,CAAC;CAChD;AAED;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,EAAE,EACN,KAAK,EAAE;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC3E,OAAO,CAAC,sBAAsB,CAAC,CA4BjC;AAED,wBAAsB,2BAA2B,CAC/C,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAuBnC;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,EAAE,EACN,KAAK,EAAE;IACL,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,GACA,OAAO,CAAC,sBAAsB,EAAE,CAAC,CA6BnC;AAED,0DAA0D;AAC1D,wBAAsB,4BAA4B,CAChD,EAAE,EAAE,EAAE,EACN,KAAK,EAAE;IACL,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,GACA,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAuBnC;AAED,0EAA0E;AAC1E,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,CAAC,CAK/B;AAED,qEAAqE;AACrE,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,sBAAsB,EAClC,WAAW,EAAE,iBAAiB,GAC7B,OAAO,CAET"}
@@ -0,0 +1,126 @@
1
+ import { uuidv7 } from '@baruk/shared';
2
+ import { appendAudit } from '../audit/audit.js';
3
+ import { getTenantFiscalAutomation, } from './eligibility.js';
4
+ /**
5
+ * Declara em que ambiente o escritório opera automação fiscal.
6
+ *
7
+ * `production` é a fronteira entre ensaio e operação real: a partir dela, o
8
+ * material de um contribuinte pode ser apresentado a um servidor da Receita. Por
9
+ * isso o ato é auditado com o modo anterior e o novo — "passou a ser produção"
10
+ * não responde a pergunta que se faz meses depois.
11
+ */
12
+ export async function setTenantFiscalAutomation(tx, input) {
13
+ const anterior = await getTenantFiscalAutomation(tx);
14
+ const ligando = input.mode !== 'disabled';
15
+ await tx `
16
+ INSERT INTO tenant_fiscal_automation (tenant_id, mode, enabled_at, enabled_by, note, updated_at)
17
+ VALUES (
18
+ current_setting('app.current_tenant')::uuid, ${input.mode},
19
+ ${ligando ? new Date() : null}, ${ligando ? input.actorId : null},
20
+ ${input.note ?? null}, now()
21
+ )
22
+ ON CONFLICT (tenant_id) DO UPDATE SET
23
+ mode = EXCLUDED.mode,
24
+ enabled_at = EXCLUDED.enabled_at,
25
+ enabled_by = EXCLUDED.enabled_by,
26
+ note = EXCLUDED.note,
27
+ updated_at = now()
28
+ `;
29
+ await appendAudit(tx, {
30
+ actorId: input.actorId,
31
+ action: 'fiscal_automation_mode_changed',
32
+ resourceType: 'tenant_fiscal_automation',
33
+ resourceId: null,
34
+ metadata: { from: anterior.mode, to: input.mode, note: input.note ?? null },
35
+ });
36
+ return getTenantFiscalAutomation(tx);
37
+ }
38
+ export async function listClientFiscalEnrollments(tx, clientId) {
39
+ const rows = await tx `
40
+ SELECT document_type, enrolled_at, enrolled_by, revoked_at, note
41
+ FROM client_fiscal_enrollment
42
+ WHERE client_id = ${clientId}
43
+ ORDER BY document_type, enrolled_at DESC
44
+ `;
45
+ return rows.map((r) => ({
46
+ documentType: r.document_type,
47
+ enrolledAt: r.enrolled_at.toISOString(),
48
+ enrolledBy: r.enrolled_by,
49
+ revokedAt: r.revoked_at?.toISOString() ?? null,
50
+ note: r.note,
51
+ active: r.revoked_at === null,
52
+ }));
53
+ }
54
+ /**
55
+ * Matricula um cliente para UM tipo de documento.
56
+ *
57
+ * A granularidade é o ponto: NFS-e e NF-e são conectores diferentes, com
58
+ * maturidades diferentes. Uma matrícula que valesse "para tudo" habilitaria o
59
+ * cliente num conector que talvez nem exista.
60
+ *
61
+ * Rematricular um cliente revogado o reativa — e o registro guarda as duas
62
+ * datas, porque quem revogou tinha um motivo e ele não deve sumir.
63
+ */
64
+ export async function enrollClientFiscal(tx, input) {
65
+ await tx `
66
+ INSERT INTO client_fiscal_enrollment (
67
+ id, tenant_id, client_id, document_type, enrolled_at, enrolled_by, note
68
+ ) VALUES (
69
+ ${uuidv7()}, current_setting('app.current_tenant')::uuid, ${input.clientId},
70
+ ${input.documentType}, now(), ${input.actorId}, ${input.note ?? null}
71
+ )
72
+ -- O índice único é PARCIAL (WHERE revoked_at IS NULL), e o ON CONFLICT
73
+ -- precisa repetir o predicado: sem ele o Postgres não sabe qual índice usar
74
+ -- e a instrução falha.
75
+ --
76
+ -- E a consequência do índice ser parcial é desejável: rematricular um
77
+ -- cliente que foi revogado cria uma linha NOVA em vez de reescrever a
78
+ -- antiga. Quem revogou tinha um motivo, e ele fica no registro.
79
+ ON CONFLICT (tenant_id, client_id, document_type) WHERE revoked_at IS NULL
80
+ DO UPDATE SET
81
+ enrolled_at = now(), enrolled_by = ${input.actorId}, note = ${input.note ?? null}
82
+ `;
83
+ await appendAudit(tx, {
84
+ actorId: input.actorId,
85
+ action: 'fiscal_enrollment_created',
86
+ resourceType: 'client_fiscal_enrollment',
87
+ resourceId: null,
88
+ metadata: { clientId: input.clientId, documentType: input.documentType },
89
+ });
90
+ return listClientFiscalEnrollments(tx, input.clientId);
91
+ }
92
+ /** Tira o cliente da automação. Não apaga o histórico. */
93
+ export async function revokeClientFiscalEnrollment(tx, input) {
94
+ await tx `
95
+ UPDATE client_fiscal_enrollment
96
+ SET revoked_at = now(), revoked_by = ${input.actorId},
97
+ note = COALESCE(${input.reason ?? null}, note)
98
+ WHERE client_id = ${input.clientId}
99
+ AND document_type = ${input.documentType}
100
+ AND revoked_at IS NULL
101
+ `;
102
+ await appendAudit(tx, {
103
+ actorId: input.actorId,
104
+ action: 'fiscal_enrollment_revoked',
105
+ resourceType: 'client_fiscal_enrollment',
106
+ resourceId: null,
107
+ metadata: {
108
+ clientId: input.clientId,
109
+ documentType: input.documentType,
110
+ reason: input.reason ?? null,
111
+ },
112
+ });
113
+ return listClientFiscalEnrollments(tx, input.clientId);
114
+ }
115
+ /** As duas chaves numa leitura só, para a tela não fazer duas viagens. */
116
+ export async function readFiscalEnrollmentView(tx, clientId) {
117
+ return {
118
+ automation: await getTenantFiscalAutomation(tx),
119
+ enrollments: await listClientFiscalEnrollments(tx, clientId),
120
+ };
121
+ }
122
+ /** O ambiente declarado casa com o pedido? Chave-geral do portão. */
123
+ export function automationCovers(automation, environment) {
124
+ return automation.mode !== 'disabled' && automation.mode === environment;
125
+ }
126
+ //# sourceMappingURL=enrollment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enrollment.js","sourceRoot":"","sources":["../../src/fiscal/enrollment.ts"],"names":[],"mappings":"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"}