factory/kernel (0.10.1)

Published 2026-08-16 13:43:38 +00:00 by bmiadmin

Installation

{
	"repositories": [{
			"type": "composer",
			"url": ""
		}
	]
}
composer require factory/kernel:0.10.1

About this package

SaaS Factory kernel: product registry, tenant resolution, actor context, Money, references, FSM engine, fail-closed audit chokepoint.

factory/kernel — v0.2.1

The shared kernel every product consumes as a versioned package (never copied).

Supports Laravel 10 · 11 · 12 on PHP 8.2+. A library must not pin the fleet to one framework major, so the constraint is wide and both ends are proven by the suite (see Tests below).

Provides

  • Product registry — Products\ProductRegistry, hydrated from the host app's config/products.php (registry map + default slug). One entry per product: name, currency, shape, ordered FSM statuses, policy knobs (config).
  • Tenant resolution — factory.product middleware (Http\Middleware\ResolveProduct): reads X-Product-Slug, falls back to the configured default, refuses unknown slugs (400). Binds Products\CurrentProduct for scopes/controllers.
  • Tenant scoping — Tenancy\BelongsToProduct trait: global scope on product_slug, fail-closed (no product bound over HTTP → empty result set), auto-stamps inserts.
  • Money — Support\Money, int minor units + currency, no floats, cross-currency arithmetic refused. CURRENCIES maps each code to its minor-unit exponent, so format() → KES 2,800 and XAF 5,000 (the CFA francs and UGX have no sub-unit; a ÷100 there would print a hundredth of the real amount). fromMajor() is the currency-aware inverse — never a hardcoded × 100 at a call site. Two drift guards in the suite pin this table to factory_core's Dart _currencyDecimals and assert every currency factory/jurisdiction can route is expressible here.
  • References — Support\Reference::generate('BK') → BK-<base36 ms><2 digits>, same format the factory_core client mints offline.
  • FSM engine — Fsm\StateMachine (ordered statuses, forward-only guarded transitions, cancelled escape hatch) + Fsm\HasFsm model trait (per-transition timestamps in status_timestamps, audited, transactional).
  • Audit chokepoint — Audit\AuditLog::record() is the single write path for the hash-chained audit_entries table. Fail-closed: an audit failure throws AuditWriteFailedException; transitions run action + audit in one transaction, so no audit ⇒ no action. Each product_slug owns an independent chain (events with no current product hang on __platform__); a row's hash covers an immutable skeleton that commits to body_digest, so a GDPR erasure can null the body and the chain still verifies. verifyChain() re-walks a chain and names the row that broke it. Concurrency is fork-proofed twice: the tail is read under a row lock, and UNIQUE(product_slug, prev_hash) makes a second child of the same parent impossible.
  • Double-spend guard — Idempotency\IdempotencyGuard::claim(): one bare UNIQUE-index INSERT (never select-then-insert), scoped by actor + scope + token, with a stale-reclaim window. Returns false only on a genuine collision — an infrastructure error propagates rather than reading as "already claimed", because a swallowed error would silently drop a real money movement.

Tests

composer update && vendor/bin/phpunit          # highest supported (Laravel 12)
composer update --prefer-lowest && vendor/bin/phpunit   # lowest supported (Laravel 10)

61 tests over Testbench on in-memory SQLite. They pin invariants that must fail loudly, not coverage: raw-SQL tamper detection (bypassing the model layer, as a compromised DB user would), mid-chain deletion, per-product chain isolation, secret scrubbing verified against the raw column, digest stability under key reordering, redaction that survives verification, and a fail-closed write with the table dropped.

The body column is body, not changes. Eloquent\Model declares its own protected $changes (the dirty tracker), so inside the model $this->changes = null writes the tracker and leaves the stored PII in place — a silent GDPR failure that the ORM reads back as success. See docs/dev/AI_CODE_PITFALLS.md #30.

Config expected from the host app

// config/products.php
return [
    'default' => env('PRODUCT_DEFAULT', 'makeover'),
    'registry' => [
        'makeover' => [
            'name' => 'Makeover', 'currency' => 'KES', 'shape' => 'booking',
            'statuses' => ['Requested', ...],
            'config' => ['fee_minor' => 9900, 'tax_bps' => 1600],
        ],
        // ...
    ],
];

Endpoints

None — the kernel is infrastructure; shapes own routes.

Dependencies

Dependencies

ID Version
illuminate/database ^10.0 || ^11.0 || ^12.0 || ^13.0
illuminate/http ^10.0 || ^11.0 || ^12.0 || ^13.0
illuminate/support ^10.0 || ^11.0 || ^12.0 || ^13.0
php ^8.2

Development dependencies

ID Version
orchestra/testbench ^8.0 || ^9.0 || ^10.0 || ^11.0
phpunit/phpunit ^10.1 || ^11.0 || ^12.0
Details
Composer
2026-08-16 13:43:38 +00:00
32
proprietary
133 KiB
Assets (1)
Versions (2) View all
0.10.1 2026-08-16
0.9.1 2026-08-06