@ai-sdlc/orchestrator 0.10.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 (156) 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/backlog-adapter.d.ts +45 -0
  10. package/dist/backlog-adapter.js +145 -1
  11. package/dist/calibration.d.ts +81 -0
  12. package/dist/calibration.js +76 -0
  13. package/dist/cli/commands/init-features.d.ts +301 -2
  14. package/dist/cli/commands/init-features.js +634 -7
  15. package/dist/cli/commands/init-templates.d.ts +198 -1
  16. package/dist/cli/commands/init-templates.js +943 -1
  17. package/dist/cli/commands/init.d.ts +45 -0
  18. package/dist/cli/commands/init.js +147 -5
  19. package/dist/cli/commands/run.js +9 -1
  20. package/dist/cli/index.d.ts +11 -0
  21. package/dist/cli/index.js +42 -8
  22. package/dist/compliance/composer.d.ts +79 -0
  23. package/dist/compliance/composer.js +258 -0
  24. package/dist/compliance/errors.d.ts +64 -0
  25. package/dist/compliance/errors.js +85 -0
  26. package/dist/compliance/loader.d.ts +52 -0
  27. package/dist/compliance/loader.js +124 -0
  28. package/dist/compliance/types.d.ts +184 -0
  29. package/dist/compliance/types.js +41 -0
  30. package/dist/compliance-clearance.d.ts +269 -0
  31. package/dist/compliance-clearance.js +269 -0
  32. package/dist/config.js +17 -0
  33. package/dist/cost-tracker.d.ts +22 -0
  34. package/dist/cost-tracker.js +41 -0
  35. package/dist/database/adapters/external.js +5 -1
  36. package/dist/embedding/adapters/openai-text-embedding-3-small.d.ts +71 -0
  37. package/dist/embedding/adapters/openai-text-embedding-3-small.js +190 -0
  38. package/dist/embedding/consumers/tessellation-drift.d.ts +74 -0
  39. package/dist/embedding/consumers/tessellation-drift.js +76 -0
  40. package/dist/embedding/cross-provider.d.ts +78 -0
  41. package/dist/embedding/cross-provider.js +75 -0
  42. package/dist/embedding/deprecation.d.ts +151 -0
  43. package/dist/embedding/deprecation.js +229 -0
  44. package/dist/embedding/errors.d.ts +90 -0
  45. package/dist/embedding/errors.js +150 -0
  46. package/dist/embedding/index.d.ts +29 -0
  47. package/dist/embedding/index.js +24 -0
  48. package/dist/embedding/pipeline-load.d.ts +146 -0
  49. package/dist/embedding/pipeline-load.js +178 -0
  50. package/dist/embedding/registry.d.ts +45 -0
  51. package/dist/embedding/registry.js +61 -0
  52. package/dist/embedding/stale-vector.d.ts +110 -0
  53. package/dist/embedding/stale-vector.js +92 -0
  54. package/dist/embedding/storage/index.d.ts +51 -0
  55. package/dist/embedding/storage/index.js +43 -0
  56. package/dist/embedding/storage/jsonl-backend.d.ts +150 -0
  57. package/dist/embedding/storage/jsonl-backend.js +332 -0
  58. package/dist/embedding/storage/types.d.ts +135 -0
  59. package/dist/embedding/storage/types.js +13 -0
  60. package/dist/embedding/types.d.ts +180 -0
  61. package/dist/embedding/types.js +10 -0
  62. package/dist/execute.d.ts +29 -2
  63. package/dist/execute.js +171 -30
  64. package/dist/index.d.ts +14 -2
  65. package/dist/index.js +19 -2
  66. package/dist/journey/inheritance-validator.d.ts +396 -0
  67. package/dist/journey/inheritance-validator.js +370 -0
  68. package/dist/journey/state-id-drift-rule.d.ts +137 -0
  69. package/dist/journey/state-id-drift-rule.js +245 -0
  70. package/dist/journey-sa2-router.d.ts +395 -0
  71. package/dist/journey-sa2-router.js +308 -0
  72. package/dist/runners/review-agent.js +6 -2
  73. package/dist/runners/runner-registry.d.ts +36 -0
  74. package/dist/runners/runner-registry.js +90 -0
  75. package/dist/runtime/attestations.d.ts +173 -13
  76. package/dist/runtime/attestations.js +252 -40
  77. package/dist/runtime/index.d.ts +1 -1
  78. package/dist/runtime/index.js +1 -1
  79. package/dist/sa-scoring/layer3-llm.js +6 -1
  80. package/dist/sa-scoring/revision-proposal-config.d.ts +178 -0
  81. package/dist/sa-scoring/revision-proposal-config.js +198 -0
  82. package/dist/sa-scoring/revision-proposal.d.ts +285 -0
  83. package/dist/sa-scoring/revision-proposal.js +417 -0
  84. package/dist/signal-ingestion/adapters/community-thread.d.ts +43 -0
  85. package/dist/signal-ingestion/adapters/community-thread.js +55 -0
  86. package/dist/signal-ingestion/adapters/in-app-feedback.d.ts +67 -0
  87. package/dist/signal-ingestion/adapters/in-app-feedback.js +51 -0
  88. package/dist/signal-ingestion/adapters/manual.d.ts +78 -0
  89. package/dist/signal-ingestion/adapters/manual.js +112 -0
  90. package/dist/signal-ingestion/adapters/support-ticket.d.ts +47 -0
  91. package/dist/signal-ingestion/adapters/support-ticket.js +51 -0
  92. package/dist/signal-ingestion/classifier.d.ts +205 -0
  93. package/dist/signal-ingestion/classifier.js +494 -0
  94. package/dist/signal-ingestion/clustering-types.d.ts +36 -0
  95. package/dist/signal-ingestion/clustering-types.js +14 -0
  96. package/dist/signal-ingestion/clustering.d.ts +200 -0
  97. package/dist/signal-ingestion/clustering.js +413 -0
  98. package/dist/signal-ingestion/config.d.ts +351 -0
  99. package/dist/signal-ingestion/config.js +587 -0
  100. package/dist/signal-ingestion/d1.d.ts +252 -0
  101. package/dist/signal-ingestion/d1.js +235 -0
  102. package/dist/signal-ingestion/errors.d.ts +73 -0
  103. package/dist/signal-ingestion/errors.js +108 -0
  104. package/dist/signal-ingestion/governance-events.d.ts +181 -0
  105. package/dist/signal-ingestion/governance-events.js +189 -0
  106. package/dist/signal-ingestion/index.d.ts +35 -0
  107. package/dist/signal-ingestion/index.js +53 -0
  108. package/dist/signal-ingestion/manual-share-metric.d.ts +93 -0
  109. package/dist/signal-ingestion/manual-share-metric.js +106 -0
  110. package/dist/signal-ingestion/registry.d.ts +40 -0
  111. package/dist/signal-ingestion/registry.js +137 -0
  112. package/dist/signal-ingestion/residency.d.ts +227 -0
  113. package/dist/signal-ingestion/residency.js +238 -0
  114. package/dist/signal-ingestion/significance.d.ts +554 -0
  115. package/dist/signal-ingestion/significance.js +555 -0
  116. package/dist/signal-ingestion/types.d.ts +191 -0
  117. package/dist/signal-ingestion/types.js +8 -0
  118. package/dist/substrate/drift-composition.d.ts +270 -0
  119. package/dist/substrate/drift-composition.js +306 -0
  120. package/dist/substrate/drift-tui-surface.d.ts +61 -0
  121. package/dist/substrate/drift-tui-surface.js +102 -0
  122. package/dist/substrate/identity-class.d.ts +176 -0
  123. package/dist/substrate/identity-class.js +201 -0
  124. package/dist/tessellation/cross-soul-provenance-rule.d.ts +133 -0
  125. package/dist/tessellation/cross-soul-provenance-rule.js +171 -0
  126. package/dist/tessellation/inter-soul-embedding-distance-rule.d.ts +61 -0
  127. package/dist/tessellation/inter-soul-embedding-distance-rule.js +67 -0
  128. package/dist/tessellation/rule-registry.d.ts +269 -0
  129. package/dist/tessellation/rule-registry.js +92 -0
  130. package/dist/tessellation/soul-slug-ast-scan-rule.d.ts +90 -0
  131. package/dist/tessellation/soul-slug-ast-scan-rule.js +158 -0
  132. package/dist/tessellation-admission.d.ts +162 -0
  133. package/dist/tessellation-admission.js +146 -0
  134. package/dist/tessellation-drift.d.ts +246 -0
  135. package/dist/tessellation-drift.js +250 -0
  136. package/dist/validate-config.js +13 -0
  137. package/dist/validate-issue.js +2 -2
  138. package/dist/variant/cardinality-activation.d.ts +126 -0
  139. package/dist/variant/cardinality-activation.js +101 -0
  140. package/dist/variant/deprecation-lifecycle.d.ts +184 -0
  141. package/dist/variant/deprecation-lifecycle.js +208 -0
  142. package/dist/variant/drift-extension.d.ts +136 -0
  143. package/dist/variant/drift-extension.js +164 -0
  144. package/dist/variant/engineering-review.d.ts +185 -0
  145. package/dist/variant/engineering-review.js +142 -0
  146. package/dist/variant/index.d.ts +32 -0
  147. package/dist/variant/index.js +32 -0
  148. package/dist/variant/inheritance-validator.d.ts +165 -0
  149. package/dist/variant/inheritance-validator.js +139 -0
  150. package/dist/variant/internal-adopter/index.d.ts +11 -0
  151. package/dist/variant/internal-adopter/index.js +10 -0
  152. package/dist/variant/internal-adopter/products.d.ts +156 -0
  153. package/dist/variant/internal-adopter/products.js +366 -0
  154. package/dist/variant-admission.d.ts +316 -0
  155. package/dist/variant-admission.js +247 -0
  156. package/package.json +10 -8
@@ -0,0 +1,370 @@
1
+ /**
2
+ * RFC-0018 Phase 1 — Journey Inheritance Validator.
3
+ *
4
+ * Implements RFC-0018 §5.3 bounded-inheritance enforcement for journey
5
+ * declarations on Soul DIDs and Variants. When a violation is detected the
6
+ * validator emits a `JourneyInheritanceViolation` event (RFC-0008 §C5).
7
+ *
8
+ * Covers the following AC items (AISDLC-465):
9
+ * AC #5: JourneyInheritanceViolation event emitted for all 5 violation classes.
10
+ * AC #7: Journey count thresholds emit Decisions (journey-count-soft-warning at >=10;
11
+ * journey-count-hard-limit-exceeded at >=50).
12
+ * AC #8: State count thresholds emit Decisions (journey-state-count-soft-warning at >=12
13
+ * with v1-workaround message; journey-state-count-hard-limit-exceeded at >=100).
14
+ * AC #9: Nested journeys[] rejected at schema validation (schema-enforced flat, OQ-3).
15
+ * AC #10: Decision: journey-sub-flow-activation-request Stage A counter wired.
16
+ *
17
+ * ### Bounded inheritance (§5.3)
18
+ *
19
+ * Journeys MUST NOT attempt to override the following fields inherited from
20
+ * the parent Soul DID (or Variant when scope=variant):
21
+ *
22
+ * 1. `complianceRegimes` — per-soul (locked at soul/variant level)
23
+ * 2. `targetAudience` — inherits from soul or variant
24
+ * 3. `substrateInvariants` — locked at soul level
25
+ * 4. `complianceFloor` — when scope=variant, MUST be 'inherit' (never a custom value)
26
+ * 5. `wcagLevel` lowering — journeys MAY raise WCAG above parent but NEVER lower it
27
+ *
28
+ * ### Count + state limits (OQ-1, OQ-2)
29
+ *
30
+ * Per-org configurable via `.ai-sdlc/journey-config.yaml`:
31
+ * journey.limits.softWarnAt (default 10) / journey.limits.hardLimit (default 50)
32
+ * journey.stateLimits.softWarnAt (default 12) / journey.stateLimits.hardLimit (default 100)
33
+ *
34
+ * ### Nested-journey rejection (OQ-3)
35
+ *
36
+ * Schema-enforced flat: a journey declaration MUST NOT contain a `journeys[]`
37
+ * field. Emits `NestedJourneyRejected` (blocking).
38
+ *
39
+ * ### Sub-flow activation counter (OQ-3)
40
+ *
41
+ * Tracks distinct adopter requests for journey-sub-flow activation. When a
42
+ * journey declaration contains a nested `journeys[]` field, the validator:
43
+ * 1. Emits `NestedJourneyRejected` (blocking).
44
+ * 2. Records the request in the sub-flow activation counter.
45
+ * 3. When distinctAdopterCount >= threshold (default 2), marks
46
+ * `promotedToOperatorReview: true` in the counter result, signalling
47
+ * that a follow-on RFC discussion should be surfaced.
48
+ *
49
+ * @see spec/rfcs/RFC-0018-in-soul-journey-pattern.md §5.3 + §10.1
50
+ * @see orchestrator/src/variant/inheritance-validator.ts — parallel RFC-0017 validator
51
+ */
52
+ // ── Inherited (locked) field names per RFC-0018 §5.3 ──────────────────────────
53
+ /**
54
+ * Field names that are INHERITED from the parent Soul DID and cannot be
55
+ * overridden by any journey declaration. Attempting to declare these on a
56
+ * journey triggers a `JourneyInheritanceViolation`.
57
+ *
58
+ * Note: `complianceFloor` is validated separately (it must be 'inherit' when
59
+ * scope=variant, not merely absent) — see `validateComplianceFloor`.
60
+ */
61
+ export const JOURNEY_INHERITED_LOCKED_FIELDS = [
62
+ 'complianceRegimes',
63
+ 'targetAudience',
64
+ 'substrateInvariants',
65
+ ];
66
+ // ── WCAG level ordering ────────────────────────────────────────────────────────
67
+ /**
68
+ * Numeric ordering for WCAG levels. Higher = stricter.
69
+ * Used to detect lowering of WCAG level below parent.
70
+ */
71
+ export const WCAG_LEVEL_ORDER = {
72
+ A: 1,
73
+ AA: 2,
74
+ AAA: 3,
75
+ };
76
+ // ── Default constants ─────────────────────────────────────────────────────────
77
+ /** OQ-1 default: soft warn at 10 journeys (Miller 7±2 + industry advisory). */
78
+ export const DEFAULT_JOURNEY_SOFT_WARN_AT = 10;
79
+ /** OQ-1 default: hard limit at 50 journeys (Salesforce-style enterprise ceiling). */
80
+ export const DEFAULT_JOURNEY_HARD_LIMIT = 50;
81
+ /** OQ-2 default: soft warn at 12 states (Miller 7±2 + XState advisory + IEEE readability). */
82
+ export const DEFAULT_STATE_SOFT_WARN_AT = 12;
83
+ /**
84
+ * OQ-2 default: hard limit at 100 states (sanity guard, NOT architectural constraint).
85
+ * Regulatory-submission journeys with 25-40 states are legitimate.
86
+ */
87
+ export const DEFAULT_STATE_HARD_LIMIT = 100;
88
+ /**
89
+ * OQ-2 default v1 workaround message: operator-actionable guidance when
90
+ * a journey's state count reaches the soft-warn threshold.
91
+ */
92
+ export const DEFAULT_STATE_SOFT_WARN_MESSAGE = 'Consider splitting into multiple top-level journeys with handoff terminal states ' +
93
+ '(v1 workaround) OR await OQ-3 sub-journey activation (v2)';
94
+ /** OQ-3 default: auto-promote sub-flow activation at 2 distinct adopter requests. */
95
+ export const DEFAULT_SUB_FLOW_ACTIVATION_THRESHOLD = 2;
96
+ // ── Validator ─────────────────────────────────────────────────────────────────
97
+ /**
98
+ * Validate a Soul DID's (or Variant's) `journeys[]` declarations against
99
+ * RFC-0018 §5.3 rules.
100
+ *
101
+ * Returns all emitted events. Callers check `event.blocking` to determine
102
+ * whether the Soul DID should be rejected. Caller's responsibility to write
103
+ * events to events.jsonl via the artifact layer.
104
+ *
105
+ * Validation rules (in order):
106
+ *
107
+ * 1. **Hard-limit check (OQ-1)** — if `journeys.length >= hardLimit`, emit
108
+ * `JourneyCountHardLimitExceeded` (blocking). Continue to check individual
109
+ * journeys for completeness of the error report.
110
+ *
111
+ * 2. **Soft-warn check (OQ-1)** — if `journeys.length >= softWarnAt` AND
112
+ * below hard limit, emit `JourneyCountSoftWarning` (non-blocking).
113
+ *
114
+ * 3. **Per-journey checks** — for each journey:
115
+ * a. Nested-journey rejection (OQ-3) — if journey has `journeys` key,
116
+ * emit `NestedJourneyRejected` (blocking).
117
+ * b. Inheritance violation check (§5.3) — for each locked field
118
+ * (`complianceRegimes`, `targetAudience`, `substrateInvariants`), if
119
+ * present on the journey emit `JourneyInheritanceViolation` (blocking).
120
+ * c. `complianceFloor` check — if scope=variant and complianceFloor is
121
+ * present but not 'inherit', emit `JourneyInheritanceViolation` (blocking).
122
+ * d. WCAG-level lowering check — if parentWcagLevel provided and journey's
123
+ * wcagLevel is LOWER than parent, emit `JourneyInheritanceViolation`
124
+ * (blocking).
125
+ * e. State count checks (OQ-2) — per-journey state count against
126
+ * stateLimits.hardLimit / stateLimits.softWarnAt.
127
+ */
128
+ export function validateJourneyDeclarations(options) {
129
+ const { soulId, journeys, limits, stateLimits, parentWcagLevel, now } = options;
130
+ const timestamp = now ?? new Date().toISOString();
131
+ const softWarnAt = limits?.softWarnAt ?? DEFAULT_JOURNEY_SOFT_WARN_AT;
132
+ const hardLimit = limits?.hardLimit ?? DEFAULT_JOURNEY_HARD_LIMIT;
133
+ const stateSoftWarnAt = stateLimits?.softWarnAt ?? DEFAULT_STATE_SOFT_WARN_AT;
134
+ const stateHardLimit = stateLimits?.hardLimit ?? DEFAULT_STATE_HARD_LIMIT;
135
+ const stateWarnMessage = stateLimits?.softWarnMessage ?? DEFAULT_STATE_SOFT_WARN_MESSAGE;
136
+ const events = [];
137
+ const count = journeys.length;
138
+ // Rule 1 — Journey count hard limit (OQ-1)
139
+ if (count >= hardLimit) {
140
+ events.push({
141
+ kind: 'JourneyCountHardLimitExceeded',
142
+ soulId,
143
+ journeyCount: count,
144
+ limit: hardLimit,
145
+ message: `Soul '${soulId}' declares ${count} journey(s), reaching or exceeding the hard limit of ` +
146
+ `${hardLimit}. Declaration rejected. Consider re-architecting as multiple top-level ` +
147
+ `journeys with handoff terminal states or splitting across soul variants ` +
148
+ `(RFC-0018 §5.5 boundary guidance). Decision: journey-count-hard-limit-exceeded.`,
149
+ blocking: true,
150
+ timestamp,
151
+ });
152
+ }
153
+ else if (count >= softWarnAt) {
154
+ // Rule 2 — Journey count soft warn (OQ-1, non-blocking)
155
+ events.push({
156
+ kind: 'JourneyCountSoftWarning',
157
+ soulId,
158
+ journeyCount: count,
159
+ threshold: softWarnAt,
160
+ message: `Soul '${soulId}' declares ${count} journey(s), at or above the soft-warn threshold of ` +
161
+ `${softWarnAt}. Non-blocking review recommended. ` +
162
+ `Decision: journey-count-soft-warning.`,
163
+ blocking: false,
164
+ timestamp,
165
+ });
166
+ }
167
+ // Rule 3 — Per-journey checks
168
+ for (const journey of journeys) {
169
+ const journeyId = String(journey.id ?? '<unknown>');
170
+ // Rule 3a — Nested journeys rejection (OQ-3)
171
+ if (Object.prototype.hasOwnProperty.call(journey, 'journeys')) {
172
+ events.push({
173
+ kind: 'NestedJourneyRejected',
174
+ soulId,
175
+ journeyId,
176
+ message: `Journey '${journeyId}' on soul '${soulId}' declares a nested 'journeys[]' field. ` +
177
+ `RFC-0018 OQ-3 resolution mandates schema-enforced flat: journeys cannot contain ` +
178
+ `sub-journeys in v1. Remove the nested 'journeys' field. ` +
179
+ `v1 workaround: model sub-flows as multiple top-level journeys with handoff ` +
180
+ `terminal states using shared userId/sessionId correlation. ` +
181
+ `Future activation: Decision: journey-sub-flow-activation-request ` +
182
+ `(auto-promote at >=2 distinct adopter requests).`,
183
+ blocking: true,
184
+ timestamp,
185
+ });
186
+ }
187
+ // Rule 3b — Inheritance violation check for locked fields (§5.3)
188
+ for (const field of JOURNEY_INHERITED_LOCKED_FIELDS) {
189
+ if (Object.prototype.hasOwnProperty.call(journey, field)) {
190
+ events.push({
191
+ kind: 'JourneyInheritanceViolation',
192
+ soulId,
193
+ journeyId,
194
+ violationClass: field,
195
+ message: `Journey '${journeyId}' on soul '${soulId}' attempts to override '${field}', ` +
196
+ `which is inherited-and-locked from the parent Soul DID (RFC-0018 §5.3 bounded ` +
197
+ `inheritance table). Remove '${field}' from the journey declaration.`,
198
+ blocking: true,
199
+ timestamp,
200
+ });
201
+ }
202
+ }
203
+ // Rule 3c — complianceFloor check (§5.3)
204
+ // When scope=variant, complianceFloor MUST be 'inherit'.
205
+ // Presence of complianceFloor with any value other than 'inherit' is a violation.
206
+ const isVariantScoped = typeof journey.scope === 'string' && journey.scope.startsWith('variant:');
207
+ if (isVariantScoped && Object.prototype.hasOwnProperty.call(journey, 'complianceFloor')) {
208
+ if (journey.complianceFloor !== 'inherit') {
209
+ events.push({
210
+ kind: 'JourneyInheritanceViolation',
211
+ soulId,
212
+ journeyId,
213
+ violationClass: 'complianceFloor',
214
+ message: `Journey '${journeyId}' on soul '${soulId}' has scope '${journey.scope}' ` +
215
+ `(variant-scoped) but declares complianceFloor='${String(journey.complianceFloor)}'. ` +
216
+ `Variant-scoped journeys MUST set complianceFloor='inherit' (RFC-0018 §5.3 — ` +
217
+ `journeys cannot diverge from parent compliance regime). ` +
218
+ `Set complianceFloor: inherit.`,
219
+ blocking: true,
220
+ timestamp,
221
+ });
222
+ }
223
+ }
224
+ // Rule 3d — WCAG level lowering (§5.3)
225
+ // Journeys MAY raise WCAG above parent; MUST NOT lower it.
226
+ if (parentWcagLevel && journey.accessibility?.wcagLevel) {
227
+ const parentOrder = WCAG_LEVEL_ORDER[parentWcagLevel] ?? 0;
228
+ const journeyOrder = WCAG_LEVEL_ORDER[journey.accessibility.wcagLevel] ?? 0;
229
+ if (journeyOrder > 0 && journeyOrder < parentOrder) {
230
+ events.push({
231
+ kind: 'JourneyInheritanceViolation',
232
+ soulId,
233
+ journeyId,
234
+ violationClass: 'wcagLevel-lowered-below-parent',
235
+ message: `Journey '${journeyId}' on soul '${soulId}' declares wcagLevel='${journey.accessibility.wcagLevel}', ` +
236
+ `which is LOWER than the parent's wcagLevel='${parentWcagLevel}'. ` +
237
+ `RFC-0018 §5.3: journeys MAY raise WCAG above the parent but MUST NOT lower it. ` +
238
+ `Set wcagLevel to '${parentWcagLevel}' or higher (e.g. 'AAA' for regulatory journeys).`,
239
+ blocking: true,
240
+ timestamp,
241
+ });
242
+ }
243
+ }
244
+ // Rule 3e — State count checks (OQ-2)
245
+ const stateCount = Array.isArray(journey.states) ? journey.states.length : 0;
246
+ if (stateCount >= stateHardLimit) {
247
+ events.push({
248
+ kind: 'JourneyStateCountHardLimitExceeded',
249
+ soulId,
250
+ journeyId,
251
+ stateCount,
252
+ limit: stateHardLimit,
253
+ message: `Journey '${journeyId}' on soul '${soulId}' declares ${stateCount} state(s), ` +
254
+ `reaching or exceeding the sanity-guard hard limit of ${stateHardLimit}. ` +
255
+ `Declaration rejected. This limit guards against typos and runaway-loop declarations ` +
256
+ `(NOT an architectural constraint — regulatory journeys with 25-40 states are valid). ` +
257
+ `Decision: journey-state-count-hard-limit-exceeded.`,
258
+ blocking: true,
259
+ timestamp,
260
+ });
261
+ }
262
+ else if (stateCount >= stateSoftWarnAt) {
263
+ events.push({
264
+ kind: 'JourneyStateCountSoftWarning',
265
+ soulId,
266
+ journeyId,
267
+ stateCount,
268
+ threshold: stateSoftWarnAt,
269
+ message: `Journey '${journeyId}' on soul '${soulId}' declares ${stateCount} state(s), ` +
270
+ `at or above the soft-warn threshold of ${stateSoftWarnAt}. ` +
271
+ `Non-blocking review recommended. ` +
272
+ `Decision: journey-state-count-soft-warning.`,
273
+ v1WorkaroundMessage: stateWarnMessage,
274
+ blocking: false,
275
+ timestamp,
276
+ });
277
+ }
278
+ }
279
+ return events;
280
+ }
281
+ /**
282
+ * Convenience predicate: returns true when any event in the list is blocking.
283
+ * Use to decide whether to reject the Soul DID declaration.
284
+ */
285
+ export function hasBlockingJourneyViolations(events) {
286
+ return events.some((e) => e.blocking);
287
+ }
288
+ /**
289
+ * Track a stage-A counter of distinct adopter requests for journey sub-flow
290
+ * activation (RFC-0018 OQ-3 resolution, AISDLC-465 AC #10).
291
+ *
292
+ * v1 does NOT activate sub-flows — this is a pure counter. Its purpose is to
293
+ * accumulate demand signal until `threshold` distinct adopters have requested
294
+ * the capability, at which point `promotedToOperatorReview: true` signals that
295
+ * a follow-on RFC discussion should be opened.
296
+ *
297
+ * Adopter deduplication is based on `request.requestedBy`. Multiple requests
298
+ * from the same adopter count as one distinct request.
299
+ *
300
+ * All requests route through RFC-0035 G0 (non-blocking pipeline contract).
301
+ *
302
+ * ### Usage
303
+ *
304
+ * ```ts
305
+ * const result = trackSubFlowActivationRequests([
306
+ * { requestedBy: 'adopter-acme', journeyId: 'onboarding', soulId: 'spry-engage' },
307
+ * { requestedBy: 'adopter-beta' },
308
+ * ], { distinctAdopterRequestsThreshold: 2 });
309
+ *
310
+ * // result.distinctAdopterCount === 2
311
+ * // result.promotedToOperatorReview === true (threshold reached)
312
+ * ```
313
+ *
314
+ * @param requests Accumulated adopter activation requests (may include duplicates).
315
+ * @param config Per-org counter configuration (threshold defaults to 2).
316
+ */
317
+ export function trackSubFlowActivationRequests(requests, config = {}) {
318
+ const threshold = config.distinctAdopterRequestsThreshold ?? DEFAULT_SUB_FLOW_ACTIVATION_THRESHOLD;
319
+ // Deduplicate by requestedBy — one signal per distinct adopter.
320
+ const distinctAdopters = new Set();
321
+ for (const req of requests) {
322
+ if (req.requestedBy) {
323
+ distinctAdopters.add(req.requestedBy);
324
+ }
325
+ }
326
+ const distinctAdopterCount = distinctAdopters.size;
327
+ const promotedToOperatorReview = distinctAdopterCount >= threshold;
328
+ return {
329
+ decision: 'journey-sub-flow-activation-request',
330
+ distinctAdopterCount,
331
+ threshold,
332
+ promotedToOperatorReview,
333
+ adopters: Array.from(distinctAdopters),
334
+ recommendation: promotedToOperatorReview
335
+ ? `${distinctAdopterCount} distinct adopter(s) have requested journey sub-flow activation ` +
336
+ `(threshold: ${threshold}). Recommended action: file a follow-on RFC to formally ` +
337
+ `evaluate nested journeys with CEL-based composition rules. ` +
338
+ `v1 workaround: model sub-flows as multiple top-level journeys with handoff ` +
339
+ `terminal states using shared userId/sessionId correlation.`
340
+ : `${distinctAdopterCount} of ${threshold} required distinct adopter requests ` +
341
+ `collected for journey sub-flow activation. No operator action needed yet.`,
342
+ };
343
+ }
344
+ /**
345
+ * Parse a `targetedJourneys` URI entry into its component parts.
346
+ *
347
+ * @param raw Raw URI string from the work item's `targetedJourneys[]` field.
348
+ * @returns Parsed components or null if the URI is malformed.
349
+ */
350
+ export function parseTargetedJourneyRef(raw) {
351
+ if (!raw || typeof raw !== 'string')
352
+ return null;
353
+ // Validate against the JSON Schema pattern:
354
+ // ^[a-z][a-z0-9-]*/([a-z][a-z0-9-]*/)?[a-z][a-z0-9-]*$
355
+ const kebab = '[a-z][a-z0-9-]*';
356
+ const pattern = new RegExp(`^(${kebab})/(?:(${kebab})/)?(${kebab})$`);
357
+ const match = raw.match(pattern);
358
+ if (!match)
359
+ return null;
360
+ const soulId = match[1];
361
+ const middle = match[2]; // present only for variant-scoped form
362
+ const last = match[3];
363
+ if (middle) {
364
+ // Variant-scoped: soul-id/variant-id/journey-id
365
+ return { soulId, variantId: middle, journeyId: last };
366
+ }
367
+ // Soul-scoped: soul-id/journey-id
368
+ return { soulId, journeyId: last };
369
+ }
370
+ //# sourceMappingURL=inheritance-validator.js.map
@@ -0,0 +1,137 @@
1
+ /**
2
+ * RFC-0018 Phase 3 — JourneyStateIdDriftRule (RFC-0009 §13 Rule #4).
3
+ *
4
+ * Implements the 4th §13 drift-detection rule resolving RFC-0018 OQ-8 + OQ-10
5
+ * (2026-05-28, full rubric):
6
+ *
7
+ * OQ-8: "AST scan from v1, reusing RFC-0009 §13 Rule #1 infrastructure."
8
+ * The existing AST scan engine ALREADY EXISTS for soul-slug leakage
9
+ * detection — adding journey state-ID detection is extending the
10
+ * engine with one additional rule, not building from scratch.
11
+ *
12
+ * OQ-10: "4th rule in the same §13 engine + concrete registration mechanism
13
+ * spec'd: Tessellation§13RuleRegistry.register(rule)."
14
+ *
15
+ * ### Detection strategy
16
+ *
17
+ * The rule reuses the `scanSubstrateFile` textual-scan approach from the
18
+ * existing AST scan engine in `tessellation-drift.ts`. This is deliberately
19
+ * NOT a string match — it uses the same regex-based pattern matching that
20
+ * Rule #1 employs, consistent with the OQ-8 resolution:
21
+ *
22
+ * - Bare string literal: `'<state-id>'` or `"<state-id>"`
23
+ * - State-discriminating conditional: `state === '<state-id>'` / similar
24
+ *
25
+ * This matches the OQ-8 resolution that explicitly rejected the string-match
26
+ * path in favour of the existing AST scan infrastructure.
27
+ *
28
+ * ### Drift conditions
29
+ *
30
+ * Emits `Decision: journey-state-id-drift` when substrate code references:
31
+ * 1. A state ID that is NOT declared in any active journey.
32
+ * 2. A state ID from a journey that has been removed (lifecycle = 'removed').
33
+ *
34
+ * ### Composition with RFC-0028 OQ-7.2
35
+ *
36
+ * Structural drift (this rule at CI authoring time) slots into the structural
37
+ * side of the OQ-7.2 pairing:
38
+ * - `severity === 'high'` → BLOCKS PR via Decision severity HIGH.
39
+ * - `severity === 'medium'` (default) → SURFACES non-blocking via RFC-0035
40
+ * G0 catalog route for operator batch review.
41
+ *
42
+ * Default severity is `'medium'` per RFC-0018 §10.1 (per-org configurable
43
+ * via `journey-config.yaml driftDetection.severityOverride`).
44
+ *
45
+ * @see spec/rfcs/RFC-0018-in-soul-journey-pattern.md §6.2 + §10.1 OQ-8 + OQ-10
46
+ * @see spec/rfcs/RFC-0009-tessellated-design-intent-documents.md §13
47
+ * @see spec/rfcs/RFC-0028-engineering-axis-substrate-enforcement.md OQ-7.2
48
+ * @see orchestrator/src/tessellation-drift.ts (source of the AST-scan engine reused here)
49
+ */
50
+ import type { TessellationRule, DriftEvent, DriftSeverity, RuleScanTarget } from '../tessellation/rule-registry.js';
51
+ /**
52
+ * Discriminator for a single journey-state-id drift finding.
53
+ *
54
+ * - `'undeclared-state-id'` — state ID referenced in substrate but not
55
+ * declared in any active journey.
56
+ * - `'removed-journey-state-id'` — state ID referenced in substrate from
57
+ * a journey that has been removed (lifecycle = 'removed').
58
+ */
59
+ export type JourneyStateIdFindingKind = 'undeclared-state-id' | 'removed-journey-state-id';
60
+ /**
61
+ * A single journey-state-id drift finding from the AST scan.
62
+ */
63
+ export interface JourneyStateIdFinding {
64
+ /** What kind of drift was detected. */
65
+ kind: JourneyStateIdFindingKind;
66
+ /** The state ID that was found in substrate code. */
67
+ stateId: string;
68
+ /** The journey ID the state ID came from (for removed-journey findings). */
69
+ journeyId?: string;
70
+ /** The soul ID the journey belongs to (for context). */
71
+ soulId?: string;
72
+ /** Path of the substrate file containing the reference. */
73
+ filePath: string;
74
+ /** 1-based line number in `filePath`. */
75
+ line: number;
76
+ /**
77
+ * Scan pattern that triggered the finding:
78
+ * - `'string-literal'` — bare `'<state-id>'` in substrate code.
79
+ * - `'state-conditional'` — `state === '<state-id>'` / similar branching.
80
+ */
81
+ pattern: 'string-literal' | 'state-conditional';
82
+ /** The raw matching line (trimmed, max 200 chars) for operator inspection. */
83
+ excerpt: string;
84
+ }
85
+ /** Structured details payload for journey-state-id-drift events. */
86
+ export interface JourneyStateIdDriftDetails {
87
+ rule: 'journey-state-id-drift';
88
+ findings: JourneyStateIdFinding[];
89
+ }
90
+ /**
91
+ * Per-org / per-soul drift detection configuration for JourneyStateIdDriftRule.
92
+ *
93
+ * Maps to the `driftDetection` block in `.ai-sdlc/journey-config.yaml`
94
+ * per RFC-0018 §10.1 OQ-8 resolution.
95
+ */
96
+ export interface JourneyStateIdDriftConfig {
97
+ /**
98
+ * Severity override for this rule.
99
+ *
100
+ * Per RFC-0028 OQ-7.2:
101
+ * - `'high'` → structural-blocking (BLOCKS PR at CI)
102
+ * - `'medium'` → non-blocking, RFC-0035 G0 catalog route (DEFAULT)
103
+ * - `'warning'` → informational
104
+ */
105
+ severityOverride?: DriftSeverity;
106
+ }
107
+ /**
108
+ * JourneyStateIdDriftRule — RFC-0009 §13 Rule #4.
109
+ *
110
+ * Scans substrate code for references to journey-state-id strings using
111
+ * the existing AST scan engine from Rule #1 (OQ-8 resolution: NOT string
112
+ * match). Emits `Decision: journey-state-id-drift` when:
113
+ *
114
+ * - Referenced state ID is not declared in any active journey, OR
115
+ * - The journey itself has been removed (cross-references journey lifecycle /
116
+ * deprecation tooling).
117
+ *
118
+ * Severity is per-org configurable (default `'medium'`).
119
+ *
120
+ * ### Registration
121
+ *
122
+ * ```ts
123
+ * const registry = createTessellation13Registry();
124
+ * registry.register(new JourneyStateIdDriftRule());
125
+ * // With config override:
126
+ * registry.register(new JourneyStateIdDriftRule({ severityOverride: 'high' }));
127
+ * ```
128
+ */
129
+ export declare class JourneyStateIdDriftRule implements TessellationRule {
130
+ readonly name = "journey-state-id-drift";
131
+ readonly description = "Scans substrate code for references to journey state IDs that are not declared in any active journey or belong to a removed journey (RFC-0018 Phase 3, OQ-8 resolution)";
132
+ readonly severity: DriftSeverity;
133
+ private readonly config;
134
+ constructor(config?: JourneyStateIdDriftConfig);
135
+ scan(target: RuleScanTarget): DriftEvent[];
136
+ }
137
+ //# sourceMappingURL=state-id-drift-rule.d.ts.map