chore(agents): implement v2 hardening and enforcement-ready QA
This commit is contained in:
@@ -10,6 +10,7 @@ contracts/ ← JSON schemas for role outputs (7 roles)
|
||||
templates/ ← Starter JSON payloads per role (7 templates)
|
||||
prompts/ ← Role-specific prompt baselines (7 prompts)
|
||||
checks/ ← Guard catalog (22 guards), quality gates (9), checklists
|
||||
← Enforcement policy + guard enforcement map
|
||||
skills/ ← Reusable skill packages (guardrails, planner, grid, PHP CI)
|
||||
runs/ ← Runtime artifacts per workflow run (gitignored)
|
||||
```
|
||||
@@ -33,6 +34,8 @@ runs/ ← Runtime artifacts per workflow run (gitignored)
|
||||
| Workflow & roles | `workflow.md` |
|
||||
| Guard catalog (22 guards) | `checks/guard-catalog.json` |
|
||||
| Quality gates (9 gates) | `checks/quality-gates.json` |
|
||||
| Enforcement policy | `checks/enforcement-policy.json` |
|
||||
| Guard enforcement map | `checks/guard-enforcement-map.json` |
|
||||
| Code review checklist | `checks/guard-checklist.md` |
|
||||
| Acceptance checklist | `checks/acceptance-checklist.md` |
|
||||
| Module manifest schema | `contracts/module-manifest.schema.json` |
|
||||
|
||||
77
.agents/checks/enforcement-policy.json
Normal file
77
.agents/checks/enforcement-policy.json
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"phase": "enforcement-ready",
|
||||
"full_workflow_required_when": [
|
||||
{
|
||||
"id": "risk-security",
|
||||
"description": "Any change touching authentication, authorization, CSRF, encryption, logging redaction, file uploads, or API bootstrap/security behavior.",
|
||||
"paths": [
|
||||
"lib/Http/**",
|
||||
"lib/Service/Access/**",
|
||||
"lib/Support/Crypto.php",
|
||||
"pages/api/**",
|
||||
"pages/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "risk-data-boundary",
|
||||
"description": "Any change touching tenant-scoped data access, repository query boundaries, DB schema updates, or API contracts.",
|
||||
"paths": [
|
||||
"lib/Repository/**",
|
||||
"db/updates/**",
|
||||
"db/init/init.sql",
|
||||
"docs/openapi.yaml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "risk-modules",
|
||||
"description": "Any change touching module manifests, module runtime wiring, module routes, module providers, or module migrations/assets.",
|
||||
"paths": [
|
||||
"modules/*/module.php",
|
||||
"lib/App/Module/**",
|
||||
"storage/runtime/pages/**",
|
||||
"web/modules/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "risk-cross-layer",
|
||||
"description": "Any feature/addition changing more than one architecture layer (Repository, Service, pages/templates/web, modules, config, db).",
|
||||
"paths": [
|
||||
"lib/**",
|
||||
"pages/**",
|
||||
"templates/**",
|
||||
"web/**",
|
||||
"modules/**",
|
||||
"config/**",
|
||||
"db/**"
|
||||
]
|
||||
}
|
||||
],
|
||||
"quick_fix_allowlist": [
|
||||
"single-file typo fix without behavior change",
|
||||
"single-file translation text/key correction without flow change",
|
||||
"single-file CSS visual adjustment without behavioral JS or backend change",
|
||||
"single-file docs-only update"
|
||||
],
|
||||
"gate_policy": {
|
||||
"required_gates_always": [
|
||||
"QG-001",
|
||||
"QG-002",
|
||||
"QG-003",
|
||||
"QG-006",
|
||||
"QG-008",
|
||||
"QG-009"
|
||||
],
|
||||
"manual_non_blocking": [
|
||||
"QG-005"
|
||||
],
|
||||
"periodic_non_blocking": [
|
||||
"QG-007"
|
||||
]
|
||||
},
|
||||
"ci_rollout": {
|
||||
"target_platform": "gitea",
|
||||
"blocking_enabled": false,
|
||||
"phase_2_activation": "Enable required status checks in repository branch protection once qa-required is stable on runner infrastructure."
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,10 @@ Guard IDs live in `.agents/checks/guard-catalog.json` (guards where `reviewer` =
|
||||
- `GR-UI-REUSE` existing JS/CSS checked before adding new functions/rules
|
||||
|
||||
## Quality Gates
|
||||
- `QG-001` to `QG-003` are run unless explicitly out of scope
|
||||
- required always (per `enforcement-policy.json`): `QG-001`, `QG-002`, `QG-003`, `QG-006`, `QG-008`, `QG-009`
|
||||
- `QG-004` structural checks reported when relevant
|
||||
- `QG-005` JS smoke for JS-touching changes
|
||||
- `QG-006` PHP style check for PHP-touching changes
|
||||
- `QG-007` composer-unused is periodic / non-blocking
|
||||
|
||||
# Security Review Checklist
|
||||
|
||||
|
||||
173
.agents/checks/guard-enforcement-map.json
Normal file
173
.agents/checks/guard-enforcement-map.json
Normal file
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"entries": [
|
||||
{
|
||||
"guard_id": "GR-CORE-003",
|
||||
"enforcement_mode": "automated",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/CoreStarterkitContractTest.php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-CORE-007",
|
||||
"enforcement_mode": "hybrid",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/ApiSchemaContractTest.php",
|
||||
".agents/runs/<TASK>/review-code.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-CORE-010",
|
||||
"enforcement_mode": "automated",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/DatabaseUpdatesContractTest.php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-CORE-011",
|
||||
"enforcement_mode": "reviewer",
|
||||
"evidence_source": [
|
||||
".agents/runs/<TASK>/review-code.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-CORE-012",
|
||||
"enforcement_mode": "automated",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/PostRedirectGetContractTest.php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-TEST-001",
|
||||
"enforcement_mode": "reviewer",
|
||||
"evidence_source": [
|
||||
".agents/runs/<TASK>/review-code.json",
|
||||
".agents/runs/<TASK>/review-acceptance.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-TEST-002",
|
||||
"enforcement_mode": "hybrid",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/CoreStarterkitContractTest.php",
|
||||
".agents/runs/<TASK>/review-code.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-UI-014",
|
||||
"enforcement_mode": "reviewer",
|
||||
"evidence_source": [
|
||||
".agents/runs/<TASK>/review-code.json",
|
||||
".agents/runs/<TASK>/review-acceptance.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-UI-LIST",
|
||||
"enforcement_mode": "automated",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/ListDataEndpointContractTest.php",
|
||||
"tests/Architecture/ListTemplateContractTest.php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-UI-DETAIL",
|
||||
"enforcement_mode": "automated",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/DetailPageRuntimeContractTest.php",
|
||||
"tests/Architecture/DetailActionPolicyRuntimeContractTest.php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-UI-A11Y",
|
||||
"enforcement_mode": "automated",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/AuthLoginTemplateAccessibilityContractTest.php",
|
||||
"tests/Architecture/AuthLoginFieldAccessibilityContractTest.php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-UI-I18N",
|
||||
"enforcement_mode": "automated",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/I18nKeyCompletenessContractTest.php",
|
||||
"tests/Architecture/ModuleI18nContractTest.php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-UI-REUSE",
|
||||
"enforcement_mode": "hybrid",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/CoreStarterkitContractTest.php",
|
||||
".agents/runs/<TASK>/review-code.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-SEC-001",
|
||||
"enforcement_mode": "automated",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/PostEndpointCsrfContractTest.php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-SEC-002",
|
||||
"enforcement_mode": "automated",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/SecurityLoggingRedactionContractTest.php",
|
||||
"tests/Architecture/SecurityLoggingTelemetryContractTest.php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-SEC-003",
|
||||
"enforcement_mode": "reviewer",
|
||||
"evidence_source": [
|
||||
".agents/runs/<TASK>/review-security.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-SEC-004",
|
||||
"enforcement_mode": "automated",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/SecurityRemoteAssetContractTest.php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-SEC-005",
|
||||
"enforcement_mode": "automated",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/SecurityCryptoContractTest.php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-SEC-006",
|
||||
"enforcement_mode": "hybrid",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/FileStorageContractTest.php",
|
||||
".agents/runs/<TASK>/review-security.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-SEC-007",
|
||||
"enforcement_mode": "automated",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/ApiSessionIsolationContractTest.php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-SEC-008",
|
||||
"enforcement_mode": "hybrid",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/AuthzAdminUsersContractTest.php",
|
||||
"tests/Architecture/AuthzApiBootstrapContractTest.php",
|
||||
".agents/runs/<TASK>/review-security.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guard_id": "GR-SEC-009",
|
||||
"enforcement_mode": "hybrid",
|
||||
"evidence_source": [
|
||||
"tests/Architecture/ApiResourceContractTest.php",
|
||||
".agents/runs/<TASK>/review-security.json"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "module-manifest.schema.json",
|
||||
"$id": "https://corecore.local/schemas/module-manifest.schema.json",
|
||||
"title": "Module Manifest Schema",
|
||||
"description": "Declarative schema for module.php manifest files under modules/<id>/.",
|
||||
"type": "object",
|
||||
@@ -61,40 +61,53 @@
|
||||
"default": []
|
||||
},
|
||||
"ui_slots": {
|
||||
"type": "object",
|
||||
"description": "Keyed by slot name (e.g. 'aside.tab_panel'). Values are arrays of contribution objects.",
|
||||
"patternProperties": {
|
||||
"^.+$": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["key"],
|
||||
"properties": {
|
||||
"key": { "type": "string", "minLength": 1 },
|
||||
"label": { "type": "string" },
|
||||
"icon": { "type": "string" },
|
||||
"href": { "type": "string" },
|
||||
"permission": { "type": "string" },
|
||||
"panel_template": { "type": "string" },
|
||||
"template": { "type": "string" },
|
||||
"path": { "type": "string" },
|
||||
"script": { "type": "string" },
|
||||
"export": { "type": "string" },
|
||||
"selector": { "type": "string" },
|
||||
"scope": { "type": "string" },
|
||||
"config_path": { "type": "string" },
|
||||
"default_config": { "type": "object" },
|
||||
"phase": { "type": "string", "enum": ["early", "default", "late"] },
|
||||
"type": { "type": "string" },
|
||||
"order": { "type": "integer", "default": 100 },
|
||||
"details_storage": { "type": "string" },
|
||||
"details_open_active": { "type": "boolean" },
|
||||
"base_url": { "type": "string" },
|
||||
"group": { "type": "string", "description": "Sidebar nav group key for grouping related items under a details/summary section." }
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.+$": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["key"],
|
||||
"properties": {
|
||||
"key": { "type": "string", "minLength": 1 },
|
||||
"label": { "type": "string" },
|
||||
"icon": { "type": "string" },
|
||||
"href": { "type": "string" },
|
||||
"permission": { "type": "string" },
|
||||
"panel_template": { "type": "string" },
|
||||
"template": { "type": "string" },
|
||||
"path": { "type": "string" },
|
||||
"script": { "type": "string" },
|
||||
"export": { "type": "string" },
|
||||
"selector": { "type": "string" },
|
||||
"scope": { "type": "string" },
|
||||
"config_path": { "type": "string" },
|
||||
"default_config": {
|
||||
"anyOf": [
|
||||
{ "type": "object" },
|
||||
{ "type": "array", "maxItems": 0 }
|
||||
]
|
||||
},
|
||||
"phase": { "type": "string", "enum": ["early", "default", "late"] },
|
||||
"type": { "type": "string" },
|
||||
"order": { "type": "integer", "default": 100 },
|
||||
"details_storage": { "type": "string" },
|
||||
"details_open_active": { "type": "boolean" },
|
||||
"base_url": { "type": "string" },
|
||||
"group": { "type": "string", "description": "Sidebar nav group key for grouping related items under a details/summary section." }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"maxItems": 0
|
||||
}
|
||||
},
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"search_resources": {
|
||||
@@ -103,14 +116,22 @@
|
||||
"default": []
|
||||
},
|
||||
"asset_groups": {
|
||||
"type": "object",
|
||||
"description": "Asset group name → file list.",
|
||||
"patternProperties": {
|
||||
"^.+$": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.+$": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
"maxItems": 0
|
||||
}
|
||||
},
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"layout_context_providers": {
|
||||
@@ -136,8 +157,8 @@
|
||||
"properties": {
|
||||
"key": { "type": "string", "minLength": 1 },
|
||||
"description": { "type": "string", "minLength": 1 },
|
||||
"active": { "type": "boolean", "default": true },
|
||||
"is_system": { "type": "boolean", "default": true }
|
||||
"active": { "type": ["boolean", "integer"], "default": true },
|
||||
"is_system": { "type": ["boolean", "integer"], "default": true }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
@@ -177,22 +198,30 @@
|
||||
"default": []
|
||||
},
|
||||
"event_listeners": {
|
||||
"type": "object",
|
||||
"description": "Event name → list of listener descriptors.",
|
||||
"patternProperties": {
|
||||
"^.+$": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["class", "method"],
|
||||
"properties": {
|
||||
"class": { "type": "string", "minLength": 1, "description": "FQCN implementing EventListener." },
|
||||
"method": { "type": "string", "minLength": 1 }
|
||||
},
|
||||
"additionalProperties": false
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.+$": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["class", "method"],
|
||||
"properties": {
|
||||
"class": { "type": "string", "minLength": 1, "description": "FQCN implementing EventListener." },
|
||||
"method": { "type": "string", "minLength": 1 }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"maxItems": 0
|
||||
}
|
||||
},
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"deactivation_handler": {
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
1. `docker compose exec php vendor/bin/phpunit`
|
||||
2. `docker compose exec php vendor/bin/phpstan analyse -c phpstan.neon --no-progress`
|
||||
3. `docker compose exec php vendor/bin/phpunit tests/Architecture/CoreStarterkitContractTest.php`
|
||||
4. `docker compose exec php vendor/bin/php-cs-fixer fix --config=tools/php-cs-fixer/.php-cs-fixer.dist.php --dry-run --diff --verbose`
|
||||
5. `bin/docs-link-check.sh`
|
||||
6. `bin/codex-skills-sync.sh --check`
|
||||
|
||||
Policy-Hinweis:
|
||||
- Verbindliche Enforcement-Einteilung (`required`, `manual_non_blocking`, `periodic_non_blocking`) wird zentral in `/.agents/checks/enforcement-policy.json` gepflegt.
|
||||
|
||||
## Struktur-Gates (schnell)
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ Primary goals:
|
||||
Source of truth:
|
||||
- Guard catalog (22 guards): `.agents/checks/guard-catalog.json`
|
||||
- Quality gates (9 gates): `.agents/checks/quality-gates.json`
|
||||
- Enforcement policy (workflow trigger + gate enforcement): `.agents/checks/enforcement-policy.json`
|
||||
- Guard enforcement map (guard -> automated/hybrid/reviewer evidence): `.agents/checks/guard-enforcement-map.json`
|
||||
- Contracts: `.agents/contracts/`
|
||||
|
||||
---
|
||||
@@ -81,6 +83,17 @@ analyzed → planned → executing → reviewing → finalize → done
|
||||
The reviewing state runs Code Reviewer, Security Reviewer, and Acceptance Tester.
|
||||
If any reviewer returns `FAIL`, state goes back to `executing` with explicit findings.
|
||||
|
||||
## Workflow Trigger Policy
|
||||
|
||||
Workflow triggering is risk-based and centrally defined in:
|
||||
- `.agents/checks/enforcement-policy.json` (`full_workflow_required_when`)
|
||||
- `.agents/checks/enforcement-policy.json` (`quick_fix_allowlist`)
|
||||
|
||||
Interpretation:
|
||||
- Full workflow is mandatory for risk/security/data-boundary/module/cross-layer changes
|
||||
- Quick fixes are only exempt if they match the explicit allowlist
|
||||
- `>1 layer` remains a useful heuristic but is no longer the sole trigger
|
||||
|
||||
## Handover Artifacts
|
||||
|
||||
| Role | Artifact |
|
||||
@@ -123,3 +136,5 @@ The following concerns are covered by QG-001/QG-003/QG-004 and do NOT need manua
|
||||
- Repository sanitizeLimitOffset (RepositoryInterfaceContractTest)
|
||||
- List init standard, legacy filter toggle, template RBAC (architecture tests)
|
||||
- Component lifecycle contract (architecture tests)
|
||||
- Guard reference integrity (`GR-*`/`QG-*`) across docs/agents/tests/bin
|
||||
- Guard enforcement mapping completeness and automated evidence file existence
|
||||
|
||||
Reference in New Issue
Block a user