mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-08-06 17:36:53 +01:00
R6 Tier-1: content-owned routing index generator + CI guard
Retire the orchestrator's hardcoded ~28-token signal catalog by compiling a routing index from article front-matter. New tools/Build-RoutingIndex.ps1 scans all knowledge layers and emits routing-index.json (signal-token -> domain + backing articles), seeded from the migrated legacy catalog (routing-seed.json) so recall is never below today. Optional per-article 'signals:' front-matter (validator rule R24) is the authored precision path; domain-normalization reconciles front-matter/orchestrator/feedback domain vocabularies. Artifact is a runtime build (gitignored), orchestrator-facing only (not the lean agent-replayed knowledge index). CI guard Test-RoutingIndex.ps1 asserts determinism, seed recall floor, no orphaned signals, and normalization completeness; wired into the index workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
4119417ce4
commit
6ed56b95b7
8 changed files with 722 additions and 7 deletions
32
.github/scripts/validate_frontmatter.py
vendored
32
.github/scripts/validate_frontmatter.py
vendored
|
|
@ -34,6 +34,11 @@ KNOWLEDGE_REQUIRED_KEYS = {
|
|||
"bc-version", "domain", "keywords", "technologies",
|
||||
"countries", "application-area",
|
||||
}
|
||||
# Optional knowledge keys (do not trigger the R02 closed-key-set error).
|
||||
# `signals`: OPTIONAL routing-signal declarations consumed by the routing index
|
||||
# (tools/Build-RoutingIndex.ps1). Back-compatible — articles that omit it are
|
||||
# routed from the seed catalog + keyword derivation. See tools/routing-index.md.
|
||||
KNOWLEDGE_OPTIONAL_KEYS = {"signals"}
|
||||
ACTION_SKILL_REQUIRED_KEYS = {
|
||||
"kind", "id", "version", "title", "description", "inputs", "outputs",
|
||||
}
|
||||
|
|
@ -202,7 +207,7 @@ def validate_knowledge(path: Path, parsed: Parsed, report: Report) -> None:
|
|||
|
||||
# R02 required keys, no extras, none empty
|
||||
missing = KNOWLEDGE_REQUIRED_KEYS - fm.keys()
|
||||
extras = fm.keys() - KNOWLEDGE_REQUIRED_KEYS
|
||||
extras = fm.keys() - KNOWLEDGE_REQUIRED_KEYS - KNOWLEDGE_OPTIONAL_KEYS
|
||||
if missing:
|
||||
report.error(path, "R02", f"missing required frontmatter keys: {sorted(missing)}", 1)
|
||||
if extras:
|
||||
|
|
@ -212,6 +217,31 @@ def validate_knowledge(path: Path, parsed: Parsed, report: Report) -> None:
|
|||
if v is None or v == "" or v == []:
|
||||
report.error(path, "R02", f"frontmatter key '{k}' must not be empty", 1)
|
||||
|
||||
# R24 optional routing `signals` block. Each entry is either a bare token
|
||||
# string or a mapping with a required 'token' and optional 'pattern'/'domain'
|
||||
# (all non-empty strings). Keeps the routing-index generator's input honest.
|
||||
if "signals" in fm:
|
||||
sigs = fm["signals"]
|
||||
if not isinstance(sigs, list) or not sigs:
|
||||
report.error(path, "R24", "signals must be a non-empty list", 1)
|
||||
else:
|
||||
for entry in sigs:
|
||||
if isinstance(entry, str):
|
||||
if not entry.strip():
|
||||
report.error(path, "R24", "signals token string must not be empty", 1)
|
||||
elif isinstance(entry, dict):
|
||||
tok = entry.get("token")
|
||||
if not isinstance(tok, str) or not tok.strip():
|
||||
report.error(path, "R24", "signals entry must have a non-empty 'token'", 1)
|
||||
for opt in ("pattern", "domain"):
|
||||
if opt in entry and (not isinstance(entry[opt], str) or not entry[opt].strip()):
|
||||
report.error(path, "R24", f"signals '{opt}' must be a non-empty string", 1)
|
||||
unknown = set(entry.keys()) - {"token", "pattern", "domain"}
|
||||
if unknown:
|
||||
report.error(path, "R24", f"signals entry has unknown keys: {sorted(unknown)}", 1)
|
||||
else:
|
||||
report.error(path, "R24", "signals entry must be a string or a mapping", 1)
|
||||
|
||||
# R03 bc-version
|
||||
if "bc-version" in fm:
|
||||
_, err = expand_bc_version(fm["bc-version"])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue