@wildo-ai/saas-backend-lib 1.1.0 → 1.1.2
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.
- package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.d.ts +14 -3
- package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.js +115 -22
- package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.d.ts +3 -1
- package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.js +8 -3
- package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.d.ts +11 -0
- package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.js +11 -2
- package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.d.ts +1 -0
- package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.js +6 -1
- package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.js +16 -0
- package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.d.ts +20 -6
- package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.js +88 -32
- package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.d.ts +3 -1
- package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.js +32 -7
- package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.d.ts +8 -5
- package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.d.ts +31 -0
- package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.js +127 -5
- package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.d.ts +71 -1
- package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.js +62 -2
- package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.d.ts +21 -0
- package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.js +126 -4
- package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.d.ts +3 -26
- package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.d.ts.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.js +3 -68
- package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.js.map +1 -1
- package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.js +4 -2
- package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.js.map +1 -1
- package/dist/esm/billing/services/custom-impl/billing-account.mutations.custom-impl.backend.service.js +6 -6
- package/dist/esm/billing/services/custom-impl/billing-account.mutations.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.d.ts +38 -0
- package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.d.ts.map +1 -1
- package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.js +40 -0
- package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.js.map +1 -1
- package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.d.ts +20 -0
- package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.d.ts.map +1 -1
- package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.js +28 -0
- package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.js.map +1 -1
- package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.d.ts +12 -0
- package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.d.ts.map +1 -0
- package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.js +118 -0
- package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.js.map +1 -0
- package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.d.ts.map +1 -1
- package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.js +8 -5
- package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.js.map +1 -1
- package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.d.ts.map +1 -1
- package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.js +14 -0
- package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.js.map +1 -1
- package/dist/esm/config/custom/application-config-controller.backend.service.d.ts.map +1 -1
- package/dist/esm/config/custom/application-config-controller.backend.service.js +7 -1
- package/dist/esm/config/custom/application-config-controller.backend.service.js.map +1 -1
- package/dist/esm/config/models/app-config.backend.schemas.d.ts +4 -0
- package/dist/esm/config/models/app-config.backend.schemas.d.ts.map +1 -1
- package/dist/esm/config/services/app-configuration.backend.utils.d.ts +10 -1
- package/dist/esm/config/services/app-configuration.backend.utils.d.ts.map +1 -1
- package/dist/esm/config/services/app-configuration.backend.utils.js +13 -2
- package/dist/esm/config/services/app-configuration.backend.utils.js.map +1 -1
- package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.d.ts +2 -2
- package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.js +2 -2
- package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.js.map +1 -1
- package/dist/esm/databases/models/filter.backend.schemas.js +2 -2
- package/dist/esm/databases/models/filter.backend.schemas.js.map +1 -1
- package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.d.ts.map +1 -1
- package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.js +20 -1
- package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.js.map +1 -1
- package/dist/esm/databases/services/redis.service.d.ts +27 -0
- package/dist/esm/databases/services/redis.service.d.ts.map +1 -1
- package/dist/esm/databases/services/redis.service.js +29 -0
- package/dist/esm/databases/services/redis.service.js.map +1 -1
- package/dist/esm/errors/services/error-handler.backend.service.d.ts.map +1 -1
- package/dist/esm/errors/services/error-handler.backend.service.js +5 -0
- package/dist/esm/errors/services/error-handler.backend.service.js.map +1 -1
- package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.d.ts +41 -0
- package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.d.ts.map +1 -0
- package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.js +40 -0
- package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.js.map +1 -0
- package/dist/esm/external-data/controllers/external-data-admin.backend.controller.d.ts +64 -0
- package/dist/esm/external-data/controllers/external-data-admin.backend.controller.d.ts.map +1 -0
- package/dist/esm/external-data/controllers/external-data-admin.backend.controller.js +193 -0
- package/dist/esm/external-data/controllers/external-data-admin.backend.controller.js.map +1 -0
- package/dist/esm/external-data/external-data-pipeline-run.backend.service.d.ts +13 -2
- package/dist/esm/external-data/external-data-pipeline-run.backend.service.d.ts.map +1 -1
- package/dist/esm/external-data/external-data-pipeline-run.backend.service.js +8 -2
- package/dist/esm/external-data/external-data-pipeline-run.backend.service.js.map +1 -1
- package/dist/esm/external-data/external-data-transform.backend.d.ts.map +1 -1
- package/dist/esm/external-data/external-data-transform.backend.js +7 -1
- package/dist/esm/external-data/external-data-transform.backend.js.map +1 -1
- package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.d.ts +2 -0
- package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.d.ts.map +1 -1
- package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.js +13 -0
- package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.js.map +1 -1
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts +14 -0
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts.map +1 -1
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.js +6 -0
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.js.map +1 -1
- package/dist/esm/files/controllers/file.controller.d.ts +24 -1
- package/dist/esm/files/controllers/file.controller.d.ts.map +1 -1
- package/dist/esm/files/controllers/file.controller.js +66 -2
- package/dist/esm/files/controllers/file.controller.js.map +1 -1
- package/dist/esm/files/handlers/file-upload-route-locals.backend.types.d.ts +17 -0
- package/dist/esm/files/handlers/file-upload-route-locals.backend.types.d.ts.map +1 -0
- package/dist/esm/files/handlers/file-upload-route-locals.backend.types.js +1 -0
- package/dist/esm/files/handlers/file-upload-route-locals.backend.types.js.map +1 -0
- package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.d.ts +5 -1
- package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.d.ts.map +1 -1
- package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.js +42 -3
- package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.js.map +1 -1
- package/dist/esm/files/services/file-operation-handler.backend.service.d.ts +18 -0
- package/dist/esm/files/services/file-operation-handler.backend.service.d.ts.map +1 -1
- package/dist/esm/files/services/file-operation-handler.backend.service.js +36 -28
- package/dist/esm/files/services/file-operation-handler.backend.service.js.map +1 -1
- package/dist/esm/files/services/file-upload-grant.backend.service.d.ts +163 -0
- package/dist/esm/files/services/file-upload-grant.backend.service.d.ts.map +1 -0
- package/dist/esm/files/services/file-upload-grant.backend.service.js +442 -0
- package/dist/esm/files/services/file-upload-grant.backend.service.js.map +1 -0
- package/dist/esm/files/services/files.backend.service.d.ts +19 -0
- package/dist/esm/files/services/files.backend.service.d.ts.map +1 -1
- package/dist/esm/files/services/files.backend.service.js +78 -15
- package/dist/esm/files/services/files.backend.service.js.map +1 -1
- package/dist/esm/files/utils/file-uploader.utils.d.ts.map +1 -1
- package/dist/esm/files/utils/file-uploader.utils.js +12 -0
- package/dist/esm/files/utils/file-uploader.utils.js.map +1 -1
- package/dist/esm/flows-actors/a2a/services/a2a-task-cancellation.backend.service.js +5 -5
- package/dist/esm/flows-actors/a2a/services/a2a-task-cancellation.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/services/agents.backend.service.d.ts +79 -0
- package/dist/esm/flows-actors/services/agents.backend.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/agents.backend.service.js +319 -42
- package/dist/esm/flows-actors/services/agents.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts +151 -41
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts.map +1 -1
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js +300 -139
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js.map +1 -1
- package/dist/esm/index.d.ts +4 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +14 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/inject/models/container.d.ts.map +1 -1
- package/dist/esm/inject/models/container.js +4 -0
- package/dist/esm/inject/models/container.js.map +1 -1
- package/dist/esm/inject/models/service-types.d.ts +3 -0
- package/dist/esm/inject/models/service-types.d.ts.map +1 -1
- package/dist/esm/inject/models/service-types.js +2 -0
- package/dist/esm/inject/models/service-types.js.map +1 -1
- package/dist/esm/mcp/controllers/mcp.controller.d.ts.map +1 -1
- package/dist/esm/mcp/controllers/mcp.controller.js +12 -4
- package/dist/esm/mcp/controllers/mcp.controller.js.map +1 -1
- package/dist/esm/mcp/models/mcp-tool.backend.schemas.d.ts +34 -0
- package/dist/esm/mcp/models/mcp-tool.backend.schemas.d.ts.map +1 -1
- package/dist/esm/mcp/models/mcp-tool.backend.schemas.js +42 -0
- package/dist/esm/mcp/models/mcp-tool.backend.schemas.js.map +1 -1
- package/dist/esm/mcp/services/mcp-server.backend.service.d.ts +29 -1
- package/dist/esm/mcp/services/mcp-server.backend.service.d.ts.map +1 -1
- package/dist/esm/mcp/services/mcp-server.backend.service.js +131 -3
- package/dist/esm/mcp/services/mcp-server.backend.service.js.map +1 -1
- package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.d.ts +64 -2
- package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.d.ts.map +1 -1
- package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.js +130 -6
- package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.js.map +1 -1
- package/dist/esm/notifications/services/notifications-dispatcher.backend.service.d.ts +9 -0
- package/dist/esm/notifications/services/notifications-dispatcher.backend.service.d.ts.map +1 -1
- package/dist/esm/notifications/services/notifications-dispatcher.backend.service.js +11 -36
- package/dist/esm/notifications/services/notifications-dispatcher.backend.service.js.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.d.ts.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.js +9 -7
- package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.js.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.d.ts +5 -5
- package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.d.ts.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.js +14 -13
- package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.js.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.d.ts +2 -1
- package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.d.ts.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.js +2 -2
- package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.js.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.js +9 -7
- package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.d.ts.map +1 -1
- package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.js +4 -1
- package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.js.map +1 -1
- package/dist/esm/requests/service/websocket.service.d.ts.map +1 -1
- package/dist/esm/requests/service/websocket.service.js +56 -7
- package/dist/esm/requests/service/websocket.service.js.map +1 -1
- package/dist/esm/resources/models/custom-service-implementations.backend.definition.d.ts.map +1 -1
- package/dist/esm/resources/models/custom-service-implementations.backend.definition.js +2 -0
- package/dist/esm/resources/models/custom-service-implementations.backend.definition.js.map +1 -1
- package/dist/esm/resources/services/resource-id.backend.service.js +3 -3
- package/dist/esm/resources/services/resource-id.backend.service.js.map +1 -1
- package/dist/esm/resources/services/resources-registry.backend.service.js +1 -1
- package/dist/esm/resources/services/resources-registry.backend.service.js.map +1 -1
- package/dist/esm/runtime/services/execution-context-creator.backend.service.d.ts.map +1 -1
- package/dist/esm/runtime/services/execution-context-creator.backend.service.js +13 -7
- package/dist/esm/runtime/services/execution-context-creator.backend.service.js.map +1 -1
- package/dist/esm/runtime/services/start-application.backend.service.d.ts +28 -0
- package/dist/esm/runtime/services/start-application.backend.service.d.ts.map +1 -1
- package/dist/esm/runtime/services/start-application.backend.service.js +82 -0
- package/dist/esm/runtime/services/start-application.backend.service.js.map +1 -1
- package/dist/esm/security/authentications/models/jwt.backend.schemas.d.ts +3 -23
- package/dist/esm/security/authentications/models/jwt.backend.schemas.d.ts.map +1 -1
- package/dist/esm/security/authentications/models/jwt.backend.schemas.js +20 -45
- package/dist/esm/security/authentications/models/jwt.backend.schemas.js.map +1 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.d.ts +9 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.d.ts.map +1 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.js +9 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.js.map +1 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.d.ts.map +1 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.js +5 -3
- package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.js.map +1 -1
- package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.d.ts +25 -1
- package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.d.ts.map +1 -1
- package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.js +55 -4
- package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.js.map +1 -1
- package/dist/esm/security/authorizations/initiator-organization-confinement.backend.d.ts.map +1 -1
- package/dist/esm/security/authorizations/initiator-organization-confinement.backend.js +24 -1
- package/dist/esm/security/authorizations/initiator-organization-confinement.backend.js.map +1 -1
- package/dist/esm/security/authorizations/privileged-access-review.backend.utils.d.ts +117 -0
- package/dist/esm/security/authorizations/privileged-access-review.backend.utils.d.ts.map +1 -0
- package/dist/esm/security/authorizations/privileged-access-review.backend.utils.js +104 -0
- package/dist/esm/security/authorizations/privileged-access-review.backend.utils.js.map +1 -0
- package/dist/esm/security/authorizations/services/authorizations.backend.service.d.ts +101 -6
- package/dist/esm/security/authorizations/services/authorizations.backend.service.d.ts.map +1 -1
- package/dist/esm/security/authorizations/services/authorizations.backend.service.js +210 -30
- package/dist/esm/security/authorizations/services/authorizations.backend.service.js.map +1 -1
- package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.d.ts +18 -1
- package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.d.ts.map +1 -1
- package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.js +110 -74
- package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.js.map +1 -1
- package/dist/esm/security/consumable-tokens/consumable-token-models.exports.d.ts +22 -0
- package/dist/esm/security/consumable-tokens/consumable-token-models.exports.d.ts.map +1 -0
- package/dist/esm/security/consumable-tokens/consumable-token-models.exports.js +22 -0
- package/dist/esm/security/consumable-tokens/consumable-token-models.exports.js.map +1 -0
- package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.d.ts +17 -3
- package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.js +12 -0
- package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.js.map +1 -1
- package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.d.ts +20 -0
- package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.d.ts.map +1 -1
- package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.js +58 -5
- package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.js.map +1 -1
- package/dist/esm/security/jwt/services/jwt.backend.service.d.ts.map +1 -1
- package/dist/esm/security/jwt/services/jwt.backend.service.js +1 -18
- package/dist/esm/security/jwt/services/jwt.backend.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +15 -9
- package/dist/esm/.builder.pid +0 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-config-test-endpoint.custom-impl.backend.service.js","sourceRoot":"","sources":["../../../../../../../src/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Webhook Config TEST_ENDPOINT — REPLACE_CORE_OPERATIONS factory.\n *\n * Makes `webhookConfig.testEndpoint` (org + app variants) do the thing it is named after:\n * fire one signed synthetic delivery at a single stored endpoint and report what came back.\n *\n * Before this implementation the operation was declared `resourceOperationLike: READ` with\n * no handler, so the generic core read answered 200 with the config row. An admin pressing\n * \"test\" on an endpoint whose URL had rotted was told it worked. A connectivity probe that\n * cannot fail is worse than no probe — it converts an unknown into a false assurance — so\n * every failure mode is now a distinct, reported outcome.\n *\n * Design commitments:\n *\n * - **Same transport as real deliveries.** The POST goes through\n * {@link sendSignedWebhookRequest}, shared verbatim with the delivery worker. A probe\n * that reached the endpoint by a different route would attest to that route, not to the\n * delivery path the admin actually cares about.\n * - **Signed like a real delivery.** Same key (`JwtPrivateKeys.APPLICATION_WEBHOOK`), same\n * claim shape, same `X-Webhook-Signature` header, so the consumer's verification code is\n * genuinely exercised rather than bypassed.\n * - **Unmistakably synthetic.** The claims carry `synthetic: true` and the body is a\n * self-describing test envelope. We are POSTing to a customer's production listener; a\n * probe indistinguishable from a real event would invite consumers to act on a fake one.\n * Unknown claims are ignored by existing consumers, so this adds no breaking change.\n * - **No delivery-log row.** Per the operation contract, test traffic stays out of the user\n * event log. The audit trail below is where a probe is recorded.\n * - **A dead endpoint is a 200.** Transport failure at the customer's side is a successful\n * probe carrying `outcome: UNREACHABLE`. HTTP errors are reserved for faults in the\n * request itself — unknown `endpointId`, absent config, rate limit exceeded.\n *\n * Scope resolution mirrors the sibling delivery-log impls: the ORG variant reads\n * `executionContext.organizationId`, the APP variant `applicationId`. The endpoint is then\n * selected by `endpointId` *within that scope's config only*, so a caller cannot aim the\n * probe at another tenant's endpoint — or at an arbitrary URL of their own choosing.\n *\n * @see ../m2m-webhook-signed-request.backend.utils.ts — the shared transport.\n * @see m2m-webhook-delivery-log.custom-impl.backend.service.ts — sibling REPLACE impls.\n */\n\nimport { createHash, randomUUID } from 'node:crypto';\nimport {\n AuthScopeType,\n CoreAuditableEventType,\n CoreM2MNotificationChannel,\n CoreResourceType,\n ErrorCustomMessageReference,\n ErrorType,\n ResourceOperationRiskLevel,\n ResourceOperationVariantType,\n WebhookConfig_Operations,\n WebhookConfig_TestEndpointRequestDto,\n WebhookEndpointProbeOutcome,\n type AuditableEvent_Webhook_EndpointTestDispatchedEventData,\n type WebhookConfigBase,\n type WebhookEndpoint,\n} from '@wildo-ai/saas-models';\nimport type { Container } from 'inversify';\nimport {\n createResourceCustomServiceImplementation,\n type ResourceCustomServiceImplementationFactory,\n} from '~wildo-backend-lib/base-classes/services/services/resource-operation-custom-implementation.factory.backend';\nimport type { AuditLogsBackendService } from '~wildo-backend-lib/compliance/audit-trails/services/audit-logs.backend.service';\nimport type { RedisService } from '~wildo-backend-lib/databases/services/redis.service';\nimport { describeCaughtError } from '~wildo-backend-lib/errors/utils/error-log-level-classification.backend.utils';\nimport { relaxAbuseThrottleMaxForEnvironment } from '~wildo-backend-lib/errors/utils/rate-limit-environment-relaxation.backend.utils';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport type { AppConfigurationService } from '~wildo-backend-lib/config/services/app-configuration.backend.service';\nimport type { ExecutionContext } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\nimport { JwtPrivateKeys } from '~wildo-backend-lib/security/authentications/models/jwt.backend.schemas';\nimport type { JwtBackendService } from '~wildo-backend-lib/security/jwt/services/jwt.backend.service';\nimport { M2M_WEBHOOK_BODY_HASH_ALGORITHM, sendSignedWebhookRequest } from '../m2m-webhook-signed-request.backend.utils';\n\n/**\n * Variant → (scope type, notification channel) pairing. The channel rides the signed claims\n * so the probe presents itself on the same channel a real delivery from this scope would.\n */\nconst WEBHOOK_CONFIG_VARIANTS = [\n {\n resourceType: CoreResourceType.ORGANIZATION_WEBHOOK_CONFIG,\n scopeType: AuthScopeType.ORGANIZATION,\n channel: CoreM2MNotificationChannel.WEBHOOK_ORGANIZATION,\n },\n {\n resourceType: CoreResourceType.APPLICATION_WEBHOOK_CONFIG,\n scopeType: AuthScopeType.APPLICATION,\n channel: CoreM2MNotificationChannel.WEBHOOK_APPLICATION,\n },\n] as const;\n\n/**\n * Probe budget: at most this many attempts per endpoint per window.\n *\n * Scoped per ENDPOINT rather than per admin: the resource being protected is the customer's\n * listener, which does not care which of our admins is iterating on it. The allowance is\n * loose enough for the real workflow (tweak a URL, retest) and tight enough that the\n * operation cannot be used to generate sustained traffic at a third party.\n */\nconst PROBE_RATE_LIMIT_WINDOW_SECONDS = 60;\nconst PROBE_RATE_LIMIT_MAX_PER_WINDOW = 5;\n\n/** Host-only projection for the audit trail — see the audit schema for why the path is withheld. */\nfunction extractHost(url: string): string {\n try {\n return new URL(url).host;\n } catch {\n return 'unparseable';\n }\n}\n\n/**\n * Map a transport result onto the reported verdict.\n *\n * Deliberately NOT `classifyResponseStatus`: that function answers a delivery-retry question\n * (\"should we send this again?\"), where 429 and 503 are `retry`. A probe answers a\n * reachability question, where 429 and 503 both mean the endpoint is very much reachable and\n * chose to refuse. Reusing the retry classifier here would report a live, throttling endpoint\n * as though nothing had been learned about it.\n */\nexport function resolveProbeOutcome(responseStatus: number | undefined): WebhookEndpointProbeOutcome {\n if (responseStatus === undefined) return WebhookEndpointProbeOutcome.UNREACHABLE;\n return responseStatus >= 200 && responseStatus < 300\n ? WebhookEndpointProbeOutcome.ACCEPTED\n : WebhookEndpointProbeOutcome.REJECTED;\n}\n\nexport function createWebhookConfigTestEndpointFactories(): ResourceCustomServiceImplementationFactory[] {\n return WEBHOOK_CONFIG_VARIANTS.map(({ resourceType, scopeType, channel }): ResourceCustomServiceImplementationFactory => {\n return (_resourcesRegistryService, container: Container) => {\n return createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: resourceType,\n operationIdentifier: WebhookConfig_Operations.TEST_ENDPOINT,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n requestDtoSchema: WebhookConfig_TestEndpointRequestDto,\n handlers: {\n replaceCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n const { endpointId } = input as { endpointId: string };\n\n const scopeId = scopeType === AuthScopeType.ORGANIZATION\n ? executionContext.organizationId\n : executionContext.applicationId;\n if (!scopeId) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.VALIDATION,\n context: { message: `Webhook endpoint test requires a ${scopeType}-scoped execution context.` },\n });\n }\n\n // ── Rate limit ────────────────────────────────────────────\n // Fails CLOSED when the limiter is unavailable. This operation is the one\n // admin surface that makes the platform open a connection to an\n // operator-supplied URL; running it unmetered because Redis is down is\n // exactly the window an abuse path would want. An honest 4xx/5xx beats\n // silently unbounded egress.\n const redis = container.get<RedisService>(SAAS_SERVICE_TYPES.RedisService);\n // Non-production relaxation of the probe ceiling (shared seam — see\n // `rate-limit-environment-relaxation.backend.utils`). Five probes per minute is\n // a production egress guard; iterating on a webhook endpoint locally legitimately\n // exceeds it in seconds. The live-traffic warning in the message below still holds\n // in dev — the relaxation raises the ceiling, it does not make the probe cheap.\n const appConfigService = container.get<AppConfigurationService>(SAAS_SERVICE_TYPES.AppConfigService);\n const effectiveProbeMaxPerWindow = relaxAbuseThrottleMaxForEnvironment(\n PROBE_RATE_LIMIT_MAX_PER_WINDOW,\n appConfigService.applicationEnv,\n );\n const attemptsInWindow = await redis.incrementRateLimit(\n `webhook_endpoint_test:${scopeType}:${scopeId}:${endpointId}`,\n PROBE_RATE_LIMIT_WINDOW_SECONDS,\n );\n if (attemptsInWindow > effectiveProbeMaxPerWindow) {\n throw utils.errorBuilder.buildError(ErrorType.RATE_LIMIT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.RATE_LIMIT,\n context: {\n message: `Endpoint tests are limited to ${effectiveProbeMaxPerWindow} per ${PROBE_RATE_LIMIT_WINDOW_SECONDS}s per endpoint; each one sends live traffic to the destination.`,\n endpointId,\n retryAfterSeconds: PROBE_RATE_LIMIT_WINDOW_SECONDS,\n },\n });\n }\n\n // ── Resolve the endpoint (scope-confined) ─────────────────\n // `readAsSystem` is the sanctioned privileged path and the resource opts into\n // it (`systemAccessPolicy: { read: ALLOWED }`). The caller has already passed\n // the org-admin / app-super-admin gate on this operation; the system read only\n // avoids re-deriving row visibility, and the filter pins the caller's own scope.\n if (!utils.systemAccessService) {\n throw utils.errorBuilder.buildError(ErrorType.CONFIGURATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.CONFIGURATION,\n context: { message: 'System access service is unavailable; cannot resolve the webhook endpoint to test.' },\n });\n }\n const scopeFieldName = scopeType === AuthScopeType.ORGANIZATION ? 'organizationId' : 'applicationId';\n const config = await utils.systemAccessService.readAsSystem<WebhookConfigBase>(\n resourceType,\n { [scopeFieldName]: scopeId },\n { scopeContext: { [scopeFieldName]: scopeId } },\n );\n if (!config) {\n throw utils.errorBuilder.buildError(ErrorType.NOT_FOUND, executionContext, {\n customMessageReference: ErrorCustomMessageReference.NOT_FOUND,\n context: { message: 'No webhook configuration exists for this scope.' },\n });\n }\n\n const endpoint: WebhookEndpoint | undefined = (config.endpoints ?? []).find((e) => e.id === endpointId);\n if (!endpoint) {\n throw utils.errorBuilder.buildError(ErrorType.NOT_FOUND, executionContext, {\n customMessageReference: ErrorCustomMessageReference.NOT_FOUND,\n context: { message: 'No endpoint with this id exists on the webhook configuration.', endpointId },\n });\n }\n if (!endpoint.url) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.VALIDATION,\n context: { message: 'The stored endpoint has no URL, so no meaningful test can be sent.', endpointId },\n });\n }\n // A DISABLED endpoint is still probed on purpose: \"validate before enabling\" is\n // the primary workflow this operation exists for, and refusing here would force\n // an admin to enable an unverified URL in order to verify it.\n\n // ── Build + sign the synthetic delivery ───────────────────\n const attemptedAt = new Date();\n const signatureJti = randomUUID();\n const body = JSON.stringify({\n synthetic: true,\n message: 'Wildo webhook connectivity test. This is not a real event; do not process it as one.',\n endpointId,\n scope: { type: scopeType, id: scopeId },\n issuedAt: attemptedAt.toISOString(),\n });\n const bodyHash = createHash('sha256').update(body).digest('hex');\n\n const jwtService = container.get<JwtBackendService>(SAAS_SERVICE_TYPES.JwtBackendService);\n let signature: string;\n try {\n signature = await jwtService.sign(\n {\n jti: signatureJti,\n bodyHash,\n bodyHashAlg: M2M_WEBHOOK_BODY_HASH_ALGORITHM,\n eventChannel: channel,\n resourceIdentifier: String(resourceType),\n operationIdentifier: String(WebhookConfig_Operations.TEST_ENDPOINT),\n // Present ONLY on probes. Lets a consumer that wants to hard-refuse\n // synthetic traffic do so on the signed envelope rather than on body text.\n synthetic: true,\n },\n JwtPrivateKeys.APPLICATION_WEBHOOK,\n );\n } catch (error) {\n // Signing failure is ours, not the endpoint's. Reporting it as UNREACHABLE\n // would send an admin to debug a customer endpoint over a missing local key.\n const failureReason = `JWT signing failed: ${describeCaughtError(error)}`;\n utils.logger.error('Webhook endpoint test could not be signed; nothing was sent', {\n scopeType, scopeId, endpointId, error,\n });\n const notSent = {\n endpointId,\n url: endpoint.url,\n outcome: WebhookEndpointProbeOutcome.NOT_SENT,\n failureReason,\n durationMs: 0,\n signatureJti,\n attemptedAt,\n };\n await recordProbeAudit(container, executionContext, {\n scopeType, scopeId, endpointId, url: endpoint.url,\n outcome: WebhookEndpointProbeOutcome.NOT_SENT,\n durationMs: 0,\n signatureJti,\n });\n return notSent;\n }\n\n // ── Send ──────────────────────────────────────────────────\n const result = await sendSignedWebhookRequest({ url: endpoint.url, body, signature });\n const outcome = resolveProbeOutcome(result.responseStatus);\n\n utils.logger.info('Webhook endpoint test dispatched', {\n scopeType, scopeId, endpointId,\n destinationHost: extractHost(endpoint.url),\n outcome,\n responseStatus: result.responseStatus,\n durationMs: result.durationMs,\n signatureJti,\n });\n\n await recordProbeAudit(container, executionContext, {\n scopeType, scopeId, endpointId, url: endpoint.url,\n outcome,\n responseStatus: result.responseStatus,\n durationMs: result.durationMs,\n signatureJti,\n });\n\n // Matches `WebhookConfig_TestEndpointResponseDto`.\n return {\n endpointId,\n url: endpoint.url,\n outcome,\n responseStatus: result.responseStatus,\n responseBodyTruncated: result.responseBodyTruncated,\n failureReason: result.networkError,\n durationMs: result.durationMs,\n signatureJti,\n attemptedAt,\n };\n },\n },\n });\n };\n });\n}\n\n/**\n * Record the probe on the audit trail.\n *\n * Emitted for EVERY attempt, not only failures: the security signal is the pattern of\n * outbound probing (who, which hosts, how often), which only reads correctly if successes\n * are present too.\n *\n * Uses the generic `logEvent` rather than a dedicated `log*` method so the audit service's\n * public surface does not grow — partial `AuditLogsBackendService` mocks across the test\n * suite would otherwise start failing on a method they do not stub.\n *\n * Best-effort by construction: `logEvent` swallows its own failures, and an audit-store\n * outage must not convert a completed probe into an error the admin cannot act on.\n *\n * assurance-control: WILDO.INTEGRATION.OUTBOUND_ACTIVITY_AUDIT — attributable outbound connection\n * initiated from an administrative surface.\n */\nasync function recordProbeAudit(\n container: Container,\n executionContext: ExecutionContext<any>,\n data: {\n scopeType: AuthScopeType;\n scopeId: string;\n endpointId: string;\n url: string;\n outcome: WebhookEndpointProbeOutcome;\n responseStatus?: number;\n durationMs: number;\n signatureJti: string;\n },\n): Promise<void> {\n const auditLogs = container.get<AuditLogsBackendService>(SAAS_SERVICE_TYPES.AuditLogsService);\n const isOrgScope = data.scopeType === AuthScopeType.ORGANIZATION;\n const actorUserId = executionContext.initiatorIds?.userId;\n\n const eventData: AuditableEvent_Webhook_EndpointTestDispatchedEventData = {\n eventType: CoreAuditableEventType.WEBHOOK_ENDPOINT_TEST_DISPATCHED,\n organizationId: isOrgScope ? data.scopeId : undefined,\n applicationId: isOrgScope ? undefined : data.scopeId,\n // The operation is role-gated to a human admin, so an absent actor id means the\n // execution context was not shaped as expected. Record the gap rather than papering\n // over it — an unattributable egress probe is itself the thing worth noticing.\n dispatchedBy: actorUserId ?? 'unattributed',\n endpointId: data.endpointId,\n destinationHost: extractHost(data.url),\n outcome: data.outcome,\n responseStatus: data.responseStatus,\n durationMs: data.durationMs,\n signatureJti: data.signatureJti,\n };\n\n await auditLogs.logEvent(\n {\n userId: actorUserId,\n // Scope FK as event DATA. Per-org SIEM routing is resolved by the audit service from\n // the authenticated request context passed below, never from this field.\n organizationId: isOrgScope ? data.scopeId : undefined,\n applicationId: isOrgScope ? undefined : data.scopeId,\n // Matches the operation's declared `riskLevel` — outbound traffic to an operator-supplied\n // URL, gated behind an admin role.\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n eventType: CoreAuditableEventType.WEBHOOK_ENDPOINT_TEST_DISPATCHED,\n eventData: eventData as Record<string, any>,\n },\n executionContext,\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"webhook-config-test-endpoint.custom-impl.backend.service.js","sourceRoot":"","sources":["../../../../../../../src/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Webhook Config TEST_ENDPOINT — REPLACE_CORE_OPERATIONS factory.\n *\n * Makes `webhookConfig.testEndpoint` (org + app variants) do the thing it is named after:\n * fire one signed synthetic delivery at a single stored endpoint and report what came back.\n *\n * Before this implementation the operation was declared `resourceOperationLike: READ` with\n * no handler, so the generic core read answered 200 with the config row. An admin pressing\n * \"test\" on an endpoint whose URL had rotted was told it worked. A connectivity probe that\n * cannot fail is worse than no probe — it converts an unknown into a false assurance — so\n * every failure mode is now a distinct, reported outcome.\n *\n * Design commitments:\n *\n * - **Same transport as real deliveries.** The POST goes through\n * {@link sendSignedWebhookRequest}, shared verbatim with the delivery worker. A probe\n * that reached the endpoint by a different route would attest to that route, not to the\n * delivery path the admin actually cares about.\n * - **Signed like a real delivery.** Same key (`JwtPrivateKeys.APPLICATION_WEBHOOK`), same\n * claim shape, same `X-Webhook-Signature` header, so the consumer's verification code is\n * genuinely exercised rather than bypassed.\n * - **Unmistakably synthetic.** The claims carry `synthetic: true` and the body is a\n * self-describing test envelope. We are POSTing to a customer's production listener; a\n * probe indistinguishable from a real event would invite consumers to act on a fake one.\n * Unknown claims are ignored by existing consumers, so this adds no breaking change.\n * - **No delivery-log row.** Per the operation contract, test traffic stays out of the user\n * event log. The audit trail below is where a probe is recorded.\n * - **A dead endpoint is a 200.** Transport failure at the customer's side is a successful\n * probe carrying `outcome: UNREACHABLE`. HTTP errors are reserved for faults in the\n * request itself — unknown `endpointId`, absent config, rate limit exceeded.\n *\n * Scope resolution mirrors the sibling delivery-log impls: the ORG variant reads\n * `executionContext.organizationId`, the APP variant `applicationId`. The endpoint is then\n * selected by `endpointId` *within that scope's config only*, so a caller cannot aim the\n * probe at another tenant's endpoint — or at an arbitrary URL of their own choosing.\n *\n * @see ../m2m-webhook-signed-request.backend.utils.ts — the shared transport.\n * @see m2m-webhook-delivery-log.custom-impl.backend.service.ts — sibling REPLACE impls.\n */\n\nimport { createHash, randomUUID } from 'node:crypto';\nimport {\n AuthScopeType,\n CoreAuditableEventType,\n CoreM2MNotificationChannel,\n CoreResourceType,\n ErrorCustomMessageReference,\n ErrorType,\n ResourceOperationRiskLevel,\n ResourceOperationVariantType,\n WebhookConfig_Operations,\n WebhookConfig_TestEndpointRequestDto,\n WebhookEndpointProbeOutcome,\n type AuditableEvent_Webhook_EndpointTestDispatchedEventData,\n type WebhookConfigBase,\n type WebhookEndpoint,\n M2M_WEBHOOK_DELIVERY_CONTRACT,\n} from '@wildo-ai/saas-models';\nimport type { Container } from 'inversify';\nimport {\n createResourceCustomServiceImplementation,\n type ResourceCustomServiceImplementationFactory,\n} from '~wildo-backend-lib/base-classes/services/services/resource-operation-custom-implementation.factory.backend';\nimport type { AuditLogsBackendService } from '~wildo-backend-lib/compliance/audit-trails/services/audit-logs.backend.service';\nimport type { RedisService } from '~wildo-backend-lib/databases/services/redis.service';\nimport { describeCaughtError } from '~wildo-backend-lib/errors/utils/error-log-level-classification.backend.utils';\nimport { relaxAbuseThrottleMaxForEnvironment } from '~wildo-backend-lib/errors/utils/rate-limit-environment-relaxation.backend.utils';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport type { AppConfigurationService } from '~wildo-backend-lib/config/services/app-configuration.backend.service';\nimport type { ExecutionContext } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\nimport { JwtPrivateKeys } from '~wildo-backend-lib/security/authentications/models/jwt.backend.schemas';\nimport type { JwtBackendService } from '~wildo-backend-lib/security/jwt/services/jwt.backend.service';\nimport { M2M_WEBHOOK_BODY_HASH_ALGORITHM, sendSignedWebhookRequest } from '../m2m-webhook-signed-request.backend.utils';\n\n/**\n * Variant → (scope type, notification channel) pairing. The channel rides the signed claims\n * so the probe presents itself on the same channel a real delivery from this scope would.\n */\nconst WEBHOOK_CONFIG_VARIANTS = [\n {\n resourceType: CoreResourceType.ORGANIZATION_WEBHOOK_CONFIG,\n scopeType: AuthScopeType.ORGANIZATION,\n channel: CoreM2MNotificationChannel.WEBHOOK_ORGANIZATION,\n },\n {\n resourceType: CoreResourceType.APPLICATION_WEBHOOK_CONFIG,\n scopeType: AuthScopeType.APPLICATION,\n channel: CoreM2MNotificationChannel.WEBHOOK_APPLICATION,\n },\n] as const;\n\n/**\n * Probe budget: at most this many attempts per endpoint per window.\n *\n * Scoped per ENDPOINT rather than per admin: the resource being protected is the customer's\n * listener, which does not care which of our admins is iterating on it. The allowance is\n * loose enough for the real workflow (tweak a URL, retest) and tight enough that the\n * operation cannot be used to generate sustained traffic at a third party.\n */\nconst PROBE_RATE_LIMIT_WINDOW_SECONDS = 60;\nconst PROBE_RATE_LIMIT_MAX_PER_WINDOW = 5;\n\n/** Host-only projection for the audit trail — see the audit schema for why the path is withheld. */\nfunction extractHost(url: string): string {\n try {\n return new URL(url).host;\n } catch {\n return 'unparseable';\n }\n}\n\n/**\n * Map a transport result onto the reported verdict.\n *\n * Deliberately NOT `classifyResponseStatus`: that function answers a delivery-retry question\n * (\"should we send this again?\"), where 429 and 503 are `retry`. A probe answers a\n * reachability question, where 429 and 503 both mean the endpoint is very much reachable and\n * chose to refuse. Reusing the retry classifier here would report a live, throttling endpoint\n * as though nothing had been learned about it.\n */\nexport function resolveProbeOutcome(responseStatus: number | undefined): WebhookEndpointProbeOutcome {\n if (responseStatus === undefined) return WebhookEndpointProbeOutcome.UNREACHABLE;\n return responseStatus >= 200 && responseStatus < 300\n ? WebhookEndpointProbeOutcome.ACCEPTED\n : WebhookEndpointProbeOutcome.REJECTED;\n}\n\nexport function createWebhookConfigTestEndpointFactories(): ResourceCustomServiceImplementationFactory[] {\n return WEBHOOK_CONFIG_VARIANTS.map(({ resourceType, scopeType, channel }): ResourceCustomServiceImplementationFactory => {\n return (_resourcesRegistryService, container: Container) => {\n return createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: resourceType,\n operationIdentifier: WebhookConfig_Operations.TEST_ENDPOINT,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n requestDtoSchema: WebhookConfig_TestEndpointRequestDto,\n handlers: {\n replaceCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n const { endpointId } = input as { endpointId: string };\n\n const scopeId = scopeType === AuthScopeType.ORGANIZATION\n ? executionContext.organizationId\n : executionContext.applicationId;\n if (!scopeId) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.VALIDATION,\n context: { message: `Webhook endpoint test requires a ${scopeType}-scoped execution context.` },\n });\n }\n\n // ── Rate limit ────────────────────────────────────────────\n // Fails CLOSED when the limiter is unavailable. This operation is the one\n // admin surface that makes the platform open a connection to an\n // operator-supplied URL; running it unmetered because Redis is down is\n // exactly the window an abuse path would want. An honest 4xx/5xx beats\n // silently unbounded egress.\n const redis = container.get<RedisService>(SAAS_SERVICE_TYPES.RedisService);\n // Non-production relaxation of the probe ceiling (shared seam — see\n // `rate-limit-environment-relaxation.backend.utils`). Five probes per minute is\n // a production egress guard; iterating on a webhook endpoint locally legitimately\n // exceeds it in seconds. The live-traffic warning in the message below still holds\n // in dev — the relaxation raises the ceiling, it does not make the probe cheap.\n const appConfigService = container.get<AppConfigurationService>(SAAS_SERVICE_TYPES.AppConfigService);\n const effectiveProbeMaxPerWindow = relaxAbuseThrottleMaxForEnvironment(\n PROBE_RATE_LIMIT_MAX_PER_WINDOW,\n appConfigService.applicationEnv,\n );\n const attemptsInWindow = await redis.incrementRateLimit(\n `webhook_endpoint_test:${scopeType}:${scopeId}:${endpointId}`,\n PROBE_RATE_LIMIT_WINDOW_SECONDS,\n );\n if (attemptsInWindow > effectiveProbeMaxPerWindow) {\n throw utils.errorBuilder.buildError(ErrorType.RATE_LIMIT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.RATE_LIMIT,\n context: {\n message: `Endpoint tests are limited to ${effectiveProbeMaxPerWindow} per ${PROBE_RATE_LIMIT_WINDOW_SECONDS}s per endpoint; each one sends live traffic to the destination.`,\n endpointId,\n retryAfterSeconds: PROBE_RATE_LIMIT_WINDOW_SECONDS,\n },\n });\n }\n\n // ── Resolve the endpoint (scope-confined) ─────────────────\n // `readAsSystem` is the sanctioned privileged path and the resource opts into\n // it (`systemAccessPolicy: { read: ALLOWED }`). The caller has already passed\n // the org-admin / app-super-admin gate on this operation; the system read only\n // avoids re-deriving row visibility, and the filter pins the caller's own scope.\n if (!utils.systemAccessService) {\n throw utils.errorBuilder.buildError(ErrorType.CONFIGURATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.CONFIGURATION,\n context: { message: 'System access service is unavailable; cannot resolve the webhook endpoint to test.' },\n });\n }\n const scopeFieldName = scopeType === AuthScopeType.ORGANIZATION ? 'organizationId' : 'applicationId';\n const config = await utils.systemAccessService.readAsSystem<WebhookConfigBase>(\n resourceType,\n { [scopeFieldName]: scopeId },\n { scopeContext: { [scopeFieldName]: scopeId } },\n );\n if (!config) {\n throw utils.errorBuilder.buildError(ErrorType.NOT_FOUND, executionContext, {\n customMessageReference: ErrorCustomMessageReference.NOT_FOUND,\n context: { message: 'No webhook configuration exists for this scope.' },\n });\n }\n\n const endpoint: WebhookEndpoint | undefined = (config.endpoints ?? []).find((e) => e.id === endpointId);\n if (!endpoint) {\n throw utils.errorBuilder.buildError(ErrorType.NOT_FOUND, executionContext, {\n customMessageReference: ErrorCustomMessageReference.NOT_FOUND,\n context: { message: 'No endpoint with this id exists on the webhook configuration.', endpointId },\n });\n }\n if (!endpoint.url) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.VALIDATION,\n context: { message: 'The stored endpoint has no URL, so no meaningful test can be sent.', endpointId },\n });\n }\n // A DISABLED endpoint is still probed on purpose: \"validate before enabling\" is\n // the primary workflow this operation exists for, and refusing here would force\n // an admin to enable an unverified URL in order to verify it.\n\n // ── Build + sign the synthetic delivery ───────────────────\n const attemptedAt = new Date();\n const signatureJti = randomUUID();\n const body = JSON.stringify({\n synthetic: true,\n message: 'Wildo webhook connectivity test. This is not a real event; do not process it as one.',\n endpointId,\n scope: { type: scopeType, id: scopeId },\n issuedAt: attemptedAt.toISOString(),\n });\n const bodyHash = createHash('sha256').update(body).digest('hex');\n\n const jwtService = container.get<JwtBackendService>(SAAS_SERVICE_TYPES.JwtBackendService);\n let signature: string;\n try {\n signature = await jwtService.sign(\n {\n // Claim names come from the published receiver contract, exactly as the\n // delivery worker signs them; a probe must be verifiable by the same receiver code.\n [M2M_WEBHOOK_DELIVERY_CONTRACT.signatureClaims.deliveryId]: signatureJti,\n [M2M_WEBHOOK_DELIVERY_CONTRACT.signatureClaims.bodyHash]: bodyHash,\n [M2M_WEBHOOK_DELIVERY_CONTRACT.signatureClaims.bodyHashAlgorithm]: M2M_WEBHOOK_BODY_HASH_ALGORITHM,\n [M2M_WEBHOOK_DELIVERY_CONTRACT.signatureClaims.eventChannel]: channel,\n [M2M_WEBHOOK_DELIVERY_CONTRACT.signatureClaims.resourceIdentifier]: String(resourceType),\n [M2M_WEBHOOK_DELIVERY_CONTRACT.signatureClaims.operationIdentifier]: String(WebhookConfig_Operations.TEST_ENDPOINT),\n // Present ONLY on probes. Lets a consumer that wants to hard-refuse\n // synthetic traffic do so on the signed envelope rather than on body text.\n synthetic: true,\n },\n JwtPrivateKeys.APPLICATION_WEBHOOK,\n );\n } catch (error) {\n // Signing failure is ours, not the endpoint's. Reporting it as UNREACHABLE\n // would send an admin to debug a customer endpoint over a missing local key.\n const failureReason = `JWT signing failed: ${describeCaughtError(error)}`;\n utils.logger.error('Webhook endpoint test could not be signed; nothing was sent', {\n scopeType, scopeId, endpointId, error,\n });\n const notSent = {\n endpointId,\n url: endpoint.url,\n outcome: WebhookEndpointProbeOutcome.NOT_SENT,\n failureReason,\n durationMs: 0,\n signatureJti,\n attemptedAt,\n };\n await recordProbeAudit(container, executionContext, {\n scopeType, scopeId, endpointId, url: endpoint.url,\n outcome: WebhookEndpointProbeOutcome.NOT_SENT,\n durationMs: 0,\n signatureJti,\n });\n return notSent;\n }\n\n // ── Send ──────────────────────────────────────────────────\n const result = await sendSignedWebhookRequest({ url: endpoint.url, body, signature });\n const outcome = resolveProbeOutcome(result.responseStatus);\n\n utils.logger.info('Webhook endpoint test dispatched', {\n scopeType, scopeId, endpointId,\n destinationHost: extractHost(endpoint.url),\n outcome,\n responseStatus: result.responseStatus,\n durationMs: result.durationMs,\n signatureJti,\n });\n\n await recordProbeAudit(container, executionContext, {\n scopeType, scopeId, endpointId, url: endpoint.url,\n outcome,\n responseStatus: result.responseStatus,\n durationMs: result.durationMs,\n signatureJti,\n });\n\n // Matches `WebhookConfig_TestEndpointResponseDto`.\n return {\n endpointId,\n url: endpoint.url,\n outcome,\n responseStatus: result.responseStatus,\n responseBodyTruncated: result.responseBodyTruncated,\n failureReason: result.networkError,\n durationMs: result.durationMs,\n signatureJti,\n attemptedAt,\n };\n },\n },\n });\n };\n });\n}\n\n/**\n * Record the probe on the audit trail.\n *\n * Emitted for EVERY attempt, not only failures: the security signal is the pattern of\n * outbound probing (who, which hosts, how often), which only reads correctly if successes\n * are present too.\n *\n * Uses the generic `logEvent` rather than a dedicated `log*` method so the audit service's\n * public surface does not grow — partial `AuditLogsBackendService` mocks across the test\n * suite would otherwise start failing on a method they do not stub.\n *\n * Best-effort by construction: `logEvent` swallows its own failures, and an audit-store\n * outage must not convert a completed probe into an error the admin cannot act on.\n *\n * assurance-control: WILDO.INTEGRATION.OUTBOUND_ACTIVITY_AUDIT — attributable outbound connection\n * initiated from an administrative surface.\n */\nasync function recordProbeAudit(\n container: Container,\n executionContext: ExecutionContext<any>,\n data: {\n scopeType: AuthScopeType;\n scopeId: string;\n endpointId: string;\n url: string;\n outcome: WebhookEndpointProbeOutcome;\n responseStatus?: number;\n durationMs: number;\n signatureJti: string;\n },\n): Promise<void> {\n const auditLogs = container.get<AuditLogsBackendService>(SAAS_SERVICE_TYPES.AuditLogsService);\n const isOrgScope = data.scopeType === AuthScopeType.ORGANIZATION;\n const actorUserId = executionContext.initiatorIds?.userId;\n\n const eventData: AuditableEvent_Webhook_EndpointTestDispatchedEventData = {\n eventType: CoreAuditableEventType.WEBHOOK_ENDPOINT_TEST_DISPATCHED,\n organizationId: isOrgScope ? data.scopeId : undefined,\n applicationId: isOrgScope ? undefined : data.scopeId,\n // The operation is role-gated to a human admin, so an absent actor id means the\n // execution context was not shaped as expected. Record the gap rather than papering\n // over it — an unattributable egress probe is itself the thing worth noticing.\n dispatchedBy: actorUserId ?? 'unattributed',\n endpointId: data.endpointId,\n destinationHost: extractHost(data.url),\n outcome: data.outcome,\n responseStatus: data.responseStatus,\n durationMs: data.durationMs,\n signatureJti: data.signatureJti,\n };\n\n await auditLogs.logEvent(\n {\n userId: actorUserId,\n // Scope FK as event DATA. Per-org SIEM routing is resolved by the audit service from\n // the authenticated request context passed below, never from this field.\n organizationId: isOrgScope ? data.scopeId : undefined,\n applicationId: isOrgScope ? undefined : data.scopeId,\n // Matches the operation's declared `riskLevel` — outbound traffic to an operator-supplied\n // URL, gated behind an admin role.\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n eventType: CoreAuditableEventType.WEBHOOK_ENDPOINT_TEST_DISPATCHED,\n eventData: eventData as Record<string, any>,\n },\n executionContext,\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organization-unit-custom-implementation.backend.service.d.ts","sourceRoot":"","sources":["../../../../../src/organizations/services/organization-unit-custom-implementation.backend.service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkBH,OAAO,EAEL,KAAK,0CAA0C,EAChD,MAAM,4GAA4G,CAAC;AAEpH,OAAO,KAAK,EAAE,4CAA4C,EAAE,MAAM,iHAAiH,CAAC;AACpL,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qEAAqE,CAAC;AAc5G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,gBAAgB,CAAC,GAAG,CAAC,EACvC,KAAK,EAAE,4CAA4C,GAClD,OAAO,CAAC,IAAI,CAAC,CA6Df;
|
|
1
|
+
{"version":3,"file":"organization-unit-custom-implementation.backend.service.d.ts","sourceRoot":"","sources":["../../../../../src/organizations/services/organization-unit-custom-implementation.backend.service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkBH,OAAO,EAEL,KAAK,0CAA0C,EAChD,MAAM,4GAA4G,CAAC;AAEpH,OAAO,KAAK,EAAE,4CAA4C,EAAE,MAAM,iHAAiH,CAAC;AACpL,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qEAAqE,CAAC;AAc5G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,gBAAgB,CAAC,GAAG,CAAC,EACvC,KAAK,EAAE,4CAA4C,GAClD,OAAO,CAAC,IAAI,CAAC,CA6Df;AAiHD;;;;;;;;;;GAUG;AACH,wBAAgB,yCAAyC,IAAI,0CAA0C,EAAE,CA8CxG;AAED;;;;;;;GAOG;AACH,wBAAgB,qCAAqC,IAAI,0CAA0C,EAAE,CAiDpG;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mCAAmC,IAAI,0CAA0C,EAAE,CA0GlG;AAoFD,yCAAyC;AACzC,wBAAgB,+CAA+C,IAAI,0CAA0C,EAAE,CAK9G;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oDAAoD,IAAI,0CAA0C,EAAE,CAqCnH"}
|
package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.js
CHANGED
|
@@ -204,7 +204,10 @@ async function assertSiblingCodeAvailable(candidate, executionContext, utils) {
|
|
|
204
204
|
organizationUnitId: candidate.unitId,
|
|
205
205
|
conflictingUnitId: sibling._id,
|
|
206
206
|
parentUnitId: candidate.parentUnitId ?? null,
|
|
207
|
-
code
|
|
207
|
+
// NOT `code`: that key is the client-facing refusal-code contract (allow-listed, hoisted to
|
|
208
|
+
// `error.code`) and must only ever carry a declared enum value. The user-typed unit code is
|
|
209
|
+
// diagnostic context and stays server-side; `customMessageReference` already names the refusal.
|
|
210
|
+
conflictingUnitCode: candidate.code,
|
|
208
211
|
},
|
|
209
212
|
});
|
|
210
213
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organization-unit-custom-implementation.backend.service.js","sourceRoot":"","sources":["../../../../../src/organizations/services/organization-unit-custom-implementation.backend.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Organization Unit Custom Implementations\n *\n * DELETE dependents guard: refuses to delete an interior node of the organization tree while\n * child units still hang off it.\n */\n\nimport {\n CoreResourceOperation,\n CoreResourceType,\n ErrorType,\n ErrorCustomMessageReference,\n OrganizationUnitStatus,\n OrganizationUnit_Operations,\n ResourceOperationVariantType,\n coreResourceShared_FieldIdentifier,\n buildOrganizationUnitChildPath,\n isOrganizationUnitInSubtree,\n organizationUnitAncestorIds,\n organizationUnitSubtreePrefix,\n rebaseOrganizationUnitPath,\n type OrganizationUnit,\n} from '@wildo-ai/saas-models';\nimport {\n createResourceCustomServiceImplementation,\n type ResourceCustomServiceImplementationFactory,\n} from '~wildo-backend-lib/base-classes/services/services/resource-operation-custom-implementation.factory.backend';\nimport type { ResourcesRegistryBackendService } from '~wildo-backend-lib/resources/services/resources-registry.backend.service';\nimport type { ResourceOperation_CustomImplementation_Utils } from '~wildo-backend-lib/base-classes/services/services/resource-operation-custom-implementation-base.backend.service';\nimport type { ExecutionContext } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\nimport {\n describeUnaddressableShape,\n resolveSingleSubjectId,\n} from '~wildo-backend-lib/base-classes/utils/single-subject-addressing.backend.utils';\n\n/**\n * The FK that materializes the organization-unit tree: a unit's parent node.\n *\n * Typed against {@link OrganizationUnit} so a rename of the field breaks the build here rather\n * than silently degrading the guard into a filter that matches nothing.\n */\nconst PARENT_UNIT_FK_FIELD: Extract<keyof OrganizationUnit, 'parentUnitId'> = 'parentUnitId';\n\n/**\n * Refuse removal of an organization unit while child units still hang off it.\n *\n * ## Why this guard exists\n *\n * `ORGANIZATION_UNITS -> ORGANIZATION_UNITS` (the tree self-edge on `parentUnitId`) is the ONE\n * `ORGANIZATIONS`-family relationship that declares no `onParentDelete` cascade. Deleting an\n * interior node therefore strands its entire subtree: every descendant keeps a `parentUnitId`\n * pointing at a row that no longer exists, and the materialized `path` of the whole branch — a\n * server-computed field (`.excludeFromCreate().excludeFromUpdate()`) that nothing recomputes —\n * silently describes a hierarchy that is no longer real. Neither is recoverable from the\n * remaining data, because the deleted node held the only link between the branch and its\n * grandparent.\n *\n * Enabling a cascade instead was rejected: the spec's stated policy is that the operator decides\n * where an orphaned branch belongs, and silently hard-deleting an arbitrarily deep subtree behind\n * a single-node DELETE is a far worse default for a `HIGH` risk-level operation than refusing it.\n *\n * The remedy is MOVE: re-parent each child onto another node, then delete the emptied leaf. MOVE\n * rewrites the materialized `path` of the whole subtree in one transaction, which a raw\n * `parentUnitId` edit never did — which is why `parentUnitId` became `.excludeFromUpdate()` in the\n * same pass that implemented MOVE (plan decision D-B2). Until then the guard's only escape hatch was\n * that raw edit, and it silently left every descendant's path describing a tree that no longer\n * existed.\n *\n * ## Scope: direct per-unit DELETE only\n *\n * Deleting the whole ORGANIZATION is a different path and is deliberately NOT affected. That\n * cascade IS enabled (`ORGANIZATIONS -> ORGANIZATION_UNITS`) and removes every unit of the\n * tenant in one pass — depth is irrelevant there because every unit, root or nested, carries the\n * tenant's `organizationId`. It also runs at the REPOSITORY level, which does not execute service\n * custom implementations, so this guard is structurally never consulted on that path. Tearing a\n * tenant down does not require dismantling its org chart leaf-by-leaf first.\n *\n * ## Why it fails closed\n *\n * DELETE is `riskLevel: HIGH`, so \"cannot verify\" must never read as \"nothing found\". An\n * unregistered `ORGANIZATION_UNITS` repository raises `CONFIGURATION` rather than being skipped,\n * and any scope-filter failure inside the read propagates instead of being swallowed.\n *\n * Detection uses `read()` rather than `count()` deliberately: `ORGANIZATION_UNITS` declares no\n * `COUNT` core operation, so `repository.count()` would throw INTERNAL. It does declare `READ`.\n * The read is a single indexed hit on `parentUnitId` (`.isDBIndexed()`), and the guard only needs\n * existence, not a tally.\n *\n * ## Also covered: unit assignments (since D-B5)\n *\n * Assigned members block the delete too. This dependent used to be EXCLUDED: unit assignment lived\n * in an embedded `OrganizationMember.organizationUnitsMembership` array, which is not a registered\n * relationship and has no adapter-neutral query — a dot-path filter into a JSONB array resolves to\n * NULL on PostgreSQL (matching nothing) and the LIST summary projection strips the field entirely,\n * so BOTH available scans would have failed OPEN on a guard whose whole purpose is to fail closed.\n * Promoting the assignment to the `ORGANIZATION_UNIT_MEMBERS` junction is exactly what made it\n * checkable, and it is now checked here on the same fail-closed terms as child units.\n *\n * @param unitId - The unit row about to be deleted.\n * @throws `CONFLICT` (409, `ORGANIZATION_UNIT_HAS_DEPENDENTS`) carrying whichever dependent blocked:\n * `blockingChildUnitId` for a child unit, `blockingUnitMemberId` for a member assignment.\n * @throws `CONFIGURATION` when the organization-units repository is not registered (unverifiable).\n */\nexport async function assertNoOrganizationUnitDependents(\n unitId: string,\n executionContext: ExecutionContext<any>,\n utils: ResourceOperation_CustomImplementation_Utils,\n): Promise<void> {\n const unitRepository = utils.repositoriesRegistry.getRepository(CoreResourceType.ORGANIZATION_UNITS);\n\n // Fail closed: an unregistered repository means we cannot prove the node is a leaf. Skipping\n // would silently downgrade the guard to \"nothing found\".\n if (!unitRepository) {\n throw utils.errorBuilder.buildError(ErrorType.CONFIGURATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.CONFIGURATION,\n context: {\n message: 'Cannot verify organization-unit dependents: the organization-units repository is not registered',\n organizationUnitId: unitId,\n childResourceType: CoreResourceType.ORGANIZATION_UNITS,\n },\n });\n }\n\n const blockingChildUnit = await unitRepository.read<OrganizationUnit>(executionContext, {\n [PARENT_UNIT_FK_FIELD]: unitId,\n });\n\n if (blockingChildUnit) {\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_HAS_DEPENDENTS,\n context: {\n organizationUnitId: unitId,\n blockingChildUnitId: blockingChildUnit._id,\n blockingChildUnitName: blockingChildUnit.name,\n },\n });\n }\n\n // Assigned members — checkable since D-B5 promoted unit assignment to a real junction resource.\n // Same fail-closed terms as the child-unit scan above: an unregistered repository means we cannot\n // prove the unit is unassigned, which must never read as \"nothing found\" on a HIGH-risk delete.\n const unitMemberRepository = utils.repositoriesRegistry.getRepository(CoreResourceType.ORGANIZATION_UNIT_MEMBERS);\n if (!unitMemberRepository) {\n throw utils.errorBuilder.buildError(ErrorType.CONFIGURATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.CONFIGURATION,\n context: {\n message: 'Cannot verify organization-unit assignments: the organization-unit-members repository is not registered',\n organizationUnitId: unitId,\n childResourceType: CoreResourceType.ORGANIZATION_UNIT_MEMBERS,\n },\n });\n }\n\n const blockingAssignment = await unitMemberRepository.read<{ _id: string; organizationMemberId: string }>(\n executionContext,\n { organizationUnitId: unitId },\n );\n\n if (blockingAssignment) {\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_HAS_DEPENDENTS,\n context: {\n organizationUnitId: unitId,\n blockingUnitMemberId: blockingAssignment._id,\n blockingOrganizationMemberId: blockingAssignment.organizationMemberId,\n },\n });\n }\n}\n\n/** The unit fields these implementations read; kept narrow so a schema rename breaks the build here. */\ntype OrganizationUnitTreeNodeRow = Pick<OrganizationUnit, '_id' | 'organizationId' | 'parentUnitId' | 'path' | 'status' | 'code' | 'name'>;\n\n/**\n * Load a unit that MUST exist inside the caller's scope, or fail with 404.\n *\n * Cross-tenant lookups resolve to \"not found\" rather than a conflict on purpose: the contextual\n * filter scopes the read to the caller's organization, so a unit belonging to another tenant is\n * simply absent. Reporting 409 there would confirm the existence of a row the caller may not see.\n */\nasync function loadScopedOrganizationUnit(\n unitId: string,\n executionContext: ExecutionContext<any>,\n utils: ResourceOperation_CustomImplementation_Utils,\n notFoundContext: Record<string, unknown>,\n): Promise<OrganizationUnitTreeNodeRow> {\n const unitRepository = utils.repositoriesRegistry.getRepository(CoreResourceType.ORGANIZATION_UNITS);\n if (!unitRepository) {\n throw utils.errorBuilder.buildError(ErrorType.CONFIGURATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.CONFIGURATION,\n context: { message: 'The organization-units repository is not registered', ...notFoundContext },\n });\n }\n\n const unit = await unitRepository.read<OrganizationUnitTreeNodeRow>(executionContext, { _id: unitId });\n if (!unit) {\n throw utils.errorBuilder.buildError(ErrorType.NOT_FOUND, executionContext, {\n customMessageReference: ErrorCustomMessageReference.NOT_FOUND,\n context: notFoundContext,\n });\n }\n return unit;\n}\n\n/**\n * Every unit strictly below `unit`, found by the materialized-path prefix.\n *\n * Uses `listForInternalScan` rather than `list`: this is an internal integrity sweep, and the LIST\n * summary projection would strip `path` — the one field the rewrite depends on.\n */\nasync function listOrganizationUnitDescendants(\n unit: OrganizationUnitTreeNodeRow,\n executionContext: ExecutionContext<any>,\n utils: ResourceOperation_CustomImplementation_Utils,\n): Promise<OrganizationUnitTreeNodeRow[]> {\n const unitRepository = utils.repositoriesRegistry.getRepository(CoreResourceType.ORGANIZATION_UNITS);\n const subtreePrefix = organizationUnitSubtreePrefix(unit);\n\n // Filter on the organization, then narrow by prefix in memory. An adapter-neutral \"starts with\"\n // filter does not exist across Mongo and SQL, and inventing one per adapter here would put a\n // query dialect inside a domain rule. Org trees are small (hundreds of nodes), and the scan is\n // already scoped to a single tenant.\n const scoped = await unitRepository.listForInternalScan<OrganizationUnitTreeNodeRow>(\n executionContext,\n { organizationId: unit.organizationId },\n utils.serviceOptions,\n );\n const rows: OrganizationUnitTreeNodeRow[] = Array.isArray(scoped) ? scoped : (scoped as { data: OrganizationUnitTreeNodeRow[] }).data;\n return rows.filter((row) => row._id !== unit._id && isOrganizationUnitInSubtree(row, unit));\n}\n\n/**\n * Refuse a `code` that is already taken by a sibling under `parentUnitId`.\n *\n * Sibling-scoped rather than org-wide: two teams called `sales` under different divisions are a\n * legitimate org chart, and `code` is optional so many rows carry none at all. That exact identity\n * — (`organizationId`, `parentUnitId`, `code`), scoped to rows that HAVE a code — is now also the\n * schema's business key, so MongoDB enforces it with a partial compound unique index.\n *\n * **This guard is deliberately KEPT rather than deleted as redundant.** Schema-declared uniqueness\n * is emitted for MongoDB only; the SQL planner records it as intent and leaves enforcement\n * migration-owned, so on a SQL adapter this function is the ONLY thing standing between two\n * same-coded siblings. It is a read-then-write and therefore still racy — under concurrency the\n * index is what actually closes the window on Mongo — but a racy check that usually wins is\n * strictly better than no check at all on an adapter that has no index to fall back to.\n *\n * Both layers raise the SAME reference. The index cannot know which operation was running, so an\n * operation-specific code would make one condition report differently depending on which write won\n * the race — an API client would have to handle both to recognise one situation.\n */\nasync function assertSiblingCodeAvailable(\n candidate: { unitId: string; organizationId: string; parentUnitId: string | undefined; code: string | undefined },\n executionContext: ExecutionContext<any>,\n utils: ResourceOperation_CustomImplementation_Utils,\n): Promise<void> {\n if (!candidate.code) return;\n\n const unitRepository = utils.repositoriesRegistry.getRepository(CoreResourceType.ORGANIZATION_UNITS);\n const sibling = await unitRepository.read<OrganizationUnitTreeNodeRow>(executionContext, {\n organizationId: candidate.organizationId,\n parentUnitId: candidate.parentUnitId ?? null,\n code: candidate.code,\n });\n\n if (sibling && sibling._id !== candidate.unitId) {\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_CODE_EXISTS,\n context: {\n reason: 'organization_unit_sibling_code_taken',\n organizationUnitId: candidate.unitId,\n conflictingUnitId: sibling._id,\n parentUnitId: candidate.parentUnitId ?? null,\n code: candidate.code,\n },\n });\n }\n}\n\n/**\n * CREATE prefix — materialize `path` on the SAME write that inserts the row.\n *\n * `path` is `.excludeFromCreate()` and was previously never written by anything, so every unit\n * persisted with an empty hierarchy path (the defect this closes). The prefix's return value is\n * merged into the create payload and passed through the loose repository write, which is how a\n * server-owned field reaches persistence without being client-settable.\n *\n * The path is derived from the PARENT alone — see the format contract in\n * `organization-units.shared.path.ts` for why it cannot include the new row's own id.\n */\nexport function createOrganizationUnitCreatePathFactories(): ResourceCustomServiceImplementationFactory[] {\n const factory = (_resourcesRegistryService: ResourcesRegistryBackendService) =>\n createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.ORGANIZATION_UNITS,\n operationIdentifier: CoreResourceOperation.CREATE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n debugLabel: 'organization-units.create.path-materialization',\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n const authored = input as { parentUnitId?: string; organizationId?: string; code?: string };\n\n const organizationId = authored.organizationId\n ?? executionContext.getContextResourceId?.(coreResourceShared_FieldIdentifier[CoreResourceType.ORGANIZATIONS])\n ?? executionContext.initiatorIds?.organizationId;\n\n if (!organizationId) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: { message: 'An organization scope is required to place a unit in the tree', reason: 'organization_unit_create_unscoped' },\n });\n }\n\n // A parent that was NAMED but cannot be resolved must fail, never silently fall back to\n // root: the scoped read returns nothing for a unit in another tenant, and treating that as\n // \"no parent\" would quietly promote a cross-tenant child to a root of the caller's tree.\n const parentUnit = authored.parentUnitId\n ? await loadScopedOrganizationUnit(authored.parentUnitId, executionContext, utils, {\n reason: 'organization_unit_parent_not_found',\n parentUnitId: authored.parentUnitId,\n })\n : null;\n\n await assertSiblingCodeAvailable(\n { unitId: '', organizationId, parentUnitId: authored.parentUnitId, code: authored.code },\n executionContext,\n utils,\n );\n\n return { ...input, organizationId, path: buildOrganizationUnitChildPath(parentUnit) };\n },\n },\n });\n\n return [factory as unknown as ResourceCustomServiceImplementationFactory];\n}\n\n/**\n * UPDATE prefix — keep sibling `code` unambiguous on a rename.\n *\n * Re-parenting is no longer reachable here (`parentUnitId` is `.excludeFromUpdate()` since MOVE\n * shipped — D-B2), so the only structural collision generic UPDATE can still cause is two same-coded\n * siblings under one parent. That is the surviving half of the spec's `ORGANIZATION_UNIT_CONFLICT`\n * scenario; the cycle half now lives entirely under MOVE.\n */\nexport function createOrganizationUnitUpdateFactories(): ResourceCustomServiceImplementationFactory[] {\n const factory = (_resourcesRegistryService: ResourcesRegistryBackendService) =>\n createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.ORGANIZATION_UNITS,\n operationIdentifier: CoreResourceOperation.UPDATE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n debugLabel: 'organization-units.update.sibling-code-guard',\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n const patch = input as { code?: unknown };\n // Only a rename can collide. An update that leaves `code` alone cannot introduce a\n // duplicate, and re-checking it would 409 every unrelated edit of an already-valid row.\n if (typeof patch.code !== 'string' || patch.code.length === 0) return input;\n\n // Resolve rather than cast. The former `_id as string` asserted a shape the addressing\n // contract does not guarantee: a filter-addressed rename would have handed an OBJECT to a\n // by-id read, which builds `{ _id: <object> }` and matches nothing — surfacing as a spurious\n // NOT_FOUND while the sibling-code collision it was meant to catch went unchecked.\n const organizationUnitId = resolveSingleSubjectId(_id);\n if (organizationUnitId === undefined) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: {\n message: 'Organization unit UPDATE renaming `code` requires a single addressed unit',\n reason: 'organization_unit_rename_unaddressed',\n receivedAddressing: describeUnaddressableShape(_id),\n },\n });\n }\n\n const unit = await loadScopedOrganizationUnit(organizationUnitId, executionContext, utils, {\n reason: 'organization_unit_not_found',\n organizationUnitId,\n });\n\n await assertSiblingCodeAvailable(\n { unitId: unit._id, organizationId: unit.organizationId, parentUnitId: unit.parentUnitId ?? undefined, code: patch.code },\n executionContext,\n utils,\n );\n\n return input;\n },\n },\n });\n\n return [factory as unknown as ResourceCustomServiceImplementationFactory];\n}\n\n/**\n * MOVE — re-parent a unit and re-home its entire subtree.\n *\n * The descendant rewrite is the part that must never be skipped: leaving it out is exactly the\n * \"materialized field nothing recomputes\" bug that `path` was already an instance of. Because the\n * rewrite spans many rows, the whole operation runs in one transaction — a partial rewrite would\n * leave the tree describing a shape that never existed.\n *\n * An absent `newParentUnitId` means \"promote to root\", which the request DTO models as optional. A\n * present-but-blank value is malformed and is refused rather than silently read as a promotion.\n *\n * ⚠️ Cost: the subtree rewrite is a scan plus one `update()` per descendant. `repository.updateMany`\n * is unavailable here — it requires an `UPDATE_MANY` core operation, which this resource does not\n * declare. Acceptable while org charts are small; revisit if deep trees appear.\n */\nexport function createOrganizationUnitMoveFactories(): ResourceCustomServiceImplementationFactory[] {\n const factory = (_resourcesRegistryService: ResourcesRegistryBackendService) =>\n createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.ORGANIZATION_UNITS,\n operationIdentifier: OrganizationUnit_Operations.MOVE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n debugLabel: 'organization-units.move',\n // The subtree rewrite spans many ORGANIZATION_UNITS rows plus the moved node itself. A partial\n // rewrite would leave the tree describing a shape that never existed, so they share one unit\n // of work. No OTHER resource participates — the move is entirely within the unit tree.\n transactionalCore: true,\n transactionParticipants: {},\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n // A conditional `{ _id, … }` filter names one unit and is admitted; only a genuinely\n // population-addressed move is refused, since the subtree rewrite below is computed from\n // ONE node's path.\n const organizationUnitId = resolveSingleSubjectId(_id);\n if (organizationUnitId === undefined) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: {\n message: 'Organization unit MOVE requires a single addressed unit',\n reason: 'organization_unit_move_unaddressed',\n receivedAddressing: describeUnaddressableShape(_id),\n },\n });\n }\n\n const requested = input as { newParentUnitId?: unknown };\n const hasDestination = requested.newParentUnitId !== undefined && requested.newParentUnitId !== null;\n if (hasDestination && (typeof requested.newParentUnitId !== 'string' || requested.newParentUnitId.length === 0)) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: { message: 'newParentUnitId must be a non-empty unit id, or be omitted to promote the unit to a root', reason: 'organization_unit_move_malformed_destination' },\n });\n }\n const newParentUnitId = hasDestination ? (requested.newParentUnitId as string) : undefined;\n\n const unit = await loadScopedOrganizationUnit(organizationUnitId, executionContext, utils, {\n reason: 'organization_unit_not_found',\n organizationUnitId,\n });\n\n if (newParentUnitId === unit._id) {\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_MOVE_CONFLICT,\n context: { reason: 'organization_unit_move_onto_self', organizationUnitId: unit._id },\n });\n }\n\n const newParentUnit = newParentUnitId\n ? await loadScopedOrganizationUnit(newParentUnitId, executionContext, utils, {\n reason: 'organization_unit_destination_not_found',\n newParentUnitId,\n })\n : null;\n\n // O(1) cycle test — the payoff of the materialized path. Moving a node beneath one of its\n // own descendants would detach the branch: it would have no route back to the root.\n if (newParentUnit && isOrganizationUnitInSubtree(newParentUnit, unit)) {\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_MOVE_CONFLICT,\n context: {\n reason: 'organization_unit_move_would_create_cycle',\n organizationUnitId: unit._id,\n newParentUnitId,\n },\n });\n }\n\n // Raises ORGANIZATION_UNIT_CODE_EXISTS, not MOVE_CONFLICT: the destination itself is legal\n // here — it is the unit's own `code` that collides — so renaming the unit is a remedy that\n // choosing another destination is not. The two conditions want different client handling.\n await assertSiblingCodeAvailable(\n { unitId: unit._id, organizationId: unit.organizationId, parentUnitId: newParentUnitId, code: unit.code },\n executionContext,\n utils,\n );\n\n // Re-home the branch. Descendants are rewritten BEFORE the node's own update lands, so a\n // failure anywhere rolls the whole move back rather than leaving a half-moved tree.\n const oldSubtreePrefix = organizationUnitSubtreePrefix(unit);\n const newUnitPath = buildOrganizationUnitChildPath(newParentUnit);\n const newSubtreePrefix = `${newUnitPath}${unit._id}/`;\n\n const descendants = await listOrganizationUnitDescendants(unit, executionContext, utils);\n const unitRepository = utils.repositoriesRegistry.getRepository(CoreResourceType.ORGANIZATION_UNITS);\n for (const descendant of descendants) {\n await unitRepository.update(\n executionContext,\n descendant._id,\n { path: rebaseOrganizationUnitPath(descendant.path, oldSubtreePrefix, newSubtreePrefix) } as any,\n utils.serviceOptions,\n );\n }\n\n // The MOVE request DTO names no schema field, so the core UPDATE must be handed the real\n // mutation. `parentUnitId: null` (not `undefined`) is what clears the FK on a promotion.\n return { parentUnitId: newParentUnitId ?? null, path: newUnitPath };\n },\n },\n });\n\n return [factory as unknown as ResourceCustomServiceImplementationFactory];\n}\n\n/**\n * ARCHIVE / RESTORE — node-only status transitions (plan decision Q-B1).\n *\n * Archiving a branch marks ONLY the node: descendants keep their own status, which makes ARCHIVE\n * O(1) and keeps restore order explicit. RESTORE pays for that by validating the ancestor chain —\n * restoring a node under a still-archived parent would leave an active unit reachable only through\n * an inactive one, which is the inconsistency the spec's 409 describes.\n */\nfunction createOrganizationUnitStatusTransitionFactory(\n operationIdentifier: OrganizationUnit_Operations.ARCHIVE | OrganizationUnit_Operations.RESTORE,\n): ResourceCustomServiceImplementationFactory {\n const isArchive = operationIdentifier === OrganizationUnit_Operations.ARCHIVE;\n\n const factory = (_resourcesRegistryService: ResourcesRegistryBackendService) =>\n createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.ORGANIZATION_UNITS,\n operationIdentifier,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n debugLabel: `organization-units.${isArchive ? 'archive' : 'restore'}`,\n handlers: {\n prefixCoreOperations: async (_id, _input, executionContext, _operationPath, utils) => {\n const organizationUnitId = resolveSingleSubjectId(_id);\n if (organizationUnitId === undefined) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: {\n message: `Organization unit ${operationIdentifier.toUpperCase()} requires a single addressed unit`,\n reason: 'organization_unit_status_transition_unaddressed',\n receivedAddressing: describeUnaddressableShape(_id),\n },\n });\n }\n\n const unit = await loadScopedOrganizationUnit(organizationUnitId, executionContext, utils, {\n reason: 'organization_unit_not_found',\n organizationUnitId,\n });\n\n const alreadyArchived = unit.status === OrganizationUnitStatus.ARCHIVED;\n if (isArchive === alreadyArchived) {\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_CONFLICT,\n context: {\n reason: isArchive ? 'organization_unit_already_archived' : 'organization_unit_not_archived',\n organizationUnitId: unit._id,\n currentStatus: unit.status,\n },\n });\n }\n\n if (!isArchive) {\n // Restore order is explicit: every ancestor must already be out of ARCHIVED.\n for (const ancestorId of organizationUnitAncestorIds(unit)) {\n const ancestor = await loadScopedOrganizationUnit(ancestorId, executionContext, utils, {\n reason: 'organization_unit_ancestor_not_found',\n organizationUnitId: unit._id,\n ancestorUnitId: ancestorId,\n });\n if (ancestor.status === OrganizationUnitStatus.ARCHIVED) {\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_CONFLICT,\n context: {\n reason: 'organization_unit_ancestor_still_archived',\n organizationUnitId: unit._id,\n blockingAncestorUnitId: ancestor._id,\n },\n });\n }\n }\n }\n\n // As with MOVE, the request DTO carries only `reason`, so the real mutation is supplied here.\n return { status: isArchive ? OrganizationUnitStatus.ARCHIVED : OrganizationUnitStatus.ACTIVE };\n },\n },\n });\n\n return factory as unknown as ResourceCustomServiceImplementationFactory;\n}\n\n/** ARCHIVE + RESTORE implementations. */\nexport function createOrganizationUnitStatusTransitionFactories(): ResourceCustomServiceImplementationFactory[] {\n return [\n createOrganizationUnitStatusTransitionFactory(OrganizationUnit_Operations.ARCHIVE),\n createOrganizationUnitStatusTransitionFactory(OrganizationUnit_Operations.RESTORE),\n ];\n}\n\n/**\n * Register {@link assertNoOrganizationUnitDependents} as the `ORGANIZATION_UNITS` DELETE prefix.\n *\n * PREFIX: refuses the delete with 409 `ORGANIZATION_UNIT_HAS_DEPENDENTS` while any child unit\n * still names this node as its parent. There is no CORE replacement — when the guard passes, the\n * standard DELETE proceeds untouched.\n *\n * Registered on `API_CALL` — the variant the resource configuration actually declares for DELETE.\n * Custom implementations are resolved by an exact operation-path key that embeds `variantType`,\n * so a guard registered under any other variant would never run.\n */\nexport function createOrganizationUnitDeleteDependentsGuardFactories(): ResourceCustomServiceImplementationFactory[] {\n const factory = (_resourcesRegistryService: ResourcesRegistryBackendService) => {\n return createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.ORGANIZATION_UNITS,\n operationIdentifier: CoreResourceOperation.DELETE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n // Fail closed: the dependent scan filters on a single unit id, so a POPULATION-addressed\n // delete (an ids array or an unpinned filter reaching this path via `doByOperationPath`)\n // must be refused rather than silently producing a filter that matches nothing and waving\n // the delete through. A conditional `{ _id, … }` filter resolves to the one unit the delete\n // can destroy and is admitted.\n const organizationUnitId = resolveSingleSubjectId(_id);\n if (organizationUnitId === undefined) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: {\n message: 'Organization unit DELETE requires a single addressed unit to verify dependents',\n resourceType: CoreResourceType.ORGANIZATION_UNITS,\n receivedAddressing: describeUnaddressableShape(_id),\n },\n });\n }\n\n await assertNoOrganizationUnitDependents(organizationUnitId, executionContext, utils);\n\n // Guard only — the core DELETE input is passed through unchanged.\n return input;\n },\n },\n });\n };\n\n return [factory as unknown as ResourceCustomServiceImplementationFactory];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"organization-unit-custom-implementation.backend.service.js","sourceRoot":"","sources":["../../../../../src/organizations/services/organization-unit-custom-implementation.backend.service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,SAAS,EACT,2BAA2B,EAC3B,sBAAsB,EACtB,2BAA2B,EAC3B,4BAA4B,EAC5B,kCAAkC,EAClC,8BAA8B,EAC9B,2BAA2B,EAC3B,2BAA2B,EAC3B,6BAA6B,EAC7B,0BAA0B,GAE3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,yCAAyC,GAE1C,MAAM,4GAA4G,CAAC;AAIpH,OAAO,EACL,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,+EAA+E,CAAC;AAEvF;;;;;GAKG;AACH,MAAM,oBAAoB,GAAoD,cAAc,CAAC;AAE7F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,MAAM,CAAC,KAAK,UAAU,kCAAkC,CACtD,MAAc,EACd,gBAAuC,EACvC,KAAmD;IAEnD,MAAM,cAAc,GAAG,KAAK,CAAC,oBAAoB,CAAC,aAAa,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;IAErG,6FAA6F;IAC7F,yDAAyD;IACzD,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,aAAa,EAAE,gBAAgB,EAAE;YAC7E,sBAAsB,EAAE,2BAA2B,CAAC,aAAa;YACjE,OAAO,EAAE;gBACP,OAAO,EAAE,iGAAiG;gBAC1G,kBAAkB,EAAE,MAAM;gBAC1B,iBAAiB,EAAE,gBAAgB,CAAC,kBAAkB;aACvD;SACF,CAAC,CAAC;IACL,CAAC;IAED,MAAM,iBAAiB,GAAG,MAAM,cAAc,CAAC,IAAI,CAAmB,gBAAgB,EAAE;QACtF,CAAC,oBAAoB,CAAC,EAAE,MAAM;KAC/B,CAAC,CAAC;IAEH,IAAI,iBAAiB,EAAE,CAAC;QACtB,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,EAAE,gBAAgB,EAAE;YACxE,sBAAsB,EAAE,2BAA2B,CAAC,gCAAgC;YACpF,OAAO,EAAE;gBACP,kBAAkB,EAAE,MAAM;gBAC1B,mBAAmB,EAAE,iBAAiB,CAAC,GAAG;gBAC1C,qBAAqB,EAAE,iBAAiB,CAAC,IAAI;aAC9C;SACF,CAAC,CAAC;IACL,CAAC;IAED,gGAAgG;IAChG,kGAAkG;IAClG,gGAAgG;IAChG,MAAM,oBAAoB,GAAG,KAAK,CAAC,oBAAoB,CAAC,aAAa,CAAC,gBAAgB,CAAC,yBAAyB,CAAC,CAAC;IAClH,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC1B,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,aAAa,EAAE,gBAAgB,EAAE;YAC7E,sBAAsB,EAAE,2BAA2B,CAAC,aAAa;YACjE,OAAO,EAAE;gBACP,OAAO,EAAE,yGAAyG;gBAClH,kBAAkB,EAAE,MAAM;gBAC1B,iBAAiB,EAAE,gBAAgB,CAAC,yBAAyB;aAC9D;SACF,CAAC,CAAC;IACL,CAAC;IAED,MAAM,kBAAkB,GAAG,MAAM,oBAAoB,CAAC,IAAI,CACxD,gBAAgB,EAChB,EAAE,kBAAkB,EAAE,MAAM,EAAE,CAC/B,CAAC;IAEF,IAAI,kBAAkB,EAAE,CAAC;QACvB,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,EAAE,gBAAgB,EAAE;YACxE,sBAAsB,EAAE,2BAA2B,CAAC,gCAAgC;YACpF,OAAO,EAAE;gBACP,kBAAkB,EAAE,MAAM;gBAC1B,oBAAoB,EAAE,kBAAkB,CAAC,GAAG;gBAC5C,4BAA4B,EAAE,kBAAkB,CAAC,oBAAoB;aACtE;SACF,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAKD;;;;;;GAMG;AACH,KAAK,UAAU,0BAA0B,CACvC,MAAc,EACd,gBAAuC,EACvC,KAAmD,EACnD,eAAwC;IAExC,MAAM,cAAc,GAAG,KAAK,CAAC,oBAAoB,CAAC,aAAa,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;IACrG,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,aAAa,EAAE,gBAAgB,EAAE;YAC7E,sBAAsB,EAAE,2BAA2B,CAAC,aAAa;YACjE,OAAO,EAAE,EAAE,OAAO,EAAE,qDAAqD,EAAE,GAAG,eAAe,EAAE;SAChG,CAAC,CAAC;IACL,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,IAAI,CAA8B,gBAAgB,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;IACvG,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,SAAS,EAAE,gBAAgB,EAAE;YACzE,sBAAsB,EAAE,2BAA2B,CAAC,SAAS;YAC7D,OAAO,EAAE,eAAe;SACzB,CAAC,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,+BAA+B,CAC5C,IAAiC,EACjC,gBAAuC,EACvC,KAAmD;IAEnD,MAAM,cAAc,GAAG,KAAK,CAAC,oBAAoB,CAAC,aAAa,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;IACrG,MAAM,aAAa,GAAG,6BAA6B,CAAC,IAAI,CAAC,CAAC;IAE1D,gGAAgG;IAChG,6FAA6F;IAC7F,+FAA+F;IAC/F,qCAAqC;IACrC,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,mBAAmB,CACrD,gBAAgB,EAChB,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,EACvC,KAAK,CAAC,cAAc,CACrB,CAAC;IACF,MAAM,IAAI,GAAkC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,MAAkD,CAAC,IAAI,CAAC;IACtI,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,IAAI,2BAA2B,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;AAC9F,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,KAAK,UAAU,0BAA0B,CACvC,SAAiH,EACjH,gBAAuC,EACvC,KAAmD;IAEnD,IAAI,CAAC,SAAS,CAAC,IAAI;QAAE,OAAO;IAE5B,MAAM,cAAc,GAAG,KAAK,CAAC,oBAAoB,CAAC,aAAa,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;IACrG,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,IAAI,CAA8B,gBAAgB,EAAE;QACvF,cAAc,EAAE,SAAS,CAAC,cAAc;QACxC,YAAY,EAAE,SAAS,CAAC,YAAY,IAAI,IAAI;QAC5C,IAAI,EAAE,SAAS,CAAC,IAAI;KACrB,CAAC,CAAC;IAEH,IAAI,OAAO,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;QAChD,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,EAAE,gBAAgB,EAAE;YACxE,sBAAsB,EAAE,2BAA2B,CAAC,6BAA6B;YACjF,OAAO,EAAE;gBACP,MAAM,EAAE,sCAAsC;gBAC9C,kBAAkB,EAAE,SAAS,CAAC,MAAM;gBACpC,iBAAiB,EAAE,OAAO,CAAC,GAAG;gBAC9B,YAAY,EAAE,SAAS,CAAC,YAAY,IAAI,IAAI;gBAC5C,4FAA4F;gBAC5F,4FAA4F;gBAC5F,gGAAgG;gBAChG,mBAAmB,EAAE,SAAS,CAAC,IAAI;aACpC;SACF,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,yCAAyC;IACvD,MAAM,OAAO,GAAG,CAAC,yBAA0D,EAAE,EAAE,CAC7E,yCAAyC,CAAC;QACxC,aAAa,EAAE;YACb,kBAAkB,EAAE,gBAAgB,CAAC,kBAAkB;YACvD,mBAAmB,EAAE,qBAAqB,CAAC,MAAM;YACjD,WAAW,EAAE,4BAA4B,CAAC,QAAQ;YAClD,kBAAkB,EAAE,IAAI;SACzB;QACD,UAAU,EAAE,gDAAgD;QAC5D,QAAQ,EAAE;YACR,oBAAoB,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE;gBAClF,MAAM,QAAQ,GAAG,KAA0E,CAAC;gBAE5F,MAAM,cAAc,GAAG,QAAQ,CAAC,cAAc;uBACzC,gBAAgB,CAAC,oBAAoB,EAAE,CAAC,kCAAkC,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC;uBAC3G,gBAAgB,CAAC,YAAY,EAAE,cAAc,CAAC;gBAEnD,IAAI,CAAC,cAAc,EAAE,CAAC;oBACpB,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,gBAAgB,EAAE;wBAC1E,OAAO,EAAE,EAAE,OAAO,EAAE,+DAA+D,EAAE,MAAM,EAAE,mCAAmC,EAAE;qBACnI,CAAC,CAAC;gBACL,CAAC;gBAED,wFAAwF;gBACxF,2FAA2F;gBAC3F,yFAAyF;gBACzF,MAAM,UAAU,GAAG,QAAQ,CAAC,YAAY;oBACtC,CAAC,CAAC,MAAM,0BAA0B,CAAC,QAAQ,CAAC,YAAY,EAAE,gBAAgB,EAAE,KAAK,EAAE;wBACjF,MAAM,EAAE,oCAAoC;wBAC5C,YAAY,EAAE,QAAQ,CAAC,YAAY;qBACpC,CAAC;oBACF,CAAC,CAAC,IAAI,CAAC;gBAET,MAAM,0BAA0B,CAC9B,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,CAAC,YAAY,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,EACxF,gBAAgB,EAChB,KAAK,CACN,CAAC;gBAEF,OAAO,EAAE,GAAG,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,8BAA8B,CAAC,UAAU,CAAC,EAAE,CAAC;YACxF,CAAC;SACF;KACF,CAAC,CAAC;IAEL,OAAO,CAAC,OAAgE,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qCAAqC;IACnD,MAAM,OAAO,GAAG,CAAC,yBAA0D,EAAE,EAAE,CAC7E,yCAAyC,CAAC;QACxC,aAAa,EAAE;YACb,kBAAkB,EAAE,gBAAgB,CAAC,kBAAkB;YACvD,mBAAmB,EAAE,qBAAqB,CAAC,MAAM;YACjD,WAAW,EAAE,4BAA4B,CAAC,QAAQ;YAClD,kBAAkB,EAAE,IAAI;SACzB;QACD,UAAU,EAAE,8CAA8C;QAC1D,QAAQ,EAAE;YACR,oBAAoB,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE;gBAClF,MAAM,KAAK,GAAG,KAA2B,CAAC;gBAC1C,mFAAmF;gBACnF,wFAAwF;gBACxF,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;oBAAE,OAAO,KAAK,CAAC;gBAE5E,uFAAuF;gBACvF,0FAA0F;gBAC1F,6FAA6F;gBAC7F,mFAAmF;gBACnF,MAAM,kBAAkB,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;gBACvD,IAAI,kBAAkB,KAAK,SAAS,EAAE,CAAC;oBACrC,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,gBAAgB,EAAE;wBAC1E,OAAO,EAAE;4BACP,OAAO,EAAE,2EAA2E;4BACpF,MAAM,EAAE,sCAAsC;4BAC9C,kBAAkB,EAAE,0BAA0B,CAAC,GAAG,CAAC;yBACpD;qBACF,CAAC,CAAC;gBACL,CAAC;gBAED,MAAM,IAAI,GAAG,MAAM,0BAA0B,CAAC,kBAAkB,EAAE,gBAAgB,EAAE,KAAK,EAAE;oBACzF,MAAM,EAAE,6BAA6B;oBACrC,kBAAkB;iBACnB,CAAC,CAAC;gBAEH,MAAM,0BAA0B,CAC9B,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,EACzH,gBAAgB,EAChB,KAAK,CACN,CAAC;gBAEF,OAAO,KAAK,CAAC;YACf,CAAC;SACF;KACF,CAAC,CAAC;IAEL,OAAO,CAAC,OAAgE,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,mCAAmC;IACjD,MAAM,OAAO,GAAG,CAAC,yBAA0D,EAAE,EAAE,CAC7E,yCAAyC,CAAC;QACxC,aAAa,EAAE;YACb,kBAAkB,EAAE,gBAAgB,CAAC,kBAAkB;YACvD,mBAAmB,EAAE,2BAA2B,CAAC,IAAI;YACrD,WAAW,EAAE,4BAA4B,CAAC,QAAQ;YAClD,kBAAkB,EAAE,IAAI;SACzB;QACD,UAAU,EAAE,yBAAyB;QACrC,+FAA+F;QAC/F,6FAA6F;QAC7F,uFAAuF;QACvF,iBAAiB,EAAE,IAAI;QACvB,uBAAuB,EAAE,EAAE;QAC3B,QAAQ,EAAE;YACR,oBAAoB,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE;gBAClF,qFAAqF;gBACrF,yFAAyF;gBACzF,mBAAmB;gBACnB,MAAM,kBAAkB,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;gBACvD,IAAI,kBAAkB,KAAK,SAAS,EAAE,CAAC;oBACrC,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,gBAAgB,EAAE;wBAC1E,OAAO,EAAE;4BACP,OAAO,EAAE,yDAAyD;4BAClE,MAAM,EAAE,oCAAoC;4BAC5C,kBAAkB,EAAE,0BAA0B,CAAC,GAAG,CAAC;yBACpD;qBACF,CAAC,CAAC;gBACL,CAAC;gBAED,MAAM,SAAS,GAAG,KAAsC,CAAC;gBACzD,MAAM,cAAc,GAAG,SAAS,CAAC,eAAe,KAAK,SAAS,IAAI,SAAS,CAAC,eAAe,KAAK,IAAI,CAAC;gBACrG,IAAI,cAAc,IAAI,CAAC,OAAO,SAAS,CAAC,eAAe,KAAK,QAAQ,IAAI,SAAS,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;oBAChH,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,gBAAgB,EAAE;wBAC1E,OAAO,EAAE,EAAE,OAAO,EAAE,0FAA0F,EAAE,MAAM,EAAE,8CAA8C,EAAE;qBACzK,CAAC,CAAC;gBACL,CAAC;gBACD,MAAM,eAAe,GAAG,cAAc,CAAC,CAAC,CAAE,SAAS,CAAC,eAA0B,CAAC,CAAC,CAAC,SAAS,CAAC;gBAE3F,MAAM,IAAI,GAAG,MAAM,0BAA0B,CAAC,kBAAkB,EAAE,gBAAgB,EAAE,KAAK,EAAE;oBACzF,MAAM,EAAE,6BAA6B;oBACrC,kBAAkB;iBACnB,CAAC,CAAC;gBAEH,IAAI,eAAe,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;oBACjC,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,EAAE,gBAAgB,EAAE;wBACxE,sBAAsB,EAAE,2BAA2B,CAAC,+BAA+B;wBACnF,OAAO,EAAE,EAAE,MAAM,EAAE,kCAAkC,EAAE,kBAAkB,EAAE,IAAI,CAAC,GAAG,EAAE;qBACtF,CAAC,CAAC;gBACL,CAAC;gBAED,MAAM,aAAa,GAAG,eAAe;oBACnC,CAAC,CAAC,MAAM,0BAA0B,CAAC,eAAe,EAAE,gBAAgB,EAAE,KAAK,EAAE;wBAC3E,MAAM,EAAE,yCAAyC;wBACjD,eAAe;qBAChB,CAAC;oBACF,CAAC,CAAC,IAAI,CAAC;gBAET,0FAA0F;gBAC1F,oFAAoF;gBACpF,IAAI,aAAa,IAAI,2BAA2B,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,CAAC;oBACtE,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,EAAE,gBAAgB,EAAE;wBACxE,sBAAsB,EAAE,2BAA2B,CAAC,+BAA+B;wBACnF,OAAO,EAAE;4BACP,MAAM,EAAE,2CAA2C;4BACnD,kBAAkB,EAAE,IAAI,CAAC,GAAG;4BAC5B,eAAe;yBAChB;qBACF,CAAC,CAAC;gBACL,CAAC;gBAED,2FAA2F;gBAC3F,2FAA2F;gBAC3F,0FAA0F;gBAC1F,MAAM,0BAA0B,CAC9B,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,YAAY,EAAE,eAAe,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EACzG,gBAAgB,EAChB,KAAK,CACN,CAAC;gBAEF,yFAAyF;gBACzF,oFAAoF;gBACpF,MAAM,gBAAgB,GAAG,6BAA6B,CAAC,IAAI,CAAC,CAAC;gBAC7D,MAAM,WAAW,GAAG,8BAA8B,CAAC,aAAa,CAAC,CAAC;gBAClE,MAAM,gBAAgB,GAAG,GAAG,WAAW,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC;gBAEtD,MAAM,WAAW,GAAG,MAAM,+BAA+B,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC;gBACzF,MAAM,cAAc,GAAG,KAAK,CAAC,oBAAoB,CAAC,aAAa,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;gBACrG,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;oBACrC,MAAM,cAAc,CAAC,MAAM,CACzB,gBAAgB,EAChB,UAAU,CAAC,GAAG,EACd,EAAE,IAAI,EAAE,0BAA0B,CAAC,UAAU,CAAC,IAAI,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,EAAS,EAChG,KAAK,CAAC,cAAc,CACrB,CAAC;gBACJ,CAAC;gBAED,yFAAyF;gBACzF,yFAAyF;gBACzF,OAAO,EAAE,YAAY,EAAE,eAAe,IAAI,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;YACtE,CAAC;SACF;KACF,CAAC,CAAC;IAEL,OAAO,CAAC,OAAgE,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,6CAA6C,CACpD,mBAA8F;IAE9F,MAAM,SAAS,GAAG,mBAAmB,KAAK,2BAA2B,CAAC,OAAO,CAAC;IAE9E,MAAM,OAAO,GAAG,CAAC,yBAA0D,EAAE,EAAE,CAC7E,yCAAyC,CAAC;QACxC,aAAa,EAAE;YACb,kBAAkB,EAAE,gBAAgB,CAAC,kBAAkB;YACvD,mBAAmB;YACnB,WAAW,EAAE,4BAA4B,CAAC,QAAQ;YAClD,kBAAkB,EAAE,IAAI;SACzB;QACD,UAAU,EAAE,sBAAsB,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE;QACrE,QAAQ,EAAE;YACR,oBAAoB,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,gBAAgB,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE;gBACnF,MAAM,kBAAkB,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;gBACvD,IAAI,kBAAkB,KAAK,SAAS,EAAE,CAAC;oBACrC,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,gBAAgB,EAAE;wBAC1E,OAAO,EAAE;4BACP,OAAO,EAAE,qBAAqB,mBAAmB,CAAC,WAAW,EAAE,mCAAmC;4BAClG,MAAM,EAAE,iDAAiD;4BACzD,kBAAkB,EAAE,0BAA0B,CAAC,GAAG,CAAC;yBACpD;qBACF,CAAC,CAAC;gBACL,CAAC;gBAED,MAAM,IAAI,GAAG,MAAM,0BAA0B,CAAC,kBAAkB,EAAE,gBAAgB,EAAE,KAAK,EAAE;oBACzF,MAAM,EAAE,6BAA6B;oBACrC,kBAAkB;iBACnB,CAAC,CAAC;gBAEH,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,KAAK,sBAAsB,CAAC,QAAQ,CAAC;gBACxE,IAAI,SAAS,KAAK,eAAe,EAAE,CAAC;oBAClC,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,EAAE,gBAAgB,EAAE;wBACxE,sBAAsB,EAAE,2BAA2B,CAAC,0BAA0B;wBAC9E,OAAO,EAAE;4BACP,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,oCAAoC,CAAC,CAAC,CAAC,gCAAgC;4BAC3F,kBAAkB,EAAE,IAAI,CAAC,GAAG;4BAC5B,aAAa,EAAE,IAAI,CAAC,MAAM;yBAC3B;qBACF,CAAC,CAAC;gBACL,CAAC;gBAED,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,6EAA6E;oBAC7E,KAAK,MAAM,UAAU,IAAI,2BAA2B,CAAC,IAAI,CAAC,EAAE,CAAC;wBAC3D,MAAM,QAAQ,GAAG,MAAM,0BAA0B,CAAC,UAAU,EAAE,gBAAgB,EAAE,KAAK,EAAE;4BACrF,MAAM,EAAE,sCAAsC;4BAC9C,kBAAkB,EAAE,IAAI,CAAC,GAAG;4BAC5B,cAAc,EAAE,UAAU;yBAC3B,CAAC,CAAC;wBACH,IAAI,QAAQ,CAAC,MAAM,KAAK,sBAAsB,CAAC,QAAQ,EAAE,CAAC;4BACxD,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,EAAE,gBAAgB,EAAE;gCACxE,sBAAsB,EAAE,2BAA2B,CAAC,0BAA0B;gCAC9E,OAAO,EAAE;oCACP,MAAM,EAAE,2CAA2C;oCACnD,kBAAkB,EAAE,IAAI,CAAC,GAAG;oCAC5B,sBAAsB,EAAE,QAAQ,CAAC,GAAG;iCACrC;6BACF,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;gBACH,CAAC;gBAED,8FAA8F;gBAC9F,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,sBAAsB,CAAC,QAAQ,CAAC,CAAC,CAAC,sBAAsB,CAAC,MAAM,EAAE,CAAC;YACjG,CAAC;SACF;KACF,CAAC,CAAC;IAEL,OAAO,OAAgE,CAAC;AAC1E,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,+CAA+C;IAC7D,OAAO;QACL,6CAA6C,CAAC,2BAA2B,CAAC,OAAO,CAAC;QAClF,6CAA6C,CAAC,2BAA2B,CAAC,OAAO,CAAC;KACnF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oDAAoD;IAClE,MAAM,OAAO,GAAG,CAAC,yBAA0D,EAAE,EAAE;QAC7E,OAAO,yCAAyC,CAAC;YAC/C,aAAa,EAAE;gBACb,kBAAkB,EAAE,gBAAgB,CAAC,kBAAkB;gBACvD,mBAAmB,EAAE,qBAAqB,CAAC,MAAM;gBACjD,WAAW,EAAE,4BAA4B,CAAC,QAAQ;gBAClD,kBAAkB,EAAE,IAAI;aACzB;YACD,QAAQ,EAAE;gBACR,oBAAoB,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE;oBAClF,yFAAyF;oBACzF,yFAAyF;oBACzF,0FAA0F;oBAC1F,4FAA4F;oBAC5F,+BAA+B;oBAC/B,MAAM,kBAAkB,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;oBACvD,IAAI,kBAAkB,KAAK,SAAS,EAAE,CAAC;wBACrC,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,gBAAgB,EAAE;4BAC1E,OAAO,EAAE;gCACP,OAAO,EAAE,gFAAgF;gCACzF,YAAY,EAAE,gBAAgB,CAAC,kBAAkB;gCACjD,kBAAkB,EAAE,0BAA0B,CAAC,GAAG,CAAC;6BACpD;yBACF,CAAC,CAAC;oBACL,CAAC;oBAED,MAAM,kCAAkC,CAAC,kBAAkB,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC;oBAEtF,kEAAkE;oBAClE,OAAO,KAAK,CAAC;gBACf,CAAC;aACF;SACF,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,OAAO,CAAC,OAAgE,CAAC,CAAC;AAC5E,CAAC","sourcesContent":["/**\n * Organization Unit Custom Implementations\n *\n * DELETE dependents guard: refuses to delete an interior node of the organization tree while\n * child units still hang off it.\n */\n\nimport {\n CoreResourceOperation,\n CoreResourceType,\n ErrorType,\n ErrorCustomMessageReference,\n OrganizationUnitStatus,\n OrganizationUnit_Operations,\n ResourceOperationVariantType,\n coreResourceShared_FieldIdentifier,\n buildOrganizationUnitChildPath,\n isOrganizationUnitInSubtree,\n organizationUnitAncestorIds,\n organizationUnitSubtreePrefix,\n rebaseOrganizationUnitPath,\n type OrganizationUnit,\n} from '@wildo-ai/saas-models';\nimport {\n createResourceCustomServiceImplementation,\n type ResourceCustomServiceImplementationFactory,\n} from '~wildo-backend-lib/base-classes/services/services/resource-operation-custom-implementation.factory.backend';\nimport type { ResourcesRegistryBackendService } from '~wildo-backend-lib/resources/services/resources-registry.backend.service';\nimport type { ResourceOperation_CustomImplementation_Utils } from '~wildo-backend-lib/base-classes/services/services/resource-operation-custom-implementation-base.backend.service';\nimport type { ExecutionContext } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\nimport {\n describeUnaddressableShape,\n resolveSingleSubjectId,\n} from '~wildo-backend-lib/base-classes/utils/single-subject-addressing.backend.utils';\n\n/**\n * The FK that materializes the organization-unit tree: a unit's parent node.\n *\n * Typed against {@link OrganizationUnit} so a rename of the field breaks the build here rather\n * than silently degrading the guard into a filter that matches nothing.\n */\nconst PARENT_UNIT_FK_FIELD: Extract<keyof OrganizationUnit, 'parentUnitId'> = 'parentUnitId';\n\n/**\n * Refuse removal of an organization unit while child units still hang off it.\n *\n * ## Why this guard exists\n *\n * `ORGANIZATION_UNITS -> ORGANIZATION_UNITS` (the tree self-edge on `parentUnitId`) is the ONE\n * `ORGANIZATIONS`-family relationship that declares no `onParentDelete` cascade. Deleting an\n * interior node therefore strands its entire subtree: every descendant keeps a `parentUnitId`\n * pointing at a row that no longer exists, and the materialized `path` of the whole branch — a\n * server-computed field (`.excludeFromCreate().excludeFromUpdate()`) that nothing recomputes —\n * silently describes a hierarchy that is no longer real. Neither is recoverable from the\n * remaining data, because the deleted node held the only link between the branch and its\n * grandparent.\n *\n * Enabling a cascade instead was rejected: the spec's stated policy is that the operator decides\n * where an orphaned branch belongs, and silently hard-deleting an arbitrarily deep subtree behind\n * a single-node DELETE is a far worse default for a `HIGH` risk-level operation than refusing it.\n *\n * The remedy is MOVE: re-parent each child onto another node, then delete the emptied leaf. MOVE\n * rewrites the materialized `path` of the whole subtree in one transaction, which a raw\n * `parentUnitId` edit never did — which is why `parentUnitId` became `.excludeFromUpdate()` in the\n * same pass that implemented MOVE (plan decision D-B2). Until then the guard's only escape hatch was\n * that raw edit, and it silently left every descendant's path describing a tree that no longer\n * existed.\n *\n * ## Scope: direct per-unit DELETE only\n *\n * Deleting the whole ORGANIZATION is a different path and is deliberately NOT affected. That\n * cascade IS enabled (`ORGANIZATIONS -> ORGANIZATION_UNITS`) and removes every unit of the\n * tenant in one pass — depth is irrelevant there because every unit, root or nested, carries the\n * tenant's `organizationId`. It also runs at the REPOSITORY level, which does not execute service\n * custom implementations, so this guard is structurally never consulted on that path. Tearing a\n * tenant down does not require dismantling its org chart leaf-by-leaf first.\n *\n * ## Why it fails closed\n *\n * DELETE is `riskLevel: HIGH`, so \"cannot verify\" must never read as \"nothing found\". An\n * unregistered `ORGANIZATION_UNITS` repository raises `CONFIGURATION` rather than being skipped,\n * and any scope-filter failure inside the read propagates instead of being swallowed.\n *\n * Detection uses `read()` rather than `count()` deliberately: `ORGANIZATION_UNITS` declares no\n * `COUNT` core operation, so `repository.count()` would throw INTERNAL. It does declare `READ`.\n * The read is a single indexed hit on `parentUnitId` (`.isDBIndexed()`), and the guard only needs\n * existence, not a tally.\n *\n * ## Also covered: unit assignments (since D-B5)\n *\n * Assigned members block the delete too. This dependent used to be EXCLUDED: unit assignment lived\n * in an embedded `OrganizationMember.organizationUnitsMembership` array, which is not a registered\n * relationship and has no adapter-neutral query — a dot-path filter into a JSONB array resolves to\n * NULL on PostgreSQL (matching nothing) and the LIST summary projection strips the field entirely,\n * so BOTH available scans would have failed OPEN on a guard whose whole purpose is to fail closed.\n * Promoting the assignment to the `ORGANIZATION_UNIT_MEMBERS` junction is exactly what made it\n * checkable, and it is now checked here on the same fail-closed terms as child units.\n *\n * @param unitId - The unit row about to be deleted.\n * @throws `CONFLICT` (409, `ORGANIZATION_UNIT_HAS_DEPENDENTS`) carrying whichever dependent blocked:\n * `blockingChildUnitId` for a child unit, `blockingUnitMemberId` for a member assignment.\n * @throws `CONFIGURATION` when the organization-units repository is not registered (unverifiable).\n */\nexport async function assertNoOrganizationUnitDependents(\n unitId: string,\n executionContext: ExecutionContext<any>,\n utils: ResourceOperation_CustomImplementation_Utils,\n): Promise<void> {\n const unitRepository = utils.repositoriesRegistry.getRepository(CoreResourceType.ORGANIZATION_UNITS);\n\n // Fail closed: an unregistered repository means we cannot prove the node is a leaf. Skipping\n // would silently downgrade the guard to \"nothing found\".\n if (!unitRepository) {\n throw utils.errorBuilder.buildError(ErrorType.CONFIGURATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.CONFIGURATION,\n context: {\n message: 'Cannot verify organization-unit dependents: the organization-units repository is not registered',\n organizationUnitId: unitId,\n childResourceType: CoreResourceType.ORGANIZATION_UNITS,\n },\n });\n }\n\n const blockingChildUnit = await unitRepository.read<OrganizationUnit>(executionContext, {\n [PARENT_UNIT_FK_FIELD]: unitId,\n });\n\n if (blockingChildUnit) {\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_HAS_DEPENDENTS,\n context: {\n organizationUnitId: unitId,\n blockingChildUnitId: blockingChildUnit._id,\n blockingChildUnitName: blockingChildUnit.name,\n },\n });\n }\n\n // Assigned members — checkable since D-B5 promoted unit assignment to a real junction resource.\n // Same fail-closed terms as the child-unit scan above: an unregistered repository means we cannot\n // prove the unit is unassigned, which must never read as \"nothing found\" on a HIGH-risk delete.\n const unitMemberRepository = utils.repositoriesRegistry.getRepository(CoreResourceType.ORGANIZATION_UNIT_MEMBERS);\n if (!unitMemberRepository) {\n throw utils.errorBuilder.buildError(ErrorType.CONFIGURATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.CONFIGURATION,\n context: {\n message: 'Cannot verify organization-unit assignments: the organization-unit-members repository is not registered',\n organizationUnitId: unitId,\n childResourceType: CoreResourceType.ORGANIZATION_UNIT_MEMBERS,\n },\n });\n }\n\n const blockingAssignment = await unitMemberRepository.read<{ _id: string; organizationMemberId: string }>(\n executionContext,\n { organizationUnitId: unitId },\n );\n\n if (blockingAssignment) {\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_HAS_DEPENDENTS,\n context: {\n organizationUnitId: unitId,\n blockingUnitMemberId: blockingAssignment._id,\n blockingOrganizationMemberId: blockingAssignment.organizationMemberId,\n },\n });\n }\n}\n\n/** The unit fields these implementations read; kept narrow so a schema rename breaks the build here. */\ntype OrganizationUnitTreeNodeRow = Pick<OrganizationUnit, '_id' | 'organizationId' | 'parentUnitId' | 'path' | 'status' | 'code' | 'name'>;\n\n/**\n * Load a unit that MUST exist inside the caller's scope, or fail with 404.\n *\n * Cross-tenant lookups resolve to \"not found\" rather than a conflict on purpose: the contextual\n * filter scopes the read to the caller's organization, so a unit belonging to another tenant is\n * simply absent. Reporting 409 there would confirm the existence of a row the caller may not see.\n */\nasync function loadScopedOrganizationUnit(\n unitId: string,\n executionContext: ExecutionContext<any>,\n utils: ResourceOperation_CustomImplementation_Utils,\n notFoundContext: Record<string, unknown>,\n): Promise<OrganizationUnitTreeNodeRow> {\n const unitRepository = utils.repositoriesRegistry.getRepository(CoreResourceType.ORGANIZATION_UNITS);\n if (!unitRepository) {\n throw utils.errorBuilder.buildError(ErrorType.CONFIGURATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.CONFIGURATION,\n context: { message: 'The organization-units repository is not registered', ...notFoundContext },\n });\n }\n\n const unit = await unitRepository.read<OrganizationUnitTreeNodeRow>(executionContext, { _id: unitId });\n if (!unit) {\n throw utils.errorBuilder.buildError(ErrorType.NOT_FOUND, executionContext, {\n customMessageReference: ErrorCustomMessageReference.NOT_FOUND,\n context: notFoundContext,\n });\n }\n return unit;\n}\n\n/**\n * Every unit strictly below `unit`, found by the materialized-path prefix.\n *\n * Uses `listForInternalScan` rather than `list`: this is an internal integrity sweep, and the LIST\n * summary projection would strip `path` — the one field the rewrite depends on.\n */\nasync function listOrganizationUnitDescendants(\n unit: OrganizationUnitTreeNodeRow,\n executionContext: ExecutionContext<any>,\n utils: ResourceOperation_CustomImplementation_Utils,\n): Promise<OrganizationUnitTreeNodeRow[]> {\n const unitRepository = utils.repositoriesRegistry.getRepository(CoreResourceType.ORGANIZATION_UNITS);\n const subtreePrefix = organizationUnitSubtreePrefix(unit);\n\n // Filter on the organization, then narrow by prefix in memory. An adapter-neutral \"starts with\"\n // filter does not exist across Mongo and SQL, and inventing one per adapter here would put a\n // query dialect inside a domain rule. Org trees are small (hundreds of nodes), and the scan is\n // already scoped to a single tenant.\n const scoped = await unitRepository.listForInternalScan<OrganizationUnitTreeNodeRow>(\n executionContext,\n { organizationId: unit.organizationId },\n utils.serviceOptions,\n );\n const rows: OrganizationUnitTreeNodeRow[] = Array.isArray(scoped) ? scoped : (scoped as { data: OrganizationUnitTreeNodeRow[] }).data;\n return rows.filter((row) => row._id !== unit._id && isOrganizationUnitInSubtree(row, unit));\n}\n\n/**\n * Refuse a `code` that is already taken by a sibling under `parentUnitId`.\n *\n * Sibling-scoped rather than org-wide: two teams called `sales` under different divisions are a\n * legitimate org chart, and `code` is optional so many rows carry none at all. That exact identity\n * — (`organizationId`, `parentUnitId`, `code`), scoped to rows that HAVE a code — is now also the\n * schema's business key, so MongoDB enforces it with a partial compound unique index.\n *\n * **This guard is deliberately KEPT rather than deleted as redundant.** Schema-declared uniqueness\n * is emitted for MongoDB only; the SQL planner records it as intent and leaves enforcement\n * migration-owned, so on a SQL adapter this function is the ONLY thing standing between two\n * same-coded siblings. It is a read-then-write and therefore still racy — under concurrency the\n * index is what actually closes the window on Mongo — but a racy check that usually wins is\n * strictly better than no check at all on an adapter that has no index to fall back to.\n *\n * Both layers raise the SAME reference. The index cannot know which operation was running, so an\n * operation-specific code would make one condition report differently depending on which write won\n * the race — an API client would have to handle both to recognise one situation.\n */\nasync function assertSiblingCodeAvailable(\n candidate: { unitId: string; organizationId: string; parentUnitId: string | undefined; code: string | undefined },\n executionContext: ExecutionContext<any>,\n utils: ResourceOperation_CustomImplementation_Utils,\n): Promise<void> {\n if (!candidate.code) return;\n\n const unitRepository = utils.repositoriesRegistry.getRepository(CoreResourceType.ORGANIZATION_UNITS);\n const sibling = await unitRepository.read<OrganizationUnitTreeNodeRow>(executionContext, {\n organizationId: candidate.organizationId,\n parentUnitId: candidate.parentUnitId ?? null,\n code: candidate.code,\n });\n\n if (sibling && sibling._id !== candidate.unitId) {\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_CODE_EXISTS,\n context: {\n reason: 'organization_unit_sibling_code_taken',\n organizationUnitId: candidate.unitId,\n conflictingUnitId: sibling._id,\n parentUnitId: candidate.parentUnitId ?? null,\n // NOT `code`: that key is the client-facing refusal-code contract (allow-listed, hoisted to\n // `error.code`) and must only ever carry a declared enum value. The user-typed unit code is\n // diagnostic context and stays server-side; `customMessageReference` already names the refusal.\n conflictingUnitCode: candidate.code,\n },\n });\n }\n}\n\n/**\n * CREATE prefix — materialize `path` on the SAME write that inserts the row.\n *\n * `path` is `.excludeFromCreate()` and was previously never written by anything, so every unit\n * persisted with an empty hierarchy path (the defect this closes). The prefix's return value is\n * merged into the create payload and passed through the loose repository write, which is how a\n * server-owned field reaches persistence without being client-settable.\n *\n * The path is derived from the PARENT alone — see the format contract in\n * `organization-units.shared.path.ts` for why it cannot include the new row's own id.\n */\nexport function createOrganizationUnitCreatePathFactories(): ResourceCustomServiceImplementationFactory[] {\n const factory = (_resourcesRegistryService: ResourcesRegistryBackendService) =>\n createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.ORGANIZATION_UNITS,\n operationIdentifier: CoreResourceOperation.CREATE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n debugLabel: 'organization-units.create.path-materialization',\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n const authored = input as { parentUnitId?: string; organizationId?: string; code?: string };\n\n const organizationId = authored.organizationId\n ?? executionContext.getContextResourceId?.(coreResourceShared_FieldIdentifier[CoreResourceType.ORGANIZATIONS])\n ?? executionContext.initiatorIds?.organizationId;\n\n if (!organizationId) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: { message: 'An organization scope is required to place a unit in the tree', reason: 'organization_unit_create_unscoped' },\n });\n }\n\n // A parent that was NAMED but cannot be resolved must fail, never silently fall back to\n // root: the scoped read returns nothing for a unit in another tenant, and treating that as\n // \"no parent\" would quietly promote a cross-tenant child to a root of the caller's tree.\n const parentUnit = authored.parentUnitId\n ? await loadScopedOrganizationUnit(authored.parentUnitId, executionContext, utils, {\n reason: 'organization_unit_parent_not_found',\n parentUnitId: authored.parentUnitId,\n })\n : null;\n\n await assertSiblingCodeAvailable(\n { unitId: '', organizationId, parentUnitId: authored.parentUnitId, code: authored.code },\n executionContext,\n utils,\n );\n\n return { ...input, organizationId, path: buildOrganizationUnitChildPath(parentUnit) };\n },\n },\n });\n\n return [factory as unknown as ResourceCustomServiceImplementationFactory];\n}\n\n/**\n * UPDATE prefix — keep sibling `code` unambiguous on a rename.\n *\n * Re-parenting is no longer reachable here (`parentUnitId` is `.excludeFromUpdate()` since MOVE\n * shipped — D-B2), so the only structural collision generic UPDATE can still cause is two same-coded\n * siblings under one parent. That is the surviving half of the spec's `ORGANIZATION_UNIT_CONFLICT`\n * scenario; the cycle half now lives entirely under MOVE.\n */\nexport function createOrganizationUnitUpdateFactories(): ResourceCustomServiceImplementationFactory[] {\n const factory = (_resourcesRegistryService: ResourcesRegistryBackendService) =>\n createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.ORGANIZATION_UNITS,\n operationIdentifier: CoreResourceOperation.UPDATE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n debugLabel: 'organization-units.update.sibling-code-guard',\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n const patch = input as { code?: unknown };\n // Only a rename can collide. An update that leaves `code` alone cannot introduce a\n // duplicate, and re-checking it would 409 every unrelated edit of an already-valid row.\n if (typeof patch.code !== 'string' || patch.code.length === 0) return input;\n\n // Resolve rather than cast. The former `_id as string` asserted a shape the addressing\n // contract does not guarantee: a filter-addressed rename would have handed an OBJECT to a\n // by-id read, which builds `{ _id: <object> }` and matches nothing — surfacing as a spurious\n // NOT_FOUND while the sibling-code collision it was meant to catch went unchecked.\n const organizationUnitId = resolveSingleSubjectId(_id);\n if (organizationUnitId === undefined) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: {\n message: 'Organization unit UPDATE renaming `code` requires a single addressed unit',\n reason: 'organization_unit_rename_unaddressed',\n receivedAddressing: describeUnaddressableShape(_id),\n },\n });\n }\n\n const unit = await loadScopedOrganizationUnit(organizationUnitId, executionContext, utils, {\n reason: 'organization_unit_not_found',\n organizationUnitId,\n });\n\n await assertSiblingCodeAvailable(\n { unitId: unit._id, organizationId: unit.organizationId, parentUnitId: unit.parentUnitId ?? undefined, code: patch.code },\n executionContext,\n utils,\n );\n\n return input;\n },\n },\n });\n\n return [factory as unknown as ResourceCustomServiceImplementationFactory];\n}\n\n/**\n * MOVE — re-parent a unit and re-home its entire subtree.\n *\n * The descendant rewrite is the part that must never be skipped: leaving it out is exactly the\n * \"materialized field nothing recomputes\" bug that `path` was already an instance of. Because the\n * rewrite spans many rows, the whole operation runs in one transaction — a partial rewrite would\n * leave the tree describing a shape that never existed.\n *\n * An absent `newParentUnitId` means \"promote to root\", which the request DTO models as optional. A\n * present-but-blank value is malformed and is refused rather than silently read as a promotion.\n *\n * ⚠️ Cost: the subtree rewrite is a scan plus one `update()` per descendant. `repository.updateMany`\n * is unavailable here — it requires an `UPDATE_MANY` core operation, which this resource does not\n * declare. Acceptable while org charts are small; revisit if deep trees appear.\n */\nexport function createOrganizationUnitMoveFactories(): ResourceCustomServiceImplementationFactory[] {\n const factory = (_resourcesRegistryService: ResourcesRegistryBackendService) =>\n createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.ORGANIZATION_UNITS,\n operationIdentifier: OrganizationUnit_Operations.MOVE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n debugLabel: 'organization-units.move',\n // The subtree rewrite spans many ORGANIZATION_UNITS rows plus the moved node itself. A partial\n // rewrite would leave the tree describing a shape that never existed, so they share one unit\n // of work. No OTHER resource participates — the move is entirely within the unit tree.\n transactionalCore: true,\n transactionParticipants: {},\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n // A conditional `{ _id, … }` filter names one unit and is admitted; only a genuinely\n // population-addressed move is refused, since the subtree rewrite below is computed from\n // ONE node's path.\n const organizationUnitId = resolveSingleSubjectId(_id);\n if (organizationUnitId === undefined) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: {\n message: 'Organization unit MOVE requires a single addressed unit',\n reason: 'organization_unit_move_unaddressed',\n receivedAddressing: describeUnaddressableShape(_id),\n },\n });\n }\n\n const requested = input as { newParentUnitId?: unknown };\n const hasDestination = requested.newParentUnitId !== undefined && requested.newParentUnitId !== null;\n if (hasDestination && (typeof requested.newParentUnitId !== 'string' || requested.newParentUnitId.length === 0)) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: { message: 'newParentUnitId must be a non-empty unit id, or be omitted to promote the unit to a root', reason: 'organization_unit_move_malformed_destination' },\n });\n }\n const newParentUnitId = hasDestination ? (requested.newParentUnitId as string) : undefined;\n\n const unit = await loadScopedOrganizationUnit(organizationUnitId, executionContext, utils, {\n reason: 'organization_unit_not_found',\n organizationUnitId,\n });\n\n if (newParentUnitId === unit._id) {\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_MOVE_CONFLICT,\n context: { reason: 'organization_unit_move_onto_self', organizationUnitId: unit._id },\n });\n }\n\n const newParentUnit = newParentUnitId\n ? await loadScopedOrganizationUnit(newParentUnitId, executionContext, utils, {\n reason: 'organization_unit_destination_not_found',\n newParentUnitId,\n })\n : null;\n\n // O(1) cycle test — the payoff of the materialized path. Moving a node beneath one of its\n // own descendants would detach the branch: it would have no route back to the root.\n if (newParentUnit && isOrganizationUnitInSubtree(newParentUnit, unit)) {\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_MOVE_CONFLICT,\n context: {\n reason: 'organization_unit_move_would_create_cycle',\n organizationUnitId: unit._id,\n newParentUnitId,\n },\n });\n }\n\n // Raises ORGANIZATION_UNIT_CODE_EXISTS, not MOVE_CONFLICT: the destination itself is legal\n // here — it is the unit's own `code` that collides — so renaming the unit is a remedy that\n // choosing another destination is not. The two conditions want different client handling.\n await assertSiblingCodeAvailable(\n { unitId: unit._id, organizationId: unit.organizationId, parentUnitId: newParentUnitId, code: unit.code },\n executionContext,\n utils,\n );\n\n // Re-home the branch. Descendants are rewritten BEFORE the node's own update lands, so a\n // failure anywhere rolls the whole move back rather than leaving a half-moved tree.\n const oldSubtreePrefix = organizationUnitSubtreePrefix(unit);\n const newUnitPath = buildOrganizationUnitChildPath(newParentUnit);\n const newSubtreePrefix = `${newUnitPath}${unit._id}/`;\n\n const descendants = await listOrganizationUnitDescendants(unit, executionContext, utils);\n const unitRepository = utils.repositoriesRegistry.getRepository(CoreResourceType.ORGANIZATION_UNITS);\n for (const descendant of descendants) {\n await unitRepository.update(\n executionContext,\n descendant._id,\n { path: rebaseOrganizationUnitPath(descendant.path, oldSubtreePrefix, newSubtreePrefix) } as any,\n utils.serviceOptions,\n );\n }\n\n // The MOVE request DTO names no schema field, so the core UPDATE must be handed the real\n // mutation. `parentUnitId: null` (not `undefined`) is what clears the FK on a promotion.\n return { parentUnitId: newParentUnitId ?? null, path: newUnitPath };\n },\n },\n });\n\n return [factory as unknown as ResourceCustomServiceImplementationFactory];\n}\n\n/**\n * ARCHIVE / RESTORE — node-only status transitions (plan decision Q-B1).\n *\n * Archiving a branch marks ONLY the node: descendants keep their own status, which makes ARCHIVE\n * O(1) and keeps restore order explicit. RESTORE pays for that by validating the ancestor chain —\n * restoring a node under a still-archived parent would leave an active unit reachable only through\n * an inactive one, which is the inconsistency the spec's 409 describes.\n */\nfunction createOrganizationUnitStatusTransitionFactory(\n operationIdentifier: OrganizationUnit_Operations.ARCHIVE | OrganizationUnit_Operations.RESTORE,\n): ResourceCustomServiceImplementationFactory {\n const isArchive = operationIdentifier === OrganizationUnit_Operations.ARCHIVE;\n\n const factory = (_resourcesRegistryService: ResourcesRegistryBackendService) =>\n createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.ORGANIZATION_UNITS,\n operationIdentifier,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n debugLabel: `organization-units.${isArchive ? 'archive' : 'restore'}`,\n handlers: {\n prefixCoreOperations: async (_id, _input, executionContext, _operationPath, utils) => {\n const organizationUnitId = resolveSingleSubjectId(_id);\n if (organizationUnitId === undefined) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: {\n message: `Organization unit ${operationIdentifier.toUpperCase()} requires a single addressed unit`,\n reason: 'organization_unit_status_transition_unaddressed',\n receivedAddressing: describeUnaddressableShape(_id),\n },\n });\n }\n\n const unit = await loadScopedOrganizationUnit(organizationUnitId, executionContext, utils, {\n reason: 'organization_unit_not_found',\n organizationUnitId,\n });\n\n const alreadyArchived = unit.status === OrganizationUnitStatus.ARCHIVED;\n if (isArchive === alreadyArchived) {\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_CONFLICT,\n context: {\n reason: isArchive ? 'organization_unit_already_archived' : 'organization_unit_not_archived',\n organizationUnitId: unit._id,\n currentStatus: unit.status,\n },\n });\n }\n\n if (!isArchive) {\n // Restore order is explicit: every ancestor must already be out of ARCHIVED.\n for (const ancestorId of organizationUnitAncestorIds(unit)) {\n const ancestor = await loadScopedOrganizationUnit(ancestorId, executionContext, utils, {\n reason: 'organization_unit_ancestor_not_found',\n organizationUnitId: unit._id,\n ancestorUnitId: ancestorId,\n });\n if (ancestor.status === OrganizationUnitStatus.ARCHIVED) {\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n customMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_CONFLICT,\n context: {\n reason: 'organization_unit_ancestor_still_archived',\n organizationUnitId: unit._id,\n blockingAncestorUnitId: ancestor._id,\n },\n });\n }\n }\n }\n\n // As with MOVE, the request DTO carries only `reason`, so the real mutation is supplied here.\n return { status: isArchive ? OrganizationUnitStatus.ARCHIVED : OrganizationUnitStatus.ACTIVE };\n },\n },\n });\n\n return factory as unknown as ResourceCustomServiceImplementationFactory;\n}\n\n/** ARCHIVE + RESTORE implementations. */\nexport function createOrganizationUnitStatusTransitionFactories(): ResourceCustomServiceImplementationFactory[] {\n return [\n createOrganizationUnitStatusTransitionFactory(OrganizationUnit_Operations.ARCHIVE),\n createOrganizationUnitStatusTransitionFactory(OrganizationUnit_Operations.RESTORE),\n ];\n}\n\n/**\n * Register {@link assertNoOrganizationUnitDependents} as the `ORGANIZATION_UNITS` DELETE prefix.\n *\n * PREFIX: refuses the delete with 409 `ORGANIZATION_UNIT_HAS_DEPENDENTS` while any child unit\n * still names this node as its parent. There is no CORE replacement — when the guard passes, the\n * standard DELETE proceeds untouched.\n *\n * Registered on `API_CALL` — the variant the resource configuration actually declares for DELETE.\n * Custom implementations are resolved by an exact operation-path key that embeds `variantType`,\n * so a guard registered under any other variant would never run.\n */\nexport function createOrganizationUnitDeleteDependentsGuardFactories(): ResourceCustomServiceImplementationFactory[] {\n const factory = (_resourcesRegistryService: ResourcesRegistryBackendService) => {\n return createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.ORGANIZATION_UNITS,\n operationIdentifier: CoreResourceOperation.DELETE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n // Fail closed: the dependent scan filters on a single unit id, so a POPULATION-addressed\n // delete (an ids array or an unpinned filter reaching this path via `doByOperationPath`)\n // must be refused rather than silently producing a filter that matches nothing and waving\n // the delete through. A conditional `{ _id, … }` filter resolves to the one unit the delete\n // can destroy and is admitted.\n const organizationUnitId = resolveSingleSubjectId(_id);\n if (organizationUnitId === undefined) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: {\n message: 'Organization unit DELETE requires a single addressed unit to verify dependents',\n resourceType: CoreResourceType.ORGANIZATION_UNITS,\n receivedAddressing: describeUnaddressableShape(_id),\n },\n });\n }\n\n await assertNoOrganizationUnitDependents(organizationUnitId, executionContext, utils);\n\n // Guard only — the core DELETE input is passed through unchanged.\n return input;\n },\n },\n });\n };\n\n return [factory as unknown as ResourceCustomServiceImplementationFactory];\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket.service.d.ts","sourceRoot":"","sources":["../../../../../src/requests/service/websocket.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,SAAS,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AAM5C,OAAO,EAAE,aAAa,EAAE,MAAM,8DAA8D,CAAC;AAC7F,OAAO,EAAE,uBAAuB,EAAE,MAAM,sEAAsE,CAAC;AAC/G,OAAO,EAAE,YAAY,EAAE,MAAM,qDAAqD,CAAC;AACnF,OAAO,EAAE,0BAA0B,EAAE,MAAM,kEAAkE,CAAC;AAE9G,OAAO,EAQL,QAAQ,EAST,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,qCAAqC,EACtC,MAAM,+EAA+E,CAAC;AACvF,OAAO,EAAE,4BAA4B,EAAE,MAAM,oFAAoF,CAAC;AAClI,OAAO,EAAE,+BAA+B,EAAE,MAAM,0EAA0E,CAAC;AAE3H,OAAO,EAIL,yBAAyB,EACzB,yBAAyB,EACzB,2BAA2B,EAE3B,oCAAoC,EAKpC,iBAAiB,EACjB,yBAAyB,EACzB,cAAc,
|
|
1
|
+
{"version":3,"file":"websocket.service.d.ts","sourceRoot":"","sources":["../../../../../src/requests/service/websocket.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@ import { ErrorCustomMessageReference, ErrorType, ExecutionContext_ExecutionType,
|
|
|
24
24
|
import { ExecutionContextCreatorBackendService, } from '../../runtime/services/execution-context-creator.backend.service.js';
|
|
25
25
|
import { AuthorizationsBackendService } from '../../security/authorizations/services/authorizations.backend.service.js';
|
|
26
26
|
import { ResourcesRegistryBackendService } from '../../resources/services/resources-registry.backend.service.js';
|
|
27
|
-
import { WS_ResourceSubscribePayloadSchema, WS_ResourceEditingPayloadSchema, WS_ResourceCustomEventPayloadSchema, NotificationDisplayTarget, ResourceAction, buildResourceRoomName, buildScopeRoomName, buildUserRoomName, buildOrganizationRoomName, buildOrganizationRoleRoomName, buildAppRoleRoomName, parseOrganizationRoomName, WebSocketRoomPrefix, CoreWebSocketEvent, CoreWebSocketConnectionErrorCode, WebSocketResourceDeliveryTarget, WS_FlowsActorsStartPayloadSchema, WS_FlowsActorsApprovalResponsePayloadSchema, } from '@wildo-ai/saas-models';
|
|
27
|
+
import { WS_ResourceSubscribePayloadSchema, WS_ResourceEditingPayloadSchema, WS_ResourceCustomEventPayloadSchema, NotificationDisplayTarget, ResourceAction, buildResourceRoomName, buildScopeRoomName, ResourceScopeRoomKey, buildUserRoomName, buildOrganizationRoomName, buildOrganizationRoleRoomName, buildAppRoleRoomName, parseOrganizationRoomName, WebSocketRoomPrefix, CoreWebSocketEvent, CoreWebSocketConnectionErrorCode, WebSocketResourceDeliveryTarget, WS_FlowsActorsStartPayloadSchema, WS_FlowsActorsApprovalResponsePayloadSchema, } from '@wildo-ai/saas-models';
|
|
28
28
|
import { logWildoBackendErrorAtClassifiedSeverity, projectCaughtErrorLogFields } from '../../errors/utils/error-log-level-classification.backend.utils.js';
|
|
29
29
|
import { relaxAbuseThrottleMaxForEnvironment } from '../../errors/utils/rate-limit-environment-relaxation.backend.utils.js';
|
|
30
30
|
function subscriptionMatchesOrganization(subscription, organizationId) {
|
|
@@ -201,7 +201,7 @@ let WebSocketService = class WebSocketService {
|
|
|
201
201
|
await this.verifyResourceAccess(socket, auth, subscription.resourceType, subscription.resourceId, subscription.contextResourceIdentifiers);
|
|
202
202
|
}
|
|
203
203
|
else if (subscription.scopeKey && subscription.scopeId) {
|
|
204
|
-
await this.verifyScopeAccess(auth, subscription.scopeKey, subscription.scopeId, subscription.contextResourceIdentifiers);
|
|
204
|
+
await this.verifyScopeAccess(auth, subscription.resourceType, subscription.scopeKey, subscription.scopeId, subscription.contextResourceIdentifiers);
|
|
205
205
|
}
|
|
206
206
|
else {
|
|
207
207
|
throw new Error('Tracked resource subscription is missing routing identifiers');
|
|
@@ -1056,7 +1056,7 @@ let WebSocketService = class WebSocketService {
|
|
|
1056
1056
|
async getResourceEditingUsers(resourceType, resourceId) {
|
|
1057
1057
|
const prefix = this.redisService.getAppPrefix();
|
|
1058
1058
|
const key = `${prefix}editing:${resourceType}:${resourceId}`;
|
|
1059
|
-
const redisClient = this.redisService.
|
|
1059
|
+
const redisClient = this.redisService.getReadyClient();
|
|
1060
1060
|
if (!redisClient) {
|
|
1061
1061
|
return Array.from(this.resourceEditingState.get(key) || []);
|
|
1062
1062
|
}
|
|
@@ -1559,7 +1559,7 @@ let WebSocketService = class WebSocketService {
|
|
|
1559
1559
|
}
|
|
1560
1560
|
else if (scopeKey && scopeId) {
|
|
1561
1561
|
// For collection subscription, verify context access
|
|
1562
|
-
await this.verifyScopeAccess(auth, scopeKey, scopeId, contextResourceIdentifiers);
|
|
1562
|
+
await this.verifyScopeAccess(auth, resourceType, scopeKey, scopeId, contextResourceIdentifiers);
|
|
1563
1563
|
}
|
|
1564
1564
|
else {
|
|
1565
1565
|
throw this.errorBuilder.buildError(ErrorType.VALIDATION, undefined, {
|
|
@@ -2265,7 +2265,14 @@ let WebSocketService = class WebSocketService {
|
|
|
2265
2265
|
*
|
|
2266
2266
|
* REUSES: Role-based check for organization scope, or AuthorizationsBackendService for parent resources
|
|
2267
2267
|
*/
|
|
2268
|
-
async verifyScopeAccess(auth,
|
|
2268
|
+
async verifyScopeAccess(auth,
|
|
2269
|
+
/**
|
|
2270
|
+
* The resource whose collection is being subscribed to.
|
|
2271
|
+
*
|
|
2272
|
+
* Needed because APPLICATION scope authorizes THIS resource rather than the scope — see the
|
|
2273
|
+
* branch below. The other scopes do not read it, and that asymmetry is the point.
|
|
2274
|
+
*/
|
|
2275
|
+
resourceType, scopeKey, scopeId, contextResourceIdentifiers) {
|
|
2269
2276
|
if (auth.executionType === ExecutionContext_ExecutionType.PUBLIC_EXECUTION) {
|
|
2270
2277
|
throw this.errorBuilder.buildError(ErrorType.AUTHORIZATION, undefined, {
|
|
2271
2278
|
customMessageReference: ErrorCustomMessageReference.AUTHORIZATIONS_ACCESS_DENIED,
|
|
@@ -2285,6 +2292,48 @@ let WebSocketService = class WebSocketService {
|
|
|
2285
2292
|
}
|
|
2286
2293
|
return;
|
|
2287
2294
|
}
|
|
2295
|
+
/*
|
|
2296
|
+
* APPLICATION scope authorizes the SUBSCRIBED resource, never the scope (issue #247).
|
|
2297
|
+
*
|
|
2298
|
+
* `applicationId` is a single constant per backend, so an `app` room discriminates NOTHING —
|
|
2299
|
+
* every authenticated caller is "in" it. That is precisely why it cannot be used as an
|
|
2300
|
+
* authorization proxy the way the two scopes above are: `org` is checked against the caller's own
|
|
2301
|
+
* role entries for that tenant, and a parent scope authorizes a specific parent row. Both stand
|
|
2302
|
+
* for "may you see things under here"; a constant stands for nothing.
|
|
2303
|
+
*
|
|
2304
|
+
* The tempting fix is to let this fall through to the parent branch below, since
|
|
2305
|
+
* `PRIMARY_SCOPE_TO_CORE_RESOURCE_TYPE` already maps APPLICATION to the `applications` resource
|
|
2306
|
+
* and `mapScopeKeyToResourceType` only misses it because it searches for the field identifier
|
|
2307
|
+
* `appId` while the real one is `applicationId`. It looks like completing an oversight. It is a
|
|
2308
|
+
* disclosure: READ on the `applications` row is granted to ORG_MEMBER ("Org members can view
|
|
2309
|
+
* applications"), while TEN of the eleven APPLICATION-scoped resource configs require
|
|
2310
|
+
* APP_ADMIN_SUPER_ADMIN — SSO connections and config, SIEM export config and DLQ, SCIM
|
|
2311
|
+
* provisioning, webhook config, M2M delivery logs, flows/actors task and execution. Routing
|
|
2312
|
+
* through the parent would hand every org member notification streams naming rows of all of them.
|
|
2313
|
+
*
|
|
2314
|
+
* So the rule this branch enforces, and which the two above satisfy only incidentally: **a scope
|
|
2315
|
+
* room may never grant more than a READ of what it notifies about.** Here that is asked directly,
|
|
2316
|
+
* on ROLES alone, because a scope subscription addresses no row.
|
|
2317
|
+
*
|
|
2318
|
+
* The PUBLIC guard at the top of this method is defence in depth rather than the primary gate: a
|
|
2319
|
+
* public socket joins only the public room and returns before `setupResourceRoomHandlers` ever
|
|
2320
|
+
* registers, so it has no `resource:subscribe` listener at all. This method is still reachable for
|
|
2321
|
+
* a public principal through the tracked-subscription REPLAY on auth refresh, which is why the
|
|
2322
|
+
* guard stays.
|
|
2323
|
+
*/
|
|
2324
|
+
if (normalizedScopeKey === ResourceScopeRoomKey.APPLICATION) {
|
|
2325
|
+
const readOperation = this.resolveSocketAuthorizationDefaultOperation(resourceType, CoreResourceOperation.READ);
|
|
2326
|
+
if (!readOperation) {
|
|
2327
|
+
throw this.errorBuilder.buildError(ErrorType.NOT_FOUND, undefined, {
|
|
2328
|
+
customMessageReference: ErrorCustomMessageReference.RESOURCES_REGISTRY_OPERATION_PATH_NOT_FOUND,
|
|
2329
|
+
context: { resourceType, scopeKey, operation: CoreResourceOperation.READ },
|
|
2330
|
+
});
|
|
2331
|
+
}
|
|
2332
|
+
const applicationScopeContext = await this.executionContextCreator.buildContextFromSocketAuth(auth, readOperation, contextResourceIdentifiers);
|
|
2333
|
+
await this.authorizationsService.authorizeOperationRolesOnly(readOperation, applicationScopeContext);
|
|
2334
|
+
this.logDebug('Socket application-scope authorization succeeded', { resourceType, scopeId });
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2288
2337
|
// For other scopes (e.g., todolist), verify READ access to the parent resource
|
|
2289
2338
|
const parentResourceType = this.mapScopeKeyToResourceType(scopeKey);
|
|
2290
2339
|
if (!parentResourceType) {
|
|
@@ -2493,7 +2542,7 @@ let WebSocketService = class WebSocketService {
|
|
|
2493
2542
|
async trackEditingState(resourceType, resourceId, userId, action) {
|
|
2494
2543
|
const prefix = this.redisService.getAppPrefix();
|
|
2495
2544
|
const key = `${prefix}editing:${resourceType}:${resourceId}`;
|
|
2496
|
-
const redisClient = this.redisService.
|
|
2545
|
+
const redisClient = this.redisService.getReadyClient();
|
|
2497
2546
|
if (!redisClient) {
|
|
2498
2547
|
// Fallback to in-memory if Redis unavailable
|
|
2499
2548
|
if (action === 'start') {
|
|
@@ -2534,7 +2583,7 @@ let WebSocketService = class WebSocketService {
|
|
|
2534
2583
|
async cleanupEditingStatesForUser(userId) {
|
|
2535
2584
|
if (!userId)
|
|
2536
2585
|
return;
|
|
2537
|
-
const redisClient = this.redisService.
|
|
2586
|
+
const redisClient = this.redisService.getReadyClient();
|
|
2538
2587
|
// Cleanup in-memory state on this pod
|
|
2539
2588
|
for (const [key, users] of this.resourceEditingState.entries()) {
|
|
2540
2589
|
if (users.has(userId)) {
|