DACore · coming December 2026
How to write a DACore module
After DACore is installed, a programmer should spend time on the product, not on login. The module still has rules. It must register permissions, menu items, and optional AI tools; it must be installable and uninstallable; it must render inside the DACore shell. TestInstalatora is the canonical sample: tables, seed data, rights, nested menu, an admin page, and a symmetric uninstaller — without inventing a second auth stack. Scaffold the PHP classes with DotApper. Then add the DACore contract on top.
Common mistakes
| Wrong | Right |
|---|---|
| Hand-write the module class. | php dotapper.php --create-module=Shop then add dainstall.php and init/. |
| Build a custom admin HTML layout. | DACore:Page@withMenu! and fragments in views/layouts/. |
Register menu and rights in initialize() on every boot. |
Do it in Installation.php behind Installations@exist!. |
Uninstall that forgets dacore_menu or AI tools. |
Delete your menuid prefix, Rights@deleteGroup!, unregister tools, DROP shop_*. |
Empty rights on an AI tool. |
The tool is invisible to everyone. Wildcards do not save you here. |
Package anatomy
Shop/
dainstall.php
Installation.php
init/module.init.php
init/module.listeners.php
module.init.php (stub until init/ is copied)
module.listeners.php
Controllers/
Middleware/Rights.php
views/layouts/
translations/
assets/
Table names: shop_*. Menu ids: Shop.*. Permission creator: Shop.
Never edit app/parts/ or app/modules/DACore/. Overrides for this environment go in app/config.php.
Framework-only rules still apply: in templates, DB::module('RAW'), callable strings with trailing ! on hot paths.
See how to create a module for DotApper and fallbacks, then return here for the DACore layer.
Installer callback
Each semver key runs once. Create tables, seed, create the rights group, create rights, optionally assign the installing user, register menu rows, optionally register AI tools, then insert the installation row.
'1.0.0' => function () {
$version = '1.0.0';
if (DotApp::call('DACore:Installations@exist!', 'Shop', $version) === true) {
return;
}
// CREATE TABLE shop_* …
$groupId = DotApp::call('DACore:Rights@createGroup!', 'Shop', 'Shop');
DotApp::call('DACore:Rights@createRight!', $groupId, 'View orders', '', 'Shop', 'orders.view', 'Shop');
DotApp::call('DACore:Menu@register', 'Shop.orders.list', [ /* … */ ]);
DotApp::call(
'DACore:Installations@insert!',
'Shop',
$version,
1,
['outcome' => 'ok']
);
}
Uninstaller callback
Reverse the same version. Order matters: drop dependents first if you have foreign keys inside your own schema.
DotApp::call('DACore:Rights@deleteGroup!', 'Shop');
// DELETE FROM dacore_menu WHERE menuid LIKE 'Shop.%'
// DACore:AITools@delete for each tool id, or delete by creator
// DROP TABLE IF EXISTS shop_orders, shop_items, …
DACore then deletes installation rows for that module name and removes the folder. If you skip menu cleanup, the next operator sees dead links. If you skip rights cleanup, the catalog lies.
Routes under the admin prefix
In init/module.init.php read Config::module('DACore', 'prefixUrl') and your own pagePath fallback.
Load translations. Register GET/POST only when Auth::isLogged() === true, with your Rights middleware in before.
List screens that can grow must ship paginate() and an AJAX pager in the first version. A pager that reloads the admin shell is not a pager.
Optional AI tools
If the desk AI is enabled, your module can register tools the assistant is allowed to call.
Naming: Shop.Orders.Search. The controller string is a normal callable. Rights are checked again at execution. Empty rights means nobody, including root in practice of the registry, sees a usable tool — fill the array.
DotApp::call('DACore:AITools@register', 'Shop.Orders.Search', [
'creator' => 'Shop',
'description' => 'Search orders by number or email.',
'controller' => 'Shop:AI@searchOrders',
'howtouse' => json_encode(['input_schema' => ['query' => ['type' => 'string', 'required' => true]]]),
'rights' => json_encode(['dotapp.root', 'Shop.orders.view']),
'tool_type' => 'lookup',
'risk_level' => 0,
'requires_confirmation' => false,
'intent_tags' => ['find order', 'search orders'],
]);
Mutating tools set requires_confirmation and a higher risk_level.
Use allowed_tools / forbidden_tools so a lookup workflow cannot jump into delete.
Unregister on uninstall. Details belong with AIRULES doc 34 and the sample EX-D03.
You are not teaching the model to log users in. DACore already did that. You expose your nouns.
Ship checklist
dainstall.phpcallsInstallation::module('YourModule')->install()- Versioned
installer()anduninstaller()with exist/insert guards init/module.init.phpandinit/module.listeners.php- Tables
yourmodule_* - Rights group + rights + menu ids prefixed with the module name
- Optional AI tools with non-empty rights
- Controllers use
Page@withMenu! - Your own Rights middleware, not AuthTest for permissions
- Locale JSON for menu and right labels
- ZIP passes validation (no root
install.php)
FAQ
Does DotApper create dainstall.php?
DotApper creates a framework module. You add the DACore files (or copy TestInstalatora). Do not wait for the CLI to guess the admin contract.
Can the same module have a public storefront and a DACore desk?
Yes. Public routes stay in initialize() without the admin prefix. Desk routes sit under prefixUrl and withMenu. Do not mix the public HTML shell with the admin chrome.
Where do I push “order paid”?
DACore:Notifications@push on that event. Not in the installer, not in a cron that fires every request.