@balpal4495/quorum 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,6 +65,12 @@ When a decision is made, your AI stages a Chronicle entry using `oracle.propose(
65
65
 
66
66
  Commit `.chronicle/committed/` to git. Future sessions — and your teammates' sessions — start with that context.
67
67
 
68
+ ### Every merged PR creates a Chronicle proposal automatically
69
+
70
+ A GitHub Actions workflow fires when any PR merges to main. It creates a Chronicle proposal capturing the decision, which files changed, and any explicitly deferred items from the PR description. The proposal sits in `proposals/` until you commit it — nothing is auto-indexed.
71
+
72
+ This means the gap between "PR merged" and "Chronicle knows about it" is now zero.
73
+
68
74
  ---
69
75
 
70
76
  ## Real examples
@@ -111,15 +117,36 @@ gaps: ["no lock strategy documented", "no rollback plan"]
111
117
  council_brief: challenge
112
118
  ```
113
119
 
114
- Council's Chairman gives a verdict:
115
-
116
- ```
117
- satisfied: false
118
- verdict: "On a table this size, a naive ALTER TABLE takes an exclusive lock for minutes.
119
- Specify a shadow column pattern or pg_repack. No rollback plan documented."
120
+ Council's Chairman gives a structured verdict:
121
+
122
+ ```json
123
+ {
124
+ "satisfied": false,
125
+ "blockers": [
126
+ {
127
+ "issue": "Naive ALTER TABLE takes an exclusive lock for minutes on a 50M-row table",
128
+ "evidence": ["db-017"],
129
+ "required_fix": "Use shadow column pattern or pg_repack. Add rollback path."
130
+ }
131
+ ],
132
+ "warnings": [],
133
+ "advisor_split": { "proceed": 0, "redesign": 4, "investigate-more": 1 }
134
+ }
120
135
  ```
121
136
 
122
- The agent revises the plan. You approve the Chronicle entry once it's solid. The reasoning is on record for the next time someone touches that table.
137
+ The agent revises the plan. You approve the Chronicle entry once it's solid. The reasoning — including alternatives considered and why they were rejected — is on record for the next time someone touches that table:
138
+
139
+ ```json
140
+ {
141
+ "decision": "Use shadow column pattern for NOT NULL migration on users table",
142
+ "alternatives_considered": ["naive ALTER TABLE", "pg_repack"],
143
+ "rejected_reason": ["ALTER TABLE takes exclusive lock for minutes on 50M rows"],
144
+ "scope": ["database", "migrations"],
145
+ "affected_areas": ["db/migrations/", "src/models/user.ts"],
146
+ "validation_plan": ["Confirm 100% backfill before applying NOT NULL constraint", "Test rollback path on staging"],
147
+ "review_after": "2026-08-01"
148
+ }
149
+ ```
123
150
 
124
151
  ---
125
152
 
@@ -130,14 +157,96 @@ Four portable TypeScript modules installed into `quorum/modules/`:
130
157
  | Module | What it does |
131
158
  |---|---|
132
159
  | **Oracle** | Query and write interface to Chronicle. No LLM required. |
133
- | **Jury** | Evaluates a proposed design against Chronicle evidence. Returns a confidence score. |
134
- | **Council** | A panel of advisors challenges the design independently, reviewers critique anonymously, a Chairman gives a final verdict. |
160
+ | **Jury** | Evaluates a proposed design against Chronicle evidence. Returns a decomposed confidence score and hard-blocker gaps. |
161
+ | **Council** | A panel of advisors challenges the design independently, reviewers critique anonymously, a Chairman gives a structured verdict with blockers and warnings. |
135
162
  | **Sentinel** | Shows which files the AI knows nothing about, flags stale knowledge, and posts a coverage map on every PR. |
136
163
 
137
164
  The modules live in your repo — readable by any AI working in the codebase. Nothing is hidden in `node_modules`.
138
165
 
139
166
  ---
140
167
 
168
+ ## How Jury works
169
+
170
+ Before calling the LLM, Jury runs a **deterministic preflight** — no LLM required — that checks whether the design touches sensitive areas (auth, database migrations, crypto, payments, PII, secrets), mentions a rollback strategy, and whether any refuted Chronicle entries conflict with the design. These facts are injected into the Jury prompt as hard ground truth.
171
+
172
+ The LLM then scores the design across four dimensions:
173
+
174
+ | Dimension | What it measures |
175
+ |---|---|
176
+ | Evidence support | Do validated Chronicle entries confirm this approach works here? |
177
+ | Feasibility | Do Chronicle entries suggest this is achievable? |
178
+ | Risk | How well does the design address known failure modes? |
179
+ | Completeness | Does the design cover the full outcome? |
180
+
181
+ Confidence is recomputed as the exact average of those four scores — the LLM's stated confidence is discarded. Jury also separates `blocking_gaps` (must resolve before proceeding) from `gaps` (useful but not critical).
182
+
183
+ ---
184
+
185
+ ## How Council works
186
+
187
+ Before running the full panel, a **risk classifier** reads the design text and Chronicle evidence and assigns a risk level:
188
+
189
+ | Risk | Council mode | LLM calls |
190
+ |---|---|---|
191
+ | Low | 1 advisor + 1 reviewer | 4 |
192
+ | Medium | 1 advisor + 2 reviewers | 5 |
193
+ | High | 5 advisors + 5 reviewers | 12 |
194
+ | Critical | 5 advisors + 5 reviewers (+ human architecture flag) | 12 |
195
+
196
+ Auth, crypto, payments, and data deletion trigger Critical. Database migrations, PII, permissions trigger High. Cache, queues, deployments trigger Medium. Everything else is Low.
197
+
198
+ The Chairman's verdict is **structured**:
199
+
200
+ ```json
201
+ {
202
+ "blockers": [
203
+ {
204
+ "issue": "No rollback plan for destructive migration",
205
+ "evidence": ["db-017"],
206
+ "required_fix": "Add shadow-column migration and rollback path before execution"
207
+ }
208
+ ],
209
+ "warnings": [
210
+ {
211
+ "issue": "No integration test for token expiry edge case",
212
+ "suggested_fix": "Add test covering token rotation during concurrent requests"
213
+ }
214
+ ],
215
+ "advisor_split": { "proceed": 2, "redesign": 2, "investigate-more": 1 }
216
+ }
217
+ ```
218
+
219
+ Blockers must be resolved before the human gate. Warnings can be ticketed. High `advisor_split` disagreement is surfaced explicitly — it means genuine uncertainty, not a safe proceed.
220
+
221
+ Every Oracle ID cited in the verdict is also validated against the evidence pack that was actually sent. Hallucinated citations are flagged in `citation_validation.hallucinated_ids` and stripped from the Chronicle proposal.
222
+
223
+ ---
224
+
225
+ ## Eval suite
226
+
227
+ `evals/` contains canonical test cases — known-bad proposals that Council should block and known-good ones it should pass:
228
+
229
+ | Case | Expected outcome |
230
+ |---|---|
231
+ | Naive NOT NULL migration on large table | Block — no lock strategy |
232
+ | HS256 JWT when RS256 was already chosen | Block — cites refuted entry auth-022 |
233
+ | PII fields logged to stdout | Block — GDPR violation in evidence |
234
+ | Payment charge without idempotency key | Block — duplicate charge risk |
235
+ | Redis sessions (previously removed) | Block — memory overhead already documented |
236
+ | Cache without stampede protection | Block — prior incident in Chronicle |
237
+ | Safe internal rename | Proceed — low risk, no conflicts |
238
+ | RS256 JWT (approved pattern) | Proceed — matches validated Chronicle entry |
239
+ | Migration with rollback + shadow column | Proceed — addresses documented failure mode |
240
+ | Novel WebSocket design, no evidence | Investigate-more — no Chronicle evidence either way |
241
+
242
+ Deterministic assertions (preflight, risk classifier) run on every CI pass. LLM-dependent assertions (confidence bounds, Council recommendation) activate with `EVAL_LLM=1`.
243
+
244
+ ```bash
245
+ npx vitest run evals/
246
+ ```
247
+
248
+ ---
249
+
141
250
  ## Sentinel — coverage and drift
142
251
 
143
252
  Sentinel surfaces two things Chronicle can't tell you about itself.
package/SETUP.md CHANGED
@@ -197,11 +197,17 @@ If the directory is not created, re-check that `setup()` is being awaited correc
197
197
  Confirm the modules are working in this environment:
198
198
 
199
199
  ```bash
200
+ # Module unit tests
200
201
  npx vitest run quorum/modules/
202
+
203
+ # Eval suite — deterministic assertions, no LLM required
204
+ npx vitest run quorum/evals/
201
205
  ```
202
206
 
203
207
  All tests should pass. If they fail due to missing dependencies, re-run Step 3.
204
208
 
209
+ The eval suite runs canonical test cases (known-bad proposals that should block, known-good ones that should pass) through the deterministic preflight and risk classifier. These pass without any LLM. If you later want to test Jury confidence and Council recommendations against a real LLM, set `EVAL_LLM=1` when running.
210
+
205
211
  ---
206
212
 
207
213
  ## Step 9 — Report what was done
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Eval suite — runs all cases from evals/cases/ through deterministic checks.
3
+ *
4
+ * Deterministic assertions (preflight, risk classifier) run on every CI pass.
5
+ * LLM-dependent assertions (jury confidence, council recommendation) are skipped
6
+ * unless EVAL_LLM env var is set — they are too slow and costly for standard CI.
7
+ *
8
+ * To run with a real LLM locally:
9
+ * EVAL_LLM=1 OPENAI_API_KEY=sk-... npx vitest run evals/
10
+ */
11
+ import { describe, it, expect } from "vitest"
12
+ import path from "path"
13
+ import { loadCases, runCase } from "../runner"
14
+
15
+ describe("eval suite — deterministic checks", async () => {
16
+ const cases = await loadCases(path.join(__dirname, "../cases"))
17
+
18
+ for (const evalCase of cases) {
19
+ it(`[${evalCase.id}] ${evalCase.description}`, async () => {
20
+ // No LLM — only runs deterministic assertions (preflight + risk classifier)
21
+ const result = await runCase(evalCase)
22
+
23
+ if (!result.passed) {
24
+ // Surface all failures clearly
25
+ throw new Error(
26
+ `Eval case "${evalCase.id}" failed:\n${result.failures.map(f => ` • ${f}`).join("\n")}`,
27
+ )
28
+ }
29
+ })
30
+ }
31
+ })
@@ -0,0 +1,46 @@
1
+ {
2
+ "id": "auth_hs256_rejected",
3
+ "description": "Proposing HS256 JWT when RS256 was already chosen — should block",
4
+ "outcome": "Add JWT authentication to the API",
5
+ "design": "Use HS256 symmetric JWT tokens with a shared secret stored in environment variables",
6
+ "oracle_evidence": [
7
+ {
8
+ "id": "auth-022",
9
+ "key_insight": "HS256 JWT rejected — no way to rotate keys without invalidating all active sessions",
10
+ "decision": "HS256 JWT rejected — no way to rotate keys without invalidating all active sessions. Use RS256.",
11
+ "schema_version": 2,
12
+ "affected_areas": ["src/auth/", "src/middleware/"],
13
+ "scope": ["auth", "sessions"],
14
+ "status": "refuted",
15
+ "confidence": 0.91,
16
+ "source_module": "council",
17
+ "evidence_cited": [],
18
+ "timestamp": "2025-03-01T09:00:00Z"
19
+ },
20
+ {
21
+ "id": "auth-031",
22
+ "key_insight": "RS256 with short-lived tokens and refresh rotation in httpOnly cookies is the approved pattern",
23
+ "decision": "RS256 with 15-min access tokens and refresh rotation stored in httpOnly cookies is the approved auth pattern",
24
+ "schema_version": 2,
25
+ "affected_areas": ["src/auth/", "src/middleware/"],
26
+ "scope": ["auth", "sessions"],
27
+ "status": "validated",
28
+ "confidence": 0.88,
29
+ "source_module": "council",
30
+ "evidence_cited": ["auth-022"],
31
+ "timestamp": "2025-03-15T11:00:00Z"
32
+ }
33
+ ],
34
+ "expected": {
35
+ "jury_max_confidence": 0.45,
36
+ "council_recommendation": "redesign",
37
+ "must_flag": ["key rotation", "HS256"],
38
+ "must_cite": ["auth-022"],
39
+ "risk_level": "critical",
40
+ "preflight_expects": {
41
+ "touches_sensitive_area": true,
42
+ "sensitive_areas_include": ["auth"],
43
+ "chronicle_conflicts": ["auth-022"]
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "id": "auth_rs256_valid",
3
+ "description": "Proposing the already-approved RS256 pattern — should proceed",
4
+ "outcome": "Add JWT authentication to the API",
5
+ "design": "RS256 tokens with 15-minute expiry and refresh rotation stored in httpOnly cookies, matching the approved pattern in Chronicle",
6
+ "oracle_evidence": [
7
+ {
8
+ "id": "auth-031",
9
+ "key_insight": "RS256 with short-lived tokens and refresh rotation in httpOnly cookies is the approved pattern",
10
+ "decision": "RS256 with 15-min access tokens and refresh rotation stored in httpOnly cookies is the approved auth pattern",
11
+ "schema_version": 2,
12
+ "affected_areas": ["src/auth/", "src/middleware/"],
13
+ "scope": ["auth", "sessions"],
14
+ "status": "validated",
15
+ "confidence": 0.88,
16
+ "source_module": "council",
17
+ "evidence_cited": [],
18
+ "timestamp": "2025-03-15T11:00:00Z"
19
+ }
20
+ ],
21
+ "expected": {
22
+ "jury_min_confidence": 0.65,
23
+ "council_recommendation": "proceed",
24
+ "must_not_flag": ["key rotation problem"],
25
+ "risk_level": "critical",
26
+ "preflight_expects": {
27
+ "touches_sensitive_area": true
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "id": "cache_missing_lock",
3
+ "description": "Cache implementation missing stampede protection — should warn or block",
4
+ "outcome": "Cache expensive product catalogue queries in Redis",
5
+ "design": "On cache miss, fetch from database and write to Redis with a 5-minute TTL. No locking strategy.",
6
+ "oracle_evidence": [
7
+ {
8
+ "id": "cache-008",
9
+ "key_insight": "Redis cache without stampede protection caused DB overload during peak traffic",
10
+ "decision": "Redis cache without stampede protection caused DB overload — all cache misses hit DB simultaneously during spikes",
11
+ "schema_version": 2,
12
+ "affected_areas": ["src/cache/", "src/api/products.ts"],
13
+ "scope": ["cache", "performance"],
14
+ "status": "refuted",
15
+ "confidence": 0.85,
16
+ "source_module": "council",
17
+ "evidence_cited": [],
18
+ "timestamp": "2025-02-20T14:00:00Z"
19
+ }
20
+ ],
21
+ "expected": {
22
+ "jury_max_confidence": 0.60,
23
+ "council_recommendation": "redesign",
24
+ "must_flag": ["stampede", "lock"],
25
+ "must_cite": ["cache-008"],
26
+ "risk_level": "medium",
27
+ "preflight_expects": {
28
+ "chronicle_conflicts": ["cache-008"]
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "id": "db_naive_not_null",
3
+ "description": "Naive NOT NULL migration on a large table — should block",
4
+ "outcome": "Add a NOT NULL region column to the users table",
5
+ "design": "Run ALTER TABLE users ADD COLUMN region TEXT NOT NULL DEFAULT 'uk'",
6
+ "oracle_evidence": [
7
+ {
8
+ "id": "db-017",
9
+ "key_insight": "Avoid naive locking migrations on large tables",
10
+ "decision": "Avoid naive locking migrations on large tables — use shadow column pattern or pg_repack",
11
+ "schema_version": 2,
12
+ "affected_areas": ["db/migrations", "src/models/user.ts"],
13
+ "scope": ["database", "migrations"],
14
+ "status": "refuted",
15
+ "confidence": 0.91,
16
+ "source_module": "council",
17
+ "evidence_cited": [],
18
+ "timestamp": "2025-01-10T10:00:00Z"
19
+ }
20
+ ],
21
+ "expected": {
22
+ "jury_max_confidence": 0.55,
23
+ "council_recommendation": "redesign",
24
+ "must_flag": ["lock strategy", "rollback"],
25
+ "must_cite": ["db-017"],
26
+ "risk_level": "high",
27
+ "preflight_expects": {
28
+ "rollback_mentioned": false,
29
+ "chronicle_conflicts": ["db-017"]
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "id": "logging_pii_leak",
3
+ "description": "Logging request bodies that contain PII — should block",
4
+ "outcome": "Add structured request logging to the API for debugging",
5
+ "design": "Log full request body including email, name, and any user-submitted fields to stdout using JSON format",
6
+ "oracle_evidence": [
7
+ {
8
+ "id": "sec-003",
9
+ "key_insight": "Logging PII fields violates GDPR and our data retention policy",
10
+ "decision": "Logging PII fields (email, name, address) violates GDPR Article 5 — all request logging must scrub or omit PII fields",
11
+ "schema_version": 2,
12
+ "affected_areas": ["src/middleware/logger.ts", "src/api/"],
13
+ "scope": ["pii", "compliance", "logging"],
14
+ "status": "validated",
15
+ "confidence": 0.95,
16
+ "source_module": "council",
17
+ "evidence_cited": [],
18
+ "timestamp": "2025-01-05T08:00:00Z"
19
+ }
20
+ ],
21
+ "expected": {
22
+ "jury_max_confidence": 0.40,
23
+ "council_recommendation": "redesign",
24
+ "must_flag": ["PII", "GDPR", "email"],
25
+ "must_cite": ["sec-003"],
26
+ "risk_level": "high",
27
+ "preflight_expects": {
28
+ "touches_sensitive_area": true,
29
+ "sensitive_areas_include": ["pii"]
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "id": "migration_with_rollback",
3
+ "description": "DB migration that explicitly addresses rollback and uses safe pattern — should proceed",
4
+ "outcome": "Add a NOT NULL region column to the users table",
5
+ "design": "Use shadow column pattern: add region TEXT NULLABLE, backfill via batched update, then add NOT NULL constraint after 100% fill confirmed. Rollback: drop shadow column. Uses pg_repack to avoid exclusive locks.",
6
+ "oracle_evidence": [
7
+ {
8
+ "id": "db-017",
9
+ "key_insight": "Avoid naive locking migrations on large tables — use shadow column pattern or pg_repack",
10
+ "decision": "Avoid naive locking migrations on large tables — use shadow column pattern or pg_repack",
11
+ "schema_version": 2,
12
+ "affected_areas": ["db/migrations", "src/models/user.ts"],
13
+ "scope": ["database", "migrations"],
14
+ "status": "refuted",
15
+ "confidence": 0.91,
16
+ "source_module": "council",
17
+ "evidence_cited": [],
18
+ "timestamp": "2025-01-10T10:00:00Z"
19
+ },
20
+ {
21
+ "id": "db-019",
22
+ "key_insight": "Shadow column pattern with batched backfill is the approved approach for NOT NULL migrations",
23
+ "decision": "Shadow column pattern with batched backfill is the approved approach for large NOT NULL migrations",
24
+ "schema_version": 2,
25
+ "affected_areas": ["db/migrations"],
26
+ "scope": ["database", "migrations"],
27
+ "status": "validated",
28
+ "confidence": 0.87,
29
+ "source_module": "council",
30
+ "evidence_cited": ["db-017"],
31
+ "timestamp": "2025-02-01T12:00:00Z"
32
+ }
33
+ ],
34
+ "expected": {
35
+ "jury_min_confidence": 0.65,
36
+ "council_recommendation": "proceed",
37
+ "risk_level": "high",
38
+ "preflight_expects": {
39
+ "rollback_mentioned": true,
40
+ "chronicle_conflicts": ["db-017"]
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "no_evidence_novel_design",
3
+ "description": "Novel design with no Chronicle evidence either way — should investigate-more",
4
+ "outcome": "Implement real-time collaboration features using WebSockets",
5
+ "design": "Use Socket.io for bi-directional communication, Redis pub/sub for multi-instance message fanout, and optimistic UI updates with conflict resolution via last-write-wins",
6
+ "oracle_evidence": [],
7
+ "expected": {
8
+ "jury_max_confidence": 0.65,
9
+ "council_recommendation": "investigate-more",
10
+ "risk_level": "medium",
11
+ "preflight_expects": {
12
+ "touches_sensitive_area": false,
13
+ "chronicle_conflicts": []
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "id": "payment_no_idempotency",
3
+ "description": "Payment charge without idempotency key — should block",
4
+ "outcome": "Implement one-click repurchase for customers",
5
+ "design": "On button click, POST /api/charge with the stored card token and amount. Retry on network failure up to 3 times.",
6
+ "oracle_evidence": [
7
+ {
8
+ "id": "pay-004",
9
+ "key_insight": "Payment charges without idempotency keys caused duplicate charges during network retries",
10
+ "decision": "All payment charge requests must include a Stripe idempotency key — retries without idempotency keys caused duplicate charges in production",
11
+ "schema_version": 2,
12
+ "affected_areas": ["src/payments/", "src/api/checkout.ts"],
13
+ "scope": ["payments", "stripe"],
14
+ "status": "refuted",
15
+ "confidence": 0.97,
16
+ "source_module": "council",
17
+ "evidence_cited": [],
18
+ "timestamp": "2025-04-01T16:00:00Z"
19
+ }
20
+ ],
21
+ "expected": {
22
+ "jury_max_confidence": 0.40,
23
+ "council_recommendation": "redesign",
24
+ "must_flag": ["idempotency", "duplicate charge"],
25
+ "must_cite": ["pay-004"],
26
+ "risk_level": "critical",
27
+ "preflight_expects": {
28
+ "touches_sensitive_area": true,
29
+ "sensitive_areas_include": ["payments"],
30
+ "chronicle_conflicts": ["pay-004"]
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "id": "redis_session_rejected",
3
+ "description": "Proposing Redis sessions when they were already removed — should block",
4
+ "outcome": "Implement user session management",
5
+ "design": "Store session data in Redis with a 30-minute TTL and auto-extend on activity. Use express-session with connect-redis.",
6
+ "oracle_evidence": [
7
+ {
8
+ "id": "auth-015",
9
+ "key_insight": "Redis sessions removed due to memory overhead at scale and operational complexity",
10
+ "decision": "Redis sessions removed — memory overhead at scale was unsustainable and operational complexity (Redis cluster, failover) added too much risk",
11
+ "schema_version": 2,
12
+ "affected_areas": ["src/auth/", "src/middleware/session.ts"],
13
+ "scope": ["auth", "sessions", "infrastructure"],
14
+ "status": "refuted",
15
+ "confidence": 0.89,
16
+ "source_module": "council",
17
+ "evidence_cited": [],
18
+ "timestamp": "2025-02-10T15:00:00Z"
19
+ }
20
+ ],
21
+ "expected": {
22
+ "jury_max_confidence": 0.50,
23
+ "council_recommendation": "redesign",
24
+ "must_flag": ["memory overhead", "Redis"],
25
+ "must_cite": ["auth-015"],
26
+ "risk_level": "critical",
27
+ "preflight_expects": {
28
+ "touches_sensitive_area": true,
29
+ "chronicle_conflicts": ["auth-015"]
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "id": "safe_refactor",
3
+ "description": "Low-risk internal refactor with no sensitive areas — should proceed without friction",
4
+ "outcome": "Rename internal helper functions in the reporting module for consistency",
5
+ "design": "Rename generateCsvReport to exportReportAsCsv and generatePdfReport to exportReportAsPdf in src/reports/. Update all callers. No behaviour change.",
6
+ "oracle_evidence": [],
7
+ "expected": {
8
+ "jury_min_confidence": 0.70,
9
+ "council_recommendation": "proceed",
10
+ "risk_level": "low",
11
+ "preflight_expects": {
12
+ "touches_sensitive_area": false,
13
+ "rollback_mentioned": false,
14
+ "chronicle_conflicts": []
15
+ }
16
+ }
17
+ }