@ai-sdlc/orchestrator 0.10.0 → 0.14.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.
Files changed (168) hide show
  1. package/dist/adapters.d.ts +42 -3
  2. package/dist/adapters.js +133 -3
  3. package/dist/admission-composite.d.ts +112 -1
  4. package/dist/admission-composite.js +85 -4
  5. package/dist/admission-enrichment.d.ts +12 -3
  6. package/dist/admission-enrichment.js +64 -13
  7. package/dist/admission-score.d.ts +30 -0
  8. package/dist/admission-score.js +4 -1
  9. package/dist/analysis/file-walker.js +5 -0
  10. package/dist/backlog-adapter.d.ts +45 -0
  11. package/dist/backlog-adapter.js +145 -1
  12. package/dist/calibration.d.ts +81 -0
  13. package/dist/calibration.js +76 -0
  14. package/dist/cli/commands/git-remote.js +1 -1
  15. package/dist/cli/commands/init-features.d.ts +301 -2
  16. package/dist/cli/commands/init-features.js +647 -7
  17. package/dist/cli/commands/init-templates.d.ts +198 -1
  18. package/dist/cli/commands/init-templates.js +943 -1
  19. package/dist/cli/commands/init.d.ts +45 -0
  20. package/dist/cli/commands/init.js +147 -5
  21. package/dist/cli/commands/run.js +9 -1
  22. package/dist/cli/index.d.ts +11 -0
  23. package/dist/cli/index.js +42 -8
  24. package/dist/compliance/composer.d.ts +79 -0
  25. package/dist/compliance/composer.js +258 -0
  26. package/dist/compliance/errors.d.ts +64 -0
  27. package/dist/compliance/errors.js +85 -0
  28. package/dist/compliance/loader.d.ts +52 -0
  29. package/dist/compliance/loader.js +124 -0
  30. package/dist/compliance/types.d.ts +184 -0
  31. package/dist/compliance/types.js +41 -0
  32. package/dist/compliance-clearance.d.ts +269 -0
  33. package/dist/compliance-clearance.js +269 -0
  34. package/dist/config.js +17 -0
  35. package/dist/cost-tracker.d.ts +22 -0
  36. package/dist/cost-tracker.js +41 -0
  37. package/dist/cycle-utils.js +14 -1
  38. package/dist/database/adapters/external.js +5 -1
  39. package/dist/embedding/adapters/openai-text-embedding-3-small.d.ts +71 -0
  40. package/dist/embedding/adapters/openai-text-embedding-3-small.js +190 -0
  41. package/dist/embedding/consumers/tessellation-drift.d.ts +74 -0
  42. package/dist/embedding/consumers/tessellation-drift.js +76 -0
  43. package/dist/embedding/cross-provider.d.ts +78 -0
  44. package/dist/embedding/cross-provider.js +75 -0
  45. package/dist/embedding/deprecation.d.ts +151 -0
  46. package/dist/embedding/deprecation.js +229 -0
  47. package/dist/embedding/errors.d.ts +90 -0
  48. package/dist/embedding/errors.js +150 -0
  49. package/dist/embedding/index.d.ts +29 -0
  50. package/dist/embedding/index.js +24 -0
  51. package/dist/embedding/pipeline-load.d.ts +146 -0
  52. package/dist/embedding/pipeline-load.js +178 -0
  53. package/dist/embedding/registry.d.ts +45 -0
  54. package/dist/embedding/registry.js +61 -0
  55. package/dist/embedding/stale-vector.d.ts +110 -0
  56. package/dist/embedding/stale-vector.js +92 -0
  57. package/dist/embedding/storage/index.d.ts +51 -0
  58. package/dist/embedding/storage/index.js +43 -0
  59. package/dist/embedding/storage/jsonl-backend.d.ts +150 -0
  60. package/dist/embedding/storage/jsonl-backend.js +332 -0
  61. package/dist/embedding/storage/types.d.ts +135 -0
  62. package/dist/embedding/storage/types.js +13 -0
  63. package/dist/embedding/types.d.ts +180 -0
  64. package/dist/embedding/types.js +10 -0
  65. package/dist/execute.d.ts +29 -2
  66. package/dist/execute.js +200 -38
  67. package/dist/fix-review.js +1 -1
  68. package/dist/index.d.ts +14 -2
  69. package/dist/index.js +19 -2
  70. package/dist/journey/inheritance-validator.d.ts +396 -0
  71. package/dist/journey/inheritance-validator.js +370 -0
  72. package/dist/journey/metric-snapshot.d.ts +403 -0
  73. package/dist/journey/metric-snapshot.js +370 -0
  74. package/dist/journey/state-id-drift-rule.d.ts +137 -0
  75. package/dist/journey/state-id-drift-rule.js +245 -0
  76. package/dist/journey-sa2-router.d.ts +395 -0
  77. package/dist/journey-sa2-router.js +308 -0
  78. package/dist/otel-exporter.js +0 -3
  79. package/dist/runners/claude-code.js +3 -3
  80. package/dist/runners/review-agent.js +6 -2
  81. package/dist/runners/runner-registry.d.ts +36 -0
  82. package/dist/runners/runner-registry.js +90 -0
  83. package/dist/runtime/attestations.d.ts +173 -13
  84. package/dist/runtime/attestations.js +265 -43
  85. package/dist/runtime/index.d.ts +1 -1
  86. package/dist/runtime/index.js +1 -1
  87. package/dist/sa-scoring/exemplar-bank.js +3 -1
  88. package/dist/sa-scoring/layer3-llm.js +6 -1
  89. package/dist/sa-scoring/revision-proposal-config.d.ts +178 -0
  90. package/dist/sa-scoring/revision-proposal-config.js +198 -0
  91. package/dist/sa-scoring/revision-proposal.d.ts +285 -0
  92. package/dist/sa-scoring/revision-proposal.js +417 -0
  93. package/dist/shared.d.ts +28 -0
  94. package/dist/shared.js +37 -0
  95. package/dist/signal-ingestion/adapters/community-thread.d.ts +43 -0
  96. package/dist/signal-ingestion/adapters/community-thread.js +55 -0
  97. package/dist/signal-ingestion/adapters/in-app-feedback.d.ts +67 -0
  98. package/dist/signal-ingestion/adapters/in-app-feedback.js +51 -0
  99. package/dist/signal-ingestion/adapters/manual.d.ts +78 -0
  100. package/dist/signal-ingestion/adapters/manual.js +112 -0
  101. package/dist/signal-ingestion/adapters/support-ticket.d.ts +47 -0
  102. package/dist/signal-ingestion/adapters/support-ticket.js +51 -0
  103. package/dist/signal-ingestion/classifier.d.ts +205 -0
  104. package/dist/signal-ingestion/classifier.js +494 -0
  105. package/dist/signal-ingestion/clustering-types.d.ts +36 -0
  106. package/dist/signal-ingestion/clustering-types.js +14 -0
  107. package/dist/signal-ingestion/clustering.d.ts +200 -0
  108. package/dist/signal-ingestion/clustering.js +413 -0
  109. package/dist/signal-ingestion/config.d.ts +351 -0
  110. package/dist/signal-ingestion/config.js +587 -0
  111. package/dist/signal-ingestion/d1.d.ts +252 -0
  112. package/dist/signal-ingestion/d1.js +235 -0
  113. package/dist/signal-ingestion/errors.d.ts +73 -0
  114. package/dist/signal-ingestion/errors.js +108 -0
  115. package/dist/signal-ingestion/governance-events.d.ts +181 -0
  116. package/dist/signal-ingestion/governance-events.js +189 -0
  117. package/dist/signal-ingestion/index.d.ts +35 -0
  118. package/dist/signal-ingestion/index.js +53 -0
  119. package/dist/signal-ingestion/manual-share-metric.d.ts +93 -0
  120. package/dist/signal-ingestion/manual-share-metric.js +106 -0
  121. package/dist/signal-ingestion/registry.d.ts +40 -0
  122. package/dist/signal-ingestion/registry.js +137 -0
  123. package/dist/signal-ingestion/residency.d.ts +227 -0
  124. package/dist/signal-ingestion/residency.js +238 -0
  125. package/dist/signal-ingestion/significance.d.ts +554 -0
  126. package/dist/signal-ingestion/significance.js +555 -0
  127. package/dist/signal-ingestion/types.d.ts +191 -0
  128. package/dist/signal-ingestion/types.js +8 -0
  129. package/dist/substrate/drift-composition.d.ts +270 -0
  130. package/dist/substrate/drift-composition.js +306 -0
  131. package/dist/substrate/drift-tui-surface.d.ts +61 -0
  132. package/dist/substrate/drift-tui-surface.js +102 -0
  133. package/dist/substrate/identity-class.d.ts +176 -0
  134. package/dist/substrate/identity-class.js +201 -0
  135. package/dist/tessellation/cross-soul-provenance-rule.d.ts +133 -0
  136. package/dist/tessellation/cross-soul-provenance-rule.js +171 -0
  137. package/dist/tessellation/inter-soul-embedding-distance-rule.d.ts +61 -0
  138. package/dist/tessellation/inter-soul-embedding-distance-rule.js +67 -0
  139. package/dist/tessellation/rule-registry.d.ts +269 -0
  140. package/dist/tessellation/rule-registry.js +92 -0
  141. package/dist/tessellation/soul-slug-ast-scan-rule.d.ts +90 -0
  142. package/dist/tessellation/soul-slug-ast-scan-rule.js +158 -0
  143. package/dist/tessellation-admission.d.ts +162 -0
  144. package/dist/tessellation-admission.js +146 -0
  145. package/dist/tessellation-drift.d.ts +246 -0
  146. package/dist/tessellation-drift.js +250 -0
  147. package/dist/validate-config.js +13 -0
  148. package/dist/validate-issue.js +2 -2
  149. package/dist/variant/cardinality-activation.d.ts +126 -0
  150. package/dist/variant/cardinality-activation.js +101 -0
  151. package/dist/variant/deprecation-lifecycle.d.ts +184 -0
  152. package/dist/variant/deprecation-lifecycle.js +208 -0
  153. package/dist/variant/drift-extension.d.ts +136 -0
  154. package/dist/variant/drift-extension.js +164 -0
  155. package/dist/variant/engineering-review.d.ts +185 -0
  156. package/dist/variant/engineering-review.js +142 -0
  157. package/dist/variant/index.d.ts +32 -0
  158. package/dist/variant/index.js +32 -0
  159. package/dist/variant/inheritance-validator.d.ts +165 -0
  160. package/dist/variant/inheritance-validator.js +139 -0
  161. package/dist/variant/internal-adopter/index.d.ts +11 -0
  162. package/dist/variant/internal-adopter/index.js +10 -0
  163. package/dist/variant/internal-adopter/products.d.ts +156 -0
  164. package/dist/variant/internal-adopter/products.js +366 -0
  165. package/dist/variant-admission.d.ts +316 -0
  166. package/dist/variant-admission.js +247 -0
  167. package/dist/webhook-manager.js +7 -1
  168. package/package.json +15 -12
@@ -0,0 +1,252 @@
1
+ /**
2
+ * RFC-0030 Phase 5 — D1 formula reformulation + RFC-0008 PPA integration.
3
+ *
4
+ * Reformulates the existing PPA D1 (Demand Pressure) input to consume
5
+ * cluster-level demand from the signal-ingestion pipeline per RFC-0030 §10:
6
+ *
7
+ * ```
8
+ * D1(cluster) = Σ over signals in cluster:
9
+ * signal.baseWeight # 1.0 Tier 1; 0.3 Tier 2 above threshold; 0 below
10
+ * × signal.tierMultiplier # configurable per deployment (§6.1)
11
+ * × signal.icpResonance # configurable per deployment (§6.2)
12
+ * × signal.recencyDecay # exp(-age_days × ln(2) / half_life_days)
13
+ * × cluster.saResonance # filter per §9 (applied via the SA bucket multiplier)
14
+ *
15
+ * D1 is then normalized across all active clusters to [0, 1] and fed into the
16
+ * existing PPA D formula (PPA v1.1 §3.1).
17
+ * ```
18
+ *
19
+ * **Non-replacement contract (AC #2 + AC #4)**: human-authored backlog items
20
+ * continue to feed D1 alongside signal-pipeline-derived demand. The pipeline
21
+ * adds a parallel input path; the existing path is preserved. When the
22
+ * pipeline is disabled (`config.enabled === false`, the default), this module
23
+ * contributes zero signal-pipeline demand — the composed D1 reduces to the
24
+ * backlog-item input unchanged. When both inputs are active, weights from
25
+ * `config.d1Composition` blend them (default 50/50).
26
+ *
27
+ * **RFC-0008 PPA Triad integration (AC #3)**: the resulting per-item composed
28
+ * D1 score is consumed by the `enrichDemandSignalFromClusters()` helper which
29
+ * overlays it onto a `PriorityInput.demandSignal` field — the same field the
30
+ * admission composite (`admission-composite.ts`) reads via `mapIssueToPriorityInput`
31
+ * for the Sα₁ × Dπ_adjusted × Eρ₅ admission gate per RFC-0008 §A.6.
32
+ *
33
+ * **Cluster-to-item routing**: the pipeline produces cluster-level demand;
34
+ * admission scoring is per-item (per backlog task / GitHub issue). The
35
+ * composition treats the pipeline-derived signal as a *deployment-level
36
+ * demand floor* that lifts the demand signal for items whose subject
37
+ * matches an active cluster theme (cluster-to-item matching is left to a
38
+ * caller-supplied lookup in v1; v2 will wire to RFC-0024 capture matching).
39
+ *
40
+ * @module signal-ingestion/d1
41
+ */
42
+ import type { SignalIngestionConfig } from './config.js';
43
+ import type { QuarantineStore, SignificanceAssessedCluster } from './significance.js';
44
+ /**
45
+ * Options for `computeClusterD1()` / `aggregateD1FromClusters()` per
46
+ * AISDLC-433. The optional `quarantineStore` lets the D1 path consult the
47
+ * z-score detector's quarantine state and EXCLUDE quarantined signals from
48
+ * cluster scoring (AC #6). Back-compat: when omitted, no signals are treated
49
+ * as quarantined — pre-AISDLC-433 callers continue to work unchanged.
50
+ */
51
+ export interface ComputeClusterD1Options {
52
+ quarantineStore?: QuarantineStore;
53
+ /** Clock override for quarantine expiry checks. Defaults to `new Date()`. */
54
+ asOf?: Date;
55
+ }
56
+ /**
57
+ * Per-cluster D1 score breakdown — produced by `computeClusterD1()`.
58
+ *
59
+ * `rawScore` is the §10 formula output BEFORE normalisation (sum across
60
+ * cluster members × the SA bucket multiplier). `normalizedScore` is filled
61
+ * in by `aggregateD1FromClusters()` after the full population is known.
62
+ */
63
+ export interface ClusterD1Score {
64
+ clusterId: string;
65
+ /** Sum of per-signal weights × cluster SA multiplier; pre-normalisation. */
66
+ rawScore: number;
67
+ /**
68
+ * Filled by `aggregateD1FromClusters()` — `rawScore / max(rawScore across
69
+ * eligible clusters)`. Range [0, 1]. `0` when the cluster is ineligible
70
+ * (significance gate failed or SA bucket excludes it).
71
+ */
72
+ normalizedScore: number;
73
+ /** Whether the cluster contributes to D1 at all. Mirrors `eligibleForD1`. */
74
+ eligible: boolean;
75
+ /** Cluster size (member count) — useful for downstream auditing. */
76
+ signalCount: number;
77
+ }
78
+ /**
79
+ * Result of aggregating per-cluster D1 scores into a deployment-level
80
+ * pipeline-derived demand input.
81
+ */
82
+ export interface AggregatedD1Result {
83
+ /** Per-cluster scores, with normalised values populated. */
84
+ clusters: ClusterD1Score[];
85
+ /**
86
+ * Maximum raw score across eligible clusters (the normalisation denominator).
87
+ * `0` when no clusters are eligible.
88
+ */
89
+ maxRawScore: number;
90
+ /**
91
+ * Population-level summary — the mean normalised score across eligible
92
+ * clusters. Range [0, 1]. Useful as a deployment-level "ambient pipeline
93
+ * demand" indicator when no per-item cluster match exists.
94
+ */
95
+ meanNormalizedScore: number;
96
+ }
97
+ /**
98
+ * Inputs to `composeD1Inputs()` per RFC-0030 §10 non-replacement contract.
99
+ */
100
+ export interface ComposeD1InputsArgs {
101
+ /**
102
+ * Pipeline-derived per-item demand score (post-`enrichDemandSignalFromClusters`
103
+ * matching). Range expected [0, 1]. `undefined` when no cluster match exists
104
+ * — composition then falls back to `backlogItemD1` alone.
105
+ */
106
+ signalPipelineD1?: number;
107
+ /**
108
+ * Human-authored backlog-item demand score from the existing PPA path
109
+ * (`mapIssueToPriorityInput().demandSignal`). Range [0, 1]. `undefined`
110
+ * when the backlog has no demand for the item (rare; treated as 0).
111
+ */
112
+ backlogItemD1?: number;
113
+ /**
114
+ * Signal-ingestion config — `d1Composition` weights + `enabled` flag drive
115
+ * the blend. Defaults to `DEFAULT_SIGNAL_INGESTION_CONFIG` (pipeline disabled).
116
+ */
117
+ config?: SignalIngestionConfig;
118
+ }
119
+ /** Result of composing the two D1 input streams. */
120
+ export interface ComposedD1Result {
121
+ /** The final blended D1 input. Range [0, 1]. */
122
+ composedScore: number;
123
+ /** Normalised weight actually applied to the pipeline input. */
124
+ signalPipelineWeightApplied: number;
125
+ /** Normalised weight actually applied to the backlog input. */
126
+ backlogItemWeightApplied: number;
127
+ /**
128
+ * Audit trail — what each branch contributed before weighting:
129
+ * - `signalPipelineContribution` = `signalPipelineD1 × signalPipelineWeightApplied`
130
+ * - `backlogItemContribution` = `backlogItemD1 × backlogItemWeightApplied`
131
+ */
132
+ signalPipelineContribution: number;
133
+ backlogItemContribution: number;
134
+ /**
135
+ * Backward-compat indicator — `true` when the pipeline was disabled OR no
136
+ * pipeline-derived signal was provided, so the result is the backlog input
137
+ * unchanged. AC #4 audit hook.
138
+ */
139
+ pipelineBypass: boolean;
140
+ }
141
+ /**
142
+ * Apply RFC-0030 §10 D1 formula to a single significance-assessed cluster.
143
+ *
144
+ * `rawScore` is the sum of per-member weights × the cluster's SA bucket
145
+ * multiplier (§10 line 5 — the SA filter applied at cluster level).
146
+ *
147
+ * When the cluster is ineligible (`assessment.eligibleForD1 === false` —
148
+ * Tier 2 threshold failed OR SA bucket is `out-of-scope` / `pending`), the
149
+ * raw score is forced to 0 per §8 / §9 contract.
150
+ */
151
+ export declare function computeClusterD1(assessment: SignificanceAssessedCluster, config?: SignalIngestionConfig, options?: ComputeClusterD1Options): ClusterD1Score;
152
+ /**
153
+ * Aggregate per-cluster D1 scores into a deployment-level result with
154
+ * normalised scores per §10 final line ("D1 is normalized across all active
155
+ * clusters to [0, 1]").
156
+ *
157
+ * Normalisation divides each `rawScore` by the population max — keeps the
158
+ * eligible cluster with the strongest demand at 1.0 and orders the rest
159
+ * proportionally. When no clusters are eligible, all `normalizedScore` are 0
160
+ * and `meanNormalizedScore` is 0 — D1 cleanly degrades to "no pipeline-derived
161
+ * demand" without surfacing NaNs.
162
+ *
163
+ * `meanNormalizedScore` is the mean across **eligible** clusters only —
164
+ * ineligible clusters (rawScore = 0) don't pull the population mean down.
165
+ */
166
+ export declare function aggregateD1FromClusters(assessments: SignificanceAssessedCluster[], config?: SignalIngestionConfig, options?: ComputeClusterD1Options): AggregatedD1Result;
167
+ /**
168
+ * Blend signal-pipeline-derived demand with the existing backlog-item demand
169
+ * input per RFC-0030 §10 non-replacement contract.
170
+ *
171
+ * **Backward compatibility (AC #4)**: when `config.enabled === false` the
172
+ * function bypasses the pipeline entirely and returns `composedScore =
173
+ * backlogItemD1` (or 0 when absent). `pipelineBypass = true` so callers /
174
+ * tests can audit the bypass branch.
175
+ *
176
+ * **Weight balancing (AC #5)**: when both inputs are present and the pipeline
177
+ * is enabled, weights from `config.d1Composition` are normalised to sum to 1
178
+ * and applied as a linear blend. Negative weights are clamped to 0 by the
179
+ * config validator; if both weights are 0 (operator misconfiguration), the
180
+ * function falls back to the unweighted average to avoid emitting NaN.
181
+ *
182
+ * **Single-input cases**: when only one input is present, the other is
183
+ * treated as 0; weights still apply so an operator who configured
184
+ * `{signalPipelineWeight: 0.8, backlogItemWeight: 0.2}` and supplies only
185
+ * `backlogItemD1` gets `0.2 × backlogItemD1` (which is documented behaviour —
186
+ * the missing input is genuinely missing demand). If you want "use this one
187
+ * unweighted when the other is absent" semantics, omit the absent field AND
188
+ * set the other input's weight to 1.0.
189
+ */
190
+ export declare function composeD1Inputs(args: ComposeD1InputsArgs): ComposedD1Result;
191
+ /**
192
+ * Pluggable matcher: resolve a backlog work item to the most relevant cluster
193
+ * D1 score (if any). Callers supply this so the cluster-to-item match policy
194
+ * can be swapped per deployment without rewriting the core composition.
195
+ *
196
+ * v1 contract: the matcher receives a free-form `itemKey` (typically the
197
+ * task title or a normalised slug); returns the best-matching `ClusterD1Score`
198
+ * or `undefined` when no cluster is a meaningful match. The matcher is
199
+ * intentionally NOT given write access to the score — it only chooses which
200
+ * cluster the item maps onto. RFC-0024 capture matching wires this in v2.
201
+ */
202
+ export interface ClusterMatcher {
203
+ (itemKey: string, aggregated: AggregatedD1Result): ClusterD1Score | undefined;
204
+ }
205
+ /**
206
+ * RFC-0008 PPA Triad integration helper — applies the composed D1 to a
207
+ * `PriorityInput` so the admission composite consumes pipeline-derived
208
+ * demand through the same `demandSignal` field it already reads.
209
+ *
210
+ * The returned object is a NEW `PriorityInput` (input is not mutated). When
211
+ * the pipeline is disabled OR no cluster match exists, the returned object
212
+ * carries the input's original `demandSignal` unchanged — backward-compatible
213
+ * (AC #4).
214
+ *
215
+ * Composition algorithm:
216
+ * 1. Resolve `signalPipelineD1` via `matcher(itemKey, aggregated)` →
217
+ * `match?.normalizedScore` (range [0, 1]); when matcher returns
218
+ * `undefined`, no pipeline contribution.
219
+ * 2. Read `backlogItemD1` from `priorityInput.demandSignal` (already the
220
+ * output of `mapIssueToPriorityInput()` per existing path).
221
+ * 3. Compose via `composeD1Inputs()` with the configured weights.
222
+ * 4. Override `demandSignal` on a shallow clone of the input.
223
+ *
224
+ * The composed result and its audit fields are also returned so callers can
225
+ * surface the pipeline contribution in pillar-breakdown logs.
226
+ */
227
+ export interface EnrichDemandSignalArgs<T extends {
228
+ demandSignal?: number;
229
+ }> {
230
+ /** The PriorityInput-shaped object to enrich. NOT mutated. */
231
+ priorityInput: T;
232
+ /** A short key the matcher uses to find a cluster (title, slug, or item ID). */
233
+ itemKey: string;
234
+ /** The aggregation result from `aggregateD1FromClusters()`. */
235
+ aggregated: AggregatedD1Result;
236
+ /** Cluster-to-item matcher; see `ClusterMatcher` docstring. */
237
+ matcher: ClusterMatcher;
238
+ /** Signal ingestion config. Defaults to `DEFAULT_SIGNAL_INGESTION_CONFIG`. */
239
+ config?: SignalIngestionConfig;
240
+ }
241
+ export interface EnrichDemandSignalResult<T> {
242
+ /** The enriched PriorityInput (shallow clone with `demandSignal` overlaid). */
243
+ enriched: T;
244
+ /** Full composition result for audit / breakdown surfacing. */
245
+ composition: ComposedD1Result;
246
+ /** Which cluster matched, when any. `undefined` when matcher returned none. */
247
+ matchedCluster: ClusterD1Score | undefined;
248
+ }
249
+ export declare function enrichDemandSignalFromClusters<T extends {
250
+ demandSignal?: number;
251
+ }>(args: EnrichDemandSignalArgs<T>): EnrichDemandSignalResult<T>;
252
+ //# sourceMappingURL=d1.d.ts.map
@@ -0,0 +1,235 @@
1
+ /**
2
+ * RFC-0030 Phase 5 — D1 formula reformulation + RFC-0008 PPA integration.
3
+ *
4
+ * Reformulates the existing PPA D1 (Demand Pressure) input to consume
5
+ * cluster-level demand from the signal-ingestion pipeline per RFC-0030 §10:
6
+ *
7
+ * ```
8
+ * D1(cluster) = Σ over signals in cluster:
9
+ * signal.baseWeight # 1.0 Tier 1; 0.3 Tier 2 above threshold; 0 below
10
+ * × signal.tierMultiplier # configurable per deployment (§6.1)
11
+ * × signal.icpResonance # configurable per deployment (§6.2)
12
+ * × signal.recencyDecay # exp(-age_days × ln(2) / half_life_days)
13
+ * × cluster.saResonance # filter per §9 (applied via the SA bucket multiplier)
14
+ *
15
+ * D1 is then normalized across all active clusters to [0, 1] and fed into the
16
+ * existing PPA D formula (PPA v1.1 §3.1).
17
+ * ```
18
+ *
19
+ * **Non-replacement contract (AC #2 + AC #4)**: human-authored backlog items
20
+ * continue to feed D1 alongside signal-pipeline-derived demand. The pipeline
21
+ * adds a parallel input path; the existing path is preserved. When the
22
+ * pipeline is disabled (`config.enabled === false`, the default), this module
23
+ * contributes zero signal-pipeline demand — the composed D1 reduces to the
24
+ * backlog-item input unchanged. When both inputs are active, weights from
25
+ * `config.d1Composition` blend them (default 50/50).
26
+ *
27
+ * **RFC-0008 PPA Triad integration (AC #3)**: the resulting per-item composed
28
+ * D1 score is consumed by the `enrichDemandSignalFromClusters()` helper which
29
+ * overlays it onto a `PriorityInput.demandSignal` field — the same field the
30
+ * admission composite (`admission-composite.ts`) reads via `mapIssueToPriorityInput`
31
+ * for the Sα₁ × Dπ_adjusted × Eρ₅ admission gate per RFC-0008 §A.6.
32
+ *
33
+ * **Cluster-to-item routing**: the pipeline produces cluster-level demand;
34
+ * admission scoring is per-item (per backlog task / GitHub issue). The
35
+ * composition treats the pipeline-derived signal as a *deployment-level
36
+ * demand floor* that lifts the demand signal for items whose subject
37
+ * matches an active cluster theme (cluster-to-item matching is left to a
38
+ * caller-supplied lookup in v1; v2 will wire to RFC-0024 capture matching).
39
+ *
40
+ * @module signal-ingestion/d1
41
+ */
42
+ import { DEFAULT_SIGNAL_INGESTION_CONFIG } from './config.js';
43
+ import { isSignalQuarantined } from './significance.js';
44
+ // ── §10 formula — per cluster ───────────────────────────────────────────────
45
+ /**
46
+ * Compute the per-signal weight from a cluster member's `ClusteredSignalInput`
47
+ * + config-derived multipliers. Mirrors `classifier.computeSignalWeight()` but
48
+ * operates on the Phase-3 cluster-member shape (which dropped the
49
+ * `tierMultiplier` / `icpResonanceWeight` convenience fields the classifier
50
+ * carried for Phase-4 use).
51
+ *
52
+ * Order: `baseWeight × tierMultiplier × icpResonanceWeight × recencyDecay`
53
+ * exactly per §10 lines 1-4 of the formula. The cluster-level
54
+ * `saResonance` factor (§10 line 5) is applied OUTSIDE this function, in
55
+ * `computeClusterD1()`, since it's a cluster-wide property.
56
+ */
57
+ function computeClusterMemberWeight(member, config) {
58
+ // Tier 1 sources base weight = 1.0; Tier 2 = 0.3 (matches classifier convention
59
+ // — Phase 4 significance gate independently zeros out below-threshold clusters
60
+ // via SA_WEIGHT_MULTIPLIERS, so we don't double-discount here).
61
+ const adapterTier = member.adapterTier ?? 1;
62
+ const baseWeight = adapterTier === 1 ? 1.0 : 0.3;
63
+ const tierMultiplier = config.tierMultipliers[member.customerTier];
64
+ const icpWeight = config.icpResonanceWeights[member.icpResonance];
65
+ return baseWeight * tierMultiplier * icpWeight * member.recencyDecay;
66
+ }
67
+ /**
68
+ * Apply RFC-0030 §10 D1 formula to a single significance-assessed cluster.
69
+ *
70
+ * `rawScore` is the sum of per-member weights × the cluster's SA bucket
71
+ * multiplier (§10 line 5 — the SA filter applied at cluster level).
72
+ *
73
+ * When the cluster is ineligible (`assessment.eligibleForD1 === false` —
74
+ * Tier 2 threshold failed OR SA bucket is `out-of-scope` / `pending`), the
75
+ * raw score is forced to 0 per §8 / §9 contract.
76
+ */
77
+ export function computeClusterD1(assessment, config = DEFAULT_SIGNAL_INGESTION_CONFIG, options = {}) {
78
+ if (!assessment.eligibleForD1) {
79
+ return {
80
+ clusterId: assessment.cluster.clusterId,
81
+ rawScore: 0,
82
+ normalizedScore: 0,
83
+ eligible: false,
84
+ signalCount: assessment.cluster.signalCount,
85
+ };
86
+ }
87
+ // AC #6: signals currently in quarantine (flooding-flagged with active
88
+ // expiresAt) are excluded from the D1(cluster) formula. The exclusion is
89
+ // member-level — a cluster mixing quarantined + clean signals still
90
+ // contributes the clean members' weight; clusters whose entire membership
91
+ // is quarantined effectively score 0 even though they're "eligible" per
92
+ // significance + SA.
93
+ const asOf = options.asOf ?? new Date();
94
+ const memberSum = assessment.cluster.members.reduce((acc, member) => {
95
+ if (isSignalQuarantined(member.signal, options.quarantineStore, asOf))
96
+ return acc;
97
+ return acc + computeClusterMemberWeight(member, config);
98
+ }, 0);
99
+ // `assessment.d1WeightMultiplier` is the precomputed combined multiplier
100
+ // from Phase 4: `significanceMultiplier × SA_WEIGHT_MULTIPLIERS[bucket]`.
101
+ // For any eligible cluster, significanceMultiplier === 1.0 (qualified) and
102
+ // the bucket is one of `full` / `discounted` / `low-sa-review`, so this
103
+ // value is simply the §10 line-5 SA factor (range 0.3..1.0). Multiplying
104
+ // through it preserves the spec algebra `members × cluster.saResonance`
105
+ // while letting the Phase 4 single source of truth drive the math.
106
+ const rawScore = memberSum * assessment.d1WeightMultiplier;
107
+ return {
108
+ clusterId: assessment.cluster.clusterId,
109
+ rawScore,
110
+ normalizedScore: 0, // filled by aggregateD1FromClusters
111
+ eligible: true,
112
+ signalCount: assessment.cluster.signalCount,
113
+ };
114
+ }
115
+ /**
116
+ * Aggregate per-cluster D1 scores into a deployment-level result with
117
+ * normalised scores per §10 final line ("D1 is normalized across all active
118
+ * clusters to [0, 1]").
119
+ *
120
+ * Normalisation divides each `rawScore` by the population max — keeps the
121
+ * eligible cluster with the strongest demand at 1.0 and orders the rest
122
+ * proportionally. When no clusters are eligible, all `normalizedScore` are 0
123
+ * and `meanNormalizedScore` is 0 — D1 cleanly degrades to "no pipeline-derived
124
+ * demand" without surfacing NaNs.
125
+ *
126
+ * `meanNormalizedScore` is the mean across **eligible** clusters only —
127
+ * ineligible clusters (rawScore = 0) don't pull the population mean down.
128
+ */
129
+ export function aggregateD1FromClusters(assessments, config = DEFAULT_SIGNAL_INGESTION_CONFIG, options = {}) {
130
+ const perCluster = assessments.map((a) => computeClusterD1(a, config, options));
131
+ const eligibleScores = perCluster.filter((c) => c.eligible);
132
+ const maxRawScore = eligibleScores.reduce((max, c) => Math.max(max, c.rawScore), 0);
133
+ // Populate normalisedScore in-place. When maxRawScore is 0 (no eligible
134
+ // clusters OR all eligible clusters had zero weight — possible if every
135
+ // member has icpResonance × tierMultiplier × recencyDecay = 0), normalised
136
+ // scores stay at 0 by short-circuiting.
137
+ for (const c of perCluster) {
138
+ c.normalizedScore = maxRawScore > 0 && c.eligible ? c.rawScore / maxRawScore : 0;
139
+ }
140
+ const meanNormalizedScore = eligibleScores.length > 0
141
+ ? eligibleScores.reduce((sum, c) => sum + c.normalizedScore, 0) / eligibleScores.length
142
+ : 0;
143
+ return {
144
+ clusters: perCluster,
145
+ maxRawScore,
146
+ meanNormalizedScore,
147
+ };
148
+ }
149
+ // ── Non-replacement composition (§10.4 spirit; AC #2, #4, #5) ───────────────
150
+ /**
151
+ * Blend signal-pipeline-derived demand with the existing backlog-item demand
152
+ * input per RFC-0030 §10 non-replacement contract.
153
+ *
154
+ * **Backward compatibility (AC #4)**: when `config.enabled === false` the
155
+ * function bypasses the pipeline entirely and returns `composedScore =
156
+ * backlogItemD1` (or 0 when absent). `pipelineBypass = true` so callers /
157
+ * tests can audit the bypass branch.
158
+ *
159
+ * **Weight balancing (AC #5)**: when both inputs are present and the pipeline
160
+ * is enabled, weights from `config.d1Composition` are normalised to sum to 1
161
+ * and applied as a linear blend. Negative weights are clamped to 0 by the
162
+ * config validator; if both weights are 0 (operator misconfiguration), the
163
+ * function falls back to the unweighted average to avoid emitting NaN.
164
+ *
165
+ * **Single-input cases**: when only one input is present, the other is
166
+ * treated as 0; weights still apply so an operator who configured
167
+ * `{signalPipelineWeight: 0.8, backlogItemWeight: 0.2}` and supplies only
168
+ * `backlogItemD1` gets `0.2 × backlogItemD1` (which is documented behaviour —
169
+ * the missing input is genuinely missing demand). If you want "use this one
170
+ * unweighted when the other is absent" semantics, omit the absent field AND
171
+ * set the other input's weight to 1.0.
172
+ */
173
+ export function composeD1Inputs(args) {
174
+ const config = args.config ?? DEFAULT_SIGNAL_INGESTION_CONFIG;
175
+ const backlog = args.backlogItemD1 ?? 0;
176
+ const pipeline = args.signalPipelineD1 ?? 0;
177
+ // Backward-compat fast path: pipeline disabled OR no pipeline input.
178
+ if (!config.enabled || args.signalPipelineD1 === undefined) {
179
+ return {
180
+ composedScore: clamp01(backlog),
181
+ signalPipelineWeightApplied: 0,
182
+ backlogItemWeightApplied: 1,
183
+ signalPipelineContribution: 0,
184
+ backlogItemContribution: clamp01(backlog),
185
+ pipelineBypass: true,
186
+ };
187
+ }
188
+ const { signalPipelineWeightApplied, backlogItemWeightApplied } = normaliseWeights(config.d1Composition);
189
+ const pipelineContribution = pipeline * signalPipelineWeightApplied;
190
+ const backlogContribution = backlog * backlogItemWeightApplied;
191
+ const composedScore = clamp01(pipelineContribution + backlogContribution);
192
+ return {
193
+ composedScore,
194
+ signalPipelineWeightApplied,
195
+ backlogItemWeightApplied,
196
+ signalPipelineContribution: pipelineContribution,
197
+ backlogItemContribution: backlogContribution,
198
+ pipelineBypass: false,
199
+ };
200
+ }
201
+ /**
202
+ * Normalise the configured weights so they sum to 1. When both are 0
203
+ * (degenerate operator config), fall back to a 50/50 split so the function
204
+ * never returns NaN downstream.
205
+ */
206
+ function normaliseWeights(weights) {
207
+ const sum = weights.signalPipelineWeight + weights.backlogItemWeight;
208
+ if (sum <= 0) {
209
+ return { signalPipelineWeightApplied: 0.5, backlogItemWeightApplied: 0.5 };
210
+ }
211
+ return {
212
+ signalPipelineWeightApplied: weights.signalPipelineWeight / sum,
213
+ backlogItemWeightApplied: weights.backlogItemWeight / sum,
214
+ };
215
+ }
216
+ function clamp01(n) {
217
+ if (!Number.isFinite(n) || n <= 0)
218
+ return 0;
219
+ if (n >= 1)
220
+ return 1;
221
+ return n;
222
+ }
223
+ export function enrichDemandSignalFromClusters(args) {
224
+ const config = args.config ?? DEFAULT_SIGNAL_INGESTION_CONFIG;
225
+ const matchedCluster = args.matcher(args.itemKey, args.aggregated);
226
+ const signalPipelineD1 = matchedCluster?.normalizedScore;
227
+ const backlogItemD1 = args.priorityInput.demandSignal;
228
+ const composition = composeD1Inputs({ signalPipelineD1, backlogItemD1, config });
229
+ const enriched = {
230
+ ...args.priorityInput,
231
+ demandSignal: composition.composedScore,
232
+ };
233
+ return { enriched, composition, matchedCluster };
234
+ }
235
+ //# sourceMappingURL=d1.js.map
@@ -0,0 +1,73 @@
1
+ import type { SignalSourceName } from './types.js';
2
+ export declare class UnknownSignalSource extends Error {
3
+ readonly source: string;
4
+ constructor(source: string);
5
+ }
6
+ export declare class SignalSourceUnavailable extends Error {
7
+ readonly source: SignalSourceName;
8
+ constructor(source: SignalSourceName, message?: string);
9
+ }
10
+ export declare class AdapterCredentialInvalid extends Error {
11
+ readonly source: SignalSourceName;
12
+ constructor(source: SignalSourceName, message?: string);
13
+ }
14
+ /**
15
+ * RFC-0030 OQ-13.1 v0.3 re-walkthrough: distinct from `AdapterCredentialInvalid`.
16
+ *
17
+ * Raised when an adapter probes its environment for the required credential
18
+ * env var (e.g. `SIGNAL_ZENDESK_PAT`) and the var is missing or empty.
19
+ * Downstream: emit `Decision: adapter-credential-not-configured` and surface
20
+ * an operator SETUP task ("create + set this env var"). Pipeline continues
21
+ * with the remaining valid adapters (AC #4).
22
+ */
23
+ export declare class AdapterCredentialNotConfigured extends Error {
24
+ readonly source: SignalSourceName;
25
+ readonly envVarName: string;
26
+ constructor(source: SignalSourceName, envVarName: string, message?: string);
27
+ }
28
+ /**
29
+ * RFC-0030 OQ-13.1 v0.3 re-walkthrough: distinct from `AdapterCredentialInvalid`.
30
+ *
31
+ * Raised when an adapter has the credential env var present BUT the auth call
32
+ * against the upstream service is rejected (401 / 403). Downstream: emit
33
+ * `Decision: adapter-credential-rejected` and surface an operator ROTATION
34
+ * task ("rotate / re-generate this credential"). Pipeline continues with the
35
+ * remaining valid adapters (AC #4).
36
+ */
37
+ export declare class AdapterCredentialRejected extends Error {
38
+ readonly source: SignalSourceName;
39
+ constructor(source: SignalSourceName, message?: string);
40
+ }
41
+ /**
42
+ * RFC-0030 OQ-13.1 v0.3 re-walkthrough: refuse OAuth-required adapters at
43
+ * registration. Phase 1 ships ENV-VAR-ONLY adapters; OAuth-required adapters
44
+ * (full Salesforce / HubSpot integrations, Zendesk-with-OAuth-scopes) wait
45
+ * for the future credential-management RFC. Adapters that declare
46
+ * `requiresOAuth = true` cannot register; the registry returns a Decision
47
+ * pointing the operator at the pending RFC.
48
+ */
49
+ export declare class AdapterRequiresCredentialMgmtRfc extends Error {
50
+ readonly source: SignalSourceName;
51
+ constructor(source: SignalSourceName, message?: string);
52
+ }
53
+ export declare class ManualSignalIncomplete extends Error {
54
+ readonly sourceId?: string | undefined;
55
+ readonly source: "signal-source-manual";
56
+ constructor(sourceId?: string | undefined, message?: string);
57
+ }
58
+ /**
59
+ * RFC-0030 OQ-13.4 v0.3 re-walkthrough: per-operator rate-limit at signal
60
+ * entry. When a manual signal would exceed `manualEntry.dailyCapPerOperator`
61
+ * for the attested operator (in UTC-day buckets), the adapter raises this
62
+ * error. Downstream: emit `Decision: manual-signal-rate-limit-exceeded` and
63
+ * surface an operator BATCH-REVIEW escalation path.
64
+ */
65
+ export declare class ManualSignalRateLimitExceeded extends Error {
66
+ readonly attestedBy: string;
67
+ readonly dailyCap: number;
68
+ readonly utcDate: string;
69
+ readonly sourceId?: string | undefined;
70
+ readonly source: "signal-source-manual";
71
+ constructor(attestedBy: string, dailyCap: number, utcDate: string, sourceId?: string | undefined, message?: string);
72
+ }
73
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1,108 @@
1
+ export class UnknownSignalSource extends Error {
2
+ source;
3
+ constructor(source) {
4
+ super(`Unknown signal source adapter: ${source}`);
5
+ this.source = source;
6
+ this.name = 'UnknownSignalSource';
7
+ }
8
+ }
9
+ export class SignalSourceUnavailable extends Error {
10
+ source;
11
+ constructor(source, message = `Signal source adapter unavailable: ${source}`) {
12
+ super(message);
13
+ this.source = source;
14
+ this.name = 'SignalSourceUnavailable';
15
+ }
16
+ }
17
+ export class AdapterCredentialInvalid extends Error {
18
+ source;
19
+ constructor(source, message = `Signal source adapter credentials invalid: ${source}`) {
20
+ super(message);
21
+ this.source = source;
22
+ this.name = 'AdapterCredentialInvalid';
23
+ }
24
+ }
25
+ /**
26
+ * RFC-0030 OQ-13.1 v0.3 re-walkthrough: distinct from `AdapterCredentialInvalid`.
27
+ *
28
+ * Raised when an adapter probes its environment for the required credential
29
+ * env var (e.g. `SIGNAL_ZENDESK_PAT`) and the var is missing or empty.
30
+ * Downstream: emit `Decision: adapter-credential-not-configured` and surface
31
+ * an operator SETUP task ("create + set this env var"). Pipeline continues
32
+ * with the remaining valid adapters (AC #4).
33
+ */
34
+ export class AdapterCredentialNotConfigured extends Error {
35
+ source;
36
+ envVarName;
37
+ constructor(source, envVarName, message = `Signal source adapter credential not configured: ${source} (missing env var ${envVarName})`) {
38
+ super(message);
39
+ this.source = source;
40
+ this.envVarName = envVarName;
41
+ this.name = 'AdapterCredentialNotConfigured';
42
+ }
43
+ }
44
+ /**
45
+ * RFC-0030 OQ-13.1 v0.3 re-walkthrough: distinct from `AdapterCredentialInvalid`.
46
+ *
47
+ * Raised when an adapter has the credential env var present BUT the auth call
48
+ * against the upstream service is rejected (401 / 403). Downstream: emit
49
+ * `Decision: adapter-credential-rejected` and surface an operator ROTATION
50
+ * task ("rotate / re-generate this credential"). Pipeline continues with the
51
+ * remaining valid adapters (AC #4).
52
+ */
53
+ export class AdapterCredentialRejected extends Error {
54
+ source;
55
+ constructor(source, message = `Signal source adapter credentials rejected by upstream: ${source}`) {
56
+ super(message);
57
+ this.source = source;
58
+ this.name = 'AdapterCredentialRejected';
59
+ }
60
+ }
61
+ /**
62
+ * RFC-0030 OQ-13.1 v0.3 re-walkthrough: refuse OAuth-required adapters at
63
+ * registration. Phase 1 ships ENV-VAR-ONLY adapters; OAuth-required adapters
64
+ * (full Salesforce / HubSpot integrations, Zendesk-with-OAuth-scopes) wait
65
+ * for the future credential-management RFC. Adapters that declare
66
+ * `requiresOAuth = true` cannot register; the registry returns a Decision
67
+ * pointing the operator at the pending RFC.
68
+ */
69
+ export class AdapterRequiresCredentialMgmtRfc extends Error {
70
+ source;
71
+ constructor(source, message = `Adapter ${source} requires OAuth credential management; deferred to future RFC`) {
72
+ super(message);
73
+ this.source = source;
74
+ this.name = 'AdapterRequiresCredentialMgmtRfc';
75
+ }
76
+ }
77
+ export class ManualSignalIncomplete extends Error {
78
+ sourceId;
79
+ source = 'signal-source-manual';
80
+ constructor(sourceId, message = 'manual signal missing required attestation fields') {
81
+ super(message);
82
+ this.sourceId = sourceId;
83
+ this.name = 'ManualSignalIncomplete';
84
+ }
85
+ }
86
+ /**
87
+ * RFC-0030 OQ-13.4 v0.3 re-walkthrough: per-operator rate-limit at signal
88
+ * entry. When a manual signal would exceed `manualEntry.dailyCapPerOperator`
89
+ * for the attested operator (in UTC-day buckets), the adapter raises this
90
+ * error. Downstream: emit `Decision: manual-signal-rate-limit-exceeded` and
91
+ * surface an operator BATCH-REVIEW escalation path.
92
+ */
93
+ export class ManualSignalRateLimitExceeded extends Error {
94
+ attestedBy;
95
+ dailyCap;
96
+ utcDate;
97
+ sourceId;
98
+ source = 'signal-source-manual';
99
+ constructor(attestedBy, dailyCap, utcDate, sourceId, message = `Manual signal rate limit exceeded for ${attestedBy} on ${utcDate} (cap: ${dailyCap}/day)`) {
100
+ super(message);
101
+ this.attestedBy = attestedBy;
102
+ this.dailyCap = dailyCap;
103
+ this.utcDate = utcDate;
104
+ this.sourceId = sourceId;
105
+ this.name = 'ManualSignalRateLimitExceeded';
106
+ }
107
+ }
108
+ //# sourceMappingURL=errors.js.map