@ai-sdlc/orchestrator 0.13.0 → 0.15.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.
@@ -0,0 +1,403 @@
1
+ /**
2
+ * RFC-0018 Phase 4 — MetricSnapshot resource read API (OQ-5 resolution).
3
+ *
4
+ * Implements:
5
+ * AC #1: MetricSnapshot schema (spec/schemas/metric-snapshot.v1.schema.json)
6
+ * AC #2: MetricSnapshot read API — `getLatestMetricSnapshot(journey, metricId)`
7
+ * AC #3: Stale-metric detection (default 30d; per-Soul configurable) with
8
+ * `Decision: journey-metric-stale` + warn-and-unknown Cκ behavior
9
+ * AC #4: Graduated Eρ₅ degradation (0-30/30-60/60-90/90+ thresholds + Decisions)
10
+ * AC #5: Per-Soul `accessibility.auditOverdueGracePolicy` modes
11
+ * AC #6: RFC-0022 multi-posture composition (strictest cadence applies)
12
+ *
13
+ * ### OQ-5 design summary
14
+ *
15
+ * Operators supply MetricSnapshot resources from their analytics pipeline
16
+ * (Mixpanel, Amplitude, Heap, internal-pipeline). The framework reads
17
+ * `completion-rate` and other journey-success values; it does NOT compute
18
+ * them from an analytics backend.
19
+ *
20
+ * Staleness: when `recordedAt` is older than `thresholdDays` (default 30),
21
+ * the scorer treats the metric as an unknown input (warn-and-unknown, NOT
22
+ * fail-closed). A `Decision: journey-metric-stale` is emitted for operator
23
+ * batch review.
24
+ *
25
+ * ### OQ-6 design summary
26
+ *
27
+ * When a journey's accessibility audit is overdue, Eρ₅ degrades on this
28
+ * graduated schedule (per-Soul policy `auditOverdueGracePolicy`):
29
+ *
30
+ * Policy `graduated` (default):
31
+ * 0–30d past cadence → warn only (`journey-audit-overdue-warn`)
32
+ * 30–60d → Eρ₅ -25% (`journey-audit-overdue-graduated`)
33
+ * 60–90d → Eρ₅ -50% (`journey-audit-overdue-graduated`)
34
+ * 90d+ → effective block (`journey-audit-overdue-blocking`)
35
+ *
36
+ * Policy `binary-30d`:
37
+ * 0–30d → no impact (SOC2/HIPAA early-warning model)
38
+ * 30d+ → immediate Eρ₅ fail
39
+ *
40
+ * Policy `hard-block`:
41
+ * Immediate Eρ₅ fail at cadence+0d (strictest, no grace)
42
+ *
43
+ * ### RFC-0022 multi-posture composition (AC #6)
44
+ *
45
+ * When the RFC-0022 compliance posture declares a stricter cadence than
46
+ * the soul-default, the strictest constraint wins. This mirrors the
47
+ * RFC-0030 OQ-13.3 UNION precedent for multi-posture composition.
48
+ *
49
+ * ### Decision-routing must-consume contract
50
+ *
51
+ * `getLatestMetricSnapshot` emits `decision: 'journey-metric-stale'` when a
52
+ * snapshot is present but older than `thresholdDays`. Callers MUST inspect
53
+ * `result.decision` and route it — typically to the RFC-0035 G0 batch-review
54
+ * queue — before using `result.snapshot.spec.value` for Cκ scoring. Silently
55
+ * dropping `result.decision` defeats the operator-visibility guarantee that
56
+ * makes warn-and-unknown safe (non-fail-closed). A typed must-consume pattern:
57
+ *
58
+ * ```ts
59
+ * const result = getLatestMetricSnapshot(journey, metricId, opts);
60
+ * if (result.decision) emitDecision(result.decision); // required
61
+ * if (result.freshness === 'fresh') useValue(result.snapshot!.spec.value);
62
+ * ```
63
+ *
64
+ * @see spec/rfcs/RFC-0018-in-soul-journey-pattern.md §10.1 OQ-5 + OQ-6
65
+ * @see spec/schemas/metric-snapshot.v1.schema.json
66
+ */
67
+ /**
68
+ * A single journey success-metric snapshot as supplied by the operator's
69
+ * analytics pipeline. Matches `spec/schemas/metric-snapshot.v1.schema.json`.
70
+ */
71
+ export interface MetricSnapshot {
72
+ /** Always 'ai-sdlc.io/v1alpha1'. */
73
+ readonly apiVersion: 'ai-sdlc.io/v1alpha1';
74
+ /** Always 'MetricSnapshot'. */
75
+ readonly kind: 'MetricSnapshot';
76
+ readonly metadata: {
77
+ /**
78
+ * Path-style journey URI.
79
+ * Soul-scoped: `<soul-id>/<journey-id>`
80
+ * Variant-scoped: `<soul-id>/<variant-id>/<journey-id>`
81
+ */
82
+ readonly journey: string;
83
+ /**
84
+ * Metric identifier (kebab-case). MUST match a `successMetrics[].id`
85
+ * on the parent journey declaration.
86
+ * Examples: 'completion-rate', 'median-time-to-first-task-done'
87
+ */
88
+ readonly metricId: string;
89
+ readonly labels?: Record<string, string>;
90
+ readonly annotations?: Record<string, string>;
91
+ };
92
+ readonly spec: {
93
+ /** Metric value. Unit convention shared between operator and framework. */
94
+ readonly value: number;
95
+ /**
96
+ * ISO 8601 timestamp when this metric was recorded / sampled.
97
+ * Used to compute staleness relative to `thresholdDays`.
98
+ */
99
+ readonly recordedAt: string;
100
+ /**
101
+ * Free-text analytics tool identifier.
102
+ * Examples: 'mixpanel', 'amplitude', 'heap', 'internal-pipeline'
103
+ */
104
+ readonly sourceTool: string;
105
+ /** Optional ISO 8601 window start (informational). */
106
+ readonly windowStart?: string;
107
+ /** Optional ISO 8601 window end (informational). */
108
+ readonly windowEnd?: string;
109
+ };
110
+ }
111
+ /**
112
+ * Staleness configuration for `journey.successMetrics.staleness`.
113
+ * Matches the corresponding block in `journey-config.v1.schema.json`.
114
+ */
115
+ export interface MetricStalenessConfig {
116
+ /** Days after last MetricSnapshot before the metric is stale. Default 30. */
117
+ readonly thresholdDays?: number;
118
+ }
119
+ /** Default staleness threshold per OQ-5 resolution (30 days). */
120
+ export declare const DEFAULT_STALENESS_THRESHOLD_DAYS = 30;
121
+ /**
122
+ * Possible states for a metric lookup result.
123
+ *
124
+ * - `'fresh'` — snapshot found AND within staleness threshold
125
+ * - `'stale'` — snapshot found BUT older than threshold (warn-and-unknown)
126
+ * - `'missing'` — no snapshot found for the journey/metricId pair
127
+ */
128
+ export type MetricFreshness = 'fresh' | 'stale' | 'missing';
129
+ /**
130
+ * Result returned by `getLatestMetricSnapshot`.
131
+ *
132
+ * - When `freshness === 'fresh'`: `snapshot` is populated; use `snapshot.spec.value`
133
+ * - When `freshness === 'stale'`: `snapshot` is populated but `decision` is emitted
134
+ * (warn-and-unknown — Cκ treats metric as unknown input, pipeline continues)
135
+ * - When `freshness === 'missing'`: no snapshot; Cκ scores as unknown input
136
+ */
137
+ export interface MetricSnapshotResult {
138
+ /** Journey path URI (e.g. 'spry-engage/onboarding'). */
139
+ readonly journey: string;
140
+ /** Metric ID (e.g. 'completion-rate'). */
141
+ readonly metricId: string;
142
+ /** Freshness state. */
143
+ readonly freshness: MetricFreshness;
144
+ /** The matched snapshot (populated when freshness is 'fresh' or 'stale'). */
145
+ readonly snapshot?: MetricSnapshot;
146
+ /**
147
+ * Decision emitted when `freshness === 'stale'`.
148
+ * Value: `'journey-metric-stale'`.
149
+ * Routing: RFC-0035 G0 (non-blocking batch review — warn-and-unknown, not fail-closed).
150
+ */
151
+ readonly decision?: 'journey-metric-stale';
152
+ /** Days since `recordedAt` (populated when snapshot is present). */
153
+ readonly ageInDays?: number;
154
+ /** Staleness threshold in days that was applied. */
155
+ readonly thresholdDays: number;
156
+ }
157
+ /**
158
+ * Options for `getLatestMetricSnapshot`.
159
+ */
160
+ export interface GetLatestMetricSnapshotOptions {
161
+ /**
162
+ * Collection of all MetricSnapshot records known to the framework.
163
+ * Callers are responsible for loading these from their persistence layer
164
+ * (filesystem, in-memory fixture, database) before calling.
165
+ */
166
+ readonly snapshots: readonly MetricSnapshot[];
167
+ /**
168
+ * Per-Soul staleness config (from the soul's `spec.journeyConfig.successMetrics.staleness`
169
+ * or the org-wide `.ai-sdlc/journey-config.yaml` default).
170
+ * When omitted, the default 30d threshold applies.
171
+ */
172
+ readonly stalenessConfig?: MetricStalenessConfig;
173
+ /**
174
+ * Reference "now" timestamp (ISO 8601). Defaults to `new Date().toISOString()`.
175
+ * Provided for deterministic testing.
176
+ */
177
+ readonly now?: string;
178
+ }
179
+ /**
180
+ * Retrieve the **latest** MetricSnapshot for the given journey + metricId pair
181
+ * and classify it as fresh, stale, or missing.
182
+ *
183
+ * Selection: when multiple snapshots match, the one with the most recent
184
+ * `spec.recordedAt` is returned (latest-wins). This covers the case where
185
+ * the operator's pipeline emits snapshots on a periodic schedule.
186
+ *
187
+ * Staleness: `ageInDays = (now - recordedAt) / (1000 * 60 * 60 * 24)`.
188
+ * When `ageInDays > thresholdDays`, the result carries:
189
+ * - `freshness: 'stale'`
190
+ * - `decision: 'journey-metric-stale'`
191
+ *
192
+ * This Decision routes through RFC-0035 G0 (non-blocking batch review):
193
+ * the Cκ scorer treats a stale metric as an unknown input (same behavior as
194
+ * `freshness: 'missing'`), NOT as a hard fail. The pipeline continues.
195
+ *
196
+ * @param journey Path-style journey URI (e.g. 'spry-engage/onboarding')
197
+ * @param metricId Metric identifier (e.g. 'completion-rate')
198
+ * @param options Snapshot collection + optional per-Soul config
199
+ */
200
+ export declare function getLatestMetricSnapshot(journey: string, metricId: string, options: GetLatestMetricSnapshotOptions): MetricSnapshotResult;
201
+ /**
202
+ * Per-Soul policy for Eρ₅ degradation when the accessibility audit is overdue.
203
+ * Matches `journey-config.v1.schema.json` `accessibility.auditOverdueGracePolicy`.
204
+ *
205
+ * - `'graduated'` — default; progressive reduction matching Vanta/Drata/Secureframe pattern
206
+ * - `'binary-30d'` — SOC2/HIPAA strict: no impact within 30d, then fail-closed
207
+ * - `'hard-block'` — immediate fail at cadence+0d (no grace)
208
+ */
209
+ export type AuditOverdueGracePolicy = 'graduated' | 'binary-30d' | 'hard-block';
210
+ /**
211
+ * Eρ₅ impact tiers for graduated degradation.
212
+ *
213
+ * - `'warn'` — Eρ₅ unchanged; Decision emitted for operator visibility
214
+ * - `'reduced-25'` — Eρ₅ multiplied by 0.75 (−25%)
215
+ * - `'reduced-50'` — Eρ₅ multiplied by 0.50 (−50%)
216
+ * - `'effective-block'`— Eρ₅ set to 0 (admission blocked)
217
+ */
218
+ export type Erho5Impact = 'warn' | 'reduced-25' | 'reduced-50' | 'effective-block';
219
+ /** Eρ₅ multiplier for each impact tier. */
220
+ export declare const ERHO5_MULTIPLIERS: Record<Erho5Impact, number>;
221
+ /**
222
+ * Graduated thresholds configuration (days past audit cadence).
223
+ * Matches `accessibility.graduatedThresholds` in `journey-config.v1.schema.json`.
224
+ */
225
+ export interface GraduatedThresholds {
226
+ /** Days past cadence at which 'warn' Decision fires. Default 0. */
227
+ readonly warnAt?: number;
228
+ /** Days past cadence at which −25% reduction fires. Default 30. */
229
+ readonly reduced25At?: number;
230
+ /** Days past cadence at which −50% reduction fires. Default 60. */
231
+ readonly reduced50At?: number;
232
+ /** Days past cadence at which effective-block fires. Default 90. */
233
+ readonly effectiveBlockAt?: number;
234
+ }
235
+ /** Default graduated thresholds per OQ-6 resolution. */
236
+ export declare const DEFAULT_GRADUATED_THRESHOLDS: Required<GraduatedThresholds>;
237
+ /**
238
+ * Decision kinds emitted for accessibility audit overdue events.
239
+ * Routes through RFC-0035 G0 non-blocking pipeline contract.
240
+ */
241
+ export type AuditOverdueDecision = 'journey-audit-overdue-warn' | 'journey-audit-overdue-graduated' | 'journey-audit-overdue-blocking';
242
+ /**
243
+ * Result of the Eρ₅ degradation calculation for an overdue accessibility audit.
244
+ */
245
+ export interface AuditOverdueResult {
246
+ /** Soul identifier for which the result was computed. */
247
+ readonly soulId: string;
248
+ /** Journey identifier. */
249
+ readonly journeyId: string;
250
+ /** Days the audit is past cadence (0 means exactly at cadence). */
251
+ readonly daysOverdue: number;
252
+ /** The grace policy that was applied. */
253
+ readonly policy: AuditOverdueGracePolicy;
254
+ /** Eρ₅ impact tier. */
255
+ readonly impact: Erho5Impact;
256
+ /**
257
+ * Eρ₅ multiplier to apply to the base Eρ₅ score.
258
+ * 1.0 = no impact; 0.75 = -25%; 0.50 = -50%; 0.0 = effective block.
259
+ */
260
+ readonly erho5Multiplier: number;
261
+ /**
262
+ * Decision to emit for this result.
263
+ * `null` only when daysOverdue < 0 (audit not yet due). At daysOverdue >= 0
264
+ * (cadence+0d — no grace) a Decision is emitted per the policy.
265
+ */
266
+ readonly decision: AuditOverdueDecision | null;
267
+ }
268
+ /**
269
+ * Options for `computeAuditOverdueErho5`.
270
+ */
271
+ export interface ComputeAuditOverdueOptions {
272
+ /** Soul identifier for event attribution. */
273
+ readonly soulId: string;
274
+ /** Journey identifier for event attribution. */
275
+ readonly journeyId: string;
276
+ /**
277
+ * Days the journey's audit is past its declared cadence.
278
+ * 0 = exactly at cadence; positive = overdue; negative = not yet overdue.
279
+ */
280
+ readonly daysOverdue: number;
281
+ /**
282
+ * Per-Soul grace policy.
283
+ * Defaults to `'graduated'`.
284
+ */
285
+ readonly policy?: AuditOverdueGracePolicy;
286
+ /**
287
+ * Per-org graduated threshold configuration.
288
+ * Only used when `policy === 'graduated'`.
289
+ * Defaults to `DEFAULT_GRADUATED_THRESHOLDS`.
290
+ */
291
+ readonly graduatedThresholds?: GraduatedThresholds;
292
+ }
293
+ /**
294
+ * Compute the Eρ₅ impact and Decision for an overdue accessibility audit.
295
+ *
296
+ * Implements RFC-0018 §10.1 OQ-6 graduated Eρ₅ degradation:
297
+ *
298
+ * Policy `graduated` (default):
299
+ * - 0 ≤ daysOverdue < 30 → `warn` (multiplier 1.0)
300
+ * - 30 ≤ daysOverdue < 60 → `reduced-25` (multiplier 0.75)
301
+ * - 60 ≤ daysOverdue < 90 → `reduced-50` (multiplier 0.50)
302
+ * - daysOverdue ≥ 90 → `effective-block` (multiplier 0.0)
303
+ *
304
+ * Policy `binary-30d` (SOC2/HIPAA strict):
305
+ * - daysOverdue < 30 → no impact (multiplier 1.0, no Decision)
306
+ * - daysOverdue ≥ 30 → `effective-block` (multiplier 0.0)
307
+ *
308
+ * Policy `hard-block` (HIPAA/PCI-DSS ultra-strict):
309
+ * - daysOverdue < 0 → no impact (multiplier 1.0, no Decision; not yet due)
310
+ * - daysOverdue ≥ 0 → `effective-block` (multiplier 0.0) — cadence+0d, no grace
311
+ *
312
+ * When `daysOverdue < 0`, returns multiplier 1.0 and `decision: null`
313
+ * regardless of policy (audit is not yet due). At daysOverdue ≥ 0 each policy
314
+ * emits its Decision (no implicit grace day).
315
+ */
316
+ export declare function computeAuditOverdueErho5(options: ComputeAuditOverdueOptions): AuditOverdueResult;
317
+ /**
318
+ * Audit cadence values from the journey declaration (RFC-0018 §5.2).
319
+ * Ordered strictest → least strict for UNION selection.
320
+ */
321
+ export type AuditCadence = 'continuous' | 'release-gated' | 'quarterly' | 'annually';
322
+ /**
323
+ * Numeric strictness order for cadence values.
324
+ * Higher = stricter (shorter audit interval).
325
+ * Used by `resolveStrictestCadence` to pick the UNION result.
326
+ */
327
+ export declare const AUDIT_CADENCE_STRICTNESS: Record<AuditCadence, number>;
328
+ /**
329
+ * Options for `resolveStrictestCadence`.
330
+ */
331
+ export interface ResolveStrictestCadenceOptions {
332
+ /**
333
+ * Journey-level cadence declared in `accessibility.auditCadence`.
334
+ */
335
+ readonly journeyCadence: AuditCadence;
336
+ /**
337
+ * Cadences required by the active RFC-0022 compliance posture(s).
338
+ * An empty array means no posture constraint — journey cadence is used as-is.
339
+ * When multiple postures are active, all are included here.
340
+ */
341
+ readonly postureCadences: readonly AuditCadence[];
342
+ }
343
+ /**
344
+ * Resolve the effective audit cadence by applying the strictest constraint
345
+ * from the journey declaration and all active RFC-0022 compliance postures.
346
+ *
347
+ * This implements RFC-0018 AC #6 + RFC-0030 OQ-13.3 UNION precedent:
348
+ * the strictest constraint among all active postures and the journey's own
349
+ * declaration wins.
350
+ *
351
+ * @example
352
+ * // Journey declares 'annually', but SOC2 posture requires 'quarterly'
353
+ * resolveStrictestCadence({
354
+ * journeyCadence: 'annually',
355
+ * postureCadences: ['quarterly'],
356
+ * })
357
+ * // → 'quarterly' (posture wins — stricter)
358
+ *
359
+ * @example
360
+ * // Journey declares 'continuous' (strictest possible)
361
+ * resolveStrictestCadence({
362
+ * journeyCadence: 'continuous',
363
+ * postureCadences: ['quarterly', 'annually'],
364
+ * })
365
+ * // → 'continuous' (journey wins — already strictest)
366
+ */
367
+ export declare function resolveStrictestCadence(options: ResolveStrictestCadenceOptions): AuditCadence;
368
+ /**
369
+ * Options for `resolveStrictestGracePolicy`.
370
+ */
371
+ export interface ResolveStrictestGracePolicyOptions {
372
+ /**
373
+ * Per-Soul grace policy from `accessibility.auditOverdueGracePolicy`.
374
+ * Defaults to `'graduated'`.
375
+ */
376
+ readonly soulPolicy?: AuditOverdueGracePolicy;
377
+ /**
378
+ * Grace policies required by the active RFC-0022 compliance postures.
379
+ * An empty array means no posture constraint — soul policy is used as-is.
380
+ * SOC2/HIPAA postures typically impose 'binary-30d' or 'hard-block'.
381
+ */
382
+ readonly posturesPolicies: readonly AuditOverdueGracePolicy[];
383
+ }
384
+ /**
385
+ * Policy strictness order (higher = stricter).
386
+ */
387
+ export declare const GRACE_POLICY_STRICTNESS: Record<AuditOverdueGracePolicy, number>;
388
+ /**
389
+ * Resolve the effective grace policy by picking the STRICTEST among the
390
+ * soul-level policy and all active RFC-0022 compliance posture policies.
391
+ *
392
+ * RFC-0022 + RFC-0018 AC #6: multi-posture UNION → strictest applies.
393
+ *
394
+ * @example
395
+ * // Soul defaults to 'graduated'; SOC2 posture requires 'binary-30d'
396
+ * resolveStrictestGracePolicy({
397
+ * soulPolicy: 'graduated',
398
+ * posturesPolicies: ['binary-30d'],
399
+ * })
400
+ * // → 'binary-30d' (posture wins — stricter)
401
+ */
402
+ export declare function resolveStrictestGracePolicy(options: ResolveStrictestGracePolicyOptions): AuditOverdueGracePolicy;
403
+ //# sourceMappingURL=metric-snapshot.d.ts.map