Skip to content

Feat/contracts validation zod 422 versioning#97

Closed
adm01-debug wants to merge 10 commits into
mainfrom
feat/contracts-validation-zod-422-versioning
Closed

Feat/contracts validation zod 422 versioning#97
adm01-debug wants to merge 10 commits into
mainfrom
feat/contracts-validation-zod-422-versioning

Conversation

@adm01-debug
Copy link
Copy Markdown
Owner

@adm01-debug adm01-debug commented May 22, 2026

📋 Descrição

🎯 Tipo de mudança

  • 🚀 feat — nova funcionalidade
  • 🐛 fix — correção de bug
  • ♻️ refactor — refatoração (sem mudança de comportamento)
  • 🔧 chore — manutenção, deps, config
  • 📚 docs — documentação
  • ⚡ perf — performance
  • 🔒 security — segurança
  • 🚨 hotfix — correção urgente em produção
  • 💥 breaking change — quebra compatibilidade

🔗 Issues relacionadas

Closes #
Refs #

🌐 Sistemas afetados

  • Bitrix24 (CRM, SPAs, BizProc)
  • Supabase (DB, Edge Functions, RLS, migrations)
  • n8n (workflows)
  • Evolution API / WhatsApp
  • Bling (NFe, OAuth)
  • Cloudflare (Workers, Images, Tunnels)
  • Frontend (UI, dashboards)
  • CI / GitHub Actions
  • Outro: ____

🧪 Como testar

✅ Checklist pré-merge

Qualidade

  • Código segue style guide (ESLint passa)
  • npx tsc --noEmit passa sem erros
  • Testes passam (npm run test)
  • Adicionei testes para novas funcionalidades quando aplicável
  • CodeRabbit revisou o PR (ou justificativa para skip)

Segurança

  • Sem secrets, tokens ou credenciais hardcoded
  • Variáveis de ambiente novas documentadas
  • Sem console.log com payloads sensíveis (usar logger.*)
  • RLS revisado se houve mudança em tabelas
  • Edge functions: input validado com Zod

Documentação

  • Atualizei docs (README / CHANGELOG / docs/) se necessário
  • Memória atualizada (mem://) se a mudança afetar arquitetura/regras
  • Migrations com backup em _backup_*_YYYYMMDD se destrutivas

UI

  • Componentes usam tokens semânticos (sem cores hardcoded)
  • Screenshots / vídeo anexados (se mudança visual)

📸 Screenshots (se UI)

🔄 Plano de rollback

⚠️ Notas para o reviewer


Summary by cubic

Adds a shared contract validation package with Zod, version negotiation, and a single error format, then migrates key Edge Functions to use it. Clients can select versions via accept-version/?v= and get Deprecation/Sunset headers when on v1.

  • New Features

    • Introduces supabase/functions/_shared/contracts with parseContract, versioning, and standardized errors {code, message, fields[]} (422 for schema errors, 400 for invalid JSON/missing body, 406 for unsupported version).
    • Adds v1/v2 contracts and migrates handlers for 16 Edge Functions (including product-webhook, webhook-inbound, webhook-dispatcher, and related internal flows).
    • Version resolution via accept-version header or ?v=; deprecated versions include Deprecation and Sunset headers plus migration links.
    • Docs: docs/contracts/README.md and docs/contracts/MIGRATION_GUIDE.md; tests: new contract/unit suites and updated vitest.config.ts alias for zod; rewrites scripts/contract-testing.mjs for smoke testing.
  • Migration

    • No breaking changes now; v1 is default and returns Deprecation/Sunset. Opt in to v2 with accept-version: 2 (or ?v=2).
    • Update clients to handle 422/406 with the new error shape. Use the guide in docs/contracts/MIGRATION_GUIDE.md for remaining functions and timelines.

Written for commit b2d8da6. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

Release Notes

  • New Features

    • Adicionado sistema de validação de contratos para Edge Functions com suporte a versionamento de API.
    • Implementado suporte a múltiplas versões de API com negociação automática via headers e query parameters.
    • Estrutura padronizada de respostas de erro com validação em nível de campo.
  • Documentation

    • Guia de migração para endpoints legados.
    • Documentação completa do sistema de validação e versionamento.
  • Tests

    • Suite de testes para validação de contratos de API.

Review Change Stack

…rseContract (backward-compat v1; opt-in v2 via accept-version)
…t of 14 funcs + 5-step recipe + special cases)
…rseContract (#69)

* feat(contracts): P0 schemas (1/4) — send-transactional-email, kit-ai-builder, bi-copilot

* feat(contracts): P0 schemas (2/4) — market-intelligence-insights, step-up-verify

* feat(contracts): P1 schemas (3/4) — ownership-audit, ownership-repair, simulation-orchestrator, sync-external-db

* feat(contracts): P1+P2 schemas (4/4) — trends-insights, force-global-logout, e2e-cleanup, block-ip-temporarily

* feat(contracts): handler P0 — send-transactional-email migrado para parseContract

* feat(contracts): handlers P0 — kit-ai-builder, bi-copilot migrados para parseContract

* feat(contracts): handler P0 — market-intelligence-insights migrado para parseContract

* feat(contracts): handler P0 — step-up-verify migrado para parseContract

* feat(contracts): handlers P1 (1/2) — ownership-audit, ownership-repair, sync-external-db, trends-insights

* feat(contracts): handler P1 (2/2) — simulation-orchestrator migrado para parseContract

* feat(contracts): handlers P2 (1/2) — force-global-logout, block-ip-temporarily

* feat(contracts): handler P2 (2/2) — e2e-cleanup migrado para parseContract

* test(contracts): adiciona 49 testes de contrato para os 13 endpoints migrados
Copilot AI review requested due to automatic review settings May 22, 2026 10:17
@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
we-dream-big Ready Ready Preview, Comment May 22, 2026 10:17am

@supabase
Copy link
Copy Markdown

supabase Bot commented May 22, 2026

Updates to Preview Branch (feat/contracts-validation-zod-422-versioning) ↗︎

Deployments Status Updated
Database Fri, 22 May 2026 10:17:42 UTC
Services Fri, 22 May 2026 10:17:42 UTC
APIs Fri, 22 May 2026 10:17:42 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Fri, 22 May 2026 10:17:48 UTC
Migrations Fri, 22 May 2026 10:17:58 UTC
Seeding ⏸️ Fri, 22 May 2026 10:17:36 UTC
Edge Functions ⏸️ Fri, 22 May 2026 10:17:36 UTC

❌ Branch Error • Fri, 22 May 2026 10:17:59 UTC

ERROR: syntax error at or near "LS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Ci0tIEdJRlRTIFNUT1JFIC0gUkxTIENPTSBPUkdBTklaQVRJT05TIChNVUxUSS1URU5BTlQpCi0tIEFwbGljYSBSb3cgTGV2ZWwgU2VjdXJpdHkgYmFzZWFkbyBlbSBPcmdhbml6YXRpb25zCi0tIERhdGE6IDAzLzAxLzIwMjUKLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Ci0tIEd1YXJkOiB0aGlzIGVudGlyZSBtaWdyYXRpb24gaXMgd3JhcHBlZCBpbiBhIERPIGJsb2NrIHNvIGl0Ci0tIGV4aXRzIGNsZWFubHkgd2hlbiBwdWJsaWMub3JnYW5pemF0aW9ucyBkb2Vzbid0IGV4aXN0IHlldC4KLS0gT24gYSBmcmVzaCBTdXBhYmFzZSBQcmV2aWV3IEJyYW5jaCB0aGF0IHJlcGxheXMgYWxsIG1pZ3JhdGlvbnMKLS0gZnJvbSBzY3JhdGNoLCBvcmdhbml6YXRpb25zIGlzIGNyZWF0ZWQgbGF0ZXIgaW4gdGhlIHNlcXVlbmNlLgotLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCkRPICRvdXRlciQKQkVHSU4KICBJRiBOT1QgRVhJU1RTICgKICAgIFNFTEVDVCAxIEZST00gaW5mb3JtYXRpb25fc2NoZW1hLnRhYmxlcwogICAgV0hFUkUgdGFibGVfc2NoZW1hID0gJ3B1YmxpYycgQU5EIHRhYmxlX25hbWUgPSAnb3JnYW5pemF0aW9ucycKICApIFRIRU4KICAgIFJBSVNFIE5PVElDRSAnTWlncmF0aW9uIDIwMjUwMTAzXzAyX3Jsc19vcmdhbml6YXRpb25zIHNraXBwZWQ6IHB1YmxpYy5vcmdhbml6YXRpb25zIGRvZXMgbm90IGV4aXN0IHlldC4nOwogICAgUkVUVVJOOwogIEVORCBJRjsKCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAgLS0gUEFSVEUgMTogQURJQ0lPTkFSIG9yZ2FuaXphdGlvbl9pZCBOQVMgVEFCRUxBUyBQUklOQ0lQQUlTCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5jYXRlZ29yaWVzCiAgICBBREQgQ09MVU1OIElGIE5PVCBFWElTVFMgb3JnYW5pemF0aW9uX2lkIFVVSUQgUkVGRVJFTkNFUyBwdWJsaWMub3JnYW5pemF0aW9ucyhpZCkgT04gREVMRVRFIENBU0NBREU7CiAgQ1JFQVRFIElOREVYIElGIE5PVCBFWElTVFMgaWR4X2NhdGVnb3JpZXNfb3JnIE9OIHB1YmxpYy5jYXRlZ29yaWVzKG9yZ2FuaXphdGlvbl9pZCk7CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5zdXBwbGllcnMKICAgIEFERCBDT0xVTU4gSUYgTk9UIEVYSVNUUyBvcmdhbml6YXRpb25faWQgVVVJRCBSRUZFUkVOQ0VTIHB1YmxpYy5vcmdhbml6YXRpb25zKGlkKSBPTiBERUxFVEUgQ0FTQ0FERTsKICBDUkVBVEUgSU5ERVggSUYgTk9UIEVYSVNUUyBpZHhfc3VwcGxpZXJzX29yZyBPTiBwdWJsaWMuc3VwcGxpZXJzKG9yZ2FuaXphdGlvbl9pZCk7CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5wcm9kdWN0cwogICAgQUREIENPTFVNTiBJRiBOT1QgRVhJU1RTIG9yZ2FuaXphdGlvbl9pZCBVVUlEIFJFRkVSRU5DRVMgcHVibGljLm9yZ2FuaXphdGlvbnMoaWQpIE9OIERFTEVURSBDQVNDQURFOwogIENSRUFURSBJTkRFWCBJRiBOT1QgRVhJU1RTIGlkeF9wcm9kdWN0c19vcmcgT04gcHVibGljLnByb2R1Y3RzKG9yZ2FuaXphdGlvbl9pZCk7CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5xdW90ZXMKICAgIEFERCBDT0xVTU4gSUYgTk9UIEVYSVNUUyBvcmdhbml6YXRpb25faWQgVVVJRCBSRUZFUkVOQ0VTIHB1YmxpYy5vcmdhbml6YXRpb25zKGlkKSBPTiBERUxFVEUgQ0FTQ0FERTsKICBDUkVBVEUgSU5ERVggSUYgTk9UIEVYSVNUUyBpZHhfcXVvdGVzX29yZyBPTiBwdWJsaWMucXVvdGVzKG9yZ2FuaXphdGlvbl9pZCk7CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5vcmRlcnMKICAgIEFERCBDT0xVTU4gSUYgTk9UIEVYSVNUUyBvcmdhbml6YXRpb25faWQgVVVJRCBSRUZFUkVOQ0VTIHB1YmxpYy5vcmdhbml6YXRpb25zKGlkKSBPTiBERUxFVEUgQ0FTQ0FERTsKICBDUkVBVEUgSU5ERVggSUYgTk9UIEVYSVNUUyBpZHhfb3JkZXJzX29yZyBPTiBwdWJsaWMub3JkZXJzKG9yZ2FuaXphdGlvbl9pZCk7CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5iaXRyaXhfY2xpZW50cwogICAgQUREIENPTFVNTiBJRiBOT1QgRVhJU1RTIG9yZ2FuaXphdGlvbl9pZCBVVUlEIFJFRkVSRU5DRVMgcHVibGljLm9yZ2FuaXphdGlvbnMoaWQpIE9OIERFTEVURSBDQVNDQURFOwogIENSRUFURSBJTkRFWCBJRiBOT1QgRVhJU1RTIGlkeF9iaXRyaXhfY2xpZW50c19vcmcgT04gcHVibGljLmJpdHJpeF9jbGllbnRzKG9yZ2FuaXphdGlvbl9pZCk7CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5tb2NrdXBfZ2VuZXJhdGlvbl9qb2JzCiAgICBBREQgQ09MVU1OIElGIE5PVCBFWElTVFMgb3JnYW5pemF0aW9uX2lkIFVVSUQgUkVGRVJFTkNFUyBwdWJsaWMub3JnYW5pemF0aW9ucyhpZCkgT04gREVMRVRFIENBU0NBREU7CiAgQ1JFQVRFIElOREVYIElGIE5PVCBFWElTVFMgaWR4X21vY2t1cF9qb2JzX29yZyBPTiBwdWJsaWMubW9ja3VwX2dlbmVyYXRpb25fam9icyhvcmdhbml6YXRpb25faWQpOwoKICBBTFRFUiBUQUJMRSBwdWJsaWMuY29sbGVjdGlvbnMKICAgIEFERCBDT0xVTU4gSUYgTk9UIEVYSVNUUyBvcmdhbml6YXRpb25faWQgVVVJRCBSRUZFUkVOQ0VTIHB1YmxpYy5vcmdhbml6YXRpb25zKGlkKSBPTiBERUxFVEUgQ0FTQ0FERTsKICBDUkVBVEUgSU5ERVggSUYgTk9UIEVYSVNUUyBpZHhfY29sbGVjdGlvbnNfb3JnIE9OIHB1YmxpYy5jb2xsZWN0aW9ucyhvcmdhbml6YXRpb25faWQpOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSAyOiBGVU7Dh8ODTyBIRUxQRVIgLSBWZXJpZmljYXIgc2UgdXNlciBwZXJ0ZW5jZSDDoCBvcmcKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgQ1JFQVRFIE9SIFJFUExBQ0UgRlVOQ1RJT04gcHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdfaWQgVVVJRCkKICBSRVRVUk5TIEJPT0xFQU4gQVMgJGZuJAogIEJFR0lOCiAgICBSRVRVUk4gRVhJU1RTICgKICAgICAgU0VMRUNUIDEKICAgICAgRlJPTSBwdWJsaWMudXNlcl9vcmdhbml6YXRpb25zCiAgICAgIFdIRVJFIG9yZ2FuaXphdGlvbl9pZCA9IG9yZ19pZAogICAgICAgIEFORCB1c2VyX2lkID0gYXV0aC51aWQoKQogICAgKTsKICBFTkQ7CiAgJGZuJCBMQU5HVUFHRSBwbHBnc3FsIFNFQ1VSSVRZIERFRklORVIgU1RBQkxFOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSAzOiBBUExJQ0FSIFJMUyBFTSBUT0RBUyBBUyBUQUJFTEFTCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5jYXRlZ29yaWVzIEVOQUJMRSBST1cgTEVWRUwgU0VDVVJJVFk7CiAgQUxURVIgVEFCTEUgcHVibGljLnN1cHBsaWVycyBFTkFCTEUgUk9XIExFVkVMIFNFQ1VSSVRZOwogIEFMVEVSIFRBQkxFIHB1YmxpYy5wcm9kdWN0cyBFTkFCTEUgUk9XIExFVkVMIFNFQ1VSSVRZOwogIEFMVEVSIFRBQkxFIHB1YmxpYy5wcm9kdWN0X3ZhcmlhbnRzIEVOQUJMRSBST1cgTEVWRUwgU0VDVVJJVFk7CiAgQUxURVIgVEFCTEUgcHVibGljLnF1b3RlcyBFTkFCTEUgUk9XIExFVkVMIFNFQ1VSSVRZOwogIEFMVEVSIFRBQkxFIHB1YmxpYy5xdW90ZV9pdGVtcyBFTkFCTEUgUk9XIExFVkVMIFNFQ1VSSVRZOwogIEFMVEVSIFRBQkxFIHB1YmxpYy5vcmRlcnMgRU5BQkxFIFJPVyBMRVZFTCBTRUNVUklUWTsKICBBTFRFUiBUQUJMRSBwdWJsaWMub3JkZXJfaXRlbXMgRU5BQkxFIFJPVyBMRVZFTCBTRUNVUklUWTsKICBJRiBFWElTVFMgKFNFTEVDVCAxIEZST00gaW5mb3JtYXRpb25fc2NoZW1hLnRhYmxlcyBXSEVSRSB0YWJsZV9zY2hlbWE9J3B1YmxpYycgQU5EIHRhYmxlX25hbWU9J3BheW1lbnRzJykgVEhFTgogICAgQUxURVIgVEFCTEUgcHVibGljLnBheW1lbnRzIEVOQUJMRSBST1cgTEVWRUwgU0VDVVJJVFk7CiAgRU5EIElGOwogIEFMVEVSIFRBQkxFIHB1YmxpYy5iaXRyaXhfY2xpZW50cyBFTkFCTEUgUk9XIExFVkVMIFNFQ1VSSVRZOwogIEFMVEVSIFRBQkxFIHB1YmxpYy5tb2NrdXBfZ2VuZXJhdGlvbl9qb2JzIEVOQUJMRSBST1cgTEVWRUwgU0VDVVJJVFk7CiAgQUxURVIgVEFCTEUgcHVibGljLmdlbmVyYXRlZF9tb2NrdXBzIEVOQUJMRSBST1cgTEVWRUwgU0VDVVJJVFk7CiAgQUxURVIgVEFCTEUgcHVibGljLmNvbGxlY3Rpb25zIEVOQUJMRSBST1cgTEVWRUwgU0VDVVJJVFk7CiAgQUxURVIgVEFCTEUgcHVibGljLmNvbGxlY3Rpb25fcHJvZHVjdHMgRU5BQkxFIFJPVyBMRVZFTCBTRUNVUklUWTsKICBBTFRFUiBUQUJMRSBwdWJsaWMucGVyc29uYWxpemF0aW9uX3RlY2huaXF1ZXMgRU5BQkxFIFJPVyBMRVZFTCBTRUNVUklUWTsKICBBTFRFUiBUQUJMRSBwdWJsaWMubm90aWZpY2F0aW9ucyBFTkFCTEUgUk9XIExFVkVMIFNFQ1VSSVRZOwogIEFMVEVSIFRBQkxFIHB1YmxpYy5mZWF0dXJlX2ZsYWdzIEVOQUJMRSBST1cgTEVWRUwgU0VDVVJJVFk7CiAgQUxURVIgVEFCTEUgcHVibGljLnN5c3RlbV9zZXR0aW5ncyBFTkFCTEUgUk9XIExFVkVMIFNFQ1VSSVRZOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSA0OiBQT0xJQ0lFUyAtIENBVEVHT1JJRVMKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc192aWV3X2NhdGVnb3JpZXMiIE9OIHB1YmxpYy5jYXRlZ29yaWVzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19tZW1iZXJzX3ZpZXdfY2F0ZWdvcmllcyIKICAgIE9OIHB1YmxpYy5jYXRlZ29yaWVzIEZPUiBTRUxFQ1QgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKHB1YmxpYy51c2VyX2lzX29yZ19tZW1iZXIob3JnYW5pemF0aW9uX2lkKSk7CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAib3JnX2FkbWluc19jcmVhdGVfY2F0ZWdvcmllcyIgT04gcHVibGljLmNhdGVnb3JpZXM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX2FkbWluc19jcmVhdGVfY2F0ZWdvcmllcyIKICAgIE9OIHB1YmxpYy5jYXRlZ29yaWVzIEZPUiBJTlNFUlQgVE8gYXV0aGVudGljYXRlZAogICAgV0lUSCBDSEVDSyAocHVibGljLmlzX29yZ19vd25lcl9vcl9hZG1pbihvcmdhbml6YXRpb25faWQpKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfYWRtaW5zX3VwZGF0ZV9jYXRlZ29yaWVzIiBPTiBwdWJsaWMuY2F0ZWdvcmllczsKICBDUkVBVEUgUE9MSUNZICJvcmdfYWRtaW5zX3VwZGF0ZV9jYXRlZ29yaWVzIgogICAgT04gcHVibGljLmNhdGVnb3JpZXMgRk9SIFVQREFURSBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAocHVibGljLmlzX29yZ19vd25lcl9vcl9hZG1pbihvcmdhbml6YXRpb25faWQpKQogICAgV0lUSCBDSEVDSyAocHVibGljLmlzX29yZ19vd25lcl9vcl9hZG1pbihvcmdhbml6YXRpb25faWQpKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfYWRtaW5zX2RlbGV0ZV9jYXRlZ29yaWVzIiBPTiBwdWJsaWMuY2F0ZWdvcmllczsKICBDUkVBVEUgUE9MSUNZICJvcmdfYWRtaW5zX2RlbGV0ZV9jYXRlZ29yaWVzIgogICAgT04gcHVibGljLmNhdGVnb3JpZXMgRk9SIERFTEVURSBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAocHVibGljLmlzX29yZ19vd25lcl9vcl9hZG1pbihvcmdhbml6YXRpb25faWQpKTsKCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAgLS0gUEFSVEUgNTogUE9MSUNJRVMgLSBTVVBQTElFUlMKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc192aWV3X3N1cHBsaWVycyIgT04gcHVibGljLnN1cHBsaWVyczsKICBDUkVBVEUgUE9MSUNZICJvcmdfbWVtYmVyc192aWV3X3N1cHBsaWVycyIKICAgIE9OIHB1YmxpYy5zdXBwbGllcnMgRk9SIFNFTEVDVCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAocHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfYWRtaW5zX21hbmFnZV9zdXBwbGllcnMiIE9OIHB1YmxpYy5zdXBwbGllcnM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX2FkbWluc19tYW5hZ2Vfc3VwcGxpZXJzIgogICAgT04gcHVibGljLnN1cHBsaWVycyBGT1IgQUxMIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HIChwdWJsaWMuaXNfb3JnX293bmVyX29yX2FkbWluKG9yZ2FuaXphdGlvbl9pZCkpCiAgICBXSVRIIENIRUNLIChwdWJsaWMuaXNfb3JnX293bmVyX29yX2FkbWluKG9yZ2FuaXphdGlvbl9pZCkpOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSA2OiBQT0xJQ0lFUyAtIFBST0RVQ1RTCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAib3JnX21lbWJlcnNfdmlld19wcm9kdWN0cyIgT04gcHVibGljLnByb2R1Y3RzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19tZW1iZXJzX3ZpZXdfcHJvZHVjdHMiCiAgICBPTiBwdWJsaWMucHJvZHVjdHMgRk9SIFNFTEVDVCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAocHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfYWRtaW5zX21hbmFnZV9wcm9kdWN0cyIgT04gcHVibGljLnByb2R1Y3RzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19hZG1pbnNfbWFuYWdlX3Byb2R1Y3RzIgogICAgT04gcHVibGljLnByb2R1Y3RzIEZPUiBBTEwgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKHB1YmxpYy5pc19vcmdfb3duZXJfb3JfYWRtaW4ob3JnYW5pemF0aW9uX2lkKSkKICAgIFdJVEggQ0hFQ0sgKHB1YmxpYy5pc19vcmdfb3duZXJfb3JfYWRtaW4ob3JnYW5pemF0aW9uX2lkKSk7CgogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogIC0tIFBBUlRFIDc6IFBPTElDSUVTIC0gUFJPRFVDVF9WQVJJQU5UUwogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19tZW1iZXJzX3ZpZXdfdmFyaWFudHMiIE9OIHB1YmxpYy5wcm9kdWN0X3ZhcmlhbnRzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19tZW1iZXJzX3ZpZXdfdmFyaWFudHMiCiAgICBPTiBwdWJsaWMucHJvZHVjdF92YXJpYW50cyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HICgKICAgICAgRVhJU1RTICgKICAgICAgICBTRUxFQ1QgMSBGUk9NIHB1YmxpYy5wcm9kdWN0cwogICAgICAgIFdIRVJFIGlkID0gcHJvZHVjdF92YXJpYW50cy5wcm9kdWN0X2lkCiAgICAgICAgICBBTkQgcHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpCiAgICAgICkKICAgICk7CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAib3JnX2FkbWluc19tYW5hZ2VfdmFyaWFudHMiIE9OIHB1YmxpYy5wcm9kdWN0X3ZhcmlhbnRzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19hZG1pbnNfbWFuYWdlX3ZhcmlhbnRzIgogICAgT04gcHVibGljLnByb2R1Y3RfdmFyaWFudHMgRk9SIEFMTCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAoCiAgICAgIEVYSVNUUyAoCiAgICAgICAgU0VMRUNUIDEgRlJPTSBwdWJsaWMucHJvZHVjdHMKICAgICAgICBXSEVSRSBpZCA9IHByb2R1Y3RfdmFyaWFudHMucHJvZHVjdF9pZAogICAgICAgICAgQU5EIHB1YmxpYy5pc19vcmdfb3duZXJfb3JfYWRtaW4ob3JnYW5pemF0aW9uX2lkKQogICAgICApCiAgICApCiAgICBXSVRIIENIRUNLICgKICAgICAgRVhJU1RTICgKICAgICAgICBTRUxFQ1QgMSBGUk9NIHB1YmxpYy5wcm9kdWN0cwogICAgICAgIFdIRVJFIGlkID0gcHJvZHVjdF92YXJpYW50cy5wcm9kdWN0X2lkCiAgICAgICAgICBBTkQgcHVibGljLmlzX29yZ19vd25lcl9vcl9hZG1pbihvcmdhbml6YXRpb25faWQpCiAgICAgICkKICAgICk7CgogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogIC0tIFBBUlRFIDg6IFBPTElDSUVTIC0gUVVPVEVTCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAib3JnX21lbWJlcnNfdmlld19xdW90ZXMiIE9OIHB1YmxpYy5xdW90ZXM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX21lbWJlcnNfdmlld19xdW90ZXMiCiAgICBPTiBwdWJsaWMucXVvdGVzIEZPUiBTRUxFQ1QgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKHB1YmxpYy51c2VyX2lzX29yZ19tZW1iZXIob3JnYW5pemF0aW9uX2lkKSk7CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAib3JnX21lbWJlcnNfY3JlYXRlX3F1b3RlcyIgT04gcHVibGljLnF1b3RlczsKICBDUkVBVEUgUE9MSUNZICJvcmdfbWVtYmVyc19jcmVhdGVfcXVvdGVzIgogICAgT04gcHVibGljLnF1b3RlcyBGT1IgSU5TRVJUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFdJVEggQ0hFQ0sgKHB1YmxpYy51c2VyX2lzX29yZ19tZW1iZXIob3JnYW5pemF0aW9uX2lkKSk7CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAib3JnX21lbWJlcnNfdXBkYXRlX293bl9xdW90ZXMiIE9OIHB1YmxpYy5xdW90ZXM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX21lbWJlcnNfdXBkYXRlX293bl9xdW90ZXMiCiAgICBPTiBwdWJsaWMucXVvdGVzIEZPUiBVUERBVEUgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKAogICAgICBwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkKICAgICAgQU5EIChjcmVhdGVkX2J5ID0gYXV0aC51aWQoKSBPUiBwdWJsaWMuaXNfb3JnX2FkbWluKG9yZ2FuaXphdGlvbl9pZCkpCiAgICApOwoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19hZG1pbnNfZGVsZXRlX3F1b3RlcyIgT04gcHVibGljLnF1b3RlczsKICBDUkVBVEUgUE9MSUNZICJvcmdfYWRtaW5zX2RlbGV0ZV9xdW90ZXMiCiAgICBPTiBwdWJsaWMucXVvdGVzIEZPUiBERUxFVEUgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKHB1YmxpYy5pc19vcmdfb3duZXJfb3JfYWRtaW4ob3JnYW5pemF0aW9uX2lkKSk7CgogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogIC0tIFBBUlRFIDk6IFBPTElDSUVTIC0gUVVPVEVfSVRFTVMKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc192aWV3X3F1b3RlX2l0ZW1zIiBPTiBwdWJsaWMucXVvdGVfaXRlbXM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX21lbWJlcnNfdmlld19xdW90ZV9pdGVtcyIKICAgIE9OIHB1YmxpYy5xdW90ZV9pdGVtcyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HICgKICAgICAgRVhJU1RTICgKICAgICAgICBTRUxFQ1QgMSBGUk9NIHB1YmxpYy5xdW90ZXMKICAgICAgICBXSEVSRSBpZCA9IHF1b3RlX2l0ZW1zLnF1b3RlX2lkCiAgICAgICAgICBBTkQgcHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpCiAgICAgICkKICAgICk7CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAib3JnX21lbWJlcnNfbWFuYWdlX3F1b3RlX2l0ZW1zIiBPTiBwdWJsaWMucXVvdGVfaXRlbXM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX21lbWJlcnNfbWFuYWdlX3F1b3RlX2l0ZW1zIgogICAgT04gcHVibGljLnF1b3RlX2l0ZW1zIEZPUiBBTEwgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKAogICAgICBFWElTVFMgKAogICAgICAgIFNFTEVDVCAxIEZST00gcHVibGljLnF1b3RlcwogICAgICAgIFdIRVJFIGlkID0gcXVvdGVfaXRlbXMucXVvdGVfaWQKICAgICAgICAgIEFORCAoY3JlYXRlZF9ieSA9IGF1dGgudWlkKCkgT1IgcHVibGljLmlzX29yZ19hZG1pbihvcmdhbml6YXRpb25faWQpKQogICAgICApCiAgICApCiAgICBXSVRIIENIRUNLICgKICAgICAgRVhJU1RTICgKICAgICAgICBTRUxFQ1QgMSBGUk9NIHB1YmxpYy5xdW90ZXMKICAgICAgICBXSEVSRSBpZCA9IHF1b3RlX2l0ZW1zLnF1b3RlX2lkCiAgICAgICAgICBBTkQgcHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpCiAgICAgICkKICAgICk7CgogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogIC0tIFBBUlRFIDEwOiBQT0xJQ0lFUyAtIE9SREVSUwogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19tZW1iZXJzX3ZpZXdfb3JkZXJzIiBPTiBwdWJsaWMub3JkZXJzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19tZW1iZXJzX3ZpZXdfb3JkZXJzIgogICAgT04gcHVibGljLm9yZGVycyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HIChwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkpOwoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19tZW1iZXJzX2NyZWF0ZV9vcmRlcnMiIE9OIHB1YmxpYy5vcmRlcnM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX21lbWJlcnNfY3JlYXRlX29yZGVycyIKICAgIE9OIHB1YmxpYy5vcmRlcnMgRk9SIElOU0VSVCBUTyBhdXRoZW50aWNhdGVkCiAgICBXSVRIIENIRUNLIChwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkpOwoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19tZW1iZXJzX3VwZGF0ZV9vd25fb3JkZXJzIiBPTiBwdWJsaWMub3JkZXJzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19tZW1iZXJzX3VwZGF0ZV9vd25fb3JkZXJzIgogICAgT04gcHVibGljLm9yZGVycyBGT1IgVVBEQVRFIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HICgKICAgICAgcHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpCiAgICAgIEFORCAoY3JlYXRlZF9ieSA9IGF1dGgudWlkKCkgT1IgcHVibGljLmlzX29yZ19hZG1pbihvcmdhbml6YXRpb25faWQpKQogICAgKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfYWRtaW5zX2RlbGV0ZV9vcmRlcnMiIE9OIHB1YmxpYy5vcmRlcnM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX2FkbWluc19kZWxldGVfb3JkZXJzIgogICAgT04gcHVibGljLm9yZGVycyBGT1IgREVMRVRFIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HIChwdWJsaWMuaXNfb3JnX293bmVyX29yX2FkbWluKG9yZ2FuaXphdGlvbl9pZCkpOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSAxMTogUE9MSUNJRVMgLSBPUkRFUl9JVEVNUwogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19tZW1iZXJzX3ZpZXdfb3JkZXJfaXRlbXMiIE9OIHB1YmxpYy5vcmRlcl9pdGVtczsKICBDUkVBVEUgUE9MSUNZICJvcmdfbWVtYmVyc192aWV3X29yZGVyX2l0ZW1zIgogICAgT04gcHVibGljLm9yZGVyX2l0ZW1zIEZPUiBTRUxFQ1QgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKAogICAgICBFWElTVFMgKAogICAgICAgIFNFTEVDVCAxIEZST00gcHVibGljLm9yZGVycwogICAgICAgIFdIRVJFIGlkID0gb3JkZXJfaXRlbXMub3JkZXJfaWQKICAgICAgICAgIEFORCBwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkKICAgICAgKQogICAgKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc19tYW5hZ2Vfb3JkZXJfaXRlbXMiIE9OIHB1YmxpYy5vcmRlcl9pdGVtczsKICBDUkVBVEUgUE9MSUNZICJvcmdfbWVtYmVyc19tYW5hZ2Vfb3JkZXJfaXRlbXMiCiAgICBPTiBwdWJsaWMub3JkZXJfaXRlbXMgRk9SIEFMTCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAoCiAgICAgIEVYSVNUUyAoCiAgICAgICAgU0VMRUNUIDEgRlJPTSBwdWJsaWMub3JkZXJzCiAgICAgICAgV0hFUkUgaWQgPSBvcmRlcl9pdGVtcy5vcmRlcl9pZAogICAgICAgICAgQU5EIChjcmVhdGVkX2J5ID0gYXV0aC51aWQoKSBPUiBwdWJsaWMuaXNfb3JnX2FkbWluKG9yZ2FuaXphdGlvbl9pZCkpCiAgICAgICkKICAgICkKICAgIFdJVEggQ0hFQ0sgKAogICAgICBFWElTVFMgKAogICAgICAgIFNFTEVDVCAxIEZST00gcHVibGljLm9yZGVycwogICAgICAgIFdIRVJFIGlkID0gb3JkZXJfaXRlbXMub3JkZXJfaWQKICAgICAgICAgIEFORCBwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkKICAgICAgKQogICAgKTsKCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAgLS0gUEFSVEUgMTI6IFBPTElDSUVTIC0gUEFZTUVOVFMgKGd1YXJkZWQ6IHRhYmxlIG1heSBub3QgZXhpc3QgeWV0KQogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKICBJRiBFWElTVFMgKFNFTEVDVCAxIEZST00gaW5mb3JtYXRpb25fc2NoZW1hLnRhYmxlcyBXSEVSRSB0YWJsZV9zY2hlbWE9J3B1YmxpYycgQU5EIHRhYmxlX25hbWU9J3BheW1lbnRzJykgVEhFTgogICAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc192aWV3X3BheW1lbnRzIiBPTiBwdWJsaWMucGF5bWVudHM7CiAgICBDUkVBVEUgUE9MSUNZICJvcmdfbWVtYmVyc192aWV3X3BheW1lbnRzIgogICAgICBPTiBwdWJsaWMucGF5bWVudHMgRk9SIFNFTEVDVCBUTyBhdXRoZW50aWNhdGVkCiAgICAgIFVTSU5HICgKICAgICAgICBFWElTVFMgKAogICAgICAgICAgU0VMRUNUIDEgRlJPTSBwdWJsaWMub3JkZXJzCiAgICAgICAgICBXSEVSRSBpZCA9IHBheW1lbnRzLm9yZGVyX2lkCiAgICAgICAgICAgIEFORCBwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkKICAgICAgICApCiAgICAgICk7CgogICAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfYWRtaW5zX21hbmFnZV9wYXltZW50cyIgT04gcHVibGljLnBheW1lbnRzOwogICAgQ1JFQVRFIFBPTElDWSAib3JnX2FkbWluc19tYW5hZ2VfcGF5bWVudHMiCiAgICAgIE9OIHB1YmxpYy5wYXltZW50cyBGT1IgQUxMIFRPIGF1dGhlbnRpY2F0ZWQKICAgICAgVVNJTkcgKAogICAgICAgIEVYSVNUUyAoCiAgICAgICAgICBTRUxFQ1QgMSBGUk9NIHB1YmxpYy5vcmRlcnMKICAgICAgICAgIFdIRVJFIGlkID0gcGF5bWVudHMub3JkZXJfaWQKICAgICAgICAgICAgQU5EIHB1YmxpYy5pc19vcmdfYWRtaW4ob3JnYW5pemF0aW9uX2lkKQogICAgICAgICkKICAgICAgKQogICAgICBXSVRIIENIRUNLICgKICAgICAgICBFWElTVFMgKAogICAgICAgICAgU0VMRUNUIDEgRlJPTSBwdWJsaWMub3JkZXJzCiAgICAgICAgICBXSEVSRSBpZCA9IHBheW1lbnRzLm9yZGVyX2lkCiAgICAgICAgICAgIEFORCBwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkKICAgICAgICApCiAgICAgICk7CiAgRU5EIElGOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSAxMzogUE9MSUNJRVMgLSBCSVRSSVhfQ0xJRU5UUwogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19tZW1iZXJzX3ZpZXdfY2xpZW50cyIgT04gcHVibGljLmJpdHJpeF9jbGllbnRzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19tZW1iZXJzX3ZpZXdfY2xpZW50cyIKICAgIE9OIHB1YmxpYy5iaXRyaXhfY2xpZW50cyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HIChwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkpOwoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19hZG1pbnNfbWFuYWdlX2NsaWVudHMiIE9OIHB1YmxpYy5iaXRyaXhfY2xpZW50czsKICBDUkVBVEUgUE9MSUNZICJvcmdfYWRtaW5zX21hbmFnZV9jbGllbnRzIgogICAgT04gcHVibGljLmJpdHJpeF9jbGllbnRzIEZPUiBBTEwgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKHB1YmxpYy5pc19vcmdfb3duZXJfb3JfYWRtaW4ob3JnYW5pemF0aW9uX2lkKSkKICAgIFdJVEggQ0hFQ0sgKHB1YmxpYy5pc19vcmdfb3duZXJfb3JfYWRtaW4ob3JnYW5pemF0aW9uX2lkKSk7CgogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogIC0tIFBBUlRFIDE0OiBQT0xJQ0lFUyAtIE1PQ0tVUFMKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc192aWV3X21vY2t1cF9qb2JzIiBPTiBwdWJsaWMubW9ja3VwX2dlbmVyYXRpb25fam9iczsKICBDUkVBVEUgUE9MSUNZICJvcmdfbWVtYmVyc192aWV3X21vY2t1cF9qb2JzIgogICAgT04gcHVibGljLm1vY2t1cF9nZW5lcmF0aW9uX2pvYnMgRk9SIFNFTEVDVCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAocHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc19jcmVhdGVfbW9ja3VwX2pvYnMiIE9OIHB1YmxpYy5tb2NrdXBfZ2VuZXJhdGlvbl9qb2JzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19tZW1iZXJzX2NyZWF0ZV9tb2NrdXBfam9icyIKICAgIE9OIHB1YmxpYy5tb2NrdXBfZ2VuZXJhdGlvbl9qb2JzIEZPUiBJTlNFUlQgVE8gYXV0aGVudGljYXRlZAogICAgV0lUSCBDSEVDSyAocHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc192aWV3X2dlbmVyYXRlZF9tb2NrdXBzIiBPTiBwdWJsaWMuZ2VuZXJhdGVkX21vY2t1cHM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX21lbWJlcnNfdmlld19nZW5lcmF0ZWRfbW9ja3VwcyIKICAgIE9OIHB1YmxpYy5nZW5lcmF0ZWRfbW9ja3VwcyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HICgKICAgICAgRVhJU1RTICgKICAgICAgICBTRUxFQ1QgMSBGUk9NIHB1YmxpYy5tb2NrdXBfZ2VuZXJhdGlvbl9qb2JzCiAgICAgICAgV0hFUkUgaWQgPSBnZW5lcmF0ZWRfbW9ja3Vwcy5qb2JfaWQKICAgICAgICAgIEFORCBwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkKICAgICAgKQogICAgKTsKCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAgLS0gUEFSVEUgMTU6IFBPTElDSUVTIC0gQ09MTEVDVElPTlMKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc192aWV3X2NvbGxlY3Rpb25zIiBPTiBwdWJsaWMuY29sbGVjdGlvbnM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX21lbWJlcnNfdmlld19jb2xsZWN0aW9ucyIKICAgIE9OIHB1YmxpYy5jb2xsZWN0aW9ucyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HIChwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkpOwoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19hZG1pbnNfbWFuYWdlX2NvbGxlY3Rpb25zIiBPTiBwdWJsaWMuY29sbGVjdGlvbnM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX2FkbWluc19tYW5hZ2VfY29sbGVjdGlvbnMiCiAgICBPTiBwdWJsaWMuY29sbGVjdGlvbnMgRk9SIEFMTCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAocHVibGljLmlzX29yZ19vd25lcl9vcl9hZG1pbihvcmdhbml6YXRpb25faWQpKQogICAgV0lUSCBDSEVDSyAocHVibGljLmlzX29yZ19vd25lcl9vcl9hZG1pbihvcmdhbml6YXRpb25faWQpKTsKCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAgLS0gUEFSVEUgMTY6IFBPTElDSUVTIC0gUEVSU09OQUxJWkFUSU9OX1RFQ0hOSVFVRVMgKEdMT0JBTCkKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJhbnlvbmVfdmlld190ZWNobmlxdWVzIiBPTiBwdWJsaWMucGVyc29uYWxpemF0aW9uX3RlY2huaXF1ZXM7CiAgQ1JFQVRFIFBPTElDWSAiYW55b25lX3ZpZXdfdGVjaG5pcXVlcyIKICAgIE9OIHB1YmxpYy5wZXJzb25hbGl6YXRpb25fdGVjaG5pcXVlcyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HIChpc19hY3RpdmUgPSB0cnVlKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJhZG1pbnNfbWFuYWdlX3RlY2huaXF1ZXMiIE9OIHB1YmxpYy5wZXJzb25hbGl6YXRpb25fdGVjaG5pcXVlczsKICBDUkVBVEUgUE9MSUNZICJhZG1pbnNfbWFuYWdlX3RlY2huaXF1ZXMiCiAgICBPTiBwdWJsaWMucGVyc29uYWxpemF0aW9uX3RlY2huaXF1ZXMgRk9SIEFMTCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAoCiAgICAgIEVYSVNUUyAoCiAgICAgICAgU0VMRUNUIDEgRlJPTSBwdWJsaWMudXNlcl9vcmdhbml6YXRpb25zCiAgICAgICAgV0hFUkUgdXNlcl9pZCA9IGF1dGgudWlkKCkKICAgICAgICAgIEFORCByb2xlIElOICgnb3duZXInLCAnYWRtaW4nKQogICAgICApCiAgICApOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSAxNzogUE9MSUNJRVMgLSBOT1RJRklDQVRJT05TIChVU0VSLVNDT1BFRCkKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJ1c2Vyc192aWV3X293bl9ub3RpZmljYXRpb25zIiBPTiBwdWJsaWMubm90aWZpY2F0aW9uczsKICBDUkVBVEUgUE9MSUNZICJ1c2Vyc192aWV3X293bl9ub3RpZmljYXRpb25zIgogICAgT04gcHVibGljLm5vdGlmaWNhdGlvbnMgRk9SIFNFTEVDVCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAodXNlcl9pZCA9IGF1dGgudWlkKCkpOwoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgInVzZXJzX3VwZGF0ZV9vd25fbm90aWZpY2F0aW9ucyIgT04gcHVibGljLm5vdGlmaWNhdGlvbnM7CiAgQ1JFQVRFIFBPTElDWSAidXNlcnNfdXBkYXRlX293bl9ub3RpZmljYXRpb25zIgogICAgT04gcHVibGljLm5vdGlmaWNhdGlvbnMgRk9SIFVQREFURSBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAodXNlcl9pZCA9IGF1dGgudWlkKCkpOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSAxODogUE9MSUNJRVMgLSBTWVNURU0gVEFCTEVTIChBRE1JTiBPTkxZKQogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgImFkbWluc192aWV3X2ZlYXR1cmVfZmxhZ3MiIE9OIHB1YmxpYy5mZWF0dXJlX2ZsYWdzOwogIENSRUFURSBQT0xJQ1kgImFkbWluc192aWV3X2ZlYXR1cmVfZmxhZ3MiCiAgICBPTiBwdWJsaWMuZmVhdHVyZV9mbGFncyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HICgKICAgICAgRVhJU1RTICgKICAgICAgICBTRUxFQ1QgMSBGUk9NIHB1YmxpYy51c2VyX29yZ2FuaXphdGlvbnMKICAgICAgICBXSEVSRSB1c2VyX2lkID0gYXV0aC51aWQoKQogICAgICAgICAgQU5EIHJvbGUgSU4gKCdvd25lcicsICdhZG1pbicpCiAgICAgICkKICAgICk7CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAiYWRtaW5zX21hbmFnZV9zeXN0ZW1fc2V0dGluZ3MiIE9OIHB1YmxpYy5zeXN0ZW1fc2V0dGluZ3M7CiAgQ1JFQVRFIFBPTElDWSAiYWRtaW5zX21hbmFnZV9zeXN0ZW1fc2V0dGluZ3MiCiAgICBPTiBwdWJsaWMuc3lzdGVtX3NldHRpbmdzIEZPUiBBTEwgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKAogICAgICBFWElTVFMgKAogICAgICAgIFNFTEVDVCAxIEZST00gcHVibGljLnVzZXJfb3JnYW5pemF0aW9ucwogICAgICAgIFdIRVJFIHVzZXJfaWQgPSBhdXRoLnVpZCgpCiAgICAgICAgICBBTkQgcm9sZSBJTiAoJ293bmVyJywgJ2FkbWluJykKICAgICAgKQogICAgKTsKCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAgLS0gUEFSVEUgMTk6IEdSQU5UUwogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKICBHUkFOVCBTRUxFQ1QgT04gcHVibGljLmNhdGVnb3JpZXMgVE8gYXV0aGVudGljYXRlZDsKICBHUkFOVCBTRUxFQ1QgT04gcHVibGljLnN1cHBsaWVycyBUTyBhdXRoZW50aWNhdGVkOwogIEdSQU5UIFNFTEVDVCBPTiBwdWJsaWMucHJvZHVjdHMgVE8gYXV0aGVudGljYXRlZDsKICBHUkFOVCBTRUxFQ1QgT04gcHVibGljLnF1b3RlcyBUTyBhdXRoZW50aWNhdGVkOwogIEdSQU5UIFNFTEVDVCBPTiBwdWJsaWMub3JkZXJzIFRPIGF1dGhlbnRpY2F0ZWQ7CiAgSUYgRVhJU1RTIChTRUxFQ1QgMSBGUk9NIGluZm9ybWF0aW9uX3NjaGVtYS50YWJsZXMgV0hFUkUgdGFibGVfc2NoZW1hPSdwdWJsaWMnIEFORCB0YWJsZV9uYW1lPSdwYXltZW50cycpIFRIRU4KICAgIEdSQU5UIFNFTEVDVCBPTiBwdWJsaWMucGF5bWVudHMgVE8gYXV0aGVudGljYXRlZDsKICBFTkQgSUY7CgogIFJBSVNFIE5PVElDRSAnTWlncmF0aW9uIDIwMjUwMTAzXzAyX3Jsc19vcmdhbml6YXRpb25zIGFwcGxpZWQgc3VjY2Vzc2Z1bGx5Lic7CkVORCAkb3V0ZXIkOwo" (SQLSTATE 42601)
At statement: 0
LS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Ci0tIEdJRlRTIFNUT1JFIC0gUkxTIENPTSBPUkdBTklaQVRJT05TIChNVUxUSS1URU5BTlQpCi0tIEFwbGljYSBSb3cgTGV2ZWwgU2VjdXJpdHkgYmFzZWFkbyBlbSBPcmdhbml6YXRpb25zCi0tIERhdGE6IDAzLzAxLzIwMjUKLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Ci0tIEd1YXJkOiB0aGlzIGVudGlyZSBtaWdyYXRpb24gaXMgd3JhcHBlZCBpbiBhIERPIGJsb2NrIHNvIGl0Ci0tIGV4aXRzIGNsZWFubHkgd2hlbiBwdWJsaWMub3JnYW5pemF0aW9ucyBkb2Vzbid0IGV4aXN0IHlldC4KLS0gT24gYSBmcmVzaCBTdXBhYmFzZSBQcmV2aWV3IEJyYW5jaCB0aGF0IHJlcGxheXMgYWxsIG1pZ3JhdGlvbnMKLS0gZnJvbSBzY3JhdGNoLCBvcmdhbml6YXRpb25zIGlzIGNyZWF0ZWQgbGF0ZXIgaW4gdGhlIHNlcXVlbmNlLgotLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCkRPICRvdXRlciQKQkVHSU4KICBJRiBOT1QgRVhJU1RTICgKICAgIFNFTEVDVCAxIEZST00gaW5mb3JtYXRpb25fc2NoZW1hLnRhYmxlcwogICAgV0hFUkUgdGFibGVfc2NoZW1hID0gJ3B1YmxpYycgQU5EIHRhYmxlX25hbWUgPSAnb3JnYW5pemF0aW9ucycKICApIFRIRU4KICAgIFJBSVNFIE5PVElDRSAnTWlncmF0aW9uIDIwMjUwMTAzXzAyX3Jsc19vcmdhbml6YXRpb25zIHNraXBwZWQ6IHB1YmxpYy5vcmdhbml6YXRpb25zIGRvZXMgbm90IGV4aXN0IHlldC4nOwogICAgUkVUVVJOOwogIEVORCBJRjsKCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAgLS0gUEFSVEUgMTogQURJQ0lPTkFSIG9yZ2FuaXphdGlvbl9pZCBOQVMgVEFCRUxBUyBQUklOQ0lQQUlTCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5jYXRlZ29yaWVzCiAgICBBREQgQ09MVU1OIElGIE5PVCBFWElTVFMgb3JnYW5pemF0aW9uX2lkIFVVSUQgUkVGRVJFTkNFUyBwdWJsaWMub3JnYW5pemF0aW9ucyhpZCkgT04gREVMRVRFIENBU0NBREU7CiAgQ1JFQVRFIElOREVYIElGIE5PVCBFWElTVFMgaWR4X2NhdGVnb3JpZXNfb3JnIE9OIHB1YmxpYy5jYXRlZ29yaWVzKG9yZ2FuaXphdGlvbl9pZCk7CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5zdXBwbGllcnMKICAgIEFERCBDT0xVTU4gSUYgTk9UIEVYSVNUUyBvcmdhbml6YXRpb25faWQgVVVJRCBSRUZFUkVOQ0VTIHB1YmxpYy5vcmdhbml6YXRpb25zKGlkKSBPTiBERUxFVEUgQ0FTQ0FERTsKICBDUkVBVEUgSU5ERVggSUYgTk9UIEVYSVNUUyBpZHhfc3VwcGxpZXJzX29yZyBPTiBwdWJsaWMuc3VwcGxpZXJzKG9yZ2FuaXphdGlvbl9pZCk7CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5wcm9kdWN0cwogICAgQUREIENPTFVNTiBJRiBOT1QgRVhJU1RTIG9yZ2FuaXphdGlvbl9pZCBVVUlEIFJFRkVSRU5DRVMgcHVibGljLm9yZ2FuaXphdGlvbnMoaWQpIE9OIERFTEVURSBDQVNDQURFOwogIENSRUFURSBJTkRFWCBJRiBOT1QgRVhJU1RTIGlkeF9wcm9kdWN0c19vcmcgT04gcHVibGljLnByb2R1Y3RzKG9yZ2FuaXphdGlvbl9pZCk7CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5xdW90ZXMKICAgIEFERCBDT0xVTU4gSUYgTk9UIEVYSVNUUyBvcmdhbml6YXRpb25faWQgVVVJRCBSRUZFUkVOQ0VTIHB1YmxpYy5vcmdhbml6YXRpb25zKGlkKSBPTiBERUxFVEUgQ0FTQ0FERTsKICBDUkVBVEUgSU5ERVggSUYgTk9UIEVYSVNUUyBpZHhfcXVvdGVzX29yZyBPTiBwdWJsaWMucXVvdGVzKG9yZ2FuaXphdGlvbl9pZCk7CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5vcmRlcnMKICAgIEFERCBDT0xVTU4gSUYgTk9UIEVYSVNUUyBvcmdhbml6YXRpb25faWQgVVVJRCBSRUZFUkVOQ0VTIHB1YmxpYy5vcmdhbml6YXRpb25zKGlkKSBPTiBERUxFVEUgQ0FTQ0FERTsKICBDUkVBVEUgSU5ERVggSUYgTk9UIEVYSVNUUyBpZHhfb3JkZXJzX29yZyBPTiBwdWJsaWMub3JkZXJzKG9yZ2FuaXphdGlvbl9pZCk7CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5iaXRyaXhfY2xpZW50cwogICAgQUREIENPTFVNTiBJRiBOT1QgRVhJU1RTIG9yZ2FuaXphdGlvbl9pZCBVVUlEIFJFRkVSRU5DRVMgcHVibGljLm9yZ2FuaXphdGlvbnMoaWQpIE9OIERFTEVURSBDQVNDQURFOwogIENSRUFURSBJTkRFWCBJRiBOT1QgRVhJU1RTIGlkeF9iaXRyaXhfY2xpZW50c19vcmcgT04gcHVibGljLmJpdHJpeF9jbGllbnRzKG9yZ2FuaXphdGlvbl9pZCk7CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5tb2NrdXBfZ2VuZXJhdGlvbl9qb2JzCiAgICBBREQgQ09MVU1OIElGIE5PVCBFWElTVFMgb3JnYW5pemF0aW9uX2lkIFVVSUQgUkVGRVJFTkNFUyBwdWJsaWMub3JnYW5pemF0aW9ucyhpZCkgT04gREVMRVRFIENBU0NBREU7CiAgQ1JFQVRFIElOREVYIElGIE5PVCBFWElTVFMgaWR4X21vY2t1cF9qb2JzX29yZyBPTiBwdWJsaWMubW9ja3VwX2dlbmVyYXRpb25fam9icyhvcmdhbml6YXRpb25faWQpOwoKICBBTFRFUiBUQUJMRSBwdWJsaWMuY29sbGVjdGlvbnMKICAgIEFERCBDT0xVTU4gSUYgTk9UIEVYSVNUUyBvcmdhbml6YXRpb25faWQgVVVJRCBSRUZFUkVOQ0VTIHB1YmxpYy5vcmdhbml6YXRpb25zKGlkKSBPTiBERUxFVEUgQ0FTQ0FERTsKICBDUkVBVEUgSU5ERVggSUYgTk9UIEVYSVNUUyBpZHhfY29sbGVjdGlvbnNfb3JnIE9OIHB1YmxpYy5jb2xsZWN0aW9ucyhvcmdhbml6YXRpb25faWQpOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSAyOiBGVU7Dh8ODTyBIRUxQRVIgLSBWZXJpZmljYXIgc2UgdXNlciBwZXJ0ZW5jZSDDoCBvcmcKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgQ1JFQVRFIE9SIFJFUExBQ0UgRlVOQ1RJT04gcHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdfaWQgVVVJRCkKICBSRVRVUk5TIEJPT0xFQU4gQVMgJGZuJAogIEJFR0lOCiAgICBSRVRVUk4gRVhJU1RTICgKICAgICAgU0VMRUNUIDEKICAgICAgRlJPTSBwdWJsaWMudXNlcl9vcmdhbml6YXRpb25zCiAgICAgIFdIRVJFIG9yZ2FuaXphdGlvbl9pZCA9IG9yZ19pZAogICAgICAgIEFORCB1c2VyX2lkID0gYXV0aC51aWQoKQogICAgKTsKICBFTkQ7CiAgJGZuJCBMQU5HVUFHRSBwbHBnc3FsIFNFQ1VSSVRZIERFRklORVIgU1RBQkxFOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSAzOiBBUExJQ0FSIFJMUyBFTSBUT0RBUyBBUyBUQUJFTEFTCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgogIEFMVEVSIFRBQkxFIHB1YmxpYy5jYXRlZ29yaWVzIEVOQUJMRSBST1cgTEVWRUwgU0VDVVJJVFk7CiAgQUxURVIgVEFCTEUgcHVibGljLnN1cHBsaWVycyBFTkFCTEUgUk9XIExFVkVMIFNFQ1VSSVRZOwogIEFMVEVSIFRBQkxFIHB1YmxpYy5wcm9kdWN0cyBFTkFCTEUgUk9XIExFVkVMIFNFQ1VSSVRZOwogIEFMVEVSIFRBQkxFIHB1YmxpYy5wcm9kdWN0X3ZhcmlhbnRzIEVOQUJMRSBST1cgTEVWRUwgU0VDVVJJVFk7CiAgQUxURVIgVEFCTEUgcHVibGljLnF1b3RlcyBFTkFCTEUgUk9XIExFVkVMIFNFQ1VSSVRZOwogIEFMVEVSIFRBQkxFIHB1YmxpYy5xdW90ZV9pdGVtcyBFTkFCTEUgUk9XIExFVkVMIFNFQ1VSSVRZOwogIEFMVEVSIFRBQkxFIHB1YmxpYy5vcmRlcnMgRU5BQkxFIFJPVyBMRVZFTCBTRUNVUklUWTsKICBBTFRFUiBUQUJMRSBwdWJsaWMub3JkZXJfaXRlbXMgRU5BQkxFIFJPVyBMRVZFTCBTRUNVUklUWTsKICBJRiBFWElTVFMgKFNFTEVDVCAxIEZST00gaW5mb3JtYXRpb25fc2NoZW1hLnRhYmxlcyBXSEVSRSB0YWJsZV9zY2hlbWE9J3B1YmxpYycgQU5EIHRhYmxlX25hbWU9J3BheW1lbnRzJykgVEhFTgogICAgQUxURVIgVEFCTEUgcHVibGljLnBheW1lbnRzIEVOQUJMRSBST1cgTEVWRUwgU0VDVVJJVFk7CiAgRU5EIElGOwogIEFMVEVSIFRBQkxFIHB1YmxpYy5iaXRyaXhfY2xpZW50cyBFTkFCTEUgUk9XIExFVkVMIFNFQ1VSSVRZOwogIEFMVEVSIFRBQkxFIHB1YmxpYy5tb2NrdXBfZ2VuZXJhdGlvbl9qb2JzIEVOQUJMRSBST1cgTEVWRUwgU0VDVVJJVFk7CiAgQUxURVIgVEFCTEUgcHVibGljLmdlbmVyYXRlZF9tb2NrdXBzIEVOQUJMRSBST1cgTEVWRUwgU0VDVVJJVFk7CiAgQUxURVIgVEFCTEUgcHVibGljLmNvbGxlY3Rpb25zIEVOQUJMRSBST1cgTEVWRUwgU0VDVVJJVFk7CiAgQUxURVIgVEFCTEUgcHVibGljLmNvbGxlY3Rpb25fcHJvZHVjdHMgRU5BQkxFIFJPVyBMRVZFTCBTRUNVUklUWTsKICBBTFRFUiBUQUJMRSBwdWJsaWMucGVyc29uYWxpemF0aW9uX3RlY2huaXF1ZXMgRU5BQkxFIFJPVyBMRVZFTCBTRUNVUklUWTsKICBBTFRFUiBUQUJMRSBwdWJsaWMubm90aWZpY2F0aW9ucyBFTkFCTEUgUk9XIExFVkVMIFNFQ1VSSVRZOwogIEFMVEVSIFRBQkxFIHB1YmxpYy5mZWF0dXJlX2ZsYWdzIEVOQUJMRSBST1cgTEVWRUwgU0VDVVJJVFk7CiAgQUxURVIgVEFCTEUgcHVibGljLnN5c3RlbV9zZXR0aW5ncyBFTkFCTEUgUk9XIExFVkVMIFNFQ1VSSVRZOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSA0OiBQT0xJQ0lFUyAtIENBVEVHT1JJRVMKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc192aWV3X2NhdGVnb3JpZXMiIE9OIHB1YmxpYy5jYXRlZ29yaWVzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19tZW1iZXJzX3ZpZXdfY2F0ZWdvcmllcyIKICAgIE9OIHB1YmxpYy5jYXRlZ29yaWVzIEZPUiBTRUxFQ1QgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKHB1YmxpYy51c2VyX2lzX29yZ19tZW1iZXIob3JnYW5pemF0aW9uX2lkKSk7CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAib3JnX2FkbWluc19jcmVhdGVfY2F0ZWdvcmllcyIgT04gcHVibGljLmNhdGVnb3JpZXM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX2FkbWluc19jcmVhdGVfY2F0ZWdvcmllcyIKICAgIE9OIHB1YmxpYy5jYXRlZ29yaWVzIEZPUiBJTlNFUlQgVE8gYXV0aGVudGljYXRlZAogICAgV0lUSCBDSEVDSyAocHVibGljLmlzX29yZ19vd25lcl9vcl9hZG1pbihvcmdhbml6YXRpb25faWQpKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfYWRtaW5zX3VwZGF0ZV9jYXRlZ29yaWVzIiBPTiBwdWJsaWMuY2F0ZWdvcmllczsKICBDUkVBVEUgUE9MSUNZICJvcmdfYWRtaW5zX3VwZGF0ZV9jYXRlZ29yaWVzIgogICAgT04gcHVibGljLmNhdGVnb3JpZXMgRk9SIFVQREFURSBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAocHVibGljLmlzX29yZ19vd25lcl9vcl9hZG1pbihvcmdhbml6YXRpb25faWQpKQogICAgV0lUSCBDSEVDSyAocHVibGljLmlzX29yZ19vd25lcl9vcl9hZG1pbihvcmdhbml6YXRpb25faWQpKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfYWRtaW5zX2RlbGV0ZV9jYXRlZ29yaWVzIiBPTiBwdWJsaWMuY2F0ZWdvcmllczsKICBDUkVBVEUgUE9MSUNZICJvcmdfYWRtaW5zX2RlbGV0ZV9jYXRlZ29yaWVzIgogICAgT04gcHVibGljLmNhdGVnb3JpZXMgRk9SIERFTEVURSBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAocHVibGljLmlzX29yZ19vd25lcl9vcl9hZG1pbihvcmdhbml6YXRpb25faWQpKTsKCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAgLS0gUEFSVEUgNTogUE9MSUNJRVMgLSBTVVBQTElFUlMKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc192aWV3X3N1cHBsaWVycyIgT04gcHVibGljLnN1cHBsaWVyczsKICBDUkVBVEUgUE9MSUNZICJvcmdfbWVtYmVyc192aWV3X3N1cHBsaWVycyIKICAgIE9OIHB1YmxpYy5zdXBwbGllcnMgRk9SIFNFTEVDVCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAocHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfYWRtaW5zX21hbmFnZV9zdXBwbGllcnMiIE9OIHB1YmxpYy5zdXBwbGllcnM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX2FkbWluc19tYW5hZ2Vfc3VwcGxpZXJzIgogICAgT04gcHVibGljLnN1cHBsaWVycyBGT1IgQUxMIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HIChwdWJsaWMuaXNfb3JnX293bmVyX29yX2FkbWluKG9yZ2FuaXphdGlvbl9pZCkpCiAgICBXSVRIIENIRUNLIChwdWJsaWMuaXNfb3JnX293bmVyX29yX2FkbWluKG9yZ2FuaXphdGlvbl9pZCkpOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSA2OiBQT0xJQ0lFUyAtIFBST0RVQ1RTCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAib3JnX21lbWJlcnNfdmlld19wcm9kdWN0cyIgT04gcHVibGljLnByb2R1Y3RzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19tZW1iZXJzX3ZpZXdfcHJvZHVjdHMiCiAgICBPTiBwdWJsaWMucHJvZHVjdHMgRk9SIFNFTEVDVCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAocHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfYWRtaW5zX21hbmFnZV9wcm9kdWN0cyIgT04gcHVibGljLnByb2R1Y3RzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19hZG1pbnNfbWFuYWdlX3Byb2R1Y3RzIgogICAgT04gcHVibGljLnByb2R1Y3RzIEZPUiBBTEwgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKHB1YmxpYy5pc19vcmdfb3duZXJfb3JfYWRtaW4ob3JnYW5pemF0aW9uX2lkKSkKICAgIFdJVEggQ0hFQ0sgKHB1YmxpYy5pc19vcmdfb3duZXJfb3JfYWRtaW4ob3JnYW5pemF0aW9uX2lkKSk7CgogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogIC0tIFBBUlRFIDc6IFBPTElDSUVTIC0gUFJPRFVDVF9WQVJJQU5UUwogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19tZW1iZXJzX3ZpZXdfdmFyaWFudHMiIE9OIHB1YmxpYy5wcm9kdWN0X3ZhcmlhbnRzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19tZW1iZXJzX3ZpZXdfdmFyaWFudHMiCiAgICBPTiBwdWJsaWMucHJvZHVjdF92YXJpYW50cyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HICgKICAgICAgRVhJU1RTICgKICAgICAgICBTRUxFQ1QgMSBGUk9NIHB1YmxpYy5wcm9kdWN0cwogICAgICAgIFdIRVJFIGlkID0gcHJvZHVjdF92YXJpYW50cy5wcm9kdWN0X2lkCiAgICAgICAgICBBTkQgcHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpCiAgICAgICkKICAgICk7CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAib3JnX2FkbWluc19tYW5hZ2VfdmFyaWFudHMiIE9OIHB1YmxpYy5wcm9kdWN0X3ZhcmlhbnRzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19hZG1pbnNfbWFuYWdlX3ZhcmlhbnRzIgogICAgT04gcHVibGljLnByb2R1Y3RfdmFyaWFudHMgRk9SIEFMTCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAoCiAgICAgIEVYSVNUUyAoCiAgICAgICAgU0VMRUNUIDEgRlJPTSBwdWJsaWMucHJvZHVjdHMKICAgICAgICBXSEVSRSBpZCA9IHByb2R1Y3RfdmFyaWFudHMucHJvZHVjdF9pZAogICAgICAgICAgQU5EIHB1YmxpYy5pc19vcmdfb3duZXJfb3JfYWRtaW4ob3JnYW5pemF0aW9uX2lkKQogICAgICApCiAgICApCiAgICBXSVRIIENIRUNLICgKICAgICAgRVhJU1RTICgKICAgICAgICBTRUxFQ1QgMSBGUk9NIHB1YmxpYy5wcm9kdWN0cwogICAgICAgIFdIRVJFIGlkID0gcHJvZHVjdF92YXJpYW50cy5wcm9kdWN0X2lkCiAgICAgICAgICBBTkQgcHVibGljLmlzX29yZ19vd25lcl9vcl9hZG1pbihvcmdhbml6YXRpb25faWQpCiAgICAgICkKICAgICk7CgogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogIC0tIFBBUlRFIDg6IFBPTElDSUVTIC0gUVVPVEVTCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAib3JnX21lbWJlcnNfdmlld19xdW90ZXMiIE9OIHB1YmxpYy5xdW90ZXM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX21lbWJlcnNfdmlld19xdW90ZXMiCiAgICBPTiBwdWJsaWMucXVvdGVzIEZPUiBTRUxFQ1QgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKHB1YmxpYy51c2VyX2lzX29yZ19tZW1iZXIob3JnYW5pemF0aW9uX2lkKSk7CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAib3JnX21lbWJlcnNfY3JlYXRlX3F1b3RlcyIgT04gcHVibGljLnF1b3RlczsKICBDUkVBVEUgUE9MSUNZICJvcmdfbWVtYmVyc19jcmVhdGVfcXVvdGVzIgogICAgT04gcHVibGljLnF1b3RlcyBGT1IgSU5TRVJUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFdJVEggQ0hFQ0sgKHB1YmxpYy51c2VyX2lzX29yZ19tZW1iZXIob3JnYW5pemF0aW9uX2lkKSk7CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAib3JnX21lbWJlcnNfdXBkYXRlX293bl9xdW90ZXMiIE9OIHB1YmxpYy5xdW90ZXM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX21lbWJlcnNfdXBkYXRlX293bl9xdW90ZXMiCiAgICBPTiBwdWJsaWMucXVvdGVzIEZPUiBVUERBVEUgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKAogICAgICBwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkKICAgICAgQU5EIChjcmVhdGVkX2J5ID0gYXV0aC51aWQoKSBPUiBwdWJsaWMuaXNfb3JnX2FkbWluKG9yZ2FuaXphdGlvbl9pZCkpCiAgICApOwoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19hZG1pbnNfZGVsZXRlX3F1b3RlcyIgT04gcHVibGljLnF1b3RlczsKICBDUkVBVEUgUE9MSUNZICJvcmdfYWRtaW5zX2RlbGV0ZV9xdW90ZXMiCiAgICBPTiBwdWJsaWMucXVvdGVzIEZPUiBERUxFVEUgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKHB1YmxpYy5pc19vcmdfb3duZXJfb3JfYWRtaW4ob3JnYW5pemF0aW9uX2lkKSk7CgogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogIC0tIFBBUlRFIDk6IFBPTElDSUVTIC0gUVVPVEVfSVRFTVMKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc192aWV3X3F1b3RlX2l0ZW1zIiBPTiBwdWJsaWMucXVvdGVfaXRlbXM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX21lbWJlcnNfdmlld19xdW90ZV9pdGVtcyIKICAgIE9OIHB1YmxpYy5xdW90ZV9pdGVtcyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HICgKICAgICAgRVhJU1RTICgKICAgICAgICBTRUxFQ1QgMSBGUk9NIHB1YmxpYy5xdW90ZXMKICAgICAgICBXSEVSRSBpZCA9IHF1b3RlX2l0ZW1zLnF1b3RlX2lkCiAgICAgICAgICBBTkQgcHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpCiAgICAgICkKICAgICk7CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAib3JnX21lbWJlcnNfbWFuYWdlX3F1b3RlX2l0ZW1zIiBPTiBwdWJsaWMucXVvdGVfaXRlbXM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX21lbWJlcnNfbWFuYWdlX3F1b3RlX2l0ZW1zIgogICAgT04gcHVibGljLnF1b3RlX2l0ZW1zIEZPUiBBTEwgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKAogICAgICBFWElTVFMgKAogICAgICAgIFNFTEVDVCAxIEZST00gcHVibGljLnF1b3RlcwogICAgICAgIFdIRVJFIGlkID0gcXVvdGVfaXRlbXMucXVvdGVfaWQKICAgICAgICAgIEFORCAoY3JlYXRlZF9ieSA9IGF1dGgudWlkKCkgT1IgcHVibGljLmlzX29yZ19hZG1pbihvcmdhbml6YXRpb25faWQpKQogICAgICApCiAgICApCiAgICBXSVRIIENIRUNLICgKICAgICAgRVhJU1RTICgKICAgICAgICBTRUxFQ1QgMSBGUk9NIHB1YmxpYy5xdW90ZXMKICAgICAgICBXSEVSRSBpZCA9IHF1b3RlX2l0ZW1zLnF1b3RlX2lkCiAgICAgICAgICBBTkQgcHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpCiAgICAgICkKICAgICk7CgogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogIC0tIFBBUlRFIDEwOiBQT0xJQ0lFUyAtIE9SREVSUwogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19tZW1iZXJzX3ZpZXdfb3JkZXJzIiBPTiBwdWJsaWMub3JkZXJzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19tZW1iZXJzX3ZpZXdfb3JkZXJzIgogICAgT04gcHVibGljLm9yZGVycyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HIChwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkpOwoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19tZW1iZXJzX2NyZWF0ZV9vcmRlcnMiIE9OIHB1YmxpYy5vcmRlcnM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX21lbWJlcnNfY3JlYXRlX29yZGVycyIKICAgIE9OIHB1YmxpYy5vcmRlcnMgRk9SIElOU0VSVCBUTyBhdXRoZW50aWNhdGVkCiAgICBXSVRIIENIRUNLIChwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkpOwoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19tZW1iZXJzX3VwZGF0ZV9vd25fb3JkZXJzIiBPTiBwdWJsaWMub3JkZXJzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19tZW1iZXJzX3VwZGF0ZV9vd25fb3JkZXJzIgogICAgT04gcHVibGljLm9yZGVycyBGT1IgVVBEQVRFIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HICgKICAgICAgcHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpCiAgICAgIEFORCAoY3JlYXRlZF9ieSA9IGF1dGgudWlkKCkgT1IgcHVibGljLmlzX29yZ19hZG1pbihvcmdhbml6YXRpb25faWQpKQogICAgKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfYWRtaW5zX2RlbGV0ZV9vcmRlcnMiIE9OIHB1YmxpYy5vcmRlcnM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX2FkbWluc19kZWxldGVfb3JkZXJzIgogICAgT04gcHVibGljLm9yZGVycyBGT1IgREVMRVRFIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HIChwdWJsaWMuaXNfb3JnX293bmVyX29yX2FkbWluKG9yZ2FuaXphdGlvbl9pZCkpOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSAxMTogUE9MSUNJRVMgLSBPUkRFUl9JVEVNUwogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19tZW1iZXJzX3ZpZXdfb3JkZXJfaXRlbXMiIE9OIHB1YmxpYy5vcmRlcl9pdGVtczsKICBDUkVBVEUgUE9MSUNZICJvcmdfbWVtYmVyc192aWV3X29yZGVyX2l0ZW1zIgogICAgT04gcHVibGljLm9yZGVyX2l0ZW1zIEZPUiBTRUxFQ1QgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKAogICAgICBFWElTVFMgKAogICAgICAgIFNFTEVDVCAxIEZST00gcHVibGljLm9yZGVycwogICAgICAgIFdIRVJFIGlkID0gb3JkZXJfaXRlbXMub3JkZXJfaWQKICAgICAgICAgIEFORCBwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkKICAgICAgKQogICAgKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc19tYW5hZ2Vfb3JkZXJfaXRlbXMiIE9OIHB1YmxpYy5vcmRlcl9pdGVtczsKICBDUkVBVEUgUE9MSUNZICJvcmdfbWVtYmVyc19tYW5hZ2Vfb3JkZXJfaXRlbXMiCiAgICBPTiBwdWJsaWMub3JkZXJfaXRlbXMgRk9SIEFMTCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAoCiAgICAgIEVYSVNUUyAoCiAgICAgICAgU0VMRUNUIDEgRlJPTSBwdWJsaWMub3JkZXJzCiAgICAgICAgV0hFUkUgaWQgPSBvcmRlcl9pdGVtcy5vcmRlcl9pZAogICAgICAgICAgQU5EIChjcmVhdGVkX2J5ID0gYXV0aC51aWQoKSBPUiBwdWJsaWMuaXNfb3JnX2FkbWluKG9yZ2FuaXphdGlvbl9pZCkpCiAgICAgICkKICAgICkKICAgIFdJVEggQ0hFQ0sgKAogICAgICBFWElTVFMgKAogICAgICAgIFNFTEVDVCAxIEZST00gcHVibGljLm9yZGVycwogICAgICAgIFdIRVJFIGlkID0gb3JkZXJfaXRlbXMub3JkZXJfaWQKICAgICAgICAgIEFORCBwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkKICAgICAgKQogICAgKTsKCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAgLS0gUEFSVEUgMTI6IFBPTElDSUVTIC0gUEFZTUVOVFMgKGd1YXJkZWQ6IHRhYmxlIG1heSBub3QgZXhpc3QgeWV0KQogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKICBJRiBFWElTVFMgKFNFTEVDVCAxIEZST00gaW5mb3JtYXRpb25fc2NoZW1hLnRhYmxlcyBXSEVSRSB0YWJsZV9zY2hlbWE9J3B1YmxpYycgQU5EIHRhYmxlX25hbWU9J3BheW1lbnRzJykgVEhFTgogICAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc192aWV3X3BheW1lbnRzIiBPTiBwdWJsaWMucGF5bWVudHM7CiAgICBDUkVBVEUgUE9MSUNZICJvcmdfbWVtYmVyc192aWV3X3BheW1lbnRzIgogICAgICBPTiBwdWJsaWMucGF5bWVudHMgRk9SIFNFTEVDVCBUTyBhdXRoZW50aWNhdGVkCiAgICAgIFVTSU5HICgKICAgICAgICBFWElTVFMgKAogICAgICAgICAgU0VMRUNUIDEgRlJPTSBwdWJsaWMub3JkZXJzCiAgICAgICAgICBXSEVSRSBpZCA9IHBheW1lbnRzLm9yZGVyX2lkCiAgICAgICAgICAgIEFORCBwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkKICAgICAgICApCiAgICAgICk7CgogICAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfYWRtaW5zX21hbmFnZV9wYXltZW50cyIgT04gcHVibGljLnBheW1lbnRzOwogICAgQ1JFQVRFIFBPTElDWSAib3JnX2FkbWluc19tYW5hZ2VfcGF5bWVudHMiCiAgICAgIE9OIHB1YmxpYy5wYXltZW50cyBGT1IgQUxMIFRPIGF1dGhlbnRpY2F0ZWQKICAgICAgVVNJTkcgKAogICAgICAgIEVYSVNUUyAoCiAgICAgICAgICBTRUxFQ1QgMSBGUk9NIHB1YmxpYy5vcmRlcnMKICAgICAgICAgIFdIRVJFIGlkID0gcGF5bWVudHMub3JkZXJfaWQKICAgICAgICAgICAgQU5EIHB1YmxpYy5pc19vcmdfYWRtaW4ob3JnYW5pemF0aW9uX2lkKQogICAgICAgICkKICAgICAgKQogICAgICBXSVRIIENIRUNLICgKICAgICAgICBFWElTVFMgKAogICAgICAgICAgU0VMRUNUIDEgRlJPTSBwdWJsaWMub3JkZXJzCiAgICAgICAgICBXSEVSRSBpZCA9IHBheW1lbnRzLm9yZGVyX2lkCiAgICAgICAgICAgIEFORCBwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkKICAgICAgICApCiAgICAgICk7CiAgRU5EIElGOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSAxMzogUE9MSUNJRVMgLSBCSVRSSVhfQ0xJRU5UUwogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19tZW1iZXJzX3ZpZXdfY2xpZW50cyIgT04gcHVibGljLmJpdHJpeF9jbGllbnRzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19tZW1iZXJzX3ZpZXdfY2xpZW50cyIKICAgIE9OIHB1YmxpYy5iaXRyaXhfY2xpZW50cyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HIChwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkpOwoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19hZG1pbnNfbWFuYWdlX2NsaWVudHMiIE9OIHB1YmxpYy5iaXRyaXhfY2xpZW50czsKICBDUkVBVEUgUE9MSUNZICJvcmdfYWRtaW5zX21hbmFnZV9jbGllbnRzIgogICAgT04gcHVibGljLmJpdHJpeF9jbGllbnRzIEZPUiBBTEwgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKHB1YmxpYy5pc19vcmdfb3duZXJfb3JfYWRtaW4ob3JnYW5pemF0aW9uX2lkKSkKICAgIFdJVEggQ0hFQ0sgKHB1YmxpYy5pc19vcmdfb3duZXJfb3JfYWRtaW4ob3JnYW5pemF0aW9uX2lkKSk7CgogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogIC0tIFBBUlRFIDE0OiBQT0xJQ0lFUyAtIE1PQ0tVUFMKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc192aWV3X21vY2t1cF9qb2JzIiBPTiBwdWJsaWMubW9ja3VwX2dlbmVyYXRpb25fam9iczsKICBDUkVBVEUgUE9MSUNZICJvcmdfbWVtYmVyc192aWV3X21vY2t1cF9qb2JzIgogICAgT04gcHVibGljLm1vY2t1cF9nZW5lcmF0aW9uX2pvYnMgRk9SIFNFTEVDVCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAocHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc19jcmVhdGVfbW9ja3VwX2pvYnMiIE9OIHB1YmxpYy5tb2NrdXBfZ2VuZXJhdGlvbl9qb2JzOwogIENSRUFURSBQT0xJQ1kgIm9yZ19tZW1iZXJzX2NyZWF0ZV9tb2NrdXBfam9icyIKICAgIE9OIHB1YmxpYy5tb2NrdXBfZ2VuZXJhdGlvbl9qb2JzIEZPUiBJTlNFUlQgVE8gYXV0aGVudGljYXRlZAogICAgV0lUSCBDSEVDSyAocHVibGljLnVzZXJfaXNfb3JnX21lbWJlcihvcmdhbml6YXRpb25faWQpKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc192aWV3X2dlbmVyYXRlZF9tb2NrdXBzIiBPTiBwdWJsaWMuZ2VuZXJhdGVkX21vY2t1cHM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX21lbWJlcnNfdmlld19nZW5lcmF0ZWRfbW9ja3VwcyIKICAgIE9OIHB1YmxpYy5nZW5lcmF0ZWRfbW9ja3VwcyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HICgKICAgICAgRVhJU1RTICgKICAgICAgICBTRUxFQ1QgMSBGUk9NIHB1YmxpYy5tb2NrdXBfZ2VuZXJhdGlvbl9qb2JzCiAgICAgICAgV0hFUkUgaWQgPSBnZW5lcmF0ZWRfbW9ja3Vwcy5qb2JfaWQKICAgICAgICAgIEFORCBwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkKICAgICAgKQogICAgKTsKCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAgLS0gUEFSVEUgMTU6IFBPTElDSUVTIC0gQ09MTEVDVElPTlMKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJvcmdfbWVtYmVyc192aWV3X2NvbGxlY3Rpb25zIiBPTiBwdWJsaWMuY29sbGVjdGlvbnM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX21lbWJlcnNfdmlld19jb2xsZWN0aW9ucyIKICAgIE9OIHB1YmxpYy5jb2xsZWN0aW9ucyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HIChwdWJsaWMudXNlcl9pc19vcmdfbWVtYmVyKG9yZ2FuaXphdGlvbl9pZCkpOwoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgIm9yZ19hZG1pbnNfbWFuYWdlX2NvbGxlY3Rpb25zIiBPTiBwdWJsaWMuY29sbGVjdGlvbnM7CiAgQ1JFQVRFIFBPTElDWSAib3JnX2FkbWluc19tYW5hZ2VfY29sbGVjdGlvbnMiCiAgICBPTiBwdWJsaWMuY29sbGVjdGlvbnMgRk9SIEFMTCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAocHVibGljLmlzX29yZ19vd25lcl9vcl9hZG1pbihvcmdhbml6YXRpb25faWQpKQogICAgV0lUSCBDSEVDSyAocHVibGljLmlzX29yZ19vd25lcl9vcl9hZG1pbihvcmdhbml6YXRpb25faWQpKTsKCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAgLS0gUEFSVEUgMTY6IFBPTElDSUVTIC0gUEVSU09OQUxJWkFUSU9OX1RFQ0hOSVFVRVMgKEdMT0JBTCkKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJhbnlvbmVfdmlld190ZWNobmlxdWVzIiBPTiBwdWJsaWMucGVyc29uYWxpemF0aW9uX3RlY2huaXF1ZXM7CiAgQ1JFQVRFIFBPTElDWSAiYW55b25lX3ZpZXdfdGVjaG5pcXVlcyIKICAgIE9OIHB1YmxpYy5wZXJzb25hbGl6YXRpb25fdGVjaG5pcXVlcyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HIChpc19hY3RpdmUgPSB0cnVlKTsKCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJhZG1pbnNfbWFuYWdlX3RlY2huaXF1ZXMiIE9OIHB1YmxpYy5wZXJzb25hbGl6YXRpb25fdGVjaG5pcXVlczsKICBDUkVBVEUgUE9MSUNZICJhZG1pbnNfbWFuYWdlX3RlY2huaXF1ZXMiCiAgICBPTiBwdWJsaWMucGVyc29uYWxpemF0aW9uX3RlY2huaXF1ZXMgRk9SIEFMTCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAoCiAgICAgIEVYSVNUUyAoCiAgICAgICAgU0VMRUNUIDEgRlJPTSBwdWJsaWMudXNlcl9vcmdhbml6YXRpb25zCiAgICAgICAgV0hFUkUgdXNlcl9pZCA9IGF1dGgudWlkKCkKICAgICAgICAgIEFORCByb2xlIElOICgnb3duZXInLCAnYWRtaW4nKQogICAgICApCiAgICApOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSAxNzogUE9MSUNJRVMgLSBOT1RJRklDQVRJT05TIChVU0VSLVNDT1BFRCkKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiAgRFJPUCBQT0xJQ1kgSUYgRVhJU1RTICJ1c2Vyc192aWV3X293bl9ub3RpZmljYXRpb25zIiBPTiBwdWJsaWMubm90aWZpY2F0aW9uczsKICBDUkVBVEUgUE9MSUNZICJ1c2Vyc192aWV3X293bl9ub3RpZmljYXRpb25zIgogICAgT04gcHVibGljLm5vdGlmaWNhdGlvbnMgRk9SIFNFTEVDVCBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAodXNlcl9pZCA9IGF1dGgudWlkKCkpOwoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgInVzZXJzX3VwZGF0ZV9vd25fbm90aWZpY2F0aW9ucyIgT04gcHVibGljLm5vdGlmaWNhdGlvbnM7CiAgQ1JFQVRFIFBPTElDWSAidXNlcnNfdXBkYXRlX293bl9ub3RpZmljYXRpb25zIgogICAgT04gcHVibGljLm5vdGlmaWNhdGlvbnMgRk9SIFVQREFURSBUTyBhdXRoZW50aWNhdGVkCiAgICBVU0lORyAodXNlcl9pZCA9IGF1dGgudWlkKCkpOwoKICAtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAtLSBQQVJURSAxODogUE9MSUNJRVMgLSBTWVNURU0gVEFCTEVTIChBRE1JTiBPTkxZKQogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKICBEUk9QIFBPTElDWSBJRiBFWElTVFMgImFkbWluc192aWV3X2ZlYXR1cmVfZmxhZ3MiIE9OIHB1YmxpYy5mZWF0dXJlX2ZsYWdzOwogIENSRUFURSBQT0xJQ1kgImFkbWluc192aWV3X2ZlYXR1cmVfZmxhZ3MiCiAgICBPTiBwdWJsaWMuZmVhdHVyZV9mbGFncyBGT1IgU0VMRUNUIFRPIGF1dGhlbnRpY2F0ZWQKICAgIFVTSU5HICgKICAgICAgRVhJU1RTICgKICAgICAgICBTRUxFQ1QgMSBGUk9NIHB1YmxpYy51c2VyX29yZ2FuaXphdGlvbnMKICAgICAgICBXSEVSRSB1c2VyX2lkID0gYXV0aC51aWQoKQogICAgICAgICAgQU5EIHJvbGUgSU4gKCdvd25lcicsICdhZG1pbicpCiAgICAgICkKICAgICk7CgogIERST1AgUE9MSUNZIElGIEVYSVNUUyAiYWRtaW5zX21hbmFnZV9zeXN0ZW1fc2V0dGluZ3MiIE9OIHB1YmxpYy5zeXN0ZW1fc2V0dGluZ3M7CiAgQ1JFQVRFIFBPTElDWSAiYWRtaW5zX21hbmFnZV9zeXN0ZW1fc2V0dGluZ3MiCiAgICBPTiBwdWJsaWMuc3lzdGVtX3NldHRpbmdzIEZPUiBBTEwgVE8gYXV0aGVudGljYXRlZAogICAgVVNJTkcgKAogICAgICBFWElTVFMgKAogICAgICAgIFNFTEVDVCAxIEZST00gcHVibGljLnVzZXJfb3JnYW5pemF0aW9ucwogICAgICAgIFdIRVJFIHVzZXJfaWQgPSBhdXRoLnVpZCgpCiAgICAgICAgICBBTkQgcm9sZSBJTiAoJ293bmVyJywgJ2FkbWluJykKICAgICAgKQogICAgKTsKCiAgLS0gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiAgLS0gUEFSVEUgMTk6IEdSQU5UUwogIC0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKICBHUkFOVCBTRUxFQ1QgT04gcHVibGljLmNhdGVnb3JpZXMgVE8gYXV0aGVudGljYXRlZDsKICBHUkFOVCBTRUxFQ1QgT04gcHVibGljLnN1cHBsaWVycyBUTyBhdXRoZW50aWNhdGVkOwogIEdSQU5UIFNFTEVDVCBPTiBwdWJsaWMucHJvZHVjdHMgVE8gYXV0aGVudGljYXRlZDsKICBHUkFOVCBTRUxFQ1QgT04gcHVibGljLnF1b3RlcyBUTyBhdXRoZW50aWNhdGVkOwogIEdSQU5UIFNFTEVDVCBPTiBwdWJsaWMub3JkZXJzIFRPIGF1dGhlbnRpY2F0ZWQ7CiAgSUYgRVhJU1RTIChTRUxFQ1QgMSBGUk9NIGluZm9ybWF0aW9uX3NjaGVtYS50YWJsZXMgV0hFUkUgdGFibGVfc2NoZW1hPSdwdWJsaWMnIEFORCB0YWJsZV9uYW1lPSdwYXltZW50cycpIFRIRU4KICAgIEdSQU5UIFNFTEVDVCBPTiBwdWJsaWMucGF5bWVudHMgVE8gYXV0aGVudGljYXRlZDsKICBFTkQgSUY7CgogIFJBSVNFIE5PVElDRSAnTWlncmF0aW9uIDIwMjUwMTAzXzAyX3Jsc19vcmdhbml6YXRpb25zIGFwcGxpZWQgc3VjY2Vzc2Z1bGx5Lic7CkVORCAkb3V0ZXIkOwo=
^

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Walkthrough

PR implementa um framework centralizado de validação de contratos para Edge Functions com versionamento semântico, resolução automática de versão, deprecação com RFC 8594, 16 schemas versionados (v1/v2), migração de handlers, testes unitários e smoke tests executáveis via Node.

Changes

Migração de Edge Functions para validação centralizada

Layer / File(s) Summary
Tratamento de erros canônico e resolução de versão com RFC 8594
supabase/functions/_shared/contracts/errors.ts, supabase/functions/_shared/contracts/versioning.ts
Define tipos canônicos de erro (ContractErrorCode, FieldIssue, ContractError), builders de resposta para 400/422/406, conversão de ZodError para paths com notação ponto/índice, e lógica completa de resolução de versão com headers Deprecation, Sunset (RFC 1123) e Link para versionamento seguro.
Parser de contrato e exportações centralizadas
supabase/functions/_shared/contracts/parse.ts, supabase/functions/_shared/contracts/index.ts
Implementação de parseContract que orquestra resolução de versão, leitura do body com suporte a prereadBody, validação JSON e parse Zod tipado. Retorna resultado discriminado com version, data tipado pela versão e responseHeaders. Barrel exports consolidam API do módulo.
Schemas versionados para 16 endpoints (v1 compat + v2 strict)
supabase/functions/_shared/contracts/schemas/*.ts
Definição de 16 conjuntos: bi-copilot, block-ip-temporarily, e2e-cleanup, force-global-logout, kit-ai-builder, market-intelligence-insights, ownership-audit, ownership-repair, product-webhook, send-transactional-email, simulation-orchestrator, step-up-verify, sync-external-db, trends-insights, webhook-dispatcher, webhook-inbound. Cada v1 preserva formato atual, v2 adiciona validações rigorosas, idempotency_key, .strict(), discriminated unions e metadados de deprecação com sunset.
Migração de 16 handlers para parseContract
supabase/functions/{bi-copilot,block-ip-temporarily,e2e-cleanup,force-global-logout,kit-ai-builder,market-intelligence-insights,ownership-audit,ownership-repair,product-webhook,send-transactional-email,simulation-orchestrator,step-up-verify,sync-external-db,trends-insights,webhook-dispatcher,webhook-inbound}/index.ts
Atualização sistemática: remove parse manual de JSON, integra parseContract com schemas, retorna early em caso de falha, propaga responseHeaders do contrato em todas as respostas JSON (cache hit, erro, sucesso).
Testes unitários, helpers e configuração vitest
tests/contracts/_helpers.ts, tests/contracts/errors.test.ts, tests/contracts/versioning.test.ts, vitest.config.ts
Utilitários (makeRequest, readBody, expectContractError), testes de formato de erro e conversão Zod com paths aninhados, testes de resolução de versão e headers RFC 8594, aliases Zod CDN para Node.
Testes de contrato para 16 endpoints (v1 + v2)
tests/contracts/migrated-endpoints.contract.test.ts, tests/contracts/product-webhook.contract.test.ts, tests/contracts/send-transactional-email.contract.test.ts, tests/contracts/step-up-verify.contract.test.ts, tests/contracts/webhooks.contract.test.ts
Suítes de testes cobrindo: aceitação (v1 com Deprecation, v2 sem), erros (missing body, JSON inválido, tipos inválidos, campos faltantes, versão 406), validações específicas (discriminated unions, cross-field rules, rejeição de sync em v2 product-webhook).
Documentação, MIGRATION_GUIDE e smoke tests
docs/contracts/README.md, docs/contracts/MIGRATION_GUIDE.md, scripts/contract-testing.mjs
README especifica API completa, formato canônico de erro, negociação de versão e testes. MIGRATION_GUIDE descreve 5 passos, casos especiais (HMAC via prereadBody, discriminated union), tabelas de migração v1→v2. Contract-testing.mjs reescrito como smoke test Node com fetch real, timeout, validação de headers e mapeamento de erros.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • adm01-debug/promo-gifts-v4#46: Implementação da migração de 5 endpoints P0 listados (bi-copilot, kit-ai-builder, market-intelligence-insights, product-webhook, send-transactional-email) para parseContract com schemas v1/v2 e testes.
  • adm01-debug/promo-gifts-v4#47: Migração de 5 endpoints P1 (ownership-audit, ownership-repair, simulation-orchestrator, sync-external-db, trends-insights) com parseContract, schemas versionados e validações.

Possibly related PRs

  • adm01-debug/promo-gifts-v4#45: Mesmo pacote _shared/contracts com infra foundation (errors, parse, versioning) — este PR estende com 16 schemas, handlers e testes completos.
  • adm01-debug/promo-gifts-v4#87: Reutiliza mesmos arquivos centrais (parse.ts, errors.ts, versioning.ts) e schemas (product-webhook, webhook-inbound, webhook-dispatcher) — PRs conectadas na mesma migração.
  • adm01-debug/promo-gifts-v4#57: Workflow CI que executa testes em tests/contracts/ e scripts/contract-testing.mjs — fluxo direto de validação.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/contracts-validation-zod-422-versioning
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/contracts-validation-zod-422-versioning

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduz um pacote canônico de validação/versionamento de contratos (Zod) para Edge Functions Supabase, padronizando parsing do body, negociação de versão (accept-version / ?v=), headers de depreciação (RFC 8594) e formato de erro (400/422/406). O PR também migra múltiplas funções para esse fluxo e adiciona uma suíte de contract tests (Vitest) + um runner de smoke tests via HTTP real.

Changes:

  • Adiciona supabase/functions/_shared/contracts/* (errors/versioning/parse + schemas versionados) para validação e respostas padronizadas.
  • Migra diversos endpoints para parseContract(...), com suporte a v1 (compat) e v2 (strict), e adiciona headers x-contract-version/Deprecation/Sunset.
  • Cria testes de contrato em tests/contracts/*, ajusta vitest.config.ts para alias de imports Deno (Zod via URL) e atualiza o script scripts/contract-testing.mjs.

Reviewed changes

Copilot reviewed 48 out of 48 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
vitest.config.ts Adiciona aliases para mapear imports Deno (Zod via URL) para zod no Vitest.
tests/contracts/_helpers.ts Helpers para montar Request/asserções de erro canônico em testes.
tests/contracts/errors.test.ts Testa builders/conversores do formato de erro canônico.
tests/contracts/versioning.test.ts Testa negociação de versão e headers de depreciação/sunset.
tests/contracts/product-webhook.contract.test.ts Testa contratos v1/v2 do product-webhook (400/422/406 + strict).
tests/contracts/send-transactional-email.contract.test.ts Testa contratos v1/v2 do send-transactional-email e versioning.
tests/contracts/step-up-verify.contract.test.ts Testa contratos v1/v2 do step-up-verify (discriminated union).
tests/contracts/webhooks.contract.test.ts Testa contratos v1/v2 de webhook-inbound e webhook-dispatcher.
tests/contracts/migrated-endpoints.contract.test.ts Smoke tests consolidados para endpoints migrados adicionais.
supabase/functions/webhook-inbound/index.ts Aplica parseContract (com prereadBody) e adiciona versionamento no ingest HMAC.
supabase/functions/webhook-dispatcher/index.ts Substitui schema local por contratos versionados compartilhados.
supabase/functions/trends-insights/index.ts Passa a validar body via parseContract e propagar headers de versão.
supabase/functions/sync-external-db/index.ts Valida payload + versioning via parseContract.
supabase/functions/step-up-verify/index.ts Valida payload via parseContract e passa a anexar headers de contrato.
supabase/functions/simulation-orchestrator/index.ts Valida payload via parseContract e propaga headers em resposta 200.
supabase/functions/send-transactional-email/index.ts Valida body via parseContract com schemas v1/v2.
supabase/functions/product-webhook/index.ts Troca validação manual por parseContract + schemas v1/v2 e headers.
supabase/functions/ownership-repair/index.ts Adiciona parseContract/schemas para versionar e validar body.
supabase/functions/ownership-audit/index.ts Adiciona parseContract/schemas para versionar e validar body.
supabase/functions/market-intelligence-insights/index.ts Valida body via parseContract e propaga headers nas respostas principais.
supabase/functions/kit-ai-builder/index.ts Valida body via parseContract e adiciona schema versionado.
supabase/functions/force-global-logout/index.ts Valida body via parseContract com versões v1/v2.
supabase/functions/e2e-cleanup/index.ts Valida body via parseContract e preserva audit em falha de payload.
supabase/functions/block-ip-temporarily/index.ts Valida body via parseContract com schema versionado.
supabase/functions/bi-copilot/index.ts Valida body via parseContract e adiciona schema versionado.
supabase/functions/_shared/contracts/index.ts Barrel export do pacote de contratos + re-export de Zod.
supabase/functions/_shared/contracts/parse.ts Implementa parseContract: resolve versão, lê body, valida com Zod, retorna responses canônicas.
supabase/functions/_shared/contracts/errors.ts Builders do formato canônico de erro (400/422/406) e utilitários (ZodError → fields).
supabase/functions/_shared/contracts/versioning.ts Negociação de versão + headers x-contract-version e RFC 8594 (Deprecation/Sunset/Link).
supabase/functions/_shared/contracts/schemas/webhook-inbound.ts Define contratos v1 passthrough e v2 envelope strict para inbound webhooks.
supabase/functions/_shared/contracts/schemas/webhook-dispatcher.ts Define contratos v1 compat e v2 strict (discriminated union por mode).
supabase/functions/_shared/contracts/schemas/trends-insights.ts Define contratos v1/v2 para trends-insights (v2 strict).
supabase/functions/_shared/contracts/schemas/sync-external-db.ts Define contratos v1/v2 para sync-external-db (v2 strict + datetime).
supabase/functions/_shared/contracts/schemas/step-up-verify.ts Define contratos v1 permissivo e v2 strict (discriminated union por step).
supabase/functions/_shared/contracts/schemas/simulation-orchestrator.ts Define contratos v1/v2 para simulation-orchestrator (v2 exige idempotency_key).
supabase/functions/_shared/contracts/schemas/send-transactional-email.ts Define contratos v1/v2 para send-transactional-email (v2 strict + idempotency_key).
supabase/functions/_shared/contracts/schemas/product-webhook.ts Define contratos v1 compat e v2 strict + validações cruzadas e idempotency_key.
supabase/functions/_shared/contracts/schemas/ownership-repair.ts Define contratos v1/v2 para ownership-repair (v2 strict + idempotency_key).
supabase/functions/_shared/contracts/schemas/ownership-audit.ts Define contratos v1/v2 para ownership-audit (v2 strict exige triggered_by).
supabase/functions/_shared/contracts/schemas/market-intelligence-insights.ts Define contratos v1/v2 para market-intelligence-insights (v2 strict + UUIDs).
supabase/functions/_shared/contracts/schemas/kit-ai-builder.ts Define contratos v1/v2 para kit-ai-builder (v2 strict + idempotency_key).
supabase/functions/_shared/contracts/schemas/force-global-logout.ts Define contratos v1/v2 para force-global-logout (v2 strict + idempotency_key).
supabase/functions/_shared/contracts/schemas/e2e-cleanup.ts Define contratos v1/v2 para e2e-cleanup (v2 strict + confirm/idempotency_key).
supabase/functions/_shared/contracts/schemas/block-ip-temporarily.ts Define contratos v1/v2 para block-ip-temporarily (v2 strict + regex rigoroso).
supabase/functions/_shared/contracts/schemas/bi-copilot.ts Define contratos v1/v2 para bi-copilot (v2 strict + context obrigatório).
scripts/contract-testing.mjs Reescreve runner de smoke tests via HTTP com checks de status/code/headers.
docs/contracts/README.md Documenta o pacote de contratos, formato de erro e versionamento.
docs/contracts/MIGRATION_GUIDE.md Guia de migração para adotar parseContract (inclui casos especiais).
Comments suppressed due to low confidence (1)

supabase/functions/kit-ai-builder/index.ts:46

  • Depois que parseContract resolve a versão, o handler recebe responseHeaders, mas várias respostas de erro (ex.: LOVABLE_API_KEY ausente, falhas 429/402/500 do AI gateway e o catch) retornam só com corsHeaders. Isso perde x-contract-version/Deprecation/Sunset e contradiz a semântica de responseHeaders (anexar em toda resposta). Solução: compor const baseHeaders = { ...corsHeaders, ...responseHeaders, 'Content-Type': 'application/json' } e reutilizar em todos os returns após o parse.
    const contractResult = await parseContract(req, KitAiBuilderSchemas, {
      corsHeaders,
    });
    if (!contractResult.ok) return contractResult.response;
    const { data: body, responseHeaders } = contractResult;
    const prompt = body.prompt.trim();

    const LOVABLE_API_KEY = Deno.env.get('LOVABLE_API_KEY');
    if (!LOVABLE_API_KEY) {
      return new Response(
        JSON.stringify({ error: 'LOVABLE_API_KEY não configurado' }),
        { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      );

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +56 to +61
// Validação de contrato (v1 default, v2 via accept-version).
// parseContract retorna 400 (json inválido/vazio), 422 (validação), 406 (versão).
const contractResult = await parseContract(req, WebhookDispatcherSchemas, { corsHeaders });
if (!contractResult.ok) return contractResult.response;
const { version: contractVersion, data: parsedData } = contractResult;

Comment on lines +41 to +48
// Parse + valida + versiona em um único passo
const result = await parseContract(req, ProductWebhookSchemas, { corsHeaders });
if (!result.ok) return result.response;

const parsed = WebhookPayloadSchema.safeParse(rawBody);
if (!parsed.success) {
return new Response(JSON.stringify({ error: "Validation failed", details: parsed.error.flatten().fieldErrors }), {
status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
const payload: WebhookPayload = parsed.data;
console.log(`Product webhook action: ${payload.action}`);
const { version, data, responseHeaders } = result;
// headers anexados em TODAS as respostas de sucesso (versão + deprecation)
const okHeaders = { ...corsHeaders, ...responseHeaders, "Content-Type": "application/json" };

Comment on lines +52 to +57
const contractResult = await parseContract(req, TrendsInsightsSchemas, {
corsHeaders,
});
if (!contractResult.ok) return contractResult.response;
const { data: body, responseHeaders } = contractResult;
const days = body.days ?? 30;
Comment on lines +19 to +26
const contractResult = await parseContract(req, SyncExternalDbSchemas, {
corsHeaders,
});
if (!contractResult.ok) return contractResult.response;
const { data: body, responseHeaders } = contractResult;
const { table } = body;
const direction = body.direction ?? "to-external";
const since = body.since;
Comment on lines +162 to 167
const contractResult = await parseContract(req, SendTransactionalEmailSchemas, {
corsHeaders,
});
if (!contractResult.ok) return contractResult.response;
const { data: body, responseHeaders } = contractResult;

Comment on lines 8 to 16
// Module-scope CORS headers — atribuído per-request no handler.
let corsHeaders: Record<string, string> = {};
let contractResponseHeaders: Record<string, string> = {};

function jsonRes(body: unknown, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { ...corsHeaders, "Content-Type": "application/json" },
headers: { ...corsHeaders, ...contractResponseHeaders, "Content-Type": "application/json" },
});
Comment on lines 41 to 48
const corsHeaders = buildPublicCorsHeaders({ extraAllowHeaders: ["x-e2e-cleanup-token"], allowMethods: "POST, OPTIONS" });
let contractResponseHeaders: Record<string, string> = {};

function jsonResponse(body: unknown, status = 200, extraHeaders: Record<string, string> = {}) {
return new Response(JSON.stringify(body), {
status,
headers: { ...corsHeaders, "Content-Type": "application/json", ...extraHeaders },
headers: { ...corsHeaders, ...contractResponseHeaders, "Content-Type": "application/json", ...extraHeaders },
});
corsHeaders,
});
if (!contractResult.ok) return contractResult.response;
const { data: body, responseHeaders } = contractResult;
Comment on lines 220 to 231
let userId: string | null = null;
let responseHeaders: Record<string, string> = {};
try {
const auth = await authenticateRequest(req);
userId = auth.userId;
const body = (await req.json().catch(() => ({}))) as RequestBody;
const contractResult = await parseContract(req, MarketIntelligenceInsightsSchemas, {
corsHeaders,
});
if (!contractResult.ok) return contractResult.response;
const body = contractResult.data as RequestBody;
responseHeaders = contractResult.responseHeaders;
const cacheKey = await buildCacheKey(body);
Comment on lines +53 to +60
const contractResult = await parseContract(req, SimulationOrchestratorSchemas, {
corsHeaders,
});
if (!contractResult.ok) return contractResult.response;
const { data: parsedBody, responseHeaders } = contractResult;
const count = parsedBody.count ?? 100;
const targetFunctions = parsedBody.targetFunctions ?? ["external-db-bridge", "webhook-inbound", "product-webhook"];
const mode = parsedBody.mode ?? "resilience";
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

🧹 Nitpick comments (2)
supabase/functions/_shared/contracts/schemas/product-webhook.ts (1)

91-117: ⚡ Quick win

Falta exclusividade entre campos por action no v2.

Hoje o schema aceita combinações ambíguas (ex.: action="delete" com product/products, ou upsert com external_ids). Além de exigir o campo correto, recomendo rejeitar os campos incompatíveis no superRefine para evitar regressão de comportamento no handler.

Diff sugerido
   .superRefine((val, ctx) => {
     if (val.action === "delete") {
       if (!val.external_ids || val.external_ids.length === 0) {
         ctx.addIssue({
           code: z.ZodIssueCode.custom,
           path: ["external_ids"],
           message: "external_ids is required when action='delete'",
         });
       }
+      if (val.product || (val.products && val.products.length > 0)) {
+        ctx.addIssue({
+          code: z.ZodIssueCode.custom,
+          path: ["action"],
+          message: "delete does not allow product/products payload",
+        });
+      }
     } else if (val.action === "upsert") {
       if (!val.product) {
         ctx.addIssue({
           code: z.ZodIssueCode.custom,
           path: ["product"],
           message: "product is required when action='upsert'",
         });
       }
+      if (val.external_ids || (val.products && val.products.length > 0)) {
+        ctx.addIssue({
+          code: z.ZodIssueCode.custom,
+          path: ["action"],
+          message: "upsert does not allow external_ids/products payload",
+        });
+      }
     } else if (val.action === "batch_upsert") {
       if (!val.products || val.products.length === 0) {
         ctx.addIssue({
           code: z.ZodIssueCode.custom,
           path: ["products"],
           message: "products[] (non-empty) is required when action='batch_upsert'",
         });
       }
+      if (val.product || val.external_ids) {
+        ctx.addIssue({
+          code: z.ZodIssueCode.custom,
+          path: ["action"],
+          message: "batch_upsert does not allow product/external_ids payload",
+        });
+      }
     }
   });

As per coding guidelines, "supabase/functions/**/*.ts: Validação de payload em webhooks (shared secret, assinatura HMAC quando aplicável)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/functions/_shared/contracts/schemas/product-webhook.ts` around lines
91 - 117, No superRefine atual do schema (a função que inspeciona val.action)
falta rejeitar campos incompatíveis por action; dentro da mesma superRefine,
além de exigir o campo correto para cada valor de val.action ("delete" requer
external_ids, "upsert" requer product, "batch_upsert" requer products
non-empty), adicione checks que detectem e adicionem ctx.addIssue quando campos
mutuamente exclusivos apareçam (por exemplo: quando action === "delete" erro se
product ou products estiverem presentes; quando action === "upsert" erro se
external_ids ou products estiverem presentes; quando action === "batch_upsert"
erro se external_ids ou product estiverem presentes), usando ZodIssueCode.custom
e preenchendo path com o nome do campo para garantir validação exclusiva e
evitar combinações ambíguas.
docs/contracts/README.md (1)

20-30: ⚡ Quick win

Adicionar linguagem nos blocos de código Markdown (MD040).

Os fences nessas seções estão sem linguagem declarada. Isso já apareceu no lint e é rápido de corrigir.

Diff sugerido
-```
+```text
 supabase/functions/_shared/contracts/
 ├── index.ts          # barrel export
 ├── errors.ts         # builders 400 / 422 / 406 com formato {code, message, fields[]}
 ├── versioning.ts     # negociação via accept-version | ?v= | default
 ├── parse.ts          # parseContract(req, schemas) — orquestrador
 └── schemas/          # schemas por endpoint, sempre múltiplas versões
     ├── product-webhook.ts
     ├── webhook-inbound.ts
     └── webhook-dispatcher.ts

- +http
Deprecation: true
Sunset: Mon, 31 Aug 2026 00:00:00 GMT
Link: https://docs/...; rel="deprecation"

Also applies to: 89-93

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/contracts/README.md` around lines 20 - 30, Update the fenced code blocks
in docs/contracts/README.md so they declare a language: add "text" (or "bash")
to the directory-tree fence that starts with
"supabase/functions/_shared/contracts/" and add "http" to the HTTP header
example block that begins with "Deprecation: true" (also apply the same change
to the similar block around the "Deprecation" headers at the other occurrence).
This fixes MD040 by specifying the language on the three fences and keeps the
visible content unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/contracts/MIGRATION_GUIDE.md`:
- Around line 14-38: The header says “14 candidatas” but the enumerated services
(`send-transactional-email`, `kit-ai-builder`, `market-intelligence-insights`,
`bi-copilot`, `step-up-verify`, `ownership-audit`, `ownership-repair`,
`simulation-orchestrator`, `sync-external-db`, `trends-insights`,
`force-global-logout`, `e2e-cleanup`, `block-ip-temporarily`) total 13; either
change the count to “13 candidatas” or insert the missing service into the
appropriate priority group so the totals align, and keep the P0/P1/P2 numbering
consistent after the change.

In `@scripts/contract-testing.mjs`:
- Line 35: TIMEOUT_MS is assigned directly from
Number(process.env.CONTRACT_TEST_TIMEOUT_MS || 10000) which can yield NaN, 0 or
negative and cause immediate abort; change the assignment to parse and normalize
the env var into a positive integer with a safe fallback (e.g.,
parseInt(process.env.CONTRACT_TEST_TIMEOUT_MS, 10), ensure it's a finite integer
> 0, clamp to a minimum like 1000 if desired) and assign that sanitized value to
TIMEOUT_MS so the constant is always a valid positive timeout.
- Around line 33-40: The current initializer permits using
SUPABASE_SERVICE_ROLE_KEY as a fallback for ANON_KEY (see the ANON_KEY
constant), which elevates privileges; change ANON_KEY to read only
process.env.SUPABASE_ANON_KEY (remove reference to SUPABASE_SERVICE_ROLE_KEY)
and keep the existing guard that logs the error and exits when ANON_KEY is falsy
so the script fails unless an anon key is provided.

In `@supabase/functions/_shared/contracts/schemas/block-ip-temporarily.ts`:
- Around line 12-13: The current IP_REGEX_V2 in block-ip-temporarily.ts allows
semantically invalid IPs; replace the single-regex approach with a proper
validator function (e.g., isValidIpOrCidr) that parses input into address and
optional prefix and uses a robust parser (net.isIP or ipaddr.js / a small CIDR
parser) to confirm IPv4 octets are 0-255, IPv6 group structure is valid, and
CIDR prefix ranges (0–32 for IPv4, 0–128 for IPv6) are enforced; remove or stop
relying on IP_REGEX_V2 and call the new isValidIpOrCidr from wherever
IP_REGEX_V2 was used to ensure only semantically valid IP/CIDR values pass the
contract.

In `@supabase/functions/_shared/contracts/schemas/webhook-inbound.ts`:
- Around line 20-53: The current defaultVersion is "1" while WebhookInboundV1 is
z.any(), allowing unrestricted payloads; update
WebhookInboundSchemas.defaultVersion to "2" so the service uses the structured
WebhookInboundV2 by default (event, occurred_at, data, optional
idempotency_key), and also harden legacy WebhookInboundV1 to at least require an
object (change WebhookInboundV1 from z.any() to z.record(z.unknown())) if you
want a safer transition period while keeping v1 as an explicit opt-in; ensure
references to WebhookInboundV1, WebhookInboundV2 and defaultVersion are updated
accordingly.

In `@supabase/functions/_shared/contracts/versioning.ts`:
- Around line 103-113: The normalization logic for headerVal and query param qv
can produce an empty string (e.g., "   " or "v") which currently gets treated as
a provided version and triggers unsupported_version; update the handling in the
function that reads accept-version/v so that after applying .replace(/^v/i,
"").split(".")[0].trim() you only return the value if it's non-empty (otherwise
treat it as absent and continue to default behavior). Reference the existing
identifiers headerVal and qv in your changes and ensure both branches (the
header branch and the query-param branch inside the try) perform the non-empty
check before returning.

In `@supabase/functions/bi-copilot/index.ts`:
- Line 51: The error responses after parsing (use of contractResult destructure:
const { data: body, responseHeaders } = contractResult) are not propagating
responseHeaders back to the client; update every error return in the
parse/validation branches (the returns around lines flagged: roughly 87-105 and
117-120) to include responseHeaders in the returned Response/response object
(i.e., when constructing error Responses or objects in the functions that
currently return errors at those spots, add responseHeaders into the Response
init or attach them to the returned payload), ensuring all branches that
reference body/contractResult propagate the responseHeaders consistently.

In `@supabase/functions/block-ip-temporarily/index.ts`:
- Around line 9-10: The module-level mutable objects corsHeaders and
contractResponseHeaders are being reused across requests (symbols: corsHeaders,
contractResponseHeaders) causing header leakage under concurrency; make these
header maps local to each request handler instead: remove or stop using the
module-scoped corsHeaders/contractResponseHeaders and initialize fresh local
header objects at the start of the handler(s) that set them (e.g., where you
currently assign to corsHeaders and contractResponseHeaders), then use those
local variables when building the response(s) so each request has its own header
state.

In `@supabase/functions/e2e-cleanup/index.ts`:
- Line 42: O objeto global contractResponseHeaders está compartilhado entre
requests e pode vazar entre concorrentes; em vez de usar a variável de módulo
contractResponseHeaders, crie um novo objeto de headers por request (por exemplo
dentro do handler ou imediatamente antes de chamar jsonResponse) e passe esse
objeto local para jsonResponse; atualize todas as ocorrências que atualmente
usam a variável global (referências a contractResponseHeaders nas seções
correspondentes) para usar o novo objeto local, garantindo que cada request
tenha seu próprio mapa de headers.

In `@supabase/functions/force-global-logout/index.ts`:
- Around line 9-10: The module-level mutable objects corsHeaders and
contractResponseHeaders are reused/updated across requests causing unsafe
cross-request state; move their declarations and any updates into the request
handler (or create fresh local objects per invocation) so each request gets its
own headers object, and ensure any places that update them (the code at the
sites referenced around the former lines 24-25 and 63) operate on the
local/request-scoped variables or on shallow copies rather than the module-level
variables.

In `@supabase/functions/kit-ai-builder/index.ts`:
- Around line 38-39: contractResult.ok === true currently extracts const { data:
body, responseHeaders } = contractResult and prompt = body.prompt.trim(), but
several error branches (the ones that throw/return errors after parsing body) do
not include responseHeaders in their error responses; update each error
return/throw that follows the successful contractResult check (the branches that
use body/prompt and currently send errors around
validation/authorization/processing) to include responseHeaders alongside the
error payload so metadata is preserved. Locate all error-returning code paths
that reference body or prompt (e.g., where contractResult was deconstructed) and
add responseHeaders to the response object or error constructor so the error
responses carry responseHeaders back to the caller. Ensure the shape matches
existing success responses (include responseHeaders property) and run tests to
confirm headers propagate.

In `@supabase/functions/ownership-audit/index.ts`:
- Around line 19-20: corsHeaders and contractResponseHeaders are declared at
module scope causing request-crossing race conditions; move these variables into
the per-request handler so each incoming request gets its own local header
objects. Specifically, remove the module-level let declarations for corsHeaders
and contractResponseHeaders and instead create const corsHeaders = {} and const
contractResponseHeaders = {} at the start of the exported request handler (the
function that processes incoming requests), update all references to use these
local variables, and ensure no other module-scope state is mutated so headers
from one request cannot leak into another.

In `@supabase/functions/ownership-repair/index.ts`:
- Around line 19-20: The module-level variables corsHeaders and
contractResponseHeaders are storing per-request state and can cause race
conditions; move these into the request handler scope so each invocation creates
its own header objects, update all uses (where corsHeaders and
contractResponseHeaders are currently read/modified and later used to form the
response) to reference the local variables, and ensure the response-building
code (the handler's return) uses the local header objects rather than any
module-level state.

In `@supabase/functions/product-webhook/index.ts`:
- Around line 140-145: O catch block in the product webhook (the catch handling
the variable error and building the Response) must not echo error.message back
to clients; instead keep a full internal log (console.error(error)) and return a
fixed, generic 500 message in the JSON payload (e.g., code: "internal_error",
message: "Internal server error") while preserving CORS and Content-Type
headers. Update the Response creation that currently uses errorMessage to use
the sanitized fixed string and ensure no SQL/credential details or error.message
are returned.

In `@supabase/functions/step-up-verify/index.ts`:
- Around line 15-16: Os headers de resposta (corsHeaders e
contractResponseHeaders) estão declarados em escopo de módulo e podem ser
compartilhados entre requests concorrentes; mova essas variáveis para dentro do
handler que processa a requisição (por exemplo a função exportada do arquivo) e
passe o objeto de headers resultante como argumento para o helper json onde for
chamado; atualize todas referências atuais a corsHeaders e
contractResponseHeaders (incluindo os lugares identificados em torno de
parseContract e nos blocos que setam Deprecation/Sunset) para usar a versão
local do handler em vez das variáveis de módulo.

In `@supabase/functions/webhook-dispatcher/index.ts`:
- Around line 58-60: parseContract currently returns responseHeaders that are
discarded; keep and propagate them by extracting responseHeaders from
contractResult alongside contractVersion and parsedData (e.g., const { version:
contractVersion, data: parsedData, responseHeaders } = contractResult) and merge
...responseHeaders into every HTTP response returned after this point (responses
created by this handler/dispatch logic), ensuring Deprecation/Sunset/versioning
headers are preserved; update all return/response construction code paths below
to include these responseHeaders.

In `@supabase/functions/webhook-inbound/index.ts`:
- Around line 149-151: The response is currently exposing the internal error
text via the message field (variable msg) when constructing the Response;
instead, keep detailed error text only in logs and return a fixed, non-sensitive
message in the JSON response. Update the code that builds the Response (the new
Response(...) call that uses msg and corsHeaders) to log msg (or the caught
error) to your logger (e.g., console.error/processLogger) and replace the
returned message value with a fixed safe string like "internal_server_error" or
"An internal error occurred" while preserving status 500 and corsHeaders; do not
include tokens, SQL, stack traces, or secrets in the response.

In `@tests/contracts/_helpers.ts`:
- Around line 37-39: The readBody function currently returns await res.json()
without error handling which causes opaque promise rejections on invalid JSON;
wrap the JSON parse in a try/catch inside readBody (referencing readBody and the
ContractError return type) and on failure attempt to read the raw body
(res.text()) and throw a new Error that includes the HTTP status and the raw
body (or the original parsing error) so tests surface a clear message instead of
a generic rejection.

In `@tests/contracts/product-webhook.contract.test.ts`:
- Line 102: O fixture usa o UUID não-RFC4122
`11111111-2222-3333-4444-555555555555`, que faz falhar validações como
z.string().uuid(); substitua todas as ocorrências desse valor usadas em
idempotency_key e replay_delivery_id por UUIDs válidos RFC4122 (por exemplo v4
com o nibble de versão = 4 e o nibble de variante = 8/9/A/B) nas fixtures dos
testes (ocorrências em product-webhook.contract.test.ts e
webhooks.contract.test.ts) para que zod().uuid()/z.string().uuid() passem;
aplique a troca a todas as instâncias desses campos para manter consistência dos
testes.

---

Nitpick comments:
In `@docs/contracts/README.md`:
- Around line 20-30: Update the fenced code blocks in docs/contracts/README.md
so they declare a language: add "text" (or "bash") to the directory-tree fence
that starts with "supabase/functions/_shared/contracts/" and add "http" to the
HTTP header example block that begins with "Deprecation: true" (also apply the
same change to the similar block around the "Deprecation" headers at the other
occurrence). This fixes MD040 by specifying the language on the three fences and
keeps the visible content unchanged.

In `@supabase/functions/_shared/contracts/schemas/product-webhook.ts`:
- Around line 91-117: No superRefine atual do schema (a função que inspeciona
val.action) falta rejeitar campos incompatíveis por action; dentro da mesma
superRefine, além de exigir o campo correto para cada valor de val.action
("delete" requer external_ids, "upsert" requer product, "batch_upsert" requer
products non-empty), adicione checks que detectem e adicionem ctx.addIssue
quando campos mutuamente exclusivos apareçam (por exemplo: quando action ===
"delete" erro se product ou products estiverem presentes; quando action ===
"upsert" erro se external_ids ou products estiverem presentes; quando action ===
"batch_upsert" erro se external_ids ou product estiverem presentes), usando
ZodIssueCode.custom e preenchendo path com o nome do campo para garantir
validação exclusiva e evitar combinações ambíguas.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7001344c-cb89-42e5-a5e6-0404f1b256c1

📥 Commits

Reviewing files that changed from the base of the PR and between ca9ca9c and b2d8da6.

📒 Files selected for processing (48)
  • docs/contracts/MIGRATION_GUIDE.md
  • docs/contracts/README.md
  • scripts/contract-testing.mjs
  • supabase/functions/_shared/contracts/errors.ts
  • supabase/functions/_shared/contracts/index.ts
  • supabase/functions/_shared/contracts/parse.ts
  • supabase/functions/_shared/contracts/schemas/bi-copilot.ts
  • supabase/functions/_shared/contracts/schemas/block-ip-temporarily.ts
  • supabase/functions/_shared/contracts/schemas/e2e-cleanup.ts
  • supabase/functions/_shared/contracts/schemas/force-global-logout.ts
  • supabase/functions/_shared/contracts/schemas/kit-ai-builder.ts
  • supabase/functions/_shared/contracts/schemas/market-intelligence-insights.ts
  • supabase/functions/_shared/contracts/schemas/ownership-audit.ts
  • supabase/functions/_shared/contracts/schemas/ownership-repair.ts
  • supabase/functions/_shared/contracts/schemas/product-webhook.ts
  • supabase/functions/_shared/contracts/schemas/send-transactional-email.ts
  • supabase/functions/_shared/contracts/schemas/simulation-orchestrator.ts
  • supabase/functions/_shared/contracts/schemas/step-up-verify.ts
  • supabase/functions/_shared/contracts/schemas/sync-external-db.ts
  • supabase/functions/_shared/contracts/schemas/trends-insights.ts
  • supabase/functions/_shared/contracts/schemas/webhook-dispatcher.ts
  • supabase/functions/_shared/contracts/schemas/webhook-inbound.ts
  • supabase/functions/_shared/contracts/versioning.ts
  • supabase/functions/bi-copilot/index.ts
  • supabase/functions/block-ip-temporarily/index.ts
  • supabase/functions/e2e-cleanup/index.ts
  • supabase/functions/force-global-logout/index.ts
  • supabase/functions/kit-ai-builder/index.ts
  • supabase/functions/market-intelligence-insights/index.ts
  • supabase/functions/ownership-audit/index.ts
  • supabase/functions/ownership-repair/index.ts
  • supabase/functions/product-webhook/index.ts
  • supabase/functions/send-transactional-email/index.ts
  • supabase/functions/simulation-orchestrator/index.ts
  • supabase/functions/step-up-verify/index.ts
  • supabase/functions/sync-external-db/index.ts
  • supabase/functions/trends-insights/index.ts
  • supabase/functions/webhook-dispatcher/index.ts
  • supabase/functions/webhook-inbound/index.ts
  • tests/contracts/_helpers.ts
  • tests/contracts/errors.test.ts
  • tests/contracts/migrated-endpoints.contract.test.ts
  • tests/contracts/product-webhook.contract.test.ts
  • tests/contracts/send-transactional-email.contract.test.ts
  • tests/contracts/step-up-verify.contract.test.ts
  • tests/contracts/versioning.test.ts
  • tests/contracts/webhooks.contract.test.ts
  • vitest.config.ts

Comment on lines +14 to +38
então ela é candidata. Auditoria 2026-05-21 listou 14 candidatas; abaixo a
lista priorizada:

### P0 (próximas a migrar)

1. `send-transactional-email` — sem nenhuma validação runtime
2. `kit-ai-builder` — payload livre vai direto pro modelo
3. `market-intelligence-insights` — idem
4. `bi-copilot` — query SQL aceita string livre
5. `step-up-verify` — fluxo de autenticação sensível

### P1

6. `ownership-audit`
7. `ownership-repair`
8. `simulation-orchestrator`
9. `sync-external-db`
10. `trends-insights`

### P2 (já têm guarda mas merecem schema explícito)

11. `force-global-logout`
12. `e2e-cleanup`
13. `block-ip-temporarily`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inconsistência na contagem de candidatas de migração.

A linha com “14 candidatas” não bate com a lista abaixo (13 itens). Ajuste a contagem ou inclua a função faltante para evitar lacuna no plano de migração.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/contracts/MIGRATION_GUIDE.md` around lines 14 - 38, The header says “14
candidatas” but the enumerated services (`send-transactional-email`,
`kit-ai-builder`, `market-intelligence-insights`, `bi-copilot`,
`step-up-verify`, `ownership-audit`, `ownership-repair`,
`simulation-orchestrator`, `sync-external-db`, `trends-insights`,
`force-global-logout`, `e2e-cleanup`, `block-ip-temporarily`) total 13; either
change the count to “13 candidatas” or insert the missing service into the
appropriate priority group so the totals align, and keep the P0/P1/P2 numbering
consistent after the change.

Comment on lines +33 to +40
const ANON_KEY = process.env.SUPABASE_ANON_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY;
const PRODUCT_WEBHOOK_SECRET = process.env.N8N_PRODUCT_WEBHOOK_SECRET || '';
const TIMEOUT_MS = Number(process.env.CONTRACT_TEST_TIMEOUT_MS || 10000);

if (!ANON_KEY) {
console.error('❌ SUPABASE_ANON_KEY (ou SERVICE_ROLE_KEY) é obrigatório.');
process.exit(2);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Evite fallback para SUPABASE_SERVICE_ROLE_KEY neste smoke test.

Permitir service role aqui aumenta privilégio desnecessário e contradiz a mensagem/uso esperado de SUPABASE_ANON_KEY. Recomendo aceitar apenas anon key e falhar quando ausente.

Diff sugerido
-const ANON_KEY = process.env.SUPABASE_ANON_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY;
+const ANON_KEY = process.env.SUPABASE_ANON_KEY;

 if (!ANON_KEY) {
-  console.error('❌ SUPABASE_ANON_KEY (ou SERVICE_ROLE_KEY) é obrigatório.');
+  console.error('❌ SUPABASE_ANON_KEY é obrigatório.');
   process.exit(2);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ANON_KEY = process.env.SUPABASE_ANON_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY;
const PRODUCT_WEBHOOK_SECRET = process.env.N8N_PRODUCT_WEBHOOK_SECRET || '';
const TIMEOUT_MS = Number(process.env.CONTRACT_TEST_TIMEOUT_MS || 10000);
if (!ANON_KEY) {
console.error('❌ SUPABASE_ANON_KEY (ou SERVICE_ROLE_KEY) é obrigatório.');
process.exit(2);
}
const ANON_KEY = process.env.SUPABASE_ANON_KEY;
const PRODUCT_WEBHOOK_SECRET = process.env.N8N_PRODUCT_WEBHOOK_SECRET || '';
const TIMEOUT_MS = Number(process.env.CONTRACT_TEST_TIMEOUT_MS || 10000);
if (!ANON_KEY) {
console.error('❌ SUPABASE_ANON_KEY é obrigatório.');
process.exit(2);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/contract-testing.mjs` around lines 33 - 40, The current initializer
permits using SUPABASE_SERVICE_ROLE_KEY as a fallback for ANON_KEY (see the
ANON_KEY constant), which elevates privileges; change ANON_KEY to read only
process.env.SUPABASE_ANON_KEY (remove reference to SUPABASE_SERVICE_ROLE_KEY)
and keep the existing guard that logs the error and exits when ANON_KEY is falsy
so the script fails unless an anon key is provided.

const SUPABASE_URL = process.env.SUPABASE_URL || 'http://localhost:54321';
const ANON_KEY = process.env.SUPABASE_ANON_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY;
const PRODUCT_WEBHOOK_SECRET = process.env.N8N_PRODUCT_WEBHOOK_SECRET || '';
const TIMEOUT_MS = Number(process.env.CONTRACT_TEST_TIMEOUT_MS || 10000);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Valide CONTRACT_TEST_TIMEOUT_MS para evitar comportamento instável.

Se vier NaN, 0 ou negativo, o abort pode disparar imediatamente. Vale normalizar para inteiro positivo com fallback seguro.

Diff sugerido
-const TIMEOUT_MS = Number(process.env.CONTRACT_TEST_TIMEOUT_MS || 10000);
+const parsedTimeout = Number(process.env.CONTRACT_TEST_TIMEOUT_MS);
+const TIMEOUT_MS = Number.isFinite(parsedTimeout) && parsedTimeout > 0
+  ? Math.floor(parsedTimeout)
+  : 10000;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/contract-testing.mjs` at line 35, TIMEOUT_MS is assigned directly
from Number(process.env.CONTRACT_TEST_TIMEOUT_MS || 10000) which can yield NaN,
0 or negative and cause immediate abort; change the assignment to parse and
normalize the env var into a positive integer with a safe fallback (e.g.,
parseInt(process.env.CONTRACT_TEST_TIMEOUT_MS, 10), ensure it's a finite integer
> 0, clamp to a minimum like 1000 if desired) and assign that sanitized value to
TIMEOUT_MS so the constant is always a valid positive timeout.

Comment on lines +12 to +13
const IP_REGEX_V2 =
/^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$|^([0-9a-fA-F:]+)(\/([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]))?$/;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Regex de IP na v2 aceita valores inválidos.

A expressão atual ainda aceita entradas inválidas (ex.: octetos IPv4 fora de faixa e sequências IPv6 malformadas), então payload inválido pode passar no contrato e quebrar mais adiante. Para v2, vale validar IP/CIDR de forma semântica (não só por regex única).

As per coding guidelines, "supabase/functions/**/*.ts: Edge Functions Supabase em produção. Verificar com rigor".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/functions/_shared/contracts/schemas/block-ip-temporarily.ts` around
lines 12 - 13, The current IP_REGEX_V2 in block-ip-temporarily.ts allows
semantically invalid IPs; replace the single-regex approach with a proper
validator function (e.g., isValidIpOrCidr) that parses input into address and
optional prefix and uses a robust parser (net.isIP or ipaddr.js / a small CIDR
parser) to confirm IPv4 octets are 0-255, IPv6 group structure is valid, and
CIDR prefix ranges (0–32 for IPv4, 0–128 for IPv6) are enforced; remove or stop
relying on IP_REGEX_V2 and call the new isValidIpOrCidr from wherever
IP_REGEX_V2 was used to ensure only semantically valid IP/CIDR values pass the
contract.

Comment on lines +20 to +53
export const WebhookInboundV1 = z.any();

// ---------------------------------------------------------------------------
// v2 — envelope estruturado
// ---------------------------------------------------------------------------

export const WebhookInboundV2 = z
.object({
/** Tipo do evento (ex.: "order.created"). */
event: z.string().min(1).max(150).regex(
/^[a-z][a-z0-9_.-]*$/i,
"event must be slug-like (letters, digits, '.', '_', '-')",
),
/** Timestamp ISO em que o evento ocorreu no sistema de origem. */
occurred_at: z.string().datetime(),
/** Payload livre do emissor; objeto exigido para evitar bytes soltos. */
data: z.record(z.unknown()),
/** Idempotency-key opcional do emissor — quando presente, deve ser UUID. */
idempotency_key: z.string().uuid().optional(),
})
.strict();

// ---------------------------------------------------------------------------
// Schemas exportados
// ---------------------------------------------------------------------------

export const WebhookInboundSchemas = {
name: "webhook-inbound",
versions: {
"1": WebhookInboundV1,
"2": WebhookInboundV2,
},
defaultVersion: "1" as const,
deprecated: [
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Default ainda sem validação estrutural no webhook inbound

No caminho padrão, Line 20 (z.any()) + Line 52 (defaultVersion: "1") mantém aceitação irrestrita de payload. Isso preserva o risco de persistir payload inválido/lixo no endpoint de webhook em produção.

Sugestão: trocar o default para "2" e manter v1 apenas por opt-in explícito de legado, ou endurecer v1 para ao menos exigir objeto (z.record(z.unknown())) durante a janela de transição.

As per coding guidelines, "Validação de payload em webhooks (shared secret, assinatura HMAC quando aplicável)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/functions/_shared/contracts/schemas/webhook-inbound.ts` around lines
20 - 53, The current defaultVersion is "1" while WebhookInboundV1 is z.any(),
allowing unrestricted payloads; update WebhookInboundSchemas.defaultVersion to
"2" so the service uses the structured WebhookInboundV2 by default (event,
occurred_at, data, optional idempotency_key), and also harden legacy
WebhookInboundV1 to at least require an object (change WebhookInboundV1 from
z.any() to z.record(z.unknown())) if you want a safer transition period while
keeping v1 as an explicit opt-in; ensure references to WebhookInboundV1,
WebhookInboundV2 and defaultVersion are updated accordingly.

Comment on lines +15 to +16
// Module-scope contract response headers (Deprecation/Sunset) — setado após parseContract OK.
let contractResponseHeaders: Record<string, string> = {};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Não guarde headers de request em variáveis de módulo.

corsHeaders e contractResponseHeaders ficam compartilhados entre requests concorrentes no mesmo isolate. Isso pode misturar CORS/Deprecation de uma chamada na resposta de outra. Prefira manter esses headers no escopo do handler e passar o conjunto atual para o helper json.

Also applies to: 48-52, 64-65, 126-131

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/functions/step-up-verify/index.ts` around lines 15 - 16, Os headers
de resposta (corsHeaders e contractResponseHeaders) estão declarados em escopo
de módulo e podem ser compartilhados entre requests concorrentes; mova essas
variáveis para dentro do handler que processa a requisição (por exemplo a função
exportada do arquivo) e passe o objeto de headers resultante como argumento para
o helper json onde for chamado; atualize todas referências atuais a corsHeaders
e contractResponseHeaders (incluindo os lugares identificados em torno de
parseContract e nos blocos que setam Deprecation/Sunset) para usar a versão
local do handler em vez das variáveis de módulo.

Comment on lines +58 to +60
const contractResult = await parseContract(req, WebhookDispatcherSchemas, { corsHeaders });
if (!contractResult.ok) return contractResult.response;
const { version: contractVersion, data: parsedData } = contractResult;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propague responseHeaders depois do parseContract.

Aqui o handler descarta os headers retornados pelo contrato. Com isso, chamadas v1 deixam de receber Deprecation/Sunset e o versionamento some de todas as respostas geradas abaixo.

💡 Ajuste sugerido
-    const { version: contractVersion, data: parsedData } = contractResult;
+    const { version: contractVersion, data: parsedData, responseHeaders } = contractResult;

Depois, mescle ...responseHeaders em todas as respostas emitidas após esse ponto.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const contractResult = await parseContract(req, WebhookDispatcherSchemas, { corsHeaders });
if (!contractResult.ok) return contractResult.response;
const { version: contractVersion, data: parsedData } = contractResult;
const contractResult = await parseContract(req, WebhookDispatcherSchemas, { corsHeaders });
if (!contractResult.ok) return contractResult.response;
const { version: contractVersion, data: parsedData, responseHeaders } = contractResult;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/functions/webhook-dispatcher/index.ts` around lines 58 - 60,
parseContract currently returns responseHeaders that are discarded; keep and
propagate them by extracting responseHeaders from contractResult alongside
contractVersion and parsedData (e.g., const { version: contractVersion, data:
parsedData, responseHeaders } = contractResult) and merge ...responseHeaders
into every HTTP response returned after this point (responses created by this
handler/dispatch logic), ensuring Deprecation/Sunset/versioning headers are
preserved; update all return/response construction code paths below to include
these responseHeaders.

Comment on lines +149 to +151
return new Response(
JSON.stringify({ code: "internal_error", message: msg, fields: [] }),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } },
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Evite expor a mensagem interna no internal_error.

Esse retorno pode vazar detalhe de banco, secret lookup ou falha operacional para quem chamou o endpoint. Mantenha o erro detalhado só no log e responda com uma mensagem fixa.

💡 Ajuste sugerido
-    const msg = err instanceof Error ? err.message : "Erro";
     return new Response(
-      JSON.stringify({ code: "internal_error", message: msg, fields: [] }),
+      JSON.stringify({ code: "internal_error", message: "Internal server error", fields: [] }),
       { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } },
     );

As per coding guidelines, "Tratamento de erro não pode vazar tokens, SQL ou credenciais no response".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/functions/webhook-inbound/index.ts` around lines 149 - 151, The
response is currently exposing the internal error text via the message field
(variable msg) when constructing the Response; instead, keep detailed error text
only in logs and return a fixed, non-sensitive message in the JSON response.
Update the code that builds the Response (the new Response(...) call that uses
msg and corsHeaders) to log msg (or the caught error) to your logger (e.g.,
console.error/processLogger) and replace the returned message value with a fixed
safe string like "internal_server_error" or "An internal error occurred" while
preserving status 500 and corsHeaders; do not include tokens, SQL, stack traces,
or secrets in the response.

Comment on lines +37 to +39
export async function readBody(res: Response): Promise<ContractError> {
return (await res.json()) as ContractError;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Promise res.json() sem tratamento de erro.

Se a resposta não for JSON válido, o teste vai falhar com erro genérico de promise rejection. Adicionar .catch() ou try/catch melhora a mensagem de erro durante debugging dos testes.

🛡️ Sugestão de tratamento de erro
 export async function readBody(res: Response): Promise<ContractError> {
-  return (await res.json()) as ContractError;
+  try {
+    return (await res.json()) as ContractError;
+  } catch (error) {
+    throw new Error(`Failed to parse response as JSON: ${error}`);
+  }
 }

Conforme diretrizes de código, promises devem ter tratamento de erro adequado.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function readBody(res: Response): Promise<ContractError> {
return (await res.json()) as ContractError;
}
export async function readBody(res: Response): Promise<ContractError> {
try {
return (await res.json()) as ContractError;
} catch (error) {
throw new Error(`Failed to parse response as JSON: ${error}`);
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/contracts/_helpers.ts` around lines 37 - 39, The readBody function
currently returns await res.json() without error handling which causes opaque
promise rejections on invalid JSON; wrap the JSON parse in a try/catch inside
readBody (referencing readBody and the ContractError return type) and on failure
attempt to read the raw body (res.text()) and throw a new Error that includes
the HTTP status and the raw body (or the original parsing error) so tests
surface a clear message instead of a generic rejection.

describe('contract: product-webhook v2 (strict)', () => {
const validV2 = {
action: 'upsert',
idempotency_key: '11111111-2222-3333-4444-555555555555',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verifica UUIDs hardcoded nesses testes contra padrão RFC4122 (compatível com zod().uuid()).
python - <<'PY'
import re, pathlib

files = [
  "tests/contracts/product-webhook.contract.test.ts",
  "tests/contracts/webhooks.contract.test.ts",
]
uuid_like = re.compile(r'\b[0-9a-fA-F-]{36}\b')
rfc4122 = re.compile(r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$')

for f in files:
    txt = pathlib.Path(f).read_text(encoding="utf-8")
    for m in uuid_like.findall(txt):
        if m.count("-") == 4 and not rfc4122.match(m):
            print(f"{f}: UUID fora de RFC4122 -> {m}")
PY

Repository: adm01-debug/promo-gifts-v4

Length of output: 611


Corrigir fixture de UUID não-RFC4122 em testes de webhook
O UUID 11111111-2222-3333-4444-555555555555 não atende RFC4122 (o “variant” no 4º grupo começa com 4), então validações como z.string().uuid()/zod().uuid() podem falhar e mascarar cenários “válidos”.

  • tests/contracts/product-webhook.contract.test.ts: trocar idempotency_key nas ocorrências desse UUID
  • tests/contracts/webhooks.contract.test.ts: trocar replay_delivery_id nas ocorrências desse mesmo UUID
🔧 Diff sugerido
-    idempotency_key: '11111111-2222-3333-4444-555555555555',
+    idempotency_key: '11111111-1111-4111-8111-111111111111',
...
-        idempotency_key: '11111111-2222-3333-4444-555555555555',
+        idempotency_key: '11111111-1111-4111-8111-111111111111',
...
-        idempotency_key: '11111111-2222-3333-4444-555555555555',
+        idempotency_key: '11111111-1111-4111-8111-111111111111',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
idempotency_key: '11111111-2222-3333-4444-555555555555',
idempotency_key: '11111111-1111-4111-8111-111111111111',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/contracts/product-webhook.contract.test.ts` at line 102, O fixture usa
o UUID não-RFC4122 `11111111-2222-3333-4444-555555555555`, que faz falhar
validações como z.string().uuid(); substitua todas as ocorrências desse valor
usadas em idempotency_key e replay_delivery_id por UUIDs válidos RFC4122 (por
exemplo v4 com o nibble de versão = 4 e o nibble de variante = 8/9/A/B) nas
fixtures dos testes (ocorrências em product-webhook.contract.test.ts e
webhooks.contract.test.ts) para que zod().uuid()/z.string().uuid() passem;
aplique a troca a todas as instâncias desses campos para manter consistência dos
testes.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b2d8da6e64

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +82 to +86
const contractResult = await parseContract(req, WebhookInboundSchemas, {
corsHeaders,
prereadBody: rawBody,
});
if (!contractResult.ok) return contractResult.response;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Log inbound attempts before contract validation

Returning immediately on parseContract failure skips both the inbound_webhook_events insert and inbound_webhook_endpoints counters update, so malformed/empty webhook deliveries are no longer recorded. This regresses the function’s auditability for bad integrations or probing traffic: requests that fail JSON/schema validation now disappear from history instead of being tracked as invalid attempts.

Useful? React with 👍 / 👎.

// parseContract retorna 400 (json inválido/vazio), 422 (validação), 406 (versão).
const contractResult = await parseContract(req, WebhookDispatcherSchemas, { corsHeaders });
if (!contractResult.ok) return contractResult.response;
const { version: contractVersion, data: parsedData } = contractResult;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve negotiated version headers in dispatcher replies

The dispatcher reads version/data from parseContract but drops responseHeaders, so downstream responses are built without x-contract-version and deprecation headers. For clients using contract negotiation, successful dispatch/test/replay responses no longer expose the negotiated version or sunset warnings, which breaks the migration signaling provided by the shared contract layer.

Useful? React with 👍 / 👎.

});
// Validação de contrato (v1 default, v2 via accept-version).
// parseContract retorna 400 (json inválido/vazio), 422 (validação), 406 (versão).
const contractResult = await parseContract(req, WebhookDispatcherSchemas, { corsHeaders });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow version header in dispatcher preflight flow

This endpoint now negotiates contracts via accept-version (parseContract + WebhookDispatcherSchemas), but its CORS setup still uses buildPublicCorsHeaders without adding accept-version to allowed headers. Browser clients that send accept-version: 2 will fail preflight and cannot reach v2 at all, even though server-side versioning is implemented.

Useful? React with 👍 / 👎.

message: "IP inválido (use IPv4, IPv6 ou CIDR)",
}),
reason: z.string().max(500).optional(),
hours: z.number().int().min(1).max(720).optional(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve v1 hour coercion for IP block requests

The v1 schema now requires hours to be a JSON number, but the previous handler accepted string numerics by coercing Number(body.hours) before clamping. Existing clients posting "hours": "24" (common in form-encoded/front-end pipelines) will now get 422 validation_failed instead of succeeding, which is a backward-compatibility regression for the default v1 contract.

Useful? React with 👍 / 👎.

Comment on lines +47 to +49
const contractResult = await parseContract(req, BiCopilotSchemas, {
corsHeaders,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow accept-version on browser BI requests

This endpoint was migrated to contract version negotiation via parseContract, but it still relies on getCorsHeaders and the shared allowlist does not include accept-version. Any browser client (including the existing supabase.functions.invoke("bi-copilot") UI flow) that sends accept-version: 2 will fail CORS preflight before reaching the function, blocking header-based version rollout.

Useful? React with 👍 / 👎.

headers: { 'accept-version': '2' },
payload: {
action: 'upsert',
idempotency_key: '11111111-2222-3333-4444-555555555555',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a valid UUID in v2 contract smoke case

The scenario labeled as a valid v2 product-webhook request uses idempotency_key: "11111111-2222-3333-4444-555555555555", but that value is not RFC-compliant UUID (invalid variant nibble), so Zod UUID validation rejects it. This makes the smoke script report a false failure for the "valid v2" path and undermines trust in npm run test:contract results.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

18 issues found across 48 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="supabase/functions/_shared/contracts/schemas/block-ip-temporarily.ts">

<violation number="1" location="supabase/functions/_shared/contracts/schemas/block-ip-temporarily.ts:13">
P1: `IP_REGEX_V2` is not actually strict and allows invalid IPv4/IPv6 values, so V2 can accept malformed IPs.</violation>
</file>

<file name="supabase/functions/ownership-repair/index.ts">

<violation number="1" location="supabase/functions/ownership-repair/index.ts:20">
P2: Avoid storing `contractResponseHeaders` in module-level mutable state; it is request-specific and can be overwritten by concurrent requests, producing incorrect response headers.</violation>
</file>

<file name="supabase/functions/sync-external-db/index.ts">

<violation number="1" location="supabase/functions/sync-external-db/index.ts:23">
P2: Contract version headers are not included in the generic error response path. This breaks the new versioning behavior for thrown errors after request parsing.</violation>
</file>

<file name="supabase/functions/step-up-verify/index.ts">

<violation number="1" location="supabase/functions/step-up-verify/index.ts:16">
P2: Avoid storing per-request response headers in module-scope mutable state; concurrent requests can leak/overwrite headers across responses.</violation>
</file>

<file name="supabase/functions/block-ip-temporarily/index.ts">

<violation number="1" location="supabase/functions/block-ip-temporarily/index.ts:10">
P2: Avoid storing per-request headers in module-scope mutable state; concurrent requests can leak/overwrite response headers between users.</violation>

<violation number="2" location="supabase/functions/block-ip-temporarily/index.ts:55">
P2: `parseContract` now validates `ip` before trimming, so valid IPs with leading/trailing spaces are rejected (422) where they were previously accepted.</violation>
</file>

<file name="supabase/functions/market-intelligence-insights/index.ts">

<violation number="1" location="supabase/functions/market-intelligence-insights/index.ts:225">
P1: `parseContract` now rejects empty request bodies, which breaks this endpoint’s previous/declared compatibility with empty-body v1 requests.</violation>
</file>

<file name="supabase/functions/force-global-logout/index.ts">

<violation number="1" location="supabase/functions/force-global-logout/index.ts:10">
P2: Do not keep per-request contract headers in module scope; shared mutable state can leak headers across concurrent requests.</violation>
</file>

<file name="supabase/functions/_shared/contracts/schemas/step-up-verify.ts">

<violation number="1" location="supabase/functions/_shared/contracts/schemas/step-up-verify.ts:10">
P2: `Action` values are duplicated between schema and handler, which can drift and cause validation/runtime inconsistencies.</violation>
</file>

<file name="supabase/functions/e2e-cleanup/index.ts">

<violation number="1" location="supabase/functions/e2e-cleanup/index.ts:42">
P1: `contractResponseHeaders` is stored in module scope but mutated per request, which can leak/override headers across concurrent requests. Keep contract headers in request-local scope and pass them to `jsonResponse` explicitly.</violation>
</file>

<file name="supabase/functions/ownership-audit/index.ts">

<violation number="1" location="supabase/functions/ownership-audit/index.ts:42">
P1: `parseContract` now rejects empty request bodies, which breaks the previous no-body cron flow for this function.</violation>
</file>

<file name="supabase/functions/kit-ai-builder/index.ts">

<violation number="1" location="supabase/functions/kit-ai-builder/index.ts:39">
P2: Validation now runs before trimming, so whitespace-padded prompts can bypass the 6-char minimum and be processed as very short input. Re-validate after trim or trim within the schema.</violation>

<violation number="2" location="supabase/functions/kit-ai-builder/index.ts:136">
P2: Contract response headers are only added on success; error responses after `parseContract` still omit them, which breaks consistent version/deprecation signaling.</violation>
</file>

<file name="supabase/functions/webhook-inbound/index.ts">

<violation number="1" location="supabase/functions/webhook-inbound/index.ts:86">
P1: Early return on contract validation failure prevents failed webhook attempts from being logged/counted, breaking the audit/metrics behavior of this endpoint.</violation>

<violation number="2" location="supabase/functions/webhook-inbound/index.ts:150">
P1: Avoid returning raw exception messages in 500 responses; this can leak internal operational details. Return a fixed public message and keep the original error only in server logs.</violation>
</file>

<file name="supabase/functions/webhook-dispatcher/index.ts">

<violation number="1" location="supabase/functions/webhook-dispatcher/index.ts:60">
P2: Capture `responseHeaders` from `parseContract` and merge them into all post-parse responses so version/deprecation metadata is returned consistently.</violation>
</file>

<file name="scripts/contract-testing.mjs">

<violation number="1" location="scripts/contract-testing.mjs:33">
P2: Require `SUPABASE_ANON_KEY` explicitly and remove the service-role fallback to avoid running smoke tests with unnecessary elevated privileges.</violation>
</file>

<file name="supabase/functions/product-webhook/index.ts">

<violation number="1" location="supabase/functions/product-webhook/index.ts:144">
P1: Do not expose `error.message` in the 500 payload. Return a fixed `internal_error` message to clients and log the detailed exception server-side only.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

const IP_REGEX_V1 = /^[0-9a-fA-F:.\/]{3,45}$/;
// V2 = validação rigorosa IPv4/IPv6/CIDR
const IP_REGEX_V2 =
/^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$|^([0-9a-fA-F:]+)(\/([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]))?$/;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: IP_REGEX_V2 is not actually strict and allows invalid IPv4/IPv6 values, so V2 can accept malformed IPs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_shared/contracts/schemas/block-ip-temporarily.ts, line 13:

<comment>`IP_REGEX_V2` is not actually strict and allows invalid IPv4/IPv6 values, so V2 can accept malformed IPs.</comment>

<file context>
@@ -0,0 +1,45 @@
+const IP_REGEX_V1 = /^[0-9a-fA-F:.\/]{3,45}$/;
+// V2 = validação rigorosa IPv4/IPv6/CIDR
+const IP_REGEX_V2 =
+  /^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$|^([0-9a-fA-F:]+)(\/([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]))?$/;
+
+export const BlockIpTemporarilyV1 = z.object({
</file context>

Comment on lines +225 to +227
const contractResult = await parseContract(req, MarketIntelligenceInsightsSchemas, {
corsHeaders,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: parseContract now rejects empty request bodies, which breaks this endpoint’s previous/declared compatibility with empty-body v1 requests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/market-intelligence-insights/index.ts, line 225:

<comment>`parseContract` now rejects empty request bodies, which breaks this endpoint’s previous/declared compatibility with empty-body v1 requests.</comment>

<file context>
@@ -214,10 +218,16 @@ Deno.serve(async (req) => {
     const auth = await authenticateRequest(req);
     userId = auth.userId;
-    const body = (await req.json().catch(() => ({}))) as RequestBody;
+    const contractResult = await parseContract(req, MarketIntelligenceInsightsSchemas, {
+      corsHeaders,
+    });
</file context>
Suggested change
const contractResult = await parseContract(req, MarketIntelligenceInsightsSchemas, {
corsHeaders,
});
const rawBody = await req.text().catch(() => "");
const contractResult = await parseContract(req, MarketIntelligenceInsightsSchemas, {
corsHeaders,
prereadBody: rawBody.trim() === "" ? "{}" : rawBody,
});

};

const corsHeaders = buildPublicCorsHeaders({ extraAllowHeaders: ["x-e2e-cleanup-token"], allowMethods: "POST, OPTIONS" });
let contractResponseHeaders: Record<string, string> = {};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: contractResponseHeaders is stored in module scope but mutated per request, which can leak/override headers across concurrent requests. Keep contract headers in request-local scope and pass them to jsonResponse explicitly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/e2e-cleanup/index.ts, line 42:

<comment>`contractResponseHeaders` is stored in module scope but mutated per request, which can leak/override headers across concurrent requests. Keep contract headers in request-local scope and pass them to `jsonResponse` explicitly.</comment>

<file context>
@@ -35,11 +39,12 @@ type E2ERateLimitRow = {
 };
 
 const corsHeaders = buildPublicCorsHeaders({ extraAllowHeaders: ["x-e2e-cleanup-token"], allowMethods: "POST, OPTIONS" });
+let contractResponseHeaders: Record<string, string> = {};
 
 function jsonResponse(body: unknown, status = 200, extraHeaders: Record<string, string> = {}) {
</file context>

Comment on lines +42 to +44
const contractResult = await parseContract(req, OwnershipAuditSchemas, {
corsHeaders,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: parseContract now rejects empty request bodies, which breaks the previous no-body cron flow for this function.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/ownership-audit/index.ts, line 42:

<comment>`parseContract` now rejects empty request bodies, which breaks the previous no-body cron flow for this function.</comment>

<file context>
@@ -33,13 +39,12 @@ Deno.serve(async (req) => {
-    } catch {
-      // sem body — ok, usa default "cron"
-    }
+    const contractResult = await parseContract(req, OwnershipAuditSchemas, {
+      corsHeaders,
+    });
</file context>
Suggested change
const contractResult = await parseContract(req, OwnershipAuditSchemas, {
corsHeaders,
});
const rawBody = await req.text();
const contractResult = await parseContract(req, OwnershipAuditSchemas, {
corsHeaders,
prereadBody: rawBody.trim() === "" ? "{}" : rawBody,
});

corsHeaders,
prereadBody: rawBody,
});
if (!contractResult.ok) return contractResult.response;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Early return on contract validation failure prevents failed webhook attempts from being logged/counted, breaking the audit/metrics behavior of this endpoint.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/webhook-inbound/index.ts, line 86:

<comment>Early return on contract validation failure prevents failed webhook attempts from being logged/counted, breaking the audit/metrics behavior of this endpoint.</comment>

<file context>
@@ -50,60 +67,88 @@ Deno.serve(async (req) => {
+      corsHeaders,
+      prereadBody: rawBody,
+    });
+    if (!contractResult.ok) return contractResult.response;
+    const { version, data: payloadParsed, responseHeaders } = contractResult;
+
</file context>

*/
import { z } from "https://esm.sh/zod@3.23.8";

const ActionEnum = z.enum([
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Action values are duplicated between schema and handler, which can drift and cause validation/runtime inconsistencies.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_shared/contracts/schemas/step-up-verify.ts, line 10:

<comment>`Action` values are duplicated between schema and handler, which can drift and cause validation/runtime inconsistencies.</comment>

<file context>
@@ -0,0 +1,84 @@
+ */
+import { z } from "https://esm.sh/zod@3.23.8";
+
+const ActionEnum = z.enum([
+  "promote_dev",
+  "demote_dev",
</file context>

return new Response(
JSON.stringify({ suggestion }),
{ status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
{ status: 200, headers: { ...corsHeaders, ...responseHeaders, 'Content-Type': 'application/json' } }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Contract response headers are only added on success; error responses after parseContract still omit them, which breaks consistent version/deprecation signaling.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/kit-ai-builder/index.ts, line 136:

<comment>Contract response headers are only added on success; error responses after `parseContract` still omit them, which breaks consistent version/deprecation signaling.</comment>

<file context>
@@ -131,7 +133,7 @@ Use português do Brasil. Seja conciso e prático.`;
     return new Response(
       JSON.stringify({ suggestion }),
-      { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+      { status: 200, headers: { ...corsHeaders, ...responseHeaders, 'Content-Type': 'application/json' } }
     );
   } catch (e) {
</file context>

});
if (!contractResult.ok) return contractResult.response;
const { data: body, responseHeaders } = contractResult;
const prompt = body.prompt.trim();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Validation now runs before trimming, so whitespace-padded prompts can bypass the 6-char minimum and be processed as very short input. Re-validate after trim or trim within the schema.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/kit-ai-builder/index.ts, line 39:

<comment>Validation now runs before trimming, so whitespace-padded prompts can bypass the 6-char minimum and be processed as very short input. Re-validate after trim or trim within the schema.</comment>

<file context>
@@ -27,14 +31,12 @@ Deno.serve(async (req: Request) => {
+    });
+    if (!contractResult.ok) return contractResult.response;
+    const { data: body, responseHeaders } = contractResult;
+    const prompt = body.prompt.trim();
 
     const LOVABLE_API_KEY = Deno.env.get('LOVABLE_API_KEY');
</file context>
Suggested change
const prompt = body.prompt.trim();
const prompt = body.prompt.trim();
if (prompt.length < 6 || prompt.length > 2000) {
return new Response(
JSON.stringify({ error: 'prompt inválido (6–2000 chars)' }),
{ status: 400, headers: { ...corsHeaders, ...responseHeaders, 'Content-Type': 'application/json' } }
);
}

// parseContract retorna 400 (json inválido/vazio), 422 (validação), 406 (versão).
const contractResult = await parseContract(req, WebhookDispatcherSchemas, { corsHeaders });
if (!contractResult.ok) return contractResult.response;
const { version: contractVersion, data: parsedData } = contractResult;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Capture responseHeaders from parseContract and merge them into all post-parse responses so version/deprecation metadata is returned consistently.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/webhook-dispatcher/index.ts, line 60:

<comment>Capture `responseHeaders` from `parseContract` and merge them into all post-parse responses so version/deprecation metadata is returned consistently.</comment>

<file context>
@@ -60,16 +53,52 @@ Deno.serve(async (req) => {
+    // parseContract retorna 400 (json inválido/vazio), 422 (validação), 406 (versão).
+    const contractResult = await parseContract(req, WebhookDispatcherSchemas, { corsHeaders });
+    if (!contractResult.ok) return contractResult.response;
+    const { version: contractVersion, data: parsedData } = contractResult;
+
+    // Normalização v1/v2 → forma interna comum.
</file context>

// ---------------------------------------------------------------------------

const SUPABASE_URL = process.env.SUPABASE_URL || 'http://localhost:54321';
const ANON_KEY = process.env.SUPABASE_ANON_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Require SUPABASE_ANON_KEY explicitly and remove the service-role fallback to avoid running smoke tests with unnecessary elevated privileges.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/contract-testing.mjs, line 33:

<comment>Require `SUPABASE_ANON_KEY` explicitly and remove the service-role fallback to avoid running smoke tests with unnecessary elevated privileges.</comment>

<file context>
@@ -1,112 +1,303 @@
+// ---------------------------------------------------------------------------
+
+const SUPABASE_URL = process.env.SUPABASE_URL || 'http://localhost:54321';
+const ANON_KEY = process.env.SUPABASE_ANON_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY;
+const PRODUCT_WEBHOOK_SECRET = process.env.N8N_PRODUCT_WEBHOOK_SECRET || '';
+const TIMEOUT_MS = Number(process.env.CONTRACT_TEST_TIMEOUT_MS || 10000);
</file context>
Suggested change
const ANON_KEY = process.env.SUPABASE_ANON_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY;
const ANON_KEY = process.env.SUPABASE_ANON_KEY;

@adm01-debug
Copy link
Copy Markdown
Owner Author

Conteúdo majoritariamente já em main — 3 dos 9 commits foram detectados como "patch contents already upstream" pelo git rebase (cherry-picks aplicados em PRs anteriores: barrel export, errors/versioning/parse helpers, e v1/v2 schemas).

Os 6 commits restantes (migração de 13 edge functions para parseContract) conflitam em supabase/functions/webhook-inbound/index.ts e outros arquivos que foram tocados por PRs subsequentes.

Fechando como mostly-upstream. Branch feat/contracts-validation-zod-422-versioning preservado no remote para cherry-pick seletivo das migrações de edge functions remanescentes quando o Abner rotacionar o PAT.

@adm01-debug adm01-debug deleted the feat/contracts-validation-zod-422-versioning branch May 24, 2026 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants