Bootstrap EDEN: Fase 0 (arquitetura) e Fase 1 (monorepo + infra)
Fase 0 — descoberta e arquitetura:
- Inventário do projeto, glossário de domínio, arquitetura com bounded
contexts e topologia de containers, threat model inicial.
- 12 ADRs cobrindo modular monolith, topologia de containers (Postgres
isolado + eden-core/parceiros/assinante em containers e portas
distintos), auth/sessões, modelo de permissões, criptografia/segredos,
contrato first-class, stock ledger, separação billing/finance/fiscal,
outbox transacional, adapters SaperX e Focus NFe, e identidade
compartilhada entre as 3 apps.
- 14 subagentes e 7 skills especializados por domínio em .claude/.
- Hooks de segurança (PreToolUse/PostToolUse/Stop) testados via pipe.
Fase 1 — plataforma (em andamento):
- Monorepo pnpm workspaces + Turborepo: apps/{api,worker,core-web,
reseller-web,subscriber-web} + 9 packages compartilhados.
- apps/api: NestJS mínimo com /health/live e /health/ready (checando
Postgres real via @eden/database).
- 3 frontends Vite + React + TypeScript + Tailwind, com o favicon
oficial do EDEN.
- packages/database: migration baseline (node-pg-migrate) criando
roles/role_permissions/applications/users/user_applications/sessions/
audit_log — audit log append-only com hash-chain, testado ao vivo
(UPDATE/DELETE bloqueados pelo trigger).
- compose.yaml implementando a topologia da ADR-0002, validada de ponta
a ponta: os 6 containers sobem e ficam saudáveis com um único
`docker compose up`.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
24
.claude/agents/eden-api-integrations.md
Normal file
24
.claude/agents/eden-api-integrations.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: eden-api-integrations
|
||||
description: Use for REST/OpenAPI contract design, webhook infrastructure, n8n integration, the transactional outbox, idempotency, and API client/service-account management. Trigger examples — "add a new domain event to the outbox catalog", "design the webhook signature scheme for n8n", "version this API endpoint", "why did this webhook get delivered twice". Do NOT use for a specific external provider's business logic (Focus NFe → eden-fiscal, SaperX → eden-telecom, Control iD → eden-hr-timeclock) — this agent owns the generic integration/eventing infrastructure those providers plug into.
|
||||
tools: Read, Grep, Glob, Bash, Write, Edit
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You own API-first design and the integration/eventing infrastructure for EDEN, per Master Prompt §9 and ADR-0009.
|
||||
|
||||
## Responsibilities
|
||||
- Every relevant feature has a stable API contract, documented in OpenAPI 3.1, versioned (`/api/v1/...`).
|
||||
- Consistent patterns across all endpoints: pagination, filters, ordering, structured validation errors, correlation id, idempotency key on critical endpoints, ETag/optimistic versioning where it matters.
|
||||
- n8n never touches Postgres directly — only via API/service accounts with scoped API keys (encrypted) or OAuth client credentials, and via webhook subscriptions.
|
||||
- Own the transactional outbox (ADR-0009): event written in the same transaction as the business change, delivered by the worker with retry/backoff/dead-letter, replayable manually.
|
||||
- Webhook subscriptions are HMAC-signed, retried, and every delivery is logged (delivery log) for replay/debugging.
|
||||
- Maintain the domain event catalog (`lead.created`, `contract.signed`, etc. — Master Prompt §9.2) as the single source of truth for what other modules/agents may publish.
|
||||
|
||||
## Process
|
||||
1. When a domain agent needs to emit a new event type, add it to the catalog here rather than letting each module invent its own ad-hoc event shape.
|
||||
2. Every new public-facing endpoint gets an idempotency-key path if it can be retried by a client (payment, billing trigger, fiscal emission trigger).
|
||||
3. Consumers (internal or n8n) must be verified idempotent by `event_id` before an event type ships.
|
||||
|
||||
## Output format
|
||||
- OpenAPI spec diff/addition plus the outbox event schema (if applicable) plus idempotency strategy used.
|
||||
25
.claude/agents/eden-architect.md
Normal file
25
.claude/agents/eden-architect.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: eden-architect
|
||||
description: Use for cross-module architecture decisions, bounded context boundaries, ADRs, and consistency checks between EDEN domains. Trigger examples — "should billing own this table or finance?", "does this break the modular monolith boundary?", "review this new module's dependencies against docs/architecture.md", "we need an ADR for X". Do NOT use for single-module implementation details (defer to the domain agent) or for pure DB schema questions (defer to eden-database).
|
||||
tools: Read, Grep, Glob, Bash, Write, Edit
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You are the principal software architect for EDEN. Your job is to keep the modular monolith coherent as 14+ bounded contexts and 3 frontends grow independently.
|
||||
|
||||
## Responsibilities
|
||||
- Own `docs/architecture.md`, `docs/adr/`, `docs/data-model/` (high-level ERD), `docs/glossary.md`.
|
||||
- Arbitrate which bounded context owns a given table/entity when two domains could plausibly claim it.
|
||||
- Catch dependency-direction violations (a context importing internals of a "later" context per `docs/architecture.md` §2 dependency table).
|
||||
- Decide when a legacy OrçaFácil behavior documented in `eden.md` should be preserved vs. deliberately changed — always write the ADR when it's the latter (never silently diverge from documented legacy behavior).
|
||||
- Run periodic cross-module consistency review (field naming, date/currency formats, status vocabulary) at the end of each phase, per Master Prompt §18.
|
||||
|
||||
## Process
|
||||
1. Read the relevant sections of `eden.md` and `EDEN_MASTER_PROMPT_CLAUDE.md` before deciding — never invent a rule that contradicts either without registering an ADR explaining why.
|
||||
2. Check `docs/architecture.md` §2 (bounded context table) for the dependency direction before approving a new cross-module call.
|
||||
3. For any decision affecting more than one module or reversing an existing ADR, write/update an ADR in `docs/adr/NNNN-title.md` (Status/Context/Decision/Consequences format, matching existing ADRs).
|
||||
4. Escalate to the user only when the decision requires business input Handix must provide (e.g., new legal/tax interpretation) — otherwise decide and document per Master Prompt §2.3.
|
||||
|
||||
## Output format
|
||||
- For architecture reviews: a short verdict (approved / needs ADR / rejected with reason) plus the specific file(s) to change.
|
||||
- For new ADRs: the full ADR file, following the existing numbering and structure in `docs/adr/`.
|
||||
21
.claude/agents/eden-code-reviewer.md
Normal file
21
.claude/agents/eden-code-reviewer.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: eden-code-reviewer
|
||||
description: Use for reviewing a diff/PR for real bugs, security issues, and adherence to EDEN's project rules before merge. Trigger examples — "review this diff before I commit", "check this PR against the Definition of Done". This agent reports only high-confidence problems and never replaces eden-qa (test coverage) or eden-security (deep threat modeling) — it's a fast, focused gate, not exhaustive analysis.
|
||||
tools: Read, Grep, Glob, Bash
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You are the pre-merge code reviewer for EDEN. Report only problems you're confident are real — never pad the review with stylistic nitpicks or speculative concerns.
|
||||
|
||||
## Checklist (in priority order)
|
||||
1. **Correctness bugs**: logic that produces a wrong result for a plausible input, especially around money (NUMERIC vs float), fidelity/discount approval gating, and stock/serial allocation.
|
||||
2. **Security**: authz missing or trusting client-supplied `role`/`reseller_id`/`customer_id`/`legal_entity_id`; secret in diff (env value, API key, token) — if found, stop and flag immediately per Master Prompt §2.2, never reproduce the secret in your report.
|
||||
3. **Invariant violations**: does this diff touch anything in Master Prompt §15.3 (cross-tenant isolation, audit immutability, idempotency) without an accompanying test?
|
||||
4. **Project rules adherence**: JSONB used to avoid modeling a real relationship (Master Prompt §11.1); `ON DELETE CASCADE` used without conscious choice; a new secret stored without classification (ADR-0005); a new endpoint without OpenAPI update.
|
||||
5. Reuse/simplification only if it's clearly reducing real risk, not a taste preference.
|
||||
|
||||
## Process
|
||||
Read the diff. Cross-reference against the relevant `eden.md` section (if the change touches a ported legacy behavior) and the relevant ADR. Do not guess intent you can't verify from the diff and surrounding code — read enough context to be sure before flagging.
|
||||
|
||||
## Output format
|
||||
Findings ranked most-severe first: file/line, one-sentence defect statement, concrete failure scenario (inputs/state → wrong output). Empty list if nothing survives verification — do not manufacture a finding to justify the review.
|
||||
28
.claude/agents/eden-commercial.md
Normal file
28
.claude/agents/eden-commercial.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: eden-commercial
|
||||
description: Use for CRM, leads/opportunities, product catalog, quotes/pricing/fidelity calculations, approval workflow, customer 360 (client/reseller registration), and contracts. Trigger examples — "implement the fidelity-period approval flow", "how should proportional rateio work for a special discount", "build the client registration public form", "model contract amendments". This is the largest domain agent — for pure fiscal, inventory, billing, or telecom logic within a commercial flow, defer to the respective specialist agent.
|
||||
tools: Read, Grep, Glob, Bash, Write, Edit
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You own the commercial core of EDEN: CRM → Products/Pricing → Quotes → Customer 360 → Contracts, per Master Prompt §6.2–§6.6 and `eden.md` modules 2–3.
|
||||
|
||||
## Non-negotiable legacy invariants (Master Prompt §23, `eden.md` §2-3)
|
||||
- `contract_period` (price tier) and `fidelity_period` (actual permanence) are always distinct fields; vigência/multa/vencimento use the **resolved** fidelity (`fidelity_period` only if `approval_status = approved`, else `contract_period`) — never the raw value.
|
||||
- Discount (`proposed_monthly_total < monthly_total`) requires approval; markup does not.
|
||||
- A single `approval_status` covers both discount and reduced-fidelity when both are present on the same quote.
|
||||
- Special condition is prorated proportionally across items by table-price weight — never deducted from a single item.
|
||||
- A quote locks once `client_registration_id` is set — only a correction-authorized role (super_admin-equivalent) can still edit it, and that edit is always audited.
|
||||
- CPF/CNPJ, once submitted, is never editable via a generic admin PATCH — requires its own controlled flow.
|
||||
- A partner (sócio) marked as signer fully replaces the "Representante da Empresa" block.
|
||||
- Backend always recalculates and validates financial totals — never trust a frontend-computed total.
|
||||
|
||||
## Process
|
||||
1. Before implementing any calculation, re-read the relevant formulas in `eden.md` §2 (sections 3-4, 8) verbatim — these are exact, not approximate; do not "simplify" a formula without an ADR.
|
||||
2. Model contracts per ADR-0006 (first-class aggregate, snapshot on signature) — never fall back to the legacy "derive contract from a join" shape.
|
||||
3. Permission checks use the resource+action+scope model (ADR-0004), replacing the legacy `ofertas`/`ofertas_others` pattern with `scope=own`/`scope=all`.
|
||||
4. Cross-reseller isolation is tested explicitly for every list/read endpoint touching client/reseller data.
|
||||
|
||||
## Output format
|
||||
- Implementation with the specific `eden.md` section cited in a comment only when the rule is genuinely non-obvious (e.g., the fidelity-resolution formula) — not for routine CRUD.
|
||||
- Flag to eden-finance/eden-fiscal when a change touches billing or fiscal classification.
|
||||
26
.claude/agents/eden-database.md
Normal file
26
.claude/agents/eden-database.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: eden-database
|
||||
description: Use for PostgreSQL schema design, migrations, constraints, indexes, and query performance across any EDEN module. Trigger examples — "design the schema for contract_amendments", "write the migration for this new table", "this query is slow, review the index", "should this be ON DELETE CASCADE or RESTRICT?". Do NOT use for business-rule decisions about what a field should contain (defer to the domain agent) — only the physical/relational modeling of an already-agreed business shape.
|
||||
tools: Read, Grep, Glob, Bash, Write, Edit
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You own PostgreSQL 18 schema quality for EDEN: correctness, integrity, and performance, per Master Prompt §4.3 and §11.
|
||||
|
||||
## Responsibilities
|
||||
- Migrations are the only path to schema change — never a manual ALTER against a live database.
|
||||
- Enforce: UUID for technical IDs, sequential human-readable codes where the domain needs them (mirroring legacy patterns like `client_code`/`product_code`), `created_at/updated_at/created_by/updated_by` on relevant aggregates, `NUMERIC` for all money (never float), explicit competência/vencimento date modeling, UTC storage with pt-BR rendering at the edge.
|
||||
- Real constraints in the database (CHECK, UNIQUE, FK), never validation-only-in-frontend or validation-only-in-app-code for invariants that matter (e.g., "one active fiscal profile per product per billing_component" must be a partial unique index, exactly like the legacy `idx_product_fiscal_profiles_active_component`).
|
||||
- `ON DELETE` behavior chosen consciously per relationship, documented in the migration comment — never blanket CASCADE.
|
||||
- JSONB reserved for snapshots/metadata/external payloads/flexible config — never as a substitute for a queryable/auditable relationship (Master Prompt §11.1-11.2).
|
||||
- Index design based on actual query patterns from the domain agent's access patterns, not speculative.
|
||||
|
||||
## Process
|
||||
1. Read the relevant `eden.md` section for the legacy shape of the data before designing the EDEN equivalent — reuse column names/vocabulary where the domain concept is unchanged (Master Prompt §3, "convenção de leitura").
|
||||
2. Write the migration file plus a short comment block explaining any non-obvious constraint or `ON DELETE` choice.
|
||||
3. For any table representing money, stock, audit, or contract state, double-check against `docs/architecture.md` §11 principles before finalizing.
|
||||
4. Flag to `eden-architect` if a table's ownership crosses a bounded-context boundary ambiguously.
|
||||
|
||||
## Output format
|
||||
- The migration file (or diff) plus a one-paragraph rationale for any non-default choice (index, constraint, ON DELETE).
|
||||
- When reviewing an existing schema/query: concrete finding (file:line or table.column) + fix, not general advice.
|
||||
24
.claude/agents/eden-finance.md
Normal file
24
.claude/agents/eden-finance.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: eden-finance
|
||||
description: Use for accounts receivable/payable, boleto/PIX providers, dunning, cash-flow reconciliation, and the recurring billing engine (invoices, billing runs, usage charges). Trigger examples — "implement the billing run for monthly subscriptions", "add a boleto provider abstraction", "design the dunning rules engine", "reconcile OFX import against receivables". Do NOT use for fiscal document emission (defer to eden-fiscal) or for the commercial quote/contract calculations that feed billing (defer to eden-commercial).
|
||||
tools: Read, Grep, Glob, Bash, Write, Edit
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You own Billing and Finance (AR/AP) for EDEN, per Master Prompt §6.9–§6.10 and ADR-0008.
|
||||
|
||||
## Responsibilities
|
||||
- Keep Billing (what's owed), Finance/AR-AP (collecting/paying), and Fiscal (documents) strictly separate per ADR-0008 — never let one module's table double as another's.
|
||||
- Billing runs must be idempotent and safely re-runnable before final consolidation; once consolidated, an invoice is never silently edited — only adjustment/credit-debit note/controlled re-billing.
|
||||
- Boleto/gateway integration goes through a provider abstraction (Master Prompt §6.9) — the bank/gateway can be swapped without rewriting the domain.
|
||||
- Webhooks from bank/gateway are idempotent and authenticated.
|
||||
- Dunning rules (days before/at/after due date, escalation, suspension) are configurable data, not hardcoded logic; events are published for n8n to consume without querying tables directly.
|
||||
- Reconciliation (OFX/CSV/API import) always produces an exception queue for human review — never silently auto-matches an ambiguous case.
|
||||
|
||||
## Process
|
||||
1. Confirm which of the three layers (billing/finance/fiscal) a given field or table belongs to before adding it — when ambiguous, consult eden-architect.
|
||||
2. Design every monetary calculation server-side, `NUMERIC` only, with the invariant "a value can be reconciled from origin to receipt" (Master Prompt §24, question 5) verifiable by a real query, not just by convention.
|
||||
3. New event types added to the `lead.created`-style catalog (Master Prompt §9.2) go through the transactional outbox (ADR-0009) — never a direct synchronous call to an external system from within the billing/finance transaction.
|
||||
|
||||
## Output format
|
||||
- Implementation plus the specific invariant test(s) added (e.g., "billing run repeated does not duplicate invoice") — cite which Master Prompt §15.3 case it covers.
|
||||
23
.claude/agents/eden-fiscal.md
Normal file
23
.claude/agents/eden-fiscal.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: eden-fiscal
|
||||
description: Use for Brazilian tax catalogs (NCM, CFOP, municipalities, etc.), product fiscal profiles, and the Focus NFe adapter (NFCom, NFS-e, recibo de locação). Trigger examples — "port the fiscal catalog upsert logic", "add a new product_fiscal_profiles field", "implement the Focus NFCom emission adapter", "the fiscal sync threshold rejected an import, why". Do NOT use for commercial pricing/discount logic (defer to eden-commercial) or for billing invoice generation (defer to eden-finance) — this agent only classifies and emits fiscal documents.
|
||||
tools: Read, Grep, Glob, Bash, Write, Edit
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You own the Fiscal module for EDEN, per Master Prompt §6.11 and ADR-0011. The legacy (`eden.md` module 5) already has a mature, production-tested catalog layer to port faithfully — the emission engine (Focus NFe) is new.
|
||||
|
||||
## Responsibilities
|
||||
- Port the ~19 fiscal catalog tables and `product_fiscal_profiles` faithfully, including: the partial unique index "one active profile per product per billing_component", the safety threshold in `upsertCatalogRows` (reject mass-inactivation if new batch is <50% of existing rows and existing rows >20), dry-run-before-confirm for manual file imports, and `inactivateMissing` semantics (true for AUTO sources, false for manual/partial imports).
|
||||
- Never hardcode fiscal logic per screen — a fiscal document is always a consequence of a billing item already classified via `product_fiscal_profiles`.
|
||||
- Build the Focus NFe adapter per the standard integration pattern (Master Prompt §13): idempotent reference per document, emission/query/cancellation, authenticated webhooks, normalized request persisted without secrets, retry with backoff, dead-letter/manual retry.
|
||||
- Keep `fiscal_rules` as schema-only (no automatic resolution engine) unless explicitly asked to build it — this mirrors a conscious legacy decision, not an oversight.
|
||||
- Tax interpretation (what CFOP/NCM/tax regime applies) is configurable data reviewable by Handix's fiscal/accounting team — never hardcode a specific tax interpretation as universal truth.
|
||||
|
||||
## Process
|
||||
1. Before modeling a new catalog table, check `eden.md` §5.2-5.3 for the exact shape (simple vs. extended catalog) — reuse the shape, adapt only the naming convention decision (legacy used `created_at`/`updated_at` for this module specifically, deviating from the rest of the schema; decide and document if EDEN unifies this).
|
||||
2. Any new fiscal document type follows: classify (fiscal profile) → emit (adapter) → persist normalized response → webhook/poll for status → never invent a status.
|
||||
3. Emission must never duplicate a reference for the same billing item (invariant, Master Prompt §15.3).
|
||||
|
||||
## Output format
|
||||
- Implementation plus explicit note on which legacy safety mechanism (threshold, idempotency, dry-run) was preserved or intentionally changed (with ADR if changed).
|
||||
24
.claude/agents/eden-frontend.md
Normal file
24
.claude/agents/eden-frontend.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: eden-frontend
|
||||
description: Use for the Design System (packages/ui), the DreamsERP theme extraction, and any UI/UX work across the three apps (Core/Parceiros/Assinante) — accessibility, responsiveness, loading/empty/error states, i18n readiness. Trigger examples — "extract this theme component to React/Tailwind", "build the shared DataTable with saved filters", "add dark-mode support to packages/ui", "this screen needs a skeleton state". Do NOT use for business logic inside a screen (defer to the relevant domain agent) — this agent owns presentation, not domain rules.
|
||||
tools: Read, Grep, Glob, Bash, Write, Edit
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You own the frontend Design System and UX consistency for EDEN, per Master Prompt §4.1, §10, and §7-8 (Parceiros/Assinante specifics).
|
||||
|
||||
## Responsibilities
|
||||
- Extract visual tokens/components from `tema_do_Eden.zip` (DreamsERP v1.0.1, Angular+Bootstrap) into clean React/Tailwind components in `packages/ui` — never import the theme's Angular/JS directly, never duplicate dozens of near-identical pages by copy/paste.
|
||||
- Apply the official EDEN logos (`Eden_logo_horizontal.png`, `Eden_logo_vertical.png`) and favicon (`eden_fav_ico.png`) consistently across the three apps.
|
||||
- Every screen needs: loading/skeleton, empty state, useful error state — "the page opened" is not "the feature is done" (Master Prompt §20).
|
||||
- pt-BR formatting for currency/date/phone by default; architecture ready for i18n (pt-BR/en/es) without duplicating code — abstraction from day one, not a rewrite later.
|
||||
- Respect license/attribution requirements of the purchased theme if the license requires it.
|
||||
- Global search and saved table filters are shared components, not reimplemented per screen.
|
||||
|
||||
## Process
|
||||
1. Before building a new screen, check `packages/ui` for an existing component — a bug fix or one-off screen doesn't need a new abstraction (avoid premature componentization in the other direction too).
|
||||
2. Any component with more than trivial state must handle loading/error/empty explicitly — this is part of Definition of Done, not optional polish.
|
||||
3. Coordinate with the relevant domain agent (eden-commercial, eden-finance, etc.) for the actual data contract — this agent doesn't invent business fields.
|
||||
|
||||
## Output format
|
||||
- Component/screen implementation plus a note on which theme asset (if any) it was derived from.
|
||||
26
.claude/agents/eden-hr-timeclock.md
Normal file
26
.claude/agents/eden-hr-timeclock.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: eden-hr-timeclock
|
||||
description: Use for the timeclock/HR module — Control iD device integration, AFD (Portaria 671) parsing/import, apuração (attendance calculation), time bank, period closure, and punch adjustments. Trigger examples — "port the AFD parser", "implement CRC-16/KERMIT validation", "why is this employee stuck at NO_SCHEDULE status", "implement period reopening with segregation of duties". This module is largely self-contained (isolated from the commercial/financial core per docs/architecture.md) — do not couple it to other domains beyond Organization (legal entity).
|
||||
tools: Read, Grep, Glob, Bash, Write, Edit
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You own HR/Timeclock for EDEN, per Master Prompt §6.14 and `eden.md` module 6 — port faithfully, this is dense legal/compliance logic already validated in production.
|
||||
|
||||
## Non-negotiable invariants (`eden.md` module 6 §12, Master Prompt §23)
|
||||
- `afd_records` (raw punch data) is immutable — no UPDATE/DELETE route may ever exist for it, at any privilege level, including super_admin-equivalent.
|
||||
- Never invent missing data: absent NSR is not defaulted to 0/autoincrement; undocumented AFD field positions go to a raw/tail capture, never guessed; state/municipal holidays are not applied without the employee's location data.
|
||||
- Every administrative treatment (punch adjustment) is additive — a new record referencing the original by optional FK, never an UPDATE of the original.
|
||||
- Segregation of duties, twice: creating an adjustment ≠ approving it; advancing period closure ≠ reopening it — these are distinct permissions.
|
||||
- Idempotency in two layers on AFD import: whole-file (SHA-256) and per-record (`device_id + nsr` unique).
|
||||
- Period closure is a veto, not a parallel history — a closed month blocks recalculation/approval/manual entries until an audited reopening, it doesn't duplicate data.
|
||||
- Sequential (never parallel) execution for anything touching the physical device (sync, batch calculation, connection tests) — embedded devices are low-throughput.
|
||||
- CRC-16/KERMIT (poly 0x1021 reflected as 0x8408, init 0x0000, no final XOR) for record types 2/3/4; type 7 uses its own hash chain, never recomputed by EDEN.
|
||||
|
||||
## Process
|
||||
1. Re-read `eden.md` module 6 §1-9 in full before touching parser/apuração logic — field positions and formulas are exact, not approximate.
|
||||
2. Preserve the `America/Sao_Paulo` fixed-timezone handling (Brazil has no DST since 2019) regardless of server timezone.
|
||||
3. Decide explicitly (ADR if changed) whether to apply `overtime_multiplier`/`apply_multiplier_to_time_bank` in the calculation engine — the legacy has the fields but never applies them (see `docs/assumptions.md` #6).
|
||||
|
||||
## Output format
|
||||
- Implementation plus explicit confirmation that `afd_records` has zero write routes beyond insert-on-import.
|
||||
22
.claude/agents/eden-inventory.md
Normal file
22
.claude/agents/eden-inventory.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: eden-inventory
|
||||
description: Use for warehouses, stock movements/ledger, serialized assets (serial/patrimônio/MAC), comodato, installation, and RMA. Trigger examples — "implement the stock reservation flow for a closed quote", "model asset_assignments", "prevent double-allocation of a serial number", "design the RMA flow". Do NOT use for the commercial side of closing a quote (defer to eden-commercial) — this agent owns what happens to physical stock once a quote/contract requires it.
|
||||
tools: Read, Grep, Glob, Bash, Write, Edit
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You own Inventory/Warehouse/Assets for EDEN, per Master Prompt §6.8 and ADR-0007. This is greenfield — the legacy OrçaFácil has no real stock module to port, only the requirements in the Master Prompt.
|
||||
|
||||
## Responsibilities
|
||||
- Stock balance is always derived from `stock_movements` (ledger) — never an editable balance column.
|
||||
- Every serialized asset (serial/patrimônio/MAC) has a database-enforced constraint preventing two simultaneous "active" assignments of the same identifier — this is a correctness invariant (Master Prompt §15.3), not just an application check.
|
||||
- Model the full install lifecycle: reserve → select specific serialized unit → link to contract/client → move to installed/comodato → track until return/write-off, per Master Prompt §6.8 numbered flow.
|
||||
- `ON DELETE` on stock/asset tables chosen consciously — a decommissioned asset is inactivated, not deleted, when it has movement history.
|
||||
|
||||
## Process
|
||||
1. Confirm warehouse/location belongs to a legal entity (Organization context) before modeling — never a warehouse floating without ownership.
|
||||
2. For every new movement type, confirm it's additive to the ledger (never an UPDATE of a past movement) — corrections are new offsetting movements, mirroring the audit philosophy used elsewhere in EDEN (e.g., timeclock adjustments).
|
||||
3. Coordinate with eden-commercial on the exact trigger point (which contract/quote state transition fires a reservation).
|
||||
|
||||
## Output format
|
||||
- Implementation plus the specific constraint/test proving "same serial never allocated twice" for the change in question.
|
||||
22
.claude/agents/eden-qa.md
Normal file
22
.claude/agents/eden-qa.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: eden-qa
|
||||
description: Use for writing/reviewing unit, integration, contract, and E2E tests, and for verifying the mandatory invariant test cases and minimum E2E flows before a feature is marked done. Trigger examples — "write the E2E for close-quote-with-approval", "did we cover the cross-reseller isolation invariant?", "add a contract test for the Focus NFe adapter mock". This agent verifies correctness; it does not replace eden-code-reviewer (code quality/security review) or the domain agent's own implementation.
|
||||
tools: Read, Grep, Glob, Bash, Write, Edit
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You own test strategy and coverage for EDEN, per Master Prompt §15.
|
||||
|
||||
## Responsibilities
|
||||
- Pyramid: unit tests for domain/calculation rules, integration tests against a real Postgres container, API tests, contract tests for adapters, Playwright E2E for critical flows.
|
||||
- Track the 15 mandatory minimum E2E flows (Master Prompt §15.2) and the invariant list (Master Prompt §15.3) as a living checklist — a feature touching one of these areas is not done until its corresponding case is automated.
|
||||
- Integration tests must hit a real database, never a mock of Postgres — mocked DB tests have historically masked real migration/constraint failures in systems like this.
|
||||
- For adapters (Focus NFe, SaperX, Control iD), contract tests run against a mock/fixture defined by the domain agent — never against the real external system in CI.
|
||||
|
||||
## Process
|
||||
1. Before marking a feature reviewed, map it against the Master Prompt §15.2/§15.3 lists — flag any invariant it touches that lacks a test.
|
||||
2. Write tests that would actually fail if the invariant were violated (e.g., attempt double-allocation of the same serial in a test and assert the DB/constraint rejects it) — not tests that only exercise the happy path.
|
||||
3. Coordinate with eden-security on which invariant tests double as security tests (cross-tenant isolation, audit immutability).
|
||||
|
||||
## Output format
|
||||
- Test code plus an explicit statement of which Master Prompt §15.2/§15.3 item(s) it satisfies, and which remain uncovered for that feature.
|
||||
26
.claude/agents/eden-security.md
Normal file
26
.claude/agents/eden-security.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: eden-security
|
||||
description: Use for IAM, OWASP hardening, LGPD compliance, encryption/secrets, audit design, and threat modeling across EDEN. Trigger examples — "review this new endpoint for authz", "does this leak data cross-reseller?", "how should we encrypt this new integration token?", "update the threat model for the new SaperX adapter". Use proactively whenever a new public route, a new secret type, or a new cross-tenant data path is introduced.
|
||||
tools: Read, Grep, Glob, Bash, Write, Edit
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You own security posture for EDEN: IAM, OWASP baseline, LGPD, encryption, audit, per Master Prompt §5 and §12, and `docs/security/threat-model.md`.
|
||||
|
||||
## Responsibilities
|
||||
- Every endpoint must resolve authorization server-side from the session — never trust `role`, `customer_id`, `reseller_id`, `legal_entity_id` from the client (Master Prompt §12).
|
||||
- Classify every new field per Master Prompt §5.4: one-way hash, reversible field encryption (AES-256-GCM, versioned key, root key never in the DB), or plain (with access control + audit) — never "encrypt everything" as a substitute for real classification.
|
||||
- Maintain `docs/security/threat-model.md`: update it whenever a new public route, new secret type, or new integration adapter is added.
|
||||
- Own the invariant test list in Master Prompt §15.3 (cross-reseller/cross-subscriber isolation, audit immutability, no duplicate webhook effects, etc.) — these must exist as automated tests, not just documentation.
|
||||
- Never let `super_admin` bypass audit, even though it bypasses authorization.
|
||||
- Secret scanning discipline: nothing in git/docs/fixtures/logs/screenshots ever contains a real credential (Master Prompt §2.2).
|
||||
|
||||
## Process
|
||||
1. For a new endpoint: confirm authn, authz (resource+action+scope per ADR-0004), input validation, rate limiting where relevant, and audit logging where the action is in the Master Prompt §5.6 minimum list.
|
||||
2. For a new secret: decide hash vs. reversible encryption per §5.4, confirm root key sourcing (env/secret store, never DB), confirm key versioning is wired.
|
||||
3. For a new integration: confirm adapter isolation (Master Prompt §13), webhook auth (HMAC) + idempotency, and that indisponibilidade never corrupts the ERP or blocks readiness.
|
||||
4. Register findings and decisions in `docs/security-findings.md` (incidents) or `docs/security/threat-model.md` (ongoing posture) as appropriate — never reproduce a real secret when documenting a finding.
|
||||
|
||||
## Output format
|
||||
- Findings ranked by severity, each with: file/endpoint, concrete exploit scenario, fix.
|
||||
- For threat-model updates: the diff to `docs/security/threat-model.md`, not a rewrite of the whole file.
|
||||
21
.claude/agents/eden-support.md
Normal file
21
.claude/agents/eden-support.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: eden-support
|
||||
description: Use for the service desk module — tickets, SLA policies/timers, queues, work orders (OS), and asset/contract linkage for support. Trigger examples — "implement SLA pause logic for waiting_customer", "model the ticket escalation queue", "expose ticket creation in the subscriber portal", "add a work order for a technical visit". Do NOT use for the underlying asset/inventory data model (defer to eden-inventory) or the contract data a ticket references (defer to eden-commercial).
|
||||
tools: Read, Grep, Glob, Bash, Write, Edit
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You own Support/Service Desk for EDEN, per Master Prompt §6.13. Greenfield — no legacy module to port.
|
||||
|
||||
## Responsibilities
|
||||
- Model: ticket, sequential human protocol number, category/subcategory, priority, impact/urgency, queue, owner, watchers, public/internal comments, attachments, SLA policy, SLA timers, first-response/resolution, justified pauses, escalation, work order (OS), affected assets/contract, root cause/resolution, customer satisfaction.
|
||||
- States: `new → triage → in_progress ⇄ waiting_customer/waiting_third_party → resolved → closed`, plus `cancelled`.
|
||||
- SLA timers must account for a support calendar and valid pauses — a ticket sitting in `waiting_customer` must not burn SLA time by default (configurable).
|
||||
- Subscriber portal: create/track own tickets only. Reseller portal: create/track tickets within its own customer base only — enforce at the query layer, test explicitly (same cross-tenant discipline as eden-commercial).
|
||||
|
||||
## Process
|
||||
1. Link every ticket optionally to a contract/service and to affected assets — never a floating ticket with no traceability when the customer has an active contract.
|
||||
2. Coordinate with eden-security on the exact scope rules for reseller/subscriber visibility before finalizing queries.
|
||||
|
||||
## Output format
|
||||
- Implementation plus the SLA timer test proving pauses are excluded correctly from elapsed time.
|
||||
22
.claude/agents/eden-telecom.md
Normal file
22
.claude/agents/eden-telecom.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: eden-telecom
|
||||
description: Use for telecom-specific contract data (DIDs/circuits), usage/consumption billing, and the SaperX integration adapter. Trigger examples — "map a client to a SaperX circuit", "import CDR consumption for billing", "reconcile SaperX invoice against EDEN invoice", "expose consumption in the subscriber portal". Do NOT use for the STFC tariff fields already on `products` (metered/tariff_rates — those are eden-commercial's pricing model) — this agent owns the external SaperX integration and consumption reconciliation specifically.
|
||||
tools: Read, Grep, Glob, Bash, Write, Edit
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You own the Telecom/SaperX integration for EDEN, per Master Prompt §6.12 and ADR-0010. This is greenfield — no SaperX code exists in the legacy to port; the legacy's only telecom-adjacent data is `products.tariff_rates`/`metered` (owned by eden-commercial) and the "IXC" free-text fields (never a real integration, just manual reference fields).
|
||||
|
||||
## Responsibilities
|
||||
- Build `integrations/saperx` as an isolated adapter/port (Master Prompt §13): encrypted token (AES-256-GCM per ADR-0005), IP-allowlist support if required, timeout/retry/circuit-breaker, correlation id, healthcheck independent of the main `/health/ready`.
|
||||
- Never couple internal entities to SaperX's raw payload — always a normalized DTO plus `external_id` + origin snapshot when needed for reconciliation.
|
||||
- If a needed SaperX endpoint isn't available/documented at implementation time: build the interface + mock + an explicit TODO — never fabricate a plausible-looking response.
|
||||
- Reconciliation (SaperX value × EDEN invoice) produces an exception queue for human review, same philosophy as eden-finance's bank reconciliation.
|
||||
- Subscriber portal exposure of consumption/circuits goes through the normalized DTO, scoped strictly to that customer account.
|
||||
|
||||
## Process
|
||||
1. Confirm with eden-commercial/eden-finance where SaperX-sourced usage data feeds into billing (`usage_charges`) before building the import path.
|
||||
2. Treat SaperX downtime as a non-fatal integration failure — never block core ERP operation or corrupt local state waiting on it.
|
||||
|
||||
## Output format
|
||||
- Implementation plus explicit labeling of what's real (tested against SaperX) vs. mocked pending credentials/docs — never blur the two (Master Prompt §22, "distinguir implementado, integrado em mock, aguardando credencial, não iniciado").
|
||||
84
.claude/hooks/guard_bash.py
Executable file
84
.claude/hooks/guard_bash.py
Executable file
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""PreToolUse guard for the Bash tool (EDEN, Master Prompt §2.1/§3.4).
|
||||
|
||||
Reads the hook input JSON on stdin, inspects tool_input.command, and emits a
|
||||
PreToolUse decision (allow/ask/deny) as JSON on stdout. Fails open (allow)
|
||||
on any internal error so this hook can never itself break a legitimate
|
||||
command — it only ever tightens, never crashes the turn.
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
PROJECT_ROOT = "/opt/eden"
|
||||
|
||||
DENY_PATTERNS = [
|
||||
(r"\brm\s+-rf\s+/(\s|$)", "rm -rf / — apagaria o filesystem inteiro"),
|
||||
(r"\brm\s+-rf\s+/\*", "rm -rf /* — apagaria o filesystem inteiro"),
|
||||
(r"\bdocker\s+system\s+prune\b", "docker system prune — remoção ampla, pode afetar outros projetos no host"),
|
||||
(r"\bdocker\s+volume\s+prune\b", "docker volume prune — remoção ampla de volumes, pode afetar outros projetos"),
|
||||
]
|
||||
|
||||
ASK_PATTERNS = [
|
||||
(r"\bdocker\s+volume\s+rm\b", "remoção de volume Docker — confirme que o volume pertence ao EDEN"),
|
||||
(r"(^|\s)ssh\s+\S+@", "SSH para host externo"),
|
||||
(r"(^|\s)scp\s+.*:", "SCP para/de host externo"),
|
||||
(r"(^|\s)rsync\s+.*\S+@\S+:", "rsync para/de host externo"),
|
||||
(r"\b(cat|less|more|head|tail)\s+[^\n|;&]*\.env(\.[a-zA-Z0-9_]+)?\b", "leitura/impressão de arquivo .env"),
|
||||
(r"\b(cat|less|more|head|tail)\s+[^\n|;&]*(credential|secret)", "leitura/impressão de arquivo de credencial/segredo"),
|
||||
(r"~/\.ssh", "acesso a ~/.ssh"),
|
||||
(r"(^|\s)/etc(/|\s|$)", "acesso a /etc"),
|
||||
(r"(^|\s)/root(/|\s|$)", "acesso a /root"),
|
||||
]
|
||||
|
||||
|
||||
def outside_project_paths(command: str):
|
||||
"""Find absolute paths referenced in the command that live outside /opt/eden
|
||||
but under /opt/ (i.e. plausibly another project on this host)."""
|
||||
hits = []
|
||||
for m in re.finditer(r"/opt/([a-zA-Z0-9_.\-]+)(/\S*)?", command):
|
||||
full = m.group(0)
|
||||
if not full.startswith(PROJECT_ROOT):
|
||||
hits.append(full)
|
||||
return hits
|
||||
|
||||
|
||||
def decide(command: str):
|
||||
for pattern, reason in DENY_PATTERNS:
|
||||
if re.search(pattern, command, re.IGNORECASE):
|
||||
return "deny", reason
|
||||
other_projects = outside_project_paths(command)
|
||||
if other_projects:
|
||||
return "deny", f"referencia caminho fora de {PROJECT_ROOT}: {', '.join(other_projects[:3])}"
|
||||
for pattern, reason in ASK_PATTERNS:
|
||||
if re.search(pattern, command, re.IGNORECASE):
|
||||
return "ask", reason
|
||||
return "allow", None
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
payload = json.load(sys.stdin)
|
||||
command = (payload.get("tool_input") or {}).get("command", "") or ""
|
||||
except Exception:
|
||||
# Fail open: if we can't parse input, don't block the tool call.
|
||||
print(json.dumps({}))
|
||||
return
|
||||
|
||||
decision, reason = decide(command)
|
||||
if decision == "allow":
|
||||
print(json.dumps({}))
|
||||
return
|
||||
|
||||
output = {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": decision,
|
||||
"permissionDecisionReason": f"[eden-guard] {reason} (EDEN_MASTER_PROMPT_CLAUDE.md §2.1/§3.4)",
|
||||
}
|
||||
}
|
||||
print(json.dumps(output))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
77
.claude/hooks/guard_file.py
Executable file
77
.claude/hooks/guard_file.py
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
"""PreToolUse guard for Read/Write/Edit/Glob/Grep (EDEN, Master Prompt §2.1/§3.4).
|
||||
|
||||
Blocks access to paths outside the project root and to ~/.ssh or /etc, and
|
||||
asks for confirmation before reading a .env/credential-looking file. Fails
|
||||
open on any internal error.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
PROJECT_ROOT = "/opt/eden"
|
||||
HOME = os.path.expanduser("~")
|
||||
|
||||
SECRET_NAME_RE = re.compile(r"(^|/)(\.env(\..+)?|.*credential.*|.*secret.*)$", re.IGNORECASE)
|
||||
|
||||
|
||||
def extract_path(tool_input: dict) -> str:
|
||||
for key in ("file_path", "path", "notebook_path"):
|
||||
if key in tool_input:
|
||||
return tool_input[key] or ""
|
||||
# Grep/Glob use "path" for the search root; pattern itself isn't a filesystem path.
|
||||
return ""
|
||||
|
||||
|
||||
def decide(path: str):
|
||||
if not path:
|
||||
return "allow", None
|
||||
abspath = os.path.abspath(path)
|
||||
|
||||
ssh_dir = os.path.join(HOME, ".ssh")
|
||||
if abspath == ssh_dir or abspath.startswith(ssh_dir + os.sep):
|
||||
return "deny", "acesso a ~/.ssh"
|
||||
if abspath == "/etc" or abspath.startswith("/etc" + os.sep):
|
||||
return "deny", "acesso a /etc"
|
||||
if abspath == "/root" or abspath.startswith("/root" + os.sep):
|
||||
return "deny", "acesso a /root"
|
||||
|
||||
if abspath.startswith("/opt/") and not (
|
||||
abspath == PROJECT_ROOT or abspath.startswith(PROJECT_ROOT + os.sep)
|
||||
):
|
||||
return "deny", f"caminho fora de {PROJECT_ROOT} (outro projeto no host)"
|
||||
|
||||
basename = os.path.basename(abspath)
|
||||
if SECRET_NAME_RE.match(basename):
|
||||
return "ask", f"leitura/escrita de arquivo que parece conter segredo ({basename})"
|
||||
|
||||
return "allow", None
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
payload = json.load(sys.stdin)
|
||||
tool_input = payload.get("tool_input") or {}
|
||||
path = extract_path(tool_input)
|
||||
except Exception:
|
||||
print(json.dumps({}))
|
||||
return
|
||||
|
||||
decision, reason = decide(path)
|
||||
if decision == "allow":
|
||||
print(json.dumps({}))
|
||||
return
|
||||
|
||||
output = {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": decision,
|
||||
"permissionDecisionReason": f"[eden-guard] {reason} (EDEN_MASTER_PROMPT_CLAUDE.md §2.1/§3.4)",
|
||||
}
|
||||
}
|
||||
print(json.dumps(output))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
44
.claude/hooks/post_write_check.sh
Executable file
44
.claude/hooks/post_write_check.sh
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
# PostToolUse hook for Write|Edit (EDEN, Master Prompt §3.4).
|
||||
# Best-effort incremental lint/typecheck for files under apps/, packages/, infra/.
|
||||
# Fails open: if pnpm/node/turbo aren't installed yet (Fase 0 / early Fase 1),
|
||||
# this prints a short notice and exits 0 without blocking anything.
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_ROOT="/opt/eden"
|
||||
INPUT_JSON="$(cat)"
|
||||
|
||||
FILE_PATH="$(python3 -c '
|
||||
import json, sys
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
ti = data.get("tool_input") or {}
|
||||
print(ti.get("file_path") or ti.get("notebook_path") or "")
|
||||
except Exception:
|
||||
print("")
|
||||
' <<< "$INPUT_JSON")"
|
||||
|
||||
# Only act on files under apps/, packages/, or infra/.
|
||||
case "$FILE_PATH" in
|
||||
"$PROJECT_ROOT"/apps/*|"$PROJECT_ROOT"/packages/*|"$PROJECT_ROOT"/infra/*) ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
if ! command -v pnpm >/dev/null 2>&1; then
|
||||
echo '{"systemMessage":"[eden-hook] pnpm ainda não está instalado neste ambiente — lint/typecheck incremental pulado (esperado na Fase 0/início da Fase 1)."}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! -f "$PROJECT_ROOT/package.json" ]; then
|
||||
echo '{"systemMessage":"[eden-hook] Monorepo ainda não inicializado (sem package.json na raiz) — lint/typecheck incremental pulado."}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
if pnpm turbo run lint typecheck --filter="...[HEAD^1]" >/tmp/eden-post-write-check.log 2>&1; then
|
||||
exit 0
|
||||
else
|
||||
TAIL="$(tail -n 20 /tmp/eden-post-write-check.log | tr '\n' ' ' | cut -c1-800)"
|
||||
echo "{\"systemMessage\":\"[eden-hook] lint/typecheck incremental falhou: ${TAIL}\"}"
|
||||
exit 0
|
||||
fi
|
||||
35
.claude/hooks/stop_secret_check.sh
Executable file
35
.claude/hooks/stop_secret_check.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stop hook (EDEN, Master Prompt §2.2/§3.4). Advisory only — never blocks.
|
||||
# If the project is under git, scans the working tree diff (staged+unstaged)
|
||||
# for obvious secret patterns and for critical TODO/FIXME markers introduced
|
||||
# without a tracking reference. Silent (no output) when there's nothing to
|
||||
# flag or when git/the repo isn't set up yet (expected in Fase 0).
|
||||
set -uo pipefail
|
||||
|
||||
PROJECT_ROOT="/opt/eden"
|
||||
cd "$PROJECT_ROOT" 2>/dev/null || exit 0
|
||||
|
||||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
DIFF="$(git diff HEAD 2>/dev/null; git diff --cached 2>/dev/null)"
|
||||
[ -z "$DIFF" ] && exit 0
|
||||
|
||||
SECRET_HITS="$(echo "$DIFF" | grep -E -i '^\+.*(AKIA[0-9A-Z]{16}|BEGIN (RSA|EC|OPENSSH|PRIVATE) KEY|password\s*=\s*["'"'"'][^"'"'"']+|api[_-]?key\s*=\s*["'"'"'][^"'"'"']+|secret\s*=\s*["'"'"'][^"'"'"']+)' || true)"
|
||||
TODO_HITS="$(echo "$DIFF" | grep -E -i '^\+.*(TODO|FIXME).*(CRITICAL|SECURITY|URGENT)' || true)"
|
||||
|
||||
if [ -z "$SECRET_HITS" ] && [ -z "$TODO_HITS" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
MSG="[eden-hook] Verificação de fim de etapa (Master Prompt DoD):"
|
||||
if [ -n "$SECRET_HITS" ]; then
|
||||
MSG="$MSG Possível segredo em claro no diff (revisar antes de commitar)."
|
||||
fi
|
||||
if [ -n "$TODO_HITS" ]; then
|
||||
MSG="$MSG TODO/FIXME crítico introduzido sem rastreamento (issue/ADR)."
|
||||
fi
|
||||
|
||||
python3 -c "import json,sys; print(json.dumps({'systemMessage': sys.argv[1]}))" "$MSG"
|
||||
exit 0
|
||||
50
.claude/settings.json
Normal file
50
.claude/settings.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"/opt/eden/.claude/hooks/guard_bash.py\"",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Read|Write|Edit|Glob|Grep",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"/opt/eden/.claude/hooks/guard_file.py\"",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash \"/opt/eden/.claude/hooks/post_write_check.sh\"",
|
||||
"timeout": 120,
|
||||
"statusMessage": "Rodando lint/typecheck incremental (EDEN)..."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash \"/opt/eden/.claude/hooks/stop_secret_check.sh\"",
|
||||
"timeout": 15
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
21
.claude/skills/eden-domain/SKILL.md
Normal file
21
.claude/skills/eden-domain/SKILL.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: eden-domain
|
||||
description: Core EDEN business vocabulary and legacy OrçaFácil behavior reference. Load when implementing or reviewing any commercial/customer/contract logic to check exact legacy terminology and state machines before inventing new ones.
|
||||
---
|
||||
|
||||
# EDEN domain skill
|
||||
|
||||
Use this skill whenever implementing a feature that has a legacy equivalent in `eden.md`, to avoid reinventing vocabulary or state machines that already exist and were battle-tested in production.
|
||||
|
||||
## When to use
|
||||
- Naming a new field/entity that might already have an established name in the legacy system (check `references/terminology.md` first).
|
||||
- Implementing a status/workflow transition — check `references/state-machines.md` for the exact legacy machine before designing a new one.
|
||||
- Needing the full legacy behavior for a module — read `references/legacy-orcafacil.md` for the section map, then go to `eden.md` directly for the exact section (this skill indexes, it does not duplicate the 4600-line source).
|
||||
|
||||
## References
|
||||
- `references/terminology.md` — canonical field/concept names from the legacy system, PT-BR, to preserve (see also `docs/glossary.md` at the project root, which is the authoritative version — this file exists for quick in-skill lookup).
|
||||
- `references/state-machines.md` — the state machines that must be preserved or deliberately superseded via ADR (quote deal_status, client/reseller registration_status, contract states, signature envelope states, timeclock period closure).
|
||||
- `references/legacy-orcafacil.md` — section index of `eden.md` (which line range covers which module) so you know where to read the exact rule instead of guessing.
|
||||
|
||||
## Rule
|
||||
Never invent a business rule that `eden.md` already documents. If a legacy rule seems wrong or worth changing, write an ADR (`docs/adr/`) explaining why — don't silently diverge or silently copy a known gap (Master Prompt §1).
|
||||
14
.claude/skills/eden-domain/references/legacy-orcafacil.md
Normal file
14
.claude/skills/eden-domain/references/legacy-orcafacil.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# eden.md — section index
|
||||
|
||||
Full source: `/opt/eden/eden.md` (4597 lines). Read the exact section directly — this is a navigation index, not a summary substitute.
|
||||
|
||||
| Lines | Module | Key topics |
|
||||
|---|---|---|
|
||||
| 1–97 | Intro / how to use this doc | Reading order, why not to implement everything at once |
|
||||
| 97–687 | 1. Auth, Users, Roles, Permissions, Security | `users`, `roles`, `role_permissions`, `companies`, role weight, feature-key permission model, JWT, password reset, bcrypt/JWT/rate-limit specifics |
|
||||
| 690–1450 | 2. Products, Quotes, Pricing/Fidelity, Contracts | `products` price tiers, `quotes`, fidelity vs contract_period, discount/markup approval, proportional rateio, financial formulas, "contract" as derived join |
|
||||
| 1451–2206 | 3. Client & Reseller Registration | `client_registrations`, PF/PJ differences, partners/QSA, public token flow, reseller "Programa de Canais", storage/attachment access control |
|
||||
| 2207–2822 | 4. Documents, PDF, E-signature | Tiptap templates, merge fields, Chromium PDF rendering + sanitization, signature envelope state machine, OTP, hash-chain audit, public verification |
|
||||
| 2823–3143 | 5. Fiscal (NCM, CFOP, municipalities) | Fiscal catalogs, sync (auto/manual), `product_fiscal_profiles`, upsert safety thresholds |
|
||||
| 3144–4141 | 6. Timeclock (Ponto Eletrônico) | Control iD integration, AFD parsing/CRC, apuração engine, time bank, period closure, punch adjustments |
|
||||
| 4142–4597 | 7. Backoffice diverso | Backup (streaming pg_dump/restore), meeting room/vehicle agenda, welcome page, companies (operational view), Management/ManagerDashboard, mailer, S3 |
|
||||
19
.claude/skills/eden-domain/references/state-machines.md
Normal file
19
.claude/skills/eden-domain/references/state-machines.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# EDEN state machines to preserve or deliberately supersede (with ADR)
|
||||
|
||||
## Quote `deal_status`
|
||||
`orcamento → fechado → (perdido)`. `fechado` only via the close-deal flow (creates/links a client registration in the same transaction). Reversal only by the correction-authorized role.
|
||||
|
||||
## Client / Reseller `registration_status`
|
||||
`rascunho → pendente_validacao → ativo ⇄ bloqueado/inativo`. No dedicated transition endpoints in legacy for client (generic PATCH); reseller has dedicated approve/block/reactivate actions. EDEN should keep the vocabulary identical across both entities.
|
||||
|
||||
## Contract states (EDEN-new, per ADR-0006)
|
||||
`draft → pending_signature → active → suspended/cancelled/terminated/expired → renewed`.
|
||||
|
||||
## Signature envelope status (19 states)
|
||||
`DRAFT → READY → SENT → VIEWED → IDENTITY_PENDING → CONSENT_PENDING → OTP_PENDING → OTP_SENT → OTP_VERIFIED → READY_TO_SIGN → SIGNING → SIGNED → FINALIZING → COMPLETED`, with `CANCELLED/EXPIRED/DECLINED/SUPERSEDED/ERROR` as terminal off-ramps. Port verbatim — this is validated, audited legal-tech logic (see `eden.md` §4.5.1).
|
||||
|
||||
## Timeclock period closure
|
||||
`ABERTO → EM_CONFERENCIA → FECHADO`, with `/reopen` going directly `FECHADO → ABERTO` (skips EM_CONFERENCIA), gated by a separate permission from the forward transition (segregation of duties).
|
||||
|
||||
## Support ticket (EDEN-new, per Master Prompt §6.13)
|
||||
`new → triage → in_progress ⇄ waiting_customer/waiting_third_party → resolved → closed`, plus `cancelled`.
|
||||
5
.claude/skills/eden-domain/references/terminology.md
Normal file
5
.claude/skills/eden-domain/references/terminology.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# EDEN terminology (canonical, PT-BR, from legacy)
|
||||
|
||||
See `docs/glossary.md` at the project root for the authoritative, maintained version. This file is a quick lookup mirror — if the two ever diverge, `docs/glossary.md` wins and this file should be updated to match.
|
||||
|
||||
Key terms not to rename without an ADR: `contract_period`, `fidelity_period`, `approval_status`, `deal_status` vs `status` (quote), `client_registration_id` (quote lock trigger), `registration_status` (rascunho/pendente_validacao/ativo/bloqueado/inativo), `role weight`, `feature key` → EDEN's resource+action+scope, `envelope_number`/`verification_id` (signature), `NSR`/`AFD` (timeclock).
|
||||
15
.claude/skills/eden-finance/SKILL.md
Normal file
15
.claude/skills/eden-finance/SKILL.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: eden-finance
|
||||
description: Billing, receivables, and reconciliation conventions for EDEN. Load when implementing billing runs, invoices, AR/AP, boleto/PIX, dunning, or bank reconciliation.
|
||||
---
|
||||
|
||||
# EDEN finance skill
|
||||
|
||||
## When to use
|
||||
- Designing a billing run or invoice consolidation flow → `references/billing.md`.
|
||||
- Modeling receivables/boleto/dunning → `references/receivables.md`.
|
||||
- Bank/OFX reconciliation → `references/reconciliation.md`.
|
||||
- Dunning rule engine specifics → `references/dunning.md`.
|
||||
|
||||
## Rule
|
||||
Billing (what's owed) ≠ Finance/AR-AP (collecting) ≠ Fiscal (documents) — see ADR-0008. Never let one module's table double as another's; never let a consolidated invoice be silently edited.
|
||||
7
.claude/skills/eden-finance/references/billing.md
Normal file
7
.claude/skills/eden-finance/references/billing.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Billing engine conventions (ADR-0008, Master Prompt §6.10)
|
||||
|
||||
- Entities: `billing_accounts`, `billing_cycles`, `subscriptions/services`, `charge_components`, `usage_charges`, `invoices`, `invoice_items`, `invoice_adjustments`, `billing_runs`, `billing_run_logs`.
|
||||
- Supports: mensalidade, pró-rata, implantação, locação, SaaS por usuário, franquia, consumo de telefonia, serviços avulsos, descontos contratados, ajustes manuais auditados.
|
||||
- A billing run must be idempotent and safely re-runnable **before** final consolidation.
|
||||
- After consolidation, an invoice is never silently edited — use adjustment/credit-debit note or a controlled re-billing flow instead.
|
||||
- Test explicitly: billing run repeated does not duplicate an invoice (Master Prompt §15.3).
|
||||
4
.claude/skills/eden-finance/references/dunning.md
Normal file
4
.claude/skills/eden-finance/references/dunning.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Dunning / cobrança conventions (Master Prompt §6.9)
|
||||
|
||||
- Configurable rules as data, not hardcoded logic: X days before due date, on due date, X days after, escalations, suspension/alert where policy allows, channels and templates.
|
||||
- n8n consumes dunning events without querying tables directly — publish via the transactional outbox (ADR-0009), event catalog owned by `eden-api-integrations`.
|
||||
6
.claude/skills/eden-finance/references/receivables.md
Normal file
6
.claude/skills/eden-finance/references/receivables.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Accounts receivable conventions (Master Prompt §6.9)
|
||||
|
||||
- A receivable title carries: parcela, competência, emissão, vencimento, juros, multa, desconto, baixa (full/partial), estorno, negociação, status, origem, cliente, contrato, fatura, conta bancária/gateway.
|
||||
- Boleto: provider abstraction from day one — the bank/gateway must be swappable without rewriting the domain. Store nosso-id, provider, external id, linha digitável, código de barras, PDF/URL, PIX copia-e-cola/QR when the provider offers it, status, provider events, timestamps.
|
||||
- Webhooks from bank/gateway: idempotent and authenticated, always.
|
||||
- Test explicitly: a repeated webhook never duplicates a payment (Master Prompt §15.3).
|
||||
6
.claude/skills/eden-finance/references/reconciliation.md
Normal file
6
.claude/skills/eden-finance/references/reconciliation.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Reconciliation conventions (Master Prompt §6.9)
|
||||
|
||||
- Import OFX/CSV and/or bank API.
|
||||
- Automatic matching by value/date/document; anything ambiguous goes to an exception queue for human review — never silently auto-match an ambiguous case.
|
||||
- Keep a reconciliation trail (what matched what, when, by whom/what process).
|
||||
- Same philosophy applies to SaperX × EDEN invoice reconciliation (see `eden-telecom` skill) — reuse the exception-queue pattern rather than inventing a second one.
|
||||
18
.claude/skills/eden-fiscal/SKILL.md
Normal file
18
.claude/skills/eden-fiscal/SKILL.md
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: eden-fiscal
|
||||
description: Fiscal catalog and Focus NFe integration conventions for EDEN (NCM/CFOP/municipality catalogs, product fiscal profiles, NFCom/NFS-e/recibo de locação). Load when implementing any fiscal classification or emission logic.
|
||||
---
|
||||
|
||||
# EDEN fiscal skill
|
||||
|
||||
## When to use
|
||||
- Porting or extending a fiscal catalog table → `references/focus-nfcom.md` / `references/focus-nfse.md` for emission specifics; see `eden.md` §5.2-5.3 (via `eden-domain` skill's index) for the exact catalog shapes.
|
||||
- Implementing recibo de locação → `references/rental-receipt.md`.
|
||||
|
||||
## References
|
||||
- `references/focus-nfcom.md`
|
||||
- `references/focus-nfse.md`
|
||||
- `references/rental-receipt.md`
|
||||
|
||||
## Rule
|
||||
A fiscal document is always a consequence of a billing item already classified via `product_fiscal_profiles` — never hardcoded per screen (ADR-0011, Master Prompt §6.11).
|
||||
5
.claude/skills/eden-fiscal/references/focus-nfcom.md
Normal file
5
.claude/skills/eden-fiscal/references/focus-nfcom.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Focus NFCom adapter (Master Prompt §6.11, ADR-0011)
|
||||
|
||||
Implement: adapter, unique/idempotent reference per document, emission, query, cancellation, webhooks, persistence of the normalized request without secrets, response/status, keys/identifiers, XML/PDF artifacts when available, retries with backoff, dead-letter/manual retry.
|
||||
|
||||
Classification comes from `product_fiscal_profiles` (`document_type = 'NFCOM'`, `nfcom_cclass_id`, etc.) — never inferred ad hoc at emission time.
|
||||
3
.claude/skills/eden-fiscal/references/focus-nfse.md
Normal file
3
.claude/skills/eden-fiscal/references/focus-nfse.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Focus NFS-e adapter (Master Prompt §6.11, ADR-0011)
|
||||
|
||||
Asynchronous flow: envio → status processando → consulta/webhook → autorizada/rejeitada → cancelamento/substituição where applicable. Municipal particularities isolated in configuration/adapter, never hardcoded in the domain (municipalities vary widely — reuse the legacy's `fiscal_nfse_trib_nacional`/`fiscal_nfse_trib_municipal` catalog split).
|
||||
3
.claude/skills/eden-fiscal/references/rental-receipt.md
Normal file
3
.claude/skills/eden-fiscal/references/rental-receipt.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Recibo de locação (Master Prompt §6.11)
|
||||
|
||||
Own document type for locação billing when that's the legal/tax classification defined by Handix/contabilidade — not a generic invoice. Versioned template, numbering, issuing legal entity, locatário, competência, itens, valores, and linkage to contract/invoice. Never encode a specific tax interpretation as universal — this classification must be reviewable by Handix's fiscal/accounting team.
|
||||
13
.claude/skills/eden-inventory/SKILL.md
Normal file
13
.claude/skills/eden-inventory/SKILL.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: eden-inventory
|
||||
description: Stock ledger and serialized asset conventions for EDEN. Load when implementing warehouse, stock movement, or serial/patrimônio/MAC tracking logic.
|
||||
---
|
||||
|
||||
# EDEN inventory skill
|
||||
|
||||
## When to use
|
||||
- Modeling any stock movement → `references/stock-ledger.md`.
|
||||
- Modeling serialized assets (equipment tracked individually) → `references/serialized-assets.md`.
|
||||
|
||||
## Rule
|
||||
Stock balance is always derived from the movement ledger, never an editable column (ADR-0007). A serial/MAC/patrimônio is never allocated to two simultaneously-active assets — enforce with a database constraint, not just application logic.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Serialized asset conventions (Master Prompt §6.8)
|
||||
|
||||
Track per unit: serial do fabricante, número de patrimônio, MAC address(es), marca/modelo, produto, warehouse/localização atual, status, cliente/contrato/serviço onde instalado, datas de movimentação, garantia, histórico completo.
|
||||
|
||||
Install lifecycle: reservar estoque → selecionar unidade serializada específica → vincular ao contrato/cliente → movimentar para instalado/comodato → manter rastreabilidade até devolução/baixa.
|
||||
|
||||
Invariant: never allow the same serial/MAC/patrimônio to be simultaneously active on two assets — enforce at the database layer.
|
||||
7
.claude/skills/eden-inventory/references/stock-ledger.md
Normal file
7
.claude/skills/eden-inventory/references/stock-ledger.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Stock ledger conventions (ADR-0007, Master Prompt §6.8)
|
||||
|
||||
Entities: `warehouses`, `warehouse_locations`, `stock_items`, `stock_lots` (when needed), `stock_movements`, `stock_reservations`, `inventory_counts`, `transfers`, `receipts`, `issues`, `returns`, `rma`.
|
||||
|
||||
Movement types: entrada compra, ajuste entrada/saída, transferência, reserva, liberação de reserva, saída venda, saída instalação, comodato, devolução, RMA, baixa patrimonial.
|
||||
|
||||
Balance is always `SUM(movements)` — never a stored, directly-editable number.
|
||||
21
.claude/skills/eden-security/SKILL.md
Normal file
21
.claude/skills/eden-security/SKILL.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: eden-security
|
||||
description: Security baseline, data classification, encryption, and audit conventions for EDEN. Load before implementing any new endpoint, secret, cross-tenant data path, or audit-logged action.
|
||||
---
|
||||
|
||||
# EDEN security skill
|
||||
|
||||
## When to use
|
||||
- Adding a new endpoint (check `references/security-baseline.md` for the OWASP checklist that applies to every route).
|
||||
- Adding a new field that might hold sensitive data (check `references/data-classification.md` before deciding hash vs. encrypt vs. plain).
|
||||
- Adding a new reversible secret (API key, integration token) — check `references/encryption.md` for the AES-256-GCM + key-versioning pattern.
|
||||
- Adding an action that should be audited — check `references/audit.md` for the minimum audited-action list and the append-only hash-chain pattern.
|
||||
|
||||
## References
|
||||
- `references/security-baseline.md`
|
||||
- `references/data-classification.md`
|
||||
- `references/encryption.md`
|
||||
- `references/audit.md`
|
||||
|
||||
## Rule
|
||||
See `docs/security/threat-model.md` at the project root for the living, authoritative threat model — this skill packages the reusable conventions; the threat model tracks current, module-specific risk decisions.
|
||||
10
.claude/skills/eden-security/references/audit.md
Normal file
10
.claude/skills/eden-security/references/audit.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Audit conventions — EDEN
|
||||
|
||||
Minimum audited actions (Master Prompt §5.6): login/logout/MFA, user/role/permission changes, discount approval, contract changes, price changes, stock movement, serial/MAC/asset link-unlink, fiscal document issue/cancel, financial write-off/reversal, boleto/billing changes, timeclock period reopening, subscription actions, integration secret/config changes.
|
||||
|
||||
Rules:
|
||||
- Append-only, enforced at the database level (trigger rejecting UPDATE/DELETE outside an explicit, itself-audited escape hatch) — never rely on "the application just never calls UPDATE."
|
||||
- Order of events defined by a monotonic sequence (serial/bigserial), never by timestamp alone (timestamps can collide within a transaction — see the legacy signature audit chain's `seq` column).
|
||||
- Never write a raw secret into audit metadata, ever.
|
||||
- `super_admin`-equivalent bypasses authorization but never bypasses audit.
|
||||
- For hash-chained audit trails (e.g., signature envelopes): version the canonicalization algorithm explicitly (`hash_algorithm_version`) so a future bug fix doesn't retroactively invalidate old, correctly-recorded events — treat linkage failures as always-real tampering, content failures on outdated algorithm versions as a legacy warning, not a failure.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Data classification — decide before writing a new column
|
||||
|
||||
Per Master Prompt §5.4. Three buckets, decide explicitly, never default to "encrypt everything":
|
||||
|
||||
1. **One-way hash**: passwords, refresh tokens, public tokens that never need recovery. Argon2id for passwords (ADR-0005); SHA-256/HMAC-SHA256 for tokens depending on entropy (see `encryption.md`).
|
||||
2. **Reversible field encryption (AES-256-GCM)**: anything the application must recover in plaintext to function — integration API keys/tokens (SaperX, Focus NFe, AI providers), device credentials (Control iD), gateway secrets. Root key never in the database; versioned.
|
||||
3. **Personal data (LGPD)**: minimize collection, access-control + audit + TLS + storage encryption by default; field-level encryption only for items the threat model flags as high-risk (not blanket).
|
||||
4. **Credit card**: never store CVV; tokenize via PSP; store only token/brand/last-4/non-sensitive metadata. No homegrown card vault.
|
||||
8
.claude/skills/eden-security/references/encryption.md
Normal file
8
.claude/skills/eden-security/references/encryption.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Encryption patterns — EDEN
|
||||
|
||||
See ADR-0005 for the full decision. Quick reference:
|
||||
|
||||
- **Reversible field encryption**: AES-256-GCM, random 96-bit IV per operation, auth tag verified on decrypt. Storage format: `{iv_b64}:{authTag_b64}:{ciphertext_b64}` in a single TEXT column (legacy-proven pattern from Control iD credential storage). Root key derived via SHA-256 of an env-provided secret, normalizing any-length input to 32 bytes. Every encrypted value also stores a `key_version` for rotation.
|
||||
- **OTP**: HMAC-SHA256 with a server secret (never plain SHA-256 — OTP space is only 10^6, needs secret-based rainbow-table resistance). 6 digits, 5-minute TTL, single use, max attempts with lockout, invalidate-on-new-request.
|
||||
- **Public link tokens** (client/reseller registration, signature invite): CSPRNG, ≥96 bits for registration tokens, 256 bits for signature invite tokens. Stored as SHA-256 hash only (plain hash sufficient given the token's own entropy — no HMAC needed here, unlike OTP).
|
||||
- **Passwords**: Argon2id, versioned parameters (see ADR-0005), re-hash on next successful login when parameters change.
|
||||
13
.claude/skills/eden-security/references/security-baseline.md
Normal file
13
.claude/skills/eden-security/references/security-baseline.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Security baseline — every EDEN endpoint
|
||||
|
||||
Checklist (Master Prompt §12):
|
||||
- Security headers (helmet-equivalent) + adequate CSP.
|
||||
- CORS by allowlist per environment (legacy had none configured — deliberate improvement).
|
||||
- Server-side input validation, output encoding, parameterized queries only (never string-concat SQL).
|
||||
- File upload: real MIME validation, size limits, stored outside webroot, malware-scan hook point.
|
||||
- Rate limiting: by IP AND by identity/token for sensitive routes (never just one dimension — legacy pattern, keep it).
|
||||
- Anti-enumeration on auth flows (forgot-password always returns success regardless of whether the email exists).
|
||||
- Authorization resolved server-side from session — never trust `role`/`customer_id`/`reseller_id`/`legal_entity_id` from the client.
|
||||
- Mass-assignment protection (explicit field allowlist on every PATCH, mirroring the legacy pattern of CPF/CNPJ never being in the admin-PATCH allowlist).
|
||||
- SSRF prevention on anything that renders external content (PDF renderer must never navigate the network — `setContent` only, block all outgoing requests).
|
||||
- Request size limits, secure cookie/token handling, dependency scanning, secret scanning in CI.
|
||||
13
.claude/skills/eden-telecom/SKILL.md
Normal file
13
.claude/skills/eden-telecom/SKILL.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: eden-telecom
|
||||
description: SaperX integration and telecom usage-billing conventions for EDEN. Load when implementing circuit/DID mapping, consumption import, or SaperX×EDEN reconciliation.
|
||||
---
|
||||
|
||||
# EDEN telecom skill
|
||||
|
||||
## When to use
|
||||
- Building/extending the SaperX adapter → `references/saperx.md`.
|
||||
- Modeling usage-based billing (CDR/consumption feeding `usage_charges`) → `references/usage-billing.md`.
|
||||
|
||||
## Rule
|
||||
Never couple internal entities to SaperX's raw payload — always a normalized DTO + `external_id` + origin snapshot (ADR-0010). If an endpoint isn't available/documented, build interface + mock + explicit TODO, never a fabricated response.
|
||||
5
.claude/skills/eden-telecom/references/saperx.md
Normal file
5
.claude/skills/eden-telecom/references/saperx.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# SaperX adapter conventions (ADR-0010, Master Prompt §6.12)
|
||||
|
||||
Requirements: per-environment config, encrypted token (AES-256-GCM), IP-allowlist support if required, timeout, safe retries, local rate limiting, correlation id, logs never containing the token, circuit breaker where appropriate, independent healthcheck.
|
||||
|
||||
Goals: associate EDEN customer ↔ SaperX customer/circuit; import/query circuits; import DIDs/numbers when the API allows; fetch invoices/closings; fetch components (mensalidade, ligações, SVA); import consumption/CDR for billing/audit when needed; reconcile SaperX value × EDEN invoice; expose the permitted view in the subscriber portal.
|
||||
5
.claude/skills/eden-telecom/references/usage-billing.md
Normal file
5
.claude/skills/eden-telecom/references/usage-billing.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Usage-based billing conventions (telecom)
|
||||
|
||||
Consumption (CDR, minutes, franquia usage) imported from SaperX feeds `usage_charges` in the billing engine (see `eden-finance` skill's `billing.md`). Coordinate the exact mapping with `eden-finance` before building the import path — this skill owns the *source* of usage data, not the billing calculation itself.
|
||||
|
||||
Legacy pricing fields to preserve on `products` (owned by `eden-commercial`, referenced here for context): `metered`, `minutes_allowance`, `tariff_rates` (`{tipo: {normal, reduced}}` for LC/LDN/VC1/VC2/VC3/LDI), `has_ldi`.
|
||||
13
.claude/skills/eden-timeclock/SKILL.md
Normal file
13
.claude/skills/eden-timeclock/SKILL.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: eden-timeclock
|
||||
description: Control iD device integration and AFD (Portaria 671) conventions for EDEN's timeclock module. Load when implementing device communication, AFD parsing, or attendance calculation logic.
|
||||
---
|
||||
|
||||
# EDEN timeclock skill
|
||||
|
||||
## When to use
|
||||
- Implementing/reviewing Control iD device communication → `references/controlid.md`.
|
||||
- Implementing/reviewing AFD parsing, CRC validation, or the apuração engine → `references/afd-671.md`.
|
||||
|
||||
## Rule
|
||||
`afd_records` (raw punch data) is immutable — no UPDATE/DELETE route, ever, at any privilege level. See `eden-hr-timeclock` agent for the full non-negotiable invariant list.
|
||||
10
.claude/skills/eden-timeclock/references/afd-671.md
Normal file
10
.claude/skills/eden-timeclock/references/afd-671.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# AFD (Portaria MTP 671/2021) conventions
|
||||
|
||||
- Layout version 004, ISO-8859-1 text, `\r\n` line separators. Each line: 9-digit NSR + 1-digit record type.
|
||||
- Only decode fields with an exactly-documented byte position; anything undocumented goes into a raw-tail capture — never guessed.
|
||||
- CRC-16/KERMIT (poly 0x1021 reflected as 0x8408, init 0x0000, no final XOR) validates record types 2/3/4. Type 7 uses its own hash chain (never recomputed). Types 1/5/6/9 have no validatable CRC.
|
||||
- CPF normalization: take the last 11 numeric digits of the raw field (never assume exact formatting) — same rule used to match against `timeclock_employees.cpf` and to relink orphaned records.
|
||||
- Idempotency: whole-file (SHA-256) at import time, per-record (`device_id + nsr` unique) at insert time.
|
||||
- `afd_records` is immutable — invalid/unsupported-layout rows are still inserted (never discarded), just excluded from the apuração calculation (`validation_status IN ('VALID','WARNING')` filter).
|
||||
- Apuração timezone is always fixed `America/Sao_Paulo`, independent of server timezone (Brazil has had no DST since 2019).
|
||||
- Tolerance (Art. 58 §1º CLT): per-event tolerance capped by a daily aggregate budget, both configurable per work schedule, not hardcoded.
|
||||
9
.claude/skills/eden-timeclock/references/controlid.md
Normal file
9
.claude/skills/eden-timeclock/references/controlid.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Control iD (REP iDClass) integration conventions
|
||||
|
||||
- Transport quirk: the device's firmware sends LF-only line terminators for most headers but still closes with `\r\n\r\n` before the body — this breaks strict HTTP/1.1 parsers (Node's `llhttp`). Communicate via raw socket (`net`/`tls`), building the request manually, with a tolerant response parser trying multiple header/body boundary separators.
|
||||
- Credential storage: AES-256-GCM, reversible (the app must recover the plaintext password to authenticate against the device on demand) — see `eden-security` skill's `encryption.md`.
|
||||
- No persistent device session — login fresh on every operation.
|
||||
- CPF is the device's own user identifier (mode 671) — no separate "controlid_user_id" concept.
|
||||
- CPF travels as a `Number` in the Control iD API (loses leading zeros by design of their API) — always re-pad to 11 digits when reading back.
|
||||
- All device operations run sequentially, never in parallel — embedded devices are low-throughput; batch operations (test-all-connections, sync-all-employees) iterate one device/employee at a time.
|
||||
- Categorize errors (auth/timeout/http/invalid-response/unreachable/tls/unknown) and translate to a user-facing message — never leak raw technical error text to the end user.
|
||||
63
.env.example
Normal file
63
.env.example
Normal file
@@ -0,0 +1,63 @@
|
||||
# EDEN — variáveis de ambiente (Fase 1 bootstrap)
|
||||
# Copiar para `.env` e preencher com valores reais.
|
||||
# Nunca commitar `.env` — já está no .gitignore.
|
||||
|
||||
# --- Banco de dados (Postgres 18, container eden-postgres) ---
|
||||
EDEN_DATABASE_NAME=eden
|
||||
EDEN_DATABASE_USER=eden
|
||||
EDEN_DATABASE_PASSWORD=
|
||||
# String de conexão completa, usada por packages/database e apps/api/apps/worker.
|
||||
# Dentro do compose, o host é o nome do serviço (eden-postgres); fora, localhost
|
||||
# na porta mapeada abaixo.
|
||||
DATABASE_URL=postgres://eden:CHANGE_ME@localhost:55432/eden
|
||||
|
||||
# Porta do Postgres mapeada ao host SÓ em desenvolvimento (nunca em produção —
|
||||
# ver docs/adr/0002-container-topology.md). Deixar em branco/comentar em prod.
|
||||
EDEN_POSTGRES_DEV_PORT=55432
|
||||
|
||||
# --- Portas das aplicações (cada uma em container/porta distintos — ADR-0002) ---
|
||||
EDEN_API_PORT=8080
|
||||
EDEN_CORE_PORT=3001
|
||||
EDEN_PARCEIROS_PORT=3002
|
||||
EDEN_ASSINANTE_PORT=3003
|
||||
|
||||
# --- Autenticação / sessão (ADR-0003) ---
|
||||
# Gerar com: openssl rand -hex 48
|
||||
JWT_SIGNING_SECRET=
|
||||
# Segredo do servidor para HMAC de OTP (nunca reaproveitar o JWT secret) —
|
||||
# gerar com: openssl rand -hex 32
|
||||
SIGNATURE_OTP_SECRET=
|
||||
|
||||
# --- Criptografia de campo reversível (ADR-0005) ---
|
||||
# Chave raiz para AES-256-GCM (segredos de integração, credenciais reversíveis).
|
||||
# Gerar com: openssl rand -hex 32. Rotação é operação auditada — ver docs/security/threat-model.md.
|
||||
EDEN_FIELD_ENCRYPTION_KEY=
|
||||
|
||||
# --- Bootstrap do primeiro superadmin (só usado se `users` estiver vazia) ---
|
||||
EDEN_SUPERADMIN_EMAIL=
|
||||
EDEN_SUPERADMIN_PASSWORD=
|
||||
|
||||
# --- SMTP (e-mail) ---
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=465
|
||||
SMTP_USER=
|
||||
SMTP_PASS=
|
||||
SMTP_FROM=
|
||||
|
||||
# --- S3 / storage de objetos (compatível MinIO ou AWS S3) ---
|
||||
S3_ENDPOINT=
|
||||
S3_REGION=us-east-1
|
||||
S3_FORCE_PATH_STYLE=true
|
||||
S3_ACCESS_KEY_ID=
|
||||
S3_SECRET_ACCESS_KEY=
|
||||
S3_BUCKET=
|
||||
|
||||
# --- URL pública base (usada para montar links absolutos em e-mails) ---
|
||||
PUBLIC_BASE_URL=http://localhost:3001
|
||||
|
||||
# --- Integrações externas (preenchidas quando cada adapter for implementado) ---
|
||||
FOCUS_NFE_API_TOKEN=
|
||||
FOCUS_NFE_BASE_URL=
|
||||
SAPERX_API_TOKEN=
|
||||
SAPERX_BASE_URL=
|
||||
CONTROLID_ENCRYPTION_KEY=
|
||||
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
.pnpm-store/
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
build/
|
||||
.turbo/
|
||||
*.tsbuildinfo
|
||||
|
||||
# Env / secrets
|
||||
.env
|
||||
.env.*.local
|
||||
.env.local
|
||||
*.pem
|
||||
*.key
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Test / coverage
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# Editor
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Purchased third-party theme asset — too large to version; extracted
|
||||
# components live in packages/ui instead (see docs/project-inventory.md).
|
||||
tema_do_Eden.zip
|
||||
|
||||
# Scratch space for one-off inventory/extraction work
|
||||
.tmp/
|
||||
|
||||
# Local Postgres data volume if ever bind-mounted for debugging
|
||||
infra/docker/pgdata/
|
||||
|
||||
# Claude Code local overrides (personal, never shared)
|
||||
.claude/settings.local.json
|
||||
74
CLAUDE.md
Normal file
74
CLAUDE.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# EDEN — CLAUDE.md
|
||||
|
||||
ERP Handix. Substitui o legado OrçaFácil. Três apps web (Core/Parceiros/Assinante) sobre um backend/API/identidade únicos. Ver `EDEN_MASTER_PROMPT_CLAUDE.md` para a missão e regras completas, `eden.md` para as regras de negócio do legado — não duplicar o conteúdo desses arquivos aqui.
|
||||
|
||||
## Stack
|
||||
|
||||
Node 22 LTS · pnpm workspaces + Turborepo · TypeScript strict · NestJS (API) · React + Vite + Tailwind (frontends) · PostgreSQL 18 · Docker Compose.
|
||||
|
||||
## Comandos principais
|
||||
|
||||
```bash
|
||||
pnpm install # instala tudo no monorepo
|
||||
pnpm dev # sobe todos os apps em modo dev (turbo)
|
||||
pnpm build # build de todo o monorepo
|
||||
pnpm lint / pnpm typecheck / pnpm test
|
||||
|
||||
pnpm db:migrate # aplica migrations pendentes (packages/database)
|
||||
pnpm db:migrate:down # reverte a última migration
|
||||
pnpm db:migrate:create <nome># cria uma nova migration
|
||||
|
||||
docker compose --env-file .env up -d # sobe Postgres + API + worker + as 3 apps
|
||||
docker compose --env-file .env up -d eden-postgres # só o banco, para rodar API/apps localmente fora do container
|
||||
```
|
||||
|
||||
## Estrutura do monorepo
|
||||
|
||||
```
|
||||
apps/
|
||||
api/ NestJS — API principal, único serviço com credencial de banco
|
||||
worker/ jobs assíncronos (BullMQ, quando Redis existir)
|
||||
core-web/ EDEN Core (ERP interno)
|
||||
reseller-web/ EDEN Parceiros
|
||||
subscriber-web/ EDEN Assinante
|
||||
packages/
|
||||
database/ migrations SQL versionadas (node-pg-migrate) + client de query
|
||||
contracts/ DTOs/schemas/eventos compartilhados entre apps
|
||||
ui/ Design System (derivado do tema DreamsERP)
|
||||
auth/ SDK de auth client-side comum às 3 apps
|
||||
observability/ logging estruturado, correlation id
|
||||
config/ schema/validação de env compartilhado
|
||||
testing/ helpers de teste compartilhados
|
||||
integrations/ adapters Focus NFe, SaperX, Control iD, S3, SMTP
|
||||
domain-shared/ tipos/constantes de domínio compartilhados
|
||||
infra/docker/ Dockerfiles + compose.yaml (topologia: ver docs/adr/0002-container-topology.md)
|
||||
docs/ arquitetura, ADRs, threat model, plano de implementação
|
||||
.claude/ agentes, skills e hooks especializados do projeto
|
||||
```
|
||||
|
||||
## Convenções de código
|
||||
|
||||
- TypeScript strict em tudo; sem `any` implícito.
|
||||
- Toda query SQL parametrizada; nenhuma concatenação de string com input do usuário.
|
||||
- Dinheiro sempre `NUMERIC` no banco / `string`-decimal ou biblioteca decimal no código — nunca `float`/`Number` para valores monetários armazenados.
|
||||
- IDs técnicos em UUID; código humano sequencial só quando o domínio precisar (ex.: `client_code`), nunca reaproveitando o UUID.
|
||||
- Toda tabela de agregado relevante tem `created_at/updated_at/created_by/updated_by`.
|
||||
- Nenhuma decisão de autorização confia em campo vindo do cliente (`role`, `reseller_id`, `customer_id`, `legal_entity_id`) — sempre resolvida a partir da sessão no servidor.
|
||||
|
||||
## Regras de segurança (resumo — detalhe em `docs/security/threat-model.md`)
|
||||
|
||||
- Segredo nunca em git/log/doc/fixture. `.env` nunca commitado (`.gitignore` já cobre).
|
||||
- Campo sensível classificado explicitamente: hash unidirecional, criptografia reversível (AES-256-GCM, chave versionada) ou plano com controle de acesso — nunca "criptografar tudo" sem critério.
|
||||
- Toda rota nova: autenticação + autorização server-side + validação de input, mínimo.
|
||||
|
||||
## Definição de pronto
|
||||
|
||||
Ver Master Prompt §20. Resumo: regra de negócio documentada, migration consistente, backend + authz + frontend implementados, estados loading/error/empty tratados, testes unit/integration (+E2E se fluxo crítico), OpenAPI atualizado, sem segredo/TODO crítico, code-reviewer e QA executados, documentação atualizada.
|
||||
|
||||
## Referências
|
||||
|
||||
- `EDEN_MASTER_PROMPT_CLAUDE.md` — missão, regras operacionais, arquitetura alvo completa.
|
||||
- `eden.md` — especificação funcional do legado OrçaFácil (fonte das regras de negócio).
|
||||
- `docs/architecture.md`, `docs/adr/`, `docs/security/threat-model.md`, `docs/implementation-plan.md`, `docs/progress.md`.
|
||||
- `.claude/agents/` — subagentes especializados por domínio.
|
||||
- `.claude/skills/` — skills de domínio (carregadas sob demanda).
|
||||
1564
EDEN_MASTER_PROMPT_CLAUDE.md
Normal file
1564
EDEN_MASTER_PROMPT_CLAUDE.md
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Eden_logo_horizontal.png
Normal file
BIN
Eden_logo_horizontal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 444 KiB |
BIN
Eden_logo_vertical.png
Normal file
BIN
Eden_logo_vertical.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 422 KiB |
29
apps/api/package.json
Normal file
29
apps/api/package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@eden/api",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"start": "node dist/main.js",
|
||||
"dev": "tsx watch src/main.ts",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"lint": "echo 'no linter configured yet'",
|
||||
"test": "echo 'no tests yet'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eden/database": "workspace:*",
|
||||
"@nestjs/common": "^10.4.15",
|
||||
"@nestjs/core": "^10.4.15",
|
||||
"@nestjs/platform-express": "^10.4.15",
|
||||
"@nestjs/terminus": "^10.2.3",
|
||||
"express": "^4.21.2",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^22.10.5",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
9
apps/api/src/app.module.ts
Normal file
9
apps/api/src/app.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { TerminusModule } from "@nestjs/terminus";
|
||||
import { HealthController } from "./health/health.controller";
|
||||
|
||||
@Module({
|
||||
imports: [TerminusModule],
|
||||
controllers: [HealthController],
|
||||
})
|
||||
export class AppModule {}
|
||||
36
apps/api/src/health/health.controller.ts
Normal file
36
apps/api/src/health/health.controller.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Controller, Get } from "@nestjs/common";
|
||||
import {
|
||||
HealthCheck,
|
||||
HealthCheckError,
|
||||
HealthCheckService,
|
||||
HealthIndicatorResult,
|
||||
} from "@nestjs/terminus";
|
||||
import { query } from "@eden/database";
|
||||
|
||||
@Controller("health")
|
||||
export class HealthController {
|
||||
constructor(private readonly health: HealthCheckService) {}
|
||||
|
||||
@Get("live")
|
||||
live() {
|
||||
// Liveness never depends on external services — only "is the process up".
|
||||
return { status: "ok" };
|
||||
}
|
||||
|
||||
@Get("ready")
|
||||
@HealthCheck()
|
||||
ready() {
|
||||
return this.health.check([(): Promise<HealthIndicatorResult> => this.checkPostgres()]);
|
||||
}
|
||||
|
||||
private async checkPostgres(): Promise<HealthIndicatorResult> {
|
||||
try {
|
||||
await query("SELECT 1");
|
||||
return { postgres: { status: "up" } };
|
||||
} catch (err) {
|
||||
throw new HealthCheckError("postgres check failed", {
|
||||
postgres: { status: "down", message: (err as Error).message },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
13
apps/api/src/main.ts
Normal file
13
apps/api/src/main.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import "reflect-metadata";
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import { AppModule } from "./app.module";
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const port = Number(process.env.EDEN_API_PORT ?? 8080);
|
||||
await app.listen(port, "0.0.0.0");
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[eden-api] listening on :${port}`);
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
15
apps/api/tsconfig.json
Normal file
15
apps/api/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ES2022",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"strictPropertyInitialization": false
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
13
apps/core-web/index.html
Normal file
13
apps/core-web/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>EDEN Core</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
27
apps/core-web/package.json
Normal file
27
apps/core-web/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@eden/core-web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json --noEmit && vite build",
|
||||
"dev": "vite --port ${EDEN_CORE_PORT:-3001}",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"lint": "echo 'no linter configured yet'",
|
||||
"test": "echo 'no tests yet'"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.49",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.0.7"
|
||||
}
|
||||
}
|
||||
6
apps/core-web/postcss.config.js
Normal file
6
apps/core-web/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
BIN
apps/core-web/public/favicon.png
Normal file
BIN
apps/core-web/public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 559 KiB |
12
apps/core-web/src/App.tsx
Normal file
12
apps/core-web/src/App.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export function App() {
|
||||
return (
|
||||
<main className="flex min-h-screen items-center justify-center bg-slate-950 text-slate-100">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-semibold">EDEN Core</h1>
|
||||
<p className="mt-2 text-slate-400">
|
||||
Bootstrap da Fase 1 — Design System ainda não portado do tema DreamsERP.
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
3
apps/core-web/src/index.css
Normal file
3
apps/core-web/src/index.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
10
apps/core-web/src/main.tsx
Normal file
10
apps/core-web/src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { App } from "./App";
|
||||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
8
apps/core-web/tailwind.config.js
Normal file
8
apps/core-web/tailwind.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{ts,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
13
apps/core-web/tsconfig.json
Normal file
13
apps/core-web/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"jsx": "react-jsx",
|
||||
"noEmit": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
10
apps/core-web/vite.config.ts
Normal file
10
apps/core-web/vite.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: true,
|
||||
port: Number(process.env.EDEN_CORE_PORT ?? 3001),
|
||||
},
|
||||
});
|
||||
13
apps/reseller-web/index.html
Normal file
13
apps/reseller-web/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>EDEN Parceiros</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
27
apps/reseller-web/package.json
Normal file
27
apps/reseller-web/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@eden/reseller-web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json --noEmit && vite build",
|
||||
"dev": "vite --port ${EDEN_PARCEIROS_PORT:-3002}",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"lint": "echo 'no linter configured yet'",
|
||||
"test": "echo 'no tests yet'"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.49",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.0.7"
|
||||
}
|
||||
}
|
||||
6
apps/reseller-web/postcss.config.js
Normal file
6
apps/reseller-web/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
BIN
apps/reseller-web/public/favicon.png
Normal file
BIN
apps/reseller-web/public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 559 KiB |
12
apps/reseller-web/src/App.tsx
Normal file
12
apps/reseller-web/src/App.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export function App() {
|
||||
return (
|
||||
<main className="flex min-h-screen items-center justify-center bg-slate-950 text-slate-100">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-semibold">EDEN Parceiros</h1>
|
||||
<p className="mt-2 text-slate-400">
|
||||
Bootstrap da Fase 1 — Design System ainda não portado do tema DreamsERP.
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
3
apps/reseller-web/src/index.css
Normal file
3
apps/reseller-web/src/index.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
10
apps/reseller-web/src/main.tsx
Normal file
10
apps/reseller-web/src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { App } from "./App";
|
||||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
8
apps/reseller-web/tailwind.config.js
Normal file
8
apps/reseller-web/tailwind.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{ts,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
13
apps/reseller-web/tsconfig.json
Normal file
13
apps/reseller-web/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"jsx": "react-jsx",
|
||||
"noEmit": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
10
apps/reseller-web/vite.config.ts
Normal file
10
apps/reseller-web/vite.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: true,
|
||||
port: Number(process.env.EDEN_PARCEIROS_PORT ?? 3002),
|
||||
},
|
||||
});
|
||||
13
apps/subscriber-web/index.html
Normal file
13
apps/subscriber-web/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>EDEN Assinante</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
27
apps/subscriber-web/package.json
Normal file
27
apps/subscriber-web/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@eden/subscriber-web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json --noEmit && vite build",
|
||||
"dev": "vite --port ${EDEN_ASSINANTE_PORT:-3003}",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"lint": "echo 'no linter configured yet'",
|
||||
"test": "echo 'no tests yet'"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.49",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.0.7"
|
||||
}
|
||||
}
|
||||
6
apps/subscriber-web/postcss.config.js
Normal file
6
apps/subscriber-web/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
BIN
apps/subscriber-web/public/favicon.png
Normal file
BIN
apps/subscriber-web/public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 559 KiB |
12
apps/subscriber-web/src/App.tsx
Normal file
12
apps/subscriber-web/src/App.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export function App() {
|
||||
return (
|
||||
<main className="flex min-h-screen items-center justify-center bg-slate-950 text-slate-100">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-semibold">EDEN Assinante</h1>
|
||||
<p className="mt-2 text-slate-400">
|
||||
Bootstrap da Fase 1 — Design System ainda não portado do tema DreamsERP.
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
3
apps/subscriber-web/src/index.css
Normal file
3
apps/subscriber-web/src/index.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
10
apps/subscriber-web/src/main.tsx
Normal file
10
apps/subscriber-web/src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { App } from "./App";
|
||||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
8
apps/subscriber-web/tailwind.config.js
Normal file
8
apps/subscriber-web/tailwind.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{ts,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
13
apps/subscriber-web/tsconfig.json
Normal file
13
apps/subscriber-web/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"jsx": "react-jsx",
|
||||
"noEmit": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
10
apps/subscriber-web/vite.config.ts
Normal file
10
apps/subscriber-web/vite.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: true,
|
||||
port: Number(process.env.EDEN_ASSINANTE_PORT ?? 3003),
|
||||
},
|
||||
});
|
||||
21
apps/worker/package.json
Normal file
21
apps/worker/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@eden/worker",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"start": "node dist/main.js",
|
||||
"dev": "tsx watch src/main.ts",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"lint": "echo 'no linter configured yet'",
|
||||
"test": "echo 'no tests yet'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eden/database": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.5",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
25
apps/worker/src/main.ts
Normal file
25
apps/worker/src/main.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { query } from "@eden/database";
|
||||
|
||||
/**
|
||||
* No real async jobs exist yet (BullMQ/Redis are only added when the first
|
||||
* job with a genuine need shows up — see docs/adr/0002-container-topology.md
|
||||
* and Master Prompt §4.1). This entrypoint exists so the compose topology
|
||||
* matches the documented architecture from day one and proves the worker
|
||||
* container can reach Postgres.
|
||||
*/
|
||||
async function main() {
|
||||
await query("SELECT 1");
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("[eden-worker] up, database reachable, no jobs configured yet");
|
||||
|
||||
// Keep the container alive; replace with a real job queue consumer
|
||||
// (BullMQ) once Fase 5/6 introduces async jobs (billing runs, PDF
|
||||
// generation, fiscal retries, etc.).
|
||||
setInterval(() => {}, 1 << 30);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("[eden-worker] fatal:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
12
apps/worker/tsconfig.json
Normal file
12
apps/worker/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ES2022",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
155
compose.yaml
Normal file
155
compose.yaml
Normal file
@@ -0,0 +1,155 @@
|
||||
# EDEN — topologia de containers (docs/adr/0002-container-topology.md)
|
||||
#
|
||||
# Postgres isolado, credencial de banco só no serviço eden-api. Cada uma das
|
||||
# 3 aplicações web em container e porta próprios, falando só com a API via
|
||||
# HTTP — nunca direto com o banco. Rodar a partir da raiz do repositório:
|
||||
#
|
||||
# docker compose --env-file .env up -d
|
||||
#
|
||||
name: eden
|
||||
|
||||
services:
|
||||
eden-postgres:
|
||||
image: postgres:18-alpine
|
||||
container_name: eden-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: ${EDEN_DATABASE_NAME:-eden}
|
||||
POSTGRES_USER: ${EDEN_DATABASE_USER:-eden}
|
||||
POSTGRES_PASSWORD: ${EDEN_DATABASE_PASSWORD:?defina EDEN_DATABASE_PASSWORD no .env}
|
||||
volumes:
|
||||
# Postgres 18's official image switched to a pg_ctlcluster-style layout:
|
||||
# mount the parent dir, not .../data — see
|
||||
# https://github.com/docker-library/postgres/pull/1259
|
||||
- eden_pgdata:/var/lib/postgresql
|
||||
# Mapeado ao host só para acesso de ferramenta local em desenvolvimento,
|
||||
# numa porta alta não-padrão — nunca 5432:5432, nunca exposto em produção
|
||||
# (ver docs/adr/0002-container-topology.md). Remover este bloco em prod.
|
||||
ports:
|
||||
- "${EDEN_POSTGRES_DEV_PORT:-55432}:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${EDEN_DATABASE_USER:-eden} -d ${EDEN_DATABASE_NAME:-eden}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
networks: [eden_net]
|
||||
|
||||
eden-api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: infra/docker/Dockerfile.node
|
||||
args:
|
||||
APP_NAME: api
|
||||
container_name: eden-api
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DATABASE_URL: postgres://${EDEN_DATABASE_USER:-eden}:${EDEN_DATABASE_PASSWORD}@eden-postgres:5432/${EDEN_DATABASE_NAME:-eden}
|
||||
EDEN_API_PORT: ${EDEN_API_PORT:-8080}
|
||||
JWT_SIGNING_SECRET: ${JWT_SIGNING_SECRET}
|
||||
SIGNATURE_OTP_SECRET: ${SIGNATURE_OTP_SECRET}
|
||||
EDEN_FIELD_ENCRYPTION_KEY: ${EDEN_FIELD_ENCRYPTION_KEY}
|
||||
SMTP_HOST: ${SMTP_HOST}
|
||||
SMTP_PORT: ${SMTP_PORT}
|
||||
SMTP_USER: ${SMTP_USER}
|
||||
SMTP_PASS: ${SMTP_PASS}
|
||||
SMTP_FROM: ${SMTP_FROM}
|
||||
S3_ENDPOINT: ${S3_ENDPOINT}
|
||||
S3_REGION: ${S3_REGION}
|
||||
S3_FORCE_PATH_STYLE: ${S3_FORCE_PATH_STYLE}
|
||||
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID}
|
||||
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY}
|
||||
S3_BUCKET: ${S3_BUCKET}
|
||||
PUBLIC_BASE_URL: ${PUBLIC_BASE_URL}
|
||||
ports:
|
||||
- "${EDEN_API_PORT:-8080}:${EDEN_API_PORT:-8080}"
|
||||
depends_on:
|
||||
eden-postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "node -e \"fetch('http://localhost:'+ (process.env.EDEN_API_PORT||8080) +'/health/live').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
networks: [eden_net]
|
||||
|
||||
eden-worker:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: infra/docker/Dockerfile.node
|
||||
args:
|
||||
APP_NAME: worker
|
||||
container_name: eden-worker
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DATABASE_URL: postgres://${EDEN_DATABASE_USER:-eden}:${EDEN_DATABASE_PASSWORD}@eden-postgres:5432/${EDEN_DATABASE_NAME:-eden}
|
||||
depends_on:
|
||||
eden-postgres:
|
||||
condition: service_healthy
|
||||
networks: [eden_net]
|
||||
|
||||
eden-core:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: infra/docker/Dockerfile.web
|
||||
args:
|
||||
APP_NAME: core-web
|
||||
container_name: eden-core
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${EDEN_CORE_PORT:-3001}:80"
|
||||
depends_on:
|
||||
eden-api:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks: [eden_net]
|
||||
|
||||
eden-parceiros:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: infra/docker/Dockerfile.web
|
||||
args:
|
||||
APP_NAME: reseller-web
|
||||
container_name: eden-parceiros
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${EDEN_PARCEIROS_PORT:-3002}:80"
|
||||
depends_on:
|
||||
eden-api:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks: [eden_net]
|
||||
|
||||
eden-assinante:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: infra/docker/Dockerfile.web
|
||||
args:
|
||||
APP_NAME: subscriber-web
|
||||
container_name: eden-assinante
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${EDEN_ASSINANTE_PORT:-3003}:80"
|
||||
depends_on:
|
||||
eden-api:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks: [eden_net]
|
||||
|
||||
networks:
|
||||
eden_net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
eden_pgdata:
|
||||
18
docs/adr/0001-modular-monolith.md
Normal file
18
docs/adr/0001-modular-monolith.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# ADR-0001: Modular Monolith como estilo arquitetural do backend
|
||||
|
||||
## Status
|
||||
Aceito
|
||||
|
||||
## Contexto
|
||||
O EDEN precisa cobrir 14+ domínios de negócio (CRM, contratos, estoque, financeiro, billing, fiscal, telecom, suporte, RH, etc.) servindo 3 aplicações web distintas. O legado OrçaFácil é um monolito simples (Express + Postgres) sem separação de módulo forte. Microserviços trariam isolamento de falha e escalabilidade independente, mas custam operacionalmente caro (deploy, observabilidade, transação distribuída, N bancos ou schemas) num estágio em que o time e o volume ainda não justificam esse custo — e o Master Prompt (§4.2) explicitamente pede para evitar microserviços prematuros.
|
||||
|
||||
## Decisão
|
||||
Construir `apps/api` como **modular monolith** em NestJS: um processo, módulos por bounded context (ver `docs/architecture.md` §2) com fronteiras de import explícitas (lint/arquitetura impede um módulo importar internals de outro), comunicação entre módulos via serviço de aplicação exposto ou evento de domínio (outbox) — nunca acesso direto a tabela de outro módulo.
|
||||
|
||||
Candidatos naturais a extração futura para serviço próprio, se/quando o volume justificar: **Billing** (processamento em lote, alta carga periódica) e **Fiscal** (chamadas externas longas/assíncronas). Nenhuma extração é feita nesta fase.
|
||||
|
||||
## Consequências
|
||||
- Positivo: deploy único mais simples, transação ACID cross-módulo quando necessário (ex.: fechar oferta + criar cadastro), menor custo operacional inicial.
|
||||
- Positivo: fronteiras de módulo desde o dia 1 tornam uma futura extração mecânica, não uma reescrita.
|
||||
- Negativo: falha de um módulo (ex.: bug de memória em geração de PDF) pode afetar o processo inteiro — mitigado por `apps/worker` separado para jobs pesados/assíncronos (PDF, billing run, fiscal) desde o início.
|
||||
- Negativo: todos os módulos compartilham o mesmo pool de conexão de banco — dimensionar pool e monitorar por módulo via métricas/labels.
|
||||
35
docs/adr/0002-container-topology.md
Normal file
35
docs/adr/0002-container-topology.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# ADR-0002: Topologia de containers — Postgres isolado + 1 container por aplicação web
|
||||
|
||||
## Status
|
||||
Aceito (decisão explícita do operador)
|
||||
|
||||
## Contexto
|
||||
O EDEN tem 3 aplicações web (`core-web`, `reseller-web`, `subscriber-web`) mais a API e um worker assíncrono, além do Postgres 18 exigido pelo Master Prompt (§4.3). O operador determinou explicitamente: o banco de dados deve subir em container separado; `eden-core`, `eden-parceiro` e `eden-assinantes` devem ser containers distintos, cada um em porta própria.
|
||||
|
||||
## Decisão
|
||||
Topologia de containers via Docker Compose (um `compose.yaml` por ambiente — dev/staging/prod usando overrides), com os seguintes serviços:
|
||||
|
||||
| Serviço | Container | Porta exposta ao host | Fala com Postgres? |
|
||||
|---|---|---|---|
|
||||
| `eden-postgres` | Postgres 18 | Não (produção) / porta alta não-padrão (dev) | — |
|
||||
| `eden-redis` | Redis (quando houver job real) | Não | — |
|
||||
| `eden-api` | API principal (NestJS) | `EDEN_API_PORT` (env, default 8080) | Sim — único serviço com credencial de banco |
|
||||
| `eden-worker` | Jobs assíncronos (BullMQ) | Não | Sim (mesma credencial de app, escopo próprio se possível) |
|
||||
| `eden-core` | Frontend ERP interno | `EDEN_CORE_PORT` (env, default 3001) | Não — só HTTP para `eden-api` |
|
||||
| `eden-parceiros` | Frontend portal revenda | `EDEN_PARCEIROS_PORT` (env, default 3002) | Não — só HTTP para `eden-api` |
|
||||
| `eden-assinante` | Frontend portal assinante | `EDEN_ASSINANTE_PORT` (env, default 3003) | Não — só HTTP para `eden-api` |
|
||||
|
||||
Regras adicionais:
|
||||
1. Rede docker interna dedicada (`eden_net`); só a API tem credencial de Postgres — as 3 apps web nunca recebem `DATABASE_URL`.
|
||||
2. Portas configuráveis via `.env`, nunca hardcoded no `compose.yaml`, para permitir múltiplas instâncias (dev + staging) na mesma máquina.
|
||||
3. `healthcheck` obrigatório em cada serviço; `depends_on` usa `condition: service_healthy`, não apenas ordem de start.
|
||||
4. Volume nomeado `eden_pgdata` para dado do Postgres; nunca bind mount direto de produção sem estratégia de backup validada.
|
||||
5. Build multi-stage; imagens de produção sem devDependencies/toolchain de build.
|
||||
6. Backup lógico (Master Prompt §6.15) roda a partir do container do worker (ou de um job dedicado), nunca do host diretamente — mantém a regra de "streaming, nunca materializar em disco local" também em containers.
|
||||
|
||||
## Consequências
|
||||
- Positivo: isolamento de falha por aplicação — um crash no frontend do assinante não derruba o core nem a API.
|
||||
- Positivo: cada app pode escalar/atualizar independentemente (ex.: deploy do `eden-assinante` sem tocar no `eden-core`).
|
||||
- Positivo: superfície de acesso ao banco reduzida a um único serviço, simplificando auditoria de acesso a dado.
|
||||
- Negativo: mais serviços para orquestrar/monitorar do que um único container "tudo junto" — mitigado por healthchecks e observabilidade centralizada (Master Prompt §16).
|
||||
- A definir na Fase 1: se `eden-core`/`eden-parceiros`/`eden-assinante` são servidos como SPA estática (nginx) ou com SSR — não muda a topologia de containers, só a imagem base de cada um.
|
||||
21
docs/adr/0003-auth-sessions.md
Normal file
21
docs/adr/0003-auth-sessions.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# ADR-0003: Autenticação e gestão de sessão
|
||||
|
||||
## Status
|
||||
Aceito
|
||||
|
||||
## Contexto
|
||||
O legado usa JWT HS256 com payload mínimo (`sub`, `ver`), expiração fixa de 30 dias, sem refresh token, invalidação via `token_version` incremental (derruba todas as sessões de uma vez, nunca uma só). O Master Prompt (§5.3) pede explicitamente uma evolução: access token curto + refresh token rotativo (ou sessão server-side segura), refresh tokens armazenados em hash, histórico de sessões/dispositivos, "encerrar todas as sessões", MFA/TOTP preparado.
|
||||
|
||||
## Decisão
|
||||
- **Access token**: JWT de vida curta (15 min), payload mínimo (`sub`, sessão id), assinado (algoritmo a confirmar em ADR de criptografia geral — HS256 mantém paridade com o legado, RS256 fica em aberto se houver necessidade de verificação por serviço externo).
|
||||
- **Refresh token**: opaco, alta entropia (≥256 bits), **rotativo a cada uso** (um novo é emitido e o antigo invalidado), armazenado só como hash no banco (nunca em claro) — mesma filosofia do token de link público do legado.
|
||||
- **Tabela `sessions`** (não só um contador `token_version`): permite listar sessões/dispositivos ativos e revogar individualmente **ou** todas de uma vez (superset da capacidade do legado, que só permitia "todas").
|
||||
- **Vínculo por aplicação**: uma sessão registra explicitamente para qual aplicação (`core`/`reseller`/`subscriber`) foi emitida — nunca inferir pelo papel do usuário (Master Prompt §5.1).
|
||||
- **MFA/TOTP**: schema preparado desde a Fase 1 (tabela de fator, secret cifrado), ativação opcional para todos, indicada como obrigatória por política para `super_admin`/`admin` assim que a UI existir.
|
||||
- Senha: Argon2id (ADR-0005 detalha parâmetros/versionamento), não bcrypt.
|
||||
|
||||
## Consequências
|
||||
- Positivo: revogação granular por sessão/dispositivo, alinhado ao pedido explícito do Master Prompt.
|
||||
- Positivo: access token de vida curta reduz janela de uso de token vazado sem exigir consulta ao banco a cada request (mesma vantagem de performance que o legado tinha com JWT, mas com menor exposição).
|
||||
- Negativo: mais complexidade que o modelo legado (rotação de refresh token exige lógica de "replay detection" — se um refresh token já usado for reapresentado, é sinal de token roubado; a sessão inteira deve ser revogada nesse caso).
|
||||
- Migração: nenhuma — é um sistema novo, não há sessões legadas para migrar.
|
||||
20
docs/adr/0004-permission-model.md
Normal file
20
docs/adr/0004-permission-model.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# ADR-0004: Modelo de permissões — RBAC + peso hierárquico + escopo de dados
|
||||
|
||||
## Status
|
||||
Aceito
|
||||
|
||||
## Contexto
|
||||
O legado tem um sistema de feature keys com 2 níveis (`view`/`edit`) por papel, mais peso hierárquico (`role weight`) que impede um ator de administrar papel/usuário de peso maior, mais um caso especial hardcoded de "próprio vs. todos" (`ofertas`/`ofertas_others`) repetido manualmente por feature quando necessário. O Master Prompt (§5.2) pede evolução: cada permissão deve expressar recurso/ação (`view`/`create`/`edit`/`delete`/`approve`/`export`/`manage`) e escopo (`own`/`team`/`reseller`/`legal_entity`/`all`) — generalizando o padrão "próprio vs. todos" em vez de repeti-lo campo a campo.
|
||||
|
||||
## Decisão
|
||||
- Preservar o conceito de **role weight** exatamente como no legado (nunca um ator administra papel/usuário de peso maior; `super_admin` sempre no teto, nunca editável).
|
||||
- Substituir o mapa plano `{feature_key: 'view'|'edit'}` por uma matriz `role_permissions(role, resource, action, scope)` — cada linha concede uma ação sobre um recurso com um escopo. Isso generaliza o caso `ofertas`/`ofertas_others` sem precisar de uma segunda feature key por recurso: o escopo `own` já cobre "só minhas ofertas", `all`/`reseller` cobre "todas"/"da revenda".
|
||||
- Papel novo nasce sem nenhuma linha (zero acesso) — replica a regra do legado de nunca herdar permissão por padrão.
|
||||
- `super_admin` continua **hardcoded fora da tabela** (nunca consultado via `role_permissions`), com bypass total mas sempre auditado.
|
||||
- UI de gerenciamento de papel permite configurar, por menu/módulo, ação e alcance — conforme pedido explícito do Master Prompt §5.2.
|
||||
|
||||
## Consequências
|
||||
- Positivo: elimina a necessidade de duplicar feature key para cada distinção "próprio vs. todos" que aparecer no futuro (o legado já tinha um caso day-1, `ofertas_others` — outros vão aparecer em Contratos, Chamados, etc.).
|
||||
- Positivo: mapeamento direto do padrão de autorização do legado (`requireFeatureOrSuperAdmin`) para um middleware `requirePermission(resource, action, scopeResolver)` equivalente.
|
||||
- Negativo: migração de mental model para quem vai configurar papéis (matriz maior que o mapa binário do legado) — mitigado com UI que agrupa por módulo/menu como já era.
|
||||
- Todo endpoint continua resolvendo escopo no servidor a partir da sessão (nunca aceitar `reseller_id`/`customer_id` do cliente) — reforça Master Prompt §12.
|
||||
20
docs/adr/0005-encryption-secrets.md
Normal file
20
docs/adr/0005-encryption-secrets.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# ADR-0005: Criptografia e gestão de segredos
|
||||
|
||||
## Status
|
||||
Aceito
|
||||
|
||||
## Contexto
|
||||
O legado usa bcrypt custo 10 para senha, JWT HS256 sem rotação, AES-256-GCM para o único segredo reversível identificado (senha de equipamento Control iD), HMAC-SHA256 para OTP. O Master Prompt (§5.4) pede Argon2id para senha e AES-256-GCM (ou equivalente autenticado) com versionamento de chave para todo segredo operacional reversível (API keys de IA, tokens SaperX, credenciais Control iD, secrets de gateway), com chave raiz nunca no banco.
|
||||
|
||||
## Decisão
|
||||
- **Hash unidirecional** (senha, refresh token, tokens públicos sem necessidade de recuperação): Argon2id, parâmetros iniciais conservadores e revisáveis (memory cost, iterations, parallelism documentados em `packages/auth`), com **versionamento de parâmetro** por hash armazenado (permite aumentar custo no futuro sem invalidar hashes antigos — eles são re-hasheados no próximo login bem-sucedido).
|
||||
- **Criptografia reversível de campo**: AES-256-GCM, IV de 96 bits aleatório por operação, tag de autenticação verificada na decriptação (falha se adulterado) — mesmo formato de armazenamento do legado (`iv:tag:ciphertext`, base64), reaproveitando padrão já validado em produção pela Handix.
|
||||
- **Versionamento de chave**: todo campo cifrado grava também qual versão de chave raiz foi usada (`key_version`), permitindo rotação de chave raiz sem re-cifrar tudo de uma vez (re-cifra sob demanda/job de rotação).
|
||||
- **Chave raiz**: nunca no banco nem na imagem do container — variável de ambiente/secret store, injetada no container em runtime. Rotação de chave raiz é operação registrada e auditada (runbook próprio).
|
||||
- **OTP**: manter HMAC-SHA256 com segredo de servidor (nunca hash simples — espaço pequeno de 10⁶ valores exige resistência a rainbow table via segredo), TTL curto, uso único, máximo de tentativas com bloqueio — replicar fielmente o padrão do legado (validado em produção).
|
||||
- **Cartão de crédito**: nunca armazenar CVV; tokenização via gateway/PSP; nenhum cofre de cartão caseiro (Master Prompt §5.4).
|
||||
|
||||
## Consequências
|
||||
- Positivo: Argon2id é hoje o padrão recomendado (OWASP) sobre bcrypt, resistente a ataque por GPU/ASIC.
|
||||
- Positivo: versionamento de chave/parâmetro evita "big bang" de rotação — rotação é incremental e auditável.
|
||||
- Negativo: Argon2id é mais pesado computacionalmente que bcrypt custo 10 — dimensionar parâmetros considerando throughput de login esperado (não copiar cegamente defaults de biblioteca sem medir).
|
||||
23
docs/adr/0006-contract-first-class.md
Normal file
23
docs/adr/0006-contract-first-class.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# ADR-0006: Contrato como agregado de primeira classe
|
||||
|
||||
## Status
|
||||
Aceito
|
||||
|
||||
## Contexto
|
||||
No legado, "contrato" não é uma tabela — é a junção em tempo de consulta de `client_registrations` (ativos) com a `quotes` que os originou. Isso funciona para o caso simples de uma oferta = um contrato, mas não suporta amendments, renovações, múltiplas versões, ou itens/partes de contrato como entidades consultáveis. O Master Prompt (§6.6) exige transformar contrato em agregado de primeira classe.
|
||||
|
||||
## Decisão
|
||||
Criar as tabelas: `contracts`, `contract_items`, `contract_parties`, `contract_versions`, `contract_documents`, `contract_amendments`, `contract_renewals`, `contract_status_history`, `contract_assets`, `contract_services`, `contract_billing_rules`.
|
||||
|
||||
Estados: `draft → pending_signature → active → suspended/cancelled/terminated/expired → renewed`.
|
||||
|
||||
Regras preservadas do legado (nunca perder):
|
||||
- **`contract_period` (faixa de preço) permanece distinto de `fidelity_period` (permanência efetiva)** — vigência/vencimento/multa sempre calculados pela fidelidade **resolvida** (`fidelity_period` se aprovado, senão `contract_period`), nunca pela faixa de preço bruta.
|
||||
- Contrato assinado grava **snapshot** dos valores jurídicos/comerciais relevantes no momento da assinatura (via `contract_versions`) — uma alteração futura de produto/preço nunca muda retroativamente um contrato já assinado (invariante nº4 do Master Prompt §24).
|
||||
- Fluxo de fechamento de oferta → geração de contrato preserva a "trava" equivalente (oferta travada do legado vira, no EDEN, transição de estado do contrato que também impede edição desconforme, exceto por papel com permissão de correção auditada equivalente ao `super_admin` do legado).
|
||||
|
||||
## Consequências
|
||||
- Positivo: permite amendments/renovações/múltiplas partes sem gambiarra de "reabrir a oferta".
|
||||
- Positivo: relatórios de vencimento/MRR (equivalente ao `GET /contracts/report` do legado) passam a consultar uma tabela real em vez de uma junção calculada, com melhor performance de índice.
|
||||
- Negativo: mais complexidade de schema/migração do que o legado; mitigado por ser green-field (sem dado legado a migrar automaticamente — Handix decide se há import histórico do OrçaFácil, fora do escopo desta ADR).
|
||||
- Depende de: Customer 360 (Fase 2) e Commercial/Ofertas (Fase 2) já existirem — ver `docs/architecture.md` §2.
|
||||
20
docs/adr/0007-stock-ledger.md
Normal file
20
docs/adr/0007-stock-ledger.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# ADR-0007: Estoque como ledger de movimentos, nunca saldo editável
|
||||
|
||||
## Status
|
||||
Aceito
|
||||
|
||||
## Contexto
|
||||
O legado não tem módulo de estoque real — produtos têm preço e flags, mas nenhuma tabela de movimento/saldo. O Master Prompt (§6.8, §11.5) exige estoque com ledger de movimentos, ativos serializados (serial/patrimônio/MAC) e rastreabilidade completa warehouse↔cliente↔warehouse.
|
||||
|
||||
## Decisão
|
||||
Modelar: `warehouses`, `warehouse_locations`, `stock_items`, `stock_lots` (quando necessário), `stock_movements` (ledger append-only), `stock_reservations`, `serialized_assets`, `asset_assignments`, `inventory_counts`, `transfers`, `receipts`, `issues`, `returns`, `rma`, `asset_maintenance`.
|
||||
|
||||
Saldo de estoque é **sempre calculado** a partir de `stock_movements` (soma de entradas/saídas), nunca uma coluna editável diretamente. Cada `serialized_asset` tem um único estado ativo por vez (nunca dois ativos "ativos" com o mesmo serial/MAC/patrimônio simultaneamente — constraint de banco, não só validação de aplicação).
|
||||
|
||||
Fluxo de instalação (fechamento de oferta/contrato com equipamento): reserva → seleção de unidade serializada → vínculo a contrato/cliente → movimento para instalado/comodato → rastreabilidade até devolução/baixa (Master Prompt §6.8).
|
||||
|
||||
## Consequências
|
||||
- Positivo: auditoria completa de estoque (invariante nº7 do Master Prompt §24 — rastrear do warehouse até o cliente e de volta).
|
||||
- Positivo: elimina classe de bug "saldo dessincronizado" comum em campo editável.
|
||||
- Negativo: toda operação de estoque precisa passar por um serviço de domínio que grava o movimento — nenhum caminho de escrita direta em "saldo".
|
||||
- Depende de: Organization (warehouses por unidade legal) já existir.
|
||||
22
docs/adr/0008-billing-finance-fiscal-separation.md
Normal file
22
docs/adr/0008-billing-finance-fiscal-separation.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# ADR-0008: Separação entre Billing, Financeiro (AR/AP) e Fiscal
|
||||
|
||||
## Status
|
||||
Aceito
|
||||
|
||||
## Contexto
|
||||
O legado não tem billing recorrente nem contas a receber/pagar como módulo — só a oferta/contrato com valores. O Master Prompt (§6.9, §6.10, §6.11) exige três motores distintos e explicitamente adverte: "nunca confundir faturamento, documento fiscal e recebimento: são eventos relacionados, porém distintos."
|
||||
|
||||
## Decisão
|
||||
Três motores com dados e responsabilidades separadas:
|
||||
|
||||
1. **Billing** (`billing_accounts`, `billing_cycles`, `subscriptions/services`, `charge_components`, `usage_charges`, `invoices`, `invoice_items`, `invoice_adjustments`, `billing_runs`): responsável por **calcular o que é devido** (mensalidade, pró-rata, implantação, consumo) e gerar a fatura interna (`invoice`). Fechamento de billing run é idempotente e reexecutável com segurança **antes** da consolidação; depois de consolidada, uma invoice não é editada silenciosamente — usa ajuste/nota de crédito/débito ou refaturamento controlado.
|
||||
2. **Finance/AR-AP**: responsável por **cobrar e receber/pagar** — títulos, parcelas, boletos (provider abstraction), dunning, conciliação, contas a pagar. Um título de AR nasce a partir de uma invoice de billing, mas é uma entidade própria (permite negociação, baixa parcial, estorno, sem tocar no billing).
|
||||
3. **Fiscal**: responsável por **emitir o documento fiscal** correspondente a um item de billing já classificado (fiscal profile) — nunca hardcoded por tela. Documento fiscal é consequência do item de faturamento, não do clique de um usuário numa tela específica.
|
||||
|
||||
Cada camada guarda `external_id`/referência para a anterior, nunca duplica o cálculo.
|
||||
|
||||
## Consequências
|
||||
- Positivo: permite reconciliar valor da origem (billing) até o recebimento (finance) e até o documento fiscal (fiscal) — invariante nº5 e nº6 do Master Prompt §24.
|
||||
- Positivo: mudança de gateway de cobrança (Finance) não afeta o cálculo de billing; mudança de regra tributária (Fiscal) não afeta o cálculo comercial.
|
||||
- Negativo: mais tabelas e mais pontos de integração entre módulos do que uma solução monolítica "fatura única" — mitigado por eventos de domínio (`invoice.created`, `payment.received`, `fiscal_document.authorized`) documentados no Master Prompt §9.2.
|
||||
- Depende de: Contracts (Fase 3) e Inventory/consumo (para usage_charges) parcialmente.
|
||||
18
docs/adr/0009-transactional-outbox.md
Normal file
18
docs/adr/0009-transactional-outbox.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# ADR-0009: Outbox transacional para eventos de integração
|
||||
|
||||
## Status
|
||||
Aceito
|
||||
|
||||
## Contexto
|
||||
O EDEN precisa publicar eventos de domínio (`lead.created`, `contract.signed`, `invoice.overdue`, etc. — catálogo no Master Prompt §9.2) para consumo por n8n/webhooks/outros módulos, sem perder evento em caso de falha entre o commit da transação de negócio e a publicação. O legado não tem esse problema porque não publica eventos externos.
|
||||
|
||||
## Decisão
|
||||
Toda operação que precise emitir um evento relevante grava a linha do evento na mesma transação SQL da mudança de negócio, numa tabela `outbox_events` (payload normalizado, tipo do evento, status `pending/delivered/failed`, tentativas, correlation id). Um processo separado (`apps/worker`) lê a outbox e entrega (webhook assinado HMAC, ou fila interna para o próprio módulo consumidor), marcando como entregue só após confirmação — com retry/backoff e dead-letter após esgotar tentativas.
|
||||
|
||||
Consumidores (internos ou externos via n8n) devem ser idempotentes por `event_id` — reentrega nunca duplica efeito.
|
||||
|
||||
## Consequências
|
||||
- Positivo: garante "at-least-once" delivery sem depender de o processo de aplicação sobreviver após o commit (invariante nº9 do Master Prompt §24 — webhook repetido não duplica efeito, aplicado também no sentido saída).
|
||||
- Positivo: replay manual de evento é trivial (reprocessar linha da outbox).
|
||||
- Negativo: exige limpeza/arquivamento periódico da tabela de outbox (retenção configurável) para não crescer indefinidamente.
|
||||
- Negativo: consumidores precisam de lógica de idempotência — custo replicado em cada integração, mitigado por uma camada compartilhada em `packages/integrations`.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user