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
2Direct database queries where Entity Query suffices
3`\
4Monolithic hook_form_alter()
5Configuration stored in state
6#markup with unsanitized input
7Entity queries without accessCheck(TRUE)
8Hardcoded entity IDs, user IDs, paths
9hook_views_data() without table aliases
10Ignored cacheability metadata
11settings.php committed with credentials
12Deprecated procedural functions (node_load())
13Global $_GET / $_POST / $_SERVER
14Business 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:

ClaimMust 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 stateConfig for structured exportable settings; state for ephemeral/transient data
Twig escapingAuto-escaping is the default; `\
Dependency injection\Drupal:: acceptable only in hook_ functions in .module files, and even there delegation is preferred
CacheabilityRender arrays depending on data must declare #cache tags, contexts, and max-age
Test tiersPHPUnit 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.