@baruk/core 0.3.0 → 0.4.2
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.
- package/.baruk-provenance.json +10 -0
- package/CHANGELOG.md +97 -0
- package/README.md +9 -2
- package/dist/auth/password-reset.d.ts +30 -1
- package/dist/auth/password-reset.d.ts.map +1 -1
- package/dist/auth/password-reset.js +139 -11
- package/dist/auth/password-reset.js.map +1 -1
- package/dist/auth/service.d.ts +39 -2
- package/dist/auth/service.d.ts.map +1 -1
- package/dist/auth/service.js +75 -10
- package/dist/auth/service.js.map +1 -1
- package/dist/billing/ar-repository.d.ts +334 -0
- package/dist/billing/ar-repository.d.ts.map +1 -0
- package/dist/billing/ar-repository.js +707 -0
- package/dist/billing/ar-repository.js.map +1 -0
- package/dist/billing/auto-generation.d.ts +87 -0
- package/dist/billing/auto-generation.d.ts.map +1 -0
- package/dist/billing/auto-generation.js +181 -0
- package/dist/billing/auto-generation.js.map +1 -0
- package/dist/billing/live-guard.d.ts +64 -0
- package/dist/billing/live-guard.d.ts.map +1 -0
- package/dist/billing/live-guard.js +68 -0
- package/dist/billing/live-guard.js.map +1 -0
- package/dist/billing/payment-means-catalog.d.ts +17 -0
- package/dist/billing/payment-means-catalog.d.ts.map +1 -0
- package/dist/billing/payment-means-catalog.js +21 -0
- package/dist/billing/payment-means-catalog.js.map +1 -0
- package/dist/billing/payment-means.d.ts +38 -0
- package/dist/billing/payment-means.d.ts.map +1 -0
- package/dist/billing/payment-means.js +29 -0
- package/dist/billing/payment-means.js.map +1 -0
- package/dist/billing/payment-rail.d.ts +118 -0
- package/dist/billing/payment-rail.d.ts.map +1 -0
- package/dist/billing/payment-rail.js +295 -0
- package/dist/billing/payment-rail.js.map +1 -0
- package/dist/billing/provider/asaas.d.ts +46 -1
- package/dist/billing/provider/asaas.d.ts.map +1 -1
- package/dist/billing/provider/asaas.js +194 -14
- package/dist/billing/provider/asaas.js.map +1 -1
- package/dist/billing/provider/manual.js +1 -1
- package/dist/billing/provider/manual.js.map +1 -1
- package/dist/billing/provider/types.d.ts +115 -8
- package/dist/billing/provider/types.d.ts.map +1 -1
- package/dist/billing/provider-account.d.ts +89 -0
- package/dist/billing/provider-account.d.ts.map +1 -0
- package/dist/billing/provider-account.js +161 -0
- package/dist/billing/provider-account.js.map +1 -0
- package/dist/billing/provider-onboarding.d.ts +67 -0
- package/dist/billing/provider-onboarding.d.ts.map +1 -0
- package/dist/billing/provider-onboarding.js +234 -0
- package/dist/billing/provider-onboarding.js.map +1 -0
- package/dist/billing/receivable.d.ts +5 -1
- package/dist/billing/receivable.d.ts.map +1 -1
- package/dist/billing/reconcile.d.ts +58 -0
- package/dist/billing/reconcile.d.ts.map +1 -0
- package/dist/billing/reconcile.js +138 -0
- package/dist/billing/reconcile.js.map +1 -0
- package/dist/billing/repository.d.ts +90 -1
- package/dist/billing/repository.d.ts.map +1 -1
- package/dist/billing/repository.js +148 -19
- package/dist/billing/repository.js.map +1 -1
- package/dist/cjs/auth/password-reset.js +141 -11
- package/dist/cjs/auth/password-reset.js.map +1 -1
- package/dist/cjs/auth/service.js +75 -9
- package/dist/cjs/auth/service.js.map +1 -1
- package/dist/cjs/billing/ar-repository.js +728 -0
- package/dist/cjs/billing/ar-repository.js.map +1 -0
- package/dist/cjs/billing/auto-generation.js +186 -0
- package/dist/cjs/billing/auto-generation.js.map +1 -0
- package/dist/cjs/billing/live-guard.js +72 -0
- package/dist/cjs/billing/live-guard.js.map +1 -0
- package/dist/cjs/billing/payment-means-catalog.js +25 -0
- package/dist/cjs/billing/payment-means-catalog.js.map +1 -0
- package/dist/cjs/billing/payment-means.js +33 -0
- package/dist/cjs/billing/payment-means.js.map +1 -0
- package/dist/cjs/billing/payment-rail.js +301 -0
- package/dist/cjs/billing/payment-rail.js.map +1 -0
- package/dist/cjs/billing/provider/asaas.js +194 -14
- package/dist/cjs/billing/provider/asaas.js.map +1 -1
- package/dist/cjs/billing/provider/manual.js +1 -1
- package/dist/cjs/billing/provider/manual.js.map +1 -1
- package/dist/cjs/billing/provider-account.js +170 -0
- package/dist/cjs/billing/provider-account.js.map +1 -0
- package/dist/cjs/billing/provider-onboarding.js +239 -0
- package/dist/cjs/billing/provider-onboarding.js.map +1 -0
- package/dist/cjs/billing/reconcile.js +143 -0
- package/dist/cjs/billing/reconcile.js.map +1 -0
- package/dist/cjs/billing/repository.js +150 -18
- package/dist/cjs/billing/repository.js.map +1 -1
- package/dist/cjs/clients/repository.js +96 -20
- package/dist/cjs/clients/repository.js.map +1 -1
- package/dist/cjs/collection/branding.js +140 -0
- package/dist/cjs/collection/branding.js.map +1 -0
- package/dist/cjs/collection/channel-accounts.js +137 -0
- package/dist/cjs/collection/channel-accounts.js.map +1 -0
- package/dist/cjs/collection/channel.js +17 -0
- package/dist/cjs/collection/channel.js.map +1 -0
- package/dist/cjs/collection/communication-bridge.js +140 -0
- package/dist/cjs/collection/communication-bridge.js.map +1 -0
- package/dist/cjs/collection/communication-runtime.js +193 -0
- package/dist/cjs/collection/communication-runtime.js.map +1 -0
- package/dist/cjs/collection/consent-repository.js +84 -0
- package/dist/cjs/collection/consent-repository.js.map +1 -0
- package/dist/cjs/collection/copy.js +125 -0
- package/dist/cjs/collection/copy.js.map +1 -0
- package/dist/cjs/collection/delivery-callback.js +151 -0
- package/dist/cjs/collection/delivery-callback.js.map +1 -0
- package/dist/cjs/collection/email-layout.js +252 -0
- package/dist/cjs/collection/email-layout.js.map +1 -0
- package/dist/cjs/collection/engine.js +414 -0
- package/dist/cjs/collection/engine.js.map +1 -0
- package/dist/cjs/collection/format.js +76 -0
- package/dist/cjs/collection/format.js.map +1 -0
- package/dist/cjs/collection/payment-block.js +3 -0
- package/dist/cjs/collection/payment-block.js.map +1 -0
- package/dist/cjs/collection/policy.js +147 -0
- package/dist/cjs/collection/policy.js.map +1 -0
- package/dist/cjs/collection/repository.js +260 -0
- package/dist/cjs/collection/repository.js.map +1 -0
- package/dist/cjs/collection/subject.js +104 -0
- package/dist/cjs/collection/subject.js.map +1 -0
- package/dist/cjs/collection/template-catalog.js +165 -0
- package/dist/cjs/collection/template-catalog.js.map +1 -0
- package/dist/cjs/collection/template.js +78 -0
- package/dist/cjs/collection/template.js.map +1 -0
- package/dist/cjs/communication/adapters/email.js +132 -0
- package/dist/cjs/communication/adapters/email.js.map +1 -0
- package/dist/cjs/communication/adapters/sms.js +127 -0
- package/dist/cjs/communication/adapters/sms.js.map +1 -0
- package/dist/cjs/communication/adapters/whatsapp-language.js +39 -0
- package/dist/cjs/communication/adapters/whatsapp-language.js.map +1 -0
- package/dist/cjs/communication/adapters/whatsapp-templates.js +100 -0
- package/dist/cjs/communication/adapters/whatsapp-templates.js.map +1 -0
- package/dist/cjs/communication/adapters/whatsapp.js +213 -0
- package/dist/cjs/communication/adapters/whatsapp.js.map +1 -0
- package/dist/cjs/communication/audit.js +61 -0
- package/dist/cjs/communication/audit.js.map +1 -0
- package/dist/cjs/communication/capabilities.js +76 -0
- package/dist/cjs/communication/capabilities.js.map +1 -0
- package/dist/cjs/communication/channel.js +53 -0
- package/dist/cjs/communication/channel.js.map +1 -0
- package/dist/cjs/communication/claim.js +228 -0
- package/dist/cjs/communication/claim.js.map +1 -0
- package/dist/cjs/communication/consent.js +43 -0
- package/dist/cjs/communication/consent.js.map +1 -0
- package/dist/cjs/communication/context.js +45 -0
- package/dist/cjs/communication/context.js.map +1 -0
- package/dist/cjs/communication/credentials.js +121 -0
- package/dist/cjs/communication/credentials.js.map +1 -0
- package/dist/cjs/communication/dry-run-scenario.js +161 -0
- package/dist/cjs/communication/dry-run-scenario.js.map +1 -0
- package/dist/cjs/communication/dry-run.js +97 -0
- package/dist/cjs/communication/dry-run.js.map +1 -0
- package/dist/cjs/communication/engine.js +483 -0
- package/dist/cjs/communication/engine.js.map +1 -0
- package/dist/cjs/communication/graph.js +50 -0
- package/dist/cjs/communication/graph.js.map +1 -0
- package/dist/cjs/communication/idempotency.js +26 -0
- package/dist/cjs/communication/idempotency.js.map +1 -0
- package/dist/cjs/communication/identity.js +110 -0
- package/dist/cjs/communication/identity.js.map +1 -0
- package/dist/cjs/communication/index.js +135 -0
- package/dist/cjs/communication/index.js.map +1 -0
- package/dist/cjs/communication/onboarding.js +297 -0
- package/dist/cjs/communication/onboarding.js.map +1 -0
- package/dist/cjs/communication/policy.js +16 -0
- package/dist/cjs/communication/policy.js.map +1 -0
- package/dist/cjs/communication/routing.js +77 -0
- package/dist/cjs/communication/routing.js.map +1 -0
- package/dist/cjs/communication/sender-profile.js +128 -0
- package/dist/cjs/communication/sender-profile.js.map +1 -0
- package/dist/cjs/communication/status.js +86 -0
- package/dist/cjs/communication/status.js.map +1 -0
- package/dist/cjs/communication/stores/onboarding.js +135 -0
- package/dist/cjs/communication/stores/onboarding.js.map +1 -0
- package/dist/cjs/communication/stores/postgres.js +458 -0
- package/dist/cjs/communication/stores/postgres.js.map +1 -0
- package/dist/cjs/communication/stores/routing.js +334 -0
- package/dist/cjs/communication/stores/routing.js.map +1 -0
- package/dist/cjs/communication/stores/secrets.js +126 -0
- package/dist/cjs/communication/stores/secrets.js.map +1 -0
- package/dist/cjs/communication/template-provider.js +22 -0
- package/dist/cjs/communication/template-provider.js.map +1 -0
- package/dist/cjs/communication/template.js +63 -0
- package/dist/cjs/communication/template.js.map +1 -0
- package/dist/cjs/communication/webhook-subscription.js +258 -0
- package/dist/cjs/communication/webhook-subscription.js.map +1 -0
- package/dist/cjs/communication/webhook.js +292 -0
- package/dist/cjs/communication/webhook.js.map +1 -0
- package/dist/cjs/competence/repository.js +309 -0
- package/dist/cjs/competence/repository.js.map +1 -0
- package/dist/cjs/contacts/phone.js +192 -0
- package/dist/cjs/contacts/phone.js.map +1 -0
- package/dist/cjs/db/seed-catalog.js +48 -0
- package/dist/cjs/db/seed-catalog.js.map +1 -0
- package/dist/cjs/db/seed.js +15 -7
- package/dist/cjs/db/seed.js.map +1 -1
- package/dist/cjs/documents/repository.js +244 -0
- package/dist/cjs/documents/repository.js.map +1 -0
- package/dist/cjs/documents/requests.js +136 -0
- package/dist/cjs/documents/requests.js.map +1 -0
- package/dist/cjs/index.js +427 -48
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/legal/company.js +36 -0
- package/dist/cjs/legal/company.js.map +1 -0
- package/dist/cjs/legal/documents.js +671 -0
- package/dist/cjs/legal/documents.js.map +1 -0
- package/dist/cjs/legal/index.js +15 -0
- package/dist/cjs/legal/index.js.map +1 -0
- package/dist/cjs/legal/types.js +34 -0
- package/dist/cjs/legal/types.js.map +1 -0
- package/dist/cjs/mail/auth-templates.js +29 -0
- package/dist/cjs/mail/auth-templates.js.map +1 -0
- package/dist/cjs/mail/dedup-store.js +12 -51
- package/dist/cjs/mail/dedup-store.js.map +1 -1
- package/dist/cjs/mail/index.js +16 -4
- package/dist/cjs/mail/index.js.map +1 -1
- package/dist/cjs/mail/provider.js +12 -247
- package/dist/cjs/mail/provider.js.map +1 -1
- package/dist/cjs/mail/resend.js +14 -18
- package/dist/cjs/mail/resend.js.map +1 -1
- package/dist/cjs/mail/sender-resolver.js +12 -11
- package/dist/cjs/mail/sender-resolver.js.map +1 -1
- package/dist/cjs/mail/template-renderer.js +11 -16
- package/dist/cjs/mail/template-renderer.js.map +1 -1
- package/dist/cjs/mail/transactional-outbound.js +117 -0
- package/dist/cjs/mail/transactional-outbound.js.map +1 -0
- package/dist/cjs/mail/types.js +8 -35
- package/dist/cjs/mail/types.js.map +1 -1
- package/dist/cjs/portal/provisioning.js +144 -0
- package/dist/cjs/portal/provisioning.js.map +1 -0
- package/dist/cjs/pricing-engine/canonical.js +42 -0
- package/dist/cjs/pricing-engine/canonical.js.map +1 -0
- package/dist/cjs/pricing-engine/errors.js +6 -1
- package/dist/cjs/pricing-engine/errors.js.map +1 -1
- package/dist/cjs/pricing-engine/index.js +12 -1
- package/dist/cjs/pricing-engine/index.js.map +1 -1
- package/dist/cjs/pricing-engine/pipeline.js +200 -26
- package/dist/cjs/pricing-engine/pipeline.js.map +1 -1
- package/dist/cjs/pricing-engine/shadow-adapter.js +79 -0
- package/dist/cjs/pricing-engine/shadow-adapter.js.map +1 -0
- package/dist/cjs/pricing-engine/shadow-diff.js +55 -0
- package/dist/cjs/pricing-engine/shadow-diff.js.map +1 -0
- package/dist/cjs/pricing-engine/shadow-facade.js +85 -0
- package/dist/cjs/pricing-engine/shadow-facade.js.map +1 -0
- package/dist/cjs/pricing-engine/shadow-legacy-types.js +19 -0
- package/dist/cjs/pricing-engine/shadow-legacy-types.js.map +1 -0
- package/dist/cjs/pricing-engine/shadow-metrics.js +34 -0
- package/dist/cjs/pricing-engine/shadow-metrics.js.map +1 -0
- package/dist/cjs/pricing-engine/shadow-normalizer.js +42 -0
- package/dist/cjs/pricing-engine/shadow-normalizer.js.map +1 -0
- package/dist/cjs/rbac/authorize.js +28 -0
- package/dist/cjs/rbac/authorize.js.map +1 -1
- package/dist/cjs/rbac/catalog.js +67 -1
- package/dist/cjs/rbac/catalog.js.map +1 -1
- package/dist/cjs/rbac/workload-provisioning.js +160 -0
- package/dist/cjs/rbac/workload-provisioning.js.map +1 -0
- package/dist/cjs/rbac/workload.js +191 -0
- package/dist/cjs/rbac/workload.js.map +1 -0
- package/dist/cjs/storage/checksum.js +15 -0
- package/dist/cjs/storage/checksum.js.map +1 -0
- package/dist/cjs/storage/key.js +72 -0
- package/dist/cjs/storage/key.js.map +1 -0
- package/dist/cjs/storage/local.js +100 -0
- package/dist/cjs/storage/local.js.map +1 -0
- package/dist/cjs/storage/r2.js +181 -0
- package/dist/cjs/storage/r2.js.map +1 -0
- package/dist/cjs/storage/registry.js +41 -0
- package/dist/cjs/storage/registry.js.map +1 -0
- package/dist/cjs/storage/types.js +50 -0
- package/dist/cjs/storage/types.js.map +1 -0
- package/dist/cjs/subscription-billing/provider/asaas.js +32 -7
- package/dist/cjs/subscription-billing/provider/asaas.js.map +1 -1
- package/dist/cjs/subscription-billing/provider/disabled.js +8 -0
- package/dist/cjs/subscription-billing/provider/disabled.js.map +1 -1
- package/dist/cjs/subscription-billing/provider/registry.js +17 -0
- package/dist/cjs/subscription-billing/provider/registry.js.map +1 -1
- package/dist/cjs/subscription-billing/service.js +57 -8
- package/dist/cjs/subscription-billing/service.js.map +1 -1
- package/dist/cjs/subscription-billing/types.js.map +1 -1
- package/dist/cjs/tenancy/provisioning.js +32 -8
- package/dist/cjs/tenancy/provisioning.js.map +1 -1
- package/dist/cjs/users/repository.js +119 -2
- package/dist/cjs/users/repository.js.map +1 -1
- package/dist/cjs/vault/repository.js +81 -0
- package/dist/cjs/vault/repository.js.map +1 -1
- package/dist/clients/repository.d.ts +42 -0
- package/dist/clients/repository.d.ts.map +1 -1
- package/dist/clients/repository.js +94 -21
- package/dist/clients/repository.js.map +1 -1
- package/dist/collection/branding.d.ts +72 -0
- package/dist/collection/branding.d.ts.map +1 -0
- package/dist/collection/branding.js +136 -0
- package/dist/collection/branding.js.map +1 -0
- package/dist/collection/channel-accounts.d.ts +57 -0
- package/dist/collection/channel-accounts.d.ts.map +1 -0
- package/dist/collection/channel-accounts.js +133 -0
- package/dist/collection/channel-accounts.js.map +1 -0
- package/dist/collection/channel.d.ts +24 -0
- package/dist/collection/channel.d.ts.map +1 -0
- package/dist/collection/channel.js +16 -0
- package/dist/collection/channel.js.map +1 -0
- package/dist/collection/communication-bridge.d.ts +66 -0
- package/dist/collection/communication-bridge.d.ts.map +1 -0
- package/dist/collection/communication-bridge.js +134 -0
- package/dist/collection/communication-bridge.js.map +1 -0
- package/dist/collection/communication-runtime.d.ts +95 -0
- package/dist/collection/communication-runtime.d.ts.map +1 -0
- package/dist/collection/communication-runtime.js +187 -0
- package/dist/collection/communication-runtime.js.map +1 -0
- package/dist/collection/consent-repository.d.ts +32 -0
- package/dist/collection/consent-repository.d.ts.map +1 -0
- package/dist/collection/consent-repository.js +79 -0
- package/dist/collection/consent-repository.js.map +1 -0
- package/dist/collection/copy.d.ts +50 -0
- package/dist/collection/copy.d.ts.map +1 -0
- package/dist/collection/copy.js +122 -0
- package/dist/collection/copy.js.map +1 -0
- package/dist/collection/delivery-callback.d.ts +49 -0
- package/dist/collection/delivery-callback.d.ts.map +1 -0
- package/dist/collection/delivery-callback.js +147 -0
- package/dist/collection/delivery-callback.js.map +1 -0
- package/dist/collection/email-layout.d.ts +45 -0
- package/dist/collection/email-layout.d.ts.map +1 -0
- package/dist/collection/email-layout.js +247 -0
- package/dist/collection/email-layout.js.map +1 -0
- package/dist/collection/engine.d.ts +177 -0
- package/dist/collection/engine.d.ts.map +1 -0
- package/dist/collection/engine.js +407 -0
- package/dist/collection/engine.js.map +1 -0
- package/dist/collection/format.d.ts +32 -0
- package/dist/collection/format.d.ts.map +1 -0
- package/dist/collection/format.js +70 -0
- package/dist/collection/format.js.map +1 -0
- package/dist/collection/payment-block.d.ts +24 -0
- package/dist/collection/payment-block.d.ts.map +1 -0
- package/dist/collection/payment-block.js +2 -0
- package/dist/collection/payment-block.js.map +1 -0
- package/dist/collection/policy.d.ts +53 -0
- package/dist/collection/policy.d.ts.map +1 -0
- package/dist/collection/policy.js +141 -0
- package/dist/collection/policy.js.map +1 -0
- package/dist/collection/repository.d.ts +129 -0
- package/dist/collection/repository.d.ts.map +1 -0
- package/dist/collection/repository.js +246 -0
- package/dist/collection/repository.js.map +1 -0
- package/dist/collection/subject.d.ts +66 -0
- package/dist/collection/subject.d.ts.map +1 -0
- package/dist/collection/subject.js +99 -0
- package/dist/collection/subject.js.map +1 -0
- package/dist/collection/template-catalog.d.ts +16 -0
- package/dist/collection/template-catalog.d.ts.map +1 -0
- package/dist/collection/template-catalog.js +162 -0
- package/dist/collection/template-catalog.js.map +1 -0
- package/dist/collection/template.d.ts +37 -0
- package/dist/collection/template.d.ts.map +1 -0
- package/dist/collection/template.js +73 -0
- package/dist/collection/template.js.map +1 -0
- package/dist/communication/adapters/email.d.ts +28 -0
- package/dist/communication/adapters/email.d.ts.map +1 -0
- package/dist/communication/adapters/email.js +129 -0
- package/dist/communication/adapters/email.js.map +1 -0
- package/dist/communication/adapters/sms.d.ts +95 -0
- package/dist/communication/adapters/sms.d.ts.map +1 -0
- package/dist/communication/adapters/sms.js +121 -0
- package/dist/communication/adapters/sms.js.map +1 -0
- package/dist/communication/adapters/whatsapp-language.d.ts +34 -0
- package/dist/communication/adapters/whatsapp-language.d.ts.map +1 -0
- package/dist/communication/adapters/whatsapp-language.js +36 -0
- package/dist/communication/adapters/whatsapp-language.js.map +1 -0
- package/dist/communication/adapters/whatsapp-templates.d.ts +18 -0
- package/dist/communication/adapters/whatsapp-templates.d.ts.map +1 -0
- package/dist/communication/adapters/whatsapp-templates.js +97 -0
- package/dist/communication/adapters/whatsapp-templates.js.map +1 -0
- package/dist/communication/adapters/whatsapp.d.ts +29 -0
- package/dist/communication/adapters/whatsapp.d.ts.map +1 -0
- package/dist/communication/adapters/whatsapp.js +210 -0
- package/dist/communication/adapters/whatsapp.js.map +1 -0
- package/dist/communication/audit.d.ts +105 -0
- package/dist/communication/audit.d.ts.map +1 -0
- package/dist/communication/audit.js +56 -0
- package/dist/communication/audit.js.map +1 -0
- package/dist/communication/capabilities.d.ts +88 -0
- package/dist/communication/capabilities.d.ts.map +1 -0
- package/dist/communication/capabilities.js +72 -0
- package/dist/communication/capabilities.js.map +1 -0
- package/dist/communication/channel.d.ts +125 -0
- package/dist/communication/channel.d.ts.map +1 -0
- package/dist/communication/channel.js +47 -0
- package/dist/communication/channel.js.map +1 -0
- package/dist/communication/claim.d.ts +368 -0
- package/dist/communication/claim.d.ts.map +1 -0
- package/dist/communication/claim.js +224 -0
- package/dist/communication/claim.js.map +1 -0
- package/dist/communication/consent.d.ts +65 -0
- package/dist/communication/consent.d.ts.map +1 -0
- package/dist/communication/consent.js +40 -0
- package/dist/communication/consent.js.map +1 -0
- package/dist/communication/context.d.ts +48 -0
- package/dist/communication/context.d.ts.map +1 -0
- package/dist/communication/context.js +41 -0
- package/dist/communication/context.js.map +1 -0
- package/dist/communication/credentials.d.ts +74 -0
- package/dist/communication/credentials.d.ts.map +1 -0
- package/dist/communication/credentials.js +115 -0
- package/dist/communication/credentials.js.map +1 -0
- package/dist/communication/dry-run-scenario.d.ts +37 -0
- package/dist/communication/dry-run-scenario.d.ts.map +1 -0
- package/dist/communication/dry-run-scenario.js +158 -0
- package/dist/communication/dry-run-scenario.js.map +1 -0
- package/dist/communication/dry-run.d.ts +40 -0
- package/dist/communication/dry-run.d.ts.map +1 -0
- package/dist/communication/dry-run.js +92 -0
- package/dist/communication/dry-run.js.map +1 -0
- package/dist/communication/engine.d.ts +153 -0
- package/dist/communication/engine.d.ts.map +1 -0
- package/dist/communication/engine.js +480 -0
- package/dist/communication/engine.js.map +1 -0
- package/dist/communication/graph.d.ts +38 -0
- package/dist/communication/graph.d.ts.map +1 -0
- package/dist/communication/graph.js +45 -0
- package/dist/communication/graph.js.map +1 -0
- package/dist/communication/idempotency.d.ts +33 -0
- package/dist/communication/idempotency.d.ts.map +1 -0
- package/dist/communication/idempotency.js +23 -0
- package/dist/communication/idempotency.js.map +1 -0
- package/dist/communication/identity.d.ts +127 -0
- package/dist/communication/identity.d.ts.map +1 -0
- package/dist/communication/identity.js +105 -0
- package/dist/communication/identity.js.map +1 -0
- package/dist/communication/index.d.ts +66 -0
- package/dist/communication/index.d.ts.map +1 -0
- package/dist/communication/index.js +42 -0
- package/dist/communication/index.js.map +1 -0
- package/dist/communication/onboarding.d.ts +223 -0
- package/dist/communication/onboarding.d.ts.map +1 -0
- package/dist/communication/onboarding.js +287 -0
- package/dist/communication/onboarding.js.map +1 -0
- package/dist/communication/policy.d.ts +99 -0
- package/dist/communication/policy.d.ts.map +1 -0
- package/dist/communication/policy.js +13 -0
- package/dist/communication/policy.js.map +1 -0
- package/dist/communication/routing.d.ts +99 -0
- package/dist/communication/routing.d.ts.map +1 -0
- package/dist/communication/routing.js +73 -0
- package/dist/communication/routing.js.map +1 -0
- package/dist/communication/sender-profile.d.ts +100 -0
- package/dist/communication/sender-profile.d.ts.map +1 -0
- package/dist/communication/sender-profile.js +123 -0
- package/dist/communication/sender-profile.js.map +1 -0
- package/dist/communication/status.d.ts +48 -0
- package/dist/communication/status.d.ts.map +1 -0
- package/dist/communication/status.js +79 -0
- package/dist/communication/status.js.map +1 -0
- package/dist/communication/stores/onboarding.d.ts +88 -0
- package/dist/communication/stores/onboarding.d.ts.map +1 -0
- package/dist/communication/stores/onboarding.js +127 -0
- package/dist/communication/stores/onboarding.js.map +1 -0
- package/dist/communication/stores/postgres.d.ts +81 -0
- package/dist/communication/stores/postgres.d.ts.map +1 -0
- package/dist/communication/stores/postgres.js +453 -0
- package/dist/communication/stores/postgres.js.map +1 -0
- package/dist/communication/stores/routing.d.ts +169 -0
- package/dist/communication/stores/routing.d.ts.map +1 -0
- package/dist/communication/stores/routing.js +324 -0
- package/dist/communication/stores/routing.js.map +1 -0
- package/dist/communication/stores/secrets.d.ts +89 -0
- package/dist/communication/stores/secrets.d.ts.map +1 -0
- package/dist/communication/stores/secrets.js +122 -0
- package/dist/communication/stores/secrets.js.map +1 -0
- package/dist/communication/template-provider.d.ts +85 -0
- package/dist/communication/template-provider.d.ts.map +1 -0
- package/dist/communication/template-provider.js +21 -0
- package/dist/communication/template-provider.js.map +1 -0
- package/dist/communication/template.d.ts +87 -0
- package/dist/communication/template.d.ts.map +1 -0
- package/dist/communication/template.js +59 -0
- package/dist/communication/template.js.map +1 -0
- package/dist/communication/webhook-subscription.d.ts +156 -0
- package/dist/communication/webhook-subscription.d.ts.map +1 -0
- package/dist/communication/webhook-subscription.js +252 -0
- package/dist/communication/webhook-subscription.js.map +1 -0
- package/dist/communication/webhook.d.ts +121 -0
- package/dist/communication/webhook.d.ts.map +1 -0
- package/dist/communication/webhook.js +283 -0
- package/dist/communication/webhook.js.map +1 -0
- package/dist/competence/repository.d.ts +147 -0
- package/dist/competence/repository.d.ts.map +1 -0
- package/dist/competence/repository.js +294 -0
- package/dist/competence/repository.js.map +1 -0
- package/dist/contacts/phone.d.ts +70 -0
- package/dist/contacts/phone.d.ts.map +1 -0
- package/dist/contacts/phone.js +186 -0
- package/dist/contacts/phone.js.map +1 -0
- package/dist/db/migrate.js +17 -11
- package/dist/db/migrate.js.map +1 -1
- package/dist/db/seed-catalog.d.ts +18 -0
- package/dist/db/seed-catalog.d.ts.map +1 -0
- package/dist/db/seed-catalog.js +45 -0
- package/dist/db/seed-catalog.js.map +1 -0
- package/dist/db/seed.js +16 -8
- package/dist/db/seed.js.map +1 -1
- package/dist/documents/repository.d.ts +97 -0
- package/dist/documents/repository.d.ts.map +1 -0
- package/dist/documents/repository.js +236 -0
- package/dist/documents/repository.js.map +1 -0
- package/dist/documents/requests.d.ts +73 -0
- package/dist/documents/requests.d.ts.map +1 -0
- package/dist/documents/requests.js +127 -0
- package/dist/documents/requests.js.map +1 -0
- package/dist/index.d.ts +113 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +104 -8
- package/dist/index.js.map +1 -1
- package/dist/legal/company.d.ts +32 -0
- package/dist/legal/company.d.ts.map +1 -0
- package/dist/legal/company.js +33 -0
- package/dist/legal/company.js.map +1 -0
- package/dist/legal/documents.d.ts +30 -0
- package/dist/legal/documents.d.ts.map +1 -0
- package/dist/legal/documents.js +666 -0
- package/dist/legal/documents.js.map +1 -0
- package/dist/legal/index.d.ts +5 -0
- package/dist/legal/index.d.ts.map +1 -0
- package/dist/legal/index.js +4 -0
- package/dist/legal/index.js.map +1 -0
- package/dist/legal/types.d.ts +145 -0
- package/dist/legal/types.d.ts.map +1 -0
- package/dist/legal/types.js +31 -0
- package/dist/legal/types.js.map +1 -0
- package/dist/mail/auth-templates.d.ts +15 -0
- package/dist/mail/auth-templates.d.ts.map +1 -0
- package/dist/mail/auth-templates.js +26 -0
- package/dist/mail/auth-templates.js.map +1 -0
- package/dist/mail/dedup-store.d.ts +1 -24
- package/dist/mail/dedup-store.d.ts.map +1 -1
- package/dist/mail/dedup-store.js +9 -48
- package/dist/mail/dedup-store.js.map +1 -1
- package/dist/mail/index.d.ts +2 -1
- package/dist/mail/index.d.ts.map +1 -1
- package/dist/mail/index.js +13 -2
- package/dist/mail/index.js.map +1 -1
- package/dist/mail/provider.d.ts +2 -30
- package/dist/mail/provider.d.ts.map +1 -1
- package/dist/mail/provider.js +10 -246
- package/dist/mail/provider.js.map +1 -1
- package/dist/mail/resend.d.ts +2 -12
- package/dist/mail/resend.d.ts.map +1 -1
- package/dist/mail/resend.js +11 -17
- package/dist/mail/resend.js.map +1 -1
- package/dist/mail/sender-resolver.d.ts +1 -7
- package/dist/mail/sender-resolver.d.ts.map +1 -1
- package/dist/mail/sender-resolver.js +11 -10
- package/dist/mail/sender-resolver.js.map +1 -1
- package/dist/mail/template-renderer.d.ts +1 -9
- package/dist/mail/template-renderer.d.ts.map +1 -1
- package/dist/mail/template-renderer.js +10 -15
- package/dist/mail/template-renderer.js.map +1 -1
- package/dist/mail/transactional-outbound.d.ts +115 -0
- package/dist/mail/transactional-outbound.d.ts.map +1 -0
- package/dist/mail/transactional-outbound.js +113 -0
- package/dist/mail/transactional-outbound.js.map +1 -0
- package/dist/mail/types.d.ts +2 -142
- package/dist/mail/types.d.ts.map +1 -1
- package/dist/mail/types.js +1 -33
- package/dist/mail/types.js.map +1 -1
- package/dist/portal/provisioning.d.ts +58 -0
- package/dist/portal/provisioning.d.ts.map +1 -0
- package/dist/portal/provisioning.js +139 -0
- package/dist/portal/provisioning.js.map +1 -0
- package/dist/pricing-engine/canonical.d.ts +14 -0
- package/dist/pricing-engine/canonical.d.ts.map +1 -0
- package/dist/pricing-engine/canonical.js +39 -0
- package/dist/pricing-engine/canonical.js.map +1 -0
- package/dist/pricing-engine/errors.d.ts.map +1 -1
- package/dist/pricing-engine/errors.js +6 -1
- package/dist/pricing-engine/errors.js.map +1 -1
- package/dist/pricing-engine/index.d.ts +12 -2
- package/dist/pricing-engine/index.d.ts.map +1 -1
- package/dist/pricing-engine/index.js +6 -1
- package/dist/pricing-engine/index.js.map +1 -1
- package/dist/pricing-engine/pipeline.d.ts.map +1 -1
- package/dist/pricing-engine/pipeline.js +202 -28
- package/dist/pricing-engine/pipeline.js.map +1 -1
- package/dist/pricing-engine/shadow-adapter.d.ts +37 -0
- package/dist/pricing-engine/shadow-adapter.d.ts.map +1 -0
- package/dist/pricing-engine/shadow-adapter.js +75 -0
- package/dist/pricing-engine/shadow-adapter.js.map +1 -0
- package/dist/pricing-engine/shadow-diff.d.ts +26 -0
- package/dist/pricing-engine/shadow-diff.d.ts.map +1 -0
- package/dist/pricing-engine/shadow-diff.js +52 -0
- package/dist/pricing-engine/shadow-diff.js.map +1 -0
- package/dist/pricing-engine/shadow-facade.d.ts +42 -0
- package/dist/pricing-engine/shadow-facade.d.ts.map +1 -0
- package/dist/pricing-engine/shadow-facade.js +82 -0
- package/dist/pricing-engine/shadow-facade.js.map +1 -0
- package/dist/pricing-engine/shadow-legacy-types.d.ts +64 -0
- package/dist/pricing-engine/shadow-legacy-types.d.ts.map +1 -0
- package/dist/pricing-engine/shadow-legacy-types.js +18 -0
- package/dist/pricing-engine/shadow-legacy-types.js.map +1 -0
- package/dist/pricing-engine/shadow-metrics.d.ts +33 -0
- package/dist/pricing-engine/shadow-metrics.d.ts.map +1 -0
- package/dist/pricing-engine/shadow-metrics.js +31 -0
- package/dist/pricing-engine/shadow-metrics.js.map +1 -0
- package/dist/pricing-engine/shadow-normalizer.d.ts +17 -0
- package/dist/pricing-engine/shadow-normalizer.d.ts.map +1 -0
- package/dist/pricing-engine/shadow-normalizer.js +39 -0
- package/dist/pricing-engine/shadow-normalizer.js.map +1 -0
- package/dist/pricing-engine/types.d.ts +2 -0
- package/dist/pricing-engine/types.d.ts.map +1 -1
- package/dist/rbac/authorize.d.ts +12 -0
- package/dist/rbac/authorize.d.ts.map +1 -1
- package/dist/rbac/authorize.js +27 -0
- package/dist/rbac/authorize.js.map +1 -1
- package/dist/rbac/catalog.d.ts.map +1 -1
- package/dist/rbac/catalog.js +67 -1
- package/dist/rbac/catalog.js.map +1 -1
- package/dist/rbac/workload-provisioning.d.ts +66 -0
- package/dist/rbac/workload-provisioning.d.ts.map +1 -0
- package/dist/rbac/workload-provisioning.js +153 -0
- package/dist/rbac/workload-provisioning.js.map +1 -0
- package/dist/rbac/workload.d.ts +124 -0
- package/dist/rbac/workload.d.ts.map +1 -0
- package/dist/rbac/workload.js +184 -0
- package/dist/rbac/workload.js.map +1 -0
- package/dist/storage/checksum.d.ts +9 -0
- package/dist/storage/checksum.d.ts.map +1 -0
- package/dist/storage/checksum.js +12 -0
- package/dist/storage/checksum.js.map +1 -0
- package/dist/storage/key.d.ts +37 -0
- package/dist/storage/key.d.ts.map +1 -0
- package/dist/storage/key.js +67 -0
- package/dist/storage/key.js.map +1 -0
- package/dist/storage/local.d.ts +40 -0
- package/dist/storage/local.d.ts.map +1 -0
- package/dist/storage/local.js +96 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/storage/r2.d.ts +43 -0
- package/dist/storage/r2.d.ts.map +1 -0
- package/dist/storage/r2.js +177 -0
- package/dist/storage/r2.js.map +1 -0
- package/dist/storage/registry.d.ts +23 -0
- package/dist/storage/registry.d.ts.map +1 -0
- package/dist/storage/registry.js +37 -0
- package/dist/storage/registry.js.map +1 -0
- package/dist/storage/types.d.ts +67 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +45 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/subscription-billing/provider/asaas.d.ts +14 -0
- package/dist/subscription-billing/provider/asaas.d.ts.map +1 -1
- package/dist/subscription-billing/provider/asaas.js +32 -7
- package/dist/subscription-billing/provider/asaas.js.map +1 -1
- package/dist/subscription-billing/provider/disabled.d.ts +9 -0
- package/dist/subscription-billing/provider/disabled.d.ts.map +1 -1
- package/dist/subscription-billing/provider/disabled.js +8 -0
- package/dist/subscription-billing/provider/disabled.js.map +1 -1
- package/dist/subscription-billing/provider/registry.d.ts +10 -0
- package/dist/subscription-billing/provider/registry.d.ts.map +1 -1
- package/dist/subscription-billing/provider/registry.js +17 -0
- package/dist/subscription-billing/provider/registry.js.map +1 -1
- package/dist/subscription-billing/service.d.ts +17 -1
- package/dist/subscription-billing/service.d.ts.map +1 -1
- package/dist/subscription-billing/service.js +57 -8
- package/dist/subscription-billing/service.js.map +1 -1
- package/dist/subscription-billing/types.d.ts +13 -0
- package/dist/subscription-billing/types.d.ts.map +1 -1
- package/dist/subscription-billing/types.js.map +1 -1
- package/dist/tenancy/provisioning.d.ts.map +1 -1
- package/dist/tenancy/provisioning.js +32 -8
- package/dist/tenancy/provisioning.js.map +1 -1
- package/dist/users/repository.d.ts +38 -0
- package/dist/users/repository.d.ts.map +1 -1
- package/dist/users/repository.js +118 -3
- package/dist/users/repository.js.map +1 -1
- package/dist/vault/repository.d.ts +67 -0
- package/dist/vault/repository.d.ts.map +1 -1
- package/dist/vault/repository.js +78 -0
- package/dist/vault/repository.js.map +1 -1
- package/package.json +19 -4
- package/src/db/migrations/0023_user_invite_activation.sql +15 -0
- package/src/db/migrations/0024_set_user_password_function.sql +32 -0
- package/src/db/migrations/0025_revoke_user_session_function.sql +28 -0
- package/src/db/migrations/0026_documents_and_competence.sql +353 -0
- package/src/db/migrations/0027_receivable_client_isolation.sql +58 -0
- package/src/db/migrations/0028_collection_engine.sql +94 -0
- package/src/db/migrations/0029_payment_rail.sql +107 -0
- package/src/db/migrations/0030_provider_account_uniqueness.sql +22 -0
- package/src/db/migrations/0031_provider_account_environment_scope.sql +21 -0
- package/src/db/migrations/0032_settlement_reversal.sql +76 -0
- package/src/db/migrations/0033_receivable_idempotency.sql +28 -0
- package/src/db/migrations/0034_payment_customer_reconcile.sql +30 -0
- package/src/db/migrations/0035_payment_account_environments.sql +61 -0
- package/src/db/migrations/0036_primary_backfill_under_force_rls.sql +41 -0
- package/src/db/migrations/0037_receivable_generation_source.sql +29 -0
- package/src/db/migrations/0038_notification_channels.sql +175 -0
- package/src/db/migrations/0039_communication_dispatch_claim.sql +193 -0
- package/src/db/migrations/0040_communication_unknown_outcome.sql +99 -0
- package/src/db/migrations/0041_channel_binding_routing.sql +146 -0
- package/src/db/migrations/0042_channel_route_read_isolation.sql +165 -0
- package/src/db/migrations/0043_workload_identity.sql +105 -0
- package/src/db/migrations/0044_channel_onboarding_session.sql +96 -0
- package/dist/cjs/mail/resend-client.js +0 -103
- package/dist/cjs/mail/resend-client.js.map +0 -1
- package/dist/mail/resend-client.d.ts +0 -16
- package/dist/mail/resend-client.d.ts.map +0 -1
- package/dist/mail/resend-client.js +0 -100
- package/dist/mail/resend-client.js.map +0 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
-- 0040 — Desfecho desconhecido, marca de chamada externa e trilha durável
|
|
2
|
+
--
|
|
3
|
+
-- A 0039 fechou a janela do envio duplo sob concorrência: a reserva é tomada
|
|
4
|
+
-- ANTES do efeito externo, numa instrução atômica. O que ela NÃO fechou, e a
|
|
5
|
+
-- segunda certificação independente apontou, é o que acontece quando o titular
|
|
6
|
+
-- da reserva morre.
|
|
7
|
+
--
|
|
8
|
+
-- A reserva tem prazo (10 min) porque processo morre, e uma etapa calada para
|
|
9
|
+
-- sempre é pior que uma duplicata rara. Mas a retomada por prazo vencido era
|
|
10
|
+
-- CEGA: o motor não tinha como distinguir
|
|
11
|
+
--
|
|
12
|
+
-- (a) o titular morreu ANTES de chamar o provider — nada saiu, reenviar é certo;
|
|
13
|
+
-- (b) o titular morreu DEPOIS de chamar o provider — pode ter saído, reenviar duplica.
|
|
14
|
+
--
|
|
15
|
+
-- Os dois chegavam com a mesma cara, e o motor reenviava nos dois. Essa migration
|
|
16
|
+
-- dá ao banco o que falta para separá-los, e um estado para o caso em que a
|
|
17
|
+
-- separação não basta.
|
|
18
|
+
|
|
19
|
+
-- ── A marca da chamada externa ──────────────────────────────────────────────
|
|
20
|
+
--
|
|
21
|
+
-- Gravada IMEDIATAMENTE antes de falar com o provider, em transação própria e
|
|
22
|
+
-- commitada. Precisa sobreviver à morte do processo que a escreveu — se vivesse
|
|
23
|
+
-- na transação de quem chama, ficaria invisível justamente no caso que ela
|
|
24
|
+
-- existe para cobrir, porque o processo que morre é o que nunca commita.
|
|
25
|
+
--
|
|
26
|
+
-- Erra para o lado seguro por construção: quem morre ENTRE a marca e a chamada
|
|
27
|
+
-- é classificado como "pode ter enviado" sem ter enviado. Isso cala uma
|
|
28
|
+
-- mensagem; a alternativa manda duas.
|
|
29
|
+
ALTER TABLE communication_dispatch
|
|
30
|
+
ADD COLUMN IF NOT EXISTS provider_attempts integer NOT NULL DEFAULT 0;
|
|
31
|
+
|
|
32
|
+
-- Instante da PRIMEIRA chamada externa desta chave. A janela de idempotência do
|
|
33
|
+
-- provider conta a partir dela, não da tentativa atual: uma chave que o provider
|
|
34
|
+
-- já esqueceu não protege mais nada, e reenviar depois disso é uma mensagem nova.
|
|
35
|
+
ALTER TABLE communication_dispatch
|
|
36
|
+
ADD COLUMN IF NOT EXISTS provider_first_attempt_at timestamptz;
|
|
37
|
+
|
|
38
|
+
-- Última chamada externa. Diagnóstico operacional — responde "há quanto tempo
|
|
39
|
+
-- esta reserva está pendurada depois de já ter falado com o provider".
|
|
40
|
+
ALTER TABLE communication_dispatch
|
|
41
|
+
ADD COLUMN IF NOT EXISTS provider_last_attempt_at timestamptz;
|
|
42
|
+
|
|
43
|
+
-- ── O estado que faltava ────────────────────────────────────────────────────
|
|
44
|
+
--
|
|
45
|
+
-- `unknown_outcome`: alguém chamou o provider e ninguém sabe o que aconteceu.
|
|
46
|
+
--
|
|
47
|
+
-- Não é falha (pode ter dado certo) e não é sucesso (pode não ter saído). Os
|
|
48
|
+
-- quatro estados anteriores obrigavam a mentir: `failed_retryable` devolveria a
|
|
49
|
+
-- chave e a próxima rodada duplicaria; `sent` afirmaria uma entrega que ninguém
|
|
50
|
+
-- viu.
|
|
51
|
+
--
|
|
52
|
+
-- A chave fica travada aqui de propósito. Destravar sozinha é escolher, no
|
|
53
|
+
-- escuro, entre calar um aviso e cobrar duas vezes — e essa escolha é de quem
|
|
54
|
+
-- consegue olhar o painel do provider, não do cron.
|
|
55
|
+
ALTER TABLE communication_dispatch
|
|
56
|
+
DROP CONSTRAINT IF EXISTS communication_dispatch_status_check;
|
|
57
|
+
|
|
58
|
+
ALTER TABLE communication_dispatch
|
|
59
|
+
ADD CONSTRAINT communication_dispatch_status_check
|
|
60
|
+
CHECK (status IN ('in_flight', 'sent', 'failed_retryable', 'failed_permanent', 'unknown_outcome'));
|
|
61
|
+
|
|
62
|
+
-- Reservas paradas em desfecho desconhecido, para a fila de quem investiga.
|
|
63
|
+
-- Parcial porque a expectativa é que sejam raras: um índice cheio sobre um
|
|
64
|
+
-- estado excepcional é um índice que só custa.
|
|
65
|
+
CREATE INDEX IF NOT EXISTS communication_dispatch_unknown_idx
|
|
66
|
+
ON communication_dispatch (tenant_id, updated_at DESC)
|
|
67
|
+
WHERE status = 'unknown_outcome';
|
|
68
|
+
|
|
69
|
+
-- ── Trilha durável ──────────────────────────────────────────────────────────
|
|
70
|
+
--
|
|
71
|
+
-- `recipient_ref` passa a viver na reserva porque a trilha do CICLO DE VIDA do
|
|
72
|
+
-- disparo (`dispatch_claimed`, `dispatch_sent`, `dispatch_failed`) passou a ser
|
|
73
|
+
-- escrita na mesma transação da reserva, e não mais na transação de negócio de
|
|
74
|
+
-- quem chamou.
|
|
75
|
+
--
|
|
76
|
+
-- O defeito que isso corrige é concreto. A reserva commita em transação própria
|
|
77
|
+
-- e curta (precisa ficar visível a outros workers antes do fim do ciclo); a
|
|
78
|
+
-- trilha vivia na transação longa da régua. Um rollback da régua DEPOIS de a
|
|
79
|
+
-- mensagem sair produzia:
|
|
80
|
+
--
|
|
81
|
+
-- communication_dispatch.status = 'sent' (sobreviveu — a mensagem saiu)
|
|
82
|
+
-- communication_event.dispatch_sent (sumiu — a narrativa foi desfeita)
|
|
83
|
+
--
|
|
84
|
+
-- Um disparo enviado sem nenhuma linha explicando que foi enviado. Escrevendo as
|
|
85
|
+
-- duas coisas juntas, elas caem ou sobrevivem juntas. Não é outbox nem
|
|
86
|
+
-- dual-write com compensação: é uma escrita só.
|
|
87
|
+
--
|
|
88
|
+
-- Referência LÓGICA (id do cliente), como no resto do motor. NUNCA telefone,
|
|
89
|
+
-- NUNCA e-mail: a reserva é consultada por quem opera, e uma reserva que carrega
|
|
90
|
+
-- contato transforma toda consulta operacional numa exportação de PII.
|
|
91
|
+
ALTER TABLE communication_dispatch
|
|
92
|
+
ADD COLUMN IF NOT EXISTS recipient_ref text;
|
|
93
|
+
|
|
94
|
+
COMMENT ON COLUMN communication_dispatch.provider_attempts IS
|
|
95
|
+
'Quantas vezes o provider foi chamado por esta chave. Marcado ANTES da chamada, em transação própria.';
|
|
96
|
+
COMMENT ON COLUMN communication_dispatch.provider_first_attempt_at IS
|
|
97
|
+
'Primeira chamada externa. A janela de idempotência do provider conta a partir daqui.';
|
|
98
|
+
COMMENT ON COLUMN communication_dispatch.recipient_ref IS
|
|
99
|
+
'Referência lógica do destinatário, para a trilha durável. Nunca telefone, nunca e-mail.';
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
-- 0041 — Vínculo de canal por tenant e roteamento reverso de webhook
|
|
2
|
+
--
|
|
3
|
+
-- O que muda de premissa. Até aqui, a configuração de canal do escritório vivia
|
|
4
|
+
-- em `tenant.settings.channels` (jsonb) e o webhook trazia o tenant no PATH da
|
|
5
|
+
-- URL. Isso é coerente com uma topologia em que cada tenant tem o próprio Meta
|
|
6
|
+
-- App e o próprio webhook — e incoerente com a topologia que o motor vai
|
|
7
|
+
-- operar, em que existe UM App do motor e N contas de clientes.
|
|
8
|
+
--
|
|
9
|
+
-- Sob um App único, a URL de callback e o verify token pertencem ao APP. A Meta
|
|
10
|
+
-- entrega os eventos de todas as contas assinadas no mesmo endereço e não tem
|
|
11
|
+
-- como saber o nosso `tenantId`. O tenant precisa sair do PAYLOAD, pelo
|
|
12
|
+
-- `phone_number_id` — e isso exige um caminho de leitura que vai da conta do
|
|
13
|
+
-- provider para o tenant, exatamente o inverso do que existe hoje.
|
|
14
|
+
--
|
|
15
|
+
-- Nada aqui apaga o caminho anterior: `tenant.settings.channels` continua
|
|
16
|
+
-- válido e continua sendo lido. Esta migração acrescenta o registro canônico e
|
|
17
|
+
-- o índice de roteamento.
|
|
18
|
+
|
|
19
|
+
-- ── Vínculo de canal ────────────────────────────────────────────────────────
|
|
20
|
+
--
|
|
21
|
+
-- Uma linha por (tenant, produto, marca, canal). É o que `resolveIdentity` já
|
|
22
|
+
-- pressupõe: o motor pergunta "qual a conta DESTE contexto neste canal" e
|
|
23
|
+
-- espera no máximo uma resposta.
|
|
24
|
+
--
|
|
25
|
+
-- `credential_ref` é referência LÓGICA — o nome da credencial no Vault Engine
|
|
26
|
+
-- (`0012_vault.sql`), nunca o valor. O ciclo de vida do segredo (vencimento,
|
|
27
|
+
-- rotação, remoção) já é do Vault e não é duplicado aqui: duas fontes para
|
|
28
|
+
-- "quando este token expira" divergem no dia em que uma é atualizada e a outra
|
|
29
|
+
-- não.
|
|
30
|
+
CREATE TABLE IF NOT EXISTS channel_binding (
|
|
31
|
+
id uuid PRIMARY KEY,
|
|
32
|
+
tenant_id uuid NOT NULL REFERENCES tenant(id),
|
|
33
|
+
-- Produto e marca fazem parte da identidade do vínculo porque fazem parte do
|
|
34
|
+
-- `CommunicationContext`. Um tenant que consome dois produtos do ecossistema
|
|
35
|
+
-- tem duas conversas distintas com o cliente dele.
|
|
36
|
+
product text NOT NULL,
|
|
37
|
+
brand text NOT NULL,
|
|
38
|
+
-- Só canais de TELEFONE. E-mail não tem conta de provider por tenant no
|
|
39
|
+
-- desenho atual (o fallback de plataforma existe e é declaradamente
|
|
40
|
+
-- email-only), e criar a linha para ele daria a impressão de que tem.
|
|
41
|
+
channel text NOT NULL CHECK (channel IN ('whatsapp', 'sms')),
|
|
42
|
+
provider text NOT NULL CHECK (provider IN ('meta_whatsapp')),
|
|
43
|
+
-- Identidade visível ao destinatário (E.164). Não é segredo — aparece na tela
|
|
44
|
+
-- e na trilha.
|
|
45
|
+
sender_id text NOT NULL,
|
|
46
|
+
display_name text,
|
|
47
|
+
-- Nome da credencial no Vault. Jamais o segredo.
|
|
48
|
+
credential_ref text NOT NULL,
|
|
49
|
+
-- `pending_validation` existe porque uma conta cadastrada e não verificada
|
|
50
|
+
-- parece pronta na tela e é recusada pelo provider. O operador merece ver a
|
|
51
|
+
-- diferença antes da primeira cobrança, não depois.
|
|
52
|
+
status text NOT NULL DEFAULT 'not_configured'
|
|
53
|
+
CHECK (status IN ('not_configured','pending_validation','active','paused','error')),
|
|
54
|
+
-- Revogação do VÍNCULO — distinta da revogação da credencial, que é do Vault.
|
|
55
|
+
-- Um tenant pode retirar o acesso do motor à conta dele sem que o segredo
|
|
56
|
+
-- deixe de existir.
|
|
57
|
+
revoked_at timestamptz,
|
|
58
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
59
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
60
|
+
CONSTRAINT channel_binding_tenant_id_uq UNIQUE (tenant_id, id)
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
-- Um vínculo vivo por contexto e canal. Sem isto, duas linhas concorrentes
|
|
64
|
+
-- fariam `resolveIdentity` escolher uma — e escolher errado significa enviar
|
|
65
|
+
-- pela conta errada.
|
|
66
|
+
CREATE UNIQUE INDEX IF NOT EXISTS channel_binding_context_uq
|
|
67
|
+
ON channel_binding (tenant_id, product, brand, channel)
|
|
68
|
+
WHERE revoked_at IS NULL;
|
|
69
|
+
|
|
70
|
+
ALTER TABLE channel_binding ENABLE ROW LEVEL SECURITY;
|
|
71
|
+
ALTER TABLE channel_binding FORCE ROW LEVEL SECURITY;
|
|
72
|
+
|
|
73
|
+
CREATE POLICY channel_binding_tenant_isolation ON channel_binding
|
|
74
|
+
USING (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid)
|
|
75
|
+
WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid);
|
|
76
|
+
|
|
77
|
+
-- ── Índice de roteamento ────────────────────────────────────────────────────
|
|
78
|
+
--
|
|
79
|
+
-- Por que uma tabela separada, e não colunas em `channel_binding`.
|
|
80
|
+
--
|
|
81
|
+
-- A pergunta do webhook é "de quem é este `phone_number_id`?", feita ANTES de
|
|
82
|
+
-- existir contexto de tenant — não há como setar `app.current_tenant` para
|
|
83
|
+
-- descobrir qual é o tenant. Sob a RLS FORCE de `channel_binding`, essa
|
|
84
|
+
-- consulta não enxergaria linha nenhuma.
|
|
85
|
+
--
|
|
86
|
+
-- E há um segundo motivo, mais duro: a unicidade global. Se o par
|
|
87
|
+
-- (provider, conta) morasse numa tabela com RLS por tenant, um índice único
|
|
88
|
+
-- `(tenant_id, ...)` deixaria DOIS tenants reivindicarem o mesmo número, e
|
|
89
|
+
-- nenhum guard de aplicação pegaria isso — sob RLS, o guard do tenant A não
|
|
90
|
+
-- enxerga a linha do tenant B para comparar. A chave primária abaixo é global
|
|
91
|
+
-- de propósito: é o banco, e não o código, que garante que um número pertence a
|
|
92
|
+
-- no máximo um tenant.
|
|
93
|
+
--
|
|
94
|
+
-- O que esta tabela contém: identificadores emitidos pelo provider (que o
|
|
95
|
+
-- próprio provider manda em texto claro em todo webhook) e o dono. Nenhuma
|
|
96
|
+
-- credencial, nenhum dado de cliente, nenhuma PII.
|
|
97
|
+
CREATE TABLE IF NOT EXISTS channel_route (
|
|
98
|
+
provider text NOT NULL CHECK (provider IN ('meta_whatsapp')),
|
|
99
|
+
-- O identificador de ROTEAMENTO. Na Meta, `phone_number_id`.
|
|
100
|
+
provider_account_ref text NOT NULL,
|
|
101
|
+
-- O agrupador (WABA). Conferência de coerência, nunca segunda chave.
|
|
102
|
+
provider_parent_ref text,
|
|
103
|
+
tenant_id uuid NOT NULL,
|
|
104
|
+
binding_id uuid NOT NULL,
|
|
105
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
106
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
107
|
+
-- GLOBAL, não por tenant. Ver o comentário acima.
|
|
108
|
+
PRIMARY KEY (provider, provider_account_ref),
|
|
109
|
+
-- A FK composta amarra a rota ao vínculo do MESMO tenant. Não há como
|
|
110
|
+
-- registrar uma rota apontando para o vínculo de outro escritório.
|
|
111
|
+
FOREIGN KEY (tenant_id, binding_id)
|
|
112
|
+
REFERENCES channel_binding (tenant_id, id) ON DELETE CASCADE
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
CREATE INDEX IF NOT EXISTS channel_route_tenant_idx ON channel_route (tenant_id);
|
|
116
|
+
|
|
117
|
+
ALTER TABLE channel_route ENABLE ROW LEVEL SECURITY;
|
|
118
|
+
ALTER TABLE channel_route FORCE ROW LEVEL SECURITY;
|
|
119
|
+
|
|
120
|
+
-- LEITURA: aberta apenas quando NÃO há tenant na sessão — que é o único momento
|
|
121
|
+
-- em que o roteador precisa consultar, e o único em que ele pode. Dentro de um
|
|
122
|
+
-- contexto de tenant, um escritório continua vendo só as próprias rotas.
|
|
123
|
+
--
|
|
124
|
+
-- O ramo aberto é uma decisão consciente e é o preço da topologia de App único.
|
|
125
|
+
-- O que ele expõe a um código que rode sem GUC é o mapa
|
|
126
|
+
-- (número do provider → tenant) — sem segredo, sem PII, sem conteúdo. O que ele
|
|
127
|
+
-- NÃO abre é escrita: veja a política seguinte.
|
|
128
|
+
CREATE POLICY channel_route_read ON channel_route FOR SELECT
|
|
129
|
+
USING (
|
|
130
|
+
NULLIF(current_setting('app.current_tenant', true), '') IS NULL
|
|
131
|
+
OR tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
-- ESCRITA: sempre fechada. Registrar ou remover rota exige estar dentro do
|
|
135
|
+
-- tenant dono. Sem isto, o mesmo ramo aberto da leitura permitiria a qualquer
|
|
136
|
+
-- código sem contexto reivindicar o número de um escritório.
|
|
137
|
+
CREATE POLICY channel_route_write ON channel_route FOR ALL
|
|
138
|
+
USING (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid)
|
|
139
|
+
WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid);
|
|
140
|
+
|
|
141
|
+
COMMENT ON TABLE channel_binding IS
|
|
142
|
+
'Conta de canal de um tenant. credential_ref é o NOME no Vault, nunca o valor.';
|
|
143
|
+
COMMENT ON TABLE channel_route IS
|
|
144
|
+
'Índice de roteamento de webhook. Chave primária GLOBAL: um phone_number_id pertence a no máximo um tenant.';
|
|
145
|
+
COMMENT ON COLUMN channel_route.provider_parent_ref IS
|
|
146
|
+
'WABA. Conferência de coerência; nunca decide sozinho de quem é o evento.';
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
-- 0042 — Fecha a leitura de `channel_route` e move o roteamento para um caminho
|
|
2
|
+
-- de sistema explícito.
|
|
3
|
+
--
|
|
4
|
+
-- POR QUE UMA MIGRATION NOVA, E NÃO UMA CORREÇÃO NA 0041.
|
|
5
|
+
--
|
|
6
|
+
-- O runner (`db/migrate.ts`) guarda o checksum de cada arquivo aplicado e
|
|
7
|
+
-- recusa a subida quando ele muda: "Migration já aplicada foi modificada —
|
|
8
|
+
-- migrations são imutáveis, crie uma nova". Editar a 0041 faria o caminho de
|
|
9
|
+
-- UPGRADE (banco que já a aplicou) abortar antes de qualquer correção chegar,
|
|
10
|
+
-- e só o caminho FRESH veria a versão nova. Um defeito de segurança que se
|
|
11
|
+
-- corrige apenas em banco recriado não está corrigido.
|
|
12
|
+
--
|
|
13
|
+
-- O QUE A 0041 DEIXOU ABERTO.
|
|
14
|
+
--
|
|
15
|
+
-- A política de leitura dela era:
|
|
16
|
+
--
|
|
17
|
+
-- current_tenant IS NULL OR tenant_id = current_tenant
|
|
18
|
+
--
|
|
19
|
+
-- O primeiro ramo transformava a AUSÊNCIA de contexto em autorização. Isso
|
|
20
|
+
-- inverte a regra da casa: sem contexto, não se lê tabela de tenant. E a
|
|
21
|
+
-- ausência de contexto não é um estado raro — é o estado de qualquer código
|
|
22
|
+
-- que esqueceu de abrir `withContext`, de qualquer script, de qualquer job.
|
|
23
|
+
-- O ramo entregava o mapa (conta do provider → tenant) a todos eles, e a
|
|
24
|
+
-- listagem inteira: `SELECT * FROM channel_route` sem GUC devolvia o portfólio
|
|
25
|
+
-- de clientes do motor.
|
|
26
|
+
--
|
|
27
|
+
-- A necessidade que justificava o ramo continua real: o webhook do App único
|
|
28
|
+
-- precisa descobrir o tenant ANTES de poder abrir contexto dele. O que muda é
|
|
29
|
+
-- que essa necessidade passa a ser atendida por um caminho nomeado, com
|
|
30
|
+
-- assinatura fixa e resposta de no máximo uma linha, em vez de por um buraco na
|
|
31
|
+
-- política que qualquer consulta atravessa.
|
|
32
|
+
|
|
33
|
+
-- ── 1 · Leitura fecha ───────────────────────────────────────────────────────
|
|
34
|
+
--
|
|
35
|
+
-- Sem GUC, `NULLIF(...)::uuid` é NULL, e `tenant_id = NULL` é NULL — que a RLS
|
|
36
|
+
-- trata como falso. Fail-closed sem ramo especial.
|
|
37
|
+
DROP POLICY IF EXISTS channel_route_read ON channel_route;
|
|
38
|
+
|
|
39
|
+
CREATE POLICY channel_route_read ON channel_route FOR SELECT
|
|
40
|
+
USING (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid);
|
|
41
|
+
|
|
42
|
+
-- ── 2 · O caminho de sistema, explícito ─────────────────────────────────────
|
|
43
|
+
--
|
|
44
|
+
-- `channel_route_resolve` é a ÚNICA forma de perguntar "de quem é esta conta"
|
|
45
|
+
-- sem contexto de tenant, e ela responde só isso:
|
|
46
|
+
--
|
|
47
|
+
-- - exige a chave exata (provider + conta). Não existe consulta por prefixo,
|
|
48
|
+
-- por tenant, por faixa, nem sem argumento: não há como enumerar o mapa.
|
|
49
|
+
-- - devolve no máximo UMA linha, e só as colunas de roteamento.
|
|
50
|
+
-- - `SECURITY DEFINER` com `search_path` fixo — sem isso, um schema no
|
|
51
|
+
-- caminho de busca do chamador poderia sombrear `channel_route` e a função
|
|
52
|
+
-- leria a tabela de outra pessoa com o privilégio do dono.
|
|
53
|
+
-- - `STABLE`, nunca `VOLATILE`: não escreve.
|
|
54
|
+
--
|
|
55
|
+
-- O privilégio de execução é concedido nominalmente ao papel de aplicação. O
|
|
56
|
+
-- `REVOKE ... FROM PUBLIC` é obrigatório porque o Postgres concede EXECUTE a
|
|
57
|
+
-- PUBLIC por padrão em toda função criada — sem ele, "estritamente limitada"
|
|
58
|
+
-- seria só o nome.
|
|
59
|
+
CREATE OR REPLACE FUNCTION channel_route_resolve(
|
|
60
|
+
p_provider text,
|
|
61
|
+
p_account_ref text
|
|
62
|
+
)
|
|
63
|
+
RETURNS TABLE (
|
|
64
|
+
provider text,
|
|
65
|
+
provider_account_ref text,
|
|
66
|
+
provider_parent_ref text,
|
|
67
|
+
tenant_id uuid,
|
|
68
|
+
binding_id uuid
|
|
69
|
+
)
|
|
70
|
+
LANGUAGE sql
|
|
71
|
+
STABLE
|
|
72
|
+
SECURITY DEFINER
|
|
73
|
+
SET search_path = pg_catalog, public
|
|
74
|
+
AS $fn$
|
|
75
|
+
SELECT r.provider, r.provider_account_ref, r.provider_parent_ref,
|
|
76
|
+
r.tenant_id, r.binding_id
|
|
77
|
+
FROM public.channel_route r
|
|
78
|
+
WHERE r.provider = p_provider
|
|
79
|
+
AND r.provider_account_ref = p_account_ref
|
|
80
|
+
LIMIT 1
|
|
81
|
+
$fn$;
|
|
82
|
+
|
|
83
|
+
REVOKE ALL ON FUNCTION channel_route_resolve(text, text) FROM PUBLIC;
|
|
84
|
+
|
|
85
|
+
DO $$
|
|
86
|
+
BEGIN
|
|
87
|
+
IF EXISTS (SELECT FROM pg_roles WHERE rolname = 'baruk_app') THEN
|
|
88
|
+
EXECUTE 'GRANT EXECUTE ON FUNCTION channel_route_resolve(text, text) TO baruk_app';
|
|
89
|
+
END IF;
|
|
90
|
+
END $$;
|
|
91
|
+
|
|
92
|
+
-- ── 3 · Por que a função consegue ler o que o chamador não consegue ─────────
|
|
93
|
+
--
|
|
94
|
+
-- `channel_route` tem FORCE ROW LEVEL SECURITY, e FORCE aplica as políticas
|
|
95
|
+
-- TAMBÉM ao dono da tabela. Sem uma política que o autorize, a função
|
|
96
|
+
-- `SECURITY DEFINER` — que roda com a identidade do dono — leria zero linhas, e
|
|
97
|
+
-- o roteamento morreria em silêncio.
|
|
98
|
+
--
|
|
99
|
+
-- A autorização é dada nominalmente ao papel que é dono do schema (o mesmo que
|
|
100
|
+
-- roda migrations), e é de LEITURA APENAS. A escrita continua sujeita à
|
|
101
|
+
-- política por tenant inclusive para ele: `channel_route_write` não foi
|
|
102
|
+
-- afetada, e nenhuma política nova concede INSERT/UPDATE/DELETE.
|
|
103
|
+
--
|
|
104
|
+
-- O nome do papel varia por ambiente (`baruk_test` local, outro em produção),
|
|
105
|
+
-- então ele é descoberto — não escrito à mão.
|
|
106
|
+
DO $$
|
|
107
|
+
DECLARE
|
|
108
|
+
owner_role text;
|
|
109
|
+
BEGIN
|
|
110
|
+
SELECT tableowner INTO owner_role
|
|
111
|
+
FROM pg_tables
|
|
112
|
+
WHERE schemaname = 'public' AND tablename = 'channel_route';
|
|
113
|
+
|
|
114
|
+
IF owner_role IS NULL THEN
|
|
115
|
+
RAISE EXCEPTION 'channel_route sem dono identificável — abortando';
|
|
116
|
+
END IF;
|
|
117
|
+
|
|
118
|
+
EXECUTE format('DROP POLICY IF EXISTS channel_route_system_resolver ON channel_route');
|
|
119
|
+
EXECUTE format(
|
|
120
|
+
'CREATE POLICY channel_route_system_resolver ON channel_route FOR SELECT TO %I USING (true)',
|
|
121
|
+
owner_role
|
|
122
|
+
);
|
|
123
|
+
END $$;
|
|
124
|
+
|
|
125
|
+
COMMENT ON FUNCTION channel_route_resolve(text, text) IS
|
|
126
|
+
'Resolução de webhook fora de contexto de tenant. Chave exata, uma linha, sem enumeração.';
|
|
127
|
+
|
|
128
|
+
-- ── 4 · A credencial de canal de telefone tem de ser do COFRE ───────────────
|
|
129
|
+
--
|
|
130
|
+
-- `isAcceptableForNewBinding` já dizia isto em TypeScript, e nenhum caminho de
|
|
131
|
+
-- escrita a chamava. A 0042 corrige o caminho (ver `registerChannelBinding`) e
|
|
132
|
+
-- repete a regra aqui, porque um guard de aplicação cobre a função que o
|
|
133
|
+
-- invoca — não cobre um `UPDATE` cru, uma importação, um script de correção de
|
|
134
|
+
-- dados nem a próxima função de escrita que alguém escrever.
|
|
135
|
+
--
|
|
136
|
+
-- Um `credential_ref` que não começa por `vault://` num canal de telefone
|
|
137
|
+
-- significa um token de ambiente, e um token de ambiente é COMPARTILHADO: o
|
|
138
|
+
-- bloqueio que a Meta aplicar a um escritório cai sobre todos os outros.
|
|
139
|
+
--
|
|
140
|
+
-- `NOT VALID` de propósito, e é exatamente a compatibilidade legada do §10.
|
|
141
|
+
-- Ele NÃO afrouxa a regra para escritas: todo INSERT e todo UPDATE passam pela
|
|
142
|
+
-- verificação, inclusive um UPDATE numa linha antiga. O que ele dispensa é a
|
|
143
|
+
-- varredura das linhas que já existem — que hoje são zero (a tabela nasceu na
|
|
144
|
+
-- 0041, que nunca foi mesclada), e que num ambiente onde não fossem
|
|
145
|
+
-- continuariam legíveis e utilizáveis até serem tocadas.
|
|
146
|
+
--
|
|
147
|
+
-- Plano de remoção do `NOT VALID`: quando o inventário de `channel_binding`
|
|
148
|
+
-- confirmar zero linhas com referência de ambiente, uma migration futura roda
|
|
149
|
+
-- `VALIDATE CONSTRAINT` e a exceção deixa de existir.
|
|
150
|
+
DO $$
|
|
151
|
+
BEGIN
|
|
152
|
+
IF NOT EXISTS (
|
|
153
|
+
SELECT FROM pg_constraint WHERE conname = 'channel_binding_phone_credential_ref_chk'
|
|
154
|
+
) THEN
|
|
155
|
+
ALTER TABLE channel_binding
|
|
156
|
+
ADD CONSTRAINT channel_binding_phone_credential_ref_chk
|
|
157
|
+
CHECK (
|
|
158
|
+
channel NOT IN ('whatsapp', 'sms')
|
|
159
|
+
OR credential_ref LIKE 'vault://%'
|
|
160
|
+
) NOT VALID;
|
|
161
|
+
END IF;
|
|
162
|
+
END $$;
|
|
163
|
+
|
|
164
|
+
COMMENT ON CONSTRAINT channel_binding_phone_credential_ref_chk ON channel_binding IS
|
|
165
|
+
'Canal de telefone exige credencial do cofre. NOT VALID cobre linhas legadas; escritas são verificadas.';
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
-- 0043 — Identidade e autorização de workload NÃO-HUMANO.
|
|
2
|
+
--
|
|
3
|
+
-- Por que uma tabela nova, e não `user`/`user_role`.
|
|
4
|
+
--
|
|
5
|
+
-- O RBAC existente resolve autorização de PESSOA: `user` → `membership` →
|
|
6
|
+
-- `user_role` → `role_permission` → `permission`. Reaproveitá-lo para um cron
|
|
7
|
+
-- exigiria criar uma linha em `"user"` — e `login()` (auth/service.ts) não
|
|
8
|
+
-- filtra `user_type`. Um principal de automação virando linha de `"user"`
|
|
9
|
+
-- ganharia, de graça, a superfície de login interativo: bastaria o fluxo de
|
|
10
|
+
-- convite/reset alcançar aquele e-mail para que um humano obtivesse uma sessão
|
|
11
|
+
-- HTTP carregando `vault.credential.decrypt`. Além disso, o menor papel de
|
|
12
|
+
-- sistema que carrega essa permissão é `accountant`, que carrega junto
|
|
13
|
+
-- `client.write`, `billing.write` e mais trinta — raio de dano incompatível
|
|
14
|
+
-- com "este processo lê UMA credencial de canal".
|
|
15
|
+
--
|
|
16
|
+
-- O que estas duas tabelas fazem é o mínimo: separar IDENTIDADE (global, como
|
|
17
|
+
-- `"user"`) de AUTORIZAÇÃO (tenant-scoped, como `user_role`), mantendo o
|
|
18
|
+
-- vocabulário de permissão no catálogo já existente (FK para `permission.key`)
|
|
19
|
+
-- e acrescentando os dois escopos que o RBAC de pessoa não tem — PROPÓSITO e
|
|
20
|
+
-- RECURSO. Sem eles, autorizar um workload a decifrar "alguma coisa" no tenant
|
|
21
|
+
-- A o autorizaria a decifrar TUDO do tenant A, e um `credentialRef` vindo de
|
|
22
|
+
-- input viraria confused deputy.
|
|
23
|
+
--
|
|
24
|
+
-- A trava que importa não está aqui, e sim nos privilégios: `baruk_app` recebe
|
|
25
|
+
-- apenas SELECT nestas duas tabelas (ver `db/migrate.ts` §3 e o bloco final
|
|
26
|
+
-- deste arquivo). O processo que CONSOME a autorização não consegue ESCREVÊ-LA.
|
|
27
|
+
-- Autoridade escrita pelo controlado não é autoridade.
|
|
28
|
+
|
|
29
|
+
-- ── service_principal (identidade global de workload — espelha `"user"`) ─────
|
|
30
|
+
CREATE TABLE service_principal (
|
|
31
|
+
id uuid PRIMARY KEY,
|
|
32
|
+
-- Slug estável, é o que a raiz de composição apresenta ('collection-cron').
|
|
33
|
+
key text NOT NULL,
|
|
34
|
+
display_name text NOT NULL,
|
|
35
|
+
status text NOT NULL DEFAULT 'active'
|
|
36
|
+
CHECK (status IN ('active', 'disabled')),
|
|
37
|
+
-- scrypt, mesmo formato de `user_credential.password_hash`. NUNCA o segredo.
|
|
38
|
+
-- O segredo em claro vive só no ambiente do deploy que roda aquele workload.
|
|
39
|
+
secret_hash text NOT NULL,
|
|
40
|
+
secret_rotated_at timestamptz,
|
|
41
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
42
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
43
|
+
disabled_at timestamptz,
|
|
44
|
+
deleted_at timestamptz
|
|
45
|
+
);
|
|
46
|
+
CREATE UNIQUE INDEX service_principal_key_uq
|
|
47
|
+
ON service_principal (lower(key)) WHERE deleted_at IS NULL;
|
|
48
|
+
|
|
49
|
+
-- ── service_principal_grant (autorização tenant-scoped — espelha `user_role`) ─
|
|
50
|
+
-- Uma linha = "o principal P pode exercer a permissão K, no tenant T, para o
|
|
51
|
+
-- propósito R, sobre ESTE recurso". As quatro dimensões são conjuntivas.
|
|
52
|
+
CREATE TABLE service_principal_grant (
|
|
53
|
+
id uuid PRIMARY KEY,
|
|
54
|
+
tenant_id uuid NOT NULL REFERENCES tenant(id),
|
|
55
|
+
principal_id uuid NOT NULL REFERENCES service_principal(id),
|
|
56
|
+
-- FK para o catálogo: a permissão de workload usa o MESMO vocabulário do
|
|
57
|
+
-- RBAC de pessoa. Uma permissão inventada não entra.
|
|
58
|
+
permission_key text NOT NULL REFERENCES permission(key),
|
|
59
|
+
-- Propósito declarado. Mesmo vocabulário fechado de `SecretAccessReason`.
|
|
60
|
+
purpose text NOT NULL
|
|
61
|
+
CHECK (purpose IN ('channel_dispatch', 'onboarding_seal', 'health_probe')),
|
|
62
|
+
-- Escopo de recurso. `exact` = um nome de credencial; `prefix` = um namespace
|
|
63
|
+
-- ('whatsapp/'). O comprimento mínimo existe para que `prefix = ''` — que
|
|
64
|
+
-- alcançaria tudo — não seja representável.
|
|
65
|
+
resource_kind text NOT NULL CHECK (resource_kind IN ('exact', 'prefix')),
|
|
66
|
+
resource_value text NOT NULL CHECK (length(btrim(resource_value)) >= 3),
|
|
67
|
+
-- Quem concedeu. Humano do tenant, ou NULL quando a concessão nasce do
|
|
68
|
+
-- provisionamento com a credencial de migração. Nunca o próprio principal.
|
|
69
|
+
granted_by uuid REFERENCES "user"(id),
|
|
70
|
+
granted_reason text,
|
|
71
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
72
|
+
-- Validade temporal opcional. NULL = vigente até revogação — deliberado: uma
|
|
73
|
+
-- concessão do cron de cobrança que vence sozinha vira parada de produção
|
|
74
|
+
-- silenciosa. A validade CURTA e obrigatória vive na sessão em memória
|
|
75
|
+
-- (`WorkloadIdentity.expiresAt`), onde expirar não custa disponibilidade.
|
|
76
|
+
expires_at timestamptz,
|
|
77
|
+
revoked_at timestamptz,
|
|
78
|
+
deleted_at timestamptz
|
|
79
|
+
);
|
|
80
|
+
CREATE UNIQUE INDEX service_principal_grant_uq
|
|
81
|
+
ON service_principal_grant (tenant_id, principal_id, permission_key, purpose,
|
|
82
|
+
resource_kind, lower(resource_value))
|
|
83
|
+
WHERE deleted_at IS NULL AND revoked_at IS NULL;
|
|
84
|
+
CREATE INDEX service_principal_grant_lookup_idx
|
|
85
|
+
ON service_principal_grant (tenant_id, principal_id, permission_key);
|
|
86
|
+
|
|
87
|
+
ALTER TABLE service_principal_grant ENABLE ROW LEVEL SECURITY;
|
|
88
|
+
ALTER TABLE service_principal_grant FORCE ROW LEVEL SECURITY;
|
|
89
|
+
CREATE POLICY service_principal_grant_tenant_isolation ON service_principal_grant
|
|
90
|
+
USING (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid)
|
|
91
|
+
WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid);
|
|
92
|
+
|
|
93
|
+
-- ── Privilégios: o app LÊ, nunca escreve ────────────────────────────────────
|
|
94
|
+
-- Repetido em `db/migrate.ts` §3 porque o bloco de grants roda DEPOIS das
|
|
95
|
+
-- migrations e concede INSERT/UPDATE/DELETE em ALL TABLES. Aqui o estado já
|
|
96
|
+
-- nasce certo; lá ele sobrevive ao re-grant.
|
|
97
|
+
DO $$
|
|
98
|
+
BEGIN
|
|
99
|
+
IF EXISTS (SELECT FROM pg_roles WHERE rolname = 'baruk_app') THEN
|
|
100
|
+
EXECUTE 'GRANT SELECT ON service_principal TO baruk_app';
|
|
101
|
+
EXECUTE 'GRANT SELECT ON service_principal_grant TO baruk_app';
|
|
102
|
+
EXECUTE 'REVOKE INSERT, UPDATE, DELETE ON service_principal FROM baruk_app';
|
|
103
|
+
EXECUTE 'REVOKE INSERT, UPDATE, DELETE ON service_principal_grant FROM baruk_app';
|
|
104
|
+
END IF;
|
|
105
|
+
END $$;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
-- 0044 — Sessão de onboarding de canal (Embedded Signup da Meta)
|
|
2
|
+
--
|
|
3
|
+
-- O que esta tabela existe para impedir.
|
|
4
|
+
--
|
|
5
|
+
-- O Embedded Signup devolve, NO NAVEGADOR do operador, um `code` trocável por
|
|
6
|
+
-- um token de negócio do cliente. Quem recebe esse `code` no servidor precisa
|
|
7
|
+
-- responder três perguntas antes de trocá-lo:
|
|
8
|
+
--
|
|
9
|
+
-- 1. este `code` veio de um fluxo que ESTE tenant iniciou? (CSRF)
|
|
10
|
+
-- 2. é a PRIMEIRA vez que ele chega? (replay)
|
|
11
|
+
-- 3. o fluxo começou há pouco? (janela)
|
|
12
|
+
--
|
|
13
|
+
-- Nenhuma delas é respondível pelo `code` sozinho: ele é opaco e a Meta não o
|
|
14
|
+
-- amarra a nada nosso. A resposta é um `state` que nós emitimos, guardamos e
|
|
15
|
+
-- consumimos uma única vez — o mesmo mecanismo do OAuth clássico, e pela mesma
|
|
16
|
+
-- razão.
|
|
17
|
+
--
|
|
18
|
+
-- ── Por que o HASH, e não o state ───────────────────────────────────────────
|
|
19
|
+
--
|
|
20
|
+
-- O `state` em claro vive no navegador e volta pela requisição. Guardá-lo em
|
|
21
|
+
-- claro faria desta tabela uma lista de tokens de sessão de onboarding válidos:
|
|
22
|
+
-- qualquer leitura dela (um dump, um log de query, um SELECT de suporte)
|
|
23
|
+
-- entregaria a chave que autoriza a conclusão do fluxo de um escritório. O
|
|
24
|
+
-- hash responde "é este?" sem responder "qual é".
|
|
25
|
+
--
|
|
26
|
+
-- ── Por que o índice é GLOBAL ───────────────────────────────────────────────
|
|
27
|
+
--
|
|
28
|
+
-- A conclusão do fluxo roda no contexto do tenant autenticado, e o `state` é
|
|
29
|
+
-- procurado dentro dele — um state de outro escritório simplesmente não é
|
|
30
|
+
-- encontrado. O índice único global existe para a outra direção: garantir que o
|
|
31
|
+
-- mesmo hash não possa ser emitido duas vezes, nem por tenants distintos, o que
|
|
32
|
+
-- tornaria "consumido uma vez" uma afirmação sobre uma linha e não sobre o
|
|
33
|
+
-- valor.
|
|
34
|
+
|
|
35
|
+
CREATE TABLE IF NOT EXISTS channel_onboarding_session (
|
|
36
|
+
id uuid PRIMARY KEY,
|
|
37
|
+
tenant_id uuid NOT NULL REFERENCES tenant(id),
|
|
38
|
+
-- Produto e marca fazem parte da identidade do vínculo que este fluxo vai
|
|
39
|
+
-- criar (ver 0041). Guardá-los aqui é o que permite concluir o fluxo sem
|
|
40
|
+
-- reperguntá-los a quem chama — e portanto sem aceitar que a requisição de
|
|
41
|
+
-- conclusão os escolha.
|
|
42
|
+
product text NOT NULL,
|
|
43
|
+
brand text NOT NULL,
|
|
44
|
+
channel text NOT NULL CHECK (channel IN ('whatsapp')),
|
|
45
|
+
provider text NOT NULL CHECK (provider IN ('meta_whatsapp')),
|
|
46
|
+
-- sha256 hex do state. NUNCA o state.
|
|
47
|
+
state_hash text NOT NULL CHECK (state_hash ~ '^[0-9a-f]{64}$'),
|
|
48
|
+
-- `pending` → `consumed` (sucesso) | `failed` (troca recusada) | `expired`.
|
|
49
|
+
-- `failed` é terminal de propósito: um `code` cuja troca falhou não deve ser
|
|
50
|
+
-- retentado com o mesmo state, porque a falha pode ter sido a Meta já o ter
|
|
51
|
+
-- consumido — e a segunda tentativa não distingue "erro nosso" de "alguém
|
|
52
|
+
-- chegou antes".
|
|
53
|
+
status text NOT NULL DEFAULT 'pending'
|
|
54
|
+
CHECK (status IN ('pending', 'consumed', 'failed', 'expired')),
|
|
55
|
+
-- Quem abriu o fluxo. Humano, sempre: não existe caminho de máquina para
|
|
56
|
+
-- iniciar onboarding de canal.
|
|
57
|
+
created_by uuid REFERENCES "user"(id),
|
|
58
|
+
expires_at timestamptz NOT NULL,
|
|
59
|
+
consumed_at timestamptz,
|
|
60
|
+
-- Classe do erro, nunca a mensagem do provider. A mensagem da Meta pode
|
|
61
|
+
-- carregar identificadores do negócio do cliente.
|
|
62
|
+
failure_code text,
|
|
63
|
+
-- Identificadores devolvidos pelo fluxo, gravados DEPOIS do sucesso. Não são
|
|
64
|
+
-- segredo — a Meta os manda em texto claro em todo webhook.
|
|
65
|
+
waba_ref text,
|
|
66
|
+
phone_ref text,
|
|
67
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
68
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
69
|
+
CONSTRAINT channel_onboarding_session_tenant_id_uq UNIQUE (tenant_id, id)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
-- Global. Ver o bloco de comentário acima.
|
|
73
|
+
CREATE UNIQUE INDEX IF NOT EXISTS channel_onboarding_session_state_uq
|
|
74
|
+
ON channel_onboarding_session (state_hash);
|
|
75
|
+
|
|
76
|
+
-- Listagem do que está aberto, por tenant — é o que a tela consulta.
|
|
77
|
+
CREATE INDEX IF NOT EXISTS channel_onboarding_session_pending_idx
|
|
78
|
+
ON channel_onboarding_session (tenant_id, channel, created_at DESC)
|
|
79
|
+
WHERE status = 'pending';
|
|
80
|
+
|
|
81
|
+
ALTER TABLE channel_onboarding_session ENABLE ROW LEVEL SECURITY;
|
|
82
|
+
ALTER TABLE channel_onboarding_session FORCE ROW LEVEL SECURITY;
|
|
83
|
+
|
|
84
|
+
-- FORCE porque a política precisa valer inclusive para o dono da tabela — o
|
|
85
|
+
-- papel que roda migration. É a armadilha da 0035/0036, e ela não se repete
|
|
86
|
+
-- aqui.
|
|
87
|
+
CREATE POLICY channel_onboarding_session_tenant_isolation ON channel_onboarding_session
|
|
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
|
+
COMMENT ON TABLE channel_onboarding_session IS
|
|
92
|
+
'Sessão de Embedded Signup. Guarda o HASH do state, nunca o state. Consumo é único.';
|
|
93
|
+
COMMENT ON COLUMN channel_onboarding_session.state_hash IS
|
|
94
|
+
'sha256 hex do state emitido. O valor em claro só existe no navegador do operador.';
|
|
95
|
+
COMMENT ON COLUMN channel_onboarding_session.failure_code IS
|
|
96
|
+
'Classe do erro. Nunca a mensagem do provider — ela carrega identificadores do negócio do cliente.';
|