CliAppBootstrap and ModuleCliRuntime still required from the old
lib/ path, which would break all CLI commands. Also fixes stale
lib/ references in docs, test assertion messages, and a PHPDoc
comment.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
reviewer-code.md was missing 4 of 17 assigned guards (LAYERS, META,
MODULE, TAXONOMY); reviewer-security.md was missing GR-SEC-010. README
did not list the Scheduled Jobs feature.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Audited all 1441 keys against 12 translation pipelines (direct t() calls,
flash messages, enum labelToken(), filter schemas, module manifests, theme
configs, etc.). Removed 180 keys unused by any pipeline from both language
files (1441 → 1261 keys, parity maintained).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update security guard references from 9 to 10 (GR-SEC-010 was missing),
code reviewer guards from 13 to 17 (new GR-CORE-LAYERS, GR-CORE-MODULE,
GR-CORE-META, GR-UI-TAXONOMY), and total guard count from 22 to 27.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cover Settings gateways (Session, LoginPersistence, SystemAudit, Smtp,
FrontendTelemetry, Metadata, App), Auth gateways (ExternalIdentity,
Tenant), DirectorySettings, UserSettings, and HotkeyService.
Gateway test coverage rises from 25% to 52%, overall service/gateway
coverage from 58.6% to 70.7%.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move HelpdeskTenantSettingsRepository and HelpdeskTokenRepository from
Service/ to Repository/ directory (GR-SEC-003: SQL only in repositories)
- Make AccessControl constructor require IntendedUrlService explicitly
instead of falling back to `new IntendedUrlService()` (GR-TEST-002:
no service instantiation outside factories)
- Update all imports and tests accordingly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Core code (lib/) no longer imports any MintyPHP\Module\Audit\* classes directly.
New ApiAuditServiceInterface and ApiSystemAuditReporterInterface follow the
existing pattern (interface + null implementation in core, concrete binding
from module registrar). DoctorRunner and helpers/app.php now resolve through
DI interfaces, ensuring the audit module remains fully optional.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add missing i18n key "LDAP attribute map could not be encoded" (de+en)
- Replace raw font-weight/line-height with design tokens in app-topbar.css
- Fix import ordering in SchedulerRunService.php (php-cs-fixer)
- Fix PHPStan mock chain errors in DebitorDetailServiceTest and TenantSsoServiceTest (PHPUnit 12 API)
- Simplify redundant comparison in NotificationService dedupe window
- Add PHPStan type hint for mock callback array in SchedulerJobFailedNotificationListenerTest
- Fix undefined variable and add flow-analysis suppression in login().php
- Increase dev PHP memory_limit to 1G for PHPStan (prod stays 256M)
All 6 quality gates now pass: QG-001 (1618 tests), QG-002 (0 errors),
QG-003 (6630 assertions), QG-006 (0 fixable), QG-008, QG-009.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds 11 tests for BcODataGateway covering getMeetingsForCustomer (empty,
whitespace, not-configured), OData injection rejection for all customerNo
methods (meetings, contract lines, escalation tickets, controlling), and
valid input acceptance. Also adds meetingsKey format and inclusion tests
for DebitorCacheControl.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces escapeODataTrustedLiteral with escapeODataStrictUserInput for all
customerNo parameters that originate from HTTP request input. The trusted
literal escape (quote-only) remains for BC-sourced values like customerName.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a meetings section to the debitor detail page, fetching meeting data from BC OData API with cache control support. Includes timeline UI for upcoming and past meetings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fetches displayName/jobTitle from Graph API and syncs them via SSO profile sync. Also fixes LDAP enabled state in tenant form, adds LDAP save on tenant create, and hardens LDAP port validation precedence.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds deleteOrphanedJobs() to clean up stale job_keys during ensureSystemJobs, and prevents editing jobs that are no longer registered in the runtime registry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Users who exist in Microsoft Entra ID or LDAP but have no local account
were blocked at the email-first login step because the system required a
local user record before showing SSO options. This adds a domain-based
fallback: when the email is unknown locally, the system checks tenant SSO
configurations for matching allowed_domains and presents SSO-only login
options, enabling the existing JIT provisioning to trigger on callback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restructure helpdesk tenant settings into per-connection config with
improved token handling. Update risk radar data endpoint and UI.
Clean up ImageUploadTrait and update architecture contract tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace float-based header layout with flexbox (align-items: flex-start)
so the close button stays pinned top-right regardless of title length.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce data-icon-swap attribute with paired outline/fill <i> elements.
CSS toggles to filled icon on hover and active state. Module slots
auto-derive fill variant from icon name, with explicit icon_fill override
for icons without a fill counterpart (e.g. bi-headset). Add enforcement
script bin/icon-swap-check.sh.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use Stripe pattern: outline icons by default, filled for active state
(bookmarks bookmarked, notifications unread). Remove chevron arrows
from tenant/user dropdowns. Add max-width with ellipsis to breadcrumb
items to prevent overflow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move topbar outside the grid container as a standalone sticky header
(Shopify/Stripe pattern). Sidebar and icon bar use position:sticky
relative to topbar height. Mobile uses drawer pattern with backdrop,
focus trap, ESC close, and scroll lock. Tenant branding moved from
sidebar header into the topbar.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
createForTenantAdminUsersFromMessage relies on listActiveUserIdsByTenantId
which returns empty results in CLI context (scheduler). Instead, iterate
admin users directly and call createFromMessage per user per tenant.
Verified end-to-end: notification created, dedup blocks duplicates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change DEDUPE_TYPES from bool to int|true — int value overrides the
default 30-min window. scheduler.job_failed uses 1440 min (24h) so
admins receive at most one notification per failed job per day.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ?ModuleEventDispatcher to SchedulerRunService (fail-open, nullable).
Dispatch scheduler.job_failed event when a job fails with payload:
job_id, job_key, label, error_code, error_message, trigger_type.
New SchedulerJobFailedNotificationListener in notifications module
creates in-app admin notifications for users with JOBS_MANAGE
permission, scoped per tenant, with 30-min dedup per job_key.
Also fixes audit module manifest: system_audit_purge job_key was
mismatched (audit_system_purge vs system_audit_purge in DB).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add architecture test ViewLayerOutputEscapingContractTest that flags
raw <?php echo in .phtml files. Legitimate uses (pre-built ARIA attrs
from navActive(), hardcoded role values, pre-rendered HTML fragments)
must carry an inline // raw-html-ok: reason marker.
Annotated all 11 existing raw echo sites with justification markers.
Added guard to catalog, enforcement map (automated), CLAUDE.md security
section, and never-do-this list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace bare <input type="file"> across all upload locations with a
card-based dropzone component via shared partial. Three visual states:
current server file (thumbnail + Replace/Delete), empty dropzone, and
pending file preview (local FileReader thumbnail + metadata). Delete
actions use data-confirm-message for confirmation dialog.
Centralized as templates/partials/app-file-upload.phtml to prevent
markup drift — documented as deliberate exception to plain-HTML inputs
convention in CLAUDE.md and developer checklist.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce per-tenant override for helpdesk BC connection config.
New table helpdesk_tenant_settings stores tenant-specific credentials
with encrypted secrets (AES-256-GCM). EffectiveHelpdeskSettingsService
resolves global vs tenant config per request. Settings UI extended with
tenant override tab, toggle, and dual editing mode.
All runtime consumers (OData, SOAP, OAuth) read through the new
resolver. Token cache flushed on any config change. Default behavior
unchanged — tenants use global config until override explicitly enabled.
Also includes risk radar UI refinements: Stripe-style card redesign
with accent borders and score pills, removal of redundant KPI tiles
and search, and filtering of zero-score entries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- margin-bottom: 0 on cards to override article shell default
- Level thresholds stricter: high >= 55 (was 70), medium >= 25 (was 40)
so fewer customers appear as 'low risk' and issues surface earlier
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove background circle, just show the score as a large colored
number. Lighter, more Stripe-like.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stripe-style layout: large colored score circle on the left, customer
name + risk level label on the right. One-line key facts below
separated by middot (e.g. '5 open · 2 critical · 1 SLA · ↑3').
Removed border-left color accent, pill list, and metric row.
Three visual levels: score (eye-catching), name+level (context),
facts (detail). Clean scannable layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each dimension shows 'points / max' (e.g. '18 / 35') making the
score composition immediately understandable. Total row sums to
the final 'score / 100'. Replaces abstract percentage column.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolution time measures the past, not current risk. The remaining
4 dimensions (open pressure 35%, trend 30%, SLA overdue 25%,
inactivity 10%) are all current and actionable. Removes resolution
aggregation, median calculation, and weight redistribution logic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove duplicate metrics row and reason list. Replace with a single
row of compact pills: '5 open', '2 critical' (orange), '1 SLA' (orange),
'↑3' (orange) or '↓2'. No bullet points — pills as flex-wrap row.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove '• ' prefix from reason list items (CSS list-style handles it)
- Replace abstract dimension bars in dialog with a clear table showing
dimension name, score (0-100), and weighted contribution per row
- Total row shows final risk score
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cards now show plain-language reasons instead of abstract progress bars:
'8 open, 3 critical', '↑4 more than resolved', '2 SLA breaches',
'Oldest ticket 12d'. Instantly understandable without knowing the
scoring formula. Driver bars remain in the detail dialog for the
full dimension breakdown.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show ↑3 (growing) or ↓2 (shrinking) instead of '+3 net' / '-2 net'.
Same visual language as the team dashboard trend arrows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Search input now comes first (left), period segment control after.
Both in a horizontal flex row on the same line.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New portfolio view scoring customers 0-100 across five dimensions:
- Open pressure (30%): open + critical ticket count
- Trend (25%): net ticket flow (created - closed) in period
- SLA overdue (20%): tickets exceeding escalation targets
- Resolution time (15%): median hours to close (null = neutral)
- Inactivity (10%): age of oldest open ticket
Card grid with score badges (color-coded high/medium/low), metric
pills, driver bars. Click opens detail dialog with all dimensions
and open ticket list. Clientside search filter.
PBI_LV_Tickets with client-driven paging ($skip/$top, hardcap 5000).
Escalation definitions cached separately (30min TTL). Truncated
banner when data is capped.
New: RiskRadarService, getTicketsForRiskRadar(), 13 PHPUnit tests,
permission helpdesk.risk-radar.view, 2 routes, i18n de+en.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Change query_json column from TEXT to JSON, ip/user_agent to CHAR(64)
for PII redaction compliance
- Update repository, service, and views for hardened schema
- Add architecture contract test for security logging redaction
- Add search resource provider test coverage
- Include DB migration script for existing installs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The timeline indent is handled by the parent activity container,
not individual file elements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FsLightbox uses data-type to skip content detection. Combined with
Cache-Control: max-age=3600 on the proxy, the browser should serve
the lightbox image from cache instead of making a second SOAP call.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Skeleton shimmer placeholder shown while SOAP image loads
- onload hides skeleton, onerror hides entire preview
- data-fslightbox="ticket-files" on image link enables FsLightbox
overlay (auto-detected by global MutationObserver)
- cursor: zoom-in on preview images
- Gallery name "ticket-files" groups all images in the feed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Proxy endpoint supports ?inline=1 parameter that serves images with
correct Content-Type and Content-Disposition: inline. Uses fpassthru
via php://memory stream to bypass MintyPHP Analyzer restrictions.
Frontend always attempts image preview via <img> tag. Non-image files
gracefully hide the preview via onerror handler. Download link shown
for all file types regardless.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>