@@ -41,6 +41,13 @@ export class SeedLoaderService implements ISeedLoaderService {
4141 private engine : IDataEngine ;
4242 private metadata : IMetadataService ;
4343 private logger : Logger ;
44+ /**
45+ * Tenant org to stamp BUSINESS seed rows with when the caller pinned no
46+ * explicit `config.organizationId` (resolved per {@link resolveSoleOrganizationId}).
47+ * Set once per {@link load}; never applied to `sys_`/`cloud_`/`ai_` platform
48+ * seeds (those stay intentionally global/cross-tenant).
49+ */
50+ private fallbackOrgId ?: string ;
4451
4552 constructor ( engine : IDataEngine , metadata : IMetadataService , logger : Logger ) {
4653 this . engine = engine ;
@@ -58,6 +65,16 @@ export class SeedLoaderService implements ISeedLoaderService {
5865 const allErrors : ReferenceResolutionError [ ] = [ ] ;
5966 const allResults : SeedLoadResult [ ] = [ ] ;
6067
68+ // When the caller pinned no target org (an in-process publish has no active
69+ // user session — the AI build agent's publish path), BUSINESS seed rows
70+ // would land `organization_id = NULL` and then vanish under strict
71+ // org-scoping. If the tenant has exactly ONE organization, adopt it as a
72+ // fallback so business seeds carry the tenant key like a normal write.
73+ // Zero/many orgs → leave unset (genuinely ambiguous → keep the historical
74+ // global/cross-tenant behavior; the publisher must scope explicitly).
75+ this . fallbackOrgId =
76+ config . organizationId == null ? await this . resolveSoleOrganizationId ( ) : undefined ;
77+
6178 // 1. Filter datasets by environment
6279 const datasets = this . filterByEnv ( request . seeds , config . env ) ;
6380
@@ -265,13 +282,17 @@ export class SeedLoaderService implements ISeedLoaderService {
265282 }
266283 const record = { ...( seedResult . value as Record < string , unknown > ) } ;
267284
268- // Per-tenant tagging: when a target org is set, stamp every
269- // seeded row with it (unless the record itself already supplies
270- // an explicit organization_id — respect dataset author overrides).
271- // Skipped objects that don't declare `organization_id` will have
272- // the extra key silently ignored by the engine.
273- if ( config . organizationId && record [ 'organization_id' ] == null ) {
274- record [ 'organization_id' ] = config . organizationId ;
285+ // Per-tenant tagging: stamp every seeded row with the target org — the
286+ // caller's explicit `config.organizationId`, or (when none was pinned) the
287+ // single-org fallback for BUSINESS objects only. A `sys_`/`cloud_`/`ai_`
288+ // platform seed never takes the fallback: those stay global/cross-tenant.
289+ // A record that supplies its own `organization_id` always wins; objects
290+ // without the column ignore the extra key at the engine.
291+ const tenantOrg =
292+ config . organizationId ??
293+ ( / ^ ( s y s _ | c l o u d _ | a i _ ) / . test ( objectName ) ? undefined : this . fallbackOrgId ) ;
294+ if ( tenantOrg && record [ 'organization_id' ] == null ) {
295+ record [ 'organization_id' ] = tenantOrg ;
275296 }
276297
277298 // Resolve references
@@ -436,6 +457,32 @@ export class SeedLoaderService implements ISeedLoaderService {
436457 // Internal: Reference Resolution
437458 // ==========================================================================
438459
460+ /**
461+ * Best-effort resolve the tenant's SOLE organization id — used to stamp
462+ * business seed rows when the caller pinned no `config.organizationId` (an
463+ * in-process publish has no active user session). A fresh env has exactly one
464+ * org, so its seeds should carry it like a normal write instead of landing
465+ * org-less (→ invisible under strict org-scoping). Returns undefined when
466+ * there are zero or several orgs (genuinely ambiguous — keep the historical
467+ * global/cross-tenant NULL) or when `sys_organization` is absent.
468+ */
469+ private async resolveSoleOrganizationId ( ) : Promise < string | undefined > {
470+ try {
471+ const rows = await this . engine . find ( 'sys_organization' , {
472+ fields : [ 'id' ] ,
473+ limit : 2 ,
474+ context : { isSystem : true } ,
475+ } as any ) ;
476+ if ( Array . isArray ( rows ) && rows . length === 1 ) {
477+ const id = ( rows [ 0 ] as { id ?: unknown ; _id ?: unknown } ) ?. id ?? ( rows [ 0 ] as { _id ?: unknown } ) ?. _id ;
478+ return id ? String ( id ) : undefined ;
479+ }
480+ } catch {
481+ // sys_organization may not exist (single-tenant runtime) — ignore.
482+ }
483+ return undefined ;
484+ }
485+
439486 private async resolveFromDatabase (
440487 targetObject : string ,
441488 targetField : string ,
0 commit comments