-
Notifications
You must be signed in to change notification settings - Fork 37
feat(squads): bounded contexts + ADRs for onboarding & squads governance #332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
7af50a0
30712e2
1d669b6
c9e3168
e784d19
33fbfa4
e696486
0cb4fc3
9114069
dd2a5d5
36262a8
ce6ded5
cb96c3a
95be88d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. por que os |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| # Onboarding Context | ||
|
|
||
| The universal, mandatory entry layer of the ecosystem. Owns the polymorphic onboarding state | ||
| machines that a person walks through, and the per-type **completion** status that other contexts | ||
| consume as an access gate. Today it powers community entry (`Welcome`) and squad entry (`Squads`), | ||
| but it is designed so new onboarding types are added without touching consumers. | ||
|
|
||
| ## Glossary | ||
|
|
||
| | Term | Definition | Not to be confused with | | ||
| | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | ||
| | **Onboarding** | One person's journey through one typed flow, scoped to a tenant. One row per `(tenant, user, type)`. Holds lifecycle `status`, not the per-step detail. | The UI wizard (presentation) — the Onboarding is the persisted state machine | | ||
| | **OnboardingType** | The discriminator enum (`Welcome`, `Squads`, …). Resolves the polymorphic behaviour via `handler(): OnboardingFlow` — same idiom as `IdentityProvider::getClient`. | A step — a type _has_ steps | | ||
|
danielhe4rt marked this conversation as resolved.
|
||
| | **OnboardingFlow** | The per-type handler (a class behind the `OnboardingFlow` contract). Declares `steps()`, `prerequisites()`, `advance()`, `isComplete()`. All type-specific rules live here. | The model — the Flow is stateless behaviour; the Onboarding is the state | | ||
| | **OnboardingStep** | One auditable stage of a flow, one row in `onboarding_steps`. Carries its own `status` + `data` (jsonb) + `completed_at`. Enables pause/resume and history. | A prerequisite (another _type_ that must be complete first) | | ||
| | **Prerequisite** | Another `OnboardingType` that must be **completed** before this one can start (e.g. `Squads` requires `Welcome`). Declared by the flow, enforced on start. | A step (intra-type) — a prerequisite is inter-type | | ||
| | **APTO** | Domain shorthand for "completed the `Squads` onboarding". The condition that unlocks squad candidacy and squad creation. It is _not_ a global flag — it is `Squads` completion. | A generic "active member" — APTO is specifically squad-eligible | | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| | **Challenge** | The Git step of the `Squads` flow: open a PR (with the mandatory template) on a `challenge` repo; a human reviewer approves it on GitHub. Curation happens entirely on GitHub. | A contribution (the gamification record) — challenge repos do **not** award XP | | ||
| | **Gate** | A pre-condition checked at a transition without being a step of its own. The `Squads` flow gates `git_challenge` on having a linked GitHub `ExternalIdentity`. | A step — a gate produces no `onboarding_steps` row | | ||
|
|
||
| ## State machine (shape) | ||
|
|
||
| `status` is a generic lifecycle: `in_progress` · `paused` · `completed` · `rejected`. The _steps_ are | ||
| type-specific and defined by the flow. The `Squads` flow: | ||
|
|
||
| ``` | ||
| [prereq: Welcome completed?] | ||
| │ yes | ||
| ▼ | ||
| step: form ──(submit, auto-advance, no curation)──► [gate: GitHub linked?] | ||
| │ no -> blocked + CTA "link GitHub" | ||
| │ yes | ||
| ▼ | ||
| step: git_challenge ──(PR approved on challenge repo)──► completed = APTO | ||
| ``` | ||
|
|
||
| Pause/resume is orthogonal: `status = paused` + `paused_at`, resumable from the current step. | ||
|
|
||
| ## Structure (proposed) | ||
|
|
||
| ``` | ||
| src/ | ||
| ├── Models/ ← Onboarding · OnboardingStep | ||
| ├── Enums/ ← OnboardingType · OnboardingStatus · OnboardingStepStatus | ||
| ├── Contracts/ ← OnboardingFlow | ||
| ├── Flows/ ← WelcomeOnboardingFlow · SquadsOnboardingFlow | ||
| ├── Actions/ ← StartOnboarding · AdvanceStep · PauseOnboarding · ResumeOnboarding | ||
| ├── DTOs/ ← per-step payload contracts (validated by the flow) | ||
| └── Listeners/ ← GithubPullRequestApproved -> advance the challenge step | ||
| ``` | ||
|
|
||
| ## Module Boundaries | ||
|
|
||
| ### This module owns: | ||
|
|
||
| - The onboarding state machines (one polymorphic model + steps) and their lifecycle. | ||
| - The per-type **completion** status other modules read as a gate (`Onboarding::isCompleted(user, tenant, type)`). | ||
| - The inter-type prerequisite chain. | ||
|
|
||
| ### This module does NOT own: | ||
|
|
||
| - Squad lifecycle, membership, governance — belongs to `squads` (a consumer of the gate). | ||
| - Any HTTP communication with GitHub or the raw event lake — belongs to `integration-github`. This | ||
| module **listens to** `GithubPullRequestApproved` and reads `GithubRepository` (`purpose = challenge`). | ||
| - GitHub account linking — belongs to `identity` (`ExternalIdentity`, provider `github`). | ||
|
|
||
| ## Dependencies | ||
|
|
||
| - **Identity** — `User`, tenant scoping, and the GitHub `ExternalIdentity` link (the `git_challenge` gate). | ||
| - **Integration GitHub** — consumes the `GithubPullRequestApproved` domain event and the `challenge` | ||
| repo allowlist. Never the reverse. | ||
| - Presentation (`panel-app`) drives the UI and calls the module's Actions. | ||
|
|
||
| See `docs/adr/0001-onboarding-polimorfico-por-tipo.md` and | ||
| `docs/adr/0002-sinal-de-pr-aprovado-via-evento-de-dominio.md`. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| { | ||
| "name": "he4rt/onboarding", | ||
| "description": "", | ||
| "type": "library", | ||
| "version": "1.0.0", | ||
| "license": "proprietary", | ||
| "autoload": { | ||
| "psr-4": { | ||
| "He4rt\\Onboarding\\": "src/", | ||
| "He4rt\\Onboarding\\Database\\Factories\\": "database/factories/", | ||
| "He4rt\\Onboarding\\Database\\Seeders\\": "database/seeders/" | ||
| } | ||
| }, | ||
| "autoload-dev": { | ||
| "psr-4": { | ||
| "He4rt\\Onboarding\\Tests\\": "tests/" | ||
| } | ||
| }, | ||
| "minimum-stability": "stable", | ||
| "extra": { | ||
| "laravel": { | ||
| "providers": [ | ||
| "He4rt\\Onboarding\\Providers\\OnboardingServiceProvider" | ||
| ] | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| # ADR-0001: Onboarding polimórfico por tipo, com etapas auditáveis | ||
|
|
||
| **Status:** Accepted | ||
| **Date:** 2026-06-15 | ||
| **Deciders:** danielhe4rt | ||
|
|
||
| ## Contexto | ||
|
|
||
| Os Squads da He4rt rodam hoje no informal (grupos de WhatsApp, sem liderança formal). A primeira | ||
| entrega de software ataca **governança** (capitão/subcapitão, eleição, etc.) e, antes dela, uma | ||
| camada de **Entrada** que filtra quem realmente quer integrar a comunidade e contribuir. | ||
|
|
||
| Essa Entrada — chamada no documento da P.O. de "pré-triagem" — é **universal e obrigatória**: | ||
| ninguém se candidata a um squad nem propõe um squad novo sem concluí-la. Duas perguntas de fronteira | ||
| apareceram: | ||
|
|
||
| 1. **Onde mora?** A pré-triagem é sobre pertencer à comunidade, não a um squad específico, e reusa | ||
| pesado o `identity` (vínculo GitHub via `ExternalIdentity`). Colocá-la dentro de `squads` | ||
| amarraria um conceito universal a um consumidor específico. | ||
|
danielhe4rt marked this conversation as resolved.
Outdated
|
||
| 2. **Quantos formatos?** O time já enxerga **mais de um tipo de entrada** — `Welcome` (entrada na | ||
| comunidade) e `Squads` (entrada no programa) — e quer outros no futuro, cada um com seu próprio | ||
| contrato de payload e processamento. | ||
|
|
||
| Modelar a pré-triagem como uma máquina de estados fixa (form → desafio) resolveria o `Squads` de | ||
| hoje, mas não comportaria novos tipos sem refator. | ||
|
|
||
| ## Decisão | ||
|
|
||
| **Criar um módulo de domínio novo, `onboarding`, dono de máquinas de onboarding polimórficas por | ||
| tipo.** `squads` (e futuros consumidores) apenas leem o gate de conclusão. | ||
|
|
||
| ### Polimorfismo (enum → handler) | ||
|
|
||
| - `OnboardingType` (enum) discrimina o tipo e resolve o comportamento via `handler(): OnboardingFlow` | ||
| — mesmo idioma que `IdentityProvider::getClient()` já usa no `identity`. | ||
| - `OnboardingFlow` (contrato) declara `steps()`, `prerequisites()`, `advance()`, `isComplete()`. | ||
| Toda regra específica do tipo vive no handler; nenhum consumidor conhece os tipos concretos. | ||
|
|
||
| ### Persistência (modelo + etapas) | ||
|
|
||
| Modelo único discriminado por `type` + tabela de etapas (opção "C" avaliada): | ||
|
|
||
| `onboardings` — uma linha por `(tenant_id, user_id, type)`: | ||
|
|
||
| | Coluna | Tipo | Notas | | ||
| | -------------- | ---------------------------- | --------------------------------------------------------------------- | | ||
| | `id` | uuid (PK) | `HasUuids` | | ||
| | `tenant_id` | uuid (FK) | tenant-scoped (convenção do repo, multi-tenant-ready) | | ||
| | `user_id` | uuid (FK) | | | ||
| | `type` | string | `OnboardingType` (`welcome` \| `squads` \| …) | | ||
| | `status` | string | ciclo de vida genérico: `in_progress`/`paused`/`completed`/`rejected` | | ||
| | `completed_at` | timestamptz? | | | ||
| | `paused_at` | timestamptz? | | | ||
| | timestamps | tz | | | ||
| | UNIQUE | `(tenant_id, user_id, type)` | | | ||
|
|
||
| `onboarding_steps` — uma linha por etapa do fluxo: | ||
|
|
||
| | Coluna | Tipo | Notas | | ||
| | --------------- | --------------------------- | ------------------------------------------------- | | ||
| | `id` | uuid (PK) | | | ||
| | `onboarding_id` | uuid (FK) | | | ||
| | `step_key` | string | semântica do handler (`form`, `git_challenge`, …) | | ||
| | `status` | string | `pending`/`done`/… | | ||
| | `data` | jsonb | payload da etapa, validado pelo DTO do tipo | | ||
| | `completed_at` | timestamptz? | | | ||
| | timestamps | tz | | | ||
| | UNIQUE | `(onboarding_id, step_key)` | | | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pra fins de auditoria de transicionamento e checks mais rapidos, nao valeria armazenar a transição de steps com uma coluna de ou eles foram desenhados pra serem "ever-forward" (flow nao é um grafo direcionado, apenas guarda estados)? |
||
| A tabela de etapas (em vez de só um `payload` JSON no modelo) foi escolhida por dar **auditoria e | ||
| histórico por etapa de graça** — relevante pro desafio Git, que tem reenvio com evolução e | ||
| pausa/retoma. | ||
|
|
||
| ### Cadeia entre tipos | ||
|
|
||
| `prerequisites()` declara dependências **inter-tipo**: `Squads` exige `Welcome` concluído para poder | ||
| iniciar. O gate que o `squads` consome é `Onboarding::isCompleted(user, tenant, Squads)` — apelidado | ||
| de **APTO** no domínio. | ||
|
|
||
| ## Alternativas consideradas | ||
|
|
||
| - **Pré-triagem dentro de `identity`** (membership): conceitualmente limpo, mas mistura onboarding | ||
| evolutivo com o núcleo de autenticação e força `identity` a depender de `integration-github`. | ||
| - **Pré-triagem dentro de `squads`**: entrega rápida, mas amarra um conceito universal a um consumidor | ||
| e exigiria migração quando outro módulo (eventos, etc.) quiser o mesmo gate. | ||
| - **STI / modelo por tipo**: Laravel não tem STI nativo (exige pacote/boilerplate), foge do idioma | ||
| `enum→resolve` do repo e incha o schema com colunas nuláveis por tipo. Descartado. | ||
|
danielhe4rt marked this conversation as resolved.
|
||
| - **Modelo único só com `payload` JSON (sem tabela de etapas)**: mais simples, mas perde auditoria | ||
| por etapa. É o passo anterior natural; promovido para a tabela de etapas por causa do desafio Git. | ||
|
|
||
| ## Consequências | ||
|
|
||
| ### Positivas | ||
|
|
||
| - Somar um tipo novo = +1 case no enum + 1 classe `Flow`. Consumidores intactos. | ||
| - Auditoria etapa-a-etapa nativa (início/fim de cada etapa, tentativas de reenvio). | ||
| - Pausa/retoma natural (estado vive na etapa + `status=paused`). | ||
| - Núcleo do jogo (futuro) pode virar mais um tipo, ou consumir o gate, sem acoplar. | ||
|
|
||
| ### Negativas / diferidas | ||
|
|
||
| - `status`/`step_key` são strings genéricas — a disciplina de transição fica no handler, não no banco. | ||
| - Dois lugares de verdade (`onboardings` + `onboarding_steps`); o `isComplete()` precisa ser a única | ||
| fonte que decide conclusão para não divergirem. | ||
| - Validação do payload é só de aplicação (DTO), não de schema. | ||
|
|
||
| ## Review trigger | ||
|
|
||
| Revisitar quando (a) o Núcleo do jogo for refinado e a gente decidir se ele é um `OnboardingType` ou | ||
| um consumidor do gate; ou (b) surgir um tipo cujo estado de etapa não caiba no par modelo+steps. | ||
Uh oh!
There was an error while loading. Please reload this page.