# DGR-018 evidence — canonical Ralph and Gitea metadata schema **Completed:** 2026-07-16 **Branch:** `ralph/distributed-gguf-runtime` **Authority:** `.scratch/distributed-gguf-runtime/prd.json` **Dependency:** DGR-017 (`evidence/DGR-017/README.md`) — cleaned backlog reconciled to `origin/master`; no old pass state transferred. ## Objective Make `prd.json` the validated source from which Markdown (and, later, Gitea) issues can be generated losslessly, per `.scratch/distributed-gguf-runtime/issues/018-define-canonical-ralph-and-gitea-metadata-schema.md`. ## Pre-existing state found (not caused by this story) Before any change in this session, `git status` showed `.scratch/distributed-gguf-runtime/prd.json` already modified in the working tree relative to `HEAD` (commit `369b207`), with no corresponding progress-log entry. Diffing against `HEAD` showed the working copy had **dropped** prd.json's top-level `sourceOfTruth`, `qualityGates`, `metadataSchema`, `milestones`, and `supersededStories` objects, while `userStories` itself was byte-identical to `HEAD`. This looked like an abandoned, uncommitted partial edit from a prior session, not intentional current work — those fields are exactly the schema/quality-gate/audit-provenance content this story depends on, and their loss wasn't explained by any acceptance criterion. They were restored (see "Changes" below) rather than silently accepted or discarded, per the instruction to investigate unexplained working-tree state before building on top of it. ## Changes ### `scripts/ralph_prd_schema.py` (new) Single module providing: - **Parse:** `load_prd(path)` — JSON load with clear `PrdValidationError`s for missing file / invalid JSON / non-object document. - **Canonical schema registry:** `STORY_FIELDS` (name → required/type), `EXECUTION_MODES`, `EVIDENCE_CLASSES`, `HARDWARE_FLAGS`, `UPSTREAM_FLAGS`, `TRIAGE_VALUES`. Covers every field named in the acceptance criteria: stable `id`/`title`, `labels`, `milestone`, derived `type` (`derive_type`), `dependsOn`, derived `blocks`, `triage`, `evidenceClass`, and `hardware`/`model`/`upstream` flags. - **Structural validation:** `validate_schema(data)` — required fields, types, enum membership, ID convention, `type:`/`priority:` label cardinality, non-empty `acceptanceCriteria`. - **Semantic validation:** `validate_semantics(data)` — unique IDs, unique titles, `dependsOn` resolves to known stories (no self-dependency), dependency graph is acyclic (with a reported cycle path on failure), `blocks` matches the dependency graph exactly (sorted set equality, not superset), and `evidencePath` matches the per-story convention. - **Fresh vs. in-progress backlog:** `validate_fresh_backlog(data)` additionally requires every story to start `passes: false` (for a backlog that hasn't started execution yet); `validate_backlog(data)` is the composed check for a real, in-flight backlog where some stories have legitimately completed. - **Self-consistency check:** `validate_metadata_schema_consistency(data)` — when prd.json declares its own `metadataSchema`/`qualityGates` (as this one now does), verifies that self-documentation hasn't drifted from what the validator actually enforces (enum sets, required/optional field lists, presence of `qualityGates` and `generatedArtifactDisclaimer`). This is a no-op for minimal fixture PRDs that don't carry that documentation. - **Generation (one-directional, prd.json → artifact):** `render_issue_markdown(story, data)` renders the exact Markdown convention already used by `.scratch/distributed-gguf-runtime/issues/*.md`, sourcing the "Shared quality gates" bullets from `data["qualityGates"]` and the leading disclaimer from `data["metadataSchema"]["generatedArtifactDisclaimer"]` (falling back to a module default only when `data` omits them) — not from a duplicated Python string literal. `to_gitea_issue_payload(story, data)` wraps the same body into a Gitea create-issue-shaped payload (`title`, `body`, `labels`, `milestone`). - **Authority guard:** `check_generated_markdown_authority(text, disclaimer=...)` rejects generated Markdown that's missing the disclaimer or that contains a conflicting authority claim (e.g. "this file is authoritative"). There is deliberately no Markdown → prd.json parser, so a generated artifact structurally cannot feed `passes` (or anything else) back into the authoritative source. - CLI: `python scripts/ralph_prd_schema.py validate [--fresh]` and `... render `. ### `.scratch/distributed-gguf-runtime/prd.json` - Restored the top-level `sourceOfTruth`, `qualityGates`, `milestones`, and `supersededStories` objects to their `HEAD` content (see "Pre-existing state" above); `userStories` was already identical to `HEAD` and is unchanged in content. - Extended `metadataSchema` (previously incomplete for this story's own acceptance criteria) with: `requiredStoryFields` now also lists `notes` and `blocks` (present on all 55 stories); new `optionalStoryFields: ["completionNotes"]`; new `hardwareValues`/`upstreamValues` enums (`model` is documented as an open convention, not a closed enum, since quantization/model targets are dynamic recipe inputs per `RALPH-CONTEXT.md`); new `typeDerivation` and `labelConventions` (reserved prefixes, cardinality); new `generatedArtifactDisclaimer` (the exact string generated artifacts must start with); extended `dependencyRules`/`authorityRule` prose to match what the validator enforces. - Reworded `sourceOfTruth`'s stale "All stories are unimplemented ... passes=false" clause, which was no longer accurate once DGR-017 completed. - Marked `DGR-018.passes = true` with `completionNotes` recording this story's outcome. ### `.scratch/distributed-gguf-runtime/issues/018-define-canonical-ralph-and-gitea-metadata-schema.md` Regenerated via `render_issue_markdown` to reflect `passes: true` (checked acceptance criteria, "completed" status line, "Verified evidence" handoff line) — matching the same convention DGR-017's issue file already used for a completed story. ### `tests/test_ralph_prd_schema.py` (new) 108 deterministic, model-download-free, GPU-free tests: - **Parse** (4 tests): real backlog parses to 55 stories; missing file, invalid JSON, and non-object documents raise `PrdValidationError`. - **Structural/semantic validation against the real backlog** (7 tests): passes `validate_schema`, `validate_semantics`, and the composed `validate_backlog`; unique IDs/titles; all `dependsOn` resolve; `blocks` matches the derived dependency graph for all 55 stories; no cycle; every `passes: true` story carries `completionNotes` and an existing evidence README (a durable invariant, not a hardcoded list of which stories have completed — that list will keep growing). - **Structural/semantic failure-mode fixtures** (13 tests): missing required field, bad enum, wrong type, empty `acceptanceCriteria`, multiple `type:` labels, duplicate ID, duplicate title, unknown dependency, self-dependency, dependency cycle, mismatched `blocks`, bad `evidencePath`. - **Fresh-backlog invariant** (3 tests): accepts all-`false`, rejects a premature `passes: true`, and confirms `validate_backlog` (the in-progress variant) permits completed stories. - **prd.json-as-source-of-truth for boilerplate** (9 tests): `qualityGates`/`metadataSchema` self-consistency checks (no-op without them, catches a drifted enum, catches a missing `qualityGates`), `quality_gate_bullets` flattening order, `authority_disclaimer` precedence and fallback, and 3 tests asserting the real backlog's declared schema matches the code, its 7 quality-gate bullets are intact, and its disclaimer matches the module default. - **`derive_type`** (4 tests): label-derived type, release-gate synthetic type for HITL gate stories, `None` when absent, and confirmation that the real backlog's two release-gate stories (`DGR-054`, `DGR-070`) derive `release-gate`. - **Markdown generation round trips** (55 parametrized + 6 tests): `render_issue_markdown` for every story `DGR-017`..`DGR-071` is byte-for-byte identical to the corresponding file already in `.scratch/distributed-gguf-runtime/issues/`; determinism; leading disclaimer; `Blocks (derived)` rendering (`None` vs. listed); checkbox reflects `passes`; filename convention. - **Authority-claim rejection** (4 tests): accepts real generated text, rejects a missing disclaimer, rejects an overriding claim, and confirms every committed issue file in `.scratch/distributed-gguf-runtime/issues/` passes the check. - **Gitea payload generation** (3 tests): payload shape, body carries no information beyond what's in prd.json, and every real story's payload is well-formed and authority-clean. ## Commands and results ```bash python3 -m pytest -q tests/test_ralph_prd_schema.py ``` ```text 108 passed in 0.16s ``` ```bash python3 -m compileall -q packages tests ``` Exit code 0, no output (all files compile). ```bash git diff --check ``` Exit code 0 (no whitespace errors). ```bash python3 scripts/ralph_prd_schema.py validate .scratch/distributed-gguf-runtime/prd.json ``` ```text OK: 55 stories validated. ``` ```bash python3 scripts/ralph_prd_schema.py validate .scratch/distributed-gguf-runtime/prd.json --fresh ``` ```text ERROR: DGR-017: fresh backlog requires passes=false, got True ERROR: DGR-018: fresh backlog requires passes=false, got True 2 validation error(s). ``` Expected: `--fresh` is the invariant for a backlog that hasn't started execution; this backlog has legitimately completed two stories, so it correctly fails that stricter check while passing the plain (in-progress) `validate` command above. ### Baseline: full repository suite (ad hoc `python3`, not a project venv) ```bash python3 -m pytest -q ``` ```text 20 failed, 776 passed, 13 skipped, 2 warnings in 244.17s (0:04:04) ``` None of the failures touch `scripts/ralph_prd_schema.py` or `tests/test_ralph_prd_schema.py` (neither file existed before this story; this story adds no changes to `packages/`). Four of the 20 failures reproduce exactly the pre-existing baseline defects DGR-017's evidence already recorded (`test_tracker_models_endpoint_lists_registered_hf_repo_and_short_name_alias`, `test_torch_node_applies_tracker_load_shard_directive`, `test_shard_heal_cycle_surviving_node_covers_dead_peers_gap`, `test_a_node_with_an_unusable_precision_covers_no_layers`). The remaining 16 (activation compression, dynamic routing, gossip/relay, manual route benchmark, openai gateway, TOPLoC calibration dispatch, tracker control plane) include a `ModuleNotFoundError: langchain` failure, indicating this ad hoc `python3` lacks the project's `dev` extras (`langchain-openai`, etc.) rather than a real regression; this environment has no project virtualenv (e.g. no `.venv-rocm`) to run against instead. Not investigated further as out of scope for this story. ## Limitations - No real Gitea instance or API integration exists; `to_gitea_issue_payload` defines the payload shape (title/body/labels/milestone) only. Creating issues against a live Gitea server is future work, not claimed here. - `model` is intentionally validated as an open string, not a closed enum, per `RALPH-CONTEXT.md`'s "Quantization and placement are dynamic recipe inputs" constraint; the schema documents (`metadataSchema.modelConvention`) but does not restrict its value set. - Validation and generation were exercised only against this feature's `prd.json` (`.scratch/distributed-gguf-runtime/prd.json`); `docs/prd.json` and other `.scratch/*/prd.json` files in this repo use a materially different (simpler) shape and are out of scope. ## Dependency handoff DGR-021 and DGR-025 (this story's derived `blocks`) may treat `prd.json`'s `metadataSchema`, `qualityGates`, and this validator/generator as stable. Any future field addition to a story shape must extend `STORY_FIELDS` in `scripts/ralph_prd_schema.py` and the corresponding `metadataSchema.requiredStoryFields`/`optionalStoryFields` in `prd.json` together — `validate_metadata_schema_consistency` fails closed if they drift apart.