@ai-sdlc/orchestrator 0.9.0 → 0.13.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 (172) hide show
  1. package/dist/__test-helpers/git-env.d.ts +40 -0
  2. package/dist/__test-helpers/git-env.js +73 -0
  3. package/dist/adapters.d.ts +42 -3
  4. package/dist/adapters.js +133 -3
  5. package/dist/admission-composite.d.ts +134 -1
  6. package/dist/admission-composite.js +170 -6
  7. package/dist/admission-enrichment.d.ts +12 -3
  8. package/dist/admission-enrichment.js +71 -10
  9. package/dist/admission-hc.d.ts +20 -1
  10. package/dist/admission-hc.js +12 -1
  11. package/dist/admission-score.d.ts +44 -0
  12. package/dist/admission-score.js +11 -2
  13. package/dist/backlog-adapter.d.ts +45 -0
  14. package/dist/backlog-adapter.js +145 -1
  15. package/dist/calibration.d.ts +81 -0
  16. package/dist/calibration.js +76 -0
  17. package/dist/cli/commands/git-remote.d.ts +25 -0
  18. package/dist/cli/commands/git-remote.js +80 -1
  19. package/dist/cli/commands/init-features.d.ts +507 -0
  20. package/dist/cli/commands/init-features.js +1100 -0
  21. package/dist/cli/commands/init-templates.d.ts +301 -0
  22. package/dist/cli/commands/init-templates.js +1341 -0
  23. package/dist/cli/commands/init.d.ts +67 -0
  24. package/dist/cli/commands/init.js +301 -2
  25. package/dist/cli/commands/run.js +9 -1
  26. package/dist/cli/index.d.ts +11 -0
  27. package/dist/cli/index.js +42 -8
  28. package/dist/compliance/composer.d.ts +79 -0
  29. package/dist/compliance/composer.js +258 -0
  30. package/dist/compliance/errors.d.ts +64 -0
  31. package/dist/compliance/errors.js +85 -0
  32. package/dist/compliance/loader.d.ts +52 -0
  33. package/dist/compliance/loader.js +124 -0
  34. package/dist/compliance/types.d.ts +184 -0
  35. package/dist/compliance/types.js +41 -0
  36. package/dist/compliance-clearance.d.ts +269 -0
  37. package/dist/compliance-clearance.js +269 -0
  38. package/dist/config.js +17 -0
  39. package/dist/cost-tracker.d.ts +22 -0
  40. package/dist/cost-tracker.js +41 -0
  41. package/dist/database/adapters/external.js +5 -1
  42. package/dist/design-authority.d.ts +13 -0
  43. package/dist/design-authority.js +13 -0
  44. package/dist/embedding/adapters/openai-text-embedding-3-small.d.ts +71 -0
  45. package/dist/embedding/adapters/openai-text-embedding-3-small.js +190 -0
  46. package/dist/embedding/consumers/tessellation-drift.d.ts +74 -0
  47. package/dist/embedding/consumers/tessellation-drift.js +76 -0
  48. package/dist/embedding/cross-provider.d.ts +78 -0
  49. package/dist/embedding/cross-provider.js +75 -0
  50. package/dist/embedding/deprecation.d.ts +151 -0
  51. package/dist/embedding/deprecation.js +229 -0
  52. package/dist/embedding/errors.d.ts +90 -0
  53. package/dist/embedding/errors.js +150 -0
  54. package/dist/embedding/index.d.ts +29 -0
  55. package/dist/embedding/index.js +24 -0
  56. package/dist/embedding/pipeline-load.d.ts +146 -0
  57. package/dist/embedding/pipeline-load.js +178 -0
  58. package/dist/embedding/registry.d.ts +45 -0
  59. package/dist/embedding/registry.js +61 -0
  60. package/dist/embedding/stale-vector.d.ts +110 -0
  61. package/dist/embedding/stale-vector.js +92 -0
  62. package/dist/embedding/storage/index.d.ts +51 -0
  63. package/dist/embedding/storage/index.js +43 -0
  64. package/dist/embedding/storage/jsonl-backend.d.ts +150 -0
  65. package/dist/embedding/storage/jsonl-backend.js +332 -0
  66. package/dist/embedding/storage/types.d.ts +135 -0
  67. package/dist/embedding/storage/types.js +13 -0
  68. package/dist/embedding/types.d.ts +180 -0
  69. package/dist/embedding/types.js +10 -0
  70. package/dist/execute.d.ts +29 -2
  71. package/dist/execute.js +179 -35
  72. package/dist/index.d.ts +14 -2
  73. package/dist/index.js +19 -2
  74. package/dist/journey/inheritance-validator.d.ts +396 -0
  75. package/dist/journey/inheritance-validator.js +370 -0
  76. package/dist/journey/state-id-drift-rule.d.ts +137 -0
  77. package/dist/journey/state-id-drift-rule.js +245 -0
  78. package/dist/journey-sa2-router.d.ts +395 -0
  79. package/dist/journey-sa2-router.js +308 -0
  80. package/dist/models/classifier.d.ts +3 -1
  81. package/dist/models/classifier.js +61 -5
  82. package/dist/pillar-breakdown.d.ts +23 -1
  83. package/dist/pillar-breakdown.js +9 -0
  84. package/dist/runners/review-agent.js +6 -2
  85. package/dist/runners/runner-registry.d.ts +36 -0
  86. package/dist/runners/runner-registry.js +90 -0
  87. package/dist/runtime/attestations.d.ts +663 -11
  88. package/dist/runtime/attestations.js +971 -24
  89. package/dist/runtime/index.d.ts +1 -1
  90. package/dist/runtime/index.js +1 -1
  91. package/dist/runtime/parallelism-flag.d.ts +21 -3
  92. package/dist/runtime/parallelism-flag.js +27 -6
  93. package/dist/runtime/port-allocator.d.ts +11 -0
  94. package/dist/runtime/port-allocator.js +30 -0
  95. package/dist/sa-scoring/layer3-llm.js +6 -1
  96. package/dist/sa-scoring/revision-proposal-config.d.ts +178 -0
  97. package/dist/sa-scoring/revision-proposal-config.js +198 -0
  98. package/dist/sa-scoring/revision-proposal.d.ts +285 -0
  99. package/dist/sa-scoring/revision-proposal.js +417 -0
  100. package/dist/signal-ingestion/adapters/community-thread.d.ts +43 -0
  101. package/dist/signal-ingestion/adapters/community-thread.js +55 -0
  102. package/dist/signal-ingestion/adapters/in-app-feedback.d.ts +67 -0
  103. package/dist/signal-ingestion/adapters/in-app-feedback.js +51 -0
  104. package/dist/signal-ingestion/adapters/manual.d.ts +78 -0
  105. package/dist/signal-ingestion/adapters/manual.js +112 -0
  106. package/dist/signal-ingestion/adapters/support-ticket.d.ts +47 -0
  107. package/dist/signal-ingestion/adapters/support-ticket.js +51 -0
  108. package/dist/signal-ingestion/classifier.d.ts +205 -0
  109. package/dist/signal-ingestion/classifier.js +494 -0
  110. package/dist/signal-ingestion/clustering-types.d.ts +36 -0
  111. package/dist/signal-ingestion/clustering-types.js +14 -0
  112. package/dist/signal-ingestion/clustering.d.ts +200 -0
  113. package/dist/signal-ingestion/clustering.js +413 -0
  114. package/dist/signal-ingestion/config.d.ts +351 -0
  115. package/dist/signal-ingestion/config.js +587 -0
  116. package/dist/signal-ingestion/d1.d.ts +252 -0
  117. package/dist/signal-ingestion/d1.js +235 -0
  118. package/dist/signal-ingestion/errors.d.ts +73 -0
  119. package/dist/signal-ingestion/errors.js +108 -0
  120. package/dist/signal-ingestion/governance-events.d.ts +181 -0
  121. package/dist/signal-ingestion/governance-events.js +189 -0
  122. package/dist/signal-ingestion/index.d.ts +35 -0
  123. package/dist/signal-ingestion/index.js +53 -0
  124. package/dist/signal-ingestion/manual-share-metric.d.ts +93 -0
  125. package/dist/signal-ingestion/manual-share-metric.js +106 -0
  126. package/dist/signal-ingestion/registry.d.ts +40 -0
  127. package/dist/signal-ingestion/registry.js +137 -0
  128. package/dist/signal-ingestion/residency.d.ts +227 -0
  129. package/dist/signal-ingestion/residency.js +238 -0
  130. package/dist/signal-ingestion/significance.d.ts +554 -0
  131. package/dist/signal-ingestion/significance.js +555 -0
  132. package/dist/signal-ingestion/types.d.ts +191 -0
  133. package/dist/signal-ingestion/types.js +8 -0
  134. package/dist/substrate/drift-composition.d.ts +270 -0
  135. package/dist/substrate/drift-composition.js +306 -0
  136. package/dist/substrate/drift-tui-surface.d.ts +61 -0
  137. package/dist/substrate/drift-tui-surface.js +102 -0
  138. package/dist/substrate/identity-class.d.ts +176 -0
  139. package/dist/substrate/identity-class.js +201 -0
  140. package/dist/tessellation/cross-soul-provenance-rule.d.ts +133 -0
  141. package/dist/tessellation/cross-soul-provenance-rule.js +171 -0
  142. package/dist/tessellation/inter-soul-embedding-distance-rule.d.ts +61 -0
  143. package/dist/tessellation/inter-soul-embedding-distance-rule.js +67 -0
  144. package/dist/tessellation/rule-registry.d.ts +269 -0
  145. package/dist/tessellation/rule-registry.js +92 -0
  146. package/dist/tessellation/soul-slug-ast-scan-rule.d.ts +90 -0
  147. package/dist/tessellation/soul-slug-ast-scan-rule.js +158 -0
  148. package/dist/tessellation-admission.d.ts +162 -0
  149. package/dist/tessellation-admission.js +146 -0
  150. package/dist/tessellation-drift.d.ts +246 -0
  151. package/dist/tessellation-drift.js +250 -0
  152. package/dist/validate-config.js +13 -0
  153. package/dist/validate-issue.js +2 -2
  154. package/dist/variant/cardinality-activation.d.ts +126 -0
  155. package/dist/variant/cardinality-activation.js +101 -0
  156. package/dist/variant/deprecation-lifecycle.d.ts +184 -0
  157. package/dist/variant/deprecation-lifecycle.js +208 -0
  158. package/dist/variant/drift-extension.d.ts +136 -0
  159. package/dist/variant/drift-extension.js +164 -0
  160. package/dist/variant/engineering-review.d.ts +185 -0
  161. package/dist/variant/engineering-review.js +142 -0
  162. package/dist/variant/index.d.ts +32 -0
  163. package/dist/variant/index.js +32 -0
  164. package/dist/variant/inheritance-validator.d.ts +165 -0
  165. package/dist/variant/inheritance-validator.js +139 -0
  166. package/dist/variant/internal-adopter/index.d.ts +11 -0
  167. package/dist/variant/internal-adopter/index.js +10 -0
  168. package/dist/variant/internal-adopter/products.d.ts +156 -0
  169. package/dist/variant/internal-adopter/products.js +366 -0
  170. package/dist/variant-admission.d.ts +316 -0
  171. package/dist/variant-admission.js +247 -0
  172. package/package.json +11 -8
@@ -0,0 +1,306 @@
1
+ /**
2
+ * RFC-0028 §7.2 — structural + statistical drift composition.
3
+ *
4
+ * Phase 3 of RFC-0028 §7.2 v0.2 resolution. Wires the canonical pairing of
5
+ * the two drift-detection layers with the explicit composition rules the
6
+ * operator resolved on 2026-05-27:
7
+ *
8
+ * 1. **Structural drift (CI authoring-time) REJECTS deployment.** The
9
+ * type-registry CI gate (`scripts/check-substrate-contract.mjs`,
10
+ * AISDLC-453) is the hard gate. When any of its 5 assertions fail it emits
11
+ * `Decision: substrate-structural-drift-detected` (severity HIGH) and
12
+ * blocks PR merge via a non-zero exit code. This module does NOT rebuild
13
+ * that gate — it *represents* its outcome so the two layers can be
14
+ * composed and correlated. {@link toStructuralDriftEvents} maps the gate's
15
+ * result into the composition model.
16
+ *
17
+ * 2. **Statistical drift (runtime) SURFACES to operator (RFC-0035 G0
18
+ * non-blocking).** PPA's `SoulDriftDetected` signal (rolling 30-day mean
19
+ * < 0.4 or stddev > 0.15 sustained for 3 sprints) routes to
20
+ * `Decision: soul-statistical-drift-detected` → operator batch review with
21
+ * three reconciliation paths. The pipeline NEVER halts on statistical
22
+ * drift. {@link evaluateStatisticalDrift} computes the signal;
23
+ * {@link composeDrift} routes the firing signal to the catalog request.
24
+ *
25
+ * 3. **Both Decisions composable in the catalog.** Both classes carry a
26
+ * `soulId` + a stable `driftClass` discriminant and share one catalog
27
+ * scope ({@link DRIFT_DECISION_SCOPE}) so an operator can query
28
+ * "show me all drift events for Soul X" and get structural (rejected at
29
+ * CI) and statistical (caught at runtime) events side-by-side —
30
+ * {@link correlateDriftBySoul}.
31
+ *
32
+ * 4. **Cold-start handling.** Statistical detection needs a rolling 30d
33
+ * baseline. Pre-baseline (< 30d of signal), the detector returns a
34
+ * `calibrating` status and emits NO statistical Decisions — structural
35
+ * detection is the sole defense during the calibration window. The
36
+ * cold-start shape mirrors RFC-0030 §13 OQ-13.5's z-score flooding
37
+ * detector (calibrating until the rolling window fills, then active).
38
+ *
39
+ * **AC-8 — no parallel event emitter.** This module does NOT import or write
40
+ * to the catalog event log directly. Mirroring the Phase 1 `identity-class.ts`
41
+ * audit and the Phase 2 `check-substrate-contract.mjs` gate, it produces
42
+ * `DriftDecisionRequest` payloads (summary + scope + source + options) that
43
+ * the *caller* forwards to the existing RFC-0035 substrate via
44
+ * `node pipeline-cli/bin/cli-decisions.mjs add` (or `makeDecisionOpenedEvent`
45
+ * + `appendDecisionEvent` in-process). The request shape is byte-compatible
46
+ * with `cli-decisions add --summary … --scope … --option id:desc`. This keeps
47
+ * the orchestrator package free of a build-time dependency on
48
+ * `@ai-sdlc/pipeline-cli`'s dist output and avoids a second emitter.
49
+ *
50
+ * @see spec/rfcs/RFC-0028-engineering-axis-substrate-enforcement.md §7.2
51
+ * @see spec/rfcs/RFC-0035-decision-catalog-operator-routing.md (G0 routing)
52
+ * @see scripts/check-substrate-contract.mjs (the structural layer, AISDLC-453)
53
+ * @module substrate/drift-composition
54
+ */
55
+ /**
56
+ * The RFC-0035 Decision scope used for every drift Decision. Both classes
57
+ * share one scope so `cli-decisions list --scope substrate-drift` returns
58
+ * the composed view (AC-3 / AC-5).
59
+ */
60
+ export const DRIFT_DECISION_SCOPE = 'substrate-drift';
61
+ /** Decision summary slug for structural drift (matches AISDLC-453 gate). */
62
+ export const STRUCTURAL_DECISION_SLUG = 'substrate-structural-drift-detected';
63
+ /** Decision summary slug for statistical drift (RFC-0028 §7.2 rule 2). */
64
+ export const STATISTICAL_DECISION_SLUG = 'soul-statistical-drift-detected';
65
+ /**
66
+ * Statistical-drift detection thresholds, frozen from RFC-0028 §7.2 / PPA's
67
+ * `SoulDriftDetected` definition. A drift fires when the rolling 30-day mean
68
+ * drops below {@link MEAN_FLOOR} OR the rolling stddev exceeds
69
+ * {@link STDDEV_CEILING}, sustained for {@link SUSTAINED_SPRINTS} sprints.
70
+ */
71
+ export const MEAN_FLOOR = 0.4;
72
+ export const STDDEV_CEILING = 0.15;
73
+ export const SUSTAINED_SPRINTS = 3;
74
+ /** Rolling baseline window in days (RFC-0028 §7.2 cold-start: 30d). */
75
+ export const BASELINE_WINDOW_DAYS = 30;
76
+ // ── Reconciliation paths (RFC-0028 §7.2 — exactly three) ──────────────────
77
+ /**
78
+ * The three (and only three) operator reconciliation paths for a
79
+ * `soul-statistical-drift-detected` Decision, per RFC-0028 §7.2:
80
+ *
81
+ * (a) confirm drift as legitimate evolution → emit DID amendment
82
+ * (b) confirm drift as substrate violation → file fix task
83
+ * (c) defer for the next operator review window
84
+ */
85
+ export const STATISTICAL_RECONCILIATION_OPTIONS = Object.freeze([
86
+ {
87
+ id: 'confirm-as-evolution',
88
+ description: 'Confirm drift as legitimate evolution — emit a DID amendment to ratify the new baseline.',
89
+ consequences: ['DID amendment authored', 'New baseline ratified as intended state'],
90
+ },
91
+ {
92
+ id: 'confirm-as-violation',
93
+ description: 'Confirm drift as a substrate violation — file a fix task to restore the contracted baseline.',
94
+ consequences: ['Fix task filed', 'Substrate restored to contracted intent'],
95
+ },
96
+ {
97
+ id: 'defer',
98
+ description: 'Defer for the next operator review window — re-surface unchanged at the next batch review.',
99
+ consequences: ['Decision re-surfaces next review window', 'No state change'],
100
+ },
101
+ ]);
102
+ /** Structural-drift reconciliation options mirror the AISDLC-453 CI gate. */
103
+ export const STRUCTURAL_RECONCILIATION_OPTIONS = Object.freeze([
104
+ { id: 'fix', description: 'Correct the contract field causing the assertion failure.' },
105
+ { id: 'exempt', description: 'Document an RFC-approved exemption for this soul.' },
106
+ ]);
107
+ /**
108
+ * Project the structural CI gate's result into composition `DriftEvent`s.
109
+ *
110
+ * One `DriftEvent` per gate failure. Always `blocking: true` / severity
111
+ * `high` — structural drift is the hard gate (RFC-0028 §7.2 rule 1). Returns
112
+ * `[]` when the gate passed or was a cold-start no-op (no contracts).
113
+ */
114
+ export function toStructuralDriftEvents(result) {
115
+ if (result.passed || result.coldStart)
116
+ return [];
117
+ return result.failures.map((f) => ({
118
+ driftClass: 'structural',
119
+ soulId: f.soulId,
120
+ severity: 'high',
121
+ blocking: true,
122
+ summary: f.decisionSummary ?? `${STRUCTURAL_DECISION_SLUG}: Soul "${f.soulId}" — ${f.message}`,
123
+ detail: f.message,
124
+ }));
125
+ }
126
+ function mean(values) {
127
+ return values.reduce((a, b) => a + b, 0) / values.length;
128
+ }
129
+ function populationStdDev(values, mu) {
130
+ const variance = values.reduce((acc, v) => acc + (v - mu) ** 2, 0) / values.length;
131
+ return Math.sqrt(variance);
132
+ }
133
+ /** Count the length of the trailing run of `true` values. */
134
+ function trailingTrueRun(flags) {
135
+ let run = 0;
136
+ for (let i = flags.length - 1; i >= 0; i--) {
137
+ if (flags[i])
138
+ run += 1;
139
+ else
140
+ break;
141
+ }
142
+ return run;
143
+ }
144
+ /**
145
+ * Evaluate statistical drift for one Soul DID over its metric history.
146
+ *
147
+ * Cold-start (AC-4): if the span between the earliest sample and `now` is
148
+ * shorter than {@link BASELINE_WINDOW_DAYS}, the detector is still
149
+ * `calibrating` — it returns `drifted: false` with null statistics and the
150
+ * caller emits no Decision. Structural detection (AISDLC-453) is the sole
151
+ * defense during this window.
152
+ *
153
+ * Once the window is filled the detector is `active`: drift fires when the
154
+ * rolling mean < {@link MEAN_FLOOR} OR the rolling stddev >
155
+ * {@link STDDEV_CEILING}, AND that condition has held for at least
156
+ * {@link SUSTAINED_SPRINTS} consecutive sprint buckets.
157
+ *
158
+ * The cold-start shape mirrors RFC-0030 §13 OQ-13.5's z-score flooding
159
+ * detector (calibrating until the window accumulates, then active).
160
+ *
161
+ * @param samples Metric history (any order). Empty → calibrating.
162
+ * @param sprintFlags Per-sprint booleans (most-recent last) indicating
163
+ * whether the drift condition held in each sprint; used to enforce the
164
+ * "3 sustained sprints" rule independent of raw sample cadence. When
165
+ * omitted, sustained-sprint accounting falls back to whether the current
166
+ * window meets the condition (counts as 1 sprint, so a single-window
167
+ * breach is reported but does not satisfy the 3-sprint rule).
168
+ * @param now Clock override for deterministic tests.
169
+ */
170
+ export function evaluateStatisticalDrift(samples, sprintFlags, now = new Date()) {
171
+ if (samples.length === 0) {
172
+ return {
173
+ status: 'calibrating',
174
+ drifted: false,
175
+ rollingMean: null,
176
+ rollingStdDev: null,
177
+ sustainedSprints: 0,
178
+ reason: 'No signal yet — calibrating (cold-start).',
179
+ };
180
+ }
181
+ const sorted = [...samples].sort((a, b) => new Date(a.at).getTime() - new Date(b.at).getTime());
182
+ const earliest = new Date(sorted[0].at).getTime();
183
+ const spanDays = (now.getTime() - earliest) / (1000 * 60 * 60 * 24);
184
+ if (spanDays < BASELINE_WINDOW_DAYS) {
185
+ return {
186
+ status: 'calibrating',
187
+ drifted: false,
188
+ rollingMean: null,
189
+ rollingStdDev: null,
190
+ sustainedSprints: 0,
191
+ reason: `Baseline incomplete (${spanDays.toFixed(1)}d / ${BASELINE_WINDOW_DAYS}d) — calibrating; structural detection is sole defense.`,
192
+ };
193
+ }
194
+ // Active: compute rolling stats over the trailing 30d window.
195
+ const cutoff = now.getTime() - BASELINE_WINDOW_DAYS * 24 * 60 * 60 * 1000;
196
+ const windowValues = sorted.filter((s) => new Date(s.at).getTime() >= cutoff).map((s) => s.value);
197
+ // Fall back to the whole series if the trailing window is empty (sparse cadence).
198
+ const values = windowValues.length > 0 ? windowValues : sorted.map((s) => s.value);
199
+ const mu = mean(values);
200
+ const sigma = populationStdDev(values, mu);
201
+ const meanBreach = mu < MEAN_FLOOR;
202
+ const stdDevBreach = sigma > STDDEV_CEILING;
203
+ const conditionMet = meanBreach || stdDevBreach;
204
+ const sustainedSprints = sprintFlags ? trailingTrueRun(sprintFlags) : conditionMet ? 1 : 0;
205
+ const drifted = conditionMet && sustainedSprints >= SUSTAINED_SPRINTS;
206
+ const reasons = [];
207
+ if (meanBreach)
208
+ reasons.push(`rolling 30d mean ${mu.toFixed(3)} < ${MEAN_FLOOR}`);
209
+ if (stdDevBreach)
210
+ reasons.push(`rolling 30d stddev ${sigma.toFixed(3)} > ${STDDEV_CEILING}`);
211
+ const reason = drifted
212
+ ? `${reasons.join(' and ')} sustained for ${sustainedSprints} sprint(s).`
213
+ : conditionMet
214
+ ? `${reasons.join(' and ')} but only ${sustainedSprints}/${SUSTAINED_SPRINTS} sustained sprints — not yet drifted.`
215
+ : `Within bounds (mean ${mu.toFixed(3)}, stddev ${sigma.toFixed(3)}).`;
216
+ return {
217
+ status: 'active',
218
+ drifted,
219
+ rollingMean: mu,
220
+ rollingStdDev: sigma,
221
+ sustainedSprints,
222
+ reason,
223
+ };
224
+ }
225
+ /**
226
+ * Project an `active` + `drifted` statistical result into a composition
227
+ * `DriftEvent`. Returns `null` while calibrating or not drifted — there is
228
+ * nothing to surface to the operator (AC-4: no statistical Decisions during
229
+ * cold-start).
230
+ */
231
+ export function toStatisticalDriftEvent(soulId, result) {
232
+ if (result.status !== 'active' || !result.drifted)
233
+ return null;
234
+ return {
235
+ driftClass: 'statistical',
236
+ soulId,
237
+ severity: 'advisory',
238
+ blocking: false,
239
+ summary: `${STATISTICAL_DECISION_SLUG}: Soul "${soulId}" — ${result.reason}`,
240
+ detail: result.reason,
241
+ };
242
+ }
243
+ /** Build the catalog request for one composed drift event. */
244
+ export function toDecisionRequest(event) {
245
+ const isStructural = event.driftClass === 'structural';
246
+ return {
247
+ // Structural failures are emergent CI findings; statistical drift is a
248
+ // framework-calibration signal. Both are valid catalog sources.
249
+ source: isStructural ? 'emergent-finding' : 'framework-calibration',
250
+ scope: DRIFT_DECISION_SCOPE,
251
+ summary: event.summary,
252
+ ...(event.detail !== undefined ? { body: event.detail } : {}),
253
+ // Statistical drift is reversible/non-blocking (G0); structural is a hard
254
+ // gate the operator must clear.
255
+ reversible: !isStructural,
256
+ options: [
257
+ ...(isStructural ? STRUCTURAL_RECONCILIATION_OPTIONS : STATISTICAL_RECONCILIATION_OPTIONS),
258
+ ],
259
+ };
260
+ }
261
+ /**
262
+ * Compose the structural gate outcome with a set of statistical results.
263
+ *
264
+ * - Structural events → `substrate-structural-drift-detected` (HIGH,
265
+ * blocking). Represented from the CI gate result; the gate itself already
266
+ * emits + blocks at CI time (AISDLC-453), so the catalog request here is the
267
+ * composition/audit projection (so structural + statistical sit
268
+ * side-by-side in one scope).
269
+ * - Statistical drifted events → `soul-statistical-drift-detected`
270
+ * (advisory, non-blocking, G0) with the three reconciliation options.
271
+ * - Calibrating / non-drifted statistical results emit NO request (AC-4).
272
+ *
273
+ * The pipeline never halts here: `blocked` reports the structural hard-gate
274
+ * state for the CI caller to act on, but statistical drift never sets it.
275
+ */
276
+ export function composeDrift(structural, statistical) {
277
+ const structuralEvents = toStructuralDriftEvents(structural);
278
+ const statisticalEvents = [];
279
+ for (const { soulId, result } of statistical) {
280
+ const evt = toStatisticalDriftEvent(soulId, result);
281
+ if (evt)
282
+ statisticalEvents.push(evt);
283
+ }
284
+ const events = [...structuralEvents, ...statisticalEvents];
285
+ return {
286
+ events,
287
+ requests: events.map(toDecisionRequest),
288
+ blocked: structuralEvents.length > 0,
289
+ };
290
+ }
291
+ // ── Catalog correlation (AC-3 / AC-5) ─────────────────────────────────────
292
+ /**
293
+ * Group composed drift events by Soul DID so an operator can query
294
+ * "show me all drift events for Soul X" and get both classes side-by-side.
295
+ * Returns a map keyed by `soulId` whose values preserve event order.
296
+ */
297
+ export function correlateDriftBySoul(events) {
298
+ const bySoul = new Map();
299
+ for (const evt of events) {
300
+ const list = bySoul.get(evt.soulId) ?? [];
301
+ list.push(evt);
302
+ bySoul.set(evt.soulId, list);
303
+ }
304
+ return bySoul;
305
+ }
306
+ //# sourceMappingURL=drift-composition.js.map
@@ -0,0 +1,61 @@
1
+ /**
2
+ * RFC-0028 §7.2 — drift composition TUI surface (AC-5).
3
+ *
4
+ * Renders structural + statistical drift events side-by-side for the operator
5
+ * batch-review panel. Composes with the RFC-0023 operator TUI: this module
6
+ * produces a plain-text section (a `DriftPanel` string + a structured
7
+ * `DriftPanelModel`) that the TUI host embeds in its pipeline-visibility view.
8
+ * Keeping the render pure (string in / string out, no terminal control codes)
9
+ * lets the RFC-0023 surface own layout, paging, and color while this module
10
+ * owns the drift-specific content.
11
+ *
12
+ * Side-by-side requirement (RFC-0028 §7.2 rule 3): events are grouped by Soul
13
+ * DID via {@link correlateDriftBySoul} so each soul shows its structural
14
+ * (rejected-at-CI, HIGH) and statistical (caught-at-runtime, advisory) drift
15
+ * together — closing the "drift caught early vs drift that escaped" loop.
16
+ *
17
+ * @see spec/rfcs/RFC-0023-operator-tui.md (the host surface)
18
+ * @see spec/rfcs/RFC-0028-engineering-axis-substrate-enforcement.md §7.2
19
+ * @module substrate/drift-tui-surface
20
+ */
21
+ import { type DriftClass, type DriftEvent } from './drift-composition.js';
22
+ /** A single rendered row in the drift panel. */
23
+ export interface DriftPanelRow {
24
+ soulId: string;
25
+ driftClass: DriftClass;
26
+ /** `BLOCKS` for structural, `surface` for statistical. */
27
+ disposition: 'BLOCKS' | 'surface';
28
+ severity: DriftEvent['severity'];
29
+ summary: string;
30
+ }
31
+ /** Per-soul grouping for the side-by-side panel model. */
32
+ export interface DriftPanelSoulGroup {
33
+ soulId: string;
34
+ structural: DriftPanelRow[];
35
+ statistical: DriftPanelRow[];
36
+ }
37
+ /** The structured model a TUI host can render however it likes (AC-5). */
38
+ export interface DriftPanelModel {
39
+ /** Souls in stable (insertion) order, each with both drift classes. */
40
+ groups: DriftPanelSoulGroup[];
41
+ structuralCount: number;
42
+ statisticalCount: number;
43
+ /** True if any structural (blocking) drift is present. */
44
+ hasBlocking: boolean;
45
+ }
46
+ /**
47
+ * Build the structured panel model from composed drift events. Souls appear
48
+ * in first-seen order; within each soul, structural rows precede statistical
49
+ * rows (hard gate first, then advisory).
50
+ */
51
+ export declare function buildDriftPanelModel(events: DriftEvent[]): DriftPanelModel;
52
+ /**
53
+ * Render the drift panel as a plain-text section for the RFC-0023 TUI.
54
+ *
55
+ * Produces a per-soul block with structural drift (rejected at CI, hard gate)
56
+ * and statistical drift (surfaced for operator review, non-blocking) shown
57
+ * side-by-side under each Soul DID. Empty input renders a single "no drift"
58
+ * line so the panel never collapses to nothing.
59
+ */
60
+ export declare function renderDriftPanel(events: DriftEvent[]): string;
61
+ //# sourceMappingURL=drift-tui-surface.d.ts.map
@@ -0,0 +1,102 @@
1
+ /**
2
+ * RFC-0028 §7.2 — drift composition TUI surface (AC-5).
3
+ *
4
+ * Renders structural + statistical drift events side-by-side for the operator
5
+ * batch-review panel. Composes with the RFC-0023 operator TUI: this module
6
+ * produces a plain-text section (a `DriftPanel` string + a structured
7
+ * `DriftPanelModel`) that the TUI host embeds in its pipeline-visibility view.
8
+ * Keeping the render pure (string in / string out, no terminal control codes)
9
+ * lets the RFC-0023 surface own layout, paging, and color while this module
10
+ * owns the drift-specific content.
11
+ *
12
+ * Side-by-side requirement (RFC-0028 §7.2 rule 3): events are grouped by Soul
13
+ * DID via {@link correlateDriftBySoul} so each soul shows its structural
14
+ * (rejected-at-CI, HIGH) and statistical (caught-at-runtime, advisory) drift
15
+ * together — closing the "drift caught early vs drift that escaped" loop.
16
+ *
17
+ * @see spec/rfcs/RFC-0023-operator-tui.md (the host surface)
18
+ * @see spec/rfcs/RFC-0028-engineering-axis-substrate-enforcement.md §7.2
19
+ * @module substrate/drift-tui-surface
20
+ */
21
+ import { correlateDriftBySoul } from './drift-composition.js';
22
+ function toRow(event) {
23
+ return {
24
+ soulId: event.soulId,
25
+ driftClass: event.driftClass,
26
+ disposition: event.driftClass === 'structural' ? 'BLOCKS' : 'surface',
27
+ severity: event.severity,
28
+ summary: event.summary,
29
+ };
30
+ }
31
+ /**
32
+ * Build the structured panel model from composed drift events. Souls appear
33
+ * in first-seen order; within each soul, structural rows precede statistical
34
+ * rows (hard gate first, then advisory).
35
+ */
36
+ export function buildDriftPanelModel(events) {
37
+ const bySoul = correlateDriftBySoul(events);
38
+ const groups = [];
39
+ let structuralCount = 0;
40
+ let statisticalCount = 0;
41
+ let hasBlocking = false;
42
+ for (const [soulId, soulEvents] of bySoul) {
43
+ const structural = [];
44
+ const statistical = [];
45
+ for (const evt of soulEvents) {
46
+ const row = toRow(evt);
47
+ if (evt.driftClass === 'structural') {
48
+ structural.push(row);
49
+ structuralCount += 1;
50
+ hasBlocking = true;
51
+ }
52
+ else {
53
+ statistical.push(row);
54
+ statisticalCount += 1;
55
+ }
56
+ }
57
+ groups.push({ soulId, structural, statistical });
58
+ }
59
+ return { groups, structuralCount, statisticalCount, hasBlocking };
60
+ }
61
+ /**
62
+ * Render the drift panel as a plain-text section for the RFC-0023 TUI.
63
+ *
64
+ * Produces a per-soul block with structural drift (rejected at CI, hard gate)
65
+ * and statistical drift (surfaced for operator review, non-blocking) shown
66
+ * side-by-side under each Soul DID. Empty input renders a single "no drift"
67
+ * line so the panel never collapses to nothing.
68
+ */
69
+ export function renderDriftPanel(events) {
70
+ const model = buildDriftPanelModel(events);
71
+ const lines = [];
72
+ lines.push('Substrate Drift — structural (CI hard gate) + statistical (runtime, G0)');
73
+ lines.push(` ${model.structuralCount} structural (blocking) · ${model.statisticalCount} statistical (advisory)`);
74
+ if (model.groups.length === 0) {
75
+ lines.push(' (no drift events)');
76
+ return lines.join('\n');
77
+ }
78
+ for (const group of model.groups) {
79
+ lines.push('');
80
+ lines.push(`Soul: ${group.soulId}`);
81
+ lines.push(' Structural (REJECTS deployment):');
82
+ if (group.structural.length === 0) {
83
+ lines.push(' (none)');
84
+ }
85
+ else {
86
+ for (const row of group.structural) {
87
+ lines.push(` [BLOCKS · ${row.severity}] ${row.summary}`);
88
+ }
89
+ }
90
+ lines.push(' Statistical (surfaces to operator — non-blocking):');
91
+ if (group.statistical.length === 0) {
92
+ lines.push(' (none)');
93
+ }
94
+ else {
95
+ for (const row of group.statistical) {
96
+ lines.push(` [surface · ${row.severity}] ${row.summary}`);
97
+ }
98
+ }
99
+ }
100
+ return lines.join('\n');
101
+ }
102
+ //# sourceMappingURL=drift-tui-surface.js.map
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Canonical `identityClass` taxonomy for Substrate Contract fields.
3
+ *
4
+ * Phase 1 of RFC-0028 §7.1 v0.2 resolution. Defines the framework-level
5
+ * taxonomy `core | evolving` together with bucket assignments, a novel-field
6
+ * default helper (conservative `core`), and the type-level tightening-only
7
+ * primitives used by Substrate Contract authors.
8
+ *
9
+ * Harmonizes with the already-shipped `'core' | 'evolving'` discriminant in
10
+ * `orchestrator/src/sa-scoring/layer1-deterministic.ts` — see
11
+ * {@link auditLayer1DeterministicClassifications} for the cross-check and
12
+ * {@link IdentityClassDiscrepancy} for the discrepancy shape filed against the
13
+ * Decision Catalog.
14
+ *
15
+ * @see spec/rfcs/RFC-0028-engineering-axis-substrate-enforcement.md §6, §7.1
16
+ */
17
+ /**
18
+ * The canonical two-bucket taxonomy. Soul-DID-level identity vs operational
19
+ * tuning surface. Wider RFC-0028 §7.1 rationale: `core` changes are Soul
20
+ * pivots (full re-scoring fires); `evolving` changes are normal evolution
21
+ * (admission-queue re-scoring only).
22
+ */
23
+ export type IdentityClass = 'core' | 'evolving';
24
+ /** Runtime-enumerable list of every taxonomy bucket. */
25
+ export declare const IDENTITY_CLASSES: readonly IdentityClass[];
26
+ /**
27
+ * Substrate Contract field names that are CANONICAL `core` per RFC-0028 §7.1.
28
+ *
29
+ * Child Soul DIDs CANNOT loosen any value declared on these fields. A pivot
30
+ * rescoring fires whenever any of these fields change.
31
+ */
32
+ export declare const CORE_BUCKET: {
33
+ /** Categorical compliance locks — `boolean` locks typed as `true` literal when locked. */
34
+ readonly categoricalComplianceLocks: readonly ["requiresTenantPhysicalIsolation", "requiresVulnerableAudienceLockout"];
35
+ /** Compliance regime declarations — categorical, tightening-only. */
36
+ readonly complianceRegimeDeclarations: readonly ["HIPAA", "PCI-DSS", "SOC2", "FedRAMP", "GDPR"];
37
+ /** Director / orchestrator agent identifier — changing the director IS a Soul-level event. */
38
+ readonly directorIdentifiers: readonly ["director", "orchestratorAgentId"];
39
+ /** §6 tightening-only `complianceFloor: inherit` lock. */
40
+ readonly complianceFloorLock: readonly ["complianceFloor"];
41
+ };
42
+ /**
43
+ * Substrate Contract field names that are CANONICAL `evolving` per RFC-0028 §7.1.
44
+ *
45
+ * Free movement within tightening-only bounds (a `cadence` may shorten but
46
+ * not lengthen past the parent; a similarity threshold may tighten but not
47
+ * loosen). Admission-queue rescoring only — no Soul pivot.
48
+ */
49
+ export declare const EVOLVING_BUCKET: {
50
+ /** Operational cadence — observer cooldown / cadence minimum interval. */
51
+ readonly operationalCadence: readonly ["observerCooldownMs", "cadenceMinIntervalDays"];
52
+ /** Scoring tuning weights — bid diversity weight, recency half-life. */
53
+ readonly scoringTuningWeights: readonly ["bidDiversityWeight", "recencyHalfLife"];
54
+ /** Similarity thresholds — `clustering.similarityThreshold`. */
55
+ readonly similarityThresholds: readonly ["clustering.similarityThreshold"];
56
+ /** Quota quantities — `tenantQuotaShare`. */
57
+ readonly quotaQuantities: readonly ["tenantQuotaShare"];
58
+ };
59
+ /**
60
+ * Reverse-lookup table: field name → canonical `IdentityClass`.
61
+ * Built from {@link CORE_BUCKET} + {@link EVOLVING_BUCKET}. Fields not listed
62
+ * here are NOVEL and resolve via {@link defaultIdentityClassForNovelField}.
63
+ */
64
+ export declare const CANONICAL_FIELD_CLASSIFICATIONS: Readonly<Record<string, IdentityClass>>;
65
+ /** Optional warning hook invoked when {@link defaultIdentityClassForNovelField} fires. */
66
+ export type NovelFieldWarningHook = (fieldName: string, defaultedTo: IdentityClass) => void;
67
+ /**
68
+ * Resolve the canonical `IdentityClass` for a Substrate Contract field.
69
+ *
70
+ * - Listed in {@link CANONICAL_FIELD_CLASSIFICATIONS} → returns that bucket.
71
+ * - Otherwise NOVEL → returns `'core'` (per RFC-0028 §7.1 v0.2 conservative
72
+ * default; promotion to `evolving` needs an RFC amendment with Design +
73
+ * Engineering sign-off — burden-of-proof is "argue why operational").
74
+ *
75
+ * When defaulting fires AND a `warn` hook is supplied, the hook is invoked
76
+ * synchronously so contract authors get a visible signal that a field has
77
+ * not yet been classified canonically.
78
+ */
79
+ export declare function defaultIdentityClassForNovelField(fieldName: string, options?: {
80
+ warn?: NovelFieldWarningHook;
81
+ }): IdentityClass;
82
+ /**
83
+ * A boolean compliance lock that is LOCKED. Typed as the `true` literal so
84
+ * any attempt to assign `false` (loosening) fails at compile time.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * const lock: LockedBoolean = true; // ok
89
+ * const lock: LockedBoolean = false; // type error
90
+ * ```
91
+ */
92
+ export type LockedBoolean = true;
93
+ /**
94
+ * A numeric cap that may only DECREASE. The discriminant `kind` field
95
+ * narrows the union; consumers may attempt to inherit + tighten by passing a
96
+ * smaller `max` value. The child-tightens-parent constraint is enforced via
97
+ * the {@link assertTightenedCap} helper at authoring time.
98
+ *
99
+ * Implementation note: TypeScript's structural type system cannot enforce
100
+ * "must be ≤ parent" at the type-level for arbitrary numeric values (that
101
+ * requires dependent types). The bounded discriminated union ensures
102
+ * authors must DECLARE intent (`tightened` vs `inherited`); the assertion
103
+ * helper catches loosening at runtime/authoring time.
104
+ */
105
+ export type BoundedNumericCap = {
106
+ kind: 'inherited';
107
+ max: number;
108
+ } | {
109
+ kind: 'tightened';
110
+ max: number;
111
+ previousMax: number;
112
+ };
113
+ /** Run-time assertion that a {@link BoundedNumericCap} tightening is valid. */
114
+ export declare function assertTightenedCap(cap: BoundedNumericCap): void;
115
+ /**
116
+ * Categorical inheritance via TypeScript template-literal types. The child
117
+ * Soul DID's value MUST be a string that is provably a subset of the parent
118
+ * (encoded as a literal-string union). At the type system level, any value
119
+ * outside that union fails compile-time inference.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * type ParentRegime = 'HIPAA' | 'PCI-DSS' | 'SOC2' | 'FedRAMP' | 'GDPR';
124
+ * type ChildRegime = TightenedCategorical<ParentRegime, 'HIPAA' | 'SOC2'>;
125
+ * // ChildRegime = 'HIPAA' | 'SOC2' — strictly tightened subset
126
+ * ```
127
+ */
128
+ export type TightenedCategorical<Parent extends string, Child extends Parent> = Child;
129
+ /**
130
+ * A single shipped-code identityClass usage that disagrees with the
131
+ * canonical taxonomy. Surfaced via {@link auditLayer1DeterministicClassifications}.
132
+ */
133
+ export interface IdentityClassDiscrepancy {
134
+ /** Source file (repo-relative path). */
135
+ file: string;
136
+ /** Symbol or call-site label (e.g. function or interface name + line context). */
137
+ symbol: string;
138
+ /** Field / discriminant the classification applies to. */
139
+ field: string;
140
+ /** Value observed in the shipped code. */
141
+ observed: IdentityClass;
142
+ /** Value the canonical taxonomy prescribes. */
143
+ canonical: IdentityClass;
144
+ /** Operator-facing rationale describing why this is flagged. */
145
+ rationale: string;
146
+ }
147
+ /**
148
+ * Audit the shipped `orchestrator/src/sa-scoring/layer1-deterministic.ts`
149
+ * (and its compiler `did-compiler.ts`) identityClass classifications against
150
+ * the canonical RFC-0028 §7.1 taxonomy.
151
+ *
152
+ * RFC-0028 §7.1 canonical taxonomy classifies *Substrate Contract* fields.
153
+ * `layer1-deterministic.ts` applies the same `'core' | 'evolving'`
154
+ * discriminant to *DID scoring entries* (scope-gate terms, constraint rules,
155
+ * anti-patterns, measurable signals). The discriminant is reused across two
156
+ * layers; the taxonomy was only formally defined for the substrate layer.
157
+ *
158
+ * The defensible cross-layer discrepancy is the **default fallback**: the
159
+ * shipped `ic()` helper in `did-compiler.ts` returns `'evolving'` for fields
160
+ * with no explicit classification, but the canonical taxonomy says novel
161
+ * fields default to `'core'`. This is filed as a Decision rather than
162
+ * inline-resolved because the DID-scoring domain may legitimately prefer
163
+ * a different default than the substrate-contract domain — operator
164
+ * routing required.
165
+ *
166
+ * Returns the list of discrepancies (may be empty). Callers are responsible
167
+ * for piping each into `cli-decisions add --scope ... --option ...`.
168
+ */
169
+ export declare function auditLayer1DeterministicClassifications(options?: {
170
+ readFile?: (path: string) => string;
171
+ }): IdentityClassDiscrepancy[];
172
+ /** Thrown by {@link assertTightenedCap} when a child loosens a numeric cap. */
173
+ export declare class IdentityClassError extends Error {
174
+ constructor(message: string);
175
+ }
176
+ //# sourceMappingURL=identity-class.d.ts.map