# Changelog

All notable changes to ProbizCRM. Versions follow semantic versioning; the
database schema version tracks the application version, and every release ships
forward-only migrations.

## [0.3.3] — 2026-09-02 — shared hosting fixes

### Fixed
- The installer called `symlink()` unguarded when linking storage into the web
  root. That function is absent or disabled on a great deal of shared hosting,
  and because the call sat inside the install transaction it aborted the whole
  run *after* the schema, seed data and administrator account had all been
  created successfully. Linking is now attempted only where the function exists,
  and a failure is reported rather than fatal.
- `set_time_limit()` and `ini_set()` in the dependency step are likewise guarded,
  for the same class of host.
- The installer regenerated `APP_KEY` on every run. A retry after a partial
  install would therefore replace the key that decrypts sessions and two-factor
  secrets already written, locking people out of accounts that looked healthy.
  An existing key in `.env` is now reused.

### Added
- `GET /storage/{path}` serves the public disk through the application on hosts
  without symlinks. Where the symlink exists the web server answers first and
  this route is never reached. Restricted to an extension allowlist, rejects
  traversal before any filesystem access, and serves with a sandbox CSP so a
  crafted SVG logo cannot become stored XSS.
- The install screen now shows what completed before an error and states that
  re-running is safe.

## [0.3.2] — 2026-09-02 — dependency resolution fix

### Fixed
- `laravel/tinker` was listed under `require` at `^2.10`, which has no release
  supporting Laravel 13. Composer therefore refused to resolve anything, with or
  without `--no-dev`. Tinker is a console REPL, not a runtime dependency, and the
  browser installer removed the only workflow that needed it. It is now a
  `suggest` entry and nothing more.
- `symfony/mailer` and `predis/predis` were pinned explicitly. The first is
  already pulled in by `illuminate/mail`, and pinning its major version could
  conflict with whatever the framework wants; the second is only needed if you
  opt into Redis. Both removed from `require`.

### Changed
- Framework constraint relaxed to `^12.0 || ^13.0` and Sanctum to
  `^4.0 || ^5.0`. Composer prefers the newest that resolves, so a host running
  PHP 8.3 gets Laravel 13 and one on 8.2 gets Laravel 12. Nothing in the
  codebase is specific to either.
- PHP floor lowered from 8.3 to 8.2, in the installer's requirement check and in
  `composer.json`. Nothing here uses 8.3-only syntax, and the lower floor widens
  the set of hosts this runs on.
- `phpunit` and `nunomaduro/collision` widened to accept their next major, so a
  dev dependency cannot block a production install again.

### Added
- `php artisan probiz:enrol-admin {email}` — two-factor enrolment for platform
  administrators created outside the browser installer, replacing the tinker
  recipe the docs used to give.

## [0.3.1] — 2026-09-02 — browser installer

### Added
- `public/install.php`: a nine-step browser installer requiring no shell access.
  Checks requirements, installs dependencies, tests the database before
  accepting it, writes `.env`, generates the application key, runs migrations
  and seeders, creates the platform administrator and walks two-factor
  enrolment. Refuses to run once `storage/installed.lock` exists.
- Bundled `composer.phar`, driven in-process through the Composer console API.
  Shelling out is not an option, since `exec()` is disabled on most shared
  hosting — which is the reason the installer exists.
- Laravel skeleton files so the package is self-contained rather than an overlay
  on `composer create-project`: `artisan`, `public/index.php`, and the standard
  `config/` files for app, database, cache, session, queue, mail, filesystems,
  logging and services.
- Root `.htaccess` forwarding into `public/` and denying access to `.env`,
  `vendor/`, `storage/` and the rest, for hosting where the document root cannot
  be moved.
- `GET /system/schedule/{token}`: the scheduler over HTTP, so cPanel's Cron Jobs
  form can drive trial expiry and dunning without shell access. Guarded by a
  constant-time token comparison, an overlap lock and a rate limit.

### Changed
- Minimum database relaxed from MySQL 8.0 to MySQL 8.0+ **or MariaDB 10.6+**.
  Nothing in the schema uses MySQL-8-only syntax, and MariaDB is what shared
  hosting usually ships.
- The browser installer configures `QUEUE_CONNECTION=sync` and database-backed
  cache and sessions, since neither a queue worker nor Redis can be assumed
  without a shell. Both are one `.env` line to change later.

## [0.3.0] — 2026-09-01 — Phase 3: authorisation

### Added
- `PermissionRegistry`: resolves the permission set a user holds across all
  their roles, taking the widest scope where two roles overlap. Cached per user
  and busted on any role change.
- `ScopeResolver`: turns own / team / tenant into both a policy answer and a
  query restriction, so a list endpoint and its policy cannot disagree.
- `Builder::visibleTo($user, $permission)` macro, used by every list query.
- `BasePolicy` plus policies for contacts, companies, leads, opportunities,
  tasks, appointments, notes, files, activities, users, roles and billing.
- `Gate::before` grants the tenant owner everything; `Gate::after` refuses every
  write ability during a read-only impersonation session, whatever the
  impersonated user's own permissions allow.
- `permission:` route middleware as a coarse section-level filter.
- `RoleService`: custom role creation, editing, duplication and assignment, with
  three escalation guards — you cannot grant a permission you do not hold, at a
  wider scope than your own, or at a role level at or above your own.
- `config/authorization.php` registers the owner column per model. A model
  missing from it throws rather than defaulting to a guessed column.
- Test suites: `PermissionScopeTest` (7 cases including policy/query agreement)
  and `PrivilegeEscalationTest` (8 cases, one per escalation route).

## [0.2.0] — 2026-09-01 — Phase 2: authentication

### Added
- Tenant signup: one transaction creating the organisation, its trial
  subscription, roles, pipelines, counters and owner account.
- Sign-in with account lockout, per-account and per-IP rate limiting, and an
  identical failure response for an unknown address and a wrong password.
- TOTP two-factor with QR enrolment and single-use recovery codes. The secret is
  written only after a valid code proves the authenticator holds it.
- Password reset that kills every other session for the account, forced password
  change, and email verification.
- Team invitations: token stored as a SHA-256 hash, invited address fixed at
  issue and unchangeable at acceptance.
- Platform admin sign-in on a separate guard and table, with mandatory
  two-factor.
- Support impersonation: written reason required, read-only unless a super admin
  requests writes, self-expiring, and logged to the tenant's own audit trail as
  well as the platform's.
- `TenantAwareUserProvider`, which suspends tenant scoping for the four lookups
  authentication genuinely cannot scope.
- `EnforceSessionLifetime` middleware adding idle and absolute session caps on
  top of Laravel's inactivity-only expiry.
- `AuditLogger` with name-based redaction of passwords, tokens and secrets.
- Authentication views, self-contained from the design tokens so sign-in works
  before the asset pipeline exists.
- Test suites: `RegistrationTest`, `LoginTest`, `ImpersonationTest`.

### Changed
- `ResolveTenant` now expires an impersonation session that has run past
  `tenancy.impersonation.max_minutes`.

## [0.1.0] — 2026-08-30 — Phase 1: foundation

### Added
- Tenancy layer: `TenantContext`, `Tenancy` manager, fail-closed `TenantScope`,
  `BelongsToTenant` trait with write stamping, immutable `tenant_id` and a
  hydrated-record guard.
- Complete schema: 8 migrations, 52 application tables plus Laravel's own,
  with composite `(tenant_id, id)` uniques and composite foreign keys enforcing
  cross-tenant referential integrity at the database.
- 49 Eloquent models with relationships, casts and domain constants.
- Permission catalogue (78 keys) and the five system roles expressed as config.
- Entitlement system: `Entitlements` value object, `EntitlementResolver` with
  plan values, per-tenant overrides and fallback defaults.
- `TenantProvisioner`: transactional creation of roles, pipelines, counters and
  the owner account for a new organisation.
- Middleware: tenant resolution, subscription gate, entitlement gate.
- Seeders: permissions, three plans priced in nine currencies with entitlements,
  platform settings, first super admin, and an optional demo organisation.
- Security test suites: tenant isolation (13 assertions across ORM, primary key,
  scope bypass, foreign key and aggregate vectors) and schema integrity.
- Design token system and Tailwind configuration.

### Architecture decisions taken in this phase
- **Laravel 13 rather than a purpose-built core.** The platform is operated
  in-house rather than distributed as a ZIP, which removes the shared-hosting
  constraints that argued against a framework and leaves upstream security
  maintenance as the deciding factor.
- **Users hold a globally unique email.** One person is one account in one
  organisation, which keeps login, password reset and email verification
  unambiguous. Releasing an address is handled by anonymising it into
  `archived_email` on deletion. Cross-organisation membership, if ever needed,
  arrives as a `tenant_memberships` table rather than by making `email`
  ambiguous.
- **Per-currency plan pricing** (`plan_prices`) rather than a single price with
  conversion at checkout, because the product sells internationally.
- **Tax fields on invoices** (`tax_rate`, `tax_country`, `reverse_charge`,
  `exchange_rate`, `base_currency_total`) so both the merchant-of-record and
  direct-merchant billing paths are supported without a schema change.
- **Sanctum for API tokens** in place of a bespoke `api_tokens` table.
- **`board_position`** on leads and opportunities, spaced in tens, so a Kanban
  drag is one UPDATE rather than a renumber of the column.

### Known gaps at the time of this release
- No HTTP layer. Addressed in 0.2.0.
- Policies not yet enforced in code. Addressed in 0.3.0.
- Payment gateway implementations are configured but not built. Phase 6.

## Outstanding after 0.3.0
- CRM screens — contacts, companies, pipelines, deals, the Kanban board — are
  Phases 4 and 5, along with the compiled Tailwind bundle that replaces the
  inline token CSS in the current views.
- Team and role management screens are Phase 5; `RoleService` and `RolePolicy`
  are complete and tested, but nothing yet calls them from a controller.
- Payment gateway integrations, invoicing and dunning are Phase 6.
- The REST API is Phase 7, and the platform console beyond sign-in and
  impersonation is Phase 8.
