@endora-commerce/mod-credentials 0.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/dist/admin/api/credentials-client.d.ts +11 -0
  4. package/dist/admin/api/credentials-client.d.ts.map +1 -0
  5. package/dist/admin/api/credentials-client.js +38 -0
  6. package/dist/admin/api/credentials-client.js.map +1 -0
  7. package/dist/admin/components/ConfigurationForm.d.ts +22 -0
  8. package/dist/admin/components/ConfigurationForm.d.ts.map +1 -0
  9. package/dist/admin/components/ConfigurationForm.js +89 -0
  10. package/dist/admin/components/ConfigurationForm.js.map +1 -0
  11. package/dist/admin/index.d.ts +47 -0
  12. package/dist/admin/index.d.ts.map +1 -0
  13. package/dist/admin/index.js +65 -0
  14. package/dist/admin/index.js.map +1 -0
  15. package/dist/admin/pages/CredentialsNewPage.d.ts +19 -0
  16. package/dist/admin/pages/CredentialsNewPage.d.ts.map +1 -0
  17. package/dist/admin/pages/CredentialsNewPage.js +22 -0
  18. package/dist/admin/pages/CredentialsNewPage.js.map +1 -0
  19. package/dist/admin/pages/CredentialsPage.d.ts +22 -0
  20. package/dist/admin/pages/CredentialsPage.d.ts.map +1 -0
  21. package/dist/admin/pages/CredentialsPage.js +154 -0
  22. package/dist/admin/pages/CredentialsPage.js.map +1 -0
  23. package/dist/admin-ui/ConfigurationPreviewModal.d.ts +16 -0
  24. package/dist/admin-ui/ConfigurationPreviewModal.d.ts.map +1 -0
  25. package/dist/admin-ui/ConfigurationPreviewModal.js +15 -0
  26. package/dist/admin-ui/ConfigurationPreviewModal.js.map +1 -0
  27. package/dist/admin-ui/index.d.ts +57 -0
  28. package/dist/admin-ui/index.d.ts.map +1 -0
  29. package/dist/admin-ui/index.js +56 -0
  30. package/dist/admin-ui/index.js.map +1 -0
  31. package/dist/backend/commands/create-configuration.command.d.ts +20 -0
  32. package/dist/backend/commands/create-configuration.command.d.ts.map +1 -0
  33. package/dist/backend/commands/create-configuration.command.js +70 -0
  34. package/dist/backend/commands/create-configuration.command.js.map +1 -0
  35. package/dist/backend/commands/delete-configuration.command.d.ts +16 -0
  36. package/dist/backend/commands/delete-configuration.command.d.ts.map +1 -0
  37. package/dist/backend/commands/delete-configuration.command.js +48 -0
  38. package/dist/backend/commands/delete-configuration.command.js.map +1 -0
  39. package/dist/backend/commands/update-configuration.command.d.ts +20 -0
  40. package/dist/backend/commands/update-configuration.command.d.ts.map +1 -0
  41. package/dist/backend/commands/update-configuration.command.js +94 -0
  42. package/dist/backend/commands/update-configuration.command.js.map +1 -0
  43. package/dist/backend/entities/credential-configuration.entity.d.ts +30 -0
  44. package/dist/backend/entities/credential-configuration.entity.d.ts.map +1 -0
  45. package/dist/backend/entities/credential-configuration.entity.js +84 -0
  46. package/dist/backend/entities/credential-configuration.entity.js.map +1 -0
  47. package/dist/backend/index.d.ts +73 -0
  48. package/dist/backend/index.d.ts.map +1 -0
  49. package/dist/backend/index.js +80 -0
  50. package/dist/backend/index.js.map +1 -0
  51. package/dist/backend/plugin.d.ts +43 -0
  52. package/dist/backend/plugin.d.ts.map +1 -0
  53. package/dist/backend/plugin.js +25 -0
  54. package/dist/backend/plugin.js.map +1 -0
  55. package/dist/backend/routes.admin.d.ts +27 -0
  56. package/dist/backend/routes.admin.d.ts.map +1 -0
  57. package/dist/backend/routes.admin.js +50 -0
  58. package/dist/backend/routes.admin.js.map +1 -0
  59. package/dist/backend/services/configuration-type-registry.d.ts +105 -0
  60. package/dist/backend/services/configuration-type-registry.d.ts.map +1 -0
  61. package/dist/backend/services/configuration-type-registry.js +111 -0
  62. package/dist/backend/services/configuration-type-registry.js.map +1 -0
  63. package/dist/backend/services/credentials.service.d.ts +63 -0
  64. package/dist/backend/services/credentials.service.d.ts.map +1 -0
  65. package/dist/backend/services/credentials.service.js +128 -0
  66. package/dist/backend/services/credentials.service.js.map +1 -0
  67. package/dist/backend/services/field-validator.d.ts +65 -0
  68. package/dist/backend/services/field-validator.d.ts.map +1 -0
  69. package/dist/backend/services/field-validator.js +165 -0
  70. package/dist/backend/services/field-validator.js.map +1 -0
  71. package/dist/backend/services/registry-singleton.d.ts +28 -0
  72. package/dist/backend/services/registry-singleton.d.ts.map +1 -0
  73. package/dist/backend/services/registry-singleton.js +29 -0
  74. package/dist/backend/services/registry-singleton.js.map +1 -0
  75. package/dist/backend/services/secret-value-codec.d.ts +55 -0
  76. package/dist/backend/services/secret-value-codec.d.ts.map +1 -0
  77. package/dist/backend/services/secret-value-codec.js +105 -0
  78. package/dist/backend/services/secret-value-codec.js.map +1 -0
  79. package/dist/backend/types/email-adapter.type.d.ts +10 -0
  80. package/dist/backend/types/email-adapter.type.d.ts.map +1 -0
  81. package/dist/backend/types/email-adapter.type.js +49 -0
  82. package/dist/backend/types/email-adapter.type.js.map +1 -0
  83. package/dist/backend/types/llm.type.d.ts +3 -0
  84. package/dist/backend/types/llm.type.d.ts.map +1 -0
  85. package/dist/backend/types/llm.type.js +35 -0
  86. package/dist/backend/types/llm.type.js.map +1 -0
  87. package/dist/manifest.d.ts +176 -0
  88. package/dist/manifest.d.ts.map +1 -0
  89. package/dist/manifest.js +167 -0
  90. package/dist/manifest.js.map +1 -0
  91. package/dist/migrations/20260721T011509_credentials_init.d.ts +17 -0
  92. package/dist/migrations/20260721T011509_credentials_init.d.ts.map +1 -0
  93. package/dist/migrations/20260721T011509_credentials_init.js +36 -0
  94. package/dist/migrations/20260721T011509_credentials_init.js.map +1 -0
  95. package/dist/migrations/index.d.ts +27 -0
  96. package/dist/migrations/index.d.ts.map +1 -0
  97. package/dist/migrations/index.js +29 -0
  98. package/dist/migrations/index.js.map +1 -0
  99. package/docs/credentials.md +202 -0
  100. package/i18n/en.json +47 -0
  101. package/i18n/pl.json +47 -0
  102. package/package.json +98 -0
  103. package/tailwind.css +16 -0
@@ -0,0 +1,73 @@
1
+ import type { FastifyRequest } from 'fastify';
2
+ import type { EntityManager } from '@mikro-orm/postgresql';
3
+ import type { CommandBus } from '@endora-commerce/platform/commands';
4
+ import type { ModuleContext } from '@endora-commerce/platform/kernel';
5
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
6
+ import type { ConfigurationTypeRegistry } from './services/configuration-type-registry.js';
7
+ import { CredentialsService, type CredentialsSettingsPort } from './services/credentials.service.js';
8
+ import { CredentialConfiguration } from './entities/credential-configuration.entity.js';
9
+ /**
10
+ * `credentials` — the module three others read secrets through (feature 072,
11
+ * wave 1).
12
+ *
13
+ * `search`, `newsletter` and `prompt_actions` resolve `credentialsService` for
14
+ * the `credential_ref` path, so it is a **port**: a module reaching for a
15
+ * decrypted secret while `credentials` is absent gets an explicit 503 rather
16
+ * than a service that half-answers.
17
+ *
18
+ * One input stays with the composition root: **`adminContextResolver`** answers
19
+ * "who is this admin", which production and the harness resolve differently.
20
+ * That difference is exactly what the root exists to hold.
21
+ *
22
+ * The secret key is read from the environment here rather than threaded
23
+ * through two roots: it is configuration, and both roots were passing the same
24
+ * `process.env` lookup through identical spread expressions.
25
+ *
26
+ * **`configurationTypeRegistry` came home in T143a.** Both roots registered the
27
+ * name — importing this module's singleton to do it — and then pushed four
28
+ * descriptors into it, two of which are this module's own. Which configuration
29
+ * types exist is decided by which modules a deployment ships, but that is a
30
+ * statement about the *contributors*, not about the registry: each of them
31
+ * declares its own type from its own boot hook now, and the registry is
32
+ * declared here, by the module that owns the class.
33
+ *
34
+ * It is `ctx.di.register` rather than `ctx.di.providePort`, deliberately. A
35
+ * gated port throws `MODULE_DISABLED` on resolution, and the two other
36
+ * contributors resolve this name from `ctx.onBoot` — so gating it would turn
37
+ * "an operator switched `credentials` off" into "the platform does not boot".
38
+ * Nothing leaks by leaving it ungated: a descriptor is inert data, and the
39
+ * service that reads secrets out of it (`credentialsService`) is a port and
40
+ * does fail closed.
41
+ *
42
+ * The instance stays the process-wide singleton rather than becoming one per
43
+ * composition, because it is the documented extension seam an external or
44
+ * overlay module reaches through from an install hook, where no container is
45
+ * available (see `services/registry-singleton.ts`, and
46
+ * `test/integration/credentials/registry-extensibility.test.ts`, which is that
47
+ * path under test).
48
+ */
49
+ export interface CredentialsCradle {
50
+ readonly emFactory: () => EntityManager;
51
+ readonly commandBus: CommandBus;
52
+ readonly requireAdmin: RequireAdminFactory;
53
+ readonly configurationTypeRegistry: ConfigurationTypeRegistry;
54
+ readonly credentialsSettingsPort: CredentialsSettingsPort | undefined;
55
+ readonly adminContextResolver: (req: FastifyRequest) => {
56
+ adminUserId: string;
57
+ };
58
+ readonly credentialsService: CredentialsService;
59
+ }
60
+ export declare function registerModule(ctx: ModuleContext): void;
61
+ /**
62
+ * The module's persisted entity classes, on the `./backend` subpath, as one
63
+ * array and **no named class export** (D-168).
64
+ *
65
+ * This is the shape the platform reads when the package is *installed*: the
66
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
67
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
68
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
69
+ * the package pass of `check-entity-tenant-classification`. A missing array is
70
+ * answered with `[]` — zero entities registered, no error anywhere.
71
+ */
72
+ export declare const entities: (typeof CredentialConfiguration)[];
73
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAErE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AAI3F,OAAO,EACL,kBAAkB,EAClB,KAAK,uBAAuB,EAC7B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AAExF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,yBAAyB,EAAE,yBAAyB,CAAC;IAC9D,QAAQ,CAAC,uBAAuB,EAAE,uBAAuB,GAAG,SAAS,CAAC;IACtE,QAAQ,CAAC,oBAAoB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAChF,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;CACjD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAiEvD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,oCAEpB,CAAC"}
@@ -0,0 +1,80 @@
1
+ import { lazyPort } from '@endora-commerce/platform/kernel';
2
+ import { registerCredentialsAdminRoutes } from './routes.admin.js';
3
+ import { configurationTypeRegistry } from './services/registry-singleton.js';
4
+ import { llmConfigurationType } from './types/llm.type.js';
5
+ import { emailAdapterConfigurationType } from './types/email-adapter.type.js';
6
+ import { CredentialsService, } from './services/credentials.service.js';
7
+ import { CredentialConfiguration } from './entities/credential-configuration.entity.js';
8
+ export function registerModule(ctx) {
9
+ ctx.di.register({
10
+ configurationTypeRegistry: ctx
11
+ .asFunction(() => configurationTypeRegistry)
12
+ .singleton(),
13
+ });
14
+ ctx.di.providePort('credentialsService', ctx
15
+ .asFunction(({ emFactory, commandBus }) => {
16
+ const key = process.env['SETTINGS_SECRET_ENCRYPTION_KEY'];
17
+ // Read at construction, and that is a real constraint rather than an
18
+ // oversight — see `ALLOWED_CAPTURES` in check-port-dependencies.ts.
19
+ // A presence test cannot be deferred: `lazyPort` would hand back a
20
+ // proxy that is always defined, so the "omit the property" branch
21
+ // below could never be taken. Both roots register this port two lines
22
+ // before they resolve `credentialsService`, and that ordering is what
23
+ // makes the read work. It stops being load-bearing when `settings`
24
+ // converts and provides the port itself.
25
+ const settings = ctx.cradle().credentialsSettingsPort;
26
+ // Spread-built so an absent key or port is an **omitted** property
27
+ // rather than an explicit `undefined`, which `exactOptionalPropertyTypes`
28
+ // treats as a different thing.
29
+ return new CredentialsService({
30
+ emFactory,
31
+ commandBus,
32
+ registry: lazyPort(ctx, 'configurationTypeRegistry'),
33
+ ...(key === undefined ? {} : { secretEncryptionKey: key }),
34
+ ...(settings === undefined ? {} : { settings }),
35
+ });
36
+ })
37
+ .singleton());
38
+ /**
39
+ * This module's own two configuration types (T143a).
40
+ *
41
+ * `ctx.onBoot` rather than a registration: the registry is *read* — by the
42
+ * type catalogue endpoint and by every credential write — and a registration
43
+ * declares without resolving. Boot hooks run once every module has
44
+ * registered and before any request is served, so a type is in the catalogue
45
+ * from the first one.
46
+ */
47
+ ctx.onBoot(() => {
48
+ const registry = lazyPort(ctx, 'configurationTypeRegistry');
49
+ registry.register(llmConfigurationType);
50
+ registry.register(emailAdapterConfigurationType);
51
+ });
52
+ ctx.routes(async (app) => {
53
+ const { requireAdmin, adminContextResolver } = ctx.cradle();
54
+ await registerCredentialsAdminRoutes(app, {
55
+ // Lazily, even though the module owns this port: route *registration* runs
56
+ // inside `buildServer` whatever the module's effective state is, so
57
+ // destructuring the gate here would stop the next start instead of
58
+ // stopping the routes (D-40). The construction-time read of
59
+ // `credentialsSettingsPort` above is a different thing and stays.
60
+ service: lazyPort(ctx, 'credentialsService'),
61
+ requireAdmin,
62
+ resolveAdminContext: adminContextResolver,
63
+ });
64
+ });
65
+ }
66
+ /**
67
+ * The module's persisted entity classes, on the `./backend` subpath, as one
68
+ * array and **no named class export** (D-168).
69
+ *
70
+ * This is the shape the platform reads when the package is *installed*: the
71
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
72
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
73
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
74
+ * the package pass of `check-entity-tenant-classification`. A missing array is
75
+ * answered with `[]` — zero entities registered, no error anywhere.
76
+ */
77
+ export const entities = [
78
+ CredentialConfiguration,
79
+ ];
80
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAE5D,OAAO,EAAE,8BAA8B,EAAE,MAAM,mBAAmB,CAAC;AAEnE,OAAO,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,EACL,kBAAkB,GAEnB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AAqDxF,MAAM,UAAU,cAAc,CAAC,GAAkB;IAC/C,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC;QACd,yBAAyB,EAAE,GAAG;aAC3B,UAAU,CAAC,GAA8B,EAAE,CAAC,yBAAyB,CAAC;aACtE,SAAS,EAAE;KACf,CAAC,CAAC;IAEH,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,oBAAoB,EACpB,GAAG;SACA,UAAU,CACT,CAAC,EAAE,SAAS,EAAE,UAAU,EAAqB,EAAE,EAAE;QAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAC1D,qEAAqE;QACrE,oEAAoE;QACpE,mEAAmE;QACnE,kEAAkE;QAClE,sEAAsE;QACtE,sEAAsE;QACtE,mEAAmE;QACnE,yCAAyC;QACzC,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAqB,CAAC,uBAAuB,CAAC;QACzE,mEAAmE;QACnE,0EAA0E;QAC1E,+BAA+B;QAC/B,OAAO,IAAI,kBAAkB,CAAC;YAC5B,SAAS;YACT,UAAU;YACV,QAAQ,EAAE,QAAQ,CAA4B,GAAG,EAAE,2BAA2B,CAAC;YAC/E,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,mBAAmB,EAAE,GAAG,EAAE,CAAC;YAC1D,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;SACO,CAAC,CAAC;IAC5D,CAAC,CACF;SACA,SAAS,EAAE,CACf,CAAC;IAEF;;;;;;;;OAQG;IACH,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE;QACd,MAAM,QAAQ,GAAG,QAAQ,CAA4B,GAAG,EAAE,2BAA2B,CAAC,CAAC;QACvF,QAAQ,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC;QACxC,QAAQ,CAAC,QAAQ,CAAC,6BAA6B,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACvB,MAAM,EAAE,YAAY,EAAE,oBAAoB,EAAE,GAAG,GAAG,CAAC,MAAM,EAAqB,CAAC;QAC/E,MAAM,8BAA8B,CAAC,GAAG,EAAE;YACxC,2EAA2E;YAC3E,oEAAoE;YACpE,mEAAmE;YACnE,4DAA4D;YAC5D,kEAAkE;YAClE,OAAO,EAAE,QAAQ,CAAqB,GAAG,EAAE,oBAAoB,CAAC;YAChE,YAAY;YACZ,mBAAmB,EAAE,oBAAoB;SAC1C,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,uBAAuB;CACxB,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { FastifyInstance, FastifyRequest } from 'fastify';
2
+ import type { EntityManager } from '@mikro-orm/postgresql';
3
+ import type { CommandBus } from '@endora-commerce/platform/commands';
4
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
5
+ import type { ConfigurationTypeRegistry } from './services/configuration-type-registry.js';
6
+ import { CredentialsService, type CredentialsSettingsPort } from './services/credentials.service.js';
7
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
8
+ /**
9
+ * Composition root for the credentials module — feature 058.
10
+ *
11
+ * The module owns a `credential_configurations` table and reads configuration
12
+ * types from the process-wide `ConfigurationTypeRegistry` (the cross-module
13
+ * extension seam). Core types (LLM, email adapter) are registered in
14
+ * `composition.ts` at boot (US1), never here.
15
+ */
16
+ export type { CredentialsSettingsPort };
17
+ export interface CredentialsModuleOptions {
18
+ emFactory: () => EntityManager;
19
+ /** The settings port backing the delete-integrity guard (Principle I, FR-012). */
20
+ settings?: CredentialsSettingsPort;
21
+ permissionService: {
22
+ hasPermission(adminUserId: string, permission: string): Promise<boolean>;
23
+ };
24
+ requireAdmin: RequireAdminFactory;
25
+ resolveAdminContext: (req: FastifyRequest) => {
26
+ adminUserId: string;
27
+ };
28
+ commandBus: CommandBus;
29
+ configurationTypeRegistry: ConfigurationTypeRegistry;
30
+ auditLogService?: AuditPort;
31
+ /** Base64 AES-256 key for secret fields; shared with settings (research §R4). */
32
+ secretEncryptionKey?: string;
33
+ }
34
+ export interface CredentialsModuleHandle {
35
+ configurationTypeRegistry: ConfigurationTypeRegistry;
36
+ service: CredentialsService;
37
+ }
38
+ export interface CredentialsModuleResult {
39
+ plugin: (app: FastifyInstance) => Promise<void>;
40
+ handle: CredentialsModuleHandle;
41
+ }
42
+ export declare function credentialsModule(options: CredentialsModuleOptions): CredentialsModuleResult;
43
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AAC3F,OAAO,EACL,kBAAkB,EAClB,KAAK,uBAAuB,EAC7B,MAAM,mCAAmC,CAAC;AAI3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E;;;;;;;GAOG;AAEH,YAAY,EAAE,uBAAuB,EAAE,CAAC;AAExC,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,iBAAiB,EAAE;QACjB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KAC1E,CAAC;IACF,YAAY,EAAE,mBAAmB,CAAC;IAClC,mBAAmB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,UAAU,EAAE,UAAU,CAAC;IACvB,yBAAyB,EAAE,yBAAyB,CAAC;IACrD,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,yBAAyB,EAAE,yBAAyB,CAAC;IACrD,OAAO,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,EAAE,uBAAuB,CAAC;CACjC;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,uBAAuB,CAsB5F"}
@@ -0,0 +1,25 @@
1
+ import { CredentialsService, } from './services/credentials.service.js';
2
+ import { registerCredentialsAdminRoutes, } from './routes.admin.js';
3
+ export function credentialsModule(options) {
4
+ const service = new CredentialsService({
5
+ emFactory: options.emFactory,
6
+ commandBus: options.commandBus,
7
+ registry: options.configurationTypeRegistry,
8
+ secretEncryptionKey: options.secretEncryptionKey,
9
+ ...(options.settings ? { settings: options.settings } : {}),
10
+ });
11
+ return {
12
+ handle: {
13
+ configurationTypeRegistry: options.configurationTypeRegistry,
14
+ service,
15
+ },
16
+ plugin: async (app) => {
17
+ await registerCredentialsAdminRoutes(app, {
18
+ service,
19
+ requireAdmin: options.requireAdmin,
20
+ resolveAdminContext: options.resolveAdminContext,
21
+ });
22
+ },
23
+ };
24
+ }
25
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,kBAAkB,GAEnB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,8BAA8B,GAC/B,MAAM,mBAAmB,CAAC;AAwC3B,MAAM,UAAU,iBAAiB,CAAC,OAAiC;IACjE,MAAM,OAAO,GAAG,IAAI,kBAAkB,CAAC;QACrC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,QAAQ,EAAE,OAAO,CAAC,yBAAyB;QAC3C,mBAAmB,EAAE,OAAO,CAAC,mBAAmB;QAChD,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5D,CAAC,CAAC;IAEH,OAAO;QACL,MAAM,EAAE;YACN,yBAAyB,EAAE,OAAO,CAAC,yBAAyB;YAC5D,OAAO;SACR;QACD,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACpB,MAAM,8BAA8B,CAAC,GAAG,EAAE;gBACxC,OAAO;gBACP,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,mBAAmB,EAAE,OAAO,CAAC,mBAAmB;aACjD,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { FastifyInstance, FastifyRequest } from 'fastify';
2
+ import type { CredentialsService } from './services/credentials.service.js';
3
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
4
+ /**
5
+ * Admin HTTP surface for the credentials module — feature 058
6
+ * (contracts/admin-api.md).
7
+ *
8
+ * GET /api/v1/admin/credentials/types
9
+ * GET /api/v1/admin/credentials (?type=)
10
+ * GET /api/v1/admin/credentials/:code
11
+ * POST /api/v1/admin/credentials
12
+ * PUT /api/v1/admin/credentials/:code
13
+ * DELETE /api/v1/admin/credentials/:code
14
+ * GET /api/v1/admin/credentials/:code/preview
15
+ *
16
+ * Reads gated by `credentials:read`, writes by `credentials:write`. Secrets are
17
+ * masked on every read; only the server-side `resolve` (US2) decrypts.
18
+ */
19
+ export interface CredentialsRoutesDeps {
20
+ service: CredentialsService;
21
+ requireAdmin: RequireAdminFactory;
22
+ resolveAdminContext: (req: FastifyRequest) => {
23
+ adminUserId: string;
24
+ };
25
+ }
26
+ export declare function registerCredentialsAdminRoutes(app: FastifyInstance, deps: CredentialsRoutesDeps): Promise<void>;
27
+ //# sourceMappingURL=routes.admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.d.ts","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAS/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,YAAY,EAAE,mBAAmB,CAAC;IAClC,mBAAmB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CACvE;AAED,wBAAsB,8BAA8B,CAClD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAwEf"}
@@ -0,0 +1,50 @@
1
+ import { ConfigurationListQuerySchema, CreateConfigurationSchema, ERROR_CODES, UpdateConfigurationSchema, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { CREDENTIALS_READ_PERMISSION, CREDENTIALS_WRITE_PERMISSION } from '../manifest.js';
4
+ export async function registerCredentialsAdminRoutes(app, deps) {
5
+ const { service, requireAdmin } = deps;
6
+ const readGuard = requireAdmin(CREDENTIALS_READ_PERMISSION);
7
+ const writeGuard = requireAdmin(CREDENTIALS_WRITE_PERMISSION);
8
+ app.get('/api/v1/admin/credentials/types', { preHandler: readGuard }, async () => ({
9
+ types: service.describeTypes(),
10
+ }));
11
+ app.get('/api/v1/admin/credentials', { preHandler: readGuard }, async (request) => {
12
+ const q = ConfigurationListQuerySchema.parse(request.query ?? {});
13
+ return { configurations: await service.list(q.type) };
14
+ });
15
+ app.get('/api/v1/admin/credentials/:code', { preHandler: readGuard }, async (request) => {
16
+ const dto = await service.getByCode(request.params.code);
17
+ if (!dto) {
18
+ throw new HttpError(404, ERROR_CODES.CREDENTIAL_NOT_FOUND, 'Configuration not found.');
19
+ }
20
+ return dto;
21
+ });
22
+ // Preview target for the settings-field preview button — same masked shape as
23
+ // GET /:code (documented separately in the contract, served identically).
24
+ app.get('/api/v1/admin/credentials/:code/preview', { preHandler: readGuard }, async (request) => {
25
+ const dto = await service.getByCode(request.params.code);
26
+ if (!dto) {
27
+ throw new HttpError(404, ERROR_CODES.CREDENTIAL_NOT_FOUND, 'Configuration not found.');
28
+ }
29
+ return dto;
30
+ });
31
+ app.post('/api/v1/admin/credentials', { preHandler: writeGuard }, async (request, reply) => {
32
+ const body = CreateConfigurationSchema.parse(request.body);
33
+ const dto = await service.create(body);
34
+ return reply.code(201).send(dto);
35
+ });
36
+ app.put('/api/v1/admin/credentials/:code', { preHandler: writeGuard }, async (request) => {
37
+ // `typeCode` / `providerCode` are immutable after create (edge case).
38
+ const raw = (request.body ?? {});
39
+ if ('typeCode' in raw || 'providerCode' in raw) {
40
+ throw new HttpError(422, ERROR_CODES.CREDENTIAL_TYPE_IMMUTABLE, 'The type and provider cannot be changed after creation — create a new configuration instead.');
41
+ }
42
+ const body = UpdateConfigurationSchema.parse(request.body);
43
+ return service.update(request.params.code, body);
44
+ });
45
+ app.delete('/api/v1/admin/credentials/:code', { preHandler: writeGuard }, async (request, reply) => {
46
+ await service.delete(request.params.code);
47
+ return reply.code(204).send();
48
+ });
49
+ }
50
+ //# sourceMappingURL=routes.admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.js","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ import type { ConfigurationTypeDescriptor, ConfigurationTypeRegistryPort } from '@endora-commerce/contracts';
2
+ /**
3
+ * ConfigurationTypeRegistry (feature 058) — in-memory map of configuration-type
4
+ * code → descriptor. This is the documented extension point by which the
5
+ * platform and other/overlay modules contribute configuration types WITHOUT
6
+ * editing the credentials core (Principle XIV / XV).
7
+ *
8
+ * Shape copied from the payment/shipping adapter-registry pattern
9
+ * (`payment_methods/services/payment-adapter-registry.ts`): `register` / `get` /
10
+ * `resolve` / `list`, last-writer-wins + warn on a duplicate code so a
11
+ * re-registration during a hot reload or re-enable does not throw.
12
+ *
13
+ * The credentials service reads a descriptor only to (a) render fields, (b)
14
+ * derive the write-validator, (c) know which fields are `secret`. It MUST NOT
15
+ * branch on a specific `code` / `providerCode` for business meaning — provider
16
+ * meaning lives with the consumer (Principle XIV).
17
+ *
18
+ * **The absent-contributor policy this registry states (issue #129): skip.**
19
+ * The push is ungated — a contributor declares its type from a boot hook, which
20
+ * runs whatever the module's effective state is, and gating the push would turn
21
+ * a deactivation into a permanently missing type — so the presence question is
22
+ * answered here, at enumeration, keyed on `descriptor.ownerModule`.
23
+ *
24
+ * A configuration type is a *surface*: it is what the credentials screen offers
25
+ * to configure and what a write is validated against, so a capability an
26
+ * operator switched off must not be on offer and must not be creatable
27
+ * (Principle XVII). `get`, `resolve`, `list` and `describe` therefore answer as
28
+ * if an absent contributor's type were not registered.
29
+ *
30
+ * `entry`, `ownerOf`, `isRegistered` and `listAll` deliberately do not filter.
31
+ * Switching a module off is not uninstalling it: the configurations an operator
32
+ * already stored keep their rows, so the paths that *render* one and the paths
33
+ * that redact one into an audit snapshot need the field shape whatever the
34
+ * contributor's state, and the admin surface needs the owner's id to say which
35
+ * module is off. Dropping the descriptor there would report a stored credential
36
+ * as having lost its type — the state `unregister` produces, and a different
37
+ * thing entirely.
38
+ *
39
+ * The probe is deliberately **tri-state**, and that is not a convenience: this
40
+ * registry is the seam an overlay or external module pushes a type through, so
41
+ * `ownerModule` may be a string no manifest declares. `isPresent` collapses
42
+ * "switched off" and "not a module" into one `false` — right for a gating seam,
43
+ * wrong here, where it would delete the extension point. `undefined` means the
44
+ * platform says nothing about that id, and the entry is honoured.
45
+ */
46
+ export interface RegistryLogger {
47
+ warn(message: string): void;
48
+ }
49
+ /** Thrown by `resolve` when a type code is not registered (maps to 400). */
50
+ export declare class ConfigurationTypeUnknown extends Error {
51
+ readonly name = "ConfigurationTypeUnknown";
52
+ readonly code: "CREDENTIAL_TYPE_UNKNOWN";
53
+ constructor(typeCode: string);
54
+ }
55
+ /**
56
+ * Whether the platform holds `moduleId` present. `undefined` when the id is not
57
+ * a module the platform knows anything about — see the class doc comment.
58
+ */
59
+ export type ModulePresenceProbe = (moduleId: string) => boolean | undefined;
60
+ /**
61
+ * `implements` the shape feature 075's Phase P published — the two integration
62
+ * modules read the published type, and `tsc` refuses the day a method here
63
+ * stops matching.
64
+ */
65
+ export declare class ConfigurationTypeRegistry implements ConfigurationTypeRegistryPort {
66
+ private readonly log;
67
+ private readonly presenceOfModule;
68
+ private readonly types;
69
+ /**
70
+ * @param log duplicate-code warnings.
71
+ * @param presenceOfModule the effective-state probe. Defaults to "the platform
72
+ * says nothing", so a registry built for a unit test or a bare harness
73
+ * behaves exactly as it did before this filter existed; the process
74
+ * singleton wires it to the kernel's effective state.
75
+ */
76
+ constructor(log?: RegistryLogger, presenceOfModule?: ModulePresenceProbe);
77
+ register(descriptor: ConfigurationTypeDescriptor): void;
78
+ unregister(code: string): void;
79
+ /** Whether any module has contributed this code — presence-blind. */
80
+ isRegistered(code: string): boolean;
81
+ /** The contributed descriptor, presence-blind. Admin rendering and audit read this. */
82
+ entry(code: string): ConfigurationTypeDescriptor | undefined;
83
+ /** The module that contributed `code`, or `null` when nobody did. */
84
+ ownerOf(code: string): string | null;
85
+ /** Registered AND its contributor effectively present. */
86
+ isAvailable(code: string): boolean;
87
+ /** Every contributed descriptor, presence-blind (stable order of insertion). */
88
+ listAll(): ConfigurationTypeDescriptor[];
89
+ /** The descriptor, or `undefined` when unregistered or its contributor is absent. */
90
+ get(code: string): ConfigurationTypeDescriptor | undefined;
91
+ /**
92
+ * The descriptor, or a throw. An unregistered code is a client error and stays
93
+ * {@link ConfigurationTypeUnknown}; a registered one whose contributor is
94
+ * absent is the ordinary 503 envelope, so an operator is told which module is
95
+ * off rather than that a type they can see has become unknown.
96
+ */
97
+ resolve(code: string): ConfigurationTypeDescriptor;
98
+ /** Descriptors whose contributor is present (stable order of insertion). */
99
+ list(): ConfigurationTypeDescriptor[];
100
+ /** Admin-safe view powering `GET /api/v1/admin/credentials/types`. */
101
+ describe(): ConfigurationTypeDescriptor[];
102
+ /** Absent only when the platform knows the contributor and holds it off. */
103
+ private contributorAbsent;
104
+ }
105
+ //# sourceMappingURL=configuration-type-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configuration-type-registry.d.ts","sourceRoot":"","sources":["../../../src/backend/services/configuration-type-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,2BAA2B,EAC3B,6BAA6B,EAC9B,MAAM,4BAA4B,CAAC;AAIpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAMD,4EAA4E;AAC5E,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,SAAkB,IAAI,8BAA8B;IACpD,QAAQ,CAAC,IAAI,4BAAuC;gBACxC,QAAQ,EAAE,MAAM;CAG7B;AAED;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,GAAG,SAAS,CAAC;AAE5E;;;;GAIG;AACH,qBAAa,yBAA0B,YAAW,6BAA6B;IAW3E,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAXnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkD;IAExE;;;;;;OAMG;gBAEgB,GAAG,GAAE,cAA8B,EACnC,gBAAgB,GAAE,mBAAqC;IAG1E,QAAQ,CAAC,UAAU,EAAE,2BAA2B,GAAG,IAAI;IAmBvD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI9B,qEAAqE;IACrE,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAInC,uFAAuF;IACvF,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,2BAA2B,GAAG,SAAS;IAI5D,qEAAqE;IACrE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIpC,0DAA0D;IAC1D,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAKlC,gFAAgF;IAChF,OAAO,IAAI,2BAA2B,EAAE;IAIxC,qFAAqF;IACrF,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,2BAA2B,GAAG,SAAS;IAM1D;;;;;OAKG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,2BAA2B;IAWlD,4EAA4E;IAC5E,IAAI,IAAI,2BAA2B,EAAE;IAIrC,sEAAsE;IACtE,QAAQ,IAAI,2BAA2B,EAAE;IAIzC,4EAA4E;IAC5E,OAAO,CAAC,iBAAiB;CAG1B"}
@@ -0,0 +1,111 @@
1
+ import { ERROR_CODES } from '@endora-commerce/contracts';
2
+ import { ModuleDisabledError } from '@endora-commerce/platform/kernel';
3
+ const consoleLogger = {
4
+ warn: (message) => console.warn(message),
5
+ };
6
+ /** Thrown by `resolve` when a type code is not registered (maps to 400). */
7
+ export class ConfigurationTypeUnknown extends Error {
8
+ name = 'ConfigurationTypeUnknown';
9
+ code = ERROR_CODES.CREDENTIAL_TYPE_UNKNOWN;
10
+ constructor(typeCode) {
11
+ super(`ConfigurationTypeRegistry: no configuration type registered for code "${typeCode}".`);
12
+ }
13
+ }
14
+ /**
15
+ * `implements` the shape feature 075's Phase P published — the two integration
16
+ * modules read the published type, and `tsc` refuses the day a method here
17
+ * stops matching.
18
+ */
19
+ export class ConfigurationTypeRegistry {
20
+ log;
21
+ presenceOfModule;
22
+ types = new Map();
23
+ /**
24
+ * @param log duplicate-code warnings.
25
+ * @param presenceOfModule the effective-state probe. Defaults to "the platform
26
+ * says nothing", so a registry built for a unit test or a bare harness
27
+ * behaves exactly as it did before this filter existed; the process
28
+ * singleton wires it to the kernel's effective state.
29
+ */
30
+ constructor(log = consoleLogger, presenceOfModule = () => undefined) {
31
+ this.log = log;
32
+ this.presenceOfModule = presenceOfModule;
33
+ }
34
+ register(descriptor) {
35
+ const existing = this.types.get(descriptor.code);
36
+ // Re-registering the *same* descriptor overwrites nothing, so it warns
37
+ // about nothing. That distinction started to matter in T143a, when the four
38
+ // core registrations moved out of the composition roots and into the boot
39
+ // hooks of the modules that own the types: this registry is process-wide
40
+ // (it is the seam an overlay module's install hook imports), while a test
41
+ // run composes it several hundred times, so each of those hooks re-declares
42
+ // an identical descriptor once per composition. A genuine override — a
43
+ // different descriptor under a code someone already claimed — still warns.
44
+ if (existing === descriptor)
45
+ return;
46
+ if (existing !== undefined) {
47
+ this.log.warn(`ConfigurationTypeRegistry: type "${descriptor.code}" re-registered; overwriting previous registration.`);
48
+ }
49
+ this.types.set(descriptor.code, descriptor);
50
+ }
51
+ unregister(code) {
52
+ this.types.delete(code);
53
+ }
54
+ /** Whether any module has contributed this code — presence-blind. */
55
+ isRegistered(code) {
56
+ return this.types.has(code);
57
+ }
58
+ /** The contributed descriptor, presence-blind. Admin rendering and audit read this. */
59
+ entry(code) {
60
+ return this.types.get(code);
61
+ }
62
+ /** The module that contributed `code`, or `null` when nobody did. */
63
+ ownerOf(code) {
64
+ return this.types.get(code)?.ownerModule ?? null;
65
+ }
66
+ /** Registered AND its contributor effectively present. */
67
+ isAvailable(code) {
68
+ const descriptor = this.types.get(code);
69
+ return descriptor !== undefined && !this.contributorAbsent(descriptor);
70
+ }
71
+ /** Every contributed descriptor, presence-blind (stable order of insertion). */
72
+ listAll() {
73
+ return [...this.types.values()];
74
+ }
75
+ /** The descriptor, or `undefined` when unregistered or its contributor is absent. */
76
+ get(code) {
77
+ const descriptor = this.types.get(code);
78
+ if (!descriptor || this.contributorAbsent(descriptor))
79
+ return undefined;
80
+ return descriptor;
81
+ }
82
+ /**
83
+ * The descriptor, or a throw. An unregistered code is a client error and stays
84
+ * {@link ConfigurationTypeUnknown}; a registered one whose contributor is
85
+ * absent is the ordinary 503 envelope, so an operator is told which module is
86
+ * off rather than that a type they can see has become unknown.
87
+ */
88
+ resolve(code) {
89
+ const descriptor = this.types.get(code);
90
+ if (!descriptor) {
91
+ throw new ConfigurationTypeUnknown(code);
92
+ }
93
+ if (this.contributorAbsent(descriptor)) {
94
+ throw new ModuleDisabledError(descriptor.ownerModule);
95
+ }
96
+ return descriptor;
97
+ }
98
+ /** Descriptors whose contributor is present (stable order of insertion). */
99
+ list() {
100
+ return this.listAll().filter((descriptor) => !this.contributorAbsent(descriptor));
101
+ }
102
+ /** Admin-safe view powering `GET /api/v1/admin/credentials/types`. */
103
+ describe() {
104
+ return this.list();
105
+ }
106
+ /** Absent only when the platform knows the contributor and holds it off. */
107
+ contributorAbsent(descriptor) {
108
+ return this.presenceOfModule(descriptor.ownerModule) === false;
109
+ }
110
+ }
111
+ //# sourceMappingURL=configuration-type-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configuration-type-registry.js","sourceRoot":"","sources":["../../../src/backend/services/configuration-type-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type ConfigurationDto, type ConfigurationReference, type ConfigurationTypeDescriptor, type CreateConfiguration, type ResolveResult, type UpdateConfiguration } from '@endora-commerce/contracts';
3
+ import type { CommandBus } from '@endora-commerce/platform/commands';
4
+ import type { ConfigurationTypeRegistry } from './configuration-type-registry.js';
5
+ /**
6
+ * Narrow settings port — the ONLY channel through which credentials reaches the
7
+ * settings module (Principle I). Backs the delete-integrity lookup (FR-012).
8
+ */
9
+ export interface CredentialsSettingsPort {
10
+ listReferencesToConfiguration(configurationCode: string): Promise<ConfigurationReference[]>;
11
+ }
12
+ /**
13
+ * CredentialsService (feature 058) — the module's read + orchestration surface.
14
+ *
15
+ * All writes go through the Command Bus (Principle XIII); every read returns a
16
+ * MASKED DTO (secrets never leave as plaintext, SC-003 / FR-005). A configuration
17
+ * whose `typeCode` is no longer registered is surfaced `inert` (read-only,
18
+ * FR-016).
19
+ *
20
+ * Principle XIV — the core is type-agnostic: it reads a descriptor ONLY to
21
+ * render fields, derive the write-validator, and learn which fields are
22
+ * `secret`. It MUST NOT branch on a specific `typeCode`/`providerCode` for
23
+ * business meaning (provider meaning lives with the consumer). This invariant
24
+ * is grep-asserted in `test/integration/credentials/registry-extensibility.test.ts`.
25
+ */
26
+ export interface CredentialsServiceDeps {
27
+ emFactory: () => EntityManager;
28
+ commandBus: CommandBus;
29
+ registry: ConfigurationTypeRegistry;
30
+ secretEncryptionKey: string | undefined;
31
+ /** Wired in US2 — backs the delete-integrity guard (FR-012). */
32
+ settings?: CredentialsSettingsPort;
33
+ }
34
+ export declare class CredentialsService {
35
+ private readonly deps;
36
+ constructor(deps: CredentialsServiceDeps);
37
+ /** Registered configuration types powering the admin type picker + form. */
38
+ describeTypes(): ConfigurationTypeDescriptor[];
39
+ list(typeFilter?: string): Promise<ConfigurationDto[]>;
40
+ getByCode(code: string): Promise<ConfigurationDto | null>;
41
+ create(data: CreateConfiguration): Promise<ConfigurationDto>;
42
+ update(code: string, data: UpdateConfiguration): Promise<ConfigurationDto>;
43
+ delete(code: string): Promise<void>;
44
+ /**
45
+ * Server-side resolution (feature 058 US2) — the single path that returns
46
+ * DECRYPTED secret values, for a consumer's in-memory use only. Never exposed
47
+ * over HTTP. Fails closed: an unset reference is `not_configured`; a
48
+ * deleted/inert one is `unavailable` — never a foreign configuration
49
+ * (FR-012 / SC-005).
50
+ */
51
+ resolve(configurationCode: string): Promise<ResolveResult>;
52
+ /**
53
+ * Map an entity to a masked DTO, marking unregistered-type rows as inert.
54
+ *
55
+ * The registry's **diagnostic** read (issue #129): a stored configuration
56
+ * whose contributing module is merely switched off keeps its label and its
57
+ * field shape, because deactivation drops no data and an inert row would say
58
+ * the type is gone. `resolve` above stays on the acting read, so nothing
59
+ * decrypted leaves for the same configuration.
60
+ */
61
+ private toDto;
62
+ }
63
+ //# sourceMappingURL=credentials.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/credentials.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EAChC,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAGrE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAOlF;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,6BAA6B,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC;CAC7F;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,yBAAyB,CAAC;IACpC,mBAAmB,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,gEAAgE;IAChE,QAAQ,CAAC,EAAE,uBAAuB,CAAC;CACpC;AAED,qBAAa,kBAAkB;IACjB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,sBAAsB;IAEzD,4EAA4E;IAC5E,aAAa,IAAI,2BAA2B,EAAE;IAIxC,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAOtD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAMzD,MAAM,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAW5D,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAY1E,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBzC;;;;;;OAMG;IACG,OAAO,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAgChE;;;;;;;;OAQG;IACH,OAAO,CAAC,KAAK;CAoBd"}