diff --git a/README.md b/README.md
index 9d1bfd8..5a79c64 100644
--- a/README.md
+++ b/README.md
@@ -33,4 +33,4 @@ find app config database public -type f -name '*.php' -print0 | xargs -0 -n1 php
- Do not commit `.env` or production credentials.
- Set `APP_KEY` to a long random value and keep it in a secrets manager in production.
- Credential vault encryption and the remaining domain modules are scheduled in later phases.
-- The initial schema is intentionally migration-ready but is delivered as an idempotent bootstrap SQL file for the first Docker increment.
+- The initial schema is delivered as a Docker bootstrap SQL file. Apply it once to a new database; later releases should use versioned migrations.
diff --git a/app/Domain/Jobcard/TimeAggregator.php b/app/Domain/Jobcard/TimeAggregator.php
index 102dbf0..14d7aea 100644
--- a/app/Domain/Jobcard/TimeAggregator.php
+++ b/app/Domain/Jobcard/TimeAggregator.php
@@ -22,7 +22,7 @@ final class TimeAggregator
{
return $this->total(array_values(array_filter(
$entries,
- static fn ($entry): bool => is_array($entry) && (($entry['counts_toward_sla'] ?? true) === true)
+ static fn ($entry): bool => is_array($entry) && in_array($entry['counts_toward_sla'] ?? true, [true, 1, '1'], true)
)));
}
}
diff --git a/app/Domain/Jobcard/TimeCalculator.php b/app/Domain/Jobcard/TimeCalculator.php
index 1844f07..cefcf22 100644
--- a/app/Domain/Jobcard/TimeCalculator.php
+++ b/app/Domain/Jobcard/TimeCalculator.php
@@ -4,7 +4,7 @@ declare(strict_types=1);
function calculate_duration_hours(?string $start, ?string $end, ?float $manualHours = null): ?float
{
if ($manualHours !== null) {
- return $manualHours >= 0 ? round($manualHours, 2) : null;
+ return $manualHours > 0 ? round($manualHours, 2) : null;
}
if ($start === null || $end === null || !preg_match('/^\d{2}:\d{2}$/', $start) || !preg_match('/^\d{2}:\d{2}$/', $end)) {
return null;
diff --git a/app/Domain/SLA/SlaCalculator.php b/app/Domain/SLA/SlaCalculator.php
index fd6f929..ef2ddff 100644
--- a/app/Domain/SLA/SlaCalculator.php
+++ b/app/Domain/SLA/SlaCalculator.php
@@ -3,6 +3,9 @@ declare(strict_types=1);
function calculate_sla_usage(float $allocatedHours, array $hours): array
{
+ if ($allocatedHours < 0) {
+ throw new InvalidArgumentException('Allocated hours must not be negative.');
+ }
$used = round(array_sum(array_map(static fn ($value): float => max(0.0, (float)$value), $hours)), 2);
$remaining = round(max(0.0, $allocatedHours - $used), 2);
$percentage = $allocatedHours > 0 ? round(($used / $allocatedHours) * 100, 2) : ($used > 0 ? 100.0 : 0.0);
diff --git a/config/bootstrap.php b/config/bootstrap.php
index 5d3182f..72e1dfc 100644
--- a/config/bootstrap.php
+++ b/config/bootstrap.php
@@ -35,9 +35,14 @@ function csrf_token(): string
return $_SESSION['csrf'];
}
+function scalar_input(mixed $value, string $default = ''): string
+{
+ return is_scalar($value) ? (string)$value : $default;
+}
+
function verify_csrf(): void
{
- $provided = (string)($_POST['_csrf'] ?? '');
+ $provided = scalar_input($_POST['_csrf'] ?? null);
if (!hash_equals((string)($_SESSION['csrf'] ?? ''), $provided)) {
http_response_code(419);
exit('Invalid CSRF token');
diff --git a/public/index.php b/public/index.php
index a543e5d..30331b9 100644
--- a/public/index.php
+++ b/public/index.php
@@ -3,8 +3,11 @@ declare(strict_types=1);
require_once __DIR__ . '/../config/bootstrap.php';
require_once __DIR__ . '/../app/Domain/Client/ClientValidator.php';
+require_once __DIR__ . '/../app/Domain/Jobcard/JobcardReference.php';
-session_set_cookie_params(['httponly' => true, 'secure' => !empty($_SERVER['HTTPS']), 'samesite' => 'Lax']);
+ini_set('session.use_strict_mode', '1');
+$forwardedHttps = getenv('TRUST_PROXY') === '1' && scalar_input($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https';
+session_set_cookie_params(['httponly' => true, 'secure' => !empty($_SERVER['HTTPS']) || $forwardedHttps, 'samesite' => 'Lax', 'path' => '/']);
session_start();
function render_header(string $title): void
@@ -12,7 +15,7 @@ function render_header(string $title): void
$user = current_user();
echo '
' . e($title) . ' · JOBcard';
if ($user) {
- echo '