---
id: DOM-CRM-ENT-002
title: Organization
realises: CON-CRM-ENT-002
product_area: crm
module: entities
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
owned_by: SVC-CRM-ENT-entities
lifecycle: STATE-CRM-ENT-002
custom_fields: declared
identity: >-
  An organization uses a workspace-scoped identity supplied by the client when
  unused, or generated by the service. Its staff-facing number comes from a
  separate organization counter beginning at 10000; allocation is
  service-controlled and spent numbers are not reused.
attributes:
  - name: id
    kind: text
    required: true
    computed: false
    set_by: integration
    rules: >-
      Unused workspace identity with the API identity grammar; generated when
      omitted.
    example: harbor_west
    notes: The continuing organization identity.
  - name: number
    kind: number
    required: true
    computed: true
    set_by: system
    rules: >-
      Allocated by the organization counter beginning at 10000; spent numbers
      are not reused.
    example: 10023
    notes: The staff-facing reference independent of person numbering.
  - name: name
    kind: text
    required: true
    computed: false
    set_by: staff
    rules: Nonblank trimmed text up to 200 characters.
    example: Harbor West Chapter
    notes: The organization’s recognized name.
  - name: acronym
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Optional trimmed text.
    example: HWC
    notes: A compact organizational label.
  - name: description
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Optional trimmed text.
    example: Western chapter of the engineering association.
    notes: An explanation of the body’s work.
  - name: notes
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: 'Trimmed optional text up to 10,000 characters.'
    example: Board meets on the first Tuesday.
    notes: Operational notes about this body.
  - name: status
    kind: enum
    required: true
    computed: false
    set_by: staff
    rules: Active by default; inactive and duplicate are also accepted.
    example: active
    notes: 'The body’s business status, independent of archive and compliance.'
  - name: organization type
    kind: reference
    required: false
    computed: false
    set_by: staff
    rules: Optional workspace reference; deletion of a referenced type is blocked.
    example: Chapter
    notes: The workspace classification supplying presentation and role policy.
  - name: parent organization
    kind: reference
    required: false
    computed: false
    set_by: staff
    rules: Workspace-local; cannot be self or create a descendant cycle.
    example: Harbor National
    notes: The organization immediately above this one.
  - name: ancestor organizations
    kind: computed
    required: false
    computed: true
    set_by: system
    rules: Derived on read from the current hierarchy.
    example:
      - Harbor National
    notes: The nearest-first chain of parent identities.
  - name: number of ancestors
    kind: number
    required: false
    computed: true
    set_by: system
    rules: 'Derived from the parent chain, rather than maintained on every descendant.'
    example: 1
    notes: The current depth below the root.
  - name: top-level organization
    kind: computed
    required: false
    computed: true
    set_by: system
    rules: Derived on read.
    example: Harbor National
    notes: The root of the current hierarchy.
  - name: website
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: >-
      Recognizable website or domain input can derive the other; unrecognized
      content remains input.
    example: 'https://harbor.example'
    notes: The organization’s web address.
  - name: domain
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Optional; can be derived from a recognizable website.
    example: harbor.example
    notes: The organization’s main internet domain.
  - name: alternate domains
    kind: collection
    required: false
    computed: false
    set_by: staff
    rules: Optional string list.
    example:
      - harborwest.example
    notes: Additional internet domains associated with this body.
  - name: logo
    kind: reference
    required: false
    computed: false
    set_by: staff
    rules: >-
      Platform file handling prepares a permanent copy before the record
      transaction.
    example: Chapter logo
    notes: The body’s display image.
  - name: flags
    kind: collection
    required: false
    computed: false
    set_by: staff
    rules: String list; does not itself confer authority.
    example:
      - regional-office
    notes: Optional labels retained on the organization.
  - name: tax exempt
    kind: enum
    required: false
    computed: false
    set_by: staff
    rules: Optional boolean; recording it is not a tax decision by this module.
    example: true
    notes: The stored tax-exemption indicator.
  - name: region
    kind: reference
    required: false
    computed: true
    set_by: system
    rules: Regions assigns it unless an explicit override is selected.
    example: Western Region
    notes: The regional grouping assigned from the deciding address.
  - name: override automatic region assignment
    kind: enum
    required: false
    computed: false
    set_by: staff
    rules: Optional boolean.
    example: true
    notes: Whether an explicit regional assignment takes precedence.
  - name: Facebook
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Optional trimmed text.
    example: 'https://www.facebook.com/harborwest'
    notes: The body’s recorded social-profile reference.
  - name: Twitter
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Optional trimmed text.
    example: '@HarborWest'
    notes: The body’s recorded social-profile reference.
  - name: LinkedIn
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Optional trimmed text.
    example: 'https://www.linkedin.com/company/harbor-west'
    notes: The body’s recorded social-profile reference.
  - name: profile last updated
    kind: date
    required: false
    computed: false
    set_by: staff
    rules: Optional ISO timestamp.
    example: '2026-10-20T14:00:00Z'
    notes: The stored profile review time.
  - name: addresses
    kind: collection
    required: false
    computed: false
    set_by: staff
    rules: >-
      One row per type and at most one preferred; linked contact rows can
      resolve these values.
    example: 'Headquarters: 42 Harbor Avenue, Oakland, CA 94612, US'
    notes: Organization-owned typed postal sources.
  - name: phone numbers
    kind: collection
    required: false
    computed: false
    set_by: staff
    rules: One row per type and at most one preferred.
    example: 'Main: +1 510 555 0100'
    notes: Organization-owned typed telephone sources.
  - name: custom field values
    kind: collection
    required: false
    computed: false
    set_by: staff
    rules: Active organization definitions govern their declared data types.
    example: 'Chapter charter date: 1998-06-01'
    notes: Customer-specific extensions using Platform definitions.
  - name: role compliance status
    kind: enum
    required: false
    computed: true
    set_by: system
    rules: >-
      Evaluator owns in compliance, not in compliance or absent; ordinary
      submitted values are ignored.
    example: not in compliance
    notes: The last stored assessment of the type’s role coverage.
  - name: compliance violations
    kind: collection
    required: false
    computed: true
    set_by: system
    rules: 'Evaluator stores the role, configured bound and distinct holder count.'
    example: 'Billing Contact: minimum 1, actual 0'
    notes: Ordered explanations of failed role-count bounds.
  - name: version
    kind: number
    required: true
    computed: true
    set_by: system
    rules: >-
      System advances it for a committed aggregate change; the caller echoes its
      read version.
    example: 7
    notes: The basis against which a later correction is compared.
  - name: lock
    kind: enum
    required: false
    computed: true
    set_by: system
    rules: >-
      Dedicated lock actions choose no lock, deletes, or updates & deletes;
      ordinary saves retain it.
    example: deletes
    notes: A customer restriction on mutation of this record.
  - name: created at
    kind: date
    required: true
    computed: true
    set_by: system
    rules: System timestamp; unchanged by later corrections.
    example: '2026-10-20T14:00:00Z'
    notes: When this identity is first stored.
  - name: created by
    kind: reference
    required: true
    computed: true
    set_by: system
    rules: System attribution; a read can resolve its current display name.
    example: Association coordinator
    notes: The actor responsible for creation.
  - name: modified at
    kind: date
    required: true
    computed: true
    set_by: system
    rules: A current-version no-op preserves this timestamp.
    example: '2026-10-23T15:30:00Z'
    notes: When the latest net change commits.
  - name: modified by
    kind: reference
    required: true
    computed: true
    set_by: system
    rules: >-
      Recorded with the write context; system work can invalidate an open
      editor.
    example: CRM system
    notes: The actor or system responsible for the latest change.
  - name: archived at
    kind: date
    required: false
    computed: true
    set_by: system
    rules: Only archive and unarchive actions control the timestamp.
    example: '2026-10-23T15:30:00Z'
    notes: Whether ordinary discovery and editing exclude this identity.
  - name: archived by
    kind: reference
    required: false
    computed: true
    set_by: system
    rules: Captured by the archive action and cleared on restoration.
    example: CRM administrator
    notes: Who sets this record aside.
  - name: archive reason
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Optional trimmed reason limited to 500 characters.
    example: Firm closed
    notes: The explanation for setting the record aside.
  - name: unarchive reason
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Optional trimmed reason limited to 500 characters.
    example: Firm reopened
    notes: The explanation for making the record available again.
  - name: organization addresses / address_type_id
    kind: text
    required: true
    computed: false
    set_by: staff
    rules: >-
      The workspace address purpose, subject to one-per-type and applicability
      validation.
    example: business
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-addresses.table.ts'
  - name: organization addresses / line1
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: First postal delivery line.
    example: 410 Association Way
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-addresses.table.ts'
  - name: organization addresses / line2
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Second postal delivery line.
    example: Suite 200
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-addresses.table.ts'
  - name: organization addresses / line3
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Additional postal delivery context.
    example: Member Services
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-addresses.table.ts'
  - name: organization addresses / city
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: City in the postal value.
    example: Columbus
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-addresses.table.ts'
  - name: organization addresses / state
    kind: date
    required: false
    computed: false
    set_by: staff
    rules: State or province in the postal value.
    example: OH
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-addresses.table.ts'
  - name: organization addresses / postal_code
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Postal routing code.
    example: '43215'
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-addresses.table.ts'
  - name: organization addresses / county
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: County supplied with the postal value.
    example: Franklin
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-addresses.table.ts'
  - name: organization addresses / country
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Country code; storage defaults to US.
    example: US
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-addresses.table.ts'
  - name: organization addresses / latitude
    kind: date
    required: false
    computed: false
    set_by: staff
    rules: Optional latitude supplied with the postal value.
    example: '39.96'
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-addresses.table.ts'
  - name: organization addresses / longitude
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Optional longitude supplied with the postal value.
    example: '-83.00'
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-addresses.table.ts'
  - name: organization addresses / time_zone_id
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Optional time-zone identity associated with the location.
    example: America/New_York
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-addresses.table.ts'
  - name: organization addresses / is_preferred
    kind: boolean
    required: true
    computed: false
    set_by: staff
    rules: Preferred selection within this owner’s collection.
    example: 'true'
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-addresses.table.ts'
  - name: organization phone numbers / phone_number_type_id
    kind: text
    required: true
    computed: false
    set_by: staff
    rules: >-
      The workspace telephone purpose, subject to one-per-type and applicability
      validation.
    example: direct
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-phone-numbers.table.ts'
  - name: organization phone numbers / phone_number
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: >-
      The telephone value; linked contact entries obtain their effective value
      from the organization source.
    example: +1 614 555 0100
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-phone-numbers.table.ts'
  - name: Organization type / id
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Workspace classification identity used by the organization.
    example: chapter
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
  - name: Organization type / name
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Classification label shown to staff.
    example: Chapter
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
  - name: Organization type / is_active
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Availability for new selection without erasing retained classification.
    example: 'true'
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
  - name: Organization type / display_order
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: 'Optional ordering among classifications, with alphabetical fallback.'
    example: '2'
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
  - name: Organization type / restrict_contact_roles
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Whether the allowed-role selection constrains assigned responsibilities.
    example: 'true'
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
  - name: Organization type / applicable_contact_role_ids
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Workspace-local responsibilities selected for a restricted classification.
    example: 'President, Treasurer'
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
  - name: Organization type / enable_contact_role_compliance
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: >-
      Whether organizations of this classification have applicable role
      assessment.
    example: 'true'
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
  - name: Organization type / has_contact_role_compliance_options
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Stored indicator accompanying configured policy options.
    example: 'true'
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
  - name: Compliance rule / role
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: The responsibility whose effective people are counted.
    example: Treasurer
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
  - name: Compliance rule / minimum
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: 'Optional lower bound, validated with the other bound.'
    example: '1'
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
  - name: Compliance rule / maximum
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Optional upper bound; zero can express that no holder is allowed.
    example: '2'
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
  - name: Organization type / notify_contacts_of_compliance_violation
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Whether a transition can prepare compliance communication.
    example: 'true'
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
  - name: Organization type / notification_contact_role_ids
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: >-
      Selected recipient responsibilities; absent and explicitly empty
      selections remain distinct.
    example: President
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
  - name: Organization type / template reference
    kind: text
    required: false
    computed: false
    set_by: staff
    rules: Platform-owned notification content referenced by the classification.
    example: Chapter compliance notice
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
relationships:
  - to: DOM-CRM-ENT-002
    cardinality: 'one-to-many (parent to children), acyclic'
    ownership: The child stores and owns its parent reference.
    cross_service: false
    on_archive_or_delete: Archive preserves hierarchy; a parent with children cannot be deleted.
  - to: DOM-CRM-ENT-003
    cardinality: one-to-many
    ownership: Affiliations belong to contacts and reference this organization.
    cross_service: false
    on_archive_or_delete: >-
      Archive preserves ties; any affiliation blocks organization deletion
      regardless of its period.
  - to: DOM-CRM-ENT-001
    cardinality: >-
      one-to-many links from contacts' addresses and phone numbers to this
      organization's details
    ownership: >-
      Contacts own their references; removal of an organization detail
      disconnects those references.
    cross_service: false
    on_archive_or_delete: >-
      Source removal disconnects linked values atomically; affiliation
      dependencies ordinarily prevent deleting the organization.
  - to: DOM-CRM-ENT-033
    cardinality: >-
      one-to-one safeguards per organization, one-to-many revisions and lock
      changes
    ownership: >-
      The shared safeguard model governs protection and revisions for this
      organization.
    cross_service: false
    on_archive_or_delete: >-
      Archive retains safeguards and revisions; ordinary deletion retains
      revision evidence.
invariants:
  - id: INV-DOM-CRM-ENT-002-01
    statement: Organization names require nonblank text within the 200-character limit.
    enforced_at:
      - api
      - service
      - database
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
      a name of 201 characters, naming name
    protects: An intelligible organization identity in staff-facing lists.
  - id: INV-DOM-CRM-ENT-002-02
    statement: >-
      Organization numbers use their own allocation sequence, independent of
      contacts, without recycling spent values.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#renumbers
      after the tenant's highest organization number, never its contacts'
    protects: Independent and stable organization references.
  - id: INV-DOM-CRM-ENT-002-03
    statement: >-
      References to organization type and parent must resolve inside the same
      workspace.
    enforced_at:
      - database
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
      409 for a parent the tenant does not have
    protects: Workspace-local classification and hierarchy references.
  - id: INV-DOM-CRM-ENT-002-04
    statement: >-
      Parent references must remain acyclic, including competing opposite
      hierarchy moves.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/db/organizations.table.integration.spec.js#refuses
      the second move by organizations_parent_not_descendant once the first
      commits
    protects: A traversable hierarchy with no self-containing chain.
  - id: INV-DOM-CRM-ENT-002-05
    statement: 'The stored status is restricted to active, inactive or duplicate.'
    enforced_at:
      - database
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/db/organizations.table.integration.spec.js#refuses
      a status outside the closed list by organizations_status_check
    protects: Consistent organizational status interpretation.
  - id: INV-DOM-CRM-ENT-002-06
    statement: >-
      Only compliance recalculation can change status and violations; ordinary
      organization writes disregard submitted compliance values.
    enforced_at:
      - service
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/organization-compliance.e2e.spec.js#keep
      them against a PUT that sends others back
    protects: An assessment that clients cannot fabricate by saving a desired outcome.
  - id: INV-DOM-CRM-ENT-002-07
    statement: >-
      Recalculation clears compliance values when the organization lacks a type
      with enabled policy.
    enforced_at:
      - service
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/organizations/compliance/organization-compliance.integration.spec.js#with
      a type that holds it to no rule, or with no type: no status and no
      violations
    protects: The distinction between meeting policy and having no applicable policy.
  - id: INV-DOM-CRM-ENT-002-08
    statement: >-
      A stable key deduplicates each recipient batch submitted for a compliance
      transition. Submission and email delivery are separate outcomes.
    enforced_at:
      - service
      - database
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-email.ts:complianceIdempotencyKey
    protects: Retry-safe notice submission without claiming successful delivery.
  - id: INV-DOM-CRM-ENT-002-09
    statement: The workspace cannot have more than one running compliance reprocess.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/compliance-reprocesses.integration.spec.js#refuses
      a second running run for the tenant
    protects: A single durable scan cursor for a workspace reprocess.
  - id: INV-DOM-CRM-ENT-002-10
    statement: >-
      Deletion is refused while any child organization or affiliation references
      the organization, regardless of the affiliation dates.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
      409 to a delete while the contact is affiliated with it
    protects: >-
      Retained references from children and people instead of dangling
      organizational context.
  - id: INV-DOM-CRM-ENT-002-11
    statement: >-
      Compliance-rule roles must resolve in the workspace. Enabled restrictions
      require a selection in the service and a nonempty selection at the API.
    enforced_at:
      - api
      - service
      - database
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/organization-types.ts:organizationTypeProblems
    protects: Applicable role policy whose referenced responsibilities actually exist.
  - id: INV-DOM-CRM-ENT-002-12
    statement: >-
      A referenced organization type cannot be deleted while an organization
      uses it.
    enforced_at:
      - database
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/organization-types.e2e.spec.js#refuses
      an organization of a type the tenant does not have (409), and the delete
      of a type an organization has (409)
    protects: Meaningful classification for organizations that still use it.
physical:
  - organizations
  - organization_addresses
  - organization_phone_numbers
  - organization_custom_field_values
  - organization_compliance_violations
  - organization_types
  - organization_type_compliance_rules
  - compliance_reprocesses
  - organization_change_events
  - compliance_reprocess_change_events
  - organizations_for_queries
  - managed_assets
evidence:
  - >-
    services-crm at 7ea48a41: modules/entities/db/organizations.table.ts,
    organization-addresses.table.ts, organization-phone-numbers.table.ts,
    organization-compliance-violations.table.ts, organization-types.table.ts,
    compliance-reprocesses.table.ts, organization-domain-name.ts;
    services/persistence/organizations.ts (deleteOrganization,
    deleteOrganizationRow), organization-types.ts;
    services/organizations/compliance/*.ts; services/organizations/ancestry/;
    services/templates/compliance-notification-template.ts;
    metadata/organizations.object.ts (capabilities);
    attention/organizations-out-of-compliance.attention.ts.
  - >-
    services-platform at ad3f6a65: docs/standards/custom-field-capability.md §2
    (CRM organizations carry custom fields).
permissions:
  - action: read
    permission: crm.organizations.read
    roles: [Administrator, Power User, Standard User, Read Only]
  - action: create
    permission: crm.organizations.create
    roles: [Administrator, Power User, Standard User]
  - action: update
    permission: crm.organizations.update
    roles: [Administrator, Power User, Standard User]
    by_state: >-
      Ordinary update refuses archived or fully locked records; archive actions
      also check full locks.
  - action: archive
    permission: crm.organizations.archive
    roles: [Administrator, Power User]
    by_state: >-
      Ordinary update refuses archived or fully locked records; archive actions
      also check full locks.
  - action: delete
    permission: crm.organizations.delete
    roles: [Administrator, Power User]
  - action: lock
    permission: crm.organizations.lock
    roles: [Administrator, Power User]
  - action: unlock
    permission: crm.organizations.unlock
    roles: [Administrator, Power User]
  - action: history
    permission: crm.organizations.history
    roles: [Administrator, Power User, Standard User, Read Only]
narrative:
  status: verified
  review: NARRATIVE-REVIEW-2026-10-23
narrative_sources:
  - section: What It Models
    provenance: observed
    source: 'services-crm:modules/entities/services/persistence/organizations.ts'
  - section: Identity and Attributes
    provenance: observed
    source: 'services-crm:modules/entities/services/persistence/organizations.ts'
  - section: Relationships
    provenance: observed
    source: 'services-crm:modules/entities/services/persistence/organizations.ts'
  - section: Lifecycle
    provenance: observed
    source: 'services-crm:modules/entities/services/persistence/organizations.ts'
  - section: How It Is Stored and Shared
    provenance: observed
    source: 'services-crm:modules/entities/services/persistence/organizations.ts'
  - section: Permissions
    provenance: documented
    source: 'rhythm-v2-docs:specs/crm/AUTHORIZATION.md#Permission Catalogue'
  - section: Invariants
    provenance: inferred
    source: 'services-crm:modules/entities/services/persistence/organizations.ts'
    note: >-
      Protection explanations interpret the existing invariant rows and
      enforcement boundaries; no new product decision is claimed.
  - section: What It Models
    provenance: inferred
    source: 'services-crm:modules/entities/services/persistence/organizations.ts'
    note: >-
      The explicitly labeled aggregate rationale follows the object boundary and
      its owned records.
  - section: Open Design Questions
    provenance: documented
    source: 'rhythm-v2-docs:specs/crm/entities/FINDINGS.md#Findings'
  - section: Identity and Attributes
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-addresses.table.ts'
  - section: Identity and Attributes
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-phone-numbers.table.ts'
  - section: Identity and Attributes
    provenance: observed
    source: 'services-crm:modules/entities/db/organization-types.table.ts'
  - section: Invariants
    provenance: inferred
    source: "services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts; services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts; services-crm:modules/entities/services/persistence/record-locks.ts"
    note: "The rationale interprets this artifact’s sourced invariants and linked capability rules; it does not claim a recorded product decision."
---

# Organization

## What It Models

An organization models a body that has an identity independent of its representatives: a member firm, chapter, school or national association. It owns organizational information and can provide shared contact-point sources to people. Harbor West Chapter remains the same body when its billing representative leaves.

Our reading is that independent identity keeps hierarchy and policy intelligible as people change. The organization’s type supplies allowed responsibilities, compliance expectations and presentation overrides, while its parent supplies structure. These are different concerns: organizational ancestry does not give staff security access, and CRM classification does not create financial or membership rights.

## Identity and Attributes

An organization uses a workspace-scoped identity supplied by the client when unused, or generated by the service. Its staff-facing number comes from a separate organization counter beginning at 10000; allocation is service-controlled and spent numbers are not reused.

The table describes logical properties. Optional values can be absent; a required collection member is distinct from requiring the collection itself. Examples illustrate association information and do not establish new defaults. Service-owned values remain separate from fields that staff or integrations supply.

| Property | Meaning and writer | Constraints | Example |
|---|---|---|---|
| id | The continuing organization identity. Set by integration. | Unused workspace identity with the API identity grammar; generated when omitted. | harbor_west |
| number | The staff-facing reference independent of person numbering. Set by system. | Allocated by the organization counter beginning at 10000; spent numbers are not reused. | 10023 |
| name | The organization’s recognized name. Set by staff. | Nonblank trimmed text up to 200 characters. | Harbor West Chapter |
| acronym | A compact organizational label. Set by staff. | Optional trimmed text. | HWC |
| description | An explanation of the body’s work. Set by staff. | Optional trimmed text. | Western chapter of the engineering association. |
| notes | Operational notes about this body. Set by staff. | Trimmed optional text up to 10,000 characters. | Board meets on the first Tuesday. |
| status | The body’s business status, independent of archive and compliance. Set by staff. | Active by default; inactive and duplicate are also accepted. | active |
| organization type | The workspace classification supplying presentation and role policy. Set by staff. | Optional workspace reference; deletion of a referenced type is blocked. | Chapter |
| parent organization | The organization immediately above this one. Set by staff. | Workspace-local; cannot be self or create a descendant cycle. | Harbor National |
| ancestor organizations | The nearest-first chain of parent identities. Set by system. | Derived on read from the current hierarchy. | ["Harbor National"] |
| number of ancestors | The current depth below the root. Set by system. | Derived from the parent chain, rather than maintained on every descendant. | 1 |
| top-level organization | The root of the current hierarchy. Set by system. | Derived on read. | Harbor National |
| website | The organization’s web address. Set by staff. | Recognizable website or domain input can derive the other; unrecognized content remains input. | https://harbor.example |
| domain | The organization’s main internet domain. Set by staff. | Optional; can be derived from a recognizable website. | harbor.example |
| alternate domains | Additional internet domains associated with this body. Set by staff. | Optional string list. | ["harborwest.example"] |
| logo | The body’s display image. Set by staff. | Platform file handling prepares a permanent copy before the record transaction. | Chapter logo |
| flags | Optional labels retained on the organization. Set by staff. | String list; does not itself confer authority. | ["regional-office"] |
| tax exempt | The stored tax-exemption indicator. Set by staff. | Optional boolean; recording it is not a tax decision by this module. | true |
| region | The regional grouping assigned from the deciding address. Set by system. | Regions assigns it unless an explicit override is selected. | Western Region |
| override automatic region assignment | Whether an explicit regional assignment takes precedence. Set by staff. | Optional boolean. | true |
| Facebook | The body’s recorded social-profile reference. Set by staff. | Optional trimmed text. | https://www.facebook.com/harborwest |
| Twitter | The body’s recorded social-profile reference. Set by staff. | Optional trimmed text. | @HarborWest |
| LinkedIn | The body’s recorded social-profile reference. Set by staff. | Optional trimmed text. | https://www.linkedin.com/company/harbor-west |
| profile last updated | The stored profile review time. Set by staff. | Optional ISO timestamp. | 2026-10-20T14:00:00Z |
| addresses | Organization-owned typed postal sources. Set by staff. | One row per type and at most one preferred; linked contact rows can resolve these values. | Headquarters: 42 Harbor Avenue, Oakland, CA 94612, US |
| phone numbers | Organization-owned typed telephone sources. Set by staff. | One row per type and at most one preferred. | Main: +1 510 555 0100 |
| custom field values | Customer-specific extensions using Platform definitions. Set by staff. | Active organization definitions govern their declared data types. | Chapter charter date: 1998-06-01 |
| role compliance status | The last stored assessment of the type’s role coverage. Set by system. | Evaluator owns in compliance, not in compliance or absent; ordinary submitted values are ignored. | not in compliance |
| compliance violations | Ordered explanations of failed role-count bounds. Set by system. | Evaluator stores the role, configured bound and distinct holder count. | Billing Contact: minimum 1, actual 0 |
| version | The basis against which a later correction is compared. Set by system. | System advances it for a committed aggregate change; the caller echoes its read version. | 7 |
| lock | A customer restriction on mutation of this record. Set by system. | Dedicated lock actions choose no lock, deletes, or updates & deletes; ordinary saves retain it. | deletes |
| created at | When this identity is first stored. Set by system. | System timestamp; unchanged by later corrections. | 2026-10-20T14:00:00Z |
| created by | The actor responsible for creation. Set by system. | System attribution; a read can resolve its current display name. | Association coordinator |
| modified at | When the latest net change commits. Set by system. | A current-version no-op preserves this timestamp. | 2026-10-23T15:30:00Z |
| modified by | The actor or system responsible for the latest change. Set by system. | Recorded with the write context; system work can invalidate an open editor. | CRM system |
| archived at | Whether ordinary discovery and editing exclude this identity. Set by system. | Only archive and unarchive actions control the timestamp. | 2026-10-23T15:30:00Z |
| archived by | Who sets this record aside. Set by system. | Captured by the archive action and cleared on restoration. | CRM administrator |
| archive reason | The explanation for setting the record aside. Set by system. | Optional trimmed reason limited to 500 characters. | Firm closed |
| unarchive reason | The explanation for making the record available again. Set by system. | Optional trimmed reason limited to 500 characters. | Firm reopened |

### Detail collection properties

Each entry belongs to its aggregate and has its own identity and system lock/stamps. These child properties explain the exposed value and source fields without creating independent domain objects. Organization values supply linked contact values only through the verified affiliation and source references.

| Property | Meaning | Example |
|---|---|---|
| organization addresses / address_type_id | The workspace address purpose, subject to one-per-type and applicability validation. | business |
| organization addresses / line1 | First postal delivery line. | 410 Association Way |
| organization addresses / line2 | Second postal delivery line. | Suite 200 |
| organization addresses / line3 | Additional postal delivery context. | Member Services |
| organization addresses / city | City in the postal value. | Columbus |
| organization addresses / state | State or province in the postal value. | OH |
| organization addresses / postal_code | Postal routing code. | 43215 |
| organization addresses / county | County supplied with the postal value. | Franklin |
| organization addresses / country | Country code; storage defaults to US. | US |
| organization addresses / latitude | Optional latitude supplied with the postal value. | 39.96 |
| organization addresses / longitude | Optional longitude supplied with the postal value. | -83.00 |
| organization addresses / time_zone_id | Optional time-zone identity associated with the location. | America/New_York |
| organization addresses / is_preferred | Preferred selection within this owner’s collection. | true |
| organization phone numbers / phone_number_type_id | The workspace telephone purpose, subject to one-per-type and applicability validation. | direct |
| organization phone numbers / phone_number | The telephone value; linked contact entries obtain their effective value from the organization source. | +1 614 555 0100 |

### Policy and audit properties

These properties belong to the catalogue or safeguard facet named in the property column. They explain the implementation’s exposed policy and audit context without allocating another domain object. System attribution is read-only; catalogue policy is administered through its own permitted operation and is not changed by an ordinary record edit.

| Property | Meaning | Example |
|---|---|---|
| Organization type / id | Workspace classification identity used by the organization. | chapter |
| Organization type / name | Classification label shown to staff. | Chapter |
| Organization type / is_active | Availability for new selection without erasing retained classification. | true |
| Organization type / display_order | Optional ordering among classifications, with alphabetical fallback. | 2 |
| Organization type / restrict_contact_roles | Whether the allowed-role selection constrains assigned responsibilities. | true |
| Organization type / applicable_contact_role_ids | Workspace-local responsibilities selected for a restricted classification. | President, Treasurer |
| Organization type / enable_contact_role_compliance | Whether organizations of this classification have applicable role assessment. | true |
| Organization type / has_contact_role_compliance_options | Stored indicator accompanying configured policy options. | true |
| Compliance rule / role | The responsibility whose effective people are counted. | Treasurer |
| Compliance rule / minimum | Optional lower bound, validated with the other bound. | 1 |
| Compliance rule / maximum | Optional upper bound; zero can express that no holder is allowed. | 2 |
| Organization type / notify_contacts_of_compliance_violation | Whether a transition can prepare compliance communication. | true |
| Organization type / notification_contact_role_ids | Selected recipient responsibilities; absent and explicitly empty selections remain distinct. | President |
| Organization type / template reference | Platform-owned notification content referenced by the classification. | Chapter compliance notice |

## Relationships

A child owns one optional parent reference, giving each parent potentially many children. The live chain must remain acyclic. A parent with children cannot be hard-deleted; archiving keeps that chain and its labels available. Reads derive ancestry rather than treating a parent move as a write to every descendant.

Affiliations are contact-owned and reference this organization. Any such reference blocks hard deletion regardless of dates. Organization addresses and phones can supply many linked contacts. Source removal disconnects those details in the removal transaction and records each affected person’s change. Protection and history remain attached to the organization, with revisions retained after ordinary deletion.

## Lifecycle

The organization lifecycle separates active, inactive and duplicate business status from archive. A closed chapter can be archived while retaining its previous status, hierarchy and past representatives. Restoring it clears the archive overlay rather than inventing a new status or recreating its identity.

Compliance has a separate evaluator-owned lifecycle: no applicable policy, in compliance, or not in compliance. Changing type policy does not itself refresh every saved organization. Hard deletion checks all dependents, locks and protection and removes owned values only when those checks permit it.

## Invariants

The organization’s continuing identity holds the hierarchy and people’s ties together while staff correct its name or classification. References must stay inside the workspace. A parent chain containing the organization itself cannot be traversed meaningfully, so hierarchy validation protects both the immediate move and the ancestry returned to readers. Deleting a referenced organization would remove the context for a child or a person’s employment history; the dependency boundary prevents that result.

Compliance belongs to the evaluator rather than an editable assertion by staff. A saved satisfactory result and an absence of applicable policy answer different questions. Type policy supplies the required roles; the evaluator examines people and dated responsibilities and stores the resulting violations. Keeping that authority separate prevents a profile save from making an unmet requirement appear satisfied.

Notice identity protects accepted submissions against repetition of the same transition. It does not prove delivery. Reprocessing likewise keeps one active scan cursor per workspace so concurrent workers cannot independently claim the same progress. A completed scan establishes that its stored work reached the end, while failed delivery or later policy changes remain separate concerns. Type references and role-policy dependencies keep the evaluator’s definitions meaningful rather than leaving labels that no longer identify a usable classification.

## Permissions

The CRM authorization model separates workspace access from permission for a particular action.

The service checks these grants at the corresponding operation boundary. Additional record guards still apply: grants do not bypass archive, customer restrictions, dependencies, version comparison or Rhythm protection. Read access and history access are separate; record history checks the applicable read authority as well as the history grant.

## How It Is Stored and Shared

Entities owns the organization row, postal and telephone children, custom values and calculated violation rows. The organization save is the local transaction boundary. When a source detail disappears, database triggers disconnect linked people and capture their changes in that same transaction. Returned ancestry is assembled from the current parent chain.

Platform owns custom definitions, forms, layouts, files, notice templates and sending. Search and region consumers follow captured events; compliance notices have their own submission and delivery stages. A caller cannot infer refreshed search or delivered mail from the organization save response alone.

## Open Design Questions

Organization creation does not have the contact create’s replay recognition. The live organization page has no general editor and no organization revision-history entry, despite available service operations. Compliance reevaluation after a type change depends on a later trigger or reprocess. Those implementation limits remain in the existing findings.
