@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.
- package/dist/__test-helpers/git-env.d.ts +40 -0
- package/dist/__test-helpers/git-env.js +73 -0
- package/dist/adapters.d.ts +42 -3
- package/dist/adapters.js +133 -3
- package/dist/admission-composite.d.ts +134 -1
- package/dist/admission-composite.js +170 -6
- package/dist/admission-enrichment.d.ts +12 -3
- package/dist/admission-enrichment.js +71 -10
- package/dist/admission-hc.d.ts +20 -1
- package/dist/admission-hc.js +12 -1
- package/dist/admission-score.d.ts +44 -0
- package/dist/admission-score.js +11 -2
- package/dist/backlog-adapter.d.ts +45 -0
- package/dist/backlog-adapter.js +145 -1
- package/dist/calibration.d.ts +81 -0
- package/dist/calibration.js +76 -0
- package/dist/cli/commands/git-remote.d.ts +25 -0
- package/dist/cli/commands/git-remote.js +80 -1
- package/dist/cli/commands/init-features.d.ts +507 -0
- package/dist/cli/commands/init-features.js +1100 -0
- package/dist/cli/commands/init-templates.d.ts +301 -0
- package/dist/cli/commands/init-templates.js +1341 -0
- package/dist/cli/commands/init.d.ts +67 -0
- package/dist/cli/commands/init.js +301 -2
- package/dist/cli/commands/run.js +9 -1
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.js +42 -8
- package/dist/compliance/composer.d.ts +79 -0
- package/dist/compliance/composer.js +258 -0
- package/dist/compliance/errors.d.ts +64 -0
- package/dist/compliance/errors.js +85 -0
- package/dist/compliance/loader.d.ts +52 -0
- package/dist/compliance/loader.js +124 -0
- package/dist/compliance/types.d.ts +184 -0
- package/dist/compliance/types.js +41 -0
- package/dist/compliance-clearance.d.ts +269 -0
- package/dist/compliance-clearance.js +269 -0
- package/dist/config.js +17 -0
- package/dist/cost-tracker.d.ts +22 -0
- package/dist/cost-tracker.js +41 -0
- package/dist/database/adapters/external.js +5 -1
- package/dist/design-authority.d.ts +13 -0
- package/dist/design-authority.js +13 -0
- package/dist/embedding/adapters/openai-text-embedding-3-small.d.ts +71 -0
- package/dist/embedding/adapters/openai-text-embedding-3-small.js +190 -0
- package/dist/embedding/consumers/tessellation-drift.d.ts +74 -0
- package/dist/embedding/consumers/tessellation-drift.js +76 -0
- package/dist/embedding/cross-provider.d.ts +78 -0
- package/dist/embedding/cross-provider.js +75 -0
- package/dist/embedding/deprecation.d.ts +151 -0
- package/dist/embedding/deprecation.js +229 -0
- package/dist/embedding/errors.d.ts +90 -0
- package/dist/embedding/errors.js +150 -0
- package/dist/embedding/index.d.ts +29 -0
- package/dist/embedding/index.js +24 -0
- package/dist/embedding/pipeline-load.d.ts +146 -0
- package/dist/embedding/pipeline-load.js +178 -0
- package/dist/embedding/registry.d.ts +45 -0
- package/dist/embedding/registry.js +61 -0
- package/dist/embedding/stale-vector.d.ts +110 -0
- package/dist/embedding/stale-vector.js +92 -0
- package/dist/embedding/storage/index.d.ts +51 -0
- package/dist/embedding/storage/index.js +43 -0
- package/dist/embedding/storage/jsonl-backend.d.ts +150 -0
- package/dist/embedding/storage/jsonl-backend.js +332 -0
- package/dist/embedding/storage/types.d.ts +135 -0
- package/dist/embedding/storage/types.js +13 -0
- package/dist/embedding/types.d.ts +180 -0
- package/dist/embedding/types.js +10 -0
- package/dist/execute.d.ts +29 -2
- package/dist/execute.js +179 -35
- package/dist/index.d.ts +14 -2
- package/dist/index.js +19 -2
- package/dist/journey/inheritance-validator.d.ts +396 -0
- package/dist/journey/inheritance-validator.js +370 -0
- package/dist/journey/state-id-drift-rule.d.ts +137 -0
- package/dist/journey/state-id-drift-rule.js +245 -0
- package/dist/journey-sa2-router.d.ts +395 -0
- package/dist/journey-sa2-router.js +308 -0
- package/dist/models/classifier.d.ts +3 -1
- package/dist/models/classifier.js +61 -5
- package/dist/pillar-breakdown.d.ts +23 -1
- package/dist/pillar-breakdown.js +9 -0
- package/dist/runners/review-agent.js +6 -2
- package/dist/runners/runner-registry.d.ts +36 -0
- package/dist/runners/runner-registry.js +90 -0
- package/dist/runtime/attestations.d.ts +663 -11
- package/dist/runtime/attestations.js +971 -24
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/parallelism-flag.d.ts +21 -3
- package/dist/runtime/parallelism-flag.js +27 -6
- package/dist/runtime/port-allocator.d.ts +11 -0
- package/dist/runtime/port-allocator.js +30 -0
- package/dist/sa-scoring/layer3-llm.js +6 -1
- package/dist/sa-scoring/revision-proposal-config.d.ts +178 -0
- package/dist/sa-scoring/revision-proposal-config.js +198 -0
- package/dist/sa-scoring/revision-proposal.d.ts +285 -0
- package/dist/sa-scoring/revision-proposal.js +417 -0
- package/dist/signal-ingestion/adapters/community-thread.d.ts +43 -0
- package/dist/signal-ingestion/adapters/community-thread.js +55 -0
- package/dist/signal-ingestion/adapters/in-app-feedback.d.ts +67 -0
- package/dist/signal-ingestion/adapters/in-app-feedback.js +51 -0
- package/dist/signal-ingestion/adapters/manual.d.ts +78 -0
- package/dist/signal-ingestion/adapters/manual.js +112 -0
- package/dist/signal-ingestion/adapters/support-ticket.d.ts +47 -0
- package/dist/signal-ingestion/adapters/support-ticket.js +51 -0
- package/dist/signal-ingestion/classifier.d.ts +205 -0
- package/dist/signal-ingestion/classifier.js +494 -0
- package/dist/signal-ingestion/clustering-types.d.ts +36 -0
- package/dist/signal-ingestion/clustering-types.js +14 -0
- package/dist/signal-ingestion/clustering.d.ts +200 -0
- package/dist/signal-ingestion/clustering.js +413 -0
- package/dist/signal-ingestion/config.d.ts +351 -0
- package/dist/signal-ingestion/config.js +587 -0
- package/dist/signal-ingestion/d1.d.ts +252 -0
- package/dist/signal-ingestion/d1.js +235 -0
- package/dist/signal-ingestion/errors.d.ts +73 -0
- package/dist/signal-ingestion/errors.js +108 -0
- package/dist/signal-ingestion/governance-events.d.ts +181 -0
- package/dist/signal-ingestion/governance-events.js +189 -0
- package/dist/signal-ingestion/index.d.ts +35 -0
- package/dist/signal-ingestion/index.js +53 -0
- package/dist/signal-ingestion/manual-share-metric.d.ts +93 -0
- package/dist/signal-ingestion/manual-share-metric.js +106 -0
- package/dist/signal-ingestion/registry.d.ts +40 -0
- package/dist/signal-ingestion/registry.js +137 -0
- package/dist/signal-ingestion/residency.d.ts +227 -0
- package/dist/signal-ingestion/residency.js +238 -0
- package/dist/signal-ingestion/significance.d.ts +554 -0
- package/dist/signal-ingestion/significance.js +555 -0
- package/dist/signal-ingestion/types.d.ts +191 -0
- package/dist/signal-ingestion/types.js +8 -0
- package/dist/substrate/drift-composition.d.ts +270 -0
- package/dist/substrate/drift-composition.js +306 -0
- package/dist/substrate/drift-tui-surface.d.ts +61 -0
- package/dist/substrate/drift-tui-surface.js +102 -0
- package/dist/substrate/identity-class.d.ts +176 -0
- package/dist/substrate/identity-class.js +201 -0
- package/dist/tessellation/cross-soul-provenance-rule.d.ts +133 -0
- package/dist/tessellation/cross-soul-provenance-rule.js +171 -0
- package/dist/tessellation/inter-soul-embedding-distance-rule.d.ts +61 -0
- package/dist/tessellation/inter-soul-embedding-distance-rule.js +67 -0
- package/dist/tessellation/rule-registry.d.ts +269 -0
- package/dist/tessellation/rule-registry.js +92 -0
- package/dist/tessellation/soul-slug-ast-scan-rule.d.ts +90 -0
- package/dist/tessellation/soul-slug-ast-scan-rule.js +158 -0
- package/dist/tessellation-admission.d.ts +162 -0
- package/dist/tessellation-admission.js +146 -0
- package/dist/tessellation-drift.d.ts +246 -0
- package/dist/tessellation-drift.js +250 -0
- package/dist/validate-config.js +13 -0
- package/dist/validate-issue.js +2 -2
- package/dist/variant/cardinality-activation.d.ts +126 -0
- package/dist/variant/cardinality-activation.js +101 -0
- package/dist/variant/deprecation-lifecycle.d.ts +184 -0
- package/dist/variant/deprecation-lifecycle.js +208 -0
- package/dist/variant/drift-extension.d.ts +136 -0
- package/dist/variant/drift-extension.js +164 -0
- package/dist/variant/engineering-review.d.ts +185 -0
- package/dist/variant/engineering-review.js +142 -0
- package/dist/variant/index.d.ts +32 -0
- package/dist/variant/index.js +32 -0
- package/dist/variant/inheritance-validator.d.ts +165 -0
- package/dist/variant/inheritance-validator.js +139 -0
- package/dist/variant/internal-adopter/index.d.ts +11 -0
- package/dist/variant/internal-adopter/index.js +10 -0
- package/dist/variant/internal-adopter/products.d.ts +156 -0
- package/dist/variant/internal-adopter/products.js +366 -0
- package/dist/variant-admission.d.ts +316 -0
- package/dist/variant-admission.js +247 -0
- package/package.json +11 -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
|