PolicyFlow

Modules and Imports

Managing dependencies and organizing your PolicyFlow code

PolicyFlow uses a powerful module system inspired by modern development environments like Node.js to manage dependencies between files. This allows for a clean, scalable project structure. The unique identity of a policy or schema is determined by its file path, not a long, fully qualified name.

Import Syntax

The import statement is used at the beginning of a .pf or .pftest file to bring schemas, types, or other policies into scope.

Path Structure

The import path is structured and flexible:

  • @/ is an alias for the project's root directory
  • ./ refers to the current directory
  • ../ refers to the parent directory
  • An optional :schemaName or :policyName can be appended to a filename to specifically target one schema/policy within a file that may contain multiple definitions
  • File extensions (.pf, .pfs, .pftest) are required to disambiguate files

Import Patterns

Importing All Types from a Specific Schema

This is the most common pattern. It imports all types from a single, specified schema within a file and makes them available under an alias.

Syntax: import * as <Alias> from "<PathTo/Filename.ext>:<SchemaName>";

// Import all types from the `AuthSchema` inside the `auth.pfs` file.
import * as Auth from "@/schemas/auth.pfs:AuthSchema";

// Now you can use types like `Auth.CorporateUser`
policy UserAccess {
    schemas {
        User from Auth.CorporateUser
    }
    // ...
}

Importing Specific Named Types

If you only need a few types, you can import them directly by name. This can improve readability for simple policies.

Syntax: import { <Type1>, <Type2> } from "<PathTo/Filename.ext>:<SchemaName>";

import { CorporateUser, ServiceAccount } from "@/schemas/auth.pfs:AuthSchema";

// You can now use `CorporateUser` directly without an alias.
policy AccessControl {
    schemas {
        User from CorporateUser
    }
    // ...
}

Importing from a File with a Single Schema

If a .pfs file contains only one schema definition, you can omit the :SchemaName specifier for brevity.

Syntax: import * as <Alias> from "<PathTo/Filename.ext>";

// Assumes `document.pfs` contains only one schema.
import * as Doc from "@/schemas/document.pfs";

Importing Policies

The same syntax applies to importing policies for testing:

// Import a policy for testing - can omit :PolicyName if file has one policy
import * as DocPolicy from "@/policies/document-access.pf";

// Or explicitly specify the policy name
import * as DocPolicy from "@/policies/document-access.pf:DocumentAccessPolicy";

test DocumentPolicyTests for DocPolicy {
    // test cases...
}

Module Organization Best Practices

1. Schema Organization

Group related types together in logical schema files:

schemas/auth.pfs
schema AuthSchema {
    // User-related types
    User type CorporateUser {
        id: UUID
        email: Email
        department: String
        isActive: Boolean = true
    }

    User type ServiceAccount {
        id: UUID
        name: String
        scopes: String[]
        active: Boolean = true
    }

    // Authentication context
    Context type AuthContext {
        sessionId: String
        authMethod: String
        timestamp: DateTime = DateTime.Now()
    }
}
schemas/resources.pfs
schema ResourceSchema {
    Resource type Document {
        id: UUID
        title: String
        ownerId: UUID
        createdAt: DateTime = DateTime.Now()
    }

    Resource type Database {
        id: UUID
        name: String
        environment: String
        isPublic: Boolean = false
    }
}

2. Policy Organization

Organize policies by domain or feature:

policies/
├── core/
│   ├── authentication.pf
│   └── rate-limiting.pf
├── resources/
│   ├── document-access.pf
│   └── database-access.pf
└── compliance/
    ├── gdpr.pf
    └── sox.pf

3. Shared Base Types

Create base schemas that other schemas can inherit from:

schemas/base.pfs
/**
 * Defines foundational types used across the entire system.
 */
schema BaseSchema {
    // A regular 'type' that serves as a base for other types.
    type Entity {
        id: UUID
        createdAt: DateTime = DateTime.Now()
        updatedAt: DateTime = DateTime.Now()
        isActive: Boolean = true
    }
}
schemas/auth.pfs
// This schema imports 'base' to use its types.
import * as Base from "@/schemas/base.pfs";

schema AuthSchema {
    /**
     * CorporateUser inherits all properties from Base.Entity
     * and adds its own. It is designated as a 'User' type.
     */
    User type CorporateUser : Base.Entity {
        email: Email
        department: String
        roles: String[]
        emailVerified: Boolean = false
    }
}

Import Resolution

File Path Examples

// Absolute imports (from project root)
import * as Auth from "@/schemas/auth.pfs:AuthSchema";
import * as Core from "@/policies/core/authentication.pf";

// Relative imports
import * as Sibling from "./sibling-schema.pfs:SchemaName";
import * as Parent from "../parent-schema.pfs:SchemaName";

// Import from subdirectory
import * as Sub from "./subdirectory/schema.pfs:SchemaName";

// Always use file extensions
import * as Auth from "@/schemas/auth.pfs:AuthSchema";
import * as Policy from "@/policies/document.pf";

Import Precedence

When multiple imports might conflict, the last import takes precedence:

import { User } from "@/schemas/old-auth.pfs:OldAuth";
import { User } from "@/schemas/new-auth.pfs:NewAuth"; // This User type wins

// User now refers to the type from new-auth

To avoid confusion, use aliases:

import * as OldAuth from "@/schemas/old-auth.pfs:OldAuth";
import * as NewAuth from "@/schemas/new-auth.pfs:NewAuth";

// Now you can be explicit: OldAuth.User vs NewAuth.User

Common Patterns

The Standard Import Pattern

Most PolicyFlow files follow this pattern:

// 1. Import schemas
import * as Auth from "@/schemas/auth.pfs:AuthSchema";
import * as Resources from "@/schemas/resources.pfs:ResourceSchema";
import * as Context from "@/schemas/context.pfs:ContextSchema";

// 2. Import shared utilities or constants (if needed)
import * as Constants from "@/lib/constants.pfs";

// 3. Define your policy
policy MyPolicy {
    schemas {
        User from Auth.CorporateUser
        Resource from Resources.Document
        Context from Context.WebContext
    }

    // ... rules ...
}

Cross-Domain Imports

When policies need to reference multiple domains:

// A compliance policy that needs to understand multiple resource types
import * as Documents from "@/schemas/documents.pfs:DocumentSchema";
import * as Databases from "@/schemas/databases.pfs:DatabaseSchema";
import * as Auth from "@/schemas/auth.pfs:AuthSchema";

policy GDPRCompliance {
    // This policy can now handle multiple resource types
    schemas {
        User from Auth.CorporateUser where user.region == "EU"
        Resource from Documents.PersonalDocument | Databases.CustomerDB
    }

    // ... GDPR-specific rules ...
}

Next Steps