@validation-os/core 0.3.0 → 0.4.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.
@@ -9,11 +9,16 @@ __export(derivation_exports, {
9
9
  RUNG_ANCHOR: () => RUNG_ANCHOR,
10
10
  W0: () => W0,
11
11
  confidence: () => confidence,
12
+ confidenceAttribution: () => confidenceAttribution,
13
+ confidenceTrajectory: () => confidenceTrajectory,
12
14
  derivedImpacts: () => derivedImpacts,
15
+ experimentProgress: () => experimentProgress,
16
+ isConcluded: () => isConcluded,
13
17
  isGoalRung: () => isGoalRung,
14
18
  readingStrength: () => readingStrength,
15
19
  risk: () => risk,
16
20
  round2: () => round2,
21
+ scoreAndDedupe: () => scoreAndDedupe,
17
22
  sign: () => sign,
18
23
  sourceQuality: () => sourceQuality
19
24
  });
@@ -66,6 +71,9 @@ function sign(result) {
66
71
  if (result === "Invalidated") return -1;
67
72
  return 0;
68
73
  }
74
+ function isConcluded(result) {
75
+ return result === "Validated" || result === "Invalidated";
76
+ }
69
77
  function readingStrength(input) {
70
78
  const s = sign(input.result);
71
79
  if (s === 0) return 0;
@@ -83,12 +91,12 @@ function sourceQuality(representativeness, credibility) {
83
91
 
84
92
  // src/derivation/confidence.ts
85
93
  var W0 = 100;
86
- function confidence(readings) {
87
- const scored = readings.filter((r) => r.result === "Validated" || r.result === "Invalidated").map((r) => ({
88
- input: r,
89
- strength: readingStrength(r),
90
- sq: sourceQuality(r.representativeness, r.credibility)
91
- })).filter((x) => x.strength !== 0);
94
+ function scoreAndDedupe(readings) {
95
+ const scored = readings.filter((r) => isConcluded(r.result)).map((r) => {
96
+ const strength = readingStrength(r);
97
+ const sq = sourceQuality(r.representativeness, r.credibility);
98
+ return { input: r, strength, sq, weight: Math.abs(strength) * sq };
99
+ }).filter((x) => x.strength !== 0);
92
100
  const best = /* @__PURE__ */ new Map();
93
101
  for (const x of scored) {
94
102
  if (isGoalRung(x.input.rung)) {
@@ -100,16 +108,110 @@ function confidence(readings) {
100
108
  const better = !cur || Math.abs(x.strength) > Math.abs(cur.strength) || Math.abs(x.strength) === Math.abs(cur.strength) && (x.input.date || "") > (cur.input.date || "");
101
109
  if (better) best.set(key, x);
102
110
  }
111
+ return [...best.values()];
112
+ }
113
+ function confidence(readings) {
114
+ const winners = scoreAndDedupe(readings);
103
115
  let num = 0;
104
116
  let den = W0;
105
- for (const x of best.values()) {
106
- const w = Math.abs(x.strength) * x.sq;
107
- num += w * x.strength;
108
- den += w;
117
+ for (const x of winners) {
118
+ num += x.weight * x.strength;
119
+ den += x.weight;
109
120
  }
110
121
  return den > 0 ? round2(num / den) : 0;
111
122
  }
112
123
 
124
+ // src/derivation/attribution.ts
125
+ function bucketOf(r) {
126
+ if (r.experimentId) {
127
+ return {
128
+ key: r.experimentId,
129
+ kind: "experiment",
130
+ experimentId: r.experimentId,
131
+ goalId: null
132
+ };
133
+ }
134
+ if (r.goalId) {
135
+ return {
136
+ key: `goal:${r.goalId}`,
137
+ kind: "goal",
138
+ experimentId: null,
139
+ goalId: r.goalId
140
+ };
141
+ }
142
+ return { key: "direct", kind: "direct", experimentId: null, goalId: null };
143
+ }
144
+ function confidenceAttribution(readings) {
145
+ const winners = scoreAndDedupe(readings);
146
+ const den = winners.reduce((acc, x) => acc + x.weight, W0);
147
+ const byKey = /* @__PURE__ */ new Map();
148
+ let num = 0;
149
+ for (const x of winners) {
150
+ num += x.weight * x.strength;
151
+ const b = bucketOf(x.input);
152
+ const contribution = x.weight * x.strength / den;
153
+ const cur = byKey.get(b.key);
154
+ if (cur) {
155
+ cur.contribution += contribution;
156
+ cur.readingCount += 1;
157
+ cur.readingIds.push(x.input.id);
158
+ } else {
159
+ byKey.set(b.key, {
160
+ key: b.key,
161
+ kind: b.kind,
162
+ experimentId: b.experimentId,
163
+ goalId: b.goalId,
164
+ contribution,
165
+ magnitude: 0,
166
+ // finalised below
167
+ readingCount: 1,
168
+ readingIds: [x.input.id]
169
+ });
170
+ }
171
+ }
172
+ const movers = [...byKey.values()].map((m) => ({
173
+ ...m,
174
+ contribution: round2(m.contribution),
175
+ magnitude: round2(Math.abs(m.contribution))
176
+ }));
177
+ movers.sort((a, b) => b.magnitude - a.magnitude);
178
+ return {
179
+ confidence: den > 0 ? round2(num / den) : 0,
180
+ movers
181
+ };
182
+ }
183
+
184
+ // src/derivation/progress.ts
185
+ function experimentProgress(bars) {
186
+ const total = bars.length;
187
+ const settled = bars.filter(
188
+ (b) => typeof b.barVerdict === "string" && b.barVerdict.trim() !== ""
189
+ ).length;
190
+ return {
191
+ total,
192
+ settled,
193
+ toGo: total - settled,
194
+ concluded: total > 0 && settled === total
195
+ };
196
+ }
197
+
198
+ // src/derivation/trajectory.ts
199
+ function confidenceTrajectory(readings) {
200
+ const concluded = readings.filter((r) => isConcluded(r.result));
201
+ const undated = concluded.filter((r) => !r.date);
202
+ const dated = concluded.filter((r) => r.date);
203
+ if (dated.length === 0) return [];
204
+ const dates = [...new Set(dated.map((r) => r.date))].sort();
205
+ return dates.map((date) => ({
206
+ date,
207
+ // Everything dated on/before this point, plus the always-present undated.
208
+ confidence: confidence([
209
+ ...undated,
210
+ ...dated.filter((r) => r.date <= date)
211
+ ])
212
+ }));
213
+ }
214
+
113
215
  // src/derivation/impact.ts
114
216
  function derivedImpacts(assumptions, basedOnCounts = {}) {
115
217
  const byId = new Map(assumptions.map((a) => [a.id, a]));
@@ -161,12 +263,17 @@ export {
161
263
  GOAL_RUNG_ANCHOR,
162
264
  isGoalRung,
163
265
  sign,
266
+ isConcluded,
164
267
  readingStrength,
165
268
  sourceQuality,
166
269
  W0,
270
+ scoreAndDedupe,
167
271
  confidence,
272
+ confidenceAttribution,
273
+ experimentProgress,
274
+ confidenceTrajectory,
168
275
  derivedImpacts,
169
276
  risk,
170
277
  derivation_exports
171
278
  };
172
- //# sourceMappingURL=chunk-OS7CTJ2A.js.map
279
+ //# sourceMappingURL=chunk-4Q2B2QT3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/derivation/index.ts","../src/derivation/round.ts","../src/types.ts","../src/derivation/rung.ts","../src/derivation/strength.ts","../src/derivation/source-quality.ts","../src/derivation/confidence.ts","../src/derivation/attribution.ts","../src/derivation/progress.ts","../src/derivation/trajectory.ts","../src/derivation/impact.ts","../src/derivation/risk.ts"],"sourcesContent":["/**\n * The shared derivation module — pure functions, no I/O.\n *\n * The same module the dashboard, the API (derive-on-write), and Claude Code\n * audits all call, so every writer computes the four derived numbers\n * identically. Ported from `doshi-validation-os/migration/remodel.mjs` and\n * kept in lock-step with `skills/_shared/ontology.yaml`.\n */\nexport { round2 } from \"./round.js\";\nexport {\n RUNG_ANCHOR,\n GOAL_RUNG_ANCHOR,\n isGoalRung,\n} from \"./rung.js\";\nexport { sign, isConcluded, readingStrength } from \"./strength.js\";\nexport type { StrengthInput } from \"./strength.js\";\nexport { sourceQuality } from \"./source-quality.js\";\nexport { confidence, scoreAndDedupe, W0 } from \"./confidence.js\";\nexport type { ConfidenceReadingInput, Scored } from \"./confidence.js\";\nexport { confidenceAttribution } from \"./attribution.js\";\nexport type {\n Attribution,\n AttributionReadingInput,\n Mover,\n MoverKind,\n} from \"./attribution.js\";\nexport { experimentProgress } from \"./progress.js\";\nexport type { BarLineInput, Progress } from \"./progress.js\";\nexport { confidenceTrajectory } from \"./trajectory.js\";\nexport type { TrajectoryPoint } from \"./trajectory.js\";\nexport { derivedImpacts } from \"./impact.js\";\nexport type { ImpactAssumptionInput } from \"./impact.js\";\nexport { risk } from \"./risk.js\";\n","/**\n * Round to 2 decimals, matching the migration's `+(n).toFixed(2)`.\n * Derived values are stored/displayed rounded; full precision is only used\n * transiently inside a single computation.\n */\nexport function round2(n: number): number {\n return Number(n.toFixed(2));\n}\n","/**\n * Shared registry types — the field vocabulary the whole system speaks.\n *\n * These mirror `skills/_shared/registry-schema.md` and `ontology.yaml` (the\n * single source of truth). Field *meaning* lives there; this file is the\n * checkable TypeScript shape the packages import.\n */\n\n/** The six registers plus the `people` reference collection. */\nexport const REGISTERS = [\n \"assumptions\",\n \"experiments\",\n \"readings\",\n \"goals\",\n \"decisions\",\n \"glossary\",\n] as const;\nexport type Register = (typeof REGISTERS)[number];\n\n/** `people` is a reference collection, not one of the six registers. */\nexport type Collection = Register | \"people\";\n\n/** Every stored record carries an id, a version, and timestamps. */\nexport interface BaseRecord {\n id: string;\n /** Optimistic-concurrency token; bumped on every write. */\n version: number;\n createdAt: string;\n updatedAt: string;\n}\n\n// ── Vocabularies (canonical select-option lists) ────────────────────────────\n\nexport type AssumptionStatus = \"Draft\" | \"Live\" | \"Invalidated\";\nexport type ExperimentStatus = \"Running\" | \"Closed\";\nexport type GoalStatus = \"Draft\" | \"Active\" | \"Closed\";\nexport type DecisionStatus =\n | \"Active\"\n | \"Provisional\"\n | \"Superseded\"\n | \"Reversed\";\nexport type GlossaryStatus = \"Active\" | \"Provisional\" | \"Superseded\";\n\nexport type Result = \"Validated\" | \"Invalidated\" | \"Inconclusive\";\nexport type MagnitudeBand = \"Low\" | \"Typical\" | \"High\";\nexport type Feasibility = \"High\" | \"Medium\" | \"Low\";\n\n/** The 8-rung activity-and-strength ladder (order = strength, weakest first). */\nexport const TESTING_RUNGS = [\n \"Opinion\",\n \"Pitch-deck reaction\",\n \"Anecdotal\",\n \"Desk research\",\n \"Survey at scale\",\n \"Prototype usage\",\n] as const;\nexport const GOAL_RUNG_VALUES = [\"Signed intent\", \"Paying users\"] as const;\nexport type TestingRung = (typeof TESTING_RUNGS)[number];\nexport type GoalRung = (typeof GOAL_RUNG_VALUES)[number];\nexport type Rung = TestingRung | GoalRung;\n\n/** Representativeness and Credibility are each picked from these. */\nexport type SourceQualityPick = 1.0 | 0.7 | 0.5;\n\n// ── Records ─────────────────────────────────────────────────────────────────\n\n/** The four derived numbers stored on an assumption (never hand-typed). */\nexport interface AssumptionDerived {\n derivedImpact: number;\n risk: number;\n confidence: number;\n}\n\nexport interface AssumptionRecord extends BaseRecord {\n Title: string;\n Description: string;\n Lens: string | null;\n Theme: string[];\n /** The hand-scored seed (0–100), the only hand-scored number here. */\n Impact: number | null;\n Status: AssumptionStatus;\n Owner: string[];\n Gaps: string[];\n moot: boolean;\n /**\n * Presence-gap fields — long-form prose promoted from body sections to\n * first-class fields (OPS-1273). Their PRESENCE is a structural, blocking\n * check (see `presence.ts`): non-empty is required to move Status to `Live`\n * (the presence half of the Draft→Live gaps invariant, OPS-1251). May be\n * empty (\"\") while `Draft`.\n */\n \"5 Whys\": string;\n \"Metric for truth\": string;\n \"Scoring justification\": string;\n dependsOnIds: string[];\n enablesIds: string[];\n contradictsIds: string[];\n readingIds: string[];\n derived: AssumptionDerived;\n}\n\nexport interface ReadingRecord extends BaseRecord {\n Title: string;\n /** First-class link to the artifact — the independence-dedupe key. */\n Source: string | null;\n assumptionId: string;\n experimentId: string | null;\n goalId: string | null;\n Rung: Rung;\n Representativeness: SourceQualityPick;\n Credibility: SourceQualityPick;\n /** For Goal-rung readings: the magnitude band from the absolute outcome. */\n magnitudeBand?: MagnitudeBand;\n Result: Result;\n Date: string | null;\n Owner: string[];\n derived: { sourceQuality: number; strength: number };\n}\n\n/**\n * A pre-registered bar line — the per-belief pass/fail test on an experiment,\n * stored as an embedded array on the experiment (no identity of its own; see\n * `connectors/nosql-schema.md`). `barVerdict` is set once at closure and is a\n * report only — never folded into Confidence.\n */\nexport interface BarLine {\n assumptionId: string;\n rightIf: string;\n wrongIf?: string | null;\n plannedRung: Rung;\n barVerdict?: Result | null;\n}\n\nexport interface ExperimentRecord extends BaseRecord {\n Title: string;\n Instrument: string | null;\n Feasibility: Feasibility | null;\n Status: ExperimentStatus;\n closureReason: \"Completed\" | \"Early-stop\" | \"Kill\" | null;\n Owner: string[];\n Date: string | null;\n /** The embedded pre-registered bar lines; drives progress-to-conclusion. */\n barLines: BarLine[];\n /** Convenience projection: the assumptions the bar lines test. */\n barLineAssumptionIds: string[];\n}\n\nexport interface GoalRecord extends BaseRecord {\n Title: string;\n Status: GoalStatus;\n Outcome: \"Achieved\" | \"Missed\" | \"Dropped\" | null;\n Owner: string[];\n Date: string | null;\n basedOnIds: string[];\n}\n\nexport interface DecisionRecord extends BaseRecord {\n Title: string;\n Status: DecisionStatus;\n Owner: string[];\n basedOnIds: string[];\n resolvesIds: string[];\n}\n\nexport interface GlossaryRecord extends BaseRecord {\n Title: string;\n Status: GlossaryStatus;\n}\n\n/** A record of any register — the DataProvider's currency. */\nexport type AnyRecord = BaseRecord & Record<string, unknown>;\n\n// ── Relations (both-ends links) ─────────────────────────────────────────────\n\n/** A pointer to one record. */\nexport interface RecordRef {\n register: Collection;\n id: string;\n}\n\n/** The relations the dashboard can set (each writes both ends). */\nexport type Relation =\n | \"assumption-reading\"\n | \"assumption-depends-on\"\n | \"assumption-contradicts\"\n | \"reading-experiment\"\n | \"reading-goal\"\n | \"decision-based-on\"\n | \"decision-resolves\"\n | \"goal-based-on\";\n","/**\n * The evidence ladder anchors that feed Strength.\n *\n * Source of truth: `skills/_shared/ontology.yaml` → `vocabularies.rung`.\n * Testing rungs carry a single anchor; Goal rungs carry a magnitude band\n * (Low/Typical/High) picked from the absolute outcome.\n */\nimport type { GoalRung, MagnitudeBand, Rung, TestingRung } from \"../types.js\";\nimport { GOAL_RUNG_VALUES } from \"../types.js\";\n\nexport const RUNG_ANCHOR: Record<TestingRung, number> = {\n Opinion: 3,\n \"Pitch-deck reaction\": 6,\n Anecdotal: 10,\n \"Desk research\": 15,\n \"Survey at scale\": 25,\n \"Prototype usage\": 30,\n};\n\nexport const GOAL_RUNG_ANCHOR: Record<GoalRung, Record<MagnitudeBand, number>> =\n {\n \"Signed intent\": { Low: 55, Typical: 68, High: 80 },\n \"Paying users\": { Low: 75, Typical: 88, High: 99 },\n };\n\nconst GOAL_RUNG_SET = new Set<Rung>(GOAL_RUNG_VALUES);\n\nexport function isGoalRung(rung: Rung): rung is GoalRung {\n return GOAL_RUNG_SET.has(rung);\n}\n","/**\n * Strength — the signed reading value `s` the Confidence average reads.\n *\n * Formula (`ontology.yaml` → `derivations.strength`):\n * rung anchor (Goal rungs: × magnitude band) × sign(Result)\n * — Validated positive, Invalidated negative; 0 unless Validated/Invalidated.\n */\nimport type { MagnitudeBand, Result, Rung } from \"../types.js\";\nimport { GOAL_RUNG_ANCHOR, RUNG_ANCHOR, isGoalRung } from \"./rung.js\";\n\nexport function sign(result: Result): -1 | 0 | 1 {\n if (result === \"Validated\") return 1;\n if (result === \"Invalidated\") return -1;\n return 0;\n}\n\n/** A reading counts toward Confidence only once concluded either way; an\n * Inconclusive reading carries no signal. The single definition the whole\n * derivation module (and its record→input mappers) share. */\nexport function isConcluded(result: Result): boolean {\n return result === \"Validated\" || result === \"Invalidated\";\n}\n\nexport interface StrengthInput {\n rung: Rung;\n result: Result;\n /** Only read for Goal rungs; defaults to \"Typical\" when absent. */\n magnitudeBand?: MagnitudeBand;\n}\n\nexport function readingStrength(input: StrengthInput): number {\n const s = sign(input.result);\n if (s === 0) return 0; // Inconclusive contributes nothing.\n if (isGoalRung(input.rung)) {\n const band = input.magnitudeBand ?? \"Typical\";\n return GOAL_RUNG_ANCHOR[input.rung][band] * s;\n }\n return (RUNG_ANCHOR[input.rung] ?? 0) * s;\n}\n","/**\n * Source quality — Representativeness × Credibility.\n *\n * Scales a Reading's *weight* in the Confidence average, within its rung.\n * We keep the raw product as the weight (matching the migration's\n * `remodel.mjs`); the five display anchors {0.25, 0.35, 0.5, 0.7, 1.0} are a\n * storage/display concern, not the weight used in the average.\n */\nimport { round2 } from \"./round.js\";\n\nexport function sourceQuality(\n representativeness: number,\n credibility: number,\n): number {\n return round2(representativeness * credibility);\n}\n","/**\n * Confidence — signed −100…100, 0 = no evidence.\n *\n * Formula (`ontology.yaml` → `derivations.confidence`):\n * (w0·0 + Σ wi·si) / (w0 + Σ wi), w0 = 100,\n * wi = |si| × Source quality, si = the reading's signed Strength.\n *\n * Only concluded Validated/Invalidated readings enter. Readings sharing a\n * Source against one belief dedupe to the strongest (largest |si|, most\n * recent on ties). Goal-rung readings never dedupe (each closed goal is its\n * own unit). No corroboration bump.\n */\nimport type { MagnitudeBand, Result, Rung } from \"../types.js\";\nimport { round2 } from \"./round.js\";\nimport { isGoalRung } from \"./rung.js\";\nimport { sourceQuality } from \"./source-quality.js\";\nimport { isConcluded, readingStrength } from \"./strength.js\";\n\n/** The neutral prior weight — a hard floor per the guardrails. */\nexport const W0 = 100;\n\nexport interface ConfidenceReadingInput {\n id: string;\n /** The independence-dedupe key. Null falls back to the reading's own id. */\n source: string | null;\n rung: Rung;\n result: Result;\n representativeness: number;\n credibility: number;\n /** ISO date; used only as the dedupe tie-break (most recent wins). */\n date?: string | null;\n magnitudeBand?: MagnitudeBand;\n}\n\nexport interface Scored {\n input: ConfidenceReadingInput;\n strength: number;\n sq: number;\n /** The reading's weight in the average: |strength| × Source quality. */\n weight: number;\n}\n\n/**\n * Score every concluded reading and resolve the Source dedupe — the shared\n * front half of the Confidence average. `confidence()` reduces the winners to\n * a number; `confidenceAttribution()` reuses the same winners so the movers it\n * reports always decompose the very number the drawer shows. Goal rungs never\n * dedupe (each closed goal is its own unit).\n */\nexport function scoreAndDedupe(readings: ConfidenceReadingInput[]): Scored[] {\n const scored: Scored[] = readings\n .filter((r) => isConcluded(r.result))\n .map((r) => {\n const strength = readingStrength(r);\n const sq = sourceQuality(r.representativeness, r.credibility);\n return { input: r, strength, sq, weight: Math.abs(strength) * sq };\n })\n .filter((x) => x.strength !== 0);\n\n const best = new Map<string, Scored>();\n for (const x of scored) {\n if (isGoalRung(x.input.rung)) {\n best.set(x.input.id, x);\n continue;\n }\n const key = x.input.source || x.input.id;\n const cur = best.get(key);\n const better =\n !cur ||\n Math.abs(x.strength) > Math.abs(cur.strength) ||\n (Math.abs(x.strength) === Math.abs(cur.strength) &&\n (x.input.date || \"\") > (cur.input.date || \"\"));\n if (better) best.set(key, x);\n }\n return [...best.values()];\n}\n\nexport function confidence(readings: ConfidenceReadingInput[]): number {\n const winners = scoreAndDedupe(readings);\n let num = 0;\n let den = W0;\n for (const x of winners) {\n num += x.weight * x.strength;\n den += x.weight;\n }\n return den > 0 ? round2(num / den) : 0;\n}\n","/**\n * Confidence attribution — the \"what's moving the number\" half of the\n * understanding layer (OPS-1276). Decomposes an assumption's Confidence into\n * the experiments (and goals / direct readings) contributing to it, ranked by\n * how hard each pushes the number up or down.\n *\n * A winner's contribution is its signed share of the average:\n * cᵢ = (weightᵢ · strengthᵢ) / den, den = w0 + Σ weight\n * so Σ contributions = Σ(wᵢ·sᵢ)/den = Confidence (the w0·0 prior term is 0).\n * The reveal therefore literally adds up to the hero number. Contributions are\n * grouped by the reading's experiment (goal-rung readings and experiment-less\n * readings fall into their own buckets), then ranked by |contribution|.\n */\nimport { W0, scoreAndDedupe, type ConfidenceReadingInput } from \"./confidence.js\";\nimport { round2 } from \"./round.js\";\n\nexport interface AttributionReadingInput extends ConfidenceReadingInput {\n /** The experiment that produced the reading, if any. */\n experimentId?: string | null;\n /** The goal the reading concluded, for goal-rung readings. */\n goalId?: string | null;\n}\n\n/** What a mover is anchored to — an experiment, a goal, or nothing. */\nexport type MoverKind = \"experiment\" | \"goal\" | \"direct\";\n\nexport interface Mover {\n /** Stable grouping key: the experiment/goal id, or \"direct\". */\n key: string;\n kind: MoverKind;\n /** The experiment id when `kind === \"experiment\"`, else null. */\n experimentId: string | null;\n /** The goal id when `kind === \"goal\"`, else null. */\n goalId: string | null;\n /** Signed push on Confidence; the whole set sums to `confidence`. */\n contribution: number;\n /** |contribution| — the rank key and the \"how hard\" magnitude. */\n magnitude: number;\n /** How many (deduped) readings back this mover. */\n readingCount: number;\n /** The winning readings' ids, for drill-through. */\n readingIds: string[];\n}\n\nexport interface Attribution {\n /** The same Confidence the derived box shows (from the same winners). */\n confidence: number;\n /** Movers ranked by |contribution|, strongest first. */\n movers: Mover[];\n}\n\nfunction bucketOf(r: AttributionReadingInput): {\n key: string;\n kind: MoverKind;\n experimentId: string | null;\n goalId: string | null;\n} {\n if (r.experimentId) {\n return {\n key: r.experimentId,\n kind: \"experiment\",\n experimentId: r.experimentId,\n goalId: null,\n };\n }\n if (r.goalId) {\n return {\n key: `goal:${r.goalId}`,\n kind: \"goal\",\n experimentId: null,\n goalId: r.goalId,\n };\n }\n return { key: \"direct\", kind: \"direct\", experimentId: null, goalId: null };\n}\n\nexport function confidenceAttribution(\n readings: AttributionReadingInput[],\n): Attribution {\n const winners = scoreAndDedupe(readings);\n const den = winners.reduce((acc, x) => acc + x.weight, W0);\n\n const byKey = new Map<string, Mover>();\n let num = 0;\n for (const x of winners) {\n num += x.weight * x.strength;\n const b = bucketOf(x.input as AttributionReadingInput);\n const contribution = (x.weight * x.strength) / den;\n const cur = byKey.get(b.key);\n if (cur) {\n cur.contribution += contribution;\n cur.readingCount += 1;\n cur.readingIds.push(x.input.id);\n } else {\n byKey.set(b.key, {\n key: b.key,\n kind: b.kind,\n experimentId: b.experimentId,\n goalId: b.goalId,\n contribution,\n magnitude: 0, // finalised below\n readingCount: 1,\n readingIds: [x.input.id],\n });\n }\n }\n\n const movers = [...byKey.values()].map((m) => ({\n ...m,\n contribution: round2(m.contribution),\n magnitude: round2(Math.abs(m.contribution)),\n }));\n movers.sort((a, b) => b.magnitude - a.magnitude);\n\n return {\n confidence: den > 0 ? round2(num / den) : 0,\n movers,\n };\n}\n","/**\n * Progress-to-conclusion — how close a running experiment is to concluding\n * (OPS-1276). An experiment concludes when every pre-registered bar line has\n * been given a Bar verdict; progress is the count of settled bars against the\n * total. Bar verdict is a report, never a Confidence input (nosql-schema.md),\n * so reading it here is exactly its intended use.\n */\nexport interface BarLineInput {\n /** null/\"\" until the bar is judged at closure. */\n barVerdict?: string | null;\n}\n\nexport interface Progress {\n /** Pre-registered bar lines on the experiment. */\n total: number;\n /** Bars that have a verdict. */\n settled: number;\n /** Bars still awaiting a verdict. */\n toGo: number;\n /** True once every bar is settled (and there is at least one). */\n concluded: boolean;\n}\n\nexport function experimentProgress(bars: BarLineInput[]): Progress {\n const total = bars.length;\n const settled = bars.filter(\n (b) => typeof b.barVerdict === \"string\" && b.barVerdict.trim() !== \"\",\n ).length;\n return {\n total,\n settled,\n toGo: total - settled,\n concluded: total > 0 && settled === total,\n };\n}\n","/**\n * Confidence over time — the story of how the number got where it is\n * (OPS-1276). At each date a concluded reading was dated, we recompute\n * Confidence over every concluded reading up to and including that date, using\n * the very same `confidence()` the derived box uses (so the last point equals\n * the hero number). Undated concluded readings have no place on the timeline\n * but still bear on the belief, so they are treated as always-present — folded\n * into every point — which keeps the final point equal to today's Confidence.\n */\nimport { confidence, type ConfidenceReadingInput } from \"./confidence.js\";\nimport { isConcluded } from \"./strength.js\";\n\nexport interface TrajectoryPoint {\n /** ISO date at which Confidence took this value. */\n date: string;\n confidence: number;\n}\n\nexport function confidenceTrajectory(\n readings: ConfidenceReadingInput[],\n): TrajectoryPoint[] {\n const concluded = readings.filter((r) => isConcluded(r.result));\n const undated = concluded.filter((r) => !r.date);\n const dated = concluded.filter((r) => r.date);\n if (dated.length === 0) return [];\n\n const dates = [...new Set(dated.map((r) => r.date as string))].sort();\n return dates.map((date) => ({\n date,\n // Everything dated on/before this point, plus the always-present undated.\n confidence: confidence([\n ...undated,\n ...dated.filter((r) => (r.date as string) <= date),\n ]),\n }));\n}\n","/**\n * Derived Impact — propagates dependents' pull into a belief's seed.\n *\n * Formula (`ontology.yaml` → `derivations.derived_impact`):\n * seed + (100 − seed) × S / (S + 100), where\n * S = Σ Derived Impact of assumptions whose `Depends on` names this row\n * + 100 per standing (Provisional/Active) decision naming it via\n * `Based on assumption`.\n * Goals never contribute. Moot rows pin to 0 and contribute nothing.\n *\n * One reverse-topological pass (dependents first) with memoization and a\n * cycle guard, matching `doshi-validation-os/migration/remodel.mjs`.\n */\nimport { round2 } from \"./round.js\";\n\nexport interface ImpactAssumptionInput {\n id: string;\n /** The hand-scored seed (0–100); null treated as 0. */\n impact: number | null;\n moot?: boolean;\n /** Ids this assumption depends on. */\n dependsOnIds: string[];\n}\n\n/**\n * @param assumptions the full register (never a filtered slice — a filter\n * silently drops dependents from the propagation).\n * @param basedOnCounts id → number of standing decisions with a `Based on`\n * link to that assumption. Each contributes +100 to S.\n */\nexport function derivedImpacts(\n assumptions: ImpactAssumptionInput[],\n basedOnCounts: Record<string, number> = {},\n): Map<string, number> {\n const byId = new Map(assumptions.map((a) => [a.id, a]));\n\n // dependents(X) = assumptions whose dependsOnIds includes X.\n const dependents = new Map<string, string[]>();\n for (const a of assumptions) dependents.set(a.id, []);\n for (const a of assumptions) {\n for (const dep of a.dependsOnIds) dependents.get(dep)?.push(a.id);\n }\n\n const memo = new Map<string, number>();\n const compute = (id: string, seen: Set<string>): number => {\n const cached = memo.get(id);\n if (cached !== undefined) return cached;\n if (seen.has(id)) return 0; // cycle guard\n seen.add(id);\n const a = byId.get(id);\n if (!a) return 0;\n if (a.moot) {\n memo.set(id, 0);\n return 0;\n }\n const seed = a.impact ?? 0;\n let S = 0;\n for (const depId of dependents.get(id) ?? []) {\n const d = byId.get(depId);\n if (d && !d.moot) S += compute(depId, seen);\n }\n S += 100 * (basedOnCounts[id] ?? 0);\n const value = seed + (100 - seed) * (S / (S + 100));\n const rounded = round2(value);\n memo.set(id, rounded);\n return rounded;\n };\n\n const out = new Map<string, number>();\n for (const a of assumptions) out.set(a.id, compute(a.id, new Set()));\n return out;\n}\n","/**\n * Risk — the belief's live standing.\n *\n * Formula (`ontology.yaml` → `derivations.risk`):\n * Derived Impact × (1 − max(0, Confidence) / 100)\n * Ranges 0 to Derived Impact. Negative confidence does not raise Risk above\n * Derived Impact (the max(0, …) clamp).\n */\nimport { round2 } from \"./round.js\";\n\nexport function risk(derivedImpact: number, confidence: number): number {\n return round2(derivedImpact * (1 - Math.max(0, confidence) / 100));\n}\n"],"mappings":"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","names":["confidence"]}
@@ -36,7 +36,8 @@ function isNotFoundError(e) {
36
36
  var RELATIONS = {
37
37
  "assumption-reading": {
38
38
  from: { register: "assumptions", field: "readingIds", cardinality: "many" },
39
- to: { register: "readings", field: "assumptionId", cardinality: "one" }
39
+ to: { register: "readings", field: "assumptionId", cardinality: "one" },
40
+ targetRegister: "readings"
40
41
  },
41
42
  "assumption-depends-on": {
42
43
  from: {
@@ -44,7 +45,8 @@ var RELATIONS = {
44
45
  field: "dependsOnIds",
45
46
  cardinality: "many"
46
47
  },
47
- to: { register: "assumptions", field: "enablesIds", cardinality: "many" }
48
+ to: { register: "assumptions", field: "enablesIds", cardinality: "many" },
49
+ targetRegister: "assumptions"
48
50
  },
49
51
  "assumption-contradicts": {
50
52
  from: {
@@ -56,31 +58,37 @@ var RELATIONS = {
56
58
  register: "assumptions",
57
59
  field: "contradictsIds",
58
60
  cardinality: "many"
59
- }
61
+ },
62
+ targetRegister: "assumptions"
60
63
  },
61
64
  "reading-experiment": {
62
65
  from: { register: "readings", field: "experimentId", cardinality: "one" },
63
- to: null
66
+ to: null,
64
67
  // experiment→readings is derived
68
+ targetRegister: "experiments"
65
69
  },
66
70
  "reading-goal": {
67
71
  from: { register: "readings", field: "goalId", cardinality: "one" },
68
- to: null
72
+ to: null,
73
+ targetRegister: "goals"
69
74
  },
70
75
  "decision-based-on": {
71
76
  from: { register: "decisions", field: "basedOnIds", cardinality: "many" },
72
- to: null
77
+ to: null,
73
78
  // never touches the assumption
79
+ targetRegister: "assumptions"
74
80
  },
75
81
  "decision-resolves": {
76
82
  from: { register: "decisions", field: "resolvesIds", cardinality: "many" },
77
- to: null
83
+ to: null,
78
84
  // mooting the assumption is a gated business action, not a link
85
+ targetRegister: "assumptions"
79
86
  },
80
87
  "goal-based-on": {
81
88
  from: { register: "goals", field: "basedOnIds", cardinality: "many" },
82
- to: null
89
+ to: null,
83
90
  // goal linkage is a per-goal view, never stored on the assumption
91
+ targetRegister: "assumptions"
84
92
  }
85
93
  };
86
94
 
@@ -91,4 +99,4 @@ export {
91
99
  isNotFoundError,
92
100
  RELATIONS
93
101
  };
94
- //# sourceMappingURL=chunk-CUD5PEXL.js.map
102
+ //# sourceMappingURL=chunk-W7VU5JOU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider.ts","../src/relations.ts"],"sourcesContent":["/**\n * The `DataProvider` — the single integration seam between the dashboard/API\n * and a concrete backend. Hand-rolled (patterns borrowed from\n * Refine/React-Admin's dataProvider, not forked). One adapter per backend;\n * Firestore is the first. Writing a new adapter is a bounded task: implement\n * these five methods.\n */\nimport type { AnyRecord, Collection, RecordRef, Relation } from \"./types.js\";\n\nexport interface DataProvider {\n /** Every row of a register — never a filtered view (a filter drops rows). */\n list(register: Collection): Promise<AnyRecord[]>;\n get(register: Collection, id: string): Promise<AnyRecord>;\n create(register: Collection, data: Partial<AnyRecord>): Promise<AnyRecord>;\n /**\n * Version-guarded update: `version` is the value the caller loaded. If the\n * stored version has moved on, the write is rejected with\n * {@link StaleVersionError} (surfaced to the user as a 409).\n */\n update(\n register: Collection,\n id: string,\n patch: Partial<AnyRecord>,\n version: number,\n ): Promise<AnyRecord>;\n /** Set a relation on both ends in one logical write. */\n link(relation: Relation, from: RecordRef, to: RecordRef): Promise<void>;\n}\n\n/**\n * Thrown when an update carries a version older than what is stored — a\n * concurrent edit landed first. The API turns this into a 409 with\n * plain-language copy; it is never surfaced as version jargon.\n */\nexport class StaleVersionError extends Error {\n override readonly name = \"StaleVersionError\";\n constructor(\n readonly register: Collection,\n readonly id: string,\n readonly expected: number,\n readonly actual: number,\n ) {\n super(\n `Stale write to ${register}/${id}: caller had version ${expected}, ` +\n `stored is ${actual}.`,\n );\n }\n}\n\nexport function isStaleVersionError(e: unknown): e is StaleVersionError {\n return e instanceof StaleVersionError;\n}\n\n/** Thrown when a get/update targets a record that does not exist (→ 404). */\nexport class NotFoundError extends Error {\n override readonly name = \"NotFoundError\";\n constructor(\n readonly register: Collection,\n readonly id: string,\n ) {\n super(`No ${register} record with id ${id}.`);\n }\n}\n\nexport function isNotFoundError(e: unknown): e is NotFoundError {\n return e instanceof NotFoundError;\n}\n","/**\n * Relation config — the single table describing, for each linkable relation,\n * which field on which register holds each end. `link()` sets both ends from\n * this table, so relations stay consistent no matter which side initiated.\n *\n * A `null` `to` end means the inverse is a *derived view*, not a stored field\n * (e.g. \"experiments testing me\" is computed over bar-lines; a decision's\n * `Based on` never touches the assumption). Those relations write one end.\n */\nimport type { Collection, Relation } from \"./types.js\";\n\nexport interface RelationEnd {\n register: Collection;\n field: string;\n cardinality: \"one\" | \"many\";\n}\n\nexport interface RelationSpec {\n /** The end named by `from` in `link(relation, from, to)`. */\n from: RelationEnd;\n /** The end named by `to`; null when the inverse is derived, not stored. */\n to: RelationEnd | null;\n /**\n * The register the `to` end points at — always present, even when `to` is\n * null (the inverse is a derived view). The dashboard reads this to know\n * which register to pick a link target from; the API validates the target\n * register against it.\n */\n targetRegister: Collection;\n}\n\nexport const RELATIONS: Record<Relation, RelationSpec> = {\n \"assumption-reading\": {\n from: { register: \"assumptions\", field: \"readingIds\", cardinality: \"many\" },\n to: { register: \"readings\", field: \"assumptionId\", cardinality: \"one\" },\n targetRegister: \"readings\",\n },\n \"assumption-depends-on\": {\n from: {\n register: \"assumptions\",\n field: \"dependsOnIds\",\n cardinality: \"many\",\n },\n to: { register: \"assumptions\", field: \"enablesIds\", cardinality: \"many\" },\n targetRegister: \"assumptions\",\n },\n \"assumption-contradicts\": {\n from: {\n register: \"assumptions\",\n field: \"contradictsIds\",\n cardinality: \"many\",\n },\n to: {\n register: \"assumptions\",\n field: \"contradictsIds\",\n cardinality: \"many\",\n },\n targetRegister: \"assumptions\",\n },\n \"reading-experiment\": {\n from: { register: \"readings\", field: \"experimentId\", cardinality: \"one\" },\n to: null, // experiment→readings is derived\n targetRegister: \"experiments\",\n },\n \"reading-goal\": {\n from: { register: \"readings\", field: \"goalId\", cardinality: \"one\" },\n to: null,\n targetRegister: \"goals\",\n },\n \"decision-based-on\": {\n from: { register: \"decisions\", field: \"basedOnIds\", cardinality: \"many\" },\n to: null, // never touches the assumption\n targetRegister: \"assumptions\",\n },\n \"decision-resolves\": {\n from: { register: \"decisions\", field: \"resolvesIds\", cardinality: \"many\" },\n to: null, // mooting the assumption is a gated business action, not a link\n targetRegister: \"assumptions\",\n },\n \"goal-based-on\": {\n from: { register: \"goals\", field: \"basedOnIds\", cardinality: \"many\" },\n to: null, // goal linkage is a per-goal view, never stored on the assumption\n targetRegister: \"assumptions\",\n },\n};\n"],"mappings":";AAkCO,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAE3C,YACW,UACA,IACA,UACA,QACT;AACA;AAAA,MACE,kBAAkB,QAAQ,IAAI,EAAE,wBAAwB,QAAQ,eACjD,MAAM;AAAA,IACvB;AARS;AACA;AACA;AACA;AAAA,EAMX;AAAA,EATW;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EALO,OAAO;AAY3B;AAEO,SAAS,oBAAoB,GAAoC;AACtE,SAAO,aAAa;AACtB;AAGO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EAEvC,YACW,UACA,IACT;AACA,UAAM,MAAM,QAAQ,mBAAmB,EAAE,GAAG;AAHnC;AACA;AAAA,EAGX;AAAA,EAJW;AAAA,EACA;AAAA,EAHO,OAAO;AAO3B;AAEO,SAAS,gBAAgB,GAAgC;AAC9D,SAAO,aAAa;AACtB;;;ACnCO,IAAM,YAA4C;AAAA,EACvD,sBAAsB;AAAA,IACpB,MAAM,EAAE,UAAU,eAAe,OAAO,cAAc,aAAa,OAAO;AAAA,IAC1E,IAAI,EAAE,UAAU,YAAY,OAAO,gBAAgB,aAAa,MAAM;AAAA,IACtE,gBAAgB;AAAA,EAClB;AAAA,EACA,yBAAyB;AAAA,IACvB,MAAM;AAAA,MACJ,UAAU;AAAA,MACV,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,IAAI,EAAE,UAAU,eAAe,OAAO,cAAc,aAAa,OAAO;AAAA,IACxE,gBAAgB;AAAA,EAClB;AAAA,EACA,0BAA0B;AAAA,IACxB,MAAM;AAAA,MACJ,UAAU;AAAA,MACV,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,IAAI;AAAA,MACF,UAAU;AAAA,MACV,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,gBAAgB;AAAA,EAClB;AAAA,EACA,sBAAsB;AAAA,IACpB,MAAM,EAAE,UAAU,YAAY,OAAO,gBAAgB,aAAa,MAAM;AAAA,IACxE,IAAI;AAAA;AAAA,IACJ,gBAAgB;AAAA,EAClB;AAAA,EACA,gBAAgB;AAAA,IACd,MAAM,EAAE,UAAU,YAAY,OAAO,UAAU,aAAa,MAAM;AAAA,IAClE,IAAI;AAAA,IACJ,gBAAgB;AAAA,EAClB;AAAA,EACA,qBAAqB;AAAA,IACnB,MAAM,EAAE,UAAU,aAAa,OAAO,cAAc,aAAa,OAAO;AAAA,IACxE,IAAI;AAAA;AAAA,IACJ,gBAAgB;AAAA,EAClB;AAAA,EACA,qBAAqB;AAAA,IACnB,MAAM,EAAE,UAAU,aAAa,OAAO,eAAe,aAAa,OAAO;AAAA,IACzE,IAAI;AAAA;AAAA,IACJ,gBAAgB;AAAA,EAClB;AAAA,EACA,iBAAiB;AAAA,IACf,MAAM,EAAE,UAAU,SAAS,OAAO,cAAc,aAAa,OAAO;AAAA,IACpE,IAAI;AAAA;AAAA,IACJ,gBAAgB;AAAA,EAClB;AACF;","names":[]}
@@ -1,2 +1,2 @@
1
- export { C as ConfidenceReadingInput, G as GOAL_RUNG_ANCHOR, I as ImpactAssumptionInput, R as RUNG_ANCHOR, S as StrengthInput, W as W0, c as confidence, d as derivedImpacts, a as isGoalRung, r as readingStrength, b as risk, e as round2, f as sign, s as sourceQuality } from '../index-BWTfuyde.js';
2
- import '../types-D4Kn18fv.js';
1
+ export { a as Attribution, A as AttributionReadingInput, B as BarLineInput, C as ConfidenceReadingInput, G as GOAL_RUNG_ANCHOR, I as ImpactAssumptionInput, M as Mover, b as MoverKind, P as Progress, R as RUNG_ANCHOR, S as Scored, c as StrengthInput, T as TrajectoryPoint, W as W0, d as confidence, e as confidenceAttribution, f as confidenceTrajectory, g as derivedImpacts, h as experimentProgress, j as isConcluded, k as isGoalRung, r as readingStrength, l as risk, m as round2, n as scoreAndDedupe, o as sign, s as sourceQuality } from '../index-CieW13mJ.js';
2
+ import '../types-B3eI7ASx.js';
@@ -3,25 +3,35 @@ import {
3
3
  RUNG_ANCHOR,
4
4
  W0,
5
5
  confidence,
6
+ confidenceAttribution,
7
+ confidenceTrajectory,
6
8
  derivedImpacts,
9
+ experimentProgress,
10
+ isConcluded,
7
11
  isGoalRung,
8
12
  readingStrength,
9
13
  risk,
10
14
  round2,
15
+ scoreAndDedupe,
11
16
  sign,
12
17
  sourceQuality
13
- } from "../chunk-OS7CTJ2A.js";
18
+ } from "../chunk-4Q2B2QT3.js";
14
19
  import "../chunk-PZ5AY32C.js";
15
20
  export {
16
21
  GOAL_RUNG_ANCHOR,
17
22
  RUNG_ANCHOR,
18
23
  W0,
19
24
  confidence,
25
+ confidenceAttribution,
26
+ confidenceTrajectory,
20
27
  derivedImpacts,
28
+ experimentProgress,
29
+ isConcluded,
21
30
  isGoalRung,
22
31
  readingStrength,
23
32
  risk,
24
33
  round2,
34
+ scoreAndDedupe,
25
35
  sign,
26
36
  sourceQuality
27
37
  };
@@ -0,0 +1,234 @@
1
+ import { k as GoalRung, M as MagnitudeBand, r as TestingRung, q as Rung, p as Result } from './types-B3eI7ASx.js';
2
+
3
+ /**
4
+ * Round to 2 decimals, matching the migration's `+(n).toFixed(2)`.
5
+ * Derived values are stored/displayed rounded; full precision is only used
6
+ * transiently inside a single computation.
7
+ */
8
+ declare function round2(n: number): number;
9
+
10
+ /**
11
+ * The evidence ladder anchors that feed Strength.
12
+ *
13
+ * Source of truth: `skills/_shared/ontology.yaml` → `vocabularies.rung`.
14
+ * Testing rungs carry a single anchor; Goal rungs carry a magnitude band
15
+ * (Low/Typical/High) picked from the absolute outcome.
16
+ */
17
+
18
+ declare const RUNG_ANCHOR: Record<TestingRung, number>;
19
+ declare const GOAL_RUNG_ANCHOR: Record<GoalRung, Record<MagnitudeBand, number>>;
20
+ declare function isGoalRung(rung: Rung): rung is GoalRung;
21
+
22
+ /**
23
+ * Strength — the signed reading value `s` the Confidence average reads.
24
+ *
25
+ * Formula (`ontology.yaml` → `derivations.strength`):
26
+ * rung anchor (Goal rungs: × magnitude band) × sign(Result)
27
+ * — Validated positive, Invalidated negative; 0 unless Validated/Invalidated.
28
+ */
29
+
30
+ declare function sign(result: Result): -1 | 0 | 1;
31
+ /** A reading counts toward Confidence only once concluded either way; an
32
+ * Inconclusive reading carries no signal. The single definition the whole
33
+ * derivation module (and its record→input mappers) share. */
34
+ declare function isConcluded(result: Result): boolean;
35
+ interface StrengthInput {
36
+ rung: Rung;
37
+ result: Result;
38
+ /** Only read for Goal rungs; defaults to "Typical" when absent. */
39
+ magnitudeBand?: MagnitudeBand;
40
+ }
41
+ declare function readingStrength(input: StrengthInput): number;
42
+
43
+ declare function sourceQuality(representativeness: number, credibility: number): number;
44
+
45
+ /**
46
+ * Confidence — signed −100…100, 0 = no evidence.
47
+ *
48
+ * Formula (`ontology.yaml` → `derivations.confidence`):
49
+ * (w0·0 + Σ wi·si) / (w0 + Σ wi), w0 = 100,
50
+ * wi = |si| × Source quality, si = the reading's signed Strength.
51
+ *
52
+ * Only concluded Validated/Invalidated readings enter. Readings sharing a
53
+ * Source against one belief dedupe to the strongest (largest |si|, most
54
+ * recent on ties). Goal-rung readings never dedupe (each closed goal is its
55
+ * own unit). No corroboration bump.
56
+ */
57
+
58
+ /** The neutral prior weight — a hard floor per the guardrails. */
59
+ declare const W0 = 100;
60
+ interface ConfidenceReadingInput {
61
+ id: string;
62
+ /** The independence-dedupe key. Null falls back to the reading's own id. */
63
+ source: string | null;
64
+ rung: Rung;
65
+ result: Result;
66
+ representativeness: number;
67
+ credibility: number;
68
+ /** ISO date; used only as the dedupe tie-break (most recent wins). */
69
+ date?: string | null;
70
+ magnitudeBand?: MagnitudeBand;
71
+ }
72
+ interface Scored {
73
+ input: ConfidenceReadingInput;
74
+ strength: number;
75
+ sq: number;
76
+ /** The reading's weight in the average: |strength| × Source quality. */
77
+ weight: number;
78
+ }
79
+ /**
80
+ * Score every concluded reading and resolve the Source dedupe — the shared
81
+ * front half of the Confidence average. `confidence()` reduces the winners to
82
+ * a number; `confidenceAttribution()` reuses the same winners so the movers it
83
+ * reports always decompose the very number the drawer shows. Goal rungs never
84
+ * dedupe (each closed goal is its own unit).
85
+ */
86
+ declare function scoreAndDedupe(readings: ConfidenceReadingInput[]): Scored[];
87
+ declare function confidence(readings: ConfidenceReadingInput[]): number;
88
+
89
+ /**
90
+ * Confidence attribution — the "what's moving the number" half of the
91
+ * understanding layer (OPS-1276). Decomposes an assumption's Confidence into
92
+ * the experiments (and goals / direct readings) contributing to it, ranked by
93
+ * how hard each pushes the number up or down.
94
+ *
95
+ * A winner's contribution is its signed share of the average:
96
+ * cᵢ = (weightᵢ · strengthᵢ) / den, den = w0 + Σ weight
97
+ * so Σ contributions = Σ(wᵢ·sᵢ)/den = Confidence (the w0·0 prior term is 0).
98
+ * The reveal therefore literally adds up to the hero number. Contributions are
99
+ * grouped by the reading's experiment (goal-rung readings and experiment-less
100
+ * readings fall into their own buckets), then ranked by |contribution|.
101
+ */
102
+
103
+ interface AttributionReadingInput extends ConfidenceReadingInput {
104
+ /** The experiment that produced the reading, if any. */
105
+ experimentId?: string | null;
106
+ /** The goal the reading concluded, for goal-rung readings. */
107
+ goalId?: string | null;
108
+ }
109
+ /** What a mover is anchored to — an experiment, a goal, or nothing. */
110
+ type MoverKind = "experiment" | "goal" | "direct";
111
+ interface Mover {
112
+ /** Stable grouping key: the experiment/goal id, or "direct". */
113
+ key: string;
114
+ kind: MoverKind;
115
+ /** The experiment id when `kind === "experiment"`, else null. */
116
+ experimentId: string | null;
117
+ /** The goal id when `kind === "goal"`, else null. */
118
+ goalId: string | null;
119
+ /** Signed push on Confidence; the whole set sums to `confidence`. */
120
+ contribution: number;
121
+ /** |contribution| — the rank key and the "how hard" magnitude. */
122
+ magnitude: number;
123
+ /** How many (deduped) readings back this mover. */
124
+ readingCount: number;
125
+ /** The winning readings' ids, for drill-through. */
126
+ readingIds: string[];
127
+ }
128
+ interface Attribution {
129
+ /** The same Confidence the derived box shows (from the same winners). */
130
+ confidence: number;
131
+ /** Movers ranked by |contribution|, strongest first. */
132
+ movers: Mover[];
133
+ }
134
+ declare function confidenceAttribution(readings: AttributionReadingInput[]): Attribution;
135
+
136
+ /**
137
+ * Progress-to-conclusion — how close a running experiment is to concluding
138
+ * (OPS-1276). An experiment concludes when every pre-registered bar line has
139
+ * been given a Bar verdict; progress is the count of settled bars against the
140
+ * total. Bar verdict is a report, never a Confidence input (nosql-schema.md),
141
+ * so reading it here is exactly its intended use.
142
+ */
143
+ interface BarLineInput {
144
+ /** null/"" until the bar is judged at closure. */
145
+ barVerdict?: string | null;
146
+ }
147
+ interface Progress {
148
+ /** Pre-registered bar lines on the experiment. */
149
+ total: number;
150
+ /** Bars that have a verdict. */
151
+ settled: number;
152
+ /** Bars still awaiting a verdict. */
153
+ toGo: number;
154
+ /** True once every bar is settled (and there is at least one). */
155
+ concluded: boolean;
156
+ }
157
+ declare function experimentProgress(bars: BarLineInput[]): Progress;
158
+
159
+ /**
160
+ * Confidence over time — the story of how the number got where it is
161
+ * (OPS-1276). At each date a concluded reading was dated, we recompute
162
+ * Confidence over every concluded reading up to and including that date, using
163
+ * the very same `confidence()` the derived box uses (so the last point equals
164
+ * the hero number). Undated concluded readings have no place on the timeline
165
+ * but still bear on the belief, so they are treated as always-present — folded
166
+ * into every point — which keeps the final point equal to today's Confidence.
167
+ */
168
+
169
+ interface TrajectoryPoint {
170
+ /** ISO date at which Confidence took this value. */
171
+ date: string;
172
+ confidence: number;
173
+ }
174
+ declare function confidenceTrajectory(readings: ConfidenceReadingInput[]): TrajectoryPoint[];
175
+
176
+ interface ImpactAssumptionInput {
177
+ id: string;
178
+ /** The hand-scored seed (0–100); null treated as 0. */
179
+ impact: number | null;
180
+ moot?: boolean;
181
+ /** Ids this assumption depends on. */
182
+ dependsOnIds: string[];
183
+ }
184
+ /**
185
+ * @param assumptions the full register (never a filtered slice — a filter
186
+ * silently drops dependents from the propagation).
187
+ * @param basedOnCounts id → number of standing decisions with a `Based on`
188
+ * link to that assumption. Each contributes +100 to S.
189
+ */
190
+ declare function derivedImpacts(assumptions: ImpactAssumptionInput[], basedOnCounts?: Record<string, number>): Map<string, number>;
191
+
192
+ declare function risk(derivedImpact: number, confidence: number): number;
193
+
194
+ /**
195
+ * The shared derivation module — pure functions, no I/O.
196
+ *
197
+ * The same module the dashboard, the API (derive-on-write), and Claude Code
198
+ * audits all call, so every writer computes the four derived numbers
199
+ * identically. Ported from `doshi-validation-os/migration/remodel.mjs` and
200
+ * kept in lock-step with `skills/_shared/ontology.yaml`.
201
+ */
202
+
203
+ type index_Attribution = Attribution;
204
+ type index_AttributionReadingInput = AttributionReadingInput;
205
+ type index_BarLineInput = BarLineInput;
206
+ type index_ConfidenceReadingInput = ConfidenceReadingInput;
207
+ declare const index_GOAL_RUNG_ANCHOR: typeof GOAL_RUNG_ANCHOR;
208
+ type index_ImpactAssumptionInput = ImpactAssumptionInput;
209
+ type index_Mover = Mover;
210
+ type index_MoverKind = MoverKind;
211
+ type index_Progress = Progress;
212
+ declare const index_RUNG_ANCHOR: typeof RUNG_ANCHOR;
213
+ type index_Scored = Scored;
214
+ type index_StrengthInput = StrengthInput;
215
+ type index_TrajectoryPoint = TrajectoryPoint;
216
+ declare const index_W0: typeof W0;
217
+ declare const index_confidence: typeof confidence;
218
+ declare const index_confidenceAttribution: typeof confidenceAttribution;
219
+ declare const index_confidenceTrajectory: typeof confidenceTrajectory;
220
+ declare const index_derivedImpacts: typeof derivedImpacts;
221
+ declare const index_experimentProgress: typeof experimentProgress;
222
+ declare const index_isConcluded: typeof isConcluded;
223
+ declare const index_isGoalRung: typeof isGoalRung;
224
+ declare const index_readingStrength: typeof readingStrength;
225
+ declare const index_risk: typeof risk;
226
+ declare const index_round2: typeof round2;
227
+ declare const index_scoreAndDedupe: typeof scoreAndDedupe;
228
+ declare const index_sign: typeof sign;
229
+ declare const index_sourceQuality: typeof sourceQuality;
230
+ declare namespace index {
231
+ export { type index_Attribution as Attribution, type index_AttributionReadingInput as AttributionReadingInput, type index_BarLineInput as BarLineInput, type index_ConfidenceReadingInput as ConfidenceReadingInput, index_GOAL_RUNG_ANCHOR as GOAL_RUNG_ANCHOR, type index_ImpactAssumptionInput as ImpactAssumptionInput, type index_Mover as Mover, type index_MoverKind as MoverKind, type index_Progress as Progress, index_RUNG_ANCHOR as RUNG_ANCHOR, type index_Scored as Scored, type index_StrengthInput as StrengthInput, type index_TrajectoryPoint as TrajectoryPoint, index_W0 as W0, index_confidence as confidence, index_confidenceAttribution as confidenceAttribution, index_confidenceTrajectory as confidenceTrajectory, index_derivedImpacts as derivedImpacts, index_experimentProgress as experimentProgress, index_isConcluded as isConcluded, index_isGoalRung as isGoalRung, index_readingStrength as readingStrength, index_risk as risk, index_round2 as round2, index_scoreAndDedupe as scoreAndDedupe, index_sign as sign, index_sourceQuality as sourceQuality };
232
+ }
233
+
234
+ export { type AttributionReadingInput as A, type BarLineInput as B, type ConfidenceReadingInput as C, GOAL_RUNG_ANCHOR as G, type ImpactAssumptionInput as I, type Mover as M, type Progress as P, RUNG_ANCHOR as R, type Scored as S, type TrajectoryPoint as T, W0 as W, type Attribution as a, type MoverKind as b, type StrengthInput as c, confidence as d, confidenceAttribution as e, confidenceTrajectory as f, derivedImpacts as g, experimentProgress as h, index as i, isConcluded as j, isGoalRung as k, risk as l, round2 as m, scoreAndDedupe as n, sign as o, readingStrength as r, sourceQuality as s };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { R as Relation, C as Collection, A as AssumptionRecord, a as ReadingRecord, D as DecisionRecord, b as AssumptionDerived } from './types-D4Kn18fv.js';
2
- export { c as AnyRecord, d as AssumptionStatus, B as BaseRecord, e as DecisionStatus, E as ExperimentRecord, f as ExperimentStatus, F as Feasibility, G as GOAL_RUNG_VALUES, g as GlossaryRecord, h as GlossaryStatus, i as GoalRecord, j as GoalRung, k as GoalStatus, M as MagnitudeBand, l as REGISTERS, m as RecordRef, n as Register, o as Result, p as Rung, S as SourceQualityPick, T as TESTING_RUNGS, q as TestingRung } from './types-D4Kn18fv.js';
3
- export { D as DataProvider, N as NotFoundError, S as StaleVersionError, i as isNotFoundError, a as isStaleVersionError } from './provider-78NHbYi9.js';
4
- export { i as derivation, s as recomputeSourceQuality, r as recomputeStrength } from './index-BWTfuyde.js';
1
+ import { R as Relation, C as Collection, A as AssumptionRecord, a as ReadingRecord, D as DecisionRecord, b as AssumptionDerived, c as AnyRecord } from './types-B3eI7ASx.js';
2
+ export { d as AssumptionStatus, B as BarLine, e as BaseRecord, f as DecisionStatus, E as ExperimentRecord, g as ExperimentStatus, F as Feasibility, G as GOAL_RUNG_VALUES, h as GlossaryRecord, i as GlossaryStatus, j as GoalRecord, k as GoalRung, l as GoalStatus, M as MagnitudeBand, m as REGISTERS, n as RecordRef, o as Register, p as Result, q as Rung, S as SourceQualityPick, T as TESTING_RUNGS, r as TestingRung } from './types-B3eI7ASx.js';
3
+ export { D as DataProvider, N as NotFoundError, S as StaleVersionError, i as isNotFoundError, a as isStaleVersionError } from './provider-hW8Hxf-n.js';
4
+ import { A as AttributionReadingInput } from './index-CieW13mJ.js';
5
+ export { i as derivation, s as recomputeSourceQuality, r as recomputeStrength } from './index-CieW13mJ.js';
5
6
 
6
7
  /**
7
8
  * Presence checks — the structural half of the assumption write guardrail.
@@ -48,6 +49,13 @@ interface RelationSpec {
48
49
  from: RelationEnd;
49
50
  /** The end named by `to`; null when the inverse is derived, not stored. */
50
51
  to: RelationEnd | null;
52
+ /**
53
+ * The register the `to` end points at — always present, even when `to` is
54
+ * null (the inverse is a derived view). The dashboard reads this to know
55
+ * which register to pick a link target from; the API validates the target
56
+ * register against it.
57
+ */
58
+ targetRegister: Collection;
51
59
  }
52
60
  declare const RELATIONS: Record<Relation, RelationSpec>;
53
61
 
@@ -59,4 +67,15 @@ interface RecomputeInput {
59
67
  /** id → recomputed derived tuple for every assumption in the register. */
60
68
  declare function recomputeDerived(input: RecomputeInput): Map<string, AssumptionDerived>;
61
69
 
62
- export { ASSUMPTION_PRESENCE_FIELDS, AssumptionDerived, type AssumptionPresenceField, AssumptionRecord, Collection, DecisionRecord, RELATIONS, ReadingRecord, type RecomputeInput, Relation, type RelationEnd, type RelationSpec, assumptionPresenceComplete, missingPresenceFields, recomputeDerived };
70
+ /**
71
+ * The one place that maps a stored reading record → the derivation module's
72
+ * typed input, so the pure functions stay decoupled from field names. Both the
73
+ * server-side recompute pass and the dashboard's understanding layer map
74
+ * through here, so a reading is read identically wherever Confidence is derived
75
+ * or explained. Coercion is defensive because a record's fields are `unknown`.
76
+ */
77
+
78
+ /** Map a reading record (canonical Title-cased fields) to its derivation input. */
79
+ declare function toReadingInput(r: AnyRecord): AttributionReadingInput;
80
+
81
+ export { ASSUMPTION_PRESENCE_FIELDS, AnyRecord, AssumptionDerived, type AssumptionPresenceField, AssumptionRecord, Collection, DecisionRecord, RELATIONS, ReadingRecord, type RecomputeInput, Relation, type RelationEnd, type RelationSpec, assumptionPresenceComplete, missingPresenceFields, recomputeDerived, toReadingInput };
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  StaleVersionError,
5
5
  isNotFoundError,
6
6
  isStaleVersionError
7
- } from "./chunk-CUD5PEXL.js";
7
+ } from "./chunk-W7VU5JOU.js";
8
8
  import {
9
9
  GOAL_RUNG_VALUES,
10
10
  REGISTERS,
@@ -15,7 +15,7 @@ import {
15
15
  readingStrength,
16
16
  risk,
17
17
  sourceQuality
18
- } from "./chunk-OS7CTJ2A.js";
18
+ } from "./chunk-4Q2B2QT3.js";
19
19
  import "./chunk-PZ5AY32C.js";
20
20
 
21
21
  // src/presence.ts
@@ -34,20 +34,31 @@ function assumptionPresenceComplete(record) {
34
34
  return missingPresenceFields(record).length === 0;
35
35
  }
36
36
 
37
- // src/recompute.ts
38
- var STANDING_DECISION = /* @__PURE__ */ new Set(["Active", "Provisional"]);
39
- function toConfidenceInput(r) {
37
+ // src/reading-input.ts
38
+ function str(v) {
39
+ return typeof v === "string" && v !== "" ? v : null;
40
+ }
41
+ function num(v) {
42
+ const n = Number(v);
43
+ return Number.isFinite(n) ? n : 0;
44
+ }
45
+ function toReadingInput(r) {
40
46
  return {
41
47
  id: r.id,
42
- source: r.Source,
48
+ source: str(r.Source),
43
49
  rung: r.Rung,
44
50
  result: r.Result,
45
- representativeness: r.Representativeness,
46
- credibility: r.Credibility,
47
- date: r.Date,
48
- magnitudeBand: r.magnitudeBand
51
+ representativeness: num(r.Representativeness),
52
+ credibility: num(r.Credibility),
53
+ date: str(r.Date),
54
+ magnitudeBand: r.magnitudeBand,
55
+ experimentId: str(r.experimentId),
56
+ goalId: str(r.goalId)
49
57
  };
50
58
  }
59
+
60
+ // src/recompute.ts
61
+ var STANDING_DECISION = /* @__PURE__ */ new Set(["Active", "Provisional"]);
51
62
  function recomputeDerived(input) {
52
63
  const { assumptions, readings, decisions } = input;
53
64
  const readingsByAssumption = /* @__PURE__ */ new Map();
@@ -56,7 +67,10 @@ function recomputeDerived(input) {
56
67
  const confidenceById = /* @__PURE__ */ new Map();
57
68
  for (const a of assumptions) {
58
69
  const rs = readingsByAssumption.get(a.id) ?? [];
59
- confidenceById.set(a.id, confidence(rs.map(toConfidenceInput)));
70
+ confidenceById.set(
71
+ a.id,
72
+ confidence(rs.map((r) => toReadingInput(r)))
73
+ );
60
74
  }
61
75
  const basedOnCounts = {};
62
76
  for (const d of decisions) {
@@ -97,6 +111,7 @@ export {
97
111
  missingPresenceFields,
98
112
  recomputeDerived,
99
113
  sourceQuality as recomputeSourceQuality,
100
- readingStrength as recomputeStrength
114
+ readingStrength as recomputeStrength,
115
+ toReadingInput
101
116
  };
102
117
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/presence.ts","../src/recompute.ts"],"sourcesContent":["/**\n * Presence checks — the structural half of the assumption write guardrail.\n *\n * `5 Whys`, `Metric for truth`, and `Scoring justification` used to live as\n * body prose audited as *semantic* Gaps (the audit had to parse markdown and\n * guess). OPS-1273 promotes them to first-class fields, so their PRESENCE is a\n * cheap structural check: non-empty is required to move an assumption to\n * `Live` — the presence half of the Draft→Live gaps invariant (OPS-1251).\n *\n * These are pure functions with no backend dependency: the primitive the CRUD\n * write model is to block a Draft→Live write on (write-time enforcement lands\n * with the write slice, OPS-1256), and that the audit reports as an error-level\n * finding meanwhile (`presence-field-missing` in `ontology.yaml`).\n */\n\n/** The assumption fields whose presence is structurally required to go `Live`. */\nexport const ASSUMPTION_PRESENCE_FIELDS = [\n \"5 Whys\",\n \"Metric for truth\",\n \"Scoring justification\",\n] as const;\n\nexport type AssumptionPresenceField = (typeof ASSUMPTION_PRESENCE_FIELDS)[number];\n\n/** A value counts as present only when it is a non-blank string. */\nfunction isPresent(value: unknown): boolean {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n\n/** The presence fields that are absent or blank on a record. */\nexport function missingPresenceFields(\n record: Partial<Record<AssumptionPresenceField, unknown>>,\n): AssumptionPresenceField[] {\n return ASSUMPTION_PRESENCE_FIELDS.filter((field) => !isPresent(record[field]));\n}\n\n/**\n * True when every presence field is non-blank — the structural precondition\n * for an assumption to be `Live`. A `Draft` may legally fail this.\n */\nexport function assumptionPresenceComplete(\n record: Partial<Record<AssumptionPresenceField, unknown>>,\n): boolean {\n return missingPresenceFields(record).length === 0;\n}\n","/**\n * Derive-on-write glue: recompute the four derived numbers for a whole\n * assumptions register from its readings and standing decisions, using the\n * pure derivation module. The API calls this server-side on every touching\n * write and writes the results back; a batch pass is the backstop for\n * non-dashboard writes.\n *\n * This is the one place that maps stored record shapes the derivation\n * module's typed inputs, so the pure functions stay decoupled from field\n * names.\n */\nimport {\n confidence,\n derivedImpacts,\n risk,\n type ConfidenceReadingInput,\n} from \"./derivation/index.js\";\nimport type {\n AssumptionDerived,\n AssumptionRecord,\n DecisionRecord,\n ReadingRecord,\n} from \"./types.js\";\n\n/** Standing decisions (Provisional/Active) contribute to Derived Impact. */\nconst STANDING_DECISION = new Set([\"Active\", \"Provisional\"]);\n\nfunction toConfidenceInput(r: ReadingRecord): ConfidenceReadingInput {\n return {\n id: r.id,\n source: r.Source,\n rung: r.Rung,\n result: r.Result,\n representativeness: r.Representativeness,\n credibility: r.Credibility,\n date: r.Date,\n magnitudeBand: r.magnitudeBand,\n };\n}\n\nexport interface RecomputeInput {\n assumptions: AssumptionRecord[];\n readings: ReadingRecord[];\n decisions: DecisionRecord[];\n}\n\n/** id → recomputed derived tuple for every assumption in the register. */\nexport function recomputeDerived(\n input: RecomputeInput,\n): Map<string, AssumptionDerived> {\n const { assumptions, readings, decisions } = input;\n\n // Confidence: group concluded readings by assumption.\n const readingsByAssumption = new Map<string, ReadingRecord[]>();\n for (const a of assumptions) readingsByAssumption.set(a.id, []);\n for (const r of readings) readingsByAssumption.get(r.assumptionId)?.push(r);\n\n const confidenceById = new Map<string, number>();\n for (const a of assumptions) {\n const rs = readingsByAssumption.get(a.id) ?? [];\n confidenceById.set(a.id, confidence(rs.map(toConfidenceInput)));\n }\n\n // Derived Impact: standing-decision `Based on` links count +100 each.\n const basedOnCounts: Record<string, number> = {};\n for (const d of decisions) {\n if (!STANDING_DECISION.has(d.Status)) continue;\n for (const aid of d.basedOnIds ?? []) {\n basedOnCounts[aid] = (basedOnCounts[aid] ?? 0) + 1;\n }\n }\n const impactById = derivedImpacts(\n assumptions.map((a) => ({\n id: a.id,\n impact: a.moot ? 0 : a.Impact,\n moot: a.moot,\n dependsOnIds: a.dependsOnIds,\n })),\n basedOnCounts,\n );\n\n const out = new Map<string, AssumptionDerived>();\n for (const a of assumptions) {\n const c = confidenceById.get(a.id) ?? 0;\n const di = impactById.get(a.id) ?? 0;\n out.set(a.id, { confidence: c, derivedImpact: di, risk: risk(di, c) });\n }\n return out;\n}\n\n/** Recompute Source quality + Strength for a single reading. */\nexport {\n sourceQuality as recomputeSourceQuality,\n readingStrength as recomputeStrength,\n} from \"./derivation/index.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgBO,IAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AACF;AAKA,SAAS,UAAU,OAAyB;AAC1C,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS;AAC5D;AAGO,SAAS,sBACd,QAC2B;AAC3B,SAAO,2BAA2B,OAAO,CAAC,UAAU,CAAC,UAAU,OAAO,KAAK,CAAC,CAAC;AAC/E;AAMO,SAAS,2BACd,QACS;AACT,SAAO,sBAAsB,MAAM,EAAE,WAAW;AAClD;;;ACnBA,IAAM,oBAAoB,oBAAI,IAAI,CAAC,UAAU,aAAa,CAAC;AAE3D,SAAS,kBAAkB,GAA0C;AACnE,SAAO;AAAA,IACL,IAAI,EAAE;AAAA,IACN,QAAQ,EAAE;AAAA,IACV,MAAM,EAAE;AAAA,IACR,QAAQ,EAAE;AAAA,IACV,oBAAoB,EAAE;AAAA,IACtB,aAAa,EAAE;AAAA,IACf,MAAM,EAAE;AAAA,IACR,eAAe,EAAE;AAAA,EACnB;AACF;AASO,SAAS,iBACd,OACgC;AAChC,QAAM,EAAE,aAAa,UAAU,UAAU,IAAI;AAG7C,QAAM,uBAAuB,oBAAI,IAA6B;AAC9D,aAAW,KAAK,YAAa,sBAAqB,IAAI,EAAE,IAAI,CAAC,CAAC;AAC9D,aAAW,KAAK,SAAU,sBAAqB,IAAI,EAAE,YAAY,GAAG,KAAK,CAAC;AAE1E,QAAM,iBAAiB,oBAAI,IAAoB;AAC/C,aAAW,KAAK,aAAa;AAC3B,UAAM,KAAK,qBAAqB,IAAI,EAAE,EAAE,KAAK,CAAC;AAC9C,mBAAe,IAAI,EAAE,IAAI,WAAW,GAAG,IAAI,iBAAiB,CAAC,CAAC;AAAA,EAChE;AAGA,QAAM,gBAAwC,CAAC;AAC/C,aAAW,KAAK,WAAW;AACzB,QAAI,CAAC,kBAAkB,IAAI,EAAE,MAAM,EAAG;AACtC,eAAW,OAAO,EAAE,cAAc,CAAC,GAAG;AACpC,oBAAc,GAAG,KAAK,cAAc,GAAG,KAAK,KAAK;AAAA,IACnD;AAAA,EACF;AACA,QAAM,aAAa;AAAA,IACjB,YAAY,IAAI,CAAC,OAAO;AAAA,MACtB,IAAI,EAAE;AAAA,MACN,QAAQ,EAAE,OAAO,IAAI,EAAE;AAAA,MACvB,MAAM,EAAE;AAAA,MACR,cAAc,EAAE;AAAA,IAClB,EAAE;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,oBAAI,IAA+B;AAC/C,aAAW,KAAK,aAAa;AAC3B,UAAM,IAAI,eAAe,IAAI,EAAE,EAAE,KAAK;AACtC,UAAM,KAAK,WAAW,IAAI,EAAE,EAAE,KAAK;AACnC,QAAI,IAAI,EAAE,IAAI,EAAE,YAAY,GAAG,eAAe,IAAI,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;AAAA,EACvE;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/presence.ts","../src/reading-input.ts","../src/recompute.ts"],"sourcesContent":["/**\n * Presence checks — the structural half of the assumption write guardrail.\n *\n * `5 Whys`, `Metric for truth`, and `Scoring justification` used to live as\n * body prose audited as *semantic* Gaps (the audit had to parse markdown and\n * guess). OPS-1273 promotes them to first-class fields, so their PRESENCE is a\n * cheap structural check: non-empty is required to move an assumption to\n * `Live` — the presence half of the Draft→Live gaps invariant (OPS-1251).\n *\n * These are pure functions with no backend dependency: the primitive the CRUD\n * write model is to block a Draft→Live write on (write-time enforcement lands\n * with the write slice, OPS-1256), and that the audit reports as an error-level\n * finding meanwhile (`presence-field-missing` in `ontology.yaml`).\n */\n\n/** The assumption fields whose presence is structurally required to go `Live`. */\nexport const ASSUMPTION_PRESENCE_FIELDS = [\n \"5 Whys\",\n \"Metric for truth\",\n \"Scoring justification\",\n] as const;\n\nexport type AssumptionPresenceField = (typeof ASSUMPTION_PRESENCE_FIELDS)[number];\n\n/** A value counts as present only when it is a non-blank string. */\nfunction isPresent(value: unknown): boolean {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n\n/** The presence fields that are absent or blank on a record. */\nexport function missingPresenceFields(\n record: Partial<Record<AssumptionPresenceField, unknown>>,\n): AssumptionPresenceField[] {\n return ASSUMPTION_PRESENCE_FIELDS.filter((field) => !isPresent(record[field]));\n}\n\n/**\n * True when every presence field is non-blank — the structural precondition\n * for an assumption to be `Live`. A `Draft` may legally fail this.\n */\nexport function assumptionPresenceComplete(\n record: Partial<Record<AssumptionPresenceField, unknown>>,\n): boolean {\n return missingPresenceFields(record).length === 0;\n}\n","/**\n * The one place that maps a stored reading record → the derivation module's\n * typed input, so the pure functions stay decoupled from field names. Both the\n * server-side recompute pass and the dashboard's understanding layer map\n * through here, so a reading is read identically wherever Confidence is derived\n * or explained. Coercion is defensive because a record's fields are `unknown`.\n */\nimport type { AnyRecord } from \"./types.js\";\nimport type { AttributionReadingInput } from \"./derivation/index.js\";\n\nfunction str(v: unknown): string | null {\n return typeof v === \"string\" && v !== \"\" ? v : null;\n}\n\nfunction num(v: unknown): number {\n const n = Number(v);\n return Number.isFinite(n) ? n : 0;\n}\n\n/** Map a reading record (canonical Title-cased fields) to its derivation input. */\nexport function toReadingInput(r: AnyRecord): AttributionReadingInput {\n return {\n id: r.id,\n source: str(r.Source),\n rung: r.Rung as AttributionReadingInput[\"rung\"],\n result: r.Result as AttributionReadingInput[\"result\"],\n representativeness: num(r.Representativeness),\n credibility: num(r.Credibility),\n date: str(r.Date),\n magnitudeBand: r.magnitudeBand as AttributionReadingInput[\"magnitudeBand\"],\n experimentId: str(r.experimentId),\n goalId: str(r.goalId),\n };\n}\n","/**\n * Derive-on-write glue: recompute the four derived numbers for a whole\n * assumptions register from its readings and standing decisions, using the\n * pure derivation module. The API calls this server-side on every touching\n * write and writes the results back; a batch pass is the backstop for\n * non-dashboard writes.\n *\n * The stored-record derivation-input mapping lives in `reading-input.ts`\n * (`toReadingInput`), shared with the dashboard's understanding layer so a\n * reading is read identically wherever Confidence is derived or explained.\n */\nimport { confidence, derivedImpacts, risk } from \"./derivation/index.js\";\nimport { toReadingInput } from \"./reading-input.js\";\nimport type {\n AnyRecord,\n AssumptionDerived,\n AssumptionRecord,\n DecisionRecord,\n ReadingRecord,\n} from \"./types.js\";\n\n/** Standing decisions (Provisional/Active) contribute to Derived Impact. */\nconst STANDING_DECISION = new Set([\"Active\", \"Provisional\"]);\n\nexport interface RecomputeInput {\n assumptions: AssumptionRecord[];\n readings: ReadingRecord[];\n decisions: DecisionRecord[];\n}\n\n/** id → recomputed derived tuple for every assumption in the register. */\nexport function recomputeDerived(\n input: RecomputeInput,\n): Map<string, AssumptionDerived> {\n const { assumptions, readings, decisions } = input;\n\n // Confidence: group concluded readings by assumption.\n const readingsByAssumption = new Map<string, ReadingRecord[]>();\n for (const a of assumptions) readingsByAssumption.set(a.id, []);\n for (const r of readings) readingsByAssumption.get(r.assumptionId)?.push(r);\n\n const confidenceById = new Map<string, number>();\n for (const a of assumptions) {\n const rs = readingsByAssumption.get(a.id) ?? [];\n confidenceById.set(\n a.id,\n confidence(rs.map((r) => toReadingInput(r as unknown as AnyRecord))),\n );\n }\n\n // Derived Impact: standing-decision `Based on` links count +100 each.\n const basedOnCounts: Record<string, number> = {};\n for (const d of decisions) {\n if (!STANDING_DECISION.has(d.Status)) continue;\n for (const aid of d.basedOnIds ?? []) {\n basedOnCounts[aid] = (basedOnCounts[aid] ?? 0) + 1;\n }\n }\n const impactById = derivedImpacts(\n assumptions.map((a) => ({\n id: a.id,\n impact: a.moot ? 0 : a.Impact,\n moot: a.moot,\n dependsOnIds: a.dependsOnIds,\n })),\n basedOnCounts,\n );\n\n const out = new Map<string, AssumptionDerived>();\n for (const a of assumptions) {\n const c = confidenceById.get(a.id) ?? 0;\n const di = impactById.get(a.id) ?? 0;\n out.set(a.id, { confidence: c, derivedImpact: di, risk: risk(di, c) });\n }\n return out;\n}\n\n/** Recompute Source quality + Strength for a single reading. */\nexport {\n sourceQuality as recomputeSourceQuality,\n readingStrength as recomputeStrength,\n} from \"./derivation/index.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgBO,IAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AACF;AAKA,SAAS,UAAU,OAAyB;AAC1C,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS;AAC5D;AAGO,SAAS,sBACd,QAC2B;AAC3B,SAAO,2BAA2B,OAAO,CAAC,UAAU,CAAC,UAAU,OAAO,KAAK,CAAC,CAAC;AAC/E;AAMO,SAAS,2BACd,QACS;AACT,SAAO,sBAAsB,MAAM,EAAE,WAAW;AAClD;;;AClCA,SAAS,IAAI,GAA2B;AACtC,SAAO,OAAO,MAAM,YAAY,MAAM,KAAK,IAAI;AACjD;AAEA,SAAS,IAAI,GAAoB;AAC/B,QAAM,IAAI,OAAO,CAAC;AAClB,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAGO,SAAS,eAAe,GAAuC;AACpE,SAAO;AAAA,IACL,IAAI,EAAE;AAAA,IACN,QAAQ,IAAI,EAAE,MAAM;AAAA,IACpB,MAAM,EAAE;AAAA,IACR,QAAQ,EAAE;AAAA,IACV,oBAAoB,IAAI,EAAE,kBAAkB;AAAA,IAC5C,aAAa,IAAI,EAAE,WAAW;AAAA,IAC9B,MAAM,IAAI,EAAE,IAAI;AAAA,IAChB,eAAe,EAAE;AAAA,IACjB,cAAc,IAAI,EAAE,YAAY;AAAA,IAChC,QAAQ,IAAI,EAAE,MAAM;AAAA,EACtB;AACF;;;ACXA,IAAM,oBAAoB,oBAAI,IAAI,CAAC,UAAU,aAAa,CAAC;AASpD,SAAS,iBACd,OACgC;AAChC,QAAM,EAAE,aAAa,UAAU,UAAU,IAAI;AAG7C,QAAM,uBAAuB,oBAAI,IAA6B;AAC9D,aAAW,KAAK,YAAa,sBAAqB,IAAI,EAAE,IAAI,CAAC,CAAC;AAC9D,aAAW,KAAK,SAAU,sBAAqB,IAAI,EAAE,YAAY,GAAG,KAAK,CAAC;AAE1E,QAAM,iBAAiB,oBAAI,IAAoB;AAC/C,aAAW,KAAK,aAAa;AAC3B,UAAM,KAAK,qBAAqB,IAAI,EAAE,EAAE,KAAK,CAAC;AAC9C,mBAAe;AAAA,MACb,EAAE;AAAA,MACF,WAAW,GAAG,IAAI,CAAC,MAAM,eAAe,CAAyB,CAAC,CAAC;AAAA,IACrE;AAAA,EACF;AAGA,QAAM,gBAAwC,CAAC;AAC/C,aAAW,KAAK,WAAW;AACzB,QAAI,CAAC,kBAAkB,IAAI,EAAE,MAAM,EAAG;AACtC,eAAW,OAAO,EAAE,cAAc,CAAC,GAAG;AACpC,oBAAc,GAAG,KAAK,cAAc,GAAG,KAAK,KAAK;AAAA,IACnD;AAAA,EACF;AACA,QAAM,aAAa;AAAA,IACjB,YAAY,IAAI,CAAC,OAAO;AAAA,MACtB,IAAI,EAAE;AAAA,MACN,QAAQ,EAAE,OAAO,IAAI,EAAE;AAAA,MACvB,MAAM,EAAE;AAAA,MACR,cAAc,EAAE;AAAA,IAClB,EAAE;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,oBAAI,IAA+B;AAC/C,aAAW,KAAK,aAAa;AAC3B,UAAM,IAAI,eAAe,IAAI,EAAE,EAAE,KAAK;AACtC,UAAM,KAAK,WAAW,IAAI,EAAE,EAAE,KAAK;AACnC,QAAI,IAAI,EAAE,IAAI,EAAE,YAAY,GAAG,eAAe,IAAI,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;AAAA,EACvE;AACA,SAAO;AACT;","names":[]}
@@ -1,4 +1,4 @@
1
- import { C as Collection, c as AnyRecord, R as Relation, m as RecordRef } from './types-D4Kn18fv.js';
1
+ import { C as Collection, c as AnyRecord, R as Relation, n as RecordRef } from './types-B3eI7ASx.js';
2
2
 
3
3
  /**
4
4
  * The `DataProvider` — the single integration seam between the dashboard/API
package/dist/testing.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as DataProvider } from './provider-78NHbYi9.js';
2
- import { C as Collection, c as AnyRecord, R as Relation, m as RecordRef } from './types-D4Kn18fv.js';
1
+ import { D as DataProvider } from './provider-hW8Hxf-n.js';
2
+ import { C as Collection, c as AnyRecord, R as Relation, n as RecordRef } from './types-B3eI7ASx.js';
3
3
 
4
4
  interface InMemoryProviderOptions {
5
5
  now?: () => string;
package/dist/testing.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  NotFoundError,
3
3
  RELATIONS,
4
4
  StaleVersionError
5
- } from "./chunk-CUD5PEXL.js";
5
+ } from "./chunk-W7VU5JOU.js";
6
6
  import "./chunk-PZ5AY32C.js";
7
7
 
8
8
  // src/testing.ts
@@ -87,6 +87,19 @@ interface ReadingRecord extends BaseRecord {
87
87
  strength: number;
88
88
  };
89
89
  }
90
+ /**
91
+ * A pre-registered bar line — the per-belief pass/fail test on an experiment,
92
+ * stored as an embedded array on the experiment (no identity of its own; see
93
+ * `connectors/nosql-schema.md`). `barVerdict` is set once at closure and is a
94
+ * report only — never folded into Confidence.
95
+ */
96
+ interface BarLine {
97
+ assumptionId: string;
98
+ rightIf: string;
99
+ wrongIf?: string | null;
100
+ plannedRung: Rung;
101
+ barVerdict?: Result | null;
102
+ }
90
103
  interface ExperimentRecord extends BaseRecord {
91
104
  Title: string;
92
105
  Instrument: string | null;
@@ -95,6 +108,9 @@ interface ExperimentRecord extends BaseRecord {
95
108
  closureReason: "Completed" | "Early-stop" | "Kill" | null;
96
109
  Owner: string[];
97
110
  Date: string | null;
111
+ /** The embedded pre-registered bar lines; drives progress-to-conclusion. */
112
+ barLines: BarLine[];
113
+ /** Convenience projection: the assumptions the bar lines test. */
98
114
  barLineAssumptionIds: string[];
99
115
  }
100
116
  interface GoalRecord extends BaseRecord {
@@ -126,4 +142,4 @@ interface RecordRef {
126
142
  /** The relations the dashboard can set (each writes both ends). */
127
143
  type Relation = "assumption-reading" | "assumption-depends-on" | "assumption-contradicts" | "reading-experiment" | "reading-goal" | "decision-based-on" | "decision-resolves" | "goal-based-on";
128
144
 
129
- export { type AssumptionRecord as A, type BaseRecord as B, type Collection as C, type DecisionRecord as D, type ExperimentRecord as E, type Feasibility as F, GOAL_RUNG_VALUES as G, type MagnitudeBand as M, type Relation as R, type SourceQualityPick as S, TESTING_RUNGS as T, type ReadingRecord as a, type AssumptionDerived as b, type AnyRecord as c, type AssumptionStatus as d, type DecisionStatus as e, type ExperimentStatus as f, type GlossaryRecord as g, type GlossaryStatus as h, type GoalRecord as i, type GoalRung as j, type GoalStatus as k, REGISTERS as l, type RecordRef as m, type Register as n, type Result as o, type Rung as p, type TestingRung as q };
145
+ export { type AssumptionRecord as A, type BarLine as B, type Collection as C, type DecisionRecord as D, type ExperimentRecord as E, type Feasibility as F, GOAL_RUNG_VALUES as G, type MagnitudeBand as M, type Relation as R, type SourceQualityPick as S, TESTING_RUNGS as T, type ReadingRecord as a, type AssumptionDerived as b, type AnyRecord as c, type AssumptionStatus as d, type BaseRecord as e, type DecisionStatus as f, type ExperimentStatus as g, type GlossaryRecord as h, type GlossaryStatus as i, type GoalRecord as j, type GoalRung as k, type GoalStatus as l, REGISTERS as m, type RecordRef as n, type Register as o, type Result as p, type Rung as q, type TestingRung as r };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@validation-os/core",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "The DataProvider seam, the shared derivation module (pure functions), and shared registry types for validation-os.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/provider.ts","../src/relations.ts"],"sourcesContent":["/**\n * The `DataProvider` — the single integration seam between the dashboard/API\n * and a concrete backend. Hand-rolled (patterns borrowed from\n * Refine/React-Admin's dataProvider, not forked). One adapter per backend;\n * Firestore is the first. Writing a new adapter is a bounded task: implement\n * these five methods.\n */\nimport type { AnyRecord, Collection, RecordRef, Relation } from \"./types.js\";\n\nexport interface DataProvider {\n /** Every row of a register — never a filtered view (a filter drops rows). */\n list(register: Collection): Promise<AnyRecord[]>;\n get(register: Collection, id: string): Promise<AnyRecord>;\n create(register: Collection, data: Partial<AnyRecord>): Promise<AnyRecord>;\n /**\n * Version-guarded update: `version` is the value the caller loaded. If the\n * stored version has moved on, the write is rejected with\n * {@link StaleVersionError} (surfaced to the user as a 409).\n */\n update(\n register: Collection,\n id: string,\n patch: Partial<AnyRecord>,\n version: number,\n ): Promise<AnyRecord>;\n /** Set a relation on both ends in one logical write. */\n link(relation: Relation, from: RecordRef, to: RecordRef): Promise<void>;\n}\n\n/**\n * Thrown when an update carries a version older than what is stored — a\n * concurrent edit landed first. The API turns this into a 409 with\n * plain-language copy; it is never surfaced as version jargon.\n */\nexport class StaleVersionError extends Error {\n override readonly name = \"StaleVersionError\";\n constructor(\n readonly register: Collection,\n readonly id: string,\n readonly expected: number,\n readonly actual: number,\n ) {\n super(\n `Stale write to ${register}/${id}: caller had version ${expected}, ` +\n `stored is ${actual}.`,\n );\n }\n}\n\nexport function isStaleVersionError(e: unknown): e is StaleVersionError {\n return e instanceof StaleVersionError;\n}\n\n/** Thrown when a get/update targets a record that does not exist (→ 404). */\nexport class NotFoundError extends Error {\n override readonly name = \"NotFoundError\";\n constructor(\n readonly register: Collection,\n readonly id: string,\n ) {\n super(`No ${register} record with id ${id}.`);\n }\n}\n\nexport function isNotFoundError(e: unknown): e is NotFoundError {\n return e instanceof NotFoundError;\n}\n","/**\n * Relation config — the single table describing, for each linkable relation,\n * which field on which register holds each end. `link()` sets both ends from\n * this table, so relations stay consistent no matter which side initiated.\n *\n * A `null` `to` end means the inverse is a *derived view*, not a stored field\n * (e.g. \"experiments testing me\" is computed over bar-lines; a decision's\n * `Based on` never touches the assumption). Those relations write one end.\n */\nimport type { Collection, Relation } from \"./types.js\";\n\nexport interface RelationEnd {\n register: Collection;\n field: string;\n cardinality: \"one\" | \"many\";\n}\n\nexport interface RelationSpec {\n /** The end named by `from` in `link(relation, from, to)`. */\n from: RelationEnd;\n /** The end named by `to`; null when the inverse is derived, not stored. */\n to: RelationEnd | null;\n}\n\nexport const RELATIONS: Record<Relation, RelationSpec> = {\n \"assumption-reading\": {\n from: { register: \"assumptions\", field: \"readingIds\", cardinality: \"many\" },\n to: { register: \"readings\", field: \"assumptionId\", cardinality: \"one\" },\n },\n \"assumption-depends-on\": {\n from: {\n register: \"assumptions\",\n field: \"dependsOnIds\",\n cardinality: \"many\",\n },\n to: { register: \"assumptions\", field: \"enablesIds\", cardinality: \"many\" },\n },\n \"assumption-contradicts\": {\n from: {\n register: \"assumptions\",\n field: \"contradictsIds\",\n cardinality: \"many\",\n },\n to: {\n register: \"assumptions\",\n field: \"contradictsIds\",\n cardinality: \"many\",\n },\n },\n \"reading-experiment\": {\n from: { register: \"readings\", field: \"experimentId\", cardinality: \"one\" },\n to: null, // experiment→readings is derived\n },\n \"reading-goal\": {\n from: { register: \"readings\", field: \"goalId\", cardinality: \"one\" },\n to: null,\n },\n \"decision-based-on\": {\n from: { register: \"decisions\", field: \"basedOnIds\", cardinality: \"many\" },\n to: null, // never touches the assumption\n },\n \"decision-resolves\": {\n from: { register: \"decisions\", field: \"resolvesIds\", cardinality: \"many\" },\n to: null, // mooting the assumption is a gated business action, not a link\n },\n \"goal-based-on\": {\n from: { register: \"goals\", field: \"basedOnIds\", cardinality: \"many\" },\n to: null, // goal linkage is a per-goal view, never stored on the assumption\n },\n};\n"],"mappings":";AAkCO,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAE3C,YACW,UACA,IACA,UACA,QACT;AACA;AAAA,MACE,kBAAkB,QAAQ,IAAI,EAAE,wBAAwB,QAAQ,eACjD,MAAM;AAAA,IACvB;AARS;AACA;AACA;AACA;AAAA,EAMX;AAAA,EATW;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EALO,OAAO;AAY3B;AAEO,SAAS,oBAAoB,GAAoC;AACtE,SAAO,aAAa;AACtB;AAGO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EAEvC,YACW,UACA,IACT;AACA,UAAM,MAAM,QAAQ,mBAAmB,EAAE,GAAG;AAHnC;AACA;AAAA,EAGX;AAAA,EAJW;AAAA,EACA;AAAA,EAHO,OAAO;AAO3B;AAEO,SAAS,gBAAgB,GAAgC;AAC9D,SAAO,aAAa;AACtB;;;AC1CO,IAAM,YAA4C;AAAA,EACvD,sBAAsB;AAAA,IACpB,MAAM,EAAE,UAAU,eAAe,OAAO,cAAc,aAAa,OAAO;AAAA,IAC1E,IAAI,EAAE,UAAU,YAAY,OAAO,gBAAgB,aAAa,MAAM;AAAA,EACxE;AAAA,EACA,yBAAyB;AAAA,IACvB,MAAM;AAAA,MACJ,UAAU;AAAA,MACV,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,IAAI,EAAE,UAAU,eAAe,OAAO,cAAc,aAAa,OAAO;AAAA,EAC1E;AAAA,EACA,0BAA0B;AAAA,IACxB,MAAM;AAAA,MACJ,UAAU;AAAA,MACV,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,IAAI;AAAA,MACF,UAAU;AAAA,MACV,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,sBAAsB;AAAA,IACpB,MAAM,EAAE,UAAU,YAAY,OAAO,gBAAgB,aAAa,MAAM;AAAA,IACxE,IAAI;AAAA;AAAA,EACN;AAAA,EACA,gBAAgB;AAAA,IACd,MAAM,EAAE,UAAU,YAAY,OAAO,UAAU,aAAa,MAAM;AAAA,IAClE,IAAI;AAAA,EACN;AAAA,EACA,qBAAqB;AAAA,IACnB,MAAM,EAAE,UAAU,aAAa,OAAO,cAAc,aAAa,OAAO;AAAA,IACxE,IAAI;AAAA;AAAA,EACN;AAAA,EACA,qBAAqB;AAAA,IACnB,MAAM,EAAE,UAAU,aAAa,OAAO,eAAe,aAAa,OAAO;AAAA,IACzE,IAAI;AAAA;AAAA,EACN;AAAA,EACA,iBAAiB;AAAA,IACf,MAAM,EAAE,UAAU,SAAS,OAAO,cAAc,aAAa,OAAO;AAAA,IACpE,IAAI;AAAA;AAAA,EACN;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/derivation/index.ts","../src/derivation/round.ts","../src/types.ts","../src/derivation/rung.ts","../src/derivation/strength.ts","../src/derivation/source-quality.ts","../src/derivation/confidence.ts","../src/derivation/impact.ts","../src/derivation/risk.ts"],"sourcesContent":["/**\n * The shared derivation module — pure functions, no I/O.\n *\n * The same module the dashboard, the API (derive-on-write), and Claude Code\n * audits all call, so every writer computes the four derived numbers\n * identically. Ported from `doshi-validation-os/migration/remodel.mjs` and\n * kept in lock-step with `skills/_shared/ontology.yaml`.\n */\nexport { round2 } from \"./round.js\";\nexport {\n RUNG_ANCHOR,\n GOAL_RUNG_ANCHOR,\n isGoalRung,\n} from \"./rung.js\";\nexport { sign, readingStrength } from \"./strength.js\";\nexport type { StrengthInput } from \"./strength.js\";\nexport { sourceQuality } from \"./source-quality.js\";\nexport { confidence, W0 } from \"./confidence.js\";\nexport type { ConfidenceReadingInput } from \"./confidence.js\";\nexport { derivedImpacts } from \"./impact.js\";\nexport type { ImpactAssumptionInput } from \"./impact.js\";\nexport { risk } from \"./risk.js\";\n","/**\n * Round to 2 decimals, matching the migration's `+(n).toFixed(2)`.\n * Derived values are stored/displayed rounded; full precision is only used\n * transiently inside a single computation.\n */\nexport function round2(n: number): number {\n return Number(n.toFixed(2));\n}\n","/**\n * Shared registry types — the field vocabulary the whole system speaks.\n *\n * These mirror `skills/_shared/registry-schema.md` and `ontology.yaml` (the\n * single source of truth). Field *meaning* lives there; this file is the\n * checkable TypeScript shape the packages import.\n */\n\n/** The six registers plus the `people` reference collection. */\nexport const REGISTERS = [\n \"assumptions\",\n \"experiments\",\n \"readings\",\n \"goals\",\n \"decisions\",\n \"glossary\",\n] as const;\nexport type Register = (typeof REGISTERS)[number];\n\n/** `people` is a reference collection, not one of the six registers. */\nexport type Collection = Register | \"people\";\n\n/** Every stored record carries an id, a version, and timestamps. */\nexport interface BaseRecord {\n id: string;\n /** Optimistic-concurrency token; bumped on every write. */\n version: number;\n createdAt: string;\n updatedAt: string;\n}\n\n// ── Vocabularies (canonical select-option lists) ────────────────────────────\n\nexport type AssumptionStatus = \"Draft\" | \"Live\" | \"Invalidated\";\nexport type ExperimentStatus = \"Running\" | \"Closed\";\nexport type GoalStatus = \"Draft\" | \"Active\" | \"Closed\";\nexport type DecisionStatus =\n | \"Active\"\n | \"Provisional\"\n | \"Superseded\"\n | \"Reversed\";\nexport type GlossaryStatus = \"Active\" | \"Provisional\" | \"Superseded\";\n\nexport type Result = \"Validated\" | \"Invalidated\" | \"Inconclusive\";\nexport type MagnitudeBand = \"Low\" | \"Typical\" | \"High\";\nexport type Feasibility = \"High\" | \"Medium\" | \"Low\";\n\n/** The 8-rung activity-and-strength ladder (order = strength, weakest first). */\nexport const TESTING_RUNGS = [\n \"Opinion\",\n \"Pitch-deck reaction\",\n \"Anecdotal\",\n \"Desk research\",\n \"Survey at scale\",\n \"Prototype usage\",\n] as const;\nexport const GOAL_RUNG_VALUES = [\"Signed intent\", \"Paying users\"] as const;\nexport type TestingRung = (typeof TESTING_RUNGS)[number];\nexport type GoalRung = (typeof GOAL_RUNG_VALUES)[number];\nexport type Rung = TestingRung | GoalRung;\n\n/** Representativeness and Credibility are each picked from these. */\nexport type SourceQualityPick = 1.0 | 0.7 | 0.5;\n\n// ── Records ─────────────────────────────────────────────────────────────────\n\n/** The four derived numbers stored on an assumption (never hand-typed). */\nexport interface AssumptionDerived {\n derivedImpact: number;\n risk: number;\n confidence: number;\n}\n\nexport interface AssumptionRecord extends BaseRecord {\n Title: string;\n Description: string;\n Lens: string | null;\n Theme: string[];\n /** The hand-scored seed (0–100), the only hand-scored number here. */\n Impact: number | null;\n Status: AssumptionStatus;\n Owner: string[];\n Gaps: string[];\n moot: boolean;\n /**\n * Presence-gap fields — long-form prose promoted from body sections to\n * first-class fields (OPS-1273). Their PRESENCE is a structural, blocking\n * check (see `presence.ts`): non-empty is required to move Status to `Live`\n * (the presence half of the Draft→Live gaps invariant, OPS-1251). May be\n * empty (\"\") while `Draft`.\n */\n \"5 Whys\": string;\n \"Metric for truth\": string;\n \"Scoring justification\": string;\n dependsOnIds: string[];\n enablesIds: string[];\n contradictsIds: string[];\n readingIds: string[];\n derived: AssumptionDerived;\n}\n\nexport interface ReadingRecord extends BaseRecord {\n Title: string;\n /** First-class link to the artifact — the independence-dedupe key. */\n Source: string | null;\n assumptionId: string;\n experimentId: string | null;\n goalId: string | null;\n Rung: Rung;\n Representativeness: SourceQualityPick;\n Credibility: SourceQualityPick;\n /** For Goal-rung readings: the magnitude band from the absolute outcome. */\n magnitudeBand?: MagnitudeBand;\n Result: Result;\n Date: string | null;\n Owner: string[];\n derived: { sourceQuality: number; strength: number };\n}\n\nexport interface ExperimentRecord extends BaseRecord {\n Title: string;\n Instrument: string | null;\n Feasibility: Feasibility | null;\n Status: ExperimentStatus;\n closureReason: \"Completed\" | \"Early-stop\" | \"Kill\" | null;\n Owner: string[];\n Date: string | null;\n barLineAssumptionIds: string[];\n}\n\nexport interface GoalRecord extends BaseRecord {\n Title: string;\n Status: GoalStatus;\n Outcome: \"Achieved\" | \"Missed\" | \"Dropped\" | null;\n Owner: string[];\n Date: string | null;\n basedOnIds: string[];\n}\n\nexport interface DecisionRecord extends BaseRecord {\n Title: string;\n Status: DecisionStatus;\n Owner: string[];\n basedOnIds: string[];\n resolvesIds: string[];\n}\n\nexport interface GlossaryRecord extends BaseRecord {\n Title: string;\n Status: GlossaryStatus;\n}\n\n/** A record of any register — the DataProvider's currency. */\nexport type AnyRecord = BaseRecord & Record<string, unknown>;\n\n// ── Relations (both-ends links) ─────────────────────────────────────────────\n\n/** A pointer to one record. */\nexport interface RecordRef {\n register: Collection;\n id: string;\n}\n\n/** The relations the dashboard can set (each writes both ends). */\nexport type Relation =\n | \"assumption-reading\"\n | \"assumption-depends-on\"\n | \"assumption-contradicts\"\n | \"reading-experiment\"\n | \"reading-goal\"\n | \"decision-based-on\"\n | \"decision-resolves\"\n | \"goal-based-on\";\n","/**\n * The evidence ladder anchors that feed Strength.\n *\n * Source of truth: `skills/_shared/ontology.yaml` → `vocabularies.rung`.\n * Testing rungs carry a single anchor; Goal rungs carry a magnitude band\n * (Low/Typical/High) picked from the absolute outcome.\n */\nimport type { GoalRung, MagnitudeBand, Rung, TestingRung } from \"../types.js\";\nimport { GOAL_RUNG_VALUES } from \"../types.js\";\n\nexport const RUNG_ANCHOR: Record<TestingRung, number> = {\n Opinion: 3,\n \"Pitch-deck reaction\": 6,\n Anecdotal: 10,\n \"Desk research\": 15,\n \"Survey at scale\": 25,\n \"Prototype usage\": 30,\n};\n\nexport const GOAL_RUNG_ANCHOR: Record<GoalRung, Record<MagnitudeBand, number>> =\n {\n \"Signed intent\": { Low: 55, Typical: 68, High: 80 },\n \"Paying users\": { Low: 75, Typical: 88, High: 99 },\n };\n\nconst GOAL_RUNG_SET = new Set<Rung>(GOAL_RUNG_VALUES);\n\nexport function isGoalRung(rung: Rung): rung is GoalRung {\n return GOAL_RUNG_SET.has(rung);\n}\n","/**\n * Strength — the signed reading value `s` the Confidence average reads.\n *\n * Formula (`ontology.yaml` → `derivations.strength`):\n * rung anchor (Goal rungs: × magnitude band) × sign(Result)\n * — Validated positive, Invalidated negative; 0 unless Validated/Invalidated.\n */\nimport type { MagnitudeBand, Result, Rung } from \"../types.js\";\nimport { GOAL_RUNG_ANCHOR, RUNG_ANCHOR, isGoalRung } from \"./rung.js\";\n\nexport function sign(result: Result): -1 | 0 | 1 {\n if (result === \"Validated\") return 1;\n if (result === \"Invalidated\") return -1;\n return 0;\n}\n\nexport interface StrengthInput {\n rung: Rung;\n result: Result;\n /** Only read for Goal rungs; defaults to \"Typical\" when absent. */\n magnitudeBand?: MagnitudeBand;\n}\n\nexport function readingStrength(input: StrengthInput): number {\n const s = sign(input.result);\n if (s === 0) return 0; // Inconclusive contributes nothing.\n if (isGoalRung(input.rung)) {\n const band = input.magnitudeBand ?? \"Typical\";\n return GOAL_RUNG_ANCHOR[input.rung][band] * s;\n }\n return (RUNG_ANCHOR[input.rung] ?? 0) * s;\n}\n","/**\n * Source quality — Representativeness × Credibility.\n *\n * Scales a Reading's *weight* in the Confidence average, within its rung.\n * We keep the raw product as the weight (matching the migration's\n * `remodel.mjs`); the five display anchors {0.25, 0.35, 0.5, 0.7, 1.0} are a\n * storage/display concern, not the weight used in the average.\n */\nimport { round2 } from \"./round.js\";\n\nexport function sourceQuality(\n representativeness: number,\n credibility: number,\n): number {\n return round2(representativeness * credibility);\n}\n","/**\n * Confidence — signed −100…100, 0 = no evidence.\n *\n * Formula (`ontology.yaml` → `derivations.confidence`):\n * (w0·0 + Σ wi·si) / (w0 + Σ wi), w0 = 100,\n * wi = |si| × Source quality, si = the reading's signed Strength.\n *\n * Only concluded Validated/Invalidated readings enter. Readings sharing a\n * Source against one belief dedupe to the strongest (largest |si|, most\n * recent on ties). Goal-rung readings never dedupe (each closed goal is its\n * own unit). No corroboration bump.\n */\nimport type { MagnitudeBand, Result, Rung } from \"../types.js\";\nimport { round2 } from \"./round.js\";\nimport { isGoalRung } from \"./rung.js\";\nimport { sourceQuality } from \"./source-quality.js\";\nimport { readingStrength } from \"./strength.js\";\n\n/** The neutral prior weight — a hard floor per the guardrails. */\nexport const W0 = 100;\n\nexport interface ConfidenceReadingInput {\n id: string;\n /** The independence-dedupe key. Null falls back to the reading's own id. */\n source: string | null;\n rung: Rung;\n result: Result;\n representativeness: number;\n credibility: number;\n /** ISO date; used only as the dedupe tie-break (most recent wins). */\n date?: string | null;\n magnitudeBand?: MagnitudeBand;\n}\n\ninterface Scored {\n input: ConfidenceReadingInput;\n strength: number;\n sq: number;\n}\n\nexport function confidence(readings: ConfidenceReadingInput[]): number {\n const scored: Scored[] = readings\n .filter((r) => r.result === \"Validated\" || r.result === \"Invalidated\")\n .map((r) => ({\n input: r,\n strength: readingStrength(r),\n sq: sourceQuality(r.representativeness, r.credibility),\n }))\n .filter((x) => x.strength !== 0);\n\n // Dedupe by Source; goal rungs never dedupe.\n const best = new Map<string, Scored>();\n for (const x of scored) {\n if (isGoalRung(x.input.rung)) {\n best.set(x.input.id, x);\n continue;\n }\n const key = x.input.source || x.input.id;\n const cur = best.get(key);\n const better =\n !cur ||\n Math.abs(x.strength) > Math.abs(cur.strength) ||\n (Math.abs(x.strength) === Math.abs(cur.strength) &&\n (x.input.date || \"\") > (cur.input.date || \"\"));\n if (better) best.set(key, x);\n }\n\n let num = 0;\n let den = W0;\n for (const x of best.values()) {\n const w = Math.abs(x.strength) * x.sq;\n num += w * x.strength;\n den += w;\n }\n return den > 0 ? round2(num / den) : 0;\n}\n","/**\n * Derived Impact — propagates dependents' pull into a belief's seed.\n *\n * Formula (`ontology.yaml` → `derivations.derived_impact`):\n * seed + (100 − seed) × S / (S + 100), where\n * S = Σ Derived Impact of assumptions whose `Depends on` names this row\n * + 100 per standing (Provisional/Active) decision naming it via\n * `Based on assumption`.\n * Goals never contribute. Moot rows pin to 0 and contribute nothing.\n *\n * One reverse-topological pass (dependents first) with memoization and a\n * cycle guard, matching `doshi-validation-os/migration/remodel.mjs`.\n */\nimport { round2 } from \"./round.js\";\n\nexport interface ImpactAssumptionInput {\n id: string;\n /** The hand-scored seed (0–100); null treated as 0. */\n impact: number | null;\n moot?: boolean;\n /** Ids this assumption depends on. */\n dependsOnIds: string[];\n}\n\n/**\n * @param assumptions the full register (never a filtered slice — a filter\n * silently drops dependents from the propagation).\n * @param basedOnCounts id → number of standing decisions with a `Based on`\n * link to that assumption. Each contributes +100 to S.\n */\nexport function derivedImpacts(\n assumptions: ImpactAssumptionInput[],\n basedOnCounts: Record<string, number> = {},\n): Map<string, number> {\n const byId = new Map(assumptions.map((a) => [a.id, a]));\n\n // dependents(X) = assumptions whose dependsOnIds includes X.\n const dependents = new Map<string, string[]>();\n for (const a of assumptions) dependents.set(a.id, []);\n for (const a of assumptions) {\n for (const dep of a.dependsOnIds) dependents.get(dep)?.push(a.id);\n }\n\n const memo = new Map<string, number>();\n const compute = (id: string, seen: Set<string>): number => {\n const cached = memo.get(id);\n if (cached !== undefined) return cached;\n if (seen.has(id)) return 0; // cycle guard\n seen.add(id);\n const a = byId.get(id);\n if (!a) return 0;\n if (a.moot) {\n memo.set(id, 0);\n return 0;\n }\n const seed = a.impact ?? 0;\n let S = 0;\n for (const depId of dependents.get(id) ?? []) {\n const d = byId.get(depId);\n if (d && !d.moot) S += compute(depId, seen);\n }\n S += 100 * (basedOnCounts[id] ?? 0);\n const value = seed + (100 - seed) * (S / (S + 100));\n const rounded = round2(value);\n memo.set(id, rounded);\n return rounded;\n };\n\n const out = new Map<string, number>();\n for (const a of assumptions) out.set(a.id, compute(a.id, new Set()));\n return out;\n}\n","/**\n * Risk — the belief's live standing.\n *\n * Formula (`ontology.yaml` → `derivations.risk`):\n * Derived Impact × (1 − max(0, Confidence) / 100)\n * Ranges 0 to Derived Impact. Negative confidence does not raise Risk above\n * Derived Impact (the max(0, …) clamp).\n */\nimport { round2 } from \"./round.js\";\n\nexport function risk(derivedImpact: number, confidence: number): number {\n return round2(derivedImpact * (1 - Math.max(0, confidence) / 100));\n}\n"],"mappings":"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","names":["confidence"]}
@@ -1,115 +0,0 @@
1
- import { j as GoalRung, M as MagnitudeBand, q as TestingRung, p as Rung, o as Result } from './types-D4Kn18fv.js';
2
-
3
- /**
4
- * Round to 2 decimals, matching the migration's `+(n).toFixed(2)`.
5
- * Derived values are stored/displayed rounded; full precision is only used
6
- * transiently inside a single computation.
7
- */
8
- declare function round2(n: number): number;
9
-
10
- /**
11
- * The evidence ladder anchors that feed Strength.
12
- *
13
- * Source of truth: `skills/_shared/ontology.yaml` → `vocabularies.rung`.
14
- * Testing rungs carry a single anchor; Goal rungs carry a magnitude band
15
- * (Low/Typical/High) picked from the absolute outcome.
16
- */
17
-
18
- declare const RUNG_ANCHOR: Record<TestingRung, number>;
19
- declare const GOAL_RUNG_ANCHOR: Record<GoalRung, Record<MagnitudeBand, number>>;
20
- declare function isGoalRung(rung: Rung): rung is GoalRung;
21
-
22
- /**
23
- * Strength — the signed reading value `s` the Confidence average reads.
24
- *
25
- * Formula (`ontology.yaml` → `derivations.strength`):
26
- * rung anchor (Goal rungs: × magnitude band) × sign(Result)
27
- * — Validated positive, Invalidated negative; 0 unless Validated/Invalidated.
28
- */
29
-
30
- declare function sign(result: Result): -1 | 0 | 1;
31
- interface StrengthInput {
32
- rung: Rung;
33
- result: Result;
34
- /** Only read for Goal rungs; defaults to "Typical" when absent. */
35
- magnitudeBand?: MagnitudeBand;
36
- }
37
- declare function readingStrength(input: StrengthInput): number;
38
-
39
- declare function sourceQuality(representativeness: number, credibility: number): number;
40
-
41
- /**
42
- * Confidence — signed −100…100, 0 = no evidence.
43
- *
44
- * Formula (`ontology.yaml` → `derivations.confidence`):
45
- * (w0·0 + Σ wi·si) / (w0 + Σ wi), w0 = 100,
46
- * wi = |si| × Source quality, si = the reading's signed Strength.
47
- *
48
- * Only concluded Validated/Invalidated readings enter. Readings sharing a
49
- * Source against one belief dedupe to the strongest (largest |si|, most
50
- * recent on ties). Goal-rung readings never dedupe (each closed goal is its
51
- * own unit). No corroboration bump.
52
- */
53
-
54
- /** The neutral prior weight — a hard floor per the guardrails. */
55
- declare const W0 = 100;
56
- interface ConfidenceReadingInput {
57
- id: string;
58
- /** The independence-dedupe key. Null falls back to the reading's own id. */
59
- source: string | null;
60
- rung: Rung;
61
- result: Result;
62
- representativeness: number;
63
- credibility: number;
64
- /** ISO date; used only as the dedupe tie-break (most recent wins). */
65
- date?: string | null;
66
- magnitudeBand?: MagnitudeBand;
67
- }
68
- declare function confidence(readings: ConfidenceReadingInput[]): number;
69
-
70
- interface ImpactAssumptionInput {
71
- id: string;
72
- /** The hand-scored seed (0–100); null treated as 0. */
73
- impact: number | null;
74
- moot?: boolean;
75
- /** Ids this assumption depends on. */
76
- dependsOnIds: string[];
77
- }
78
- /**
79
- * @param assumptions the full register (never a filtered slice — a filter
80
- * silently drops dependents from the propagation).
81
- * @param basedOnCounts id → number of standing decisions with a `Based on`
82
- * link to that assumption. Each contributes +100 to S.
83
- */
84
- declare function derivedImpacts(assumptions: ImpactAssumptionInput[], basedOnCounts?: Record<string, number>): Map<string, number>;
85
-
86
- declare function risk(derivedImpact: number, confidence: number): number;
87
-
88
- /**
89
- * The shared derivation module — pure functions, no I/O.
90
- *
91
- * The same module the dashboard, the API (derive-on-write), and Claude Code
92
- * audits all call, so every writer computes the four derived numbers
93
- * identically. Ported from `doshi-validation-os/migration/remodel.mjs` and
94
- * kept in lock-step with `skills/_shared/ontology.yaml`.
95
- */
96
-
97
- type index_ConfidenceReadingInput = ConfidenceReadingInput;
98
- declare const index_GOAL_RUNG_ANCHOR: typeof GOAL_RUNG_ANCHOR;
99
- type index_ImpactAssumptionInput = ImpactAssumptionInput;
100
- declare const index_RUNG_ANCHOR: typeof RUNG_ANCHOR;
101
- type index_StrengthInput = StrengthInput;
102
- declare const index_W0: typeof W0;
103
- declare const index_confidence: typeof confidence;
104
- declare const index_derivedImpacts: typeof derivedImpacts;
105
- declare const index_isGoalRung: typeof isGoalRung;
106
- declare const index_readingStrength: typeof readingStrength;
107
- declare const index_risk: typeof risk;
108
- declare const index_round2: typeof round2;
109
- declare const index_sign: typeof sign;
110
- declare const index_sourceQuality: typeof sourceQuality;
111
- declare namespace index {
112
- export { type index_ConfidenceReadingInput as ConfidenceReadingInput, index_GOAL_RUNG_ANCHOR as GOAL_RUNG_ANCHOR, type index_ImpactAssumptionInput as ImpactAssumptionInput, index_RUNG_ANCHOR as RUNG_ANCHOR, type index_StrengthInput as StrengthInput, index_W0 as W0, index_confidence as confidence, index_derivedImpacts as derivedImpacts, index_isGoalRung as isGoalRung, index_readingStrength as readingStrength, index_risk as risk, index_round2 as round2, index_sign as sign, index_sourceQuality as sourceQuality };
113
- }
114
-
115
- export { type ConfidenceReadingInput as C, GOAL_RUNG_ANCHOR as G, type ImpactAssumptionInput as I, RUNG_ANCHOR as R, type StrengthInput as S, W0 as W, isGoalRung as a, risk as b, confidence as c, derivedImpacts as d, round2 as e, sign as f, index as i, readingStrength as r, sourceQuality as s };