@validation-os/core 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,36 +4,24 @@ import {
4
4
  StaleVersionError,
5
5
  isNotFoundError,
6
6
  isStaleVersionError
7
- } from "./chunk-W7VU5JOU.js";
7
+ } from "./chunk-43RQBE2E.js";
8
8
  import {
9
- GOAL_RUNG_VALUES,
9
+ COMPLETENESS_SLOTS,
10
+ MARKET_RUNG_VALUES,
10
11
  REGISTERS,
11
12
  TESTING_RUNGS,
13
+ assumptionComplete,
14
+ assumptionCompleteness,
12
15
  confidence,
13
16
  derivation_exports,
14
17
  derivedImpacts,
18
+ missingCompletenessSlots,
15
19
  readingStrength,
16
20
  risk,
17
21
  sourceQuality
18
- } from "./chunk-4Q2B2QT3.js";
22
+ } from "./chunk-22WJF5EZ.js";
19
23
  import "./chunk-PZ5AY32C.js";
20
24
 
21
- // src/presence.ts
22
- var ASSUMPTION_PRESENCE_FIELDS = [
23
- "5 Whys",
24
- "Metric for truth",
25
- "Scoring justification"
26
- ];
27
- function isPresent(value) {
28
- return typeof value === "string" && value.trim().length > 0;
29
- }
30
- function missingPresenceFields(record) {
31
- return ASSUMPTION_PRESENCE_FIELDS.filter((field) => !isPresent(record[field]));
32
- }
33
- function assumptionPresenceComplete(record) {
34
- return missingPresenceFields(record).length === 0;
35
- }
36
-
37
25
  // src/reading-input.ts
38
26
  function str(v) {
39
27
  return typeof v === "string" && v !== "" ? v : null;
@@ -52,8 +40,7 @@ function toReadingInput(r) {
52
40
  credibility: num(r.Credibility),
53
41
  date: str(r.Date),
54
42
  magnitudeBand: r.magnitudeBand,
55
- experimentId: str(r.experimentId),
56
- goalId: str(r.goalId)
43
+ experimentId: str(r.experimentId)
57
44
  };
58
45
  }
59
46
 
@@ -92,23 +79,32 @@ function recomputeDerived(input) {
92
79
  for (const a of assumptions) {
93
80
  const c = confidenceById.get(a.id) ?? 0;
94
81
  const di = impactById.get(a.id) ?? 0;
95
- out.set(a.id, { confidence: c, derivedImpact: di, risk: risk(di, c) });
82
+ out.set(a.id, {
83
+ confidence: c,
84
+ derivedImpact: di,
85
+ risk: risk(di, c),
86
+ // Completeness is a *structural* readiness meter: it reads a.Impact as
87
+ // present/absent, not its value, so a moot assumption (whose Impact the
88
+ // Derived Impact pass zeroes) still counts its scored Impact slot.
89
+ completeness: assumptionCompleteness(a)
90
+ });
96
91
  }
97
92
  return out;
98
93
  }
99
94
  export {
100
- ASSUMPTION_PRESENCE_FIELDS,
101
- GOAL_RUNG_VALUES,
95
+ COMPLETENESS_SLOTS as ASSUMPTION_PRESENCE_SLOTS,
96
+ MARKET_RUNG_VALUES,
102
97
  NotFoundError,
103
98
  REGISTERS,
104
99
  RELATIONS,
105
100
  StaleVersionError,
106
101
  TESTING_RUNGS,
107
- assumptionPresenceComplete,
102
+ assumptionCompleteness,
103
+ assumptionComplete as assumptionPresenceComplete,
108
104
  derivation_exports as derivation,
109
105
  isNotFoundError,
110
106
  isStaleVersionError,
111
- missingPresenceFields,
107
+ missingCompletenessSlots as missingPresenceSlots,
112
108
  recomputeDerived,
113
109
  sourceQuality as recomputeSourceQuality,
114
110
  readingStrength as recomputeStrength,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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
+ {"version":3,"sources":["../src/reading-input.ts","../src/recompute.ts"],"sourcesContent":["/**\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 };\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 {\n assumptionCompleteness,\n confidence,\n derivedImpacts,\n risk,\n} 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, {\n confidence: c,\n derivedImpact: di,\n risk: risk(di, c),\n // Completeness is a *structural* readiness meter: it reads a.Impact as\n // present/absent, not its value, so a moot assumption (whose Impact the\n // Derived Impact pass zeroes) still counts its scored Impact slot.\n completeness: assumptionCompleteness(a),\n });\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":";;;;;;;;;;;;;;;;;;;;;;;;;AAUA,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,EAClC;AACF;;;ACLA,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;AAAA,MACZ,YAAY;AAAA,MACZ,eAAe;AAAA,MACf,MAAM,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA,MAIhB,cAAc,uBAAuB,CAAC;AAAA,IACxC,CAAC;AAAA,EACH;AACA,SAAO;AACT;","names":[]}
@@ -1,4 +1,4 @@
1
- import { C as Collection, c as AnyRecord, R as Relation, n as RecordRef } from './types-B3eI7ASx.js';
1
+ import { C as Collection, c as AnyRecord, R as Relation, m as RecordRef } from './types-BAyl0w2E.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-hW8Hxf-n.js';
2
- import { C as Collection, c as AnyRecord, R as Relation, n as RecordRef } from './types-B3eI7ASx.js';
1
+ import { D as DataProvider } from './provider-DGr5Xq5U.js';
2
+ import { C as Collection, c as AnyRecord, R as Relation, m as RecordRef } from './types-BAyl0w2E.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-W7VU5JOU.js";
5
+ } from "./chunk-43RQBE2E.js";
6
6
  import "./chunk-PZ5AY32C.js";
7
7
 
8
8
  // src/testing.ts
@@ -5,11 +5,15 @@
5
5
  * single source of truth). Field *meaning* lives there; this file is the
6
6
  * checkable TypeScript shape the packages import.
7
7
  */
8
- /** The six registers plus the `people` reference collection. */
9
- declare const REGISTERS: readonly ["assumptions", "experiments", "readings", "goals", "decisions", "glossary"];
8
+ /**
9
+ * The five registers. `goals` was unified into `experiments` and the `people`
10
+ * reference collection retired (OPS-1305): Owner / Agreed by now reference a
11
+ * dashboard user (the auth-sourced team list), not a register row.
12
+ */
13
+ declare const REGISTERS: readonly ["assumptions", "experiments", "readings", "decisions", "glossary"];
10
14
  type Register = (typeof REGISTERS)[number];
11
- /** `people` is a reference collection, not one of the six registers. */
12
- type Collection = Register | "people";
15
+ /** There is no reference collection any more a collection is a register. */
16
+ type Collection = Register;
13
17
  /** Every stored record carries an id, a version, and timestamps. */
14
18
  interface BaseRecord {
15
19
  id: string;
@@ -19,26 +23,37 @@ interface BaseRecord {
19
23
  updatedAt: string;
20
24
  }
21
25
  type AssumptionStatus = "Draft" | "Live" | "Invalidated";
22
- type ExperimentStatus = "Running" | "Closed";
23
- type GoalStatus = "Draft" | "Active" | "Closed";
26
+ /**
27
+ * The unified evidence-plan lifecycle (OPS-1305). `Draft` is the new gate a
28
+ * commit clears (Draft→Running); conclude+verdict stays the Running→Closed
29
+ * gate. Absorbs what the retired Goal record's status used to carry.
30
+ */
31
+ type ExperimentStatus = "Draft" | "Running" | "Closed";
24
32
  type DecisionStatus = "Active" | "Provisional" | "Superseded" | "Reversed";
25
33
  type GlossaryStatus = "Active" | "Provisional" | "Superseded";
26
34
  type Result = "Validated" | "Invalidated" | "Inconclusive";
27
35
  type MagnitudeBand = "Low" | "Typical" | "High";
28
36
  type Feasibility = "High" | "Medium" | "Low";
29
- /** The 8-rung activity-and-strength ladder (order = strength, weakest first). */
37
+ /**
38
+ * The 8-rung activity-and-strength ladder (order = strength, weakest first).
39
+ * Two categories: Testing (recruited-sample instruments) and Market (open-world
40
+ * targets — the category formerly called "Goals", renamed with the unification,
41
+ * OPS-1305). The anchors and physics are unchanged by the rename.
42
+ */
30
43
  declare const TESTING_RUNGS: readonly ["Opinion", "Pitch-deck reaction", "Anecdotal", "Desk research", "Survey at scale", "Prototype usage"];
31
- declare const GOAL_RUNG_VALUES: readonly ["Signed intent", "Paying users"];
44
+ declare const MARKET_RUNG_VALUES: readonly ["Signed intent", "Paying users"];
32
45
  type TestingRung = (typeof TESTING_RUNGS)[number];
33
- type GoalRung = (typeof GOAL_RUNG_VALUES)[number];
34
- type Rung = TestingRung | GoalRung;
46
+ type MarketRung = (typeof MARKET_RUNG_VALUES)[number];
47
+ type Rung = TestingRung | MarketRung;
35
48
  /** Representativeness and Credibility are each picked from these. */
36
49
  type SourceQualityPick = 1.0 | 0.7 | 0.5;
37
- /** The four derived numbers stored on an assumption (never hand-typed). */
50
+ /** The derived numbers stored on an assumption (never hand-typed). */
38
51
  interface AssumptionDerived {
39
52
  derivedImpact: number;
40
53
  risk: number;
41
54
  confidence: number;
55
+ /** Structural readiness meter, 0–100 (see `derivation/completeness.ts`). */
56
+ completeness: number;
42
57
  }
43
58
  interface AssumptionRecord extends BaseRecord {
44
59
  Title: string;
@@ -48,18 +63,16 @@ interface AssumptionRecord extends BaseRecord {
48
63
  /** The hand-scored seed (0–100), the only hand-scored number here. */
49
64
  Impact: number | null;
50
65
  Status: AssumptionStatus;
66
+ /** Reference to a dashboard user (auth team list), not a `people` row. */
51
67
  Owner: string[];
52
- Gaps: string[];
53
68
  moot: boolean;
54
69
  /**
55
- * Presence-gap fields long-form prose promoted from body sections to
56
- * first-class fields (OPS-1273). Their PRESENCE is a structural, blocking
57
- * check (see `presence.ts`): non-empty is required to move Status to `Live`
58
- * (the presence half of the Draft→Live gaps invariant, OPS-1251). May be
59
- * empty ("") while `Draft`.
70
+ * Kept, narrowed to the Impact-seed rationale (OPS-1305): why the seed
71
+ * `Impact` was scored as it was, incl. dated moot lines. The `5 Whys`,
72
+ * `Metric for truth`, and `Gaps` fields are gone the why-trace lives in the
73
+ * `Depends on / Enables` chain and the audit check-types are transient grill
74
+ * stages, not stored tags. Readiness is the derived `completeness`.
60
75
  */
61
- "5 Whys": string;
62
- "Metric for truth": string;
63
76
  "Scoring justification": string;
64
77
  dependsOnIds: string[];
65
78
  enablesIds: string[];
@@ -69,17 +82,25 @@ interface AssumptionRecord extends BaseRecord {
69
82
  }
70
83
  interface ReadingRecord extends BaseRecord {
71
84
  Title: string;
72
- /** First-class link to the artifact the independence-dedupe key. */
85
+ /** The independence/dedupe key the generator (person / dataset / cohort). */
73
86
  Source: string | null;
87
+ /**
88
+ * Provenance links (recording, dashboard, CRM row, user id) — 0..N, drives no
89
+ * math and never keys dedupe (OPS-1305). Split out from `Source` so the
90
+ * dedupe key stays narrow.
91
+ */
92
+ contextLinks: string[];
74
93
  assumptionId: string;
94
+ /** The originating plan, or null for a bare/found reading (no Goal origin). */
75
95
  experimentId: string | null;
76
- goalId: string | null;
77
96
  Rung: Rung;
78
97
  Representativeness: SourceQualityPick;
79
98
  Credibility: SourceQualityPick;
80
- /** For Goal-rung readings: the magnitude band from the absolute outcome. */
99
+ /** For Market-rung readings: the magnitude band from the absolute outcome. */
81
100
  magnitudeBand?: MagnitudeBand;
82
101
  Result: Result;
102
+ /** The rationale for the rung / representativeness / credibility picks. */
103
+ "Grading justification": string;
83
104
  Date: string | null;
84
105
  Owner: string[];
85
106
  derived: {
@@ -106,6 +127,11 @@ interface ExperimentRecord extends BaseRecord {
106
127
  Feasibility: Feasibility | null;
107
128
  Status: ExperimentStatus;
108
129
  closureReason: "Completed" | "Early-stop" | "Kill" | null;
130
+ /** Optional deadline a committed plan carries (folded in from the Goal). */
131
+ Deadline: string | null;
132
+ /** Terminal closure outcome, null until Closed (folded in from the Goal). */
133
+ Outcome: "Achieved" | "Missed" | "Dropped" | null;
134
+ /** Reference to a dashboard user, not a `people` row. */
109
135
  Owner: string[];
110
136
  Date: string | null;
111
137
  /** The embedded pre-registered bar lines; drives progress-to-conclusion. */
@@ -113,24 +139,32 @@ interface ExperimentRecord extends BaseRecord {
113
139
  /** Convenience projection: the assumptions the bar lines test. */
114
140
  barLineAssumptionIds: string[];
115
141
  }
116
- interface GoalRecord extends BaseRecord {
117
- Title: string;
118
- Status: GoalStatus;
119
- Outcome: "Achieved" | "Missed" | "Dropped" | null;
120
- Owner: string[];
121
- Date: string | null;
122
- basedOnIds: string[];
123
- }
124
142
  interface DecisionRecord extends BaseRecord {
125
143
  Title: string;
126
144
  Status: DecisionStatus;
145
+ /** The one-line statement of what was decided (promoted from the body). */
146
+ Statement: string;
147
+ /** Why the Unanimity score was scored as it was (promoted from the body). */
148
+ "Unanimity justification": string;
149
+ /** References to dashboard users (auth team list), not `people` rows. */
127
150
  Owner: string[];
151
+ "Agreed by": string[];
128
152
  basedOnIds: string[];
129
153
  resolvesIds: string[];
130
154
  }
155
+ /** One structured "don't say" entry on a glossary term. */
156
+ interface GlossaryAvoid {
157
+ audience: string;
158
+ phrase: string;
159
+ fix: string;
160
+ }
131
161
  interface GlossaryRecord extends BaseRecord {
132
162
  Title: string;
133
163
  Status: GlossaryStatus;
164
+ /** All properties, no body (OPS-1305): the terminology check parses these. */
165
+ Definition: string;
166
+ Avoid: GlossaryAvoid[];
167
+ "How it differs": string;
134
168
  }
135
169
  /** A record of any register — the DataProvider's currency. */
136
170
  type AnyRecord = BaseRecord & Record<string, unknown>;
@@ -140,6 +174,6 @@ interface RecordRef {
140
174
  id: string;
141
175
  }
142
176
  /** The relations the dashboard can set (each writes both ends). */
143
- type Relation = "assumption-reading" | "assumption-depends-on" | "assumption-contradicts" | "reading-experiment" | "reading-goal" | "decision-based-on" | "decision-resolves" | "goal-based-on";
177
+ type Relation = "assumption-reading" | "assumption-depends-on" | "assumption-contradicts" | "reading-experiment" | "decision-based-on" | "decision-resolves";
144
178
 
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 };
179
+ 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, type GlossaryAvoid as G, MARKET_RUNG_VALUES 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 MagnitudeBand as j, type MarketRung 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@validation-os/core",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
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,279 +0,0 @@
1
- import {
2
- __export
3
- } from "./chunk-PZ5AY32C.js";
4
-
5
- // src/derivation/index.ts
6
- var derivation_exports = {};
7
- __export(derivation_exports, {
8
- GOAL_RUNG_ANCHOR: () => GOAL_RUNG_ANCHOR,
9
- RUNG_ANCHOR: () => RUNG_ANCHOR,
10
- W0: () => W0,
11
- confidence: () => confidence,
12
- confidenceAttribution: () => confidenceAttribution,
13
- confidenceTrajectory: () => confidenceTrajectory,
14
- derivedImpacts: () => derivedImpacts,
15
- experimentProgress: () => experimentProgress,
16
- isConcluded: () => isConcluded,
17
- isGoalRung: () => isGoalRung,
18
- readingStrength: () => readingStrength,
19
- risk: () => risk,
20
- round2: () => round2,
21
- scoreAndDedupe: () => scoreAndDedupe,
22
- sign: () => sign,
23
- sourceQuality: () => sourceQuality
24
- });
25
-
26
- // src/derivation/round.ts
27
- function round2(n) {
28
- return Number(n.toFixed(2));
29
- }
30
-
31
- // src/types.ts
32
- var REGISTERS = [
33
- "assumptions",
34
- "experiments",
35
- "readings",
36
- "goals",
37
- "decisions",
38
- "glossary"
39
- ];
40
- var TESTING_RUNGS = [
41
- "Opinion",
42
- "Pitch-deck reaction",
43
- "Anecdotal",
44
- "Desk research",
45
- "Survey at scale",
46
- "Prototype usage"
47
- ];
48
- var GOAL_RUNG_VALUES = ["Signed intent", "Paying users"];
49
-
50
- // src/derivation/rung.ts
51
- var RUNG_ANCHOR = {
52
- Opinion: 3,
53
- "Pitch-deck reaction": 6,
54
- Anecdotal: 10,
55
- "Desk research": 15,
56
- "Survey at scale": 25,
57
- "Prototype usage": 30
58
- };
59
- var GOAL_RUNG_ANCHOR = {
60
- "Signed intent": { Low: 55, Typical: 68, High: 80 },
61
- "Paying users": { Low: 75, Typical: 88, High: 99 }
62
- };
63
- var GOAL_RUNG_SET = new Set(GOAL_RUNG_VALUES);
64
- function isGoalRung(rung) {
65
- return GOAL_RUNG_SET.has(rung);
66
- }
67
-
68
- // src/derivation/strength.ts
69
- function sign(result) {
70
- if (result === "Validated") return 1;
71
- if (result === "Invalidated") return -1;
72
- return 0;
73
- }
74
- function isConcluded(result) {
75
- return result === "Validated" || result === "Invalidated";
76
- }
77
- function readingStrength(input) {
78
- const s = sign(input.result);
79
- if (s === 0) return 0;
80
- if (isGoalRung(input.rung)) {
81
- const band = input.magnitudeBand ?? "Typical";
82
- return GOAL_RUNG_ANCHOR[input.rung][band] * s;
83
- }
84
- return (RUNG_ANCHOR[input.rung] ?? 0) * s;
85
- }
86
-
87
- // src/derivation/source-quality.ts
88
- function sourceQuality(representativeness, credibility) {
89
- return round2(representativeness * credibility);
90
- }
91
-
92
- // src/derivation/confidence.ts
93
- var W0 = 100;
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);
100
- const best = /* @__PURE__ */ new Map();
101
- for (const x of scored) {
102
- if (isGoalRung(x.input.rung)) {
103
- best.set(x.input.id, x);
104
- continue;
105
- }
106
- const key = x.input.source || x.input.id;
107
- const cur = best.get(key);
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 || "");
109
- if (better) best.set(key, x);
110
- }
111
- return [...best.values()];
112
- }
113
- function confidence(readings) {
114
- const winners = scoreAndDedupe(readings);
115
- let num = 0;
116
- let den = W0;
117
- for (const x of winners) {
118
- num += x.weight * x.strength;
119
- den += x.weight;
120
- }
121
- return den > 0 ? round2(num / den) : 0;
122
- }
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
-
215
- // src/derivation/impact.ts
216
- function derivedImpacts(assumptions, basedOnCounts = {}) {
217
- const byId = new Map(assumptions.map((a) => [a.id, a]));
218
- const dependents = /* @__PURE__ */ new Map();
219
- for (const a of assumptions) dependents.set(a.id, []);
220
- for (const a of assumptions) {
221
- for (const dep of a.dependsOnIds) dependents.get(dep)?.push(a.id);
222
- }
223
- const memo = /* @__PURE__ */ new Map();
224
- const compute = (id, seen) => {
225
- const cached = memo.get(id);
226
- if (cached !== void 0) return cached;
227
- if (seen.has(id)) return 0;
228
- seen.add(id);
229
- const a = byId.get(id);
230
- if (!a) return 0;
231
- if (a.moot) {
232
- memo.set(id, 0);
233
- return 0;
234
- }
235
- const seed = a.impact ?? 0;
236
- let S = 0;
237
- for (const depId of dependents.get(id) ?? []) {
238
- const d = byId.get(depId);
239
- if (d && !d.moot) S += compute(depId, seen);
240
- }
241
- S += 100 * (basedOnCounts[id] ?? 0);
242
- const value = seed + (100 - seed) * (S / (S + 100));
243
- const rounded = round2(value);
244
- memo.set(id, rounded);
245
- return rounded;
246
- };
247
- const out = /* @__PURE__ */ new Map();
248
- for (const a of assumptions) out.set(a.id, compute(a.id, /* @__PURE__ */ new Set()));
249
- return out;
250
- }
251
-
252
- // src/derivation/risk.ts
253
- function risk(derivedImpact, confidence2) {
254
- return round2(derivedImpact * (1 - Math.max(0, confidence2) / 100));
255
- }
256
-
257
- export {
258
- REGISTERS,
259
- TESTING_RUNGS,
260
- GOAL_RUNG_VALUES,
261
- round2,
262
- RUNG_ANCHOR,
263
- GOAL_RUNG_ANCHOR,
264
- isGoalRung,
265
- sign,
266
- isConcluded,
267
- readingStrength,
268
- sourceQuality,
269
- W0,
270
- scoreAndDedupe,
271
- confidence,
272
- confidenceAttribution,
273
- experimentProgress,
274
- confidenceTrajectory,
275
- derivedImpacts,
276
- risk,
277
- derivation_exports
278
- };
279
- //# sourceMappingURL=chunk-4Q2B2QT3.js.map