---
id: CAP-PFM-OPS-012
title: Derive application licences from commercial contracts
feature: FEAT-PFM-OPS-03
product_area: platform
module: operations
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: partially-observed
supersedes: []
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
intent_sources:
  - >-
    services-platform:docs/proposals/application-access-and-entitlements.md#10.1 Decided by Andrew on
    2026-10-11 (D2, D3, D4)
  - >-
    services-platform:docs/proposals/application-access-and-entitlements.md#10.1 (product list confirmed,
    2026-10-12)
purpose: Use commercial contract terms to maintain the application licences shared by a customer’s workspaces.
outcomes:
  - Eligible commercial terms resolve to application coverage windows.
  - Provider failure preserves the last commercial copy without extending its known expiry.
concepts:
  - CON-PFM-OPS-003
actors:
  - ACT-PFM-043
  - ACT-PFM-006
  - ACT-PFM-004
  - ACT-PFM-005
surfaces:
  - api
  - job
preconditions:
  - The workspace has a commercial company link and the deployment has a configured provider token.
postconditions:
  - >-
    A completed read replaces the derived commercial windows, while an unsuccessful provider read preserves
    them.
permissions:
  - key: hq.licensing.read
    where_checked: api
    scope: global
  - key: hq.licensing.manage
    where_checked: api
    scope: global
rules:
  - id: BR-CAP-PFM-OPS-012-01
    proved_by: [ATS-PFM-OPS-012-01, TST-PFM-OPS-116]
    statement: >-
      Only in-force contracts produce licences; known inactive statuses produce none, and unfamiliar statuses
      are flagged without granting access.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/unit/licences/resolve-licence-windows.spec.ts#resolves nothing
      for a status it has never heard of, and flags it so the vocabulary can be confirmed
    origin: 'services-platform:docs/proposals/application-access-and-entitlements.md#D2'
  - id: BR-CAP-PFM-OPS-012-02
    proved_by: [ATS-PFM-OPS-012-02, TST-PFM-OPS-116]
    statement: >-
      A contract with unreadable term dates produces no licence and a flag, while an automatic-renewal
      contract may have an open-ended term.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/unit/licences/resolve-licence-windows.spec.ts#licenses nothing
      and is flagged when it has no end date and does not renew automatically
    origin: none
  - id: BR-CAP-PFM-OPS-012-03
    proved_by: [ATS-PFM-OPS-012-03, TST-PFM-OPS-116]
    statement: >-
      Each line-item term boundary independently uses its own date when supplied and otherwise inherits the
      corresponding contract date.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/unit/licences/resolve-licence-windows.spec.ts#takes each date
      independently, so a line may override one and inherit the other
    origin: none
  - id: BR-CAP-PFM-OPS-012-04
    proved_by: [ATS-PFM-OPS-012-04, TST-PFM-OPS-116]
    statement: >-
      The code-held product map expands recognized products into grants, flags unmapped products and grants no
      applications for integration, payment, service or licence-only products.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/unit/licences/resolve-licence-windows.spec.ts#licenses nothing
      and flags the SKU when the map does not cover it
    origin: 'services-platform:docs/proposals/application-access-and-entitlements.md#D4'
  - id: BR-CAP-PFM-OPS-012-05
    proved_by: [ATS-PFM-OPS-012-05, TST-PFM-OPS-116]
    statement: >-
      Overlapping and adjacent terms for an application combine into one licence window, while genuinely
      separated terms remain distinct.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/unit/licences/resolve-licence-windows.spec.ts#leaves a real
      gap between two terms as two windows, because the licence did lapse
    origin: none
  - id: BR-CAP-PFM-OPS-012-06
    proved_by: [ATS-PFM-OPS-012-06, TST-PFM-OPS-116]
    statement: 'Finite licence windows receive seven calendar days of grace, and open-ended windows have no grace end.'
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/unit/licences/resolve-licence-windows.spec.ts#is the seven
      days after the end date that decision D3 settled on
    origin: 'services-platform:docs/proposals/application-access-and-entitlements.md#D3'
  - id: BR-CAP-PFM-OPS-012-07
    proved_by: [ATS-PFM-OPS-012-07]
    statement: >-
      The product should notify workspace administrators beginning sixty days before a contract ends; this
      accepted notice behavior is absent.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: unknown
    source: FND-PFM-OPS-012
    origin: 'services-platform:docs/proposals/application-access-and-entitlements.md#D3'
  - id: BR-CAP-PFM-OPS-012-08
    proved_by: [ATS-PFM-OPS-012-08, ATS-PFM-OPS-012-14, ATS-PFM-OPS-012-15, ATS-PFM-OPS-013-08, ATS-PFM-OPS-014-09, TST-PFM-OPS-108]
    statement: >-
      A complete company read replaces contracts, lines and licence windows transactionally and records
      success; provider failure preserves the prior commercial snapshot and records a redacted error.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/integration/services/sync/sync-company-licences.integration.spec.js#changes
      no snapshot, no window and no last success
    origin: none
  - id: BR-CAP-PFM-OPS-012-09
    proved_by: [ATS-PFM-OPS-012-09, TST-PFM-OPS-108]
    statement: >-
      A missing HubSpot token records an unconfigured outcome without changing existing licences or raising a
      provider-failure alarm.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/integration/services/sync/sync-company-licences.integration.spec.js#leaves
      the licences of a company synced before the key was removed exactly as they were
    origin: none
  - id: BR-CAP-PFM-OPS-012-10
    proved_by: [ATS-PFM-OPS-012-10, TST-PFM-OPS-108]
    statement: >-
      Changed licence windows request one post-commit authorization flush, whether access increases or
      decreases, while identical windows request none.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/integration/services/sync/sync-company-licences.integration.spec.js#changes
      nothing and asks for no second flush when the read is the same as the last
    origin: none
  - id: BR-CAP-PFM-OPS-012-11
    proved_by: []
    statement: >-
      Nightly reconciliation, verified HubSpot notifications, manual staff sync and workspace company-link
      changes feed the shared company-sync operation.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: 'services-platform:docs/licensing.md#12. The operating story'
    origin: none
  - id: BR-CAP-PFM-OPS-012-12
    proved_by: [ATS-PFM-OPS-012-11, TST-PFM-OPS-080]
    statement: >-
      Webhook acceptance requires a signature for the exact request body and address within the replay window,
      and missing secret configuration refuses delivery without revealing verification detail.
    kind: validation
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/e2e/hubspot-licensing.e2e.spec.js#refuses a delivery signed
      for a different URI, so a signature cannot be replayed at another route
    origin: none
  - id: BR-CAP-PFM-OPS-012-13
    proved_by: []
    statement: >-
      Verified deliveries deduplicate company sync requests and resolve contract owners from the stored
      snapshot, leaving unknown contracts and unsupported events for nightly reconciliation.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/integration/services/sync/accept-webhook-delivery.integration.spec.js#reports
      an event about a contract it has never seen as unresolved, for the nightly pass to pick up
    origin: none
  - id: BR-CAP-PFM-OPS-012-14
    proved_by: [ATS-PFM-OPS-012-12, ATS-PFM-OPS-012-16, TST-PFM-OPS-080]
    statement: >-
      Staff licensing readers can inspect company commercial copies and synchronization health, while company
      synchronization requires management authority and customer callers are refused.
    kind: permission
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/e2e/hubspot-licensing.e2e.spec.js#refuses a staff member
      holding only hq.licensing.read on the sync, with 403
    origin: none
  - id: BR-CAP-PFM-OPS-012-15
    proved_by: [ATS-PFM-OPS-012-19]
    statement: >-
      Workspace deletion retains company-level commercial copies and licence windows shared by production and
      sandbox workspaces.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: observed
    source: 'services-platform:modules/licensing/services/maintenance/tenant-participant.ts'
    origin: none
  - id: BR-CAP-PFM-OPS-012-16
    proved_by: [ATS-PFM-OPS-012-17]
    statement: >-
      The sync worker acknowledges malformed input and recorded provider outcomes, reserving queue redelivery
      for thrown processing failures.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/licensing/aws/lambda/LICENCE_SYNC.handler.ts:processLicenceSyncBatch'
    origin: none
  - id: BR-CAP-PFM-OPS-012-17
    proved_by: [ATS-PFM-OPS-012-18, TST-PFM-OPS-106]
    statement: >-
      Webhook processing acknowledges failed queue publication, reports refused companies and relies on later
      reconciliation for recovery.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/licensing/services/sync/accept-webhook-delivery.ts:acceptWebhookDelivery'
    origin: none
  - id: BR-CAP-PFM-OPS-012-18
    proved_by: []
    statement: >-
      The sync queue allows five receives before dead-lettering, with four-day source retention and
      fourteen-day dead-letter retention.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/licensing/aws/resources/licence-sync.sqs.ts:createLicenceSyncQueue'
    origin: none
  - id: BR-CAP-PFM-OPS-012-19
    proved_by: []
    statement: >-
      Contract sync replaces commercial copies and licence windows without initializing a newly licensed
      application’s missing operational state.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/licensing/services/sync/sync-company-licences.ts:syncCompanyLicences'
    origin: none
scenarios:
  - id: SC-CAP-PFM-OPS-012-01
    given: A renewal begins immediately after the earlier term ends.
    when: The company’s commercial snapshot is synchronized.
    then: The resolver produces a continuous window through the renewal’s final day.
    provenance: inferred
    source: >-
      services-platform:modules/licensing/tests/integration/services/sync/sync-company-licences.integration.spec.js#extends
      the licence to the end of the new term, as one window
    note: >-
      This illustrative example combines behavior established by the cited test and the capability’s
      implementation evidence; it is not an independently bound end-to-end proof.
  - id: SC-CAP-PFM-OPS-012-02
    given: HubSpot cannot complete a company contract read.
    when: Synchronization records the provider failure.
    then: The previous commercial snapshot remains and health records the redacted explanation.
    provenance: inferred
    source: >-
      services-platform:modules/licensing/tests/integration/services/sync/sync-company-licences.integration.spec.js#records
      the reason, so an operator can see why the licences have stopped being refreshed
    note: >-
      This illustrative example combines behavior established by the cited test and the capability’s
      implementation evidence; it is not an independently bound end-to-end proof.
edge_cases: []
constraints:
  - >-
    HubSpot is a read-only commercial source, and the documented activation still requires property
    confirmation.
depends_on: []
related:
  - CAP-PFM-OPS-011
  - CAP-PFM-OPS-014
  - CAP-PFM-SEC-002
journeys:
  - JNY-PFM-OPS-005
open_questions: []
evidence:
  - >-
    services-platform at ad3f6a6: modules/licensing/hubspot/*, catalog/product-grants.ts,
    services/licences/resolve-licence-windows.ts, services/sync/*, services/companies/*,
    services/persistence/{commercial-products,hubspot-contracts,hubspot-company-syncs,licence-windows}.ts,
    aws/resources/licence-sync.sqs.ts, aws/logic/licensing-sync.lambda.ts, aws/lambda/LICENCE_SYNC.handler.ts,
    LICENCE_RECONCILE.handler.ts; resolve-licence-windows, product-grants, webhook-signature, webhook-events
    and sync-queue unit suites; sync, webhook and HubSpot table integration suites; hubspot-licensing e2e;
    docs/licensing.md §10 to §12.
  - >-
    services-platform at ad3f6a6, tables named by this capability: commercial-products, hubspot-contracts,
    hubspot-contract-lines, licence-windows, hubspot-company-syncs.
  - >-
    Queues and schedules in scope: the licence sync queue and its dead-letter queue, the nightly licence
    reconcile and its silence alarm.
  - >-
    Operations this capability covers: getHubspotCompanyLicensing, syncHubspotCompanyLicensing,
    getLicenceSyncHealth, receiveHubspotWebhook.
  - >-
    Examination: proposed acceptance matrices retain candidate test references; incomplete proof is recorded
    by FND-PFM-OPS-028.
controls: [TC-AVL-001, TC-AVL-004, TC-AVL-005, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-CHG-001, TC-CHG-002, TC-CHG-003, TC-CHG-004, TC-CHG-005, TC-CHG-006, TC-CRY-001, TC-CRY-002, TC-CRY-003, TC-CRY-004, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-DAT-004, TC-IAM-001, TC-IAM-002, TC-IAM-003, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-LOG-001, TC-LOG-004, TC-LOG-005, TC-MON-001, TC-MON-002, TC-PI-001, TC-PI-002, TC-PI-004, TC-PI-005, TC-SEC-001, TC-SEC-002, TC-SEC-003, TC-SEC-004, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-TEN-004, TC-VEN-001, TC-VEN-002, TC-VEN-003]
exercised_by: [FAS-PFM-OPS-03-01, FAS-PFM-OPS-03-03, FAS-PFM-OPS-03-04, FAS-PFM-OPS-03-05]
suites: [TST-PFM-OPS-080, TST-PFM-OPS-106, TST-PFM-OPS-108, TST-PFM-OPS-116]
---

# Derive application licences from commercial contracts

## Purpose

Use commercial contract terms to maintain the application licences shared by a customer’s workspaces.

## How It Works

The synchronization flow reads a company’s contracts and associated lines from HubSpot. The resolver interprets active terms and expands mapped products into application windows. Adjacent renewals combine while actual gaps remain visible. A complete read replaces the stored commercial copy and derived windows together. A provider failure preserves the prior copy and records synchronization health. That preservation does not extend a contract beyond its already known expiry and grace dates. Verified notifications and nightly work arrange future reads, while a staff operation can request a direct sync. Queue recovery distinguishes recorded provider failures from thrown processing failures. Contract sync does not prepare missing application state, leaving an accepted automatic-enablement gap. The pinned documentation reports that live configuration and property confirmation are still pending.

## Relationships

Licence decisions feed availability alongside operational state; security applies the resulting access decision and the console explains it.
