docs(workshops): add decision_refs provenance frontmatter (pql migration phase 5)

Workshop -> decision provenance was prose-only. Adds a `decision_refs:` YAML
frontmatter list (the confirmed D-records each workshop-outcomes.md touches,
filtered against the governance decision set) to all 17 workshop outcomes; the two
that lacked frontmatter (commodity-catalog, system-economic-specialization) get a
minimal block. pql indexes the list and `SELECT fm.decision_refs` round-trips it, so
"which workshops touch D-NNN" is answerable via SELECT + filter or `pql search`.

decision_refs is a relevance signal (decisions a workshop discusses/produces), not a
strict authorship claim — historical bare refs aren't disambiguated. Generated wiki
read-only sections are left untouched.

Noted in pql-requirements #5: 1.6.2 has no working DSL operator for frontmatter
list-membership (`~`/`contains` error, `in` matches nothing), so membership queries
need a client-side filter for now.

Reproducible via tooling/pql-migrate/add_workshop_provenance.py (idempotent).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 13:06:33 +02:00
co-authored by Claude Opus 4.8
parent 64be267249
commit 943f2e7749
19 changed files with 113 additions and 0 deletions
@@ -7,6 +7,7 @@ workshop: art-direction-mood-board
agent: ""
round: 0
created: 2026-02-12
decision_refs: [D-015, D-019, D-024, D-031, D-033, D-035, D-041, D-042, D-043, D-044, D-045, D-046, D-047, D-048, D-049, D-050, D-051, D-066]
---
# Art Direction & Mood Board Workshop — Final Outcomes
@@ -6,6 +6,7 @@ status: complete
sprint: 28
ticket: 684
created: 2026-03-17
decision_refs: [D-019, D-033, D-146, D-148, D-149, D-150, D-151, D-152, D-153, D-154, D-155, D-156, D-157]
---
# Sprint 28 Character Visuals Workshop — Outcomes
@@ -1,3 +1,11 @@
---
title: "Workshop Outcomes: commodity-catalog"
type: workshop
workshop: commodity-catalog
status: archived
decision_refs: [D-131, D-173, D-175, D-181, D-183, D-184, D-185, D-186, D-187]
---
# Workshop #801 Outcomes: Commodity Catalog
**Ticket:** #801 — Define commodity catalog (TOML)
@@ -5,6 +5,7 @@ type: workshop
status: archived
workshop: "control-interaction"
created: 2026-02-14
decision_refs: [D-009, D-010, D-017, D-018, D-027, D-033, D-041, D-045, D-048, D-053, D-054, D-055, D-056, D-057, D-058, D-059, D-060, D-061, D-062, D-063, D-064, D-065, D-066]
---
# Workshop Outcomes: Control & Interaction Scheme
@@ -6,6 +6,7 @@ status: complete
workshop: generation-cascade
agent: qatux
created: 2026-04-30
decision_refs: [D-194]
---
# Generation Cascade Workshop — Outcomes
@@ -7,6 +7,7 @@ workshop: generator-architecture
agent: ""
round: 0
created: 2026-02-27
decision_refs: [D-094]
---
# Generator Architecture Workshop — Outcomes
@@ -7,6 +7,7 @@ workshop: gj-6711-furthest-system
agent: ""
round: 0
created: 2026-03-16
decision_refs: []
---
# Workshop Outcomes: GJ 6711 / Abzu — Furthest System
@@ -7,6 +7,7 @@ workshop: knowledge-flow-npc-boundaries
agent: "qatux"
round: 2
created: 2026-02-24
decision_refs: [D-010, D-026, D-033, D-034, D-041, D-078, D-079, D-080, D-081, D-082, D-083]
---
# Workshop Outcomes — Knowledge Flow & NPC Information Boundaries
@@ -7,6 +7,7 @@ workshop: knowledge-graph-information-boundaries
agent: ""
round: 0
created: 2026-02-11
decision_refs: [D-010, D-011, D-012, D-017, D-028, D-033, D-041, D-080, D-083]
---
# Workshop Outcomes: Knowledge Graph & Information Boundaries
@@ -7,6 +7,7 @@ workshop: llm-voice-pipeline
agent: ""
round: 0
created: 2026-03-07
decision_refs: [D-007, D-010, D-020, D-028, D-029, D-092, D-121, D-122, D-123, D-124, D-128, D-138]
---
# LLM Voice Pipeline Workshop — Outcomes
@@ -5,6 +5,7 @@ workshop: planet-down-cascade
status: complete
created: 2026-05-01
rounds: 3
decision_refs: [D-194, D-195, D-196, D-197, D-198, D-199, D-200, D-201, D-202, D-203, D-204, D-205, D-206, D-207, D-208, D-209, D-210, D-211, D-212, D-213, D-214, D-215, D-216, D-217, D-218]
---
# Planet-Down Cascade — Workshop Outcomes
@@ -1,3 +1,11 @@
---
title: "Workshop Outcomes: system-economic-specialization"
type: workshop
workshop: system-economic-specialization
status: archived
decision_refs: [D-010, D-172, D-174, D-177, D-184, D-197, D-199, D-214, D-220, D-222, D-232, D-233, D-237, D-238]
---
# Workshop Outcomes — System Economic-Specialization
*Workshop: `system-economic-specialization` | Date: 2026-05-31*
*Participants: Miri, Burnelli-Sheldon, Paula | Documenter: Qatux*
@@ -7,6 +7,7 @@ workshop: test-architecture
agent: ""
round: 0
created: 2026-02-17
decision_refs: [D-015, D-017, D-028, D-031, D-033, D-035, D-041, D-055, D-057, D-059, D-060, D-062, D-065]
---
# QA Strategy & Test Architecture Workshop — Outcomes
@@ -7,6 +7,7 @@ workshop: v01-gap-analysis
agent: qatux
round: 0
created: 2026-02-25
decision_refs: [D-010, D-023, D-027, D-029, D-031, D-038]
---
# Workshop Outcomes: v0.1 Gap Analysis
@@ -7,6 +7,7 @@ workshop: wheres-the-fun
agent: "qatux"
round: 0
created: 2026-03-05
decision_refs: []
---
# Workshop Outcomes: Where's the Fun?
@@ -7,6 +7,7 @@ workshop: wiki-review
agent: ""
round: 0
created: 2026-02-25
decision_refs: [D-088, D-090, D-092]
---
# Workshop Outcomes: Wiki Review & Content Standards
@@ -5,6 +5,7 @@ date: 2026-03-24
status: complete
participants: Gestalt, Miri, Nigel, Ozzie, Tyre, Qatux (documenter)
rounds: 3 (SW1 only — SW2-SW4 cancelled)
decision_refs: [D-101, D-104, D-105, D-107]
---
# World Generation Architecture Workshop — Outcomes
+8
View File
@@ -109,6 +109,14 @@ requiring link syntax in source.
**Workaround.** Adopt a link convention in *authored* files only; accept that generated files are
search-only.
**Related gap — frontmatter list-membership in the DSL.** Our Phase-5 provenance puts the decisions
each workshop touches in a `decision_refs: [D-010, …]` frontmatter list. pql indexes the list and
`SELECT fm.decision_refs` round-trips it, but we found no DSL operator that filters by membership:
`fm.decision_refs ~ 'D-010'` and `… contains …` are lex/parse errors, and `'D-010' in fm.decision_refs`
parses but matches nothing. So "which workshops touch D-010?" needs a `SELECT` + client-side filter or
a `pql search`. A documented list-membership operator (or making `in` work against frontmatter arrays)
would close this.
---
## 6. WIP-limit enforcement (advisory) on `ticket status … in_progress` — **MEDIUM**
@@ -0,0 +1,74 @@
#!/usr/bin/env python3
"""Add decision_refs provenance frontmatter to workshop outcomes (pql migration, Phase 5).
Workshop -> decision provenance was prose-only. This injects a `decision_refs:` YAML
frontmatter list (the confirmed D-records each workshop-outcomes.md touches) so pql can
answer "which workshop produced D-NNN" via
pql query "SELECT name, fm.workshop WHERE fm.decision_refs CONTAINS 'D-238'"
decision_refs = distinct valid confirmed-D ids mentioned in the file body (filtered
against the governance decision set, so typos/multi-refs/stale ids are dropped). Files
without frontmatter get a minimal block. Idempotent: re-running rewrites the same line.
Default is a dry run. Pass --apply to write.
"""
import glob
import os
import re
import sqlite3
import sys
APPLY = "--apply" in sys.argv
REPO = "/var/mnt/data/projects/settled-reach/main"
DB = f"{REPO}/.pql/pql.db"
D_IDS = set(r[0] for r in sqlite3.connect(DB).execute(
"SELECT id FROM decisions WHERE type='confirmed'"))
D_REF = re.compile(r"\bD-\d{3}\b")
FILES = sorted(glob.glob(f"{REPO}/docs/workshops/*/workshop-outcomes.md"))
def split_frontmatter(text):
"""Return (fm_lines, body) where fm_lines excludes the --- fences; fm is None if absent."""
if text.startswith("---\n"):
end = text.find("\n---\n", 4)
if end != -1:
return text[4:end].splitlines(), text[end + 5:]
return None, text
def refs_in(body):
return sorted({m for m in D_REF.findall(body) if m in D_IDS}, key=lambda d: int(d[2:]))
def main():
print(f"=== add_workshop_provenance.py ({'APPLY' if APPLY else 'DRY-RUN'}) ===\n")
for f in FILES:
text = open(f, encoding="utf-8").read()
fm, body = split_frontmatter(text)
refs = refs_in(body if fm is not None else text)
rel = f.split("/main/", 1)[-1]
wname = os.path.basename(os.path.dirname(f))
refs_yaml = "[" + ", ".join(refs) + "]"
if fm is not None:
fm = [ln for ln in fm if not ln.startswith("decision_refs:")]
fm.append(f"decision_refs: {refs_yaml}")
new = "---\n" + "\n".join(fm) + "\n---\n" + body
action = f"inject decision_refs ({len(refs)})"
else:
new = (f"---\ntitle: \"Workshop Outcomes: {wname}\"\ntype: workshop\n"
f"workshop: {wname}\nstatus: archived\ndecision_refs: {refs_yaml}\n---\n\n" + text)
action = f"ADD frontmatter + decision_refs ({len(refs)})"
print(f"{rel}: {action}")
if refs:
print(f" {refs_yaml}")
if APPLY:
open(f, "w", encoding="utf-8").write(new)
print("\nDone." + ("" if APPLY else " (dry run — pass --apply)"))
if __name__ == "__main__":
main()