1. Introduction
This specification is the active working table for Pioneer rules. The numbered rules below are normative unless marked otherwise. Examples and cleanup items are informative.
2. Rules
| Id | Rule |
|---|---|
| R1 | Factory Functions, Not Classes: No class, this, or user-authored new in Illuminate or application code. Factory functions or constants only. Components expose behavior through factory functions that return plain objects with state closed over by closures. Compiler-owned runtime wrappers, such as Durable Object classes, are the narrow exception. Compiler sweeps may mechanically transform legacy classes during porting, and any lost public inference must be restored through R34.
|
| R2 | One Export Default Per Module: One method or runtime leaf per file. Folders replace classes, and mod.ts re-exports the folder surface. Laravel PSR-4 parity is represented by import maps, not classmaps or Composer autoload metadata.
|
| R3 | Zero Third-Party Runtime Packages: Illuminate runtime source depends only on Deno globals, Deno KV, Deno Temporal, Web Standards, V8 built-ins, and other Illuminate components. Distribution may use supported package specifiers, but provider runtime code must remain statically analyzable and rule-compliant. |
| R4 | Null Out, Both In: Public APIs return T | null, never undefined. Inputs may accept T | null | undefined and normalize absence to null. Defaults use ??; optional three-state input is represented as ?: T | null.
|
| R5 | All DI Through Kernel-Scoped Destructuring From App: All DI flows through kernel-scoped destructuring. Handlers receive a destructured view of the contextually typed app shape, such as ({ request, params, Str, View, session }) => {} for HTTP. Developers do not access app directly in consumed-by-DI modules; the compiler treats handler destructuring as destructuring from app internally and rejects bindings unavailable in the active kernel. Kernel facades establish context through R15, and app remains a compile-time abstraction through R21.
|
| R6 | @provider/* Is The Only Import Map Convention: Pioneer recognizes only @provider/* and development-only @provider-dev/* import-map entries as provider discovery roots. The import map is the authoritative provider registry: no glob scan, filesystem fallback, providers array, decorators, runtime registration, package manifest schema, register, or boot. Every provider root resolves to an R18 phase-2 provider file. All other import-map entries are ordinary aliases with no architectural meaning. Provider installation, publishing, dependency ordering, provider-key collision checks, and warnings are compiler-managed from the module graph and installation metadata. Provider-key collisions are fatal because they create ambiguous provider identity; alias collisions are handled by the alias policy, not by treating them as provider identity.
|
| R7 | Manager Delegates To Driver Via Config: Drivers are provided at full nominal module paths, and the public alias points to the active driver selected by config. Driver-swap methods inherit Laravel naming, such as connection, store, disk, guard, mailer, or channel, and return new spread objects without mutating the existing manager. Supported access styles are default alias, fluent switch, and direct binding.
|
| R8 | Facades Are Constant Wrappers: Facades are constant wrappers over app bindings and never use Proxy. A facade resolves a shorthand key to the active driver and may expose testing helpers only: swap(), spy(), fake(), isFake(), and restore(). Layering is Facade > Manager > Default Driver.
|
| R9 | Zero Annotations At Public API Surface And No Unsafe Casts: Runtime source files carry no public API type annotations and no unsafe as T casts. as const and as const satisfies T are allowed. Public contracts come from component contracts.d.ts files through declaration merging, declare module wiring, satisfies, const type parameters, and template-literal parsing. Unsafe casts are tracked as unsafe boundaries and must converge to zero.
|
| R10 | No Proxy Anywhere: JavaScript Proxy is forbidden. Static alternatives include predeclared methods, getter maps, Object.defineProperty, and accessor factories. Dev tooling may use V8-native named property handling where the runtime forces it, but production source remains statically knowable.
|
| R11 | Illuminate Extends Web Standards: Illuminate Request extends globalThis.Request, and Illuminate Response extends globalThis.Response. ConsoleRequest extends Request by mapping argv into URL/body semantics. Web Standards are the base contract; Pioneer adds ergonomics without replacing them.
|
| R12 | Function Scope Properties: A component folder is the namespace. Each file exports one function or constant representing a static leaf, while create factories return plain objects whose instance methods close over shared state. The component contracts.d.ts declares the full namespace type, and the compiler emits only accessed runtime leaves at each destructuring site.
|
| R13 | Laravel Folder Conventions Preserved: Laravel folder conventions such as database/migrations/, config/, routes/, and domain folders are preserved. Import maps provide the autoload mapping. There is no classmap and no Composer dump-autoload equivalent.
|
| R14 | Harvest Parity Tests: PHP Laravel parity tests run alongside TypeScript tests to verify that each ported method preserves Laravel behavior. Parity gaps are tracked explicitly rather than hidden behind type conformance. |
| R15 | Kernel Facades As Self-Selecting Entry Points: A file becomes a phase-3 entry point by importing one or more kernel facades from @find-how/pioneer and making top-level facade calls. The exhaustive facade set is Route, Artisan, Queue, Schedule, Broadcast, and Event. Middleware is HTTP route metadata and module-backed behavior, not a kernel facade. Entry-point files have no exports. The compiler statically extracts and unrolls route groups, mounts, match/any/resource expansions, schedules, queues, broadcasts, events, route middleware metadata, and commands into kernel-specific TypeGraph records. Registration arguments that name module-backed entities use canonical bare nominal paths or registered aliases per R17/R26/R28/R32. Every Pioneer-participating file must classify as exactly one provider, kernel entry point, consumed-by-DI module, or pure internal utility.
|
| R16 | Types Only In Component contracts.d.ts: All public type contracts live in component-owned contracts.d.ts files. Runtime source files contain no public type annotations, import type, or export type. Contracts files own namespace shape, runtime leaf wiring, registry augmentation, error contracts, driver contracts, taxonomy contracts, and contextual binding contracts.
|
| R17 | Canonical Identity Is Compiler-Owned: The resolved bare ESM module specifier is nominal identity for module-backed entities. Any registerable entity that has a module path uses that bare nominal path as its canonical identity, including events, listeners, middleware, queue jobs, policies, observers, global scopes, model features, query macros, casts, scopes, model bindings, and handler targets. Database tables, columns, indexes, and migration-derived data resources are canonical compiler resources but not container bindings by default; their identities use db:<connection>:<table>, db:<connection>:<table>.<column>, and db:<connection>:<table>#<index>. Arbitrary strings such as UserRegistered, rateLimit, user:create, or users are never canonical identity unless resolved to a canonical module path or generated data resource. String-literal module and data-resource references in registration and resolution APIs resolve through the same compiler graph as static imports and migration-derived resources and must satisfy the required contract at compile time.
|
| R18 | Service Providers As Compile-Time Declarations: A provider is a phase-2 entry point imported through @provider/* or @provider-dev/*. It imports app from .pioneer/autoload.ts, imports no kernel facade, has no exports, and contains only unconditional top-level provider declarations. The primary declaration form is app.provide(canonicalPath, target, options?); provider files may also declare aliases, tags, migrations, listeners, observers, policies, middleware aliases via app.middleware.alias, middleware groups via app.middleware.group, context augmentations via app.context.extend, config merges, translations, views, components, publish mappings, contextual bindings, and service extensions when those forms are on the explicit provider whitelist. Model features, query macros, casts, scopes, model method extensions, and model bindings are ordinary module-backed bindings registered through app.provide, not a separate trait or macro registry. Model files consume those features through static literal references such as Model.use([...]), which R26 resolves through provider bindings. Provider registration arguments that name module-backed entities use canonical bare nominal paths or registered aliases per R17/R26/R28/R32. Route middleware usage belongs to R15 kernel entry points. Routes, queues, schedules, broadcasts, and CLI commands also belong to R15 kernel entry points.
|
| R19 | Compiler Phase 1: Autoloader: Phase 1 runs the autoloader pipeline. deno_config loads Deno, package, workspace, import-map, and lockfile data for the compiler. That Deno-compatible configuration feeds deno_graph, which is the canonical resolver and module graph builder using Deno semantics. Source discovery uses import-map roots plus deno_graph reachability when an import map exists, and tsconfig/filesystem discovery when it does not. oxc-parser extracts exports, imports, and source-level metadata from modules after discovery. The CLI emits .pioneer/graph/autoload.json, .pioneer/graph/typegraph.json, and, when requested, .pioneer/graph/autoload.ts and .pioneer/graph/autoload.d.ts.
|
| R20 | Compiler Phase 2: Provider, Data, And Model Wiring: Phase 2 consumes the phase-1 autoload map and all R18 provider records. It builds the binding registry, alias map, tag map, migration registry, listener registry, observer registry, policy registry, config merge map, publish manifest, contextual bindings, and service-extension composition. Phase 2 also builds the data graph and model graph from database config merges, registered migration directories, migration SQL or introspection output, database manager and driver bindings, model bindings, model feature bindings, Model.table(...) declarations, and Model.use([...]) references. It emits .pioneer/boot.ts, database manifests, model manifests, generated database contracts, generated model contracts, and TypeGraph edges connecting models, tables, migrations, features, routes, queue jobs, cache keys, responses, and deployed resources, with no provider boot phase and no runtime provider discovery.
|
| R21 | App Is A Compile-Time Abstraction: app is a developer-facing DI specification, not necessarily a runtime object. The compiler may inline factory calls directly at destructuring sites and emit direct function calls in topological order, eliminating the container from runtime output.
|
| R22 | Trust Boundaries Require Validation Gates: Every external data entry point must pass through a validation function that accepts unknown and either returns typed data or throws an operational error. Trust boundaries include HTTP request bodies, database results, ORM/model hydration, third-party API responses, environment variables, queue payloads, and file reads. Database query results, migration-introspected rows, and model hydration pass through generated validation gates before entering typed application flow. Model create/update calls that receive external input validate against generated create/update contracts before insert or update, and returned rows validate before becoming model row types. No any intermediary or unsafe cast may stand in for validation.
|
| R23 | Contextual Bindings At Compile Time: Each kernel entry point receives a statically known binding snapshot based on what the handler destructures. Middleware may augment or refine the snapshot, and contextual provider bindings may override globals for specific consumers. The compiler emits only the final required bindings, services, and modules for that entry point. |
| R24 | Native Narrowing Only: No function may declare a value is T or asserts value is T return type. Use native TypeScript narrowing operators directly at the use site: typeof, in, instanceof, Array.isArray, and nullish checks. Array narrowing uses flatMap with an inline conditional rather than filter with a predicate function.
|
| R25 | Three-Tier Error Contract: Public APIs have exactly three outcome categories: expected absence returns T | null; operational failure throws an Error instance created by a component-owned error factory function declared in contracts.d.ts; developer error throws plain Error immediately and is not recovered in production code. User-authored error classes are forbidden by R1 unless compiler/runtime-owned. A function must not conflate absence, operational failure, and invariant failure on the same execution path.
|
| R26 | Binding Resolution Is Template-Literal Typed: Every string key that references a binding, module path, registerable module-backed entity, model feature binding, query macro binding, model binding, or generated data resource is typed as a template-literal union derived from provider registrations, the module graph, and the compiler-derived data graph. A string argument is either a bare nominal path, a generated data resource identity such as db:<connection>:<table>, or an alias lookup. Non-path strings compile only if a provider-declared alias maps them to a canonical bare nominal path or generated data resource in the relevant alias namespace; otherwise they are compile-time errors. Table-name sugar such as DB.table("users") or Model.table("users") compiles only when it resolves to exactly one active data resource. Model.use([...]) feature references resolve through the provider binding registry. The compiled output always resolves to direct imports or generated data-resource metadata with no runtime lookup. Middleware context augmentations declared in contracts.d.ts merge into downstream handler binding types.
|
| R27 | Proof Certificates Are Persistent And Content-Addressed: When a component passes rule checks, the compiler records a proof certificate binding module path, content hash, compiler tool versions, and passed rule set. Any source or relevant compiler-tool change that alters the hash invalidates the certificate. The TypeGraph exposes stale certificates for MCP queries and regression tracking. |
| R28 | Bare Nominal Paths: All container bindings, job keys, middleware keys, module specifiers, and string-based resolutions use bare nominal paths without .ts, .js, or .mjs extensions. The compiler normalizes extensions internally; the user-facing contract is extensionless.
|
| R29 | Module Graph Is Single Source Of Truth: The ESM module graph built from Deno manifests, import maps, and resolved import specifiers is the canonical authority for module-backed identity, container bindings, aliases, member access, and module identity. Data resource identity is derived from database config, registered migration directories, and migration SQL or introspection, all rooted in module graph and provider records. No independent runtime schema registry may redefine data identity, and no separate sub-binding registry duplicates graph facts. |
| R30 | Static-Only Container Access: Container access in handlers, jobs, middleware, commands, listeners, and services must be static. Dynamic member access such as computed property reads or app.make(variable) is forbidden in production and is at least a warning, or an error in strict mode.
|
| R31 | Dual Access Style Support With Full Tree-Shaking: Leaf style and namespace style are both supported and must tree-shake identically. Leaf style destructures leaves directly, such as const { when } = app. Namespace style destructures an aggregate, such as const { Conditionable } = app, and member accesses like Conditionable.when() resolve to exact nominal leaf paths through the module graph.
|
| R32 | Service Providers Register Aggregate Paths With Aliases: Service providers declare aggregate component paths and runtime leaves at bare nominal paths, using app.provide as the canonical provider declaration surface. Providers then add aliases for ergonomic destructuring with app.alias(canonicalPath, aliasName), such as Conditionable, conditionable, or User. Aliases are ergonomic secondary names only: they never create or replace nominal identity, and only map a short name back to one canonical bare nominal path or generated data resource in the relevant alias namespace. Model aliases such as User map to canonical model module paths such as @app/models/user; table names such as users are ergonomic metadata that resolve to generated data resource identities such as db:edge:users. The provider remains the source of truth for aggregate-to-leaf wiring, and aliases never replace model, feature, table, column, or resource identity.
|
| R33 | Durable Object Drivers For Edge Processes: Artisan, Broadcast, Queue workers, and long-lived components must be deployable as Cloudflare Durable Objects at the edge. The compiler may emit Durable Object wrapper classes around kernel facades while preserving local Deno process behavior for development. Container access remains static and tree-shaken. |
| R34 | Transition-Safe Factory Conversion: When an exported class-backed implementation is converted to a factory or closure-based leaf, any lost public type inference must be restored in the owning component contracts.d.ts in the same change. A component may not enter an intermediate state where source annotations are removed, class inference is gone, and declaration wiring is absent. The compiler must verify this through inference-gap and component audits before certification.
|
| R35 | Data And Model Resources Are Compiler-Derived Nominal Resources: Database tables, columns, indexes, and model-to-table bindings are compiler-generated resources with stable nominal identities. Table identities use db:<connection>:<table>, column identities use db:<connection>:<table>.<column>, index identities use db:<connection>:<table>#<index>, and model identities use their bare module specifier such as @app/models/user. Database config and registered migrations define the database resource graph. Models bind to that graph through Model.table("tableName") or an explicit connection-qualified form. Model features, casts, scopes, and query macros are ordinary module-backed bindings registered by providers and consumed through static literal references such as Model.use([...]). The compiler validates model declarations against migration-derived table types and feature requirements, then emits database contracts, model contracts, validation gates, TypeGraph records, MCP resources, and per-entrypoint snapshots.
|
3. Canonical Identity Examples
Correct canonical path usage:
app. events. listen( "@app/events/UserRegistered" , "@app/listeners/SendWelcomeEmail" ); Queue. job( "@app/queues/ProcessOrder" , "@app/queues/handlers/ProcessOrderHandler" ); Route. get( "/orders" , "@app/http/handlers/ListOrders" ). middleware( "@app/http/middleware/RateLimit" );
Correct alias usage:
app. alias( "@app/events/UserRegistered" , "UserRegistered" ); app. middleware. alias( "@app/http/middleware/RateLimit" , "rateLimit" ); app. events. listen( "UserRegistered" , "@app/listeners/NotifyAdmins" ); Route. get( "/orders" , "@app/http/handlers/ListOrders" ). middleware( "rateLimit" );
Compile error without a registered alias:
app. events. listen( "UserRegistered" , "@app/listeners/SendWelcomeEmail" );
External trigger names, such as route paths, queue broker names, cron expressions, and Artisan signatures, are transport or user-facing metadata rather than canonical module identity.
Data and model identity usage:
app. config. merge( "database" , "@app/config/database" ); app. migrations( "@app/database/migrations" ); app. provide( "@app/models/user" , "@app/models/user" ); app. alias( "@app/models/user" , "User" ); app. provide( "@find-how/auth/model-features/authenticatable" , "@find-how/auth/model-features/authenticatable" , ); export default Model. use([ "@find-how/auth/model-features/authenticatable" ]) . table( "users" , { fillable: [ "name" , "email" ], hidden: [ "passwordHash" ], });
The compiler resolves @app/models/user to Model.table("users"),
the active database connection, db:edge:users, the migration-derived table
type, and all feature requirements. User and users remain
ergonomic names, not canonical identity.
4. Open Cleanup Items
| Item | Notes |
|---|---|
| R1/R27/R34 references | The pasted source still mentions old replication-pack language and old R38 references. This table keeps the active R34 conversion rule and removes replication-pack machinery from active rules. |
| Provider whitelist | R18 should maintain one explicit whitelist centered on app.provide and including alias, contextual binding, service extension, and publish/config/resource registration forms only if they are part of the provider surface.
|
| Third-party packages | R3 and R6 should stay explicit about the difference between package distribution specifiers and runtime dependencies. |
| Certification scope | R27 is retained as compiler metadata only. Replication packs, adversarial verification, spec-to-artifact, and kill switches are not active rules in this table. |
| Reserved rule numbers | R35 is now active for compiler-derived data/model resource identity. R36+ remain reserved or deferred. |