Contracts
profile-contract.md
Contract: Drupal Dries Profile & Lineage
Mission: drupal-dries-profile-01M28X69
There is no HTTP or GraphQL surface in this mission. The contracts that matter are the loader contracts — the shapes the pack loader, DRG extractor, and validator accept, and the assertions that prove conformance. Each clause below is directly testable and is the acceptance criterion for its requirement.
C-P1 — The profile loads and is selectable
Requirement: FR-001, FR-013
GIVEN packs/built-in/agent_profiles/drupal-dries.agent.yaml exists
WHEN the agent-profile repository loads the built-in pack
THEN a profile with profile-id "drupal-dries" is present
AND it does NOT appear in AgentProfileRepository.skipped_profiles
AND its roles include "implementer"
AND `spec-kitty doctor doctrine --json` reports 0 skipped profiles
Negative clause — the guard that makes this non-vacuous:
GIVEN a deliberately malformed copy of the profile (in a fixture, not the shipped pack)
WHEN the repository loads it
THEN it appears in skipped_profiles
AND the doctor report does NOT describe the pack as healthy
FR-013 is about failure being visible. A test that only asserts the happy path would pass against a loader that silently swallows everything.
C-P2 — Lineage resolves to the implementer persona
Requirement: FR-005, C-002
GIVEN the regenerated DRG graph
WHEN the edges from agent_profile:drupal-dries are queried
THEN exactly one specializes_from edge exists, targeting agent_profile:implementer-ivan
AND resolve_profile traversal reaches implementer-ivan's inherited discipline
AND the specializes_from subgraph remains a DAG
Endpoint form: both endpoints must be <kind>:<id> with kind a NodeKind member. Any other token is refused at merge with unresolved_edge_endpoint. This is the failure mode CLAUDE.md documents as having once silently dropped a documented edge, so it is asserted explicitly rather than assumed.
C-P3 — Directive and tactic edges are minted from the profile
Requirement: FR-005
GIVEN the profile declares directive-references 010, 024, 025, 030, 034, 051
AND tactic-references dependency-hygiene, tdd-red-green-refactor,
supply-chain-install-safety, bug-fixing-checklist
WHEN the DRG extractor runs
THEN a requires edge exists from agent_profile:drupal-dries to each
AND every target URN resolves to an existing node
No edge in this clause is hand-authored. If one is missing, the profile's reference block is wrong — that is the diagnostic this contract is for.
C-P4 — Graph fragments are fresh
Requirement: NFR-002, R-002
GIVEN the repository's own pack and source, in a synced dev environment
WHEN `spec-kitty doctrine regenerate-graph --check` runs
THEN it exits 0
AND the pack it reports on is the repository's packs/built-in, NOT a globally installed copy
The second clause is load-bearing. A globally installed CLI reports freshness for the pack inside its own venv and exits 0 while saying nothing about the change under review — verified during Phase 0 (research.md R-003).
C-P5 — Node inventory rises by exactly four
Requirement: NFR-002, NFR-007
GIVEN the built-in pack before and after this mission
WHEN the filesystem-derived inventory is compared to the loaded graph
THEN node counts rise by exactly 4: 1 agent_profile, 2 styleguide, 1 toolguide
AND the glob-derived expectation and the graph agree
tests/doctrine/_builtin_inventory.py derives expectations by globbing source files, independently of the graph under test — so a loader that skips a shipped file reds rather than passing vacuously.
C-P6 — The boundary is reciprocal and non-contradictory
Requirement: FR-004, NFR-007, SC-004
GIVEN both the Dries and Frontend Freddy profiles
WHEN their avoidance-boundary declarations are read
THEN Dries defers generic browser component authorship to Frontend Freddy by name
AND Freddy defers Drupal-native theming to Drupal Dries by name
AND both express that Dries VERIFIES the theming JS it authored while Freddy
AUTHORS generic browser component work
AND no field of frontend-freddy.agent.yaml outside avoidance-boundary is modified
Judgement clause (SC-004): ten sample Drupal frontend tasks, assigned by a reader using only the two boundary declarations, resolve to exactly one profile each with no ties. Not machine- checkable; the honest form is a reviewer checklist, and it is recorded as such rather than dressed up as an automated assertion.
C-P7 — Activation stays opt-in
Requirement: FR-012, NFR-005, SC-006
GIVEN a project that has not activated drupal-dries
WHEN governance context is loaded for any action
THEN no Drupal material appears in it
AND the rendered context is byte-identical to its pre-mission content
GIVEN the same project
WHEN `charter activate agent-profile drupal-dries` runs
THEN the profile becomes available with no bespoke step
C-P8 — Provenance is present
Requirement: FR-010, C-003
GIVEN each delivered artifact
WHEN its references or attribution section is read
THEN it credits amazee.io drupal-agents-md (Vanilla variant) as the distillation source
AND no section reproduces the source verbatim
styleguide-toolguide-contract.md
Contract: Drupal Styleguides & Review-Checks Toolguide
Mission: drupal-dries-profile-01M28X69
Loader and content contracts for the four guidance artifacts. Schema conformance is necessary but not sufficient — a schema-valid file full of wrong Drupal advice passes every structural check, so the content clauses below carry equal weight.
C-S1 — Both styleguides load
Requirement: FR-006
GIVEN drupal-conventions.styleguide.yaml and drupal-security-performance.styleguide.yaml
WHEN the styleguide repository loads the built-in pack
THEN both are present, keyed by id
AND both validate against the styleguide schema
AND neither is reported as a load failure
Fields required by the peer shape (python-conventions, java-conventions): schema_version, id, title, scope, applies_to_languages, principles, patterns, plus tooling and references where applicable.
C-S2 — All 14 source anti-patterns are carried
Requirement: FR-008, SC-003
GIVEN drupal-conventions.styleguide.yaml
WHEN its patterns collection is read
THEN each of the 14 numbered "Never Do This" entries from the source guide is represented
AND each carries both a bad_example and a good_example
AND any omission is recorded with an explicit reason
The 14, for the implementer's checklist:
| # | Anti-pattern |
|---|---|
| 1 | \Drupal:: static calls in services, controllers, plugins |
| 2 | Direct database queries where Entity Query suffices |
| 3 | `\ |
| 4 | Monolithic hook_form_alter() |
| 5 | Configuration stored in state |
| 6 | #markup with unsanitized input |
| 7 | Entity queries without accessCheck(TRUE) |
| 8 | Hardcoded entity IDs, user IDs, paths |
| 9 | hook_views_data() without table aliases |
| 10 | Ignored cacheability metadata |
| 11 | settings.php committed with credentials |
| 12 | Deprecated procedural functions (node_load()) |
| 13 | Global $_GET / $_POST / $_SERVER |
| 14 | Business logic in .module files |
Entries 3, 6, 7, 10, 11 are security- or performance-shaped and may live in drupal-security-performance instead; the contract is that all 14 appear somewhere in the delivered styleguides, not that they all appear in one file.
C-S3 — The split is a real boundary
Requirement: FR-006, I5.2
GIVEN both styleguides
WHEN their patterns are compared
THEN no pattern name appears in both
AND each file stays within roughly 150-200 lines
AND conventions answers "how do I write this?"
while security-performance answers "how do I keep it safe and fast?"
A split that merely halves a long file by line count would satisfy the size goal and defeat its purpose. The disjointness clause is what makes it a boundary.
C-S4 — The toolguide descriptor matches its body
Requirement: FR-007
GIVEN drupal-review-checks.toolguide.yaml and DRUPAL_REVIEW_CHECKS.md
WHEN the descriptor is loaded
THEN guide_path resolves to the committed body file
AND every command in the descriptor's commands list appears in the body
AND every gate command in the body appears in the descriptor
Descriptor shape follows maven-review-checks.toolguide.yaml: schema_version, id, tool, title, guide_path, applies_to_languages, summary, commands.
C-S5 — Commands are environment-agnostic
Requirement: C-004, R-008
GIVEN the toolguide body and the profile's self-review protocol
WHEN their commands are read
THEN no command is prefixed for a specific environment (no bare `ddev`/`lando` prefix)
AND exactly one adaptation note explains how containerized setups prefix them
C-S6 — No invented thresholds
Requirement: NFR-006, R-007
GIVEN every delivered artifact
WHEN its quality gates are read
THEN no PHPStan level is asserted
AND no coverage percentage or mutation score is asserted
AND each named command traces to the source guide's Code Quality Tools
or Before Submitting Code sections
The source names the commands and specifies no level. Supplying one would be a fabrication presented as guidance, which is exactly what NFR-006 forbids.
C-S7 — Styleguide edges follow the peer precedent
Requirement: FR-006, R-002
GIVEN the regenerated styleguide graph fragment
WHEN edges from styleguide:drupal-conventions are queried
THEN a suggests edge targets agent_profile:drupal-dries
AND a suggests edge targets toolguide:drupal-review-checks
AND styleguide:drupal-security-performance likewise suggests the profile
Mirrors styleguide:java-conventions, which suggests agent_profile:java-jenny, toolguide:maven-review-checks, and its relevant directives.
C-S8 — Drupal correctness spot-checks
Requirement: NFR-006, SC-007
Content assertions a reviewer can verify against official Drupal documentation. These exist because schema validity says nothing about whether the advice is true:
| Claim | Must state |
|---|---|
accessCheck(TRUE) | Must be set explicitly on every entity query. Implicit access checking was deprecated in Drupal 9.2.0 and throws an error from Drupal 10.0.0 (change record node/3201242). On this profile's 10.x/11.x baseline an unchecked query is already a fatal error — NOT a deprecation warning, and NOT something deferred to Drupal 12. The upstream AGENTS.md states this incorrectly at its line 701; do not copy it. |
| Config vs state | Config for structured exportable settings; state for ephemeral/transient data |
| Twig escaping | Auto-escaping is the default; `\ |
| Dependency injection | \Drupal:: acceptable only in hook_ functions in .module files, and even there delegation is preferred |
| Cacheability | Render arrays depending on data must declare #cache tags, contexts, and max-age |
| Test tiers | PHPUnit unit, kernel, and functional tiers are distinct, with different bootstrap costs and capabilities |
Sampling rule for SC-007: a reviewer samples ten claims at random; all ten must trace to the source guide or official Drupal documentation.