# Products & Services Semantic Definition Proposal Status: **PROPOSAL — requires product-owner approval; not semantic authority** Date: 2026-10-15 This document converts the current Products & Services implementation gap into an explicit approval proposal. It does not publish a Definition, change a capability profile, provision a Business, mutate STAGING or add a new architecture decision number. The proposal follows the canonical owners: - [Objects and Types](../architecture/objects-and-types.md) owns the universal Object, Type, value and validation contract. - [Business Model](../architecture/business-model.md) owns Business-scoped configuration and connected business meaning. - [Operation Contract](../architecture/operation-contract.md) owns governed executable Operations and their boundary to Commands. - [Views and Content](../architecture/views-and-content.md) owns persisted Presentation, forms and model-projection UI. - [Identity and Governance](../architecture/identity-and-governance.md) owns Business scoping, authority, Policy and isolation. ## 1. Current-state evidence ### Authoritative capability configuration [`generic-business-capability-profile.json`](../../database/configuration/idy-008/generic-business-capability-profile.json) declares: ```json { "key": "products-services", "label": "Products & Services", "records": [] } ``` The persisted Business Presentation profile contains a generic Products & Services Presentation node, but it has no record collection or record editing binding. The deployed page therefore presents only the data-defined status text that record Operations are not configured. ### Existing reusable Type [`CapabilityCompositionVocabulary::OFFERING_TYPE`](../../app/Runtime/Commands/CapabilityCompositionVocabulary.php) already defines the Type: ```text a8100000-0000-4000-8000-000000000001 ``` Its existing descriptor is: | Property | Type | Required | |---|---|---:| | `name` | string | yes | | `offering_kind` | string | yes | | `description` | string | no | | `lifecycle` | string | yes | | `commercial_context` | string | no | The descriptor rejects additional properties. It is installed through the existing capability-composition vocabulary and uses the universal Object/Type model. No new Product or Service Type is required by the current repository evidence. ### Existing generic lifecycle mechanism [`BusinessRecordService`](../../app/Runtime/Business/BusinessRecordService.php) already validates configured record values against the persisted Type descriptor and submits create/update effects through the existing `CommandHandler`, with Business scope, expected versions, transaction handling, revisions and provenance. [`BusinessPresentationProvisioningService`](../../app/Runtime/Presentation/BusinessPresentationProvisioningService.php) already derives persisted Business-scoped View Definitions and Presentation Object graphs from the authoritative capability profile. It is the appropriate reconciliation owner for this record binding. The repository does not establish a Product/Service-specific delete, archive, pricing, tax, inventory, fulfilment or service-delivery Operation. ## 2. Recommended semantic model ### Recommendation: one Offering Type with a discriminator Reuse `OFFERING_TYPE` for both products and services. The `offering_kind` property distinguishes the configured offering kind. This is **ESTABLISHED** at the Type-structure level because the Type already exists and already contains `offering_kind`. The allowed values themselves are **RECOMMENDED**, not established business requirements. Recommended initial values: ```text product service ``` These values require approval before they become an authoritative validation vocabulary. Until approved, the Type's existing string validation remains the only established constraint. ### Why this model is recommended - Reuses an existing platform-owned Type and revision. - Avoids duplicate Product and Service persistence models. - Preserves one Object identity and one generic `/objects` interface. - Allows future offering kinds without a second semantic store. - Keeps shared fields and generic Presentation bindings simple. - Matches the existing `OFFERING_TYPE` descriptor and prior repository vocabulary. ### Alternative: separate Product and Service Types **Not recommended.** Separate Types would duplicate validation, Presentation, authorization targets and provisioning configuration without any established semantic distinction requiring different persistence or Operation contracts. Separate Types may become justified later only if approved requirements show different invariants or lifecycles that cannot be represented by the shared Offering Type. ### Alternative: free-form capability fields **Rejected.** A capability-local field map would bypass the universal Type contract and make validation, authorization and Presentation resolution capability-specific. ## 3. Proposed field schema The following uses the existing Type descriptor; it does not add new runtime validation. ### Essential fields | Field | Established/recommended status | Presentation | |---|---|---| | `name` | **ESTABLISHED** by `OFFERING_TYPE` and required | Required text | | `offering_kind` | **ESTABLISHED** as a required string field; `product`/`service` values are **RECOMMENDED** | Required select after value approval, otherwise required text | | `lifecycle` | **ESTABLISHED** as a required string field; vocabulary is **UNRESOLVED** | Required text until lifecycle vocabulary is approved | ### Optional fields | Field | Status | Presentation | |---|---|---| | `description` | **ESTABLISHED** by the existing Type descriptor | Optional textarea | | `commercial_context` | **ESTABLISHED** by the existing Type descriptor | Optional textarea | ### Explicitly excluded from this proposal The following are not inferred or added: - Price or price lists - Currency - Tax treatment - Stock or inventory - SKU or catalogue identity - Service delivery model - Capacity, scheduling or fulfilment - Subscription or entitlement semantics - Product variants or bundles Those concepts require separate requirements and semantic definitions if they become necessary. ## 4. Lifecycle proposal The existing Type requires a `lifecycle` string but does not establish a controlled vocabulary or a transition model. | Choice | Classification | |---|---| | Retain `lifecycle` as a required string field | **ESTABLISHED** by the existing Type | | Introduce `draft`, `active`, `retired` values | **RECOMMENDED**, requires approval | | Make lifecycle a state-transition definition | **UNRESOLVED**; requires approved State/Workflow semantics | | Add archive/delete now | **NOT PROPOSED**; no established Operation or retention requirement | The minimum implementation should preserve the existing string field and avoid pretending that lifecycle values are authoritative until approved. ## 5. Operation and authorization matrix | Capability | Mechanism | Status | |---|---|---| | Inspect Offering Objects | `AuthorizedObjectReader` through `/objects` | **ESTABLISHED** | | Create Offering Objects | Generic create effect through `CommandHandler` | **ESTABLISHED** | | Update Offering Objects | Generic update effect with expected version | **ESTABLISHED** | | Validate values | Existing Type descriptor and `TypeDescriptorValidator` | **ESTABLISHED** | | Delete | No Operation proposed | **UNRESOLVED / deferred** | | Archive | No Operation proposed | **UNRESOLVED / deferred** | | Product-specific Operation | None required for the minimum record lifecycle | **ESTABLISHED by absence of requirement** | Business authorization must continue to require: - Authenticated User. - Active Business Membership. - Applicable AuthorityGrant and Policy. - Target Type authorization for `OFFERING_TYPE`. - Business-owned Object scope. - Exact expected Object revision on update. - Existing Command transaction, revision and provenance evidence. No direct repository write, special HTTP mutation path or Product/Service mutation handler is proposed. ## 6. Capability and Presentation configuration ### Capability profile binding The Products & Services entry should reference the existing Offering Type: ```json { "key": "products-services", "label": "Products & Services", "records": [ { "key": "offering", "label": "Products & Services", "type_id": "a8100000-0000-4000-8000-000000000001", "view_id": "a2000000-0000-4000-8000-000000000004", "fields": [ { "name": "name", "label": "Name", "renderer": "text", "required": true }, { "name": "offering_kind", "label": "Product or service", "renderer": "text", "required": true }, { "name": "description", "label": "Description", "renderer": "textarea" }, { "name": "lifecycle", "label": "Lifecycle", "renderer": "text", "required": true }, { "name": "commercial_context", "label": "Commercial context", "renderer": "textarea" } ] } ] } ``` This is a **proposed configuration shape**, not yet an approved persisted change. `a2000000-0000-4000-8000-000000000004` is a proposed unused View declaration identity and must be checked before implementation. ### View Definition The Business-scoped View Definition should use the same fields for list and detail presentation, with a shorter list: - List: `name`, `offering_kind`, `lifecycle` - Detail: all five fields - No relationship sections in the minimum increment - Generic create/update behavior through the existing record form and Command pipeline The existing reconciliation mechanism should derive and persist the View Definition and Presentation Object graph. No Products-specific renderer is permitted. ### Form behavior The existing generic object form can consume the persisted `detail_fields` binding. It should not introduce a Product-specific template. The form submits the existing generic create/update mutation path and preserves expected versions and provenance. ## 7. Provisioning and idempotency Implementation, after approval, should: 1. Confirm or install the existing `OFFERING_TYPE` descriptor through `CapabilityCompositionVocabulary::install`. 2. Add the Offering record entry to `generic-business-capability-profile.json`. 3. Add the corresponding View declaration to `generic-business-capability-configuration.json` if that manifest remains the authoritative declaration source for the Business-scoped View. 4. Reconcile persisted View and Presentation Objects through `BusinessPresentationProvisioningService`. 5. Ensure the Business authorization target set includes `OFFERING_TYPE`. 6. Verify repeated reconciliation produces no duplicate Objects or revisions. 7. Verify a newly initialized Business can inspect, create and update Offering Objects only through canonical generic routes. No records should be seeded merely to make the UI appear populated. ## 8. Decisions requiring approval The following are the minimum unresolved product decisions: 1. Approve the shared `OFFERING_TYPE` model for both Products and Services. 2. Approve the `product` and `service` `offering_kind` values, or provide the authoritative vocabulary. 3. Approve whether `lifecycle` remains a free string initially or receives an approved controlled vocabulary. 4. Approve the proposed field labels and the proposed View declaration identity. 5. Confirm that pricing, currency, tax, inventory and service-delivery semantics are out of scope for this increment. 6. Confirm that delete/archive Operations are deferred. No additional architecture decision number is needed. These are product configuration decisions under the existing settled Object, Business, Operation, Governance and Presentation contracts. ## 9. Implementation and regression plan After approval: ### Focused tests - Assert `OFFERING_TYPE` exists with the established descriptor and revision. - Assert the capability profile binds `products-services` to `OFFERING_TYPE`. - Assert the persisted View/Form fields match the approved profile. - Create an Offering through the generic Command path. - Reject missing required fields and invalid Type values. - Reject unauthorized and cross-Business mutations. - Reject stale expected versions. - Assert a successful update creates a revision and provenance. - Assert canonical `/objects?type_id=...` and `/objects/{id}` rendering. - Assert idempotent Business Presentation reconciliation. - Preserve existing People, Organisation and Project tests. ### Required validation - PHP lint. - Focused Type, Operation, provisioning, Presentation and authorization tests. - Full PHPUnit against the isolated local test database. - Package validation. - HTTP acceptance. - UI structure and shared renderer validation. - `git diff --check`. No STAGING database mutation is authorized by this proposal.