Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 75 additions & 1 deletion .github/prompts/05-analysis-gate.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,80 @@ if [ -s "$ANALYSIS_DIR/coalition-mathematics.md" ]; then
|| { echo "❌ coalition-mathematics.md: missing seat-count / vote-breakdown table"; FAIL=1; }
fi

# Check 9 — PIR status sidecar (`pir-status.json`)
# A valid pir-status.json must be present after every analysis run so that
# open PIRs can be automatically rolled forward to the next cycle.
# Schema: schemas/pir-status.schema.json (v1.0)
# Roll-forward script: scripts/roll-forward-pirs.ts
PIR_FILE="$ANALYSIS_DIR/pir-status.json"
if [ ! -s "$PIR_FILE" ]; then
echo "❌ pir-status.json missing or empty in $ANALYSIS_DIR — create it per schemas/pir-status.schema.json"
FAIL=1
else
# Structural check: required top-level fields
for PIR_FIELD in schema_version cycle date subfolder pirs generated_at; do
python3 -c "
import json, sys
try:
d = json.load(open('$PIR_FILE'))
sys.exit(0 if '$PIR_FIELD' in d else 1)
except Exception:
sys.exit(1)
" 2>/dev/null || { echo "❌ pir-status.json: missing required field '$PIR_FIELD'"; FAIL=1; }
done
# schema_version must be '1.0'
python3 -c "
import json, sys
try:
d = json.load(open('$PIR_FILE'))
sys.exit(0 if d.get('schema_version') == '1.0' else 1)
except Exception:
sys.exit(1)
" 2>/dev/null || { echo "❌ pir-status.json: schema_version must be '1.0'"; FAIL=1; }
# pirs must be an array
python3 -c "
import json, sys
try:
d = json.load(open('$PIR_FILE'))
sys.exit(0 if isinstance(d.get('pirs'), list) else 1)
except Exception:
sys.exit(1)
" 2>/dev/null || { echo "❌ pir-status.json: 'pirs' field must be a JSON array"; FAIL=1; }
# each PIR (any status) must have valid pir_id, statement, status, confidence;
# answered PIRs must carry answer_summary; non-answered PIRs must not.
python3 -c "
import json, sys, re
try:
d = json.load(open('$PIR_FILE'))
PIR_ID_RE = re.compile(r'^PIR-[A-Za-z0-9]+(-[A-Za-z0-9]+)*$')
VALID_STATUS = {'open','answered','superseded','deferred','cancelled'}
VALID_CONF = {'VERY HIGH','HIGH','MEDIUM','LOW','VERY LOW'}
bad = 0
# Cross-field invariant: subfolder must equal cycle (not enforceable in pure JSON Schema).
if d.get('subfolder') != d.get('cycle'):
print(f'❌ pir-status.json: subfolder={d.get(\"subfolder\")!r} must equal cycle={d.get(\"cycle\")!r}'); bad = 1
for p in d.get('pirs', []):
pid = p.get('pir_id')
if not isinstance(pid, str) or not PIR_ID_RE.match(pid):
print(f'❌ pir-status.json: invalid pir_id format: {pid!r}'); bad = 1
for f in ('statement', 'status', 'confidence'):
if not p.get(f):
print(f'❌ pir-status.json pir={pid!r}: missing required field \"{f}\"'); bad = 1
if p.get('status') not in VALID_STATUS:
print(f'❌ pir-status.json pir={pid!r}: invalid status {p.get(\"status\")!r}'); bad = 1
if p.get('confidence') not in VALID_CONF:
print(f'❌ pir-status.json pir={pid!r}: invalid confidence {p.get(\"confidence\")!r}'); bad = 1
# Conditional: answer_summary required iff status == 'answered'.
if p.get('status') == 'answered' and not p.get('answer_summary'):
print(f'❌ pir-status.json pir={pid!r}: status=answered requires non-empty answer_summary'); bad = 1
if p.get('status') != 'answered' and 'answer_summary' in p:
print(f'❌ pir-status.json pir={pid!r}: status={p.get(\"status\")!r} must not carry answer_summary'); bad = 1
sys.exit(bad)
except Exception as e:
print(f'❌ pir-status.json: parse error: {e}'); sys.exit(1)
" 2>&1 || FAIL=1
fi

[ "$FAIL" -eq 0 ] || exit 1
```

Expand Down Expand Up @@ -264,7 +338,7 @@ Non-blocking for `standard` / `deep` runs; **blocking for `comprehensive` / Tier
Inline bash probe — append to the main block after `FAIL=0` bookkeeping completes. Supplementary artifacts have **three independent blocking triggers**, not a single tier-only rule: **aggregation article types** (`weekly-review`, `monthly-review`) require the aggregation artifacts; any run whose **tier** is `comprehensive` (the Tier-C run mode) requires the Tier-C supplementary set; and `cross-run-diff.md` is blocking whenever the workflow has **≥ 2 production runs** of the same article type, including `standard` and `deep` runs. `ARTICLE_TYPE` encodes the workflow family; `ANALYSIS_TIER` (when set) encodes the depth tier (`standard` | `deep` | `comprehensive`); `ANALYSIS_RUN_COUNT` (when set) is the numeric count of runs for the same article-generation cycle (if unset or non-numeric, treated as `1`).

```bash
# Check 9 — supplementary artifacts (blocking for aggregation types, any Tier-C run, and S5 when run-count >= 2)
# Check 10 — supplementary artifacts (blocking for aggregation types, any Tier-C run, and S5 when run-count >= 2)
IS_AGGREGATION=0
IS_TIER_C=0
IS_MULTI_RUN=0
Expand Down
51 changes: 50 additions & 1 deletion analysis/methodologies/ai-driven-analysis-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ Read every file you produced in Steps 3–5. For each one, **improve every secti
- Add one more named actor (MP, minister, official) to every stakeholder and SWOT entry.
- Add one more dok_id or vote-record citation to every evidence column that has < 2 citations.
- **Tag every key finding to a PIR/EEI** from the catalog in `political-style-guide.md`.
- **Write `pir-status.json`** — every cycle must produce `$ANALYSIS_DIR/pir-status.json` conforming to `schemas/pir-status.schema.json` v1.0 (required fields: `schema_version`, `cycle`, `date`, `subfolder`, `pirs`, `generated_at`). Newly extracted PIRs from `intelligence-assessment.md` default to `status: "open"`; rolled-forward PIRs from a prior cycle preserve their existing status (`open` / `answered` / `superseded` / `deferred` / `cancelled`) and may carry a populated `inherits_from` chain. Open PIRs that are carried forward have their confidence degraded one level (HIGH → MEDIUM, etc.) by `scripts/roll-forward-pirs.ts`; non-open PIRs are preserved unchanged so the historical lineage is never lost. This file is the machine-readable PIR sidecar used for automated roll-forward and CI gate enforcement (Check 9 in `05-analysis-gate.md`).
- Add Statskontoret evidence to every implementation-capacity or agency-burden claim where a relevant public report/page exists.
- Verify every macro/fiscal/monetary/external-sector claim is IMF-first, vintage-tagged when projected, and represented in `economic-data.json` when charted.
- Re-rank the significance scoring if the rewrite reveals a stronger lead.
Expand Down Expand Up @@ -579,6 +580,53 @@ Every security-relevant control in Family A maps to **ISO 27001:2022**, **NIST C
| [`political-threat-framework.md`](political-threat-framework.md) | Attack trees + kill chain + threat taxonomy |
| [`political-style-guide.md`](political-style-guide.md) | Writing voice, attribution, evidence density |

### PIR status sidecar — automated roll-forward

Every analysis cycle writes a `pir-status.json` sidecar alongside the 23 required artifacts:

| Item | Detail |
|------|--------|
| **Schema** | `schemas/pir-status.schema.json` v1.0 — JSON Schema 2020-12 |
| **Location** | `analysis/daily/YYYY-MM-DD/{subfolder}/pir-status.json` |
| **Fields** | `schema_version`, `cycle`, `date`, `subfolder`, `generated_at`, `inherited_from`, `pirs[]` |
| **PIR entry fields** | `pir_id` (pattern `PIR-*`), `statement`, `status`, `confidence`, `trigger`, `answer_summary`, `inherits_from[]`, `evidence_refs[]`, `horizon`, `admiralty_grade` |
| **Roll-forward script** | `scripts/roll-forward-pirs.ts` — propagates `open` PIRs from the previous cycle to the current cycle, degrading confidence by one level to flag staleness |
| **CI gate** | Check 9 in `.github/prompts/05-analysis-gate.md` — blocks article generation if `pir-status.json` is absent or structurally invalid |

**How to write `pir-status.json` during analysis (Step 7):**

```json
{
"schema_version": "1.0",
"cycle": "month-ahead",
"date": "2026-04-27",
"subfolder": "month-ahead",
"generated_at": "2026-04-27T10:00:00Z",
"inherited_from": null,
"pirs": [
{
"pir_id": "PIR-1",
"statement": "SD voting discipline on prop. 2025/26:236 (fuel tax)",
"trigger": "May 2026 chamber vote on HD01FiU48",
"status": "open",
"confidence": "HIGH",
"evidence_refs": ["HD01FiU48"],
"horizon": "2026-05-15",
"admiralty_grade": "B2"
}
]
}
```

**Roll-forward usage (next cycle):**

```bash
npx tsx scripts/roll-forward-pirs.ts \
--date 2026-04-28 --cycle month-ahead
```

---

### Templates and platform exemplars

| Document | Purpose |
Expand All @@ -591,7 +639,8 @@ Every security-relevant control in Family A maps to **ISO 27001:2022**, **NIST C

**Document Control**
- **Path:** `/analysis/methodologies/ai-driven-analysis-guide.md`
- **Version:** 6.6 — Phase 2–5 alignment (worked examples + narrative-voice + Pass-2 self-audit)
- **Version:** 6.7 — PIR status sidecar (`pir-status.json`) integration
- **Key changes in v6.7:** Added mandatory `pir-status.json` sidecar write step to Pass-2 checklist (Step 7); added PIR status sidecar reference section under Related Documents; added roll-forward usage example (`scripts/roll-forward-pirs.ts`) and schema reference (`schemas/pir-status.schema.json`).
- **Key changes in v6.6:** Step 3 now points at the v1.3 doctype-variant detector (5 extended types: motion-package, fpm, utskottsbetänkande-variants, KU-anmälan, EU-nämnd) and adds Narrative subsection requirement for ≥ L2 per-file artifacts; Step 4 cross-reference-map row links to the 7 atomic edge types in `structural-metadata-methodology.md` v1.3; Step 7 Pass-2 rewrite checklist adds two binding items — Pass-2 Self-Audit Checklist (10 items) and Narrative 6-axis rubric (18/30 floor); DIW section adds worked-example callout to `synthesis-methodology.md` v1.3 (line-by-line scoring + winner/loser rubric) and Sainte-Laguë walkthrough in `electoral-domain-methodology.md` v1.3; Quality Gate Checklist gains rows 11–12.
- **Key changes in v6.5:** source diversity rule integration (political-style-guide.md v3.1)
- **Key changes in v6.4:** Updated Step 1 reading list to reference **Source Diversity Rule** in political-style-guide.md v3.1 (multi-source corroboration by claim priority, conflict resolution, worked scenario); added Source Diversity check to Quality Gate Evidence dimension (P0/P1: ≥3 sources required); added source diversity verification to Pass-2 rewrite checklist; added IMF collection tools to referenced Collection Management Matrix.
Expand Down
132 changes: 132 additions & 0 deletions schemas/pir-status.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://riksdagsmonitor.com/schemas/pir-status.schema.json",
"title": "PIR Status Sidecar",
"description": "Machine-readable Priority Intelligence Requirement (PIR) status sidecar produced by every agentic analysis cycle. Written to `analysis/daily/YYYY-MM-DD/{subfolder}/pir-status.json`. Enables automated PIR roll-forward, cross-cycle gap detection, and CI gate enforcement. Schema v1.0.",
"type": "object",
"additionalProperties": false,
"required": ["schema_version", "cycle", "date", "subfolder", "pirs", "generated_at"],
"properties": {
"schema_version": {
"type": "string",
"description": "Schema version — always '1.0' for this release.",
"enum": ["1.0"]
},
"cycle": {
"type": "string",
"description": "Analysis cycle type slug (matches the subfolder name convention).",
"enum": [
"committeeReports",
"propositions",
"motions",
"interpellations",
"evening-analysis",
"realtime-pulse",
"week-ahead",
"month-ahead",
"weekly-review",
"monthly-review"
]
},
"date": {
"type": "string",
"description": "ISO calendar date of this analysis run (YYYY-MM-DD).",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
},
"subfolder": {
"type": "string",
"description": "Relative subfolder path under `analysis/daily/YYYY-MM-DD/` — typically equal to `cycle`. Cross-field equality is not enforced by the schema (it would require a verbose `oneOf` per cycle); the CI analysis gate (Check 10 in `.github/prompts/05-analysis-gate.md`) and the `roll-forward-pirs.ts` writer enforce `subfolder === cycle`.",
"minLength": 1
},
"generated_at": {
"type": "string",
"description": "ISO-8601 timestamp of when this sidecar was generated (UTC).",
"format": "date-time"
},
"inherited_from": {
"type": ["string", "null"],
"description": "Source path this file was rolled forward from, e.g. `analysis/daily/2026-04-26/month-ahead/pir-status.json`. Null when this is a freshly authored set (no roll-forward).",
"default": null
},
"pirs": {
"type": "array",
"description": "Ordered list of PIRs for this cycle. May be empty for short-event cycles that did not define standing PIRs.",
"items": { "$ref": "#/$defs/pirEntry" }
}
},
"$defs": {
"pirEntry": {
"type": "object",
"additionalProperties": false,
"required": ["pir_id", "statement", "status", "confidence"],
"properties": {
"pir_id": {
"type": "string",
"description": "Stable local identifier within this cycle, e.g. 'PIR-1', 'PIR-A', 'PIR-FiU-1'. Must be unique within the `pirs` array.",
"pattern": "^PIR-[A-Za-z0-9]+([-][A-Za-z0-9]+)*$"
},
"statement": {
"type": "string",
"description": "Human-readable intelligence requirement statement. Must be specific, falsifiable, and actionable.",
"minLength": 10
},
"trigger": {
"type": "string",
"description": "Observable event or threshold that would answer / close this PIR. Optional but strongly recommended.",
"minLength": 5
},
"status": {
"type": "string",
"description": "Current disposition of this PIR.",
"enum": ["open", "answered", "superseded", "deferred", "cancelled"]
},
"confidence": {
"type": "string",
"description": "ODNI-aligned confidence label on the current status assessment.",
"enum": ["VERY HIGH", "HIGH", "MEDIUM", "LOW", "VERY LOW"]
},
"answer_summary": {
"type": "string",
"description": "Short (≤ 500 chars) summary of the evidence or event that answered this PIR. Populate only when status='answered'.",
"minLength": 1,
"maxLength": 500
},
Comment on lines +88 to +93
"inherits_from": {
"type": "array",
"description": "Zero or more `pir_id` references from the previous cycle that this PIR continues or inherits from.",
"items": {
"type": "string",
"pattern": "^PIR-[A-Za-z0-9]+([-][A-Za-z0-9]+)*$"
},
"default": []
},
"evidence_refs": {
"type": "array",
"description": "Riksdag `dok_id` references, primary-source URLs, or named artifacts that provide evidence for the current status assessment. At least one reference is recommended for answered PIRs.",
"items": {
"type": "string",
"minLength": 2
},
"default": []
},
"horizon": {
"type": "string",
"description": "Monitoring horizon for this PIR, expressed as an ISO date (YYYY-MM-DD) or a relative label ('next-session', 'next-week', 'election-day'). Optional.",
"minLength": 4
},
"admiralty_grade": {
"type": "string",
"description": "Admiralty Code grade of the best available source for the current status (STANAG 2022). Source reliability A–F + information credibility 1–6.",
"pattern": "^[A-F][1-6]$"
}
},
"allOf": [
{
"if": { "properties": { "status": { "const": "answered" } }, "required": ["status"] },
"then": { "required": ["answer_summary"] },
"else": { "not": { "required": ["answer_summary"] } }
}
]
}
}
}
Loading
Loading