factory/kernel (0.9.1)
Installation
{
"repositories": [{
"type": "composer",
"url": " "
}
]
}composer require factory/kernel:0.9.1About this package
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'sconfig/products.php(registrymap +defaultslug). One entry per product: name, currency, shape, ordered FSM statuses, policy knobs (config). - Tenant resolution —
factory.productmiddleware (Http\Middleware\ResolveProduct): readsX-Product-Slug, falls back to the configured default, refuses unknown slugs (400). BindsProducts\CurrentProductfor scopes/controllers. - Tenant scoping —
Tenancy\BelongsToProducttrait: global scope onproduct_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.CURRENCIESmaps each code to its minor-unit exponent, soformat()→KES 2,800andXAF 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× 100at a call site. Two drift guards in the suite pin this table to factory_core's Dart_currencyDecimalsand assert every currencyfactory/jurisdictioncan 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,cancelledescape hatch) +Fsm\HasFsmmodel trait (per-transition timestamps instatus_timestamps, audited, transactional). - Audit chokepoint —
Audit\AuditLog::record()is the single write path for the hash-chainedaudit_entriestable. Fail-closed: an audit failure throwsAuditWriteFailedException; transitions run action + audit in one transaction, so no audit ⇒ no action. Eachproduct_slugowns an independent chain (events with no current product hang on__platform__); a row'shashcovers an immutable skeleton that commits tobody_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, andUNIQUE(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. Returnsfalseonly 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 |