Files
breadcrumb-the-shire/agent-system/checks/guard-catalog.json
fs c7b8fd516a feat: extend module platform with UI slots, runtime components, CLI tooling and {{userId}} search support
Completes the generic module platform that enables modules to contribute
UI elements, runtime JS components, and search resources without any
core hardcoding.

New generic UI slot types:
- topbar.right_item: module-contributed topbar buttons
- layout.body_end_template: module-contributed dialog/overlay templates
- layout.head_style: module-contributed global CSS
- runtime.component: declarative JS component registration with phase ordering

New infrastructure:
- ModuleAutoloader: PSR-4 autoloading for module-local PHP classes
- ModuleRuntimePageBuilder: symlinks module pages into runtime directory
- ModuleRuntimeAssetPublisher: publishes module CSS/JS to web/modules/
- ModulePermissionSynchronizer: syncs module permissions to DB
- CLI scripts: module-runtime-sync, module-build, module-migrate,
  module-permissions-sync, module-assets-sync
- {{userId}} placeholder in SearchDataService for user-scoped search queries
- Component runtime with phased initialization (early/default/late)
- AppContainer.protectExistingBindings() to prevent module→core overwrites
- Architecture tests: ModuleStructureContractTest, CoreTemplateIsolationTest,
  FrontendComponentRuntimeContractTest, AppContainerIsolationContractTest

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 22:19:56 +01:00

280 lines
17 KiB
JSON

{
"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 <script src='https://...'>, <link href='https://...'>, @import url('https://...'), or fetch() calls to external asset hosts.",
"source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md"
},
{
"id": "GR-UI-014",
"area": "ui",
"title": "UI state completeness for new views and components",
"requirement": "New user-facing views or components that display data or trigger actions MUST handle all four states: loading (progress indicator while data is fetched), empty (explicit message when no data exists), error (user-visible error feedback when an action or data load fails), and success (confirmation after a completed action). All four states MUST be defined in the plan before implementation starts. Applies to new views and components; not required for small, isolated fixes to existing UI.",
"source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md"
},
{
"id": "GR-UI-013",
"area": "ui",
"title": "A11y by design: new interactive components built accessible from the start",
"requirement": "New interactive UI components (buttons, links, forms, modals, dropdowns, custom controls) MUST use semantic HTML elements. MUST be fully keyboard-operable (Tab order, Enter/Space to activate, Escape to close overlays, no keyboard trap). MUST use ARIA attributes only where semantic HTML is insufficient — no ARIA-spam. MUST meet WCAG 2.1 AA color contrast for any newly introduced text or icon elements. These requirements apply at creation time, not as a post-hoc retrofit.",
"source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md"
},
{
"id": "GR-UI-015",
"area": "ui",
"title": "i18n key completeness: all language files updated in the same merge",
"requirement": "Every new translation key added via t('key') MUST be present in ALL language files (i18n/default_de.json and i18n/default_en.json) in the same merge. A key present in one file but missing in another causes visible fallback output. MUST NOT leave placeholder or empty string values without a TODO comment.",
"source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md"
},
{
"id": "GR-UI-010",
"area": "ui",
"title": "i18n: all visible text must use t()",
"requirement": "MUST wrap all user-visible strings in phtml views with t('key'). MUST NOT hardcode display strings in templates or partials.",
"source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md"
},
{
"id": "GR-UI-011",
"area": "ui",
"title": "JS reuse-first: prefer existing exports over new functions",
"requirement": "MUST check web/js/core/ and web/js/components/ for existing exported functions before adding a new one. A new export is only justified when no existing function covers the use-case after review. MUST NOT duplicate logic already present in app-dom.js, app-grid-factory.js, app-detail-page-factory.js, or other core modules.",
"source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md"
},
{
"id": "GR-UI-012",
"area": "ui",
"title": "CSS reuse-first: prefer existing classes over new rules",
"requirement": "MUST check existing CSS layers (base, components, layout, pages) for suitable classes before writing new rules. MUST use existing app-* component classes as the basis and only add additive overrides. MUST NOT duplicate declarations already defined in shared component or layout files.",
"source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md"
},
{
"id": "GR-UI-017",
"area": "ui",
"title": "Component lifecycle contract for migrated UI modules",
"requirement": "Components migrated to the lifecycle runtime MUST expose an init(root, config) entrypoint that returns an API with destroy(). Migrated component modules MUST NOT auto-initialize themselves via DOMContentLoaded/module side effects and MUST NOT retain legacy wrapper entrypoints or init alias exports. Event listeners, timers, and observers created during init MUST be cleaned up in destroy(). Runtime configuration MUST come from page-config payloads (readPageConfig) instead of scattered ad-hoc globals. Page-level initialization MUST be loaded via web/js/pages/* entry modules, not js/components/* script loads. Host-bound UI components (including tabs) MUST provide explicit data-app-component hosts in markup and remain root-strict (no document.querySelector fallback paths). Global utilities without a natural host MAY be runtime-registered with scope=global and MUST still satisfy the same lifecycle/destroy contract. Project-specific custom window.* globals are forbidden; cross-component communication MUST use core channel modules. Migrated UI storage MUST use the shared namespaced storage contract without legacy key fallback/sync paths.",
"source": "tools/codex-skills/core-guardrails/references/boundaries-ui.md"
}
]
}