Version 0
First Portal
Plan the first useful vertical slice for a MortelOS portal.
A MortelOS portal starts with capabilities, not pages.
Ask what each role must be able to do. Then map the data, approvals, workflows and surfaces needed to make that capability safe.
Capability map
Write a short capability map before implementation.
| Area | Question |
|---|---|
| Goal | What outcome must the portal make possible? |
| Roles | Who uses it and what trust level do they have? |
| Read capabilities | What may each role view or search? |
| Write capabilities | What may each role create, upload, edit, approve or trigger? |
| Data | Which domain objects matter? |
| External systems | Which systems provide or receive data? |
| Approvals | Which actions need human review? |
| Risk | Which data or action is sensitive? |
| Surfaces | Where should users complete the work? |
| Reuse | Could this serve another MortelOS installation? |
Keep the map short enough for a stakeholder to review. Unknowns that do not block the first slice can be follow-ups. Unknowns that affect tenant isolation, approvals or sensitive writes block implementation.
Package decision
Record the package boundary before adding a surface, workflow, connector, widget or reusable service.
| Decision | Use when |
|---|---|
package-now |
The capability can serve another MortelOS installation today. |
package-ready |
The host needs local wiring first, but the package boundary is explicit. |
workspace-only |
The behavior is specific to one customer or workspace. |
Default to package-ready when unsure. It preserves speed while keeping the future package boundary visible.
First vertical slice
Choose the smallest useful end-to-end capability. A good first slice usually contains:
- One role. In the starter: the owner, who holds
invitations.manage(app/Access/StarterGovernanceGate.php). - One domain object. In the starter: an invitation (
app/Models/Invitation.php). - One read model. In the starter: the
invitationstable, written only byapp/Projectors/InvitationProjector.php. - One workflow or action. In the starter:
app/Actions/Invitations/InviteUser.php, withapp/Aggregates/InvitationAggregate.phpandapp/Events/Invitations/InvitationCreated.phpbehind it. - One surface where the user completes the work. In the starter:
resources/views/pages/users/⚡index.blade.php. - One policy test for allowed access and one for denied access. In the starter:
tests/Feature/InvitationsTest.php.
Do not start with the full navigation tree. Build the first capability to completion, then expand.
The invitations are the worked example. The chain below is what a first slice looks like on disk:
Owner enters an email and a role on the users page
|
v
InviteUser checks invitations.manage and returns an ActionResult with an Undo
|
v
InvitationAggregate records InvitationCreated
|
v
InvitationProjector writes the invitations table
|
v
The users page lists the open invitation and shows the link once
|
+--> Undo button: RevokeInvitation records InvitationRevoked
|
+--> The invitee opens /invite/{token}: AcceptInvitation creates the account
and records InvitationAccepted
See TALL conventions for the same chain with every file named.
For a document review portal, the same shape gives this first slice:
Customer uploads a document
|
v
DocumentUploaded event is recorded
|
v
DocumentReviewStatus projection shows pending
|
v
Account manager sees an inbox item
|
v
Account manager approves or rejects
|
v
Projection updates and audit history explains the decision
Done means verified
Before claiming the first slice is done, verify:
vendor/bin/pint --dirty
php artisan starter:doctor
composer test:arch
vendor/bin/pest
composer test:browser
starter:doctor checks the boot baseline wiring. composer test:arch must report nothing. composer test:browser runs the browser test of every screen you touched.
Also run the browser smoke manually:
Open http://127.0.0.1:8000
Log in as admin@example.test / password
Reach dashboard
Open the new capability surface
Verify allowed and denied role behavior
For a capability with approvals, verify both approve and reject paths.