From 9a08f96c11537657cc09364e667add4467521cc3 Mon Sep 17 00:00:00 2001 From: fs Date: Fri, 6 Mar 2026 00:44:52 +0100 Subject: [PATCH] agent foundation --- agent-system/README.md | 14 + agent-system/checks/acceptance-checklist.md | 25 + agent-system/checks/guard-catalog.json | 272 +++++++ agent-system/checks/guard-checklist.md | 65 ++ agent-system/checks/quality-gates.json | 49 ++ agent-system/contracts/executor.schema.json | 97 +++ agent-system/contracts/finalizer.schema.json | 105 +++ agent-system/contracts/planner.schema.json | 143 ++++ .../contracts/reviewer-acceptance.schema.json | 81 ++ .../contracts/reviewer-guards.schema.json | 46 ++ agent-system/prompts/executor.md | 25 + agent-system/prompts/finalizer.md | 18 + agent-system/prompts/planner.md | 28 + agent-system/prompts/reviewer-acceptance.md | 17 + agent-system/prompts/reviewer-guards.md | 20 + agent-system/runs/.gitkeep | 1 + .../templates/execution-report.template.json | 39 + agent-system/templates/finalize.template.json | 11 + agent-system/templates/plan.template.json | 49 ++ .../templates/review-acceptance.template.json | 16 + .../templates/review-guards.template.json | 15 + config/assets.php | 1 + config/settings.php | 3 + db/init/init.sql | 8 +- ...2026-03-05-frontend-telemetry-settings.sql | 9 + docs/agents/flow.md | 40 + docs/agents/overview.md | 27 + docs/agents/roles.md | 47 ++ docs/frontend-javascript.md | 19 +- docs/index.md | 11 + i18n/default_de.json | 29 +- i18n/default_en.json | 23 + lib/App/AppContainer.php | 3 + lib/App/Bootstrap/EnvValidator.php | 3 + .../Container/Registrars/AccessRegistrar.php | 2 + .../Registrars/AppServicesRegistrar.php | 20 + .../Registrars/RepositoryFactoryRegistrar.php | 1 + .../Registrars/ServiceFactoryRegistrar.php | 25 +- .../Registrars/SettingsRegistrar.php | 34 +- lib/App/registerContainer.php | 18 +- lib/Http/ApiAuth.php | 11 +- lib/Http/ApiBootstrap.php | 26 +- lib/Http/ApiResponse.php | 1 + lib/Http/ApiSystemAuditReporter.php | 4 + lib/Http/CookieStore.php | 34 + lib/Http/CookieStoreInterface.php | 18 + lib/Http/Input/RequestInputFactory.php | 1 + lib/Http/Request.php | 2 + lib/Http/RequestContext.php | 7 + lib/Http/RequestRuntime.php | 66 ++ lib/Http/RequestRuntimeInterface.php | 21 + lib/Http/SessionStore.php | 50 ++ lib/Http/SessionStoreInterface.php | 19 + lib/Service/Access/AccessGatewayFactory.php | 7 +- lib/Service/Access/AccessPolicyFactory.php | 2 + lib/Service/Access/AuthorizationDecision.php | 3 + lib/Service/Access/AuthorizationService.php | 1 + lib/Service/Access/PermissionService.php | 19 +- lib/Service/Access/UiAccessService.php | 1 + .../Access/UserAuthorizationPolicy.php | 2 + .../AddressBook/AddressBookService.php | 3 + lib/Service/Audit/ApiAuditService.php | 18 +- lib/Service/Audit/AuditServicesFactory.php | 16 +- .../Audit/FrontendTelemetryIngestService.php | 515 +++++++++++++ .../Audit/SystemAuditRedactionService.php | 8 + lib/Service/Audit/SystemAuditService.php | 14 +- lib/Service/Auth/ApiTokenService.php | 8 +- lib/Service/Auth/AuthGatewayFactory.php | 36 +- lib/Service/Auth/AuthService.php | 92 ++- lib/Service/Auth/AuthServicesFactory.php | 34 +- .../Auth/AuthSessionTenantContextService.php | 46 ++ lib/Service/Auth/AuthSettingsGateway.php | 29 +- lib/Service/Auth/MicrosoftOidcService.php | 4 + .../Auth/MicrosoftOidcStateStoreService.php | 13 +- lib/Service/Auth/PasswordResetService.php | 2 + lib/Service/Auth/RememberMeService.php | 61 +- lib/Service/Auth/SsoUserLinkService.php | 4 + lib/Service/Auth/TenantSsoService.php | 2 + .../Branding/BrandingFaviconService.php | 6 +- lib/Service/Branding/BrandingLogoService.php | 2 + .../Branding/BrandingServicesFactory.php | 10 +- .../CustomField/TenantCustomFieldService.php | 7 + .../UserCustomFieldValueService.php | 4 +- .../Directory/DirectoryGatewayFactory.php | 2 +- .../Directory/DirectoryScopeGateway.php | 3 + .../Directory/DirectoryServicesFactory.php | 1 + .../Directory/DirectorySettingsGateway.php | 10 +- lib/Service/Import/CsvReaderService.php | 4 + lib/Service/Import/ImportService.php | 35 +- lib/Service/Import/ImportServicesFactory.php | 22 +- .../Import/ImportStateStoreService.php | 25 +- lib/Service/Import/ImportTempFileService.php | 4 + lib/Service/Mail/MailService.php | 34 +- lib/Service/Mail/MailServicesFactory.php | 10 +- lib/Service/Org/DepartmentService.php | 8 + lib/Service/Scheduler/ScheduledJobService.php | 3 + lib/Service/Scheduler/SchedulerRunService.php | 2 + lib/Service/Security/RateLimiterService.php | 5 + lib/Service/Settings/AdminSettingsService.php | 268 ++++--- lib/Service/Settings/SettingCacheService.php | 3 + lib/Service/Settings/SettingGateway.php | 285 ------- lib/Service/Settings/SettingKeys.php | 40 + lib/Service/Settings/SettingService.php | 720 ------------------ .../Settings/SettingServicesFactory.php | 69 +- .../Settings/SettingsApiPolicyGateway.php | 170 +++++ lib/Service/Settings/SettingsAppGateway.php | 162 ++++ .../Settings/SettingsCryptoGateway.php | 18 + .../SettingsCryptoGatewayInterface.php | 10 + .../Settings/SettingsDefaultsGateway.php | 78 ++ .../SettingsFrontendTelemetryGateway.php | 141 ++++ .../Settings/SettingsMetadataGateway.php | 37 + .../Settings/SettingsMicrosoftGateway.php | 100 +++ lib/Service/Settings/SettingsSmtpGateway.php | 142 ++++ .../Settings/SettingsSystemAuditGateway.php | 55 ++ .../Settings/SettingsUserLifecycleGateway.php | 80 ++ lib/Service/Settings/ThemeConfigService.php | 1 + lib/Service/Tenant/TenantScopeService.php | 3 + lib/Service/Tenant/TenantService.php | 3 + lib/Service/User/UserAccessPdfService.php | 7 - lib/Service/User/UserAccountService.php | 2 + lib/Service/User/UserAssignmentService.php | 3 + lib/Service/User/UserAvatarService.php | 3 + lib/Service/User/UserGatewayFactory.php | 28 +- lib/Service/User/UserLifecycleService.php | 2 + .../User/UserPasswordPolicyService.php | 1 + lib/Service/User/UserSettingsGateway.php | 23 +- lib/Service/User/UserTenantContextService.php | 1 + lib/Support/Crypto.php | 6 + lib/Support/Flash.php | 3 + lib/Support/Guard.php | 6 +- lib/Support/Search/SearchQueryNormalizer.php | 3 + .../Search/SearchSpecialResourceProvider.php | 3 + .../Search/SearchSqlResourceProvider.php | 3 + lib/Support/helpers/app.php | 62 +- pages/admin/departments/data().php | 4 +- pages/admin/frontend-telemetry/ingest().php | 107 +++ pages/admin/roles/data().php | 4 +- pages/admin/settings/index(default).phtml | 401 ++++++---- pages/admin/tenants/data().php | 9 +- pages/admin/users/create().php | 4 +- pages/api/v1/settings/index().php | 170 +++-- pages/api/v1/settings/public().php | 11 +- pages/lang().php | 8 +- templates/default.phtml | 25 +- templates/partials/app-confirm-dialog.phtml | 22 + .../partials/app-details-aside-actions.phtml | 10 + templates/partials/app-details-titlebar.phtml | 20 + templates/partials/app-footer.phtml | 2 +- .../partials/app-language-switcher.phtml | 30 +- .../Architecture/AgentSystemContractTest.php | 137 ++++ .../Architecture/CodexSkillsContractTest.php | 27 +- .../CoreStarterkitContractTest.php | 66 +- .../FrontendTelemetryContractTest.php | 173 +++++ tests/Service/Audit/ApiAuditServiceTest.php | 16 +- .../FrontendTelemetryIngestServiceTest.php | 332 ++++++++ .../Service/Audit/SystemAuditServiceTest.php | 20 +- .../AuthSessionTenantContextServiceTest.php | 76 ++ .../Service/Auth/MicrosoftOidcServiceTest.php | 567 ++++++++++++++ .../MicrosoftOidcStateStoreServiceTest.php | 7 +- tests/Service/Auth/TenantSsoServiceTest.php | 309 ++++++++ tests/Service/Import/ImportServiceTest.php | 12 +- .../Import/ImportStateStoreServiceTest.php | 5 +- tests/Service/Settings/SettingServiceTest.php | 144 ---- .../Settings/SettingsApiPolicyGatewayTest.php | 89 +++ .../Settings/SettingsAppGatewayTest.php | 72 ++ .../Settings/SettingsDefaultsGatewayTest.php | 101 +++ .../SettingsFrontendTelemetryGatewayTest.php | 47 ++ .../Settings/SettingsMicrosoftGatewayTest.php | 53 ++ .../Settings/SettingsSmtpGatewayTest.php | 46 ++ .../SettingsSystemAuditGatewayTest.php | 38 + .../SettingsUserLifecycleGatewayTest.php | 52 ++ tests/Service/User/UserAccountServiceTest.php | 479 ++++++++++++ web/css/components/app-confirm-dialog.css | 58 ++ web/css/layout/app-aside-icon-bar.css | 9 + web/css/layout/app-sidebar.css | 1 - web/js/app-init.js | 2 + web/js/app-login-init.js | 1 + web/js/components/app-confirm-actions.js | 81 +- web/js/components/app-settings-telemetry.js | 35 + web/js/core/app-confirm-dialog.js | 325 ++++++++ web/js/core/app-detail-page-factory.js | 15 +- web/js/core/app-details-action-policy.js | 57 +- web/js/core/app-dom.js | 31 +- web/js/core/app-grid-factory.js | 73 +- web/js/core/app-telemetry.js | 565 ++++++++++++++ web/js/pages/address-book-index.js | 3 +- web/js/pages/admin-api-audit-index.js | 3 +- web/js/pages/admin-departments-index.js | 3 +- web/js/pages/admin-import-audit-index.js | 3 +- web/js/pages/admin-mail-log-index.js | 3 +- web/js/pages/admin-permissions-index.js | 3 +- web/js/pages/admin-roles-index.js | 3 +- web/js/pages/admin-scheduled-jobs-index.js | 3 +- web/js/pages/admin-system-audit-index.js | 3 +- web/js/pages/admin-tenants-index.js | 3 +- .../pages/admin-user-lifecycle-audit-index.js | 3 +- web/js/pages/admin-users-index.js | 3 +- web/js/pages/app-users-list.js | 18 +- web/js/pages/search-index.js | 3 +- 199 files changed, 8522 insertions(+), 1880 deletions(-) create mode 100644 agent-system/README.md create mode 100644 agent-system/checks/acceptance-checklist.md create mode 100644 agent-system/checks/guard-catalog.json create mode 100644 agent-system/checks/guard-checklist.md create mode 100644 agent-system/checks/quality-gates.json create mode 100644 agent-system/contracts/executor.schema.json create mode 100644 agent-system/contracts/finalizer.schema.json create mode 100644 agent-system/contracts/planner.schema.json create mode 100644 agent-system/contracts/reviewer-acceptance.schema.json create mode 100644 agent-system/contracts/reviewer-guards.schema.json create mode 100644 agent-system/prompts/executor.md create mode 100644 agent-system/prompts/finalizer.md create mode 100644 agent-system/prompts/planner.md create mode 100644 agent-system/prompts/reviewer-acceptance.md create mode 100644 agent-system/prompts/reviewer-guards.md create mode 100644 agent-system/runs/.gitkeep create mode 100644 agent-system/templates/execution-report.template.json create mode 100644 agent-system/templates/finalize.template.json create mode 100644 agent-system/templates/plan.template.json create mode 100644 agent-system/templates/review-acceptance.template.json create mode 100644 agent-system/templates/review-guards.template.json create mode 100644 db/updates/2026-03-05-frontend-telemetry-settings.sql create mode 100644 docs/agents/flow.md create mode 100644 docs/agents/overview.md create mode 100644 docs/agents/roles.md create mode 100644 lib/Http/CookieStore.php create mode 100644 lib/Http/CookieStoreInterface.php create mode 100644 lib/Http/RequestRuntime.php create mode 100644 lib/Http/RequestRuntimeInterface.php create mode 100644 lib/Http/SessionStore.php create mode 100644 lib/Http/SessionStoreInterface.php create mode 100644 lib/Service/Audit/FrontendTelemetryIngestService.php create mode 100644 lib/Service/Auth/AuthSessionTenantContextService.php delete mode 100644 lib/Service/Settings/SettingGateway.php create mode 100644 lib/Service/Settings/SettingKeys.php delete mode 100644 lib/Service/Settings/SettingService.php create mode 100644 lib/Service/Settings/SettingsApiPolicyGateway.php create mode 100644 lib/Service/Settings/SettingsAppGateway.php create mode 100644 lib/Service/Settings/SettingsCryptoGateway.php create mode 100644 lib/Service/Settings/SettingsCryptoGatewayInterface.php create mode 100644 lib/Service/Settings/SettingsDefaultsGateway.php create mode 100644 lib/Service/Settings/SettingsFrontendTelemetryGateway.php create mode 100644 lib/Service/Settings/SettingsMetadataGateway.php create mode 100644 lib/Service/Settings/SettingsMicrosoftGateway.php create mode 100644 lib/Service/Settings/SettingsSmtpGateway.php create mode 100644 lib/Service/Settings/SettingsSystemAuditGateway.php create mode 100644 lib/Service/Settings/SettingsUserLifecycleGateway.php create mode 100644 pages/admin/frontend-telemetry/ingest().php create mode 100644 templates/partials/app-confirm-dialog.phtml create mode 100644 tests/Architecture/AgentSystemContractTest.php create mode 100644 tests/Architecture/FrontendTelemetryContractTest.php create mode 100644 tests/Service/Audit/FrontendTelemetryIngestServiceTest.php create mode 100644 tests/Service/Auth/AuthSessionTenantContextServiceTest.php create mode 100644 tests/Service/Auth/MicrosoftOidcServiceTest.php create mode 100644 tests/Service/Auth/TenantSsoServiceTest.php delete mode 100644 tests/Service/Settings/SettingServiceTest.php create mode 100644 tests/Service/Settings/SettingsApiPolicyGatewayTest.php create mode 100644 tests/Service/Settings/SettingsAppGatewayTest.php create mode 100644 tests/Service/Settings/SettingsDefaultsGatewayTest.php create mode 100644 tests/Service/Settings/SettingsFrontendTelemetryGatewayTest.php create mode 100644 tests/Service/Settings/SettingsMicrosoftGatewayTest.php create mode 100644 tests/Service/Settings/SettingsSmtpGatewayTest.php create mode 100644 tests/Service/Settings/SettingsSystemAuditGatewayTest.php create mode 100644 tests/Service/Settings/SettingsUserLifecycleGatewayTest.php create mode 100644 tests/Service/User/UserAccountServiceTest.php create mode 100644 web/css/components/app-confirm-dialog.css create mode 100644 web/js/components/app-settings-telemetry.js create mode 100644 web/js/core/app-confirm-dialog.js create mode 100644 web/js/core/app-telemetry.js diff --git a/agent-system/README.md b/agent-system/README.md new file mode 100644 index 0000000..7ad0985 --- /dev/null +++ b/agent-system/README.md @@ -0,0 +1,14 @@ +# Agent System + +This folder contains the technical assets for the multi-role workflow. + +Structure: +- `contracts/`: JSON schemas for role outputs +- `templates/`: starter JSON payloads +- `prompts/`: role-specific prompt baselines +- `checks/`: guard catalog, quality gates, and review checklists +- `runs/`: optional runtime artifacts (can stay untracked) + +References: +- Planner standard: `tools/codex-skills/starterkit-planner/SKILL.md` +- Guardrails: `tools/codex-skills/core-guardrails/SKILL.md` diff --git a/agent-system/checks/acceptance-checklist.md b/agent-system/checks/acceptance-checklist.md new file mode 100644 index 0000000..3dc64a7 --- /dev/null +++ b/agent-system/checks/acceptance-checklist.md @@ -0,0 +1,25 @@ +# Acceptance Checklist + +Use this checklist for the acceptance reviewer. + +## Scope +- all in-scope items from `plan.json` are implemented +- out-of-scope items are not silently introduced + +## Behavior +- each `SC-*` success criterion from `plan.json` is verifiably met +- negative and edge paths behave as defined + +## Regressions +- existing flows still work after change +- no critical UX break on key user paths + +## Tests +- every test listed in `plan.json tests[]` is implemented and passes (QG-001 green) +- new Service/Gateway logic has at least one test covering the happy path and one failure/edge case (GR-TEST-001) +- no new untestable code patterns introduced (GR-TEST-002) + +## Evidence +- each criterion has explicit pass/fail evidence +- each criterion check references the exact `SC-*` ID +- fail findings include exact missing behavior diff --git a/agent-system/checks/guard-catalog.json b/agent-system/checks/guard-catalog.json new file mode 100644 index 0000000..24d99d3 --- /dev/null +++ b/agent-system/checks/guard-catalog.json @@ -0,0 +1,272 @@ +{ + "version": "1.0.0", + "source_skill": "tools/codex-skills/core-guardrails/SKILL.md", + "guards": [ + { + "id": "GR-CORE-001", + "area": "core", + "title": "Layering boundaries", + "requirement": "MUST keep pages orchestration-only and business logic in services.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-CORE-002", + "area": "core", + "title": "No direct service instantiation in pages", + "requirement": "MUST NOT instantiate Service, Gateway or Repository directly in pages.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-CORE-003", + "area": "core", + "title": "Request input contract", + "requirement": "MUST read input via requestInput in pages and avoid superglobals for request data.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-CORE-004", + "area": "core", + "title": "API json body contract", + "requirement": "MUST NOT use ApiResponse::readJsonBody in pages/api/v1.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-CORE-005", + "area": "security", + "title": "Server-side authz", + "requirement": "MUST enforce authorization server-side.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-CORE-006", + "area": "security", + "title": "Server-side tenant scope", + "requirement": "MUST enforce tenant scope server-side.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-CORE-007", + "area": "api", + "title": "OpenAPI sync", + "requirement": "MUST update OpenAPI in same merge when API changes.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-UI-001", + "area": "ui", + "title": "List initialization standard", + "requirement": "MUST initialize lists via initStandardListPage.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md" + }, + { + "id": "GR-UI-002", + "area": "ui", + "title": "No legacy filter toggle markup", + "requirement": "MUST NOT introduce legacy filter toggle attributes.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md" + }, + { + "id": "GR-UI-003", + "area": "ui", + "title": "Row interaction standard", + "requirement": "MUST preserve row action column plus enter and dblclick fallback behavior.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md" + }, + { + "id": "GR-UI-004", + "area": "ui", + "title": "Page size standard", + "requirement": "MUST preserve page size options and URL/localStorage/default precedence.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md" + }, + { + "id": "GR-UI-005", + "area": "ui", + "title": "Detail page standard", + "requirement": "MUST use standard detail page contracts for dirty-state and actions.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md" + }, + { + "id": "GR-UI-006", + "area": "ui", + "title": "No inline confirm", + "requirement": "MUST NOT use inline confirm handlers on standardized detail flows.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md" + }, + { + "id": "GR-UI-007", + "area": "ui", + "title": "Shared partial usage", + "requirement": "MUST use shared list and detail partials for standardized UI blocks.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md" + }, + { + "id": "GR-UI-016", + "area": "ui", + "title": "Template structure discipline for new features", + "requirement": "New pages MUST use the established layout system (templates/default.phtml or a named layout variant). UI blocks that appear in two or more pages MUST be extracted as a partial under templates/partials/ rather than duplicated inline. New partials MUST follow the naming convention app-{context}-{purpose}.phtml. Page-specific rendering belongs in pages/*.phtml, not in templates/. MUST NOT create ad-hoc layout HTML inside pages that bypasses the layout system.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md" + }, + { + "id": "GR-UI-008", + "area": "ui", + "title": "Template RBAC contract", + "requirement": "MUST use viewAuth in templates and avoid can calls.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md" + }, + { + "id": "GR-UI-009", + "area": "ui", + "title": "No a11y regression", + "requirement": "MUST NOT introduce regressions in accessibility basics (labels, roles, keyboard focus) on touched flows.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md" + }, + { + "id": "GR-CORE-008", + "area": "core", + "title": "No direct superglobal access in pages", + "requirement": "MUST NOT use $_SESSION, $_SERVER, $_COOKIE, $_ENV directly in pages; use framework abstractions (app(SessionStoreInterface::class), RequestContext, requestInput()) instead.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-SEC-001", + "area": "security", + "title": "CSRF verification on POST endpoints", + "requirement": "MUST verify CSRF token on every POST endpoint in pages before processing the request body.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-SEC-002", + "area": "security", + "title": "No unredacted PII or secrets in logs and audit metadata", + "requirement": "MUST NOT write PII (email, names, UUIDs) or secrets (tokens, passwords) to logs or audit metadata without prior redaction.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-SEC-003", + "area": "security", + "title": "SQL via Repository with prepared statements only", + "requirement": "MUST NOT build SQL strings with user-controlled input; all queries MUST go through Repository classes using prepared statements.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-LANG-001", + "area": "lang", + "title": "PSR-4 namespace alignment", + "requirement": "MUST align PHP namespace with directory path (MintyPHP\\ → lib/, MintyPHP\\Tests\\ → tests/); violations are caught by QG-003.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-LANG-002", + "area": "lang", + "title": "PHP style compliance", + "requirement": "MUST pass composer cs:check without diff (php-cs-fixer dry-run); fix with composer cs:fix before merge.", + "source": "tools/codex-skills/starterkit-php-style-ci/SKILL.md" + }, + { + "id": "GR-TEST-001", + "area": "test", + "title": "Tests ship with new logic", + "requirement": "New or changed business logic in lib/Service/ MUST be accompanied by PHPUnit tests in the same merge covering the happy path and at least one failure or edge case. No new public method in a Service or Gateway without a test.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-TEST-002", + "area": "test", + "title": "Code must be unit-testable", + "requirement": "New code MUST use constructor injection for all dependencies. MUST NOT introduce static side-effects in constructors, hidden global state, or direct DB/HTTP calls outside Repository/Gateway abstractions — patterns that make unit testing without mocks impossible.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-CORE-010", + "area": "core", + "title": "DB schema changes for existing installs must be idempotent SQL scripts in db/updates/", + "requirement": "Any schema or data change for existing installations MUST be shipped as an idempotent SQL script in db/updates/ (e.g. using IF NOT EXISTS, ON DUPLICATE KEY, or guarded ALTER TABLE). MUST NOT modify db/init/init.sql only — existing containers will not re-run init.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-CORE-011", + "area": "core", + "title": "New settings belong in the DB settings table, not in config files", + "requirement": "New application settings MUST be stored in the settings DB table as the single source of truth. config/settings.php is a partial hot-path cache only and MUST NOT be used as primary storage for new settings. Security, integration, and feature-flag settings MUST go through the settings gateway layer.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-CORE-012", + "area": "core", + "title": "POST-Redirect-GET after successful mutating actions", + "requirement": "Actions that successfully mutate state (create, update, delete) MUST respond with a redirect (PRG pattern) rather than rendering a response directly. This prevents double-submit on browser reload. Flash messages MUST be set before the redirect via Flash::set().", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-CORE-009", + "area": "core", + "title": "Repository list queries must use RepoQuery::sanitizeLimitOffset", + "requirement": "New Repository methods that return lists MUST use RepoQuery::sanitizeLimitOffset() to enforce LIMIT and OFFSET. Unbounded SELECT queries without LIMIT on potentially large tables are forbidden. The default maxLimit of 100 MUST NOT be raised without explicit justification reviewed and approved.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-SEC-006", + "area": "security", + "title": "User-uploaded files stored in storage/, never in web/", + "requirement": "Files uploaded by users MUST be stored under storage/ (e.g. storage/users/{uuid}/, storage/tenants/{uuid}/, storage/imports/) and MUST NOT be written to web/ or any other publicly served directory. Storage paths MUST use non-guessable identifiers (UUID v4 or equivalent) to prevent enumeration. Files MUST be served through a PHP controller that enforces authorization, not directly by the web server. MUST validate file type (MIME + extension allowlist) before storing.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-SEC-005", + "area": "security", + "title": "Encryption only via lib/Support/Crypto.php", + "requirement": "Any encryption or decryption of sensitive data MUST use the Crypto class (lib/Support/Crypto.php) which provides AES-256-GCM via APP_CRYPTO_KEY. MUST NOT use raw openssl_encrypt/openssl_decrypt, base64 encoding as a substitute for encryption, or any other hand-rolled crypto primitives.", + "source": "tools/codex-skills/core-guardrails/references/boundaries-core.md" + }, + { + "id": "GR-SEC-004", + "area": "security", + "title": "No external CDN or remote asset loading", + "requirement": "MUST NOT load JS, CSS, fonts, or any other assets from external CDNs (Cloudflare, jsDelivr, unpkg, Google Fonts, etc.) or remote URLs in templates, pages, or layout files. If a third-party library is required it MUST be vendored locally in the repository (e.g. web/js/vendor/ or web/css/vendor/) and served via assetVersion(). No @@ -53,6 +75,7 @@ if ($bufferTitle !== '') { + diff --git a/templates/partials/app-confirm-dialog.phtml b/templates/partials/app-confirm-dialog.phtml new file mode 100644 index 0000000..efeb1fb --- /dev/null +++ b/templates/partials/app-confirm-dialog.phtml @@ -0,0 +1,22 @@ + +
+
+

+ +
+

+ +
+ + +
+
+
diff --git a/templates/partials/app-details-aside-actions.phtml b/templates/partials/app-details-aside-actions.phtml index 49a88a3..5a8cd87 100644 --- a/templates/partials/app-details-aside-actions.phtml +++ b/templates/partials/app-details-aside-actions.phtml @@ -32,6 +32,11 @@ if (!$asideActions) { $itemMethod = strtoupper((string) ($item['method'] ?? 'POST')); $itemAction = (string) ($item['action'] ?? ''); $itemConfirm = (string) ($item['confirm'] ?? ''); + $itemConfirmTitle = (string) ($item['confirmTitle'] ?? ''); + $itemConfirmConfirmLabel = (string) ($item['confirmConfirmLabel'] ?? ''); + $itemConfirmCancelLabel = (string) ($item['confirmCancelLabel'] ?? ''); + $itemConfirmVariant = strtolower(trim((string) ($item['confirmVariant'] ?? ''))); + $itemConfirmFocus = strtolower(trim((string) ($item['confirmFocus'] ?? ''))); $itemButtonClass = (string) ($item['class'] ?? 'secondary outline small'); $itemNeedsCsrf = !array_key_exists('csrf', $item) ? true : !empty($item['csrf']); $itemTarget = (string) ($item['target'] ?? ''); @@ -45,6 +50,11 @@ if (!$asideActions) { action="" target="" data-detail-confirm-message="" + data-detail-confirm-title="" + data-detail-confirm-confirm-label="" + data-detail-confirm-cancel-label="" + data-detail-confirm-variant="" + data-detail-confirm-focus="" data-detail-action-kind="" data-detail-submit-lock="" > diff --git a/templates/partials/app-details-titlebar.phtml b/templates/partials/app-details-titlebar.phtml index 5229667..6d10b48 100644 --- a/templates/partials/app-details-titlebar.phtml +++ b/templates/partials/app-details-titlebar.phtml @@ -55,6 +55,11 @@ $menuItems = is_array($titlebar['menuItems'] ?? null) ? $titlebar['menuItems'] : $itemMethod = strtoupper((string) ($item['method'] ?? 'POST')); $itemAction = (string) ($item['action'] ?? ''); $itemConfirm = (string) ($item['confirm'] ?? ''); + $itemConfirmTitle = (string) ($item['confirmTitle'] ?? ''); + $itemConfirmConfirmLabel = (string) ($item['confirmConfirmLabel'] ?? ''); + $itemConfirmCancelLabel = (string) ($item['confirmCancelLabel'] ?? ''); + $itemConfirmVariant = strtolower(trim((string) ($item['confirmVariant'] ?? ''))); + $itemConfirmFocus = strtolower(trim((string) ($item['confirmFocus'] ?? ''))); $itemButtonClass = (string) ($item['class'] ?? 'transparent'); $itemNeedsCsrf = !array_key_exists('csrf', $item) ? true : !empty($item['csrf']); $itemTarget = (string) ($item['target'] ?? ''); @@ -68,6 +73,11 @@ $menuItems = is_array($titlebar['menuItems'] ?? null) ? $titlebar['menuItems'] : action="" target="" data-detail-confirm-message="" + data-detail-confirm-title="" + data-detail-confirm-confirm-label="" + data-detail-confirm-cancel-label="" + data-detail-confirm-variant="" + data-detail-confirm-focus="" data-detail-action-kind="" data-detail-submit-lock="" > @@ -118,6 +128,11 @@ $menuItems = is_array($titlebar['menuItems'] ?? null) ? $titlebar['menuItems'] : $itemName = (string) ($item['name'] ?? ''); $itemValue = (string) ($item['value'] ?? ''); $itemConfirm = (string) ($item['confirm'] ?? ''); + $itemConfirmTitle = (string) ($item['confirmTitle'] ?? ''); + $itemConfirmConfirmLabel = (string) ($item['confirmConfirmLabel'] ?? ''); + $itemConfirmCancelLabel = (string) ($item['confirmCancelLabel'] ?? ''); + $itemConfirmVariant = strtolower(trim((string) ($item['confirmVariant'] ?? ''))); + $itemConfirmFocus = strtolower(trim((string) ($item['confirmFocus'] ?? ''))); $itemDetailActionKind = strtolower(trim((string) ($item['detailActionKind'] ?? ''))); $itemDetailSubmitLock = strtolower(trim((string) ($item['detailSubmitLock'] ?? ''))); $itemDisabled = !empty($item['disabled']); @@ -144,6 +159,11 @@ $menuItems = is_array($titlebar['menuItems'] ?? null) ? $titlebar['menuItems'] : name="" value="" data-detail-confirm-message="" + data-detail-confirm-title="" + data-detail-confirm-confirm-label="" + data-detail-confirm-cancel-label="" + data-detail-confirm-variant="" + data-detail-confirm-focus="" data-detail-action-kind="" data-detail-submit-lock="" data-detail-save-primary="1" diff --git a/templates/partials/app-footer.phtml b/templates/partials/app-footer.phtml index a1c0dee..02063d5 100644 --- a/templates/partials/app-footer.phtml +++ b/templates/partials/app-footer.phtml @@ -1,5 +1,5 @@