From aa3f850ad89cff76e05959cd674a979eba2be190 Mon Sep 17 00:00:00 2001 From: anthonybyrnes <150540200+AJBcoding@users.noreply.github.com> Date: Tue, 5 May 2026 00:14:19 -0700 Subject: [PATCH 01/12] =?UTF-8?q?acceptance:=20add=20English=20MA=20(CIP?= =?UTF-8?q?=2023.01,=20n=3D29,=20-4.97%)=20=E2=80=94=20REV-2=20third=20CSU?= =?UTF-8?q?LB=20direct-fail=20(cp-j0gw.1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per REV-2 narrative correction (cp-fiq8.2.2, 2026-05-03), AHEAD's published per-cell flag override surfaces English MA as the third CSULB direct-fail beyond Music MM and Art MFA. Fixture data was already present (rows 1380-1398); test now asserts: - verdict = FAIL (PPD-published, agrees with tool re-derivation) - cohort_count = 29 - ep_gap_pct = -0.0497 (inside ±15% R19 noise band) - R08 (graduate lowest-of-three) fires - R18 (graduate loan-cap advisory) fires - R19 (noise band) fires with gap_tool_derived provenance Source: analyses/csulb-impact-passes/narrative.md REV-2; senate-distribution-2026-05-04 packet. 11/11 acceptance tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/acceptance.test.ts | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/tests/acceptance.test.ts b/tests/acceptance.test.ts index 7431c22..85213c9 100644 --- a/tests/acceptance.test.ts +++ b/tests/acceptance.test.ts @@ -1,9 +1,12 @@ // Acceptance — reproduces dean memo v10 §1 numbers from the CSULB fixture. // // Source: briefs/2026-04-23-dean-royce-OBBBA-EP-synthesis.v10.md §1 bottom-line. +// Extension (cp-j0gw.1): English MA per REV-2 narrative correction (cp-fiq8.2.2, +// 2026-05-03) — third CSULB direct-fail surfaced after AHEAD-flag-override fix. // -// Music MM (5009/Master's): FAIL @ -32.65%, n=28 -// Art MFA (5007/Master's): FAIL @ -20.40%, n=23 +// Music MM (5009/Master's): FAIL @ -32.65%, n=28 +// Art MFA (5007/Master's): FAIL @ -20.40%, n=23 +// English MA (2301/Master's): FAIL @ -4.97%, n=29 (cp-j0gw.1, REV-2) // Theatre BA (5005/Bachelor): PASS @ +6.06%, n=81 — within R19 noise band // Cinematic Arts BA (5006/Bach): NOT MEASURED (B16 invisibility — absent from PPD) @@ -47,6 +50,32 @@ describe('Acceptance — dean memo v10 §1', () => { expect(v?.rules_fired.map((r) => r.id)).toContain('R17'); }); + it('English MA (2301/Master\'s) — FAIL at -4.97%, n=29 (REV-2 cp-j0gw.1)', () => { + // REV-2 narrative correction (cp-fiq8.2.2, 2026-05-03): the AHEAD-published + // per-cell flag override surfaces this as the third CSULB direct-fail. Gap + // is small (-4.97%) but PPD-published verdict is authoritative — falls + // OUTSIDE the noise band trigger range only because PPD asserts FAIL. + // Source: analyses/csulb-impact-passes/narrative.md REV-2; senate packet + // briefs/senate-distribution-2026-05-04/. + const v = find('2301', "Master's"); + expect(v).toBeDefined(); + expect(v?.verdict).toBe('FAIL'); + expect(v?.cohort_count).toBe(29); + expect(v?.median_earn_p4).toBe(47026.0); + expect(v?.benchmark).toBe(49483.0); + expect(v?.ep_gap_pct).toBeCloseTo(-0.0497, 4); + // PPD-published agrees with tool re-derivation (both FAIL). + expect(v?.cross_validation.status).toBe('agree'); + // R08 fires — graduate lowest-of-three benchmark route. + expect(v?.rules_fired.map((r) => r.id)).toContain('R08'); + // R18 fires — graduate loan-cap advisory. + expect(v?.rules_fired.map((r) => r.id)).toContain('R18'); + // R19 fires — gap is INSIDE ±15% noise band; verdict could read other way + // on the same data with privacy noise. + expect(v?.noise_band.fired).toBe(true); + expect(v?.noise_band.provenance).toBe('gap_tool_derived'); + }); + it('Theatre BA (5005/Bachelor) — PASS at +6.06%, n=81, in R19 noise band', () => { const v = find('5005', 'Bachelor'); expect(v).toBeDefined(); From 6d9a478fcba8ce085898162fa39879e16291e8a8 Mon Sep 17 00:00:00 2001 From: anthonybyrnes <150540200+AJBcoding@users.noreply.github.com> Date: Tue, 5 May 2026 00:18:02 -0700 Subject: [PATCH 02/12] disclaimers: forward-simulation framing + 'not attorneys/legislative analysts' (cp-j0gw.3 + cp-j0gw.6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two new IntegrityEnvelope fields surface as load-bearing context: 1. simulation_framing (cp-j0gw.3, per cp-dw65) — every verdict is a FORWARD SIMULATION on AHEAD's PPD:2026 negotiator analytic file, not an observation of the implemented rule. First STATS release scheduled for 2027-07-01. 2. expertise_disclaimer (cp-j0gw.6) — explicit 'not attorneys, not legislative analysts' framing with 'Independently verify all numbers and citations against primary sources' instruction. Surfaced via: - src/citations.ts: SIMULATION_FRAMING + EXPERTISE_DISCLAIMER constants - src/types.ts: IntegrityEnvelope.simulation_framing + .expertise_disclaimer - src/engine.ts: populated in buildIntegrityEnvelope - src/api/schema.ts: required JSON-schema fields - web/index.html, persona-a.html, persona-b.html: new top-disclaimer aside above main content (yellow callout, dark-mode adapted) - web/css/main.css: .top-disclaimer styling Recipe block (per-finding 'How to reproduce' — second half of D6) lands in a follow-up commit. 200/200 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/api/schema.ts | 4 ++++ src/citations.ts | 17 +++++++++++++++++ src/engine.ts | 8 +++++++- src/types.ts | 12 ++++++++++++ tests/acceptance.test.ts | 12 ++++++++++++ web/css/main.css | 28 ++++++++++++++++++++++++++++ web/index.html | 12 ++++++++++++ web/persona-a.html | 11 +++++++++++ web/persona-b.html | 11 +++++++++++ 9 files changed, 114 insertions(+), 1 deletion(-) diff --git a/src/api/schema.ts b/src/api/schema.ts index 8f370ba..0f2c25b 100644 --- a/src/api/schema.ts +++ b/src/api/schema.ts @@ -191,6 +191,8 @@ const $defs = { 'cross_validation_disagreements', 'data_status_summary', 'primary_source_reminder', + 'simulation_framing', + 'expertise_disclaimer', ], additionalProperties: false, properties: { @@ -217,6 +219,8 @@ const $defs = { }, }, primary_source_reminder: { type: 'string' }, + simulation_framing: { type: 'string' }, + expertise_disclaimer: { type: 'string' }, }, }, AnalysisResultBase: { diff --git a/src/citations.ts b/src/citations.ts index 5e8a73c..c736c5a 100644 --- a/src/citations.ts +++ b/src/citations.ts @@ -99,3 +99,20 @@ export const M_DOCS = { /** Footer reminder rendered on every result page per spec §1.4. */ export const PRIMARY_SOURCE_REMINDER = 'Re-derive against primary sources before any external submission.'; + +/** + * Forward-simulation framing per cp-dw65 (cp-j0gw.3). Every EP-test verdict + * surfaced by this tool is a SIMULATION run against AHEAD's negotiator + * analytic file (PPD:2026), not an observation of the implemented rule. The + * first official STATS earnings-premium release is scheduled for 2027-07-01. + */ +export const SIMULATION_FRAMING = + 'FORWARD SIMULATION — verdicts shown here are computed on the PPD:2026 negotiator analytic file. The first official STATS earnings-premium release is scheduled for 2027-07-01. Conditional on AHEAD-published projections holding.'; + +/** + * Expertise disclaimer per cp-j0gw.6. We are not attorneys and not legislative + * analysts. Findings derive from publicly available data; verify against + * primary sources before any external use. + */ +export const EXPERTISE_DISCLAIMER = + 'This tool is built by educators, not attorneys or legislative analysts. Every finding is derived from publicly available federal data using the rules engine documented in this repo. Independently verify all numbers and citations against the primary sources before any external submission, public comment, or institutional decision.'; diff --git a/src/engine.ts b/src/engine.ts index 40c72cd..1e6f872 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -16,7 +16,11 @@ import type { VerdictWord, } from './types.js'; import { CITATIONS, M_DOCS } from './citations.js'; -import { PRIMARY_SOURCE_REMINDER } from './citations.js'; +import { + EXPERTISE_DISCLAIMER, + PRIMARY_SOURCE_REMINDER, + SIMULATION_FRAMING, +} from './citations.js'; import { RULES, RULE_ORDER, @@ -163,6 +167,8 @@ function buildIntegrityEnvelope( out_of_scope, }, primary_source_reminder: PRIMARY_SOURCE_REMINDER, + simulation_framing: SIMULATION_FRAMING, + expertise_disclaimer: EXPERTISE_DISCLAIMER, }; } diff --git a/src/types.ts b/src/types.ts index 825e0c4..22dd40e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -206,6 +206,18 @@ export interface IntegrityEnvelope { out_of_scope: number; }; primary_source_reminder: string; + /** + * "Forward simulation" framing per cp-dw65 (cp-j0gw.3). All EP-test verdicts + * here are SIMULATIONS run on AHEAD's negotiator analytic file (PPD:2026), + * not observations of an implemented rule. The first official STATS earnings- + * premium release is scheduled for 2027-07-01. + */ + simulation_framing: string; + /** + * "Not attorneys / not legislative analysts" disclaimer per cp-j0gw.6. + * Surfaced on every result page in addition to the primary-source reminder. + */ + expertise_disclaimer: string; } export interface PanelDescriptor { diff --git a/tests/acceptance.test.ts b/tests/acceptance.test.ts index 85213c9..5a9482d 100644 --- a/tests/acceptance.test.ts +++ b/tests/acceptance.test.ts @@ -144,6 +144,18 @@ describe('Acceptance — dean memo v10 §1', () => { expect(result.integrity_envelope.cross_validation_disagreements).toBe(0); }); + it('integrity envelope surfaces simulation framing (cp-j0gw.3)', () => { + expect(result.integrity_envelope.simulation_framing).toMatch(/FORWARD SIMULATION/); + expect(result.integrity_envelope.simulation_framing).toMatch(/PPD:2026/); + expect(result.integrity_envelope.simulation_framing).toMatch(/2027-07-01/); + }); + + it('integrity envelope surfaces expertise disclaimer (cp-j0gw.6)', () => { + expect(result.integrity_envelope.expertise_disclaimer).toMatch(/not attorneys/i); + expect(result.integrity_envelope.expertise_disclaimer).toMatch(/not.*legislative analysts/i); + expect(result.integrity_envelope.expertise_disclaimer).toMatch(/Independently verify/i); + }); + it('footer reminder is present on every result page', () => { expect(result.footer_reminder).toBe( 'Re-derive against primary sources before any external submission.', diff --git a/web/css/main.css b/web/css/main.css index 193d0f8..9463e84 100644 --- a/web/css/main.css +++ b/web/css/main.css @@ -161,6 +161,34 @@ main { margin: 0; } +/* ─── Top disclaimer (cp-j0gw.6) ─────────────────────────────────────── */ + +.top-disclaimer { + background: #fff8e6; + border-left: 4px solid #c08a00; + padding: var(--space-sm) var(--space-md); + margin: 0 var(--space-md) var(--space-md); + border-radius: 4px; + font-size: 0.95rem; + line-height: 1.45; +} + +.top-disclaimer p { + margin: 0; +} + +.top-disclaimer strong { + font-weight: 700; +} + +@media (prefers-color-scheme: dark) { + .top-disclaimer { + background: #2a2410; + border-left-color: #d6a400; + color: #f4e8c2; + } +} + /* ─── Landing page ───────────────────────────────────────────────────── */ .persona-card { diff --git a/web/index.html b/web/index.html index a1f6dac..8c1ef80 100644 --- a/web/index.html +++ b/web/index.html @@ -19,6 +19,18 @@

CSU Deans EP Tool

+ +

Which view do you need?

diff --git a/web/persona-a.html b/web/persona-a.html index 2a71a34..532b93e 100644 --- a/web/persona-a.html +++ b/web/persona-a.html @@ -12,6 +12,17 @@

Check specific programs

+ +
`; } From e0eaea7944c93054132771f605e6f7e90aec5d92 Mon Sep 17 00:00:00 2001 From: anthonybyrnes <150540200+AJBcoding@users.noreply.github.com> Date: Tue, 5 May 2026 00:29:11 -0700 Subject: [PATCH 04/12] =?UTF-8?q?panel:=20M18=20=E2=80=94=20=C2=A7=20668.1?= =?UTF-8?q?6(t)=20institution-level=20pathway=20five-pass=20convergence=20?= =?UTF-8?q?(cp-j0gw.7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New auto-triggered M-doc surfaces the load-bearing senate-packet finding: the proposed § 668.16(t) institution-level escalation design must answer FIVE independent legal frames, each independently sufficient: 1. Statutory authority (Loper Bright + major-questions canon) 2. Problem statement (OIRA Circular A-4 § 1) 3. Alternatives review (OIRA Circular A-4 § 6) 4. Uncertainty analysis (OIRA Circular A-4 § E) 5. Reasoned decisionmaking (Motor Vehicle Mfrs. v. State Farm) Auto-fires when any program at the institution surfaces as FAIL (that is the trigger condition the § 668.16(t) machinery escalates from). For CSULB this fires for Music MM, Art MFA, English MA. Source: analyses/phase-2-nprm-audit/REPORT.md § 2; senate-distribution 2026-05-04 packet § 2 ("Five-pass convergence on § 668.16(t)"). Wired through: - content/mdocs/M18.html — 5-section panel using glossary-clean voice ('institution-level escalation pathway' substitutes for forbidden literal 'cascade' per design.v6 §13) - content/citations.json — M18#FIVE-PASS-CONVERGENCE anchor - src/citations.ts — M_DOCS.M18 - src/panels.ts — auto-trigger when any FAIL verdict; per-program attribution adds M18 to FAIL drilldowns - src/api/contract.ts — PANEL_IDS (now 17) + AUTO_TRIGGERED_PANELS (9) - 4 test sites updated for new panel count Includes per-panel disclaimer footer: 'Written by educators, not attorneys or legislative analysts. Verify every statutory citation, case citation, and OIRA-Circular reference against primary source.' 203/203 tests pass. Lint-glossary clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- content/citations.json | 7 +++++ content/mdocs/M18.html | 52 +++++++++++++++++++++++++++++++++ src/api/contract.ts | 4 +-- src/citations.ts | 5 ++++ src/panels.ts | 16 +++++++++- tests/acceptance.test.ts | 4 +-- tests/api/handlers.test.ts | 4 +-- tests/api/schema-parity.test.ts | 9 +++--- tests/api/server.test.ts | 5 ++-- tests/ui/citations.test.ts | 2 +- tests/ui/render.test.ts | 4 +-- 11 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 content/mdocs/M18.html diff --git a/content/citations.json b/content/citations.json index e928dd2..16a79e9 100644 --- a/content/citations.json +++ b/content/citations.json @@ -681,6 +681,13 @@ "section_heading": "M17 — BD/CSD timelines under OBBBA §§ 85001–85002", "file": "content/mdocs/M17.html" }, + "M18#FIVE-PASS-CONVERGENCE": { + "mdoc": "M18", + "convention": "panel-theme", + "title": "§ 668.16(t) institution-level pathway — five-pass convergence (panel)", + "section_heading": "M18 — five legal frames each independently testing the institution-level escalation design", + "file": "content/mdocs/M18.html" + }, "R12#FIVE-REASONS": { "mdoc": "M12", "convention": "panel-theme", diff --git a/content/mdocs/M18.html b/content/mdocs/M18.html new file mode 100644 index 0000000..eb66256 --- /dev/null +++ b/content/mdocs/M18.html @@ -0,0 +1,52 @@ +
+
+ M18 +

When a program failure could spread to the whole institution. Proposed § 668.16(t) lets program-level failures escalate into an institution-level Title IV finding. Five independent legal frames each give the Department's chosen design a separate problem to answer.

+
+ +
+

Proposed § 668.16(t) treats sustained program-level failure as evidence of an institution-level capacity problem. The proposed mechanism — share thresholds applied across two of three measurement years — is a procedural choice the Department made on top of the program-level statute. Phase 2 of the audit walked the same proposal through five independent legal registers (statutory authority, OIRA Circular A-4 problem statement, A-4 alternatives review, A-4 uncertainty analysis, and the State Farm reasoned-decisionmaking standard). Each register raises a separate, independently sufficient question about the proposal as written.

+
+ +
+

The five frames in plain terms

+
    +
  1. Statutory authority (Loper Bright + major-questions). OBBBA § 84001 sets the program-level test. The Department points to § 498 of the Higher Education Act for the institution-level institution-level escalation pathway. Loper Bright instructs courts to read the statute themselves rather than defer; the major-questions canon adds skepticism whenever an agency claims an unusual or large new power inside a smaller statutory authority. Whether the program-level statute authorizes an institution-level Title IV consequence is a question the rule's text alone does not settle.
  2. +
  3. Problem statement (Circular A-4 § 1). The preamble describes the institution-level mechanism with words like "would suggest" and "could represent." A-4 § 1 asks for a demonstrated problem, not an asserted one — empirical evidence that the institution-level institution-level escalation pathway addresses a real and otherwise-unaddressed harm. The record as published does not show that demonstration.
  4. +
  5. Alternatives review (Circular A-4 § 6). The preamble considers and discards alternatives — disclosure-only, threshold variations, phase-in — but does not provide comparable benefit-cost tables for any of them. A-4 § 6 expects each alternative considered to be analyzed at the same depth as the chosen design.
  6. +
  7. Uncertainty (Circular A-4 § E). The 50% threshold and the 2-of-3-year window have no published sensitivity bounds. The published-flag-publication gap (cells whose pool-level fail flag the Department does not publish) is not analyzed. A-4 § E asks for sensitivity analysis around the choices that swing the rule's outcome.
  8. +
  9. Reasoned decisionmaking (Motor Vehicle Mfrs. Ass'n v. State Farm, 463 U.S. 29). State Farm's four prongs — relevant data considered, satisfactory explanation given, judgment supported by the record, alternatives addressed — form the floor for a court's arbitrary-and-capricious review. The institution-level institution-level escalation pathway design as published does not, on its face, meet all four prongs.
  10. +
+

None of these five frames is by itself a verdict. Each is a separate legal hook for stakeholders, public commenters, or reviewing courts to test the proposed § 668.16(t) design.

+
+ +
+

How this might sound in plain language

+

If a program at this institution fails the earnings-premium test in the first measurement year, the rule's program-level consequences attach to that program (M05 / M07). The institution-level § 668.16(t) institution-level escalation pathway is a separate machinery: if a sustained share of the institution's recipients or aid dollars are in failing programs across multiple years, the Department could find the institution itself out of compliance. Whether the rule as written can support that escalation — given that the underlying earnings-premium statute is written program-by-program — is the load-bearing legal question this panel exists to surface.

+
+ +
+

Sources

+ +
+ +
+

+ About this panel. + Written by educators, not attorneys or legislative analysts. Each of the + five frames is a working hypothesis about a published proposed rule, not + a legal opinion. Verify every statutory citation, case citation, and + OIRA-Circular reference against the primary source before any external + submission, public comment, or institutional decision. +

+
+
diff --git a/src/api/contract.ts b/src/api/contract.ts index 771159a..6a9cbf4 100644 --- a/src/api/contract.ts +++ b/src/api/contract.ts @@ -140,13 +140,13 @@ export type AnalysisPostResponse = AnalysisGetResponse; export const PANEL_IDS = [ 'M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', - 'M10', 'M11', 'M12', 'M13', 'M14', 'M15', 'M17', + 'M10', 'M11', 'M12', 'M13', 'M14', 'M15', 'M17', 'M18', ] as const; export type PanelId = (typeof PANEL_IDS)[number]; export const AUTO_TRIGGERED_PANELS = [ - 'M01', 'M03', 'M04', 'M05', 'M07', 'M12', 'M13', 'M14', + 'M01', 'M03', 'M04', 'M05', 'M07', 'M12', 'M13', 'M14', 'M18', ] as const satisfies readonly PanelId[]; export const LEARN_MORE_PANELS = [ diff --git a/src/citations.ts b/src/citations.ts index c736c5a..38f49d4 100644 --- a/src/citations.ts +++ b/src/citations.ts @@ -93,6 +93,11 @@ export const M_DOCS = { M14: 'M14-targeting-named-populations', M15: 'M15-strategic-response-scenarios', M17: 'M17-bd-csd-delays', + // M18 added per cp-j0gw.7 — § 668.16(t) institution-level cascade + // 5-pass convergence from Phase 2 nprm-audit (Loper Bright + MQD + + // OIRA A-4 §1/§6/§E + State Farm). Auto-triggers when any program + // at the institution surfaces as FAIL. + M18: 'M18-668-16-t-five-pass-convergence', // M16 is intentionally deferred per analyses/mechanisms/_index.md. } as const; diff --git a/src/panels.ts b/src/panels.ts index 24e74d9..3608ea2 100644 --- a/src/panels.ts +++ b/src/panels.ts @@ -115,6 +115,20 @@ const PANELS: PanelDef[] = [ : null; }, }, + { + // cp-j0gw.7 — § 668.16(t) institution-level cascade. Auto-fires when any + // program at the institution surfaces as FAIL, since that is the trigger + // condition the cascade design is meant to escalate from. + id: 'M18', + m_doc: M_DOCS.M18, + header: + "When a program failure could spread to the whole institution. Proposed § 668.16(t) lets program-level failures escalate into an institution-level Title IV finding. Five independent legal frames each give the Department's chosen design a separate problem to answer.", + trigger: (verdicts) => { + const failing = verdicts.filter((v) => v.verdict === 'FAIL'); + if (failing.length === 0) return null; + return `${failing.length} failing program${failing.length === 1 ? '' : 's'} put institution-level § 668.16(t) cascade machinery in scope for this institution.`; + }, + }, ]; export function buildPanels( @@ -143,7 +157,7 @@ export function buildPanels( export function panelsTriggeredByProgram(verdict: ProgramVerdict): string[] { const ids: string[] = ['M01', 'M13']; // always fire if (verdict.verdict !== 'NOT MEASURED') ids.push('M03', 'M04'); - if (verdict.verdict === 'FAIL') ids.push('M05', 'M07'); + if (verdict.verdict === 'FAIL') ids.push('M05', 'M07', 'M18'); if (verdict.verdict === 'NOT MEASURED') ids.push('M12'); if (verdict.rules_fired.some((r) => r.id === 'R17')) ids.push('M14'); return ids; diff --git a/tests/acceptance.test.ts b/tests/acceptance.test.ts index cc06853..d4dabec 100644 --- a/tests/acceptance.test.ts +++ b/tests/acceptance.test.ts @@ -127,10 +127,10 @@ describe('Acceptance — dean memo v10 §1', () => { expect(r12?.note).toMatch(/cohort cascade did not run/i); }); - it('all eight v6 panels auto-trigger on the CSULB run', () => { + it('all nine v6+ext panels auto-trigger on the CSULB run (cp-j0gw.7 adds M18)', () => { const ids = result.panels.map((p) => p.id); expect(ids.sort()).toEqual([ - 'M01', 'M03', 'M04', 'M05', 'M07', 'M12', 'M13', 'M14', + 'M01', 'M03', 'M04', 'M05', 'M07', 'M12', 'M13', 'M14', 'M18', ]); }); diff --git a/tests/api/handlers.test.ts b/tests/api/handlers.test.ts index ed49cb6..08a586c 100644 --- a/tests/api/handlers.test.ts +++ b/tests/api/handlers.test.ts @@ -163,11 +163,11 @@ describe('GET /api/v1/analysis/:unitid (Persona B)', () => { expect(theatre?.noise_band.provenance).toBe('gap_tool_derived'); }); - it('all eight v6 panels auto-trigger', () => { + it('all nine v6+ext panels auto-trigger (cp-j0gw.7 adds M18)', () => { const result = handleAnalysisGet(loader, '110583', FROZEN_TIME); const body = result.body as AnalysisGetResponse; expect(body.panels.map((p) => p.id).sort()).toEqual([ - 'M01', 'M03', 'M04', 'M05', 'M07', 'M12', 'M13', 'M14', + 'M01', 'M03', 'M04', 'M05', 'M07', 'M12', 'M13', 'M14', 'M18', ]); }); diff --git a/tests/api/schema-parity.test.ts b/tests/api/schema-parity.test.ts index d49a400..5fa983b 100644 --- a/tests/api/schema-parity.test.ts +++ b/tests/api/schema-parity.test.ts @@ -90,15 +90,16 @@ describe('JSON Schema parity with engine output', () => { } }); - it('AUTO_TRIGGERED_PANELS matches spec §5 (eight panels)', () => { + it('AUTO_TRIGGERED_PANELS matches spec §5 + cp-j0gw.7 ext (nine panels incl. M18)', () => { expect([...AUTO_TRIGGERED_PANELS].sort()).toEqual([ - 'M01', 'M03', 'M04', 'M05', 'M07', 'M12', 'M13', 'M14', + 'M01', 'M03', 'M04', 'M05', 'M07', 'M12', 'M13', 'M14', 'M18', ]); }); - it('PANEL_IDS excludes M16 (intentionally deferred)', () => { + it('PANEL_IDS excludes M16 (intentionally deferred); cp-j0gw.7 adds M18', () => { expect(PANEL_IDS).not.toContain('M16'); - expect(PANEL_IDS).toHaveLength(16); + expect(PANEL_IDS).toContain('M18'); + expect(PANEL_IDS).toHaveLength(17); }); it('integrity envelope required fields all present in the engine output', () => { diff --git a/tests/api/server.test.ts b/tests/api/server.test.ts index bda198f..a0cf5d3 100644 --- a/tests/api/server.test.ts +++ b/tests/api/server.test.ts @@ -107,12 +107,13 @@ describe('Server integration — transport layer', () => { expect(body.error.code).toBe('BAD_REQUEST'); }); - it('GET /api/v1/panels returns the 16-panel manifest', async () => { + it('GET /api/v1/panels returns the 17-panel manifest (cp-j0gw.7 adds M18)', async () => { const res = await fetch(`${baseUrl}/api/v1/panels`); expect(res.status).toBe(200); const body = (await res.json()) as PanelManifestResponse; - expect(body.panels).toHaveLength(16); + expect(body.panels).toHaveLength(17); expect(body.panels.map((p) => p.id)).not.toContain('M16'); + expect(body.panels.map((p) => p.id)).toContain('M18'); }); it('GET /api/v1/panels/M16 returns 404 with deferral note', async () => { diff --git a/tests/ui/citations.test.ts b/tests/ui/citations.test.ts index 5cfa1f4..15d8b97 100644 --- a/tests/ui/citations.test.ts +++ b/tests/ui/citations.test.ts @@ -69,7 +69,7 @@ describe('citations — case-insensitive lookup', () => { // `sources`, not `mdoc_anchors`, and are out of scope here. setCitationsForTesting(realCitations); const cits = await loadCitations('cache-hit'); - const panelHtmls = ['M01', 'M03', 'M04', 'M05', 'M07', 'M12', 'M13', 'M14'].map( + const panelHtmls = ['M01', 'M03', 'M04', 'M05', 'M07', 'M12', 'M13', 'M14', 'M18'].map( (id) => readFileSync(resolve(__dirname, '..', '..', 'content', 'mdocs', `${id}.html`), 'utf8'), ); const panelAnchorKeys: string[] = []; diff --git a/tests/ui/render.test.ts b/tests/ui/render.test.ts index 0296e87..69ec6a6 100644 --- a/tests/ui/render.test.ts +++ b/tests/ui/render.test.ts @@ -142,8 +142,8 @@ describe('renderer — full CSULB result (Persona B)', () => { expect(matches!.length).toBeGreaterThanOrEqual(2); }); - it('renders 8 auto-triggered panel blocks (M01,M03,M04,M05,M07,M12,M13,M14)', () => { - for (const id of ['M01', 'M03', 'M04', 'M05', 'M07', 'M12', 'M13', 'M14']) { + it('renders 9 auto-triggered panel blocks (M01,M03,M04,M05,M07,M12,M13,M14,M18 — cp-j0gw.7)', () => { + for (const id of ['M01', 'M03', 'M04', 'M05', 'M07', 'M12', 'M13', 'M14', 'M18']) { expect(html).toContain(`id="panel-${id}"`); } }); From d35bbe459acb2759f1941a86e4b518e6a3b988f6 Mon Sep 17 00:00:00 2001 From: anthonybyrnes <150540200+AJBcoding@users.noreply.github.com> Date: Tue, 5 May 2026 00:34:13 -0700 Subject: [PATCH 05/12] learn: Mechanism Library + reading-depth toggle + Public Sources page (cp-j0gw.9 + .10 + .12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three additions per chair direction (this-evening message): the site must teach the rule independently of running a check, must let readers control depth, and must make the public-data provenance visible everywhere. cp-j0gw.9 — Mechanism Library (web/learn.html + web/js/learn.js): • Standalone /learn/ page surfaces every M-doc panel as instructive content, organized into 4 topic groups: 1. How the rule works (M01, M03, M04, M07) 2. What happens when a program fails (M05, M06, M18, M07) 3. Data quality and verdict edges (M12, M13, M14) 4. Reading and learning more (M02, M08, M09, M10, M11, M15, M17) • Loads each panel HTML inline via fetch. • Independent of any institutional check — pure learning surface. cp-j0gw.10 — Reading-depth toggle (Brief / Standard / Detail): • Brief = .mdoc-summary only (1-paragraph TLDR per panel). • Standard = + .mdoc-mechanism (how-it-works walk). • Detail = + .mdoc-citations + .mdoc-footer (full citations + per-panel disclaimer). • Persisted to localStorage; defaults to Standard. • CSS-only visibility (body[data-depth=...] selectors over .mdoc-summary / .mdoc-mechanism / .mdoc-citations / .mdoc-footer). • No M-doc HTML changes required — uses the existing structure. cp-j0gw.12 — Public Sources page + badge: • web/sources.html — full inventory of every primary source the tool reads or cites (PPD:2026 file 112908, IPEDS C-survey, OBBBA Public Law 119-21, NPRM FR 2026-07666, OIRA Circular A-4, Loper Bright, State Farm, plus the tool's own MIT-licensed repo). • Each source: publisher, grain, what the tool reads from it, verification path, primary-source URL where applicable. • Green 'Public data only · No private or institutional data leaves your browser' badge in every page header (index, persona-a, persona-b, learn, sources). • Header nav links Mechanism Library + Sources from every page. Build script unchanged (recursively copies web/) — new pages flow through automatically. dist-pages/ verified to include all 5 HTML pages, learn.css, learn.js. 203/203 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/css/learn.css | 215 +++++++++++++++++++++++++++++++++++++++++++++ web/index.html | 9 ++ web/js/learn.js | 77 ++++++++++++++++ web/learn.html | 121 +++++++++++++++++++++++++ web/persona-a.html | 9 ++ web/persona-b.html | 9 ++ web/sources.html | 123 ++++++++++++++++++++++++++ 7 files changed, 563 insertions(+) create mode 100644 web/css/learn.css create mode 100644 web/js/learn.js create mode 100644 web/learn.html create mode 100644 web/sources.html diff --git a/web/css/learn.css b/web/css/learn.css new file mode 100644 index 0000000..4e8fa04 --- /dev/null +++ b/web/css/learn.css @@ -0,0 +1,215 @@ +/* learn.css — Mechanism Library page (cp-j0gw.9) + reading-depth toggle (cp-j0gw.10) */ + +.public-data-badge { + display: inline-flex; + align-items: center; + gap: 0.5rem; + background: #e6f4ea; + color: #14532d; + padding: 0.35rem 0.75rem; + border-radius: 999px; + font-size: 0.85rem; + font-weight: 600; + margin-top: 0.75rem; +} + +.public-data-badge .badge-dot { + color: #16a34a; + font-size: 0.65rem; +} + +@media (prefers-color-scheme: dark) { + .public-data-badge { + background: #14321b; + color: #b6e8c4; + } +} + +/* Reading-depth toggle */ + +.learn-toolbar { + margin: 1rem; + padding: 1rem; + background: var(--color-card); + border: 1px solid var(--color-border); + border-radius: var(--radius); +} + +.depth-toggle { + border: none; + padding: 0; + margin: 0; + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: center; +} + +.depth-toggle legend { + font-weight: 700; + margin-right: 0.75rem; + padding: 0; +} + +.depth-toggle label { + display: inline-flex; + align-items: center; + gap: 0.35rem; + background: rgba(0, 0, 0, 0.04); + padding: 0.35rem 0.75rem; + border-radius: 4px; + cursor: pointer; + font-size: 0.95rem; +} + +.depth-toggle label:has(input:checked) { + background: #1f3a8a; + color: #fff; +} + +.depth-toggle input { + margin: 0; +} + +.depth-help { + margin: 0.75rem 0 0; + font-size: 0.88rem; + color: var(--color-text-muted); +} + +/* Topic groupings */ + +.learn-toc { + margin: 1rem; +} + +.learn-toc h2 { + margin: 0 0 0.5rem; +} + +.learn-group { + margin-bottom: 1.25rem; +} + +.learn-group h3 { + margin: 0 0 0.35rem; + font-size: 1rem; + color: var(--color-text-muted); + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.learn-group ul { + margin: 0; + padding-left: 1.25rem; +} + +.learn-group li { + margin: 0.2rem 0; +} + +/* Panel region — sequence of M-doc HTMLs loaded inline */ + +.panel-region { + margin: 1rem; +} + +.panel-region .panel-loading, +.panel-region .panel-error { + padding: 1rem; + background: var(--color-card); + border-left: 4px solid #888; + border-radius: 4px; +} + +.panel-region .panel-error { + border-left-color: #c44536; + color: #7a1f17; +} + +.panel-region .mdoc-panel { + background: var(--color-card); + border: 1px solid var(--color-border); + border-radius: var(--radius); + padding: 1.25rem; + margin-bottom: 1rem; +} + +.panel-region .mdoc-panel .mdoc-id { + display: inline-block; + background: #1f3a8a; + color: #fff; + font-weight: 700; + padding: 0.1rem 0.5rem; + border-radius: 4px; + margin-right: 0.5rem; + font-size: 0.85rem; +} + +.panel-region .mdoc-panel .mdoc-title { + display: inline; + font-size: 1.1rem; + line-height: 1.4; +} + +.panel-region .mdoc-panel section { + margin-top: 1rem; +} + +.panel-region .mdoc-panel h3 { + margin: 0 0 0.5rem; + font-size: 0.95rem; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--color-text-muted); +} + +.panel-region .mdoc-panel ul, +.panel-region .mdoc-panel ol { + margin: 0 0 0.5rem 1.25rem; +} + +.panel-region .mdoc-panel .mdoc-footer { + border-top: 1px dashed rgba(0, 0, 0, 0.12); + margin-top: 1rem; + padding-top: 0.75rem; + font-size: 0.9rem; + color: var(--color-text-muted); +} + +/* Reading-depth visibility (cp-j0gw.10). + Brief = .mdoc-summary only. + Standard = + .mdoc-mechanism. + Detail = + .mdoc-citations + .mdoc-footer. */ + +body[data-depth="brief"] .panel-region .mdoc-mechanism, +body[data-depth="brief"] .panel-region .mdoc-citations, +body[data-depth="brief"] .panel-region .mdoc-footer { + display: none; +} + +body[data-depth="standard"] .panel-region .mdoc-citations, +body[data-depth="standard"] .panel-region .mdoc-footer { + display: none; +} + +/* Header nav (cp-j0gw.9) */ + +.header-nav { + margin: 0.75rem 0 0; + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +.header-nav a { + color: var(--color-text); + text-decoration: none; + border-bottom: 2px solid #1f3a8a; + padding-bottom: 2px; + font-weight: 600; +} + +.header-nav a:hover { + color: #1f3a8a; +} diff --git a/web/index.html b/web/index.html index 8c1ef80..872b29f 100644 --- a/web/index.html +++ b/web/index.html @@ -9,6 +9,7 @@ content="Public deterministic web tool that lets arts deans model their institution's exposure to the OBBBA Earnings-Premium test." /> +