Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

- 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

- Every code change that requires a release must include a changeset (test-only changes do not need one)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
76 changes: 76 additions & 0 deletions specs/conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Spec Conventions

Comment thread
obarcelonap marked this conversation as resolved.
## Why specs

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
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:

- 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

A spec is not required for:

- Bug fixes with no API surface change
- Internal refactoring that doesn't affect SDK consumers
- Test or documentation additions
- Dependency updates

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

Specs live in `specs/features/` and are named with the Jira ticket prefix so
they are easy to find:

```
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:

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.
Comment thread
obarcelonap marked this conversation as resolved.
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.

### 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.
158 changes: 158 additions & 0 deletions specs/features/CEXT-6138-capability-discovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Capability Discovery

- **Ticket:** [CEXT-6138](https://jira.corp.adobe.com/browse/CEXT-6138)
- **Created:** 2026-04-23
- [ ] **Implemented**

## Summary

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.

## Motivation

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.

`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.

**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
bearer token:

```http
GET /__metadata__/openapi.json
Authorization: Bearer <ims-token>
```

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.

Other clients (CLI tools, integrations, third-party UIs) follow the same pattern:
obtain an IMS token, fetch the spec, adapt accordingly.

## 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.
Comment thread
obarcelonap marked this conversation as resolved.

### 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 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 since it is the only
place where both the full SDK spec and the app's config are available
simultaneously.

### 3. Metadata action

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`

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`
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.

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.

## Drawbacks

- Adds a new `metadata` runtime action to every SDK-based deployment.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember we discussed how to reduce the number of runtime actions.
Maybe we can add a new endpoint to one of the already existing actions?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only place this would make sense to add is the GET /app-config which is already "taken". We can return it as additional properties maybe tho. For what is worth, if the metadata action is going to return purely static data, the build time should be close to 0. Maybe also the deploy time.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if we use GET /app-config/openapi.json? this is the same discussion we had about adding new actions for uninstallation process and we decided to keep the actions to the minimum.
@oshmyheliuk @iivvaannxx if you agree I change the spec with this approach.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't think of that, sounds good to me

- 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.

## Rationale and alternatives

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.

Alternatives considered:

- **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.

## Unresolved questions

- 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

- 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.
67 changes: 67 additions & 0 deletions specs/features/_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Feature Name

- **Ticket:** [CEXT-XXXX](https://jira.corp.adobe.com/browse/CEXT-XXXX)
- **Created:** YYYY-MM-DD
- [ ] **Implemented**

## Summary

One paragraph explanation of the feature.

## Motivation

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.

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
app developer using the SDK. This means:

- 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 their existing Commerce app?

## Design

The technical detail of the feature. Cover enough that:

- 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.

## 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.

## 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)?

## 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 spec?

## Future possibilities
Comment thread
obarcelonap marked this conversation as resolved.

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.