Contracts
doctrine-integrity-gates.md
Contract: Doctrine-Integrity Gates (acceptance)
The landed set must satisfy every gate below. These are the executable DoD for WS1 + the validation half of WS2. Run from the repo root with the isolated env active.
G-1 — Surface (SC-001, C-001)
asset pairs + READMEs exist under packs/built-in/… at the paths in data-model.md §1.
- Assert:
git ls-files 'src/doctrine//built-in/'returns empty; all 16 YAML + 5 - Command:
find packs/built-in -path 'writing' -o -name 'comms-cleo.agent.yaml'… (spot-check) +git grep -l 'built-in/built-in' || true(must be empty).
G-2 — Validation (SC-002, NFR-003)
resolves once assets are in place.
spec-kitty doctrine pack validate packs/built-in → OK.
- Assert: every new artifact passes schema validation; the
type: assettactic reference - Command:
spec-kitty doctrine validate <each new artifact>→ 0 errors; and
G-3 — Doctor health (SC-002, NFR-001)
pack discovered_count == valid_count == 25, invalid_profiles == [], skipped_profiles contains none of the 7 new ids, org_drg.errors == [], exit 0.
- Assert:
spec-kitty doctor doctrine --json→profile_health.healthy == true, builtin
G-4 — Shipped-profiles gate (SC-002, D-04)
6-field contract (canonical-verbs, output-artifacts, mode-defaults+use-case, doctrine-layers, directive-references) + ≥1 role; both profile READMEs list exactly the 25 shipped ids.
- Assert:
EXPECTED_PROFILE_IDScontains the 7 new ids (25 total); all 7 satisfy the - Command:
pytest tests/doctrine/test_shipped_profiles.py -q→ green.
G-5 — DRG freshness & reachability (SC-003, NFR-001, C-002)
pytest tests/doctrine/drg/test_reachability.py -q → green (frozensets recomputed empirically; a moved pin carries a ledger row).
- Assert: committed fragments are fresh; every new node is reachable (no new orphan).
- Command:
spec-kitty doctrine regenerate-graph --check→ exit 0 (no staleness);
G-6 — Pack relocation doctor gate (SC-001, D-05)
post-regenerate measured values; glossary term count still 108.
- Assert:
EXPECTED_PROFILE_COUNT == 25; the(node_count, edge_count)tuple equals the - Command:
pytest tests/doctrine/test_pack_relocation_doctor_gate.py -q→ green.
G-7 — Terminology & no-greenwash (C-004)
- Assert: new prose obeys the Terminology Canon; assets stay
asset(no relabel). - Command:
pytest tests/architectural/test_no_legacy_terminology.py -q→ green.
G-8 — Full suite on CI (SC-006)
The full tests/ suite is the CI release authority and must be green before the operator merges. Not run in-session.
- The targeted gates above run locally per-WP (charter Testing Requirements — targeted only).
routing-behavior.md
Contract: Routing Behavior (FR-005, NFR-002, SC-004)
Preserve legacy dispatch.
Required test form (non-fakeable): the regression MUST assert against the shipped profiles — construct a ProfileRegistry populated from the real packs/built-in/agent_profiles/ YAML (not fixture stubs, not MagicMock), so the test pins the narrowed YAML and not merely the router mechanic. The _make_mock_registry([...]) form shown below and in research D-03 is illustrative of the mechanic only — a mock hand-feeds role and priority, so it passes regardless of the shipped YAML and MUST NOT be the sole coverage. Each scenario also asserts the shipped profile.role (e.g. diagram-daisy.role == "diagram-author") so a mock cannot satisfy it.
R-1 — Incumbent designer preserved (negative regression; RED-first)
(role designer, priority 60) and no discriminating context,
diagram-daisy — the test fails. After the narrowing (roles[0]=diagram-author) it passes.
- Given a registry with
designer-dagmar(roledesigner, priority 50) anddiagram-daisy - When
route("design the login screen"), - Then
decision.profile_id == "designer-dagmar". - RED-first: on the base (diagram-daisy still
roles[0]=designer) this returns
R-2 — Incumbent curator preserved (negative regression; RED-first)
role curator, no context,
- Given
curator-carla@40,doctrine-daphne@48,comms-cleo@55,synthesizer-sam@50 all - When
route("classify these documents"), - Then
decision.profile_id in {"curator-carla", "doctrine-daphne"}(an incumbent). - RED-first: on the base returns
comms-cleo; after narrowing cleo+sam leave the bucket → passes.
R-3 — New profile still routes for its own scope (positive guard)
- Given
diagram-daisynarrowed todiagram-authorwith its domain keywords, - When a request explicitly names diagram-as-code / the profile hint,
- Then
diagram-daisyis selected — narrowing did not strand it.
R-4 — No unintended re-collision from canonical-verbs (guard)
assert the narrowed profiles do NOT re-enter the DESIGNER/CURATOR buckets via a generic verb.
- The
canonical-verbsadded for the shipped-profiles contract (D-04) must be domain-specific;
R-5 — Researcher non-collision (positive documentation; SC-004)
comms-cleo/synthesizer-sam do NOT collide on researcher (it is their secondary role). This proves the D-03 sharpening rather than leaving SC-004's researcher clause unmapped.
- Given a bare researcher verb (e.g.
route("research the market")) with no context, - Then an incumbent researcher (
researcher-robbie) is selected — documenting that
Shipped-registry loading (required)
Populate a ProfileRegistry from the real packs/built-in/agent_profiles/ set (copy the shipped YAML into the registry dir — do NOT reuse FIXTURES_DIR stems, which _make_registry defaults to). Assert the registry actually contains diagram-daisy/comms-cleo/synthesizer-sam loaded from packs/built-in, and assert their narrowed profile.role values, so neither a mock nor a stale fixture can pass the regression.