From 7dc738b3b67f26255441b37a1a01a88661a5eb90 Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Thu, 23 Apr 2026 17:44:51 +0200 Subject: [PATCH 01/17] CEXT-6138: add capability discovery spec --- docs/capability-discovery.md | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 docs/capability-discovery.md diff --git a/docs/capability-discovery.md b/docs/capability-discovery.md new file mode 100644 index 000000000..c2113f561 --- /dev/null +++ b/docs/capability-discovery.md @@ -0,0 +1,73 @@ +# Capability Discovery + +## Background + +Clients that integrate with SDK-based apps need to know which endpoints are active for a given deployment. Without a formal mechanism, clients are forced to infer feature availability at runtime by calling endpoints and inspecting response shapes — a fragile pattern that couples client logic to internal API details. + +The goal of this spec is to make the active API surface explicit, static, and machine-readable by generating a filtered OpenAPI spec at build time, served as a static asset that any client can fetch. + +## Goals + +- Give any client a stable, versioned description of which endpoints are active for a given deployment. +- Remove the need for clients to infer feature availability by parsing response shapes or probing endpoints. +- Keep the SDK as the single source of truth for the API contract. + +## Approach + +### 1. Full spec in the SDK package + +`aio-commerce-lib-app` ships an `openapi.json` (OpenAPI 3.x) documenting **all** endpoints the SDK can expose. This file is included in the published npm package under a well-known path (e.g. `dist/openapi.json`). + +This spec is maintained alongside the source code and updated whenever an endpoint is added, changed, or removed. It represents the full possible surface — not what any particular deployment exposes. + +### 2. Filtered spec generated by `pre-app-build` + +The SDK's `pre-app-build` hook already reads `app.commerce.config.{ts,js}` to determine which features are active for the app being built, and uses this to generate `ext.config.yaml` with only the required actions. + +The same hook will also generate a **filtered `openapi.json`** — a subset of the full spec containing only the endpoints that correspond to the active config domains — and emit it alongside the built app. The generated file includes the SDK version and the app version for traceability. + +Availability is determined by what the app developer declares in `app.commerce.config.ts`, not by which SDK modules are imported. The `pre-app-build` hook is the correct and only insertion point for generating the filtered spec, since it is the only place where both the full SDK spec and the app's config are available simultaneously. + +The filtering logic mirrors what already drives `ext.config.yaml` generation: + +| Config domain active | Endpoints included | +| ------------------------------------------------------------------------------------ | ---------------------- | +| Always | `app-config` | +| `eventing.commerce` or `eventing.external` or `installation.customInstallationSteps` | `installation` | +| `businessConfig` | `config`, `scope-tree` | + +### 3. Metadata action + +The filtered `openapi.json` is served by a dedicated `metadata` action (not a static file) protected by the same IMS authentication as the rest of the SDK endpoints. This avoids unauthenticated disclosure of deployment-specific configuration. + +Suggested path: `/__metadata__/openapi.json` + +### 4. Client consumption + +Any authenticated client can fetch `/__metadata__/openapi.json` and use it as the authoritative list of available endpoints for that deployment. The request must include a valid IMS bearer token — the same credential already required to call any SDK endpoint. + +**Example: `commerce-app-management`** + +On load, `commerce-app-management` already holds an IMS token from the user session. It uses that token to fetch `/__metadata__/openapi.json` and drive UI visibility — showing or hiding features based on whether the corresponding endpoint is present in the spec. This replaces the current pattern of calling `getAppConfig` and inferring feature availability from the response shape. + +Other clients (CLI tools, integrations, third-party UIs) follow the same pattern: obtain an IMS token, fetch the spec, adapt accordingly. + +## Security + +The spec is auth-gated to prevent **deployment fingerprinting**: the filtered spec reveals which features a specific deployment has enabled (e.g. eventing, webhooks, businessConfig). This is deployment-specific metadata that should not be publicly enumerable. + +The full unfiltered spec committed to the `aio-commerce-sdk` repository is intentionally public — it serves as API documentation for developers. The runtime filtered spec is a different artifact and warrants a different access policy. + +## SDK upgrades + +When an app upgrades its `aio-commerce-lib-app` dependency, the full `openapi.json` shipped in the new package version may include new endpoints, updated schemas, or additional fields reflecting new SDK features. + +The upgrade flow is: + +1. App developer bumps the SDK version in `package.json` and installs. +2. On the next build, `pre-app-build` reads the updated full spec from the new package version. +3. It derives the active config domains from the app's `app.commerce.config.ts` and applies the same filtering. +4. A new filtered `openapi.json` is generated and deployed with the `metadata` action. +5. Clients fetching `/__metadata__/openapi.json` receive the updated spec and can adapt — exposing new UI features, calling new endpoints, or adjusting to changed schemas. + +No manual spec maintenance is required from the app developer. New SDK features are automatically included in the filtered spec if the corresponding config domain is already active in the app. If a new feature requires a new config domain (e.g. a newly introduced optional capability), the app developer opts in by declaring it in `app.commerce.config.ts` and rebuilding. From c4f26d6809f7b0d237ad9430736ed8c3781f6315 Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Thu, 23 Apr 2026 17:46:31 +0200 Subject: [PATCH 02/17] CEXT-6138: add metadata section to capability discovery spec --- docs/capability-discovery.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/capability-discovery.md b/docs/capability-discovery.md index c2113f561..86a83de42 100644 --- a/docs/capability-discovery.md +++ b/docs/capability-discovery.md @@ -1,5 +1,11 @@ # Capability Discovery +## Metadata + +| Ticket | Description | +| --------------------------------------------------------- | ------------ | +| [CEXT-6138](https://jira.corp.adobe.com/browse/CEXT-6138) | Initial spec | + ## Background Clients that integrate with SDK-based apps need to know which endpoints are active for a given deployment. Without a formal mechanism, clients are forced to infer feature availability at runtime by calling endpoints and inspecting response shapes — a fragile pattern that couples client logic to internal API details. From df049308ed47275dbdace9843546a68a8d6e842d Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Thu, 23 Apr 2026 18:10:12 +0200 Subject: [PATCH 03/17] CEXT-6138: introduce spec-driven development structure --- specs/conventions.md | 42 +++++++++++++++ specs/features/_template.md | 51 +++++++++++++++++++ .../features}/capability-discovery.md | 0 3 files changed, 93 insertions(+) create mode 100644 specs/conventions.md create mode 100644 specs/features/_template.md rename {docs => specs/features}/capability-discovery.md (100%) diff --git a/specs/conventions.md b/specs/conventions.md new file mode 100644 index 000000000..00d5d5f15 --- /dev/null +++ b/specs/conventions.md @@ -0,0 +1,42 @@ +# Spec Conventions + +## Structure + +Specs live in `specs/features/`. Each spec is a single Markdown file named after the feature it describes (e.g. `capability-discovery.md`). + +`specs/features/_template.md` is the canonical template for new specs. + +## File naming + +- Lowercase, hyphen-separated (e.g. `capability-discovery.md`) +- Name the file after the feature, not the ticket + +## Spec structure + +Every spec must include, in order: + +1. **Metadata** — table of CEXT tickets that introduced or changed this spec +2. **Background** — the problem being solved and why it matters +3. **Goals** — what the feature must achieve, stated as outcomes +4. **Approach** — how the feature works; use numbered subsections for multi-step flows +5. **Security** — any security considerations; omit the section if there are none +6. **Open questions** — unresolved decisions; remove the section once all are resolved + +Additional sections (e.g. **SDK upgrades**, **Migration**) may be added as needed. + +## Metadata table + +Every spec must have a Metadata section at the top listing the CEXT tickets that created or significantly changed it. Add a row each time a ticket results in a spec update. + +```md +## Metadata + +| Ticket | Description | +| --------------------------------------------------------- | --------------- | +| [CEXT-XXXX](https://jira.corp.adobe.com/browse/CEXT-XXXX) | Initial spec | +| [CEXT-YYYY](https://jira.corp.adobe.com/browse/CEXT-YYYY) | Added X section | +``` + +## Keeping specs current + +A spec is a living document. When implementation reveals that the approach needs to change, update the spec before (or alongside) the code change. The metadata table entry for the corresponding ticket serves as the changelog. diff --git a/specs/features/_template.md b/specs/features/_template.md new file mode 100644 index 000000000..65e2039b9 --- /dev/null +++ b/specs/features/_template.md @@ -0,0 +1,51 @@ +# Feature Name + +## Metadata + +| Ticket | Description | +| --------------------------------------------------------- | ------------ | +| [CEXT-XXXX](https://jira.corp.adobe.com/browse/CEXT-XXXX) | Initial spec | + +## Background + + + +## Goals + + + +- +- +- + +## Approach + +### 1. + + + +### 2. + + + +### 3. + + + +## Security + + + +## SDK upgrades + + + +## Open questions + + + +- diff --git a/docs/capability-discovery.md b/specs/features/capability-discovery.md similarity index 100% rename from docs/capability-discovery.md rename to specs/features/capability-discovery.md From e69051e3ddb17b9743e4a9942b2bf2575551c64c Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Thu, 23 Apr 2026 18:11:04 +0200 Subject: [PATCH 04/17] CEXT-6138: document spec-driven development in AGENTS.md --- AGENTS.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 744b1b342..8a1062e6b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -105,6 +105,12 @@ For source code comments, follow these rules: - Prefix the PR title with the Jira ticket (e.g. `CEXT-1234: short description`) - Always follow `.github/PULL_REQUEST_TEMPLATE.md` when writing PR descriptions +## Specs + +- Feature specs live in `specs/features/`; use `specs/features/_template.md` as the starting point +- See `specs/conventions.md` for authoring guidelines +- New features should be preceded by a spec before implementation begins + ## Changesets - Every code change that requires a release must include a changeset (test-only changes do not need one) From fbb659393e47106b3d36021019dea50b8d7975ee Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Fri, 24 Apr 2026 11:22:52 +0200 Subject: [PATCH 05/17] CEXT-6138: adopt RFC format for feature specs --- specs/conventions.md | 41 ++++---- specs/features/_template.md | 74 ++++++++------ specs/features/capability-discovery.md | 132 ++++++++++++++++++------- 3 files changed, 159 insertions(+), 88 deletions(-) diff --git a/specs/conventions.md b/specs/conventions.md index 00d5d5f15..09f469e8e 100644 --- a/specs/conventions.md +++ b/specs/conventions.md @@ -2,7 +2,8 @@ ## Structure -Specs live in `specs/features/`. Each spec is a single Markdown file named after the feature it describes (e.g. `capability-discovery.md`). +Specs live in `specs/features/`. Each spec is a single Markdown file named after +the feature it describes (e.g. `capability-discovery.md`). `specs/features/_template.md` is the canonical template for new specs. @@ -11,32 +12,32 @@ Specs live in `specs/features/`. Each spec is a single Markdown file named after - Lowercase, hyphen-separated (e.g. `capability-discovery.md`) - Name the file after the feature, not the ticket -## Spec structure +## Spec format -Every spec must include, in order: +Specs follow an RFC-inspired format. Every spec must include, in order: -1. **Metadata** — table of CEXT tickets that introduced or changed this spec -2. **Background** — the problem being solved and why it matters -3. **Goals** — what the feature must achieve, stated as outcomes -4. **Approach** — how the feature works; use numbered subsections for multi-step flows -5. **Security** — any security considerations; omit the section if there are none -6. **Open questions** — unresolved decisions; remove the section once all are resolved +1. **Title** — `# RFC: Feature Name` +2. **Metadata** — ticket and creation date (see below) +3. **Summary** — one paragraph explanation of the feature +4. **Motivation** — the problem being solved; be concrete about the pain point +5. **Developer experience** — how the feature looks from an app developer's perspective; use examples +6. **Design** — technical detail; interactions with existing SDK features, implementation approach, edge cases +7. **Drawbacks** — honest reasons not to do this +8. **Rationale and alternatives** — why this design, what was rejected and why +9. **Unresolved questions** — open decisions; remove the section once all are resolved +10. **Future possibilities** — natural extensions; out-of-scope ideas worth capturing -Additional sections (e.g. **SDK upgrades**, **Migration**) may be added as needed. - -## Metadata table +## Metadata -Every spec must have a Metadata section at the top listing the CEXT tickets that created or significantly changed it. Add a row each time a ticket results in a spec update. +Every spec includes a metadata block below the title: ```md -## Metadata - -| Ticket | Description | -| --------------------------------------------------------- | --------------- | -| [CEXT-XXXX](https://jira.corp.adobe.com/browse/CEXT-XXXX) | Initial spec | -| [CEXT-YYYY](https://jira.corp.adobe.com/browse/CEXT-YYYY) | Added X section | +- **Ticket:** [CEXT-XXXX](https://jira.corp.adobe.com/browse/CEXT-XXXX) +- **Created:** YYYY-MM-DD ``` ## Keeping specs current -A spec is a living document. When implementation reveals that the approach needs to change, update the spec before (or alongside) the code change. The metadata table entry for the corresponding ticket serves as the changelog. +A spec is a living document. When implementation reveals that the approach needs +to change, update the spec before (or alongside) the code change. The associated +Jira ticket is the changelog — link it in the commit message. diff --git a/specs/features/_template.md b/specs/features/_template.md index 65e2039b9..6ff2ae737 100644 --- a/specs/features/_template.md +++ b/specs/features/_template.md @@ -1,51 +1,63 @@ -# Feature Name +# RFC: Feature Name -## Metadata +- **Ticket:** [CEXT-XXXX](https://jira.corp.adobe.com/browse/CEXT-XXXX) +- **Created:** YYYY-MM-DD -| Ticket | Description | -| --------------------------------------------------------- | ------------ | -| [CEXT-XXXX](https://jira.corp.adobe.com/browse/CEXT-XXXX) | Initial spec | +## Summary -## Background +One paragraph explanation of the feature. - +## Motivation -## Goals +Describe the problem this feature solves for developers building Commerce apps +with the SDK. Include necessary background and specific use cases where this +feature helps. This is the most important section — be concrete about the pain +point before proposing a solution. - +## Developer experience -- -- -- +Explain the feature as if it were already shipped and you were showing it to an +app developer using the SDK. This means: -## Approach +- What new concepts or APIs does it introduce? +- Show concrete usage examples (config, code, CLI output). +- How should developers think about this feature and how does it change how they + build apps? +- If applicable, show what error messages or warnings look like. +- Does this affect how developers read or maintain existing app code? -### 1. +## Design - +The technical detail of the feature. Cover enough that: -### 2. +- Interactions with existing SDK features (lib-app, lib-auth, lib-config, etc.) + are clear. +- It is reasonably clear how the feature would be implemented. +- Edge cases are called out explicitly. - +Return to the examples from the Developer experience section and explain how the +design makes them work. -### 3. +## Drawbacks - +Why should we _not_ do this? Consider implementation cost, maintenance burden, +impact on SDK consumers, and whether it adds complexity for the common case. -## Security +## Rationale and alternatives - +- Why is this the best design in the space of possible designs? +- What other approaches were considered and why were they rejected? +- What is the impact of not doing this? +- Could this be achieved in user-space (i.e. without SDK changes)? -## SDK upgrades +## Unresolved questions - +- What needs to be resolved through discussion before this spec is accepted? +- What needs to be resolved during implementation before the feature ships? +- What related problems are explicitly out of scope for this RFC? -## Open questions +## Future possibilities - - -- +What is the natural evolution of this feature? How might it interact with other +planned SDK features? Use this section to capture related ideas that are out of +scope now but worth tracking. diff --git a/specs/features/capability-discovery.md b/specs/features/capability-discovery.md index 86a83de42..d7cdf2dde 100644 --- a/specs/features/capability-discovery.md +++ b/specs/features/capability-discovery.md @@ -1,38 +1,77 @@ -# Capability Discovery +# RFC: Capability Discovery -## Metadata +- **Ticket:** [CEXT-6138](https://jira.corp.adobe.com/browse/CEXT-6138) +- **Created:** 2026-04-23 -| Ticket | Description | -| --------------------------------------------------------- | ------------ | -| [CEXT-6138](https://jira.corp.adobe.com/browse/CEXT-6138) | Initial spec | +## Summary -## Background +Provide a formal discoverability mechanism for the features and endpoints exposed +by a Commerce SDK-based app, so that any authenticated client can reliably +determine what is available for a given deployment without needing SDK-specific +knowledge. -Clients that integrate with SDK-based apps need to know which endpoints are active for a given deployment. Without a formal mechanism, clients are forced to infer feature availability at runtime by calling endpoints and inspecting response shapes — a fragile pattern that couples client logic to internal API details. +## Motivation -The goal of this spec is to make the active API surface explicit, static, and machine-readable by generating a filtered OpenAPI spec at build time, served as a static asset that any client can fetch. +As the Commerce SDK grows in scope, clients need to know which features and +endpoints are active for a given deployment. Without a formal mechanism, clients +are forced to infer feature availability at runtime by calling endpoints and +inspecting response shapes — a fragile pattern that couples client logic to +internal API details. -## Goals +`commerce-app-management` is a concrete example: it currently calls `getAppConfig` +and inspects the response shape to decide which UI features to show or hide. Every +new SDK capability requires a corresponding change in `commerce-app-management` to +detect it. This does not scale as the number of clients and SDK features grows. -- Give any client a stable, versioned description of which endpoints are active for a given deployment. -- Remove the need for clients to infer feature availability by parsing response shapes or probing endpoints. -- Keep the SDK as the single source of truth for the API contract. +## Developer experience -## Approach +An authenticated client fetches `/__metadata__/openapi.json` with a valid IMS +bearer token: -### 1. Full spec in the SDK package +```http +GET /__metadata__/openapi.json +Authorization: Bearer +``` + +The response is an OpenAPI 3.x document listing only the endpoints active for +that deployment. A client uses it to adapt its behaviour — no SDK-specific +knowledge required. + +**Example: `commerce-app-management`** + +On load, `commerce-app-management` already holds an IMS token from the user +session. It fetches `/__metadata__/openapi.json` and drives UI visibility based on +which endpoints are present — showing or hiding features accordingly. This +replaces the current pattern of calling `getAppConfig` and inferring availability +from the response shape. -`aio-commerce-lib-app` ships an `openapi.json` (OpenAPI 3.x) documenting **all** endpoints the SDK can expose. This file is included in the published npm package under a well-known path (e.g. `dist/openapi.json`). +Other clients (CLI tools, integrations, third-party UIs) follow the same pattern: +obtain an IMS token, fetch the spec, adapt accordingly. -This spec is maintained alongside the source code and updated whenever an endpoint is added, changed, or removed. It represents the full possible surface — not what any particular deployment exposes. +## Design + +### 1. Full spec in the SDK package + +`aio-commerce-lib-app` ships an `openapi.json` (OpenAPI 3.x) documenting **all** +endpoints the SDK can expose. This file is included in the published npm package +under a well-known path (e.g. `dist/openapi.json`) and is the single source of +truth for the SDK's API contract. ### 2. Filtered spec generated by `pre-app-build` -The SDK's `pre-app-build` hook already reads `app.commerce.config.{ts,js}` to determine which features are active for the app being built, and uses this to generate `ext.config.yaml` with only the required actions. +The SDK's `pre-app-build` hook already reads `app.commerce.config.{ts,js}` to +determine which features are active for the app being built, and uses this to +generate `ext.config.yaml` with only the required actions. -The same hook will also generate a **filtered `openapi.json`** — a subset of the full spec containing only the endpoints that correspond to the active config domains — and emit it alongside the built app. The generated file includes the SDK version and the app version for traceability. +The same hook generates a **filtered `openapi.json`** — a subset of the full spec +containing only the endpoints that correspond to the active config domains. The +generated file includes the SDK version and the app version for traceability. -Availability is determined by what the app developer declares in `app.commerce.config.ts`, not by which SDK modules are imported. The `pre-app-build` hook is the correct and only insertion point for generating the filtered spec, since it is the only place where both the full SDK spec and the app's config are available simultaneously. +Availability is determined by what the app developer declares in +`app.commerce.config.ts`, not by which SDK modules are imported. The +`pre-app-build` hook is the correct and only insertion point since it is the only +place where both the full SDK spec and the app's config are available +simultaneously. The filtering logic mirrors what already drives `ext.config.yaml` generation: @@ -44,36 +83,55 @@ The filtering logic mirrors what already drives `ext.config.yaml` generation: ### 3. Metadata action -The filtered `openapi.json` is served by a dedicated `metadata` action (not a static file) protected by the same IMS authentication as the rest of the SDK endpoints. This avoids unauthenticated disclosure of deployment-specific configuration. +The filtered `openapi.json` is served by a dedicated `metadata` runtime action +protected by the same IMS authentication as the rest of the SDK endpoints. Suggested path: `/__metadata__/openapi.json` -### 4. Client consumption +### 4. SDK upgrades -Any authenticated client can fetch `/__metadata__/openapi.json` and use it as the authoritative list of available endpoints for that deployment. The request must include a valid IMS bearer token — the same credential already required to call any SDK endpoint. +When an app upgrades its `aio-commerce-lib-app` dependency, the full `openapi.json` +in the new package version may include new endpoints or updated schemas. On the +next build, `pre-app-build` derives the active config domains from +`app.commerce.config.ts`, applies the same filtering, and deploys an updated +`openapi.json` with the `metadata` action. -**Example: `commerce-app-management`** +No manual spec maintenance is required. New SDK features are automatically included +if their config domain is already active. If a new feature introduces a new config +domain, the app developer opts in by declaring it in `app.commerce.config.ts` and +rebuilding. -On load, `commerce-app-management` already holds an IMS token from the user session. It uses that token to fetch `/__metadata__/openapi.json` and drive UI visibility — showing or hiding features based on whether the corresponding endpoint is present in the spec. This replaces the current pattern of calling `getAppConfig` and inferring feature availability from the response shape. +## Drawbacks -Other clients (CLI tools, integrations, third-party UIs) follow the same pattern: obtain an IMS token, fetch the spec, adapt accordingly. +- Adds a new `metadata` runtime action to every SDK-based deployment. +- Clients must hold a valid IMS token before they can discover what is available, + which adds a bootstrap step for clients that don't already have one. -## Security +## Rationale and alternatives -The spec is auth-gated to prevent **deployment fingerprinting**: the filtered spec reveals which features a specific deployment has enabled (e.g. eventing, webhooks, businessConfig). This is deployment-specific metadata that should not be publicly enumerable. +The filtered spec is auth-gated to prevent **deployment fingerprinting**: it +reveals which features a specific deployment has enabled, which is +deployment-specific metadata that should not be publicly enumerable. Schema +disclosure is not a concern since the full spec is already public in the +`aio-commerce-sdk` repository. -The full unfiltered spec committed to the `aio-commerce-sdk` repository is intentionally public — it serves as API documentation for developers. The runtime filtered spec is a different artifact and warrants a different access policy. +Alternatives considered: -## SDK upgrades +- **Runtime inspection of `getAppConfig`** — current approach; fragile and + requires SDK-specific knowledge in every client. +- **Unauthenticated static file** — simpler to consume but exposes + deployment-specific configuration without auth. +- **Capabilities list endpoint** — a simpler boolean feature map instead of a + full OpenAPI spec; less powerful and doesn't give clients the full contract. -When an app upgrades its `aio-commerce-lib-app` dependency, the full `openapi.json` shipped in the new package version may include new endpoints, updated schemas, or additional fields reflecting new SDK features. +## Unresolved questions -The upgrade flow is: +None. -1. App developer bumps the SDK version in `package.json` and installs. -2. On the next build, `pre-app-build` reads the updated full spec from the new package version. -3. It derives the active config domains from the app's `app.commerce.config.ts` and applies the same filtering. -4. A new filtered `openapi.json` is generated and deployed with the `metadata` action. -5. Clients fetching `/__metadata__/openapi.json` receive the updated spec and can adapt — exposing new UI features, calling new endpoints, or adjusting to changed schemas. +## Future possibilities -No manual spec maintenance is required from the app developer. New SDK features are automatically included in the filtered spec if the corresponding config domain is already active in the app. If a new feature requires a new config domain (e.g. a newly introduced optional capability), the app developer opts in by declaring it in `app.commerce.config.ts` and rebuilding. +- The `metadata` action could be extended to expose additional deployment + information beyond the OpenAPI spec (e.g. SDK version, configured domains) + under a common `/__metadata__/` namespace. +- The full `openapi.json` in the SDK package could be used to power generated + client libraries or documentation automatically. From a00ccf62d7a962ec192ece2d74b00f122908569a Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Fri, 24 Apr 2026 11:32:53 +0200 Subject: [PATCH 06/17] CEXT-6138: simplify conventions and adopt ticket-prefixed filenames --- specs/conventions.md | 54 +++++++++---------- ...y.md => CEXT-6138-capability-discovery.md} | 0 2 files changed, 25 insertions(+), 29 deletions(-) rename specs/features/{capability-discovery.md => CEXT-6138-capability-discovery.md} (100%) diff --git a/specs/conventions.md b/specs/conventions.md index 09f469e8e..a57cebb9a 100644 --- a/specs/conventions.md +++ b/specs/conventions.md @@ -1,43 +1,39 @@ # Spec Conventions -## Structure +## Why specs -Specs live in `specs/features/`. Each spec is a single Markdown file named after -the feature it describes (e.g. `capability-discovery.md`). +The RFC process provides a consistent path for substantial changes to the SDK so +that all stakeholders can be confident about its direction. -`specs/features/_template.md` is the canonical template for new specs. +Many changes — bug fixes, internal refactoring, documentation improvements — can +go straight to a pull request. But some changes are substantial enough to warrant +a design process and explicit alignment before implementation begins. -## File naming +## When to write a spec -- Lowercase, hyphen-separated (e.g. `capability-discovery.md`) -- Name the file after the feature, not the ticket +Write a spec when the change is "substantial". This includes: -## Spec format +- New public API surface (new exports, new config fields, new lifecycle hooks) +- Changes to existing public API that affect SDK consumers +- New SDK-level features that span multiple packages +- Removal of existing public API -Specs follow an RFC-inspired format. Every spec must include, in order: +A spec is not required for: -1. **Title** — `# RFC: Feature Name` -2. **Metadata** — ticket and creation date (see below) -3. **Summary** — one paragraph explanation of the feature -4. **Motivation** — the problem being solved; be concrete about the pain point -5. **Developer experience** — how the feature looks from an app developer's perspective; use examples -6. **Design** — technical detail; interactions with existing SDK features, implementation approach, edge cases -7. **Drawbacks** — honest reasons not to do this -8. **Rationale and alternatives** — why this design, what was rejected and why -9. **Unresolved questions** — open decisions; remove the section once all are resolved -10. **Future possibilities** — natural extensions; out-of-scope ideas worth capturing +- Bug fixes with no API surface change +- Internal refactoring that doesn't affect SDK consumers +- Test or documentation additions +- Dependency updates -## Metadata +If in doubt, write a spec. A short spec is better than a surprise. -Every spec includes a metadata block below the title: +## Files -```md -- **Ticket:** [CEXT-XXXX](https://jira.corp.adobe.com/browse/CEXT-XXXX) -- **Created:** YYYY-MM-DD -``` +Specs live in `specs/features/` and are named with the Jira ticket prefix so +they are easy to find: -## Keeping specs current +``` +specs/features/CEXT-6138-capability-discovery.md +``` -A spec is a living document. When implementation reveals that the approach needs -to change, update the spec before (or alongside) the code change. The associated -Jira ticket is the changelog — link it in the commit message. +Use `specs/features/_template.md` as the starting point. diff --git a/specs/features/capability-discovery.md b/specs/features/CEXT-6138-capability-discovery.md similarity index 100% rename from specs/features/capability-discovery.md rename to specs/features/CEXT-6138-capability-discovery.md From 36cf9b31eb86ea0f2e275360fb8270ae373d83f0 Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Fri, 24 Apr 2026 11:37:02 +0200 Subject: [PATCH 07/17] CEXT-6138: replace RFC terminology with spec process --- AGENTS.md | 6 +++--- specs/conventions.md | 2 +- specs/features/CEXT-6138-capability-discovery.md | 2 +- specs/features/_template.md | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8a1062e6b..c81b92aba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -107,9 +107,9 @@ For source code comments, follow these rules: ## Specs -- Feature specs live in `specs/features/`; use `specs/features/_template.md` as the starting point -- See `specs/conventions.md` for authoring guidelines -- New features should be preceded by a spec before implementation begins +- Specs live in `specs/features/`, named with the Jira ticket prefix (e.g. `CEXT-6138-capability-discovery.md`) +- Use `specs/features/_template.md` as the starting point +- See `specs/conventions.md` for when a spec is required and authoring guidelines ## Changesets diff --git a/specs/conventions.md b/specs/conventions.md index a57cebb9a..f65d93e99 100644 --- a/specs/conventions.md +++ b/specs/conventions.md @@ -2,7 +2,7 @@ ## Why specs -The RFC process provides a consistent path for substantial changes to the SDK so +The spec process provides a consistent path for substantial changes to the SDK so that all stakeholders can be confident about its direction. Many changes — bug fixes, internal refactoring, documentation improvements — can diff --git a/specs/features/CEXT-6138-capability-discovery.md b/specs/features/CEXT-6138-capability-discovery.md index d7cdf2dde..6fde54de9 100644 --- a/specs/features/CEXT-6138-capability-discovery.md +++ b/specs/features/CEXT-6138-capability-discovery.md @@ -1,4 +1,4 @@ -# RFC: Capability Discovery +# Capability Discovery - **Ticket:** [CEXT-6138](https://jira.corp.adobe.com/browse/CEXT-6138) - **Created:** 2026-04-23 diff --git a/specs/features/_template.md b/specs/features/_template.md index 6ff2ae737..745047180 100644 --- a/specs/features/_template.md +++ b/specs/features/_template.md @@ -1,4 +1,4 @@ -# RFC: Feature Name +# spec: Feature Name - **Ticket:** [CEXT-XXXX](https://jira.corp.adobe.com/browse/CEXT-XXXX) - **Created:** YYYY-MM-DD @@ -54,7 +54,7 @@ impact on SDK consumers, and whether it adds complexity for the common case. - What needs to be resolved through discussion before this spec is accepted? - What needs to be resolved during implementation before the feature ships? -- What related problems are explicitly out of scope for this RFC? +- What related problems are explicitly out of scope for this spec? ## Future possibilities From fa71521f02fd0dcdd60be59ce09e111135e9d6a3 Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Fri, 24 Apr 2026 12:01:24 +0200 Subject: [PATCH 08/17] CEXT-6138: refine spec conventions and template --- specs/conventions.md | 35 ++++++++++++++++++- .../CEXT-6138-capability-discovery.md | 13 +++++++ specs/features/_template.md | 4 +++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/specs/conventions.md b/specs/conventions.md index f65d93e99..5549bce12 100644 --- a/specs/conventions.md +++ b/specs/conventions.md @@ -25,7 +25,10 @@ A spec is not required for: - Test or documentation additions - Dependency updates -If in doubt, write a spec. A short spec is better than a surprise. +If in doubt, write a spec. A short spec is better than a surprise. A spec +should be detailed enough to align on the design and unblock implementation, +but not so detailed that it describes code — leave implementation choices to +the implementor. ## Files @@ -37,3 +40,33 @@ specs/features/CEXT-6138-capability-discovery.md ``` Use `specs/features/_template.md` as the starting point. + +## Workflow + +A feature goes through two pull requests: + +1. **Spec PR** — contains only the spec file. Reviewers align on the design + before any implementation begins. Once merged, the spec is considered + approved and the feature is ready to implement. +2. **Implementation PR** — contains the code. References the approved spec. + Once merged, the spec status is updated to _Implemented_. + +This separation ensures design decisions are made explicitly and are not +shaped retroactively by implementation details. + +A spec is not a living document. It represents the design agreed upon at the +time the spec PR was merged. If a subsequent ticket changes the design, it +produces a new spec file — the original remains unchanged as a record of what +was decided and why. This avoids spec drift: the spec always reflects the +intent at the time it was written, not whatever was eventually built. + +### Status + +A spec has no explicit status until it is implemented. The repo state is the +source of truth: + +- **Spec PR open** — the spec is under review. +- **Spec PR merged** — the spec is approved and implementation can begin. +- **Implementation PR merged** — check the `Implemented` box in the spec + metadata as part of the implementation PR. This is the only status transition + that requires an explicit change. diff --git a/specs/features/CEXT-6138-capability-discovery.md b/specs/features/CEXT-6138-capability-discovery.md index 6fde54de9..220417a01 100644 --- a/specs/features/CEXT-6138-capability-discovery.md +++ b/specs/features/CEXT-6138-capability-discovery.md @@ -2,6 +2,7 @@ - **Ticket:** [CEXT-6138](https://jira.corp.adobe.com/browse/CEXT-6138) - **Created:** 2026-04-23 +- **Implemented:** [ ] ## Summary @@ -23,6 +24,18 @@ and inspects the response shape to decide which UI features to show or hide. Eve new SDK capability requires a corresponding change in `commerce-app-management` to detect it. This does not scale as the number of clients and SDK features grows. +**Goals:** + +- Give any authenticated client a stable, versioned description of which endpoints are active for a given deployment. +- Remove the need for clients to infer feature availability by parsing response shapes or probing endpoints. +- Keep the SDK as the single source of truth for the API contract. + +**Non-goals:** + +- Providing a general API documentation portal or developer-facing reference — the full spec in the SDK repository serves that purpose. +- Runtime introspection of config-dependent availability — endpoint availability is determined at build time and is fixed for a given deployment. +- Client code generation from the spec. + ## Developer experience An authenticated client fetches `/__metadata__/openapi.json` with a valid IMS diff --git a/specs/features/_template.md b/specs/features/_template.md index 745047180..86d44b90f 100644 --- a/specs/features/_template.md +++ b/specs/features/_template.md @@ -2,6 +2,7 @@ - **Ticket:** [CEXT-XXXX](https://jira.corp.adobe.com/browse/CEXT-XXXX) - **Created:** YYYY-MM-DD +- **Implemented:** [ ] ## Summary @@ -14,6 +15,9 @@ with the SDK. Include necessary background and specific use cases where this feature helps. This is the most important section — be concrete about the pain point before proposing a solution. +Close the section with explicit goals (what success looks like) and non-goals +(what is explicitly out of scope), so that the design that follows is bounded. + ## Developer experience Explain the feature as if it were already shipped and you were showing it to an From fe7a753e2c818802d15631cdea925e273ba19e44 Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Fri, 24 Apr 2026 12:09:54 +0200 Subject: [PATCH 09/17] CEXT-6138: move filtering details to unresolved questions --- specs/features/CEXT-6138-capability-discovery.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/specs/features/CEXT-6138-capability-discovery.md b/specs/features/CEXT-6138-capability-discovery.md index 220417a01..af849ff32 100644 --- a/specs/features/CEXT-6138-capability-discovery.md +++ b/specs/features/CEXT-6138-capability-discovery.md @@ -86,14 +86,6 @@ Availability is determined by what the app developer declares in place where both the full SDK spec and the app's config are available simultaneously. -The filtering logic mirrors what already drives `ext.config.yaml` generation: - -| Config domain active | Endpoints included | -| ------------------------------------------------------------------------------------ | ---------------------- | -| Always | `app-config` | -| `eventing.commerce` or `eventing.external` or `installation.customInstallationSteps` | `installation` | -| `businessConfig` | `config`, `scope-tree` | - ### 3. Metadata action The filtered `openapi.json` is served by a dedicated `metadata` runtime action @@ -139,7 +131,10 @@ Alternatives considered: ## Unresolved questions -None. +- Which config domains map to which endpoints in the filtered spec — to be + defined during implementation based on the state of the SDK at that time. +- How does the `metadata` action serve the generated file at runtime — is it + bundled at build time, read from disk, or generated inline at request time? ## Future possibilities From 544cb0b68b6ccbeb10c630a152c005e4db269d61 Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Fri, 24 Apr 2026 12:15:35 +0200 Subject: [PATCH 10/17] CEXT-6138: resolve metadata action serving approach in spec Co-Authored-By: Claude Sonnet 4.6 --- specs/features/CEXT-6138-capability-discovery.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/specs/features/CEXT-6138-capability-discovery.md b/specs/features/CEXT-6138-capability-discovery.md index af849ff32..b5e2911e2 100644 --- a/specs/features/CEXT-6138-capability-discovery.md +++ b/specs/features/CEXT-6138-capability-discovery.md @@ -93,6 +93,18 @@ protected by the same IMS authentication as the rest of the SDK endpoints. Suggested path: `/__metadata__/openapi.json` +The filtered spec is bundled into the action at build time, following the same +pattern used by other SDK actions (e.g. `app-config`, `config`): `pre-app-build` +writes `openapi.json` to a well-known path, and the generated action template +imports it statically: + +```js +import openApiSpec from "../../openapi.json" with { type: "json" }; +``` + +The bundler inlines the JSON at build time; no disk read occurs at request time. +The action factory receives the spec object and returns it in the response body. + ### 4. SDK upgrades When an app upgrades its `aio-commerce-lib-app` dependency, the full `openapi.json` @@ -133,8 +145,6 @@ Alternatives considered: - Which config domains map to which endpoints in the filtered spec — to be defined during implementation based on the state of the SDK at that time. -- How does the `metadata` action serve the generated file at runtime — is it - bundled at build time, read from disk, or generated inline at request time? ## Future possibilities From 46d49b7af9351e38d80d35404ff26ff3df3ae95c Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Fri, 24 Apr 2026 12:17:56 +0200 Subject: [PATCH 11/17] include specs/ in root format:markdown script Co-Authored-By: Claude Sonnet 4.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 854537b87..243a041ec 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "code:fix": "turbo run code:fix", "docs": "turbo run docs", "format": "turbo run format", - "format:markdown": "turbo run format:markdown", + "format:markdown": "turbo run format:markdown && prettier --no-error-on-unmatched-pattern --write 'specs/**/*.md'", "format:check": "turbo run format:check", "lint": "turbo run lint", "lint:fix": "turbo run lint:fix", From 852c219ff387b4b99886593ac7cf724a8b6adbcc Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Fri, 24 Apr 2026 12:18:50 +0200 Subject: [PATCH 12/17] note autoformatting in spec conventions Co-Authored-By: Claude Sonnet 4.6 --- specs/conventions.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/specs/conventions.md b/specs/conventions.md index 5549bce12..1602053f4 100644 --- a/specs/conventions.md +++ b/specs/conventions.md @@ -41,6 +41,10 @@ specs/features/CEXT-6138-capability-discovery.md Use `specs/features/_template.md` as the starting point. +Spec files are auto-formatted by Prettier on every commit (via lint-staged) and +by `pnpm format:markdown`. Focus on content — don't worry about line wrapping or +whitespace. + ## Workflow A feature goes through two pull requests: From 3da1b437ae49e046e05b3afc6c4e32856de3c02e Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Fri, 24 Apr 2026 12:19:49 +0200 Subject: [PATCH 13/17] remove spec: prefix from template title Co-Authored-By: Claude Sonnet 4.6 --- specs/features/_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/features/_template.md b/specs/features/_template.md index 86d44b90f..938fe2752 100644 --- a/specs/features/_template.md +++ b/specs/features/_template.md @@ -1,4 +1,4 @@ -# spec: Feature Name +# Feature Name - **Ticket:** [CEXT-XXXX](https://jira.corp.adobe.com/browse/CEXT-XXXX) - **Created:** YYYY-MM-DD From 47ea7e30d7eb4af554b3da1bc4b73da230f92b12 Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Fri, 24 Apr 2026 12:25:25 +0200 Subject: [PATCH 14/17] move frozen-spec principle to Why specs section Co-Authored-By: Claude Sonnet 4.6 --- specs/conventions.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/conventions.md b/specs/conventions.md index 1602053f4..8fad7c28f 100644 --- a/specs/conventions.md +++ b/specs/conventions.md @@ -30,6 +30,12 @@ should be detailed enough to align on the design and unblock implementation, but not so detailed that it describes code — leave implementation choices to the implementor. +A spec is not a living document. It represents the design agreed upon at the +time the spec PR was merged. If a subsequent ticket changes the design, it +produces a new spec file — the original remains unchanged as a record of what +was decided and why. This avoids spec drift: the spec always reflects the +intent at the time it was written, not whatever was eventually built. + ## Files Specs live in `specs/features/` and are named with the Jira ticket prefix so @@ -58,12 +64,6 @@ A feature goes through two pull requests: This separation ensures design decisions are made explicitly and are not shaped retroactively by implementation details. -A spec is not a living document. It represents the design agreed upon at the -time the spec PR was merged. If a subsequent ticket changes the design, it -produces a new spec file — the original remains unchanged as a record of what -was decided and why. This avoids spec drift: the spec always reflects the -intent at the time it was written, not whatever was eventually built. - ### Status A spec has no explicit status until it is implemented. The repo state is the From ebdd6a21af89947d0a5e00f3c9a23db7903b2793 Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Fri, 24 Apr 2026 12:28:23 +0200 Subject: [PATCH 15/17] move frozen-spec paragraph to Why specs section Co-Authored-By: Claude Sonnet 4.6 --- specs/conventions.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/conventions.md b/specs/conventions.md index 8fad7c28f..8639d128d 100644 --- a/specs/conventions.md +++ b/specs/conventions.md @@ -9,6 +9,12 @@ Many changes — bug fixes, internal refactoring, documentation improvements — go straight to a pull request. But some changes are substantial enough to warrant a design process and explicit alignment before implementation begins. +A spec is not a living document. It represents the design agreed upon at the +time the spec PR was merged. If a subsequent ticket changes the design, it +produces a new spec file — the original remains unchanged as a record of what +was decided and why. This avoids spec drift: the spec always reflects the +intent at the time it was written, not whatever was eventually built. + ## When to write a spec Write a spec when the change is "substantial". This includes: @@ -30,12 +36,6 @@ should be detailed enough to align on the design and unblock implementation, but not so detailed that it describes code — leave implementation choices to the implementor. -A spec is not a living document. It represents the design agreed upon at the -time the spec PR was merged. If a subsequent ticket changes the design, it -produces a new spec file — the original remains unchanged as a record of what -was decided and why. This avoids spec drift: the spec always reflects the -intent at the time it was written, not whatever was eventually built. - ## Files Specs live in `specs/features/` and are named with the Jira ticket prefix so From 17a6c91db4e89b1cdfd39cc4b06c8c0b968b716d Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Thu, 30 Apr 2026 17:21:47 +0200 Subject: [PATCH 16/17] fix implemented checkbox syntax in spec template and capability discovery spec --- specs/features/CEXT-6138-capability-discovery.md | 2 +- specs/features/_template.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/features/CEXT-6138-capability-discovery.md b/specs/features/CEXT-6138-capability-discovery.md index b5e2911e2..81540b6fb 100644 --- a/specs/features/CEXT-6138-capability-discovery.md +++ b/specs/features/CEXT-6138-capability-discovery.md @@ -2,7 +2,7 @@ - **Ticket:** [CEXT-6138](https://jira.corp.adobe.com/browse/CEXT-6138) - **Created:** 2026-04-23 -- **Implemented:** [ ] +- [ ] **Implemented** ## Summary diff --git a/specs/features/_template.md b/specs/features/_template.md index 938fe2752..eb172f2a5 100644 --- a/specs/features/_template.md +++ b/specs/features/_template.md @@ -2,7 +2,7 @@ - **Ticket:** [CEXT-XXXX](https://jira.corp.adobe.com/browse/CEXT-XXXX) - **Created:** YYYY-MM-DD -- **Implemented:** [ ] +- [ ] **Implemented** ## Summary From c85385538e065f19f83bffac9a121c73a95b45d6 Mon Sep 17 00:00:00 2001 From: Oriol Barcelona Date: Thu, 30 Apr 2026 17:31:51 +0200 Subject: [PATCH 17/17] address review comments on spec template and capability discovery spec --- specs/features/CEXT-6138-capability-discovery.md | 3 +++ specs/features/_template.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/specs/features/CEXT-6138-capability-discovery.md b/specs/features/CEXT-6138-capability-discovery.md index 81540b6fb..4ad3ca27f 100644 --- a/specs/features/CEXT-6138-capability-discovery.md +++ b/specs/features/CEXT-6138-capability-discovery.md @@ -145,6 +145,9 @@ Alternatives considered: - Which config domains map to which endpoints in the filtered spec — to be defined during implementation based on the state of the SDK at that time. +- Whether the full `openapi.json` in the SDK package is hand-authored or + generated from the `HttpActionRouter` schemas (e.g. via Standard Schema + + `@standard-community/standard-openapi`) — to be decided during implementation. ## Future possibilities diff --git a/specs/features/_template.md b/specs/features/_template.md index eb172f2a5..4f9f28b30 100644 --- a/specs/features/_template.md +++ b/specs/features/_template.md @@ -28,7 +28,7 @@ app developer using the SDK. This means: - How should developers think about this feature and how does it change how they build apps? - If applicable, show what error messages or warnings look like. -- Does this affect how developers read or maintain existing app code? +- Does this affect how developers read or maintain their existing Commerce app? ## Design