Skip to content

♻️ Refactor: Prompt service adapter - #16469

Closed
AtefBellaaj wants to merge 1 commit into
devfrom
atef/ai-2154-native-prompt-adapter-spike
Closed

AtefBellaaj wants to merge 1 commit into
devfrom
atef/ai-2154-native-prompt-adapter-spike

Conversation

@AtefBellaaj

@AtefBellaaj AtefBellaaj commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Native prompt behavior is currently coupled to route handlers and database methods. This makes it difficult to add another prompt source without copying selection, protection, and mutation rules.

This draft spike proposes a shared prompt service and a native source adapter for AI-2154. It is intentionally not wired into the HTTP routes. The goal is to review the contracts and test shape before the production refactor.

How it works

caller authorization
  shared prompt service
    native source adapter
      injected native prompt operations
    injected catalog store
    content protection
    creator ownership grant

The service owns validation, operation-specific content protection, catalog projection, Usage, and best-effort creator ownership grants. The native adapter owns Production and exact-revision selection plus one-to-one native reads and mutations. Both boundaries use plain string IDs and injected dependencies.

Type of change

  • Refactor
  • Tests / tooling / CI

Testing

Tested environments/configuration:

  • Node.js: 24.16.0
  • OS: Linux
  • Database: Not required for this contract spike

Automated tests:

  • npm run test:ci -- --runInBand --coverage=false in packages/api: 609 suites passed, 16,090 tests passed, 13 skipped by normal suite filters
  • npx tsc --noEmit in packages/api
  • npm run build:api
  • npm run static-checks:full: all affected checks passed; unused-package check skipped because global depcheck is not installed
  • Added 26 service tests and 9 native adapter tests

Screenshots / recordings

No user-facing change.

Risk / compatibility

This is a draft architecture spike. It exports the proposed service, adapter, and contracts, but no route or runtime caller uses them. It changes no HTTP response, database schema, stored data, or configuration. The shared prompt protection input now accepts the existing nullable command shape.

Checklist

  • I reviewed my own changes
  • Relevant tests have been added or updated
  • Existing relevant tests pass
  • The change does not introduce new warnings or errors
  • User-facing or complex behavior is documented where necessary
  • Required dependency changes have been merged/published
  • Required documentation PR: N/A

@AtefBellaaj

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-28T23:03:43.157838Z 47e56ff Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67a373aa3c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/prompts/service.ts Outdated
Comment on lines +150 to +152
if (input.group.name.trim().length === 0) {
return invalidInput('Prompt and group name are required');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate the initial prompt before creating its group

When a create request contains an empty prompt or an invalid prompt type, this checks only the group name and still calls source.createGroup; unlike addRevision, the identical prompt shape never passes through validateRevisionInput. A native source will reject this at the database layer and turn a client input error into a 500, while a less strict source could persist an unusable revision, so validate the complete creation payload before writing.

AGENTS.md reference: AGENTS.md:L103-L115

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 3a28bcf. Group creation now runs the shared prompt validator before calling the source. The regression test covers whitespace-only text and an invalid prompt type. Focused prompt tests and TypeScript pass.

Comment thread packages/api/src/prompts/service.ts Outdated
readonly updates: TUpdatePromptGroupSchema;
readonly filters?: ResolvePromptInput['filters'];
}): Promise<PromptServiceResult<PromptGroupRecord>> {
const updates = validatePromptGroupUpdate(input.updates);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return invalid update payloads as service results

When an update contains an unknown field, an overlong value, or an invalid command, validatePromptGroupUpdate throws a ZodError even though updateGroup promises a PromptServiceResult. Once the existing route delegates to this service, these expected client errors bypass the invalid_input contract and are liable to become generic 500 responses; use the safe validator and return the typed failure instead.

AGENTS.md reference: AGENTS.md:L103-L115

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 3a28bcf. Group updates now use the existing safe validator and return invalid_input without writing. The regression test covers a strict-schema rejection. Focused prompt tests and TypeScript pass.

@AtefBellaaj

Copy link
Copy Markdown
Collaborator Author

@codex review

Please check commit 3a28bcf. Both prior findings are fixed and covered by regression tests.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3a28bcf23b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/prompts/service.ts Outdated
@AtefBellaaj

Copy link
Copy Markdown
Collaborator Author

@codex review

Please check commit 7c80cc2. The remaining group metadata validation finding is fixed and covered by a regression test.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7c80cc23c3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/prompts/service.ts Outdated
@AtefBellaaj

Copy link
Copy Markdown
Collaborator Author

@codex review

Please check commit 1f80ecc. Validation now runs before the group name is read, and the non-string case has a regression test.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f80eccf5c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/prompts/native.ts Outdated
listRevisions: dependencies.getPrompts,
createGroup: dependencies.createPromptGroup,
addRevision: dependencies.savePrompt,
promoteRevision: dependencies.makePromptProduction,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Translate native promotion failures before reporting success

When this adapter is wired to the current makePromptProduction method, a Mongo read or update failure is caught by packages/data-schemas/src/methods/prompt.ts and resolved as { message: 'Error making prompt production' }, which has the same type as its success response. Directly delegating here lets service.promoteRevision wrap that failure in { ok: true }, so a caller can return success even though the production revision was unchanged; make the native dependency throw or expose a discriminated failure before passing it through the service.

AGENTS.md reference: AGENTS.md:L116-L121

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 98aa11c. The native adapter now accepts only the established production-success response. It throws when the data method returns its error message, so the service cannot wrap a failed mutation as success. A regression test covers the error response.

Comment thread packages/api/src/prompts/service.ts Outdated
function validateRevisionInput<T>(
input: Pick<AddPromptRevisionInput, 'prompt'>,
): PromptServiceResult<T> | null {
if (typeof input.prompt.prompt !== 'string' || input.prompt.prompt.trim().length === 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate the prompt object before reading its fields

For an untrusted create or add-revision payload where prompt is missing or null, this dereference throws a TypeError rather than returning the service's invalid_input result; the existing HTTP route treats this case as a client error. Fresh evidence after 1f80ecc is that validation now safely handles the group name but still reads input.prompt.prompt without first validating input.prompt, so validate the nested object before its fields.

AGENTS.md reference: AGENTS.md:L103-L115

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 98aa11c. The shared revision validator now checks that prompt is a non-null object before reading text or type. Regression coverage includes null creation input and missing add-revision input. The focused 35 tests and TypeScript pass.

@AtefBellaaj

Copy link
Copy Markdown
Collaborator Author

@codex review

Please check commit 98aa11c. Native promotion failures now throw, nested prompt input is safely validated, and both cases have regression tests.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98aa11c428

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/prompts/native.ts Outdated
Comment on lines +83 to +85
const result = await dependencies.makePromptProduction(promptId);
if (result.message !== 'Prompt production made successfully') {
throw new Error(result.message);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject promotions when the group update matches nothing

When an existing prompt references a deleted or tenant-inaccessible group, PromptGroup.findByIdAndUpdate in packages/data-schemas/src/methods/prompt.ts resolves to null without throwing, but the method ignores that result and still returns the exact success message accepted here. The service therefore reports { ok: true } although no production revision changed. Fresh evidence after 98aa11c is that the update's null result remains unchecked; require the native method to verify that a group was updated before returning the success response.

AGENTS.md reference: AGENTS.md:L116-L121

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@codex This adapter is intentionally not wired directly to the current PromptMethods implementation. NativePromptDependencies is a port that requires promotion to reject unless the group update succeeded. The legacy method cannot satisfy that contract because it discards the update result, so AI-2154 must fork the concrete native operation during runtime wiring. Changing the shared legacy method in this isolated spike would affect the old path and violate the repository reuse-or-fork rule. Please check.

Comment thread packages/api/src/prompts/native.ts Outdated
Comment on lines +68 to +70
loadedGroup?._id === groupId ? loadedGroup : await dependencies.getPromptGroup(groupId);
if (group == null || group.productionId == null) {
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve native group lookup failures

When production resolution is backed by the current getPromptGroup data method and its Mongo aggregation fails, that method catches the exception and returns null (packages/data-schemas/src/methods/prompt.ts:724-726). This branch interprets the same value as an absent group, so resolvePrompt reports unavailable_selection instead of propagating the database outage; change the native lookup boundary so failures throw and reserve null for a successful lookup with no record.

AGENTS.md reference: AGENTS.md:L108-L109

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@codex NativePromptDependencies.getPromptGroup already defines null as a successful missing-record result. Promise rejection is the operational-failure path. The current legacy method swallows storage errors, so it does not satisfy this port and cannot be injected directly. AI-2154 owns the forked concrete operation when runtime wiring is added; this draft explicitly has no runtime caller. Changing the old shared method here would affect the existing path and violate the repository reuse-or-fork rule. Please check.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Unknown error
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@AtefBellaaj

Copy link
Copy Markdown
Collaborator Author

@codex review

Please check commit 98aa11c and the two scoped defenses in the latest review threads. The previous review attempt failed with an internal unknown error.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98aa11c428

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/prompts/service.ts Outdated
Comment on lines +100 to +104
const prompt = input.prompt as { readonly prompt?: unknown; readonly type?: unknown };
if (typeof prompt.prompt !== 'string' || prompt.prompt.trim().length === 0) {
return invalidInput('Prompt text is required and must be a non-empty string');
}
if (prompt.type !== 'text' && prompt.type !== 'chat') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject extra fields in revision payloads

When a future HTTP caller passes extra properties under prompt, such as _id or tenantId, this validator accepts them because it checks only prompt and type; createGroup and addRevision then forward the original object at lines 178 and 206 rather than a sanitized value. The native prompt schema explicitly persists tenantId (packages/data-schemas/src/schema/prompt.ts:27-30), and Mongoose also honors a supplied _id, so malformed client input can control stored metadata or trigger duplicate-key operational failures instead of returning invalid_input. Parse with a strict prompt schema and pass only its validated output to the source.

AGENTS.md reference: AGENTS.md:L103-L115

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in e77bd94. Create and add-revision now parse prompt data with one strict shared schema and pass only its validated output to the source. Extra tenantId and _id fields return invalid_input before any write. The focused schema, service, and adapter suites have 62 passing tests, and TypeScript passes.

@AtefBellaaj

Copy link
Copy Markdown
Collaborator Author

@codex review

Please check commit e77bd94 and the two prior boundary defenses. Prompt payloads now use strict shared parsing, forward only validated fields, and include injection regression tests.

@AtefBellaaj

Copy link
Copy Markdown
Collaborator Author

@codex review

Please check commit eb193fb and the two boundary defenses. The strict prompt parser now has explicit isolated-declaration types. The exact API build and 62 focused tests pass.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: eb193fb5c9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@AtefBellaaj

Copy link
Copy Markdown
Collaborator Author

@codex review

Please check commit a5ffa82. The prompt spike is unchanged from the approved eb193fb head. This merge updates the branch to current upstream dev and refreshes the E2E client cache. The 62 focused tests and exact API build pass.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5ffa825ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/prompts/service.ts Outdated
Comment on lines +203 to +205
input.loadedGroup?._id === input.groupId
? input.loadedGroup
: await source.getGroup(input.groupId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor loaded null lookup results

When the caller has already looked up the requested group and passes loadedGroup: null, this condition treats the documented absence as if nothing was loaded and performs another database read. Besides defeating the preload contract, a record created between the two reads can make the service return a different result from the lookup on which the caller based authorization or request handling. Distinguish an omitted preload (undefined) from a supplied null; the same issue occurs for loadedRevision and in the native resolver.

AGENTS.md reference: AGENTS.md:L108-L109

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 47e56ff. A supplied null preload now remains a known missing result in service group reads, revision reads, promotion, and both native resolver paths. None of these paths performs a second lookup. Regression tests cover all affected paths, all 70 focused prompt tests pass, and the API package builds. @codex please check.

@AtefBellaaj

Copy link
Copy Markdown
Collaborator Author

@codex review

Please check commit ecfaff6. It fixes the two E2E regressions found after merging current dev. The steering test now uses the renamed menu labels. ControlCombobox now clears its controlled search when it closes, with unit coverage for reopening the full capped list.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: ecfaff651e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@AtefBellaaj

Copy link
Copy Markdown
Collaborator Author

@codex review

Please check commit 47e56ff. It fixes the valid preload finding across service and native adapter paths. A supplied null now stays a known missing result and never triggers a second lookup. All 70 focused prompt tests pass, and the API package builds.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: 47e56ffc81

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@AtefBellaaj AtefBellaaj changed the title refactor: prototype native prompt service adapter ♻️ Refactor: Prompt service adapter Sep 29, 2026
@AtefBellaaj
AtefBellaaj force-pushed the atef/ai-2154-native-prompt-adapter-spike branch from 47e56ff to 790dd98 Compare September 29, 2026 07:38
@AtefBellaaj
AtefBellaaj force-pushed the atef/ai-2154-native-prompt-adapter-spike branch from c6b0a1b to baedb6a Compare September 29, 2026 12:17
@AtefBellaaj

Copy link
Copy Markdown
Collaborator Author

closed in favor of #16506

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant