Merge pull request #433 from Hestia-Homes/docs/ara-projects-domain-language

docs(ara-projects): ADR-0018 + CONTEXT.md domain language (#405)
This commit is contained in:
Daniel Roth 2026-07-21 10:33:37 +01:00 committed by GitHub
commit 02f6dc265e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 116 additions and 2 deletions

View file

@ -119,17 +119,45 @@ _Avoid_: property query, segment, search (the postcode-search journey is unrelat
### Live projects
**Project**:
A delivery programme within a Portfolio — a batch of Properties taken through retrofit together — identified by its HubSpot **project code** (`hubspot_deal_data.project_code`; a stable **project id** will replace the code once the HubSpot ops board is settled, so the code is the *current* identifier, not the essence). A Property whose deal carries no project code sits outside every Project (the "Unknown Project" grouping) and cannot be reached by selecting Projects.
A delivery programme within a Portfolio — a batch of Properties taken through retrofit together — identified by its HubSpot **project code** (`hubspot_deal_data.project_code`; a stable **project id** will replace the code once the HubSpot ops board is settled, so the code is the *current* identifier, not the essence). A Property whose deal carries no project code sits outside every Project (the "Unknown Project" grouping) and cannot be reached by selecting Projects. Distinct from the **Ara Project** (next section) — the app-owned works programme that will eventually supersede this feature; where "Project" alone is ambiguous, say **HubSpot Project**. See [ADR-0018](./docs/adr/0018-ara-projects-supersedes-hubspot-live-projects.md).
_Avoid_: programme, scheme, deal group, batch (a **Batch** is a separate per-deal field)
**Bulk document download**:
An app-triggered, asynchronous assembly of a single ZIP of the documents held against a chosen set of Properties — picked as a row selection in the Documents tab, scoped by the tab's current Project/group — delivered as a time-limited link emailed to the requester (and, within the session, retrievable in-app). A selection that matches no documents produces no ZIP: it fails rather than emailing an empty archive. Properties are matched to their documents by **landlord property id**. See [ADR-0011](./docs/adr/0011-app-owned-task-marker-in-task-source.md).
_Avoid_: bulk export, document export, zip download
### Ara Projects
The works-management module at top-level `/projects` (schema in `src/app/db/schema/projects/`, merged in PR #404). The strategic successor to the HubSpot-sourced "Live projects" feature above — both coexist for now, with no migration in v1. See [ADR-0018](./docs/adr/0018-ara-projects-supersedes-hubspot-live-projects.md) and the schema's own docs at `src/app/db/schema/projects/schema.md`.
**Project** (Ara):
An organisation-owned works programme — the aggregate root of the Ara Projects module. Belongs to an `organisation` and is **many-to-many with Properties** (via `project_property`), unlike the HubSpot **Project** above (externally sourced, at most one per Property). Owns its **Project Workstreams**, which in turn own Stages, Evidence requirements and Contractor assignments. Where "Project" alone is ambiguous, say **Ara Project**.
_Avoid_: programme, scheme, works package
**Workstream**:
A category of works (Windows, Doors, Roofs, Electrical, Heating, …) — reference data in `workstream`, instantiated for one Ara Project as a **Project Workstream** (`project_workstream`), which owns its own Stage ladder, Evidence requirements and Contractor assignments.
_Avoid_: measure (Measure is taken by the Scenarios domain), trade, work type
**Stage**:
One step in a Project Workstream's ordered ladder (`project_workstream_stage`, positioned by `order`). Each Project Workstream owns its own ladder — different workstreams may follow different workflows. The v1 default ladder is Ordered / In progress / Completed / Charged / Closed, and the terminal stage is the one with the highest `order`. A Work order's stage FK (`work_order.project_workstream_stage_id`) is the **only lifecycle mechanism in v1**`work_order.status`, `work_order.priority` and `project_workstream.current_stage_id` exist in the schema but are untouched.
_Avoid_: status (the unused text column), phase, step
**Work order**:
One unit of work issued to a contractor: a (Property, Project Workstream Stage, contractor organisation, unique reference, forecast end date) row in `work_order`. Its position in the workflow is its Stage FK (see **Stage**).
_Avoid_: job, ticket, task (a **Task** is the BulkUpload orchestration handle)
**Evidence requirement** / **Evidence**:
An **Evidence requirement** (`project_workstream_evidence_requirement`) is configuration: a required `file_type` (the existing enum) per Project Workstream, optionally narrowed to a single Stage. **Evidence** is a submission: an `uploaded_files` row with `file_source = 'projects'`, linked to its Work order (`work_order_id`) and optionally to the requirement it satisfies. Evidence is **advisory-only in v1** — badges and counts, never a gate: it does not block stage transitions and there is no approval state.
_Avoid_: document (generic), attachment, proof
**Contractor**:
An `organisation` assigned to a Project Workstream via `project_workstream_contractor`, carrying per-assignment `update_stages` / `upload_documents` permission flags — not a separate entity or table. Contractor users sign in through the existing `team` / `team_members` structures (each organisation gets a default team).
_Avoid_: supplier, installer, vendor, subcontractor
### Tags
**Tag**:
A user-created, coloured label owned by a Portfolio, used to form an arbitrary **group** of its Properties for bulk actions (e.g. running Scenarios over the group, or excluding it from a Reporting view). Flat (no hierarchy) and many-to-many: a Property carries zero or more Tags, and a Tag labels zero or more Properties. Distinct from a **Project** (a HubSpot-sourced grouping, at most one per Property) and from a **Scenario** (a modelling question): a Tag is an app-owned, hand-curated selection with no external source and no modelling semantics of its own.
A user-created, coloured label owned by a Portfolio, used to form an arbitrary **group** of its Properties for bulk actions (e.g. running Scenarios over the group, or excluding it from a Reporting view). Flat (no hierarchy) and many-to-many: a Property carries zero or more Tags, and a Tag labels zero or more Properties. Distinct from a **HubSpot Project** (a HubSpot-sourced grouping, at most one per Property) and from a **Scenario** (a modelling question): a Tag is an app-owned, hand-curated selection with no external source and no modelling semantics of its own.
_Avoid_: label, category, group (the group is the *effect* of a Tag, not a separate entity), segment, tag group
**Bulk tag assignment**:

View file

@ -0,0 +1,86 @@
# 18. Ara Projects supersedes HubSpot Live projects, coexisting at top-level /projects
Date: 2026-07-21
## Status
Accepted
Numbering note: 00150017 are in flight on the reporting-redesign branch
(PR #416), alongside 0010; 0018 avoids collision with all of them (the same
convention as ADR-0013's numbering note).
## Context
PR #404 merged the schema for a new works-management module
(`src/app/db/schema/projects/`) whose aggregate root is called **Project**. But
"Project" is already taken in our domain language: CONTEXT.md ("Live projects")
and ADR-0013 define a Project as the HubSpot-sourced delivery grouping — at
most one per Property, identified by `hubspot_deal_data.project_code`, served
by the existing `/portfolio/[slug]/your-projects` feature.
The two are not one concept wearing two names — they differ on every axis:
- **HubSpot Project**: externally owned, read-only in the app, at most one per
Property, no internal structure beyond membership.
- **Ara Project**: app-owned, organisation-scoped, many-to-many with
Properties, and structured — it carries configuration (Workstreams, Stages,
Evidence requirements) and operational data (Contractor assignments, Work
orders).
Something had to give: rename the new module, converge the old feature into it
now, or run both under disambiguated names.
## Decision
- **Ara Projects is the strategic successor** to the HubSpot "Live projects"
feature (`your-projects`). Long-term, works delivery lives in Ara Projects
and the HubSpot grouping is retired.
- **Both coexist for now.** Convergence and data migration are explicitly out
of scope for v1 — `your-projects` keeps working unchanged, and no Ara
Projects work may depend on changing it. Retiring it gets its own ADR when
it is actually scheduled.
- **The route namespace is top-level `/projects`**, with its own layout — not
under the portfolio chrome. An Ara Project belongs to an `organisation` and
spans Properties without regard to Portfolio boundaries, so
`/portfolio/[slug]/...` is the wrong scope; it would also collide with the
very feature it supersedes.
- **Domain language is disambiguated in CONTEXT.md** (Ara Projects section):
where "Project" alone is ambiguous, qualify — **HubSpot Project** vs
**Ara Project**. The Ara Projects glossary terms (Workstream, Stage, Work
order, Evidence requirement, Evidence, Contractor) match
`src/app/db/schema/projects/schema.md` and are used verbatim by all Ara
Projects issues (#405#429).
- **ADR-0013 is unaffected**: its contrast of Tags with "Projects"
(HubSpot-owned, one-per-Property, not user-editable) refers to HubSpot
Projects and remains correct as written.
## Alternatives considered
- **Rename the new module** (Programmes, Works, Schemes). Avoids the clash
permanently, but the merged schema, the product and the business all say
"Project" — a rename trades a bounded period of ambiguity for a permanent
mismatch with the business vocabulary, and it protects the name of the
feature that is scheduled to disappear.
- **Converge now** — migrate `your-projects` data into Ara Projects in v1.
Removes the coexistence cost, but front-loads a migration out of an
externally-owned system before the new module has proven its shape, and
couples v1 delivery to HubSpot ops decisions (the project-code → project-id
switch is itself still pending on the HubSpot side).
- **Nest under the portfolio chrome** (`/portfolio/[slug]/projects`). Matches
where the old feature lives, but imposes a Portfolio scope the aggregate
doesn't have, and makes the old and new features near-indistinguishable in
the URL while both exist.
## Consequences
- Two meanings of "Project" coexist in code and conversation until
`your-projects` is retired. The mitigation is linguistic and enforced by
review: CONTEXT.md is canonical, and ambiguous uses get qualified.
- `/projects` becomes a reserved top-level namespace (it was unused before
this decision).
- Existing HubSpot-Project surfaces (the "Unknown Project" grouping, the
Documents tab's Project scoping for Bulk document download) continue to mean
HubSpot Projects; nothing there is renamed.
- The eventual retirement of `your-projects` will need its own ADR covering
data migration and user transition; nothing in v1 begins or blocks it.