# Worked example: a filled brief for a fictional company.
# Acme Analytics is invented. Numbers, names and IDs are illustrative only.

release: CRM foundation v1
portal: Acme Analytics production portal
business_owner: Head of Revenue
hubspot_admin: RevOps lead
integration_owner: Data engineer
privacy_owner: Operations director
target_launch: after all gates pass

first_release_workflows:
  - id: wf1
    statement: A sales rep needs every valid demo request from a new prospect within 15 business minutes, with account context
    trigger: Successful submission of the demo request form
    population: Contacts with a business email whose company has no active subscription
    exclusions: Existing customers, partners, test records. An import does not submit the form, so it cannot meet the trigger; while a batch runs the workflow also excludes that batch's import_batch_id, and the exclusion is removed once the batch is reconciled. Never Record Source, which would drop later requests from the 4,200 imported contacts.
    owner: Sales rep chosen by rotation, or the existing company owner
    required_data: [email, company, acme_account_id, active_workspace_count, subscription_status]
    expected_response: Rep sets lead status to accepted or records a rejection reason
    failure_path: Contact owner set to the duty rep, routing_status = needs_review
    must_not_happen: An existing customer enters the new-prospect sequence
  - id: wf2
    statement: An account manager needs current billing and product state on the company before a renewal call
    trigger: Nightly warehouse sync
    population: Companies with acme_account_id
    exclusions: Internal test accounts
    owner: Account manager
    required_data: [subscription_status, recurring_revenue_monthly, next_renewal_at, active_workspace_count, first_value_at, product_refreshed_at]
    expected_response: Values no older than 36 hours
    failure_path: Companies with stale product_refreshed_at appear in a review view
    must_not_happen: A failed sync makes an active customer look inactive

capability_register:
  pricing_programme: hub-based seats
  checked_on: 2026-10-17
  checked_where: Order form and Data Model Limits tab
  items:
    - outcome: Rotate demo requests among reps
      feature: Rotate record to owner workflow action
      requires: Sales Hub Professional, paid Sales seats for each rep
      confirmed: yes
      fallback: Duty rep assigns manually
    - outcome: One record per product workspace
      feature: Custom objects
      requires: Enterprise under the standard documentation
      confirmed: no
      fallback: Company summaries (active_workspace_count, first_value_at); workspace detail stays in the warehouse; cannot act on single workspaces in HubSpot
    - outcome: Admin and buyer roles on the same company
      feature: Custom association labels
      requires: Professional or Enterprise
      confirmed: yes
      fallback: none needed
    - outcome: Stop reps editing synced billing fields
      feature: Property view and edit restrictions
      requires: Enterprise
      confirmed: no
      fallback: Register owner per property and change requests to RevOps; the warehouse sync rewrites billing fields nightly, and a weekly view lists billing fields last changed by a user
    - outcome: Rehearse in a copy of production
      feature: Standard sandbox
      requires: Enterprise
      confirmed: no
      fallback: Tagged batch of 25 records in production with workflows off
    - outcome: Undo a bad import or sync
      feature: Restore CRM changes
      requires: Starter or above, Super Admin
      confirmed: yes
      fallback: Before-state exports after the 14-day window
    - outcome: Deal source on the pipeline report
      feature: Deal create attribution report
      requires: Marketing Hub Enterprise
      confirmed: no
      fallback: deal_original_source snapshot property set when the deal is created
    - outcome: AI enrichment step in routing
      feature: Credit-consuming data action
      requires: HubSpot Credits
      confirmed: deferred
      fallback: Not in v1; routing uses only synced fields

account_settings:
  time_zone: America/New_York
  company_currency: USD
  additional_currencies: []
  external_tracking_domains: [www.example.com, app.example.com]
  standard_sandbox: not entitled
  company_auto_association: off. Companies are keyed on acme_account_id and two accounts share example.org; contacts get their company from the multi-file import and the warehouse sync
  subscription_types: Product updates, Newsletter (defined with the privacy owner before the newsletter form or the marketing import)
  sending_domain: example.com authenticated before the first newsletter send
  permission_sets: Rep, Account manager, Marketing, RevOps; one allowed and one denied record tested per set
  super_admins: [RevOps lead, Operations director]
  seat_map_file: martech-context/hubspot/seat-map.csv
  integration_credentials:
    - integration: warehouse reverse ETL
      credential_type: service key
      holder: Data engineer
      api_version: "2026-09"

records:
  contact: One person
  company: One commercial account, identified by acme_account_id; two companies may share a domain
  deal: One qualified commercial opportunity (new business or expansion)
  other_objects: []
  kept_outside_hubspot:
    - entity: Product workspaces and usage events
      where: Warehouse
      summaries_synced: [active_workspace_count, first_value_at, product_refreshed_at]

identity:
  contact_key: Email, plus acme_user_id (unique-value property) written by the warehouse sync
  company_key: acme_account_id (unique-value property); the warehouse sync creates companies through the API, which HubSpot does not deduplicate by domain
  contact_company_association: From the multi-file import and the warehouse sync (acme_user_id to acme_account_id). A demo-form contact with no company yet is routed as a prospect, and the rep confirms the account before outreach; accepted risk recorded in decisions.md
  company_summary_rule: subscription_status by precedence active, past_due, trialing, cancel_scheduled, ended, none; next_renewal_at is the next upcoming renewal across active subscriptions
  custom_object_keys: []
  crosswalk_location: Warehouse table crm_crosswalk, updated after every approved merge
  unresolved_match: RevOps review view; no merge without RevOps approval

property_register_file: martech-context/hubspot/property-register.csv

stages:
  lifecycle_settings:
    set_on_create: lead
    sync_to_primary_company: on
    set_on_deal_created: on
    set_on_deal_won: on
    set_on_lead_associated: off
  follow_up_state: Lead status contact property holds acceptance (reps work from contact views; leads object not adopted in v1). routing_status holds routing states only
  correction_path: Workflow "Lifecycle correction", manual enrolment by RevOps only, clears then sets, writes lifecycle_correction_reason
  pipelines:
    - name: New business
      stages_file: martech-context/hubspot/pipeline-new-business.yaml
      missing_required_view: View named "New business, required stage properties empty", checked daily during observation, because imports and the API are not documented as blocked

import_plan:
  order: companies first, then contacts with their company key, then the marketing update
  files:
    - name: companies.csv
      object: company
      mode: create and update
      identifier: acme_account_id
      marketing_status: not applicable
    - name: contacts_legacy.csv plus company_keys.csv
      object: contact, with company in a multi-file import (common column acme_account_id)
      mode: contacts create and update by email; companies update existing only, so an unknown key creates no company
      identifier: email for contacts, acme_account_id for companies
      marketing_status: non-marketing (4,200 rows; multiple-object imports are always non-marketing)
    - name: contacts_optouts.csv
      object: contact
      mode: update
      identifier: Record ID
      marketing_status: unchanged; carries 380 opt-outs and 95 hard bounces from the old email tool, loaded before the marketing update (method verified on the live import pages at run time)
    - name: contacts_newsletter.csv
      object: contact
      mode: update
      identifier: Record ID
      marketing_status: marketing (1,100 rows with a recorded newsletter opt-in, none of them opted out or bounced)
  import_tag_property: import_batch_id
  before_export: exports/2026-10-20-before/
  restore_window: Restore CRM changes for each named import until 14 days after it ran; RevOps lead (Super Admin) runs it
  workflows_paused: [Demo request routing, New prospect sequence enrolment]
  rehearsal_cases_file: martech-context/hubspot/import-rehearsal-cases.csv
  reconciliation: created + updated + rejected equals rows per file; re-importing the same file creates zero records
  opt_out_check: 380 opted out in the old tool, 380 in HubSpot, 0 of them marketing
  marketing_contacts:
    tier: 2000
    current: 650
    expected_after_import: 1750
    next_update_date: 2026-11-01
    maximum_cap: 1950

forms:
  - name: Demo request
    purpose: demo
    marketing_switch: off
    default_lifecycle: lead
    hidden_fields: [utm_source, utm_medium, utm_campaign, landing_page_path]
    existing_customer_rule: Contacts whose company has subscription_status active go to the account manager task queue, not rotation
  - name: Newsletter
    purpose: newsletter
    marketing_switch: on
    marketing_note: With the switch on, any of the 3,100 non-marketing imported contacts who submits becomes marketing. Headroom is 200 under the 1,950 cap, so the count is compared before and after launch and weekly during observation
    subscription_type: Newsletter
    default_lifecycle: subscriber
    hidden_fields: [utm_source, utm_medium, utm_campaign]
    existing_customer_rule: No lifecycle change; no sales notification

integrations:
  - name: Warehouse to HubSpot companies
    direction: one-way source to HubSpot
    match_key: acme_account_id
    create_if_no_match: true
    permitted_fields: [acme_account_id, subscription_status, recurring_revenue_monthly, next_renewal_at, active_workspace_count, first_value_at, billing_observed_at, product_refreshed_at]
    associations: contact to company from acme_user_id membership of acme_account_id
    blank_input: preserve and flag
    stale_update: reject when source observed time is older than the stored value
    failure_queue_owner: Data engineer

workflows:
  - name: Demo request routing
    serves: wf1
    enrol_existing_on_activation: false
    re_enrolment: on, trigger is a new demo form submission
    overwrite_existing_owner: false
    fallback_owner: Duty rep
    credit_consuming_steps: []

reports:
  - name: Demo requests to accepted follow-up
    unit: demo requests (one per valid submission; one contact can make several)
    request_key: contact Record ID plus form submission timestamp
    timestamp: Form submission date, account time zone
    exclusions: test records and example.com internal addresses, same rule as analytics
    reconciles_to: List of request keys with a demo submission in the period
  - name: New business pipeline value
    unit: deals
    timestamp: Close date
    reopened_won_rule: a reopened closed-won deal keeps its original close period unless RevOps approves a restatement
    reconciles_to: Sum of distinct deal amounts by deal Record ID

launch_evidence:
  - Capability register has no unconfirmed first-release item
  - Rehearsal import reconciled by record, association and file arithmetic; replay created nothing
  - Opt-outs 380 in source and 380 in HubSpot
  - Marketing contact count 1,750 against tier 2,000 confirmed after import, and again after the newsletter form went live
  - Warehouse sync traced one change and a replayed batch created no companies
  - Routing passed 9 cases with outbound limited to test recipients, including an imported contact submitting the demo form
  - Dashboard numbers reconcile to record IDs
  - Three reps completed demo follow-up in the portal

observation:
  length: 30 days, extended while any defect stays open
  owner: RevOps lead
  daily: [routing fallback age, warehouse sync failures, unexpected enrolments by import_batch_id, marketing contact count, deals missing required stage properties, report discrepancies]
  restore_window_closes: 14 days after each import and after the first warehouse sync run
  change_control: new workflows and properties go through the register and controlled-record tests
  training_records: example.com addresses with is_test_record = true, excluded from outreach and reports
  historical_activity: not migrated in v1; reps see email history in the old tool until it is archived

recovery:
  pause_point: Turn off Demo request routing; requests fall to the duty rep view
  review_queues: [Routing needs review, Unmatched warehouse accounts, Stale product data]
  evidence: [before-state export, import logs, workflow history, reverse ETL run log]
  restore: Restore CRM changes filtered to the import, the warehouse sync app or the workflow, inside 14 days

open_decisions:
  - decision: Whether to buy Enterprise for workspace-level records in v2
    owner: Head of Revenue
    blocks: nothing in v1
