@ai-sdlc/orchestrator 0.10.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters.d.ts +42 -3
- package/dist/adapters.js +133 -3
- package/dist/admission-composite.d.ts +112 -1
- package/dist/admission-composite.js +85 -4
- package/dist/admission-enrichment.d.ts +12 -3
- package/dist/admission-enrichment.js +64 -13
- package/dist/admission-score.d.ts +30 -0
- package/dist/admission-score.js +4 -1
- package/dist/analysis/file-walker.js +5 -0
- 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.js +1 -1
- package/dist/cli/commands/init-features.d.ts +301 -2
- package/dist/cli/commands/init-features.js +647 -7
- package/dist/cli/commands/init-templates.d.ts +198 -1
- package/dist/cli/commands/init-templates.js +943 -1
- package/dist/cli/commands/init.d.ts +45 -0
- package/dist/cli/commands/init.js +147 -5
- 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/cycle-utils.js +14 -1
- package/dist/database/adapters/external.js +5 -1
- 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 +200 -38
- package/dist/fix-review.js +1 -1
- 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/metric-snapshot.d.ts +403 -0
- package/dist/journey/metric-snapshot.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/otel-exporter.js +0 -3
- package/dist/runners/claude-code.js +3 -3
- 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 +173 -13
- package/dist/runtime/attestations.js +265 -43
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/sa-scoring/exemplar-bank.js +3 -1
- 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/shared.d.ts +28 -0
- package/dist/shared.js +37 -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/dist/webhook-manager.js +7 -1
- package/package.json +15 -12
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC-0018 Phase 4 — MetricSnapshot resource read API (OQ-5 resolution).
|
|
3
|
+
*
|
|
4
|
+
* Implements:
|
|
5
|
+
* AC #1: MetricSnapshot schema (spec/schemas/metric-snapshot.v1.schema.json)
|
|
6
|
+
* AC #2: MetricSnapshot read API — `getLatestMetricSnapshot(journey, metricId)`
|
|
7
|
+
* AC #3: Stale-metric detection (default 30d; per-Soul configurable) with
|
|
8
|
+
* `Decision: journey-metric-stale` + warn-and-unknown Cκ behavior
|
|
9
|
+
* AC #4: Graduated Eρ₅ degradation (0-30/30-60/60-90/90+ thresholds + Decisions)
|
|
10
|
+
* AC #5: Per-Soul `accessibility.auditOverdueGracePolicy` modes
|
|
11
|
+
* AC #6: RFC-0022 multi-posture composition (strictest cadence applies)
|
|
12
|
+
*
|
|
13
|
+
* ### OQ-5 design summary
|
|
14
|
+
*
|
|
15
|
+
* Operators supply MetricSnapshot resources from their analytics pipeline
|
|
16
|
+
* (Mixpanel, Amplitude, Heap, internal-pipeline). The framework reads
|
|
17
|
+
* `completion-rate` and other journey-success values; it does NOT compute
|
|
18
|
+
* them from an analytics backend.
|
|
19
|
+
*
|
|
20
|
+
* Staleness: when `recordedAt` is older than `thresholdDays` (default 30),
|
|
21
|
+
* the scorer treats the metric as an unknown input (warn-and-unknown, NOT
|
|
22
|
+
* fail-closed). A `Decision: journey-metric-stale` is emitted for operator
|
|
23
|
+
* batch review.
|
|
24
|
+
*
|
|
25
|
+
* ### OQ-6 design summary
|
|
26
|
+
*
|
|
27
|
+
* When a journey's accessibility audit is overdue, Eρ₅ degrades on this
|
|
28
|
+
* graduated schedule (per-Soul policy `auditOverdueGracePolicy`):
|
|
29
|
+
*
|
|
30
|
+
* Policy `graduated` (default):
|
|
31
|
+
* 0–30d past cadence → warn only (`journey-audit-overdue-warn`)
|
|
32
|
+
* 30–60d → Eρ₅ -25% (`journey-audit-overdue-graduated`)
|
|
33
|
+
* 60–90d → Eρ₅ -50% (`journey-audit-overdue-graduated`)
|
|
34
|
+
* 90d+ → effective block (`journey-audit-overdue-blocking`)
|
|
35
|
+
*
|
|
36
|
+
* Policy `binary-30d`:
|
|
37
|
+
* 0–30d → no impact (SOC2/HIPAA early-warning model)
|
|
38
|
+
* 30d+ → immediate Eρ₅ fail
|
|
39
|
+
*
|
|
40
|
+
* Policy `hard-block`:
|
|
41
|
+
* Immediate Eρ₅ fail at cadence+0d (strictest, no grace)
|
|
42
|
+
*
|
|
43
|
+
* ### RFC-0022 multi-posture composition (AC #6)
|
|
44
|
+
*
|
|
45
|
+
* When the RFC-0022 compliance posture declares a stricter cadence than
|
|
46
|
+
* the soul-default, the strictest constraint wins. This mirrors the
|
|
47
|
+
* RFC-0030 OQ-13.3 UNION precedent for multi-posture composition.
|
|
48
|
+
*
|
|
49
|
+
* ### Decision-routing must-consume contract
|
|
50
|
+
*
|
|
51
|
+
* `getLatestMetricSnapshot` emits `decision: 'journey-metric-stale'` when a
|
|
52
|
+
* snapshot is present but older than `thresholdDays`. Callers MUST inspect
|
|
53
|
+
* `result.decision` and route it — typically to the RFC-0035 G0 batch-review
|
|
54
|
+
* queue — before using `result.snapshot.spec.value` for Cκ scoring. Silently
|
|
55
|
+
* dropping `result.decision` defeats the operator-visibility guarantee that
|
|
56
|
+
* makes warn-and-unknown safe (non-fail-closed). A typed must-consume pattern:
|
|
57
|
+
*
|
|
58
|
+
* ```ts
|
|
59
|
+
* const result = getLatestMetricSnapshot(journey, metricId, opts);
|
|
60
|
+
* if (result.decision) emitDecision(result.decision); // required
|
|
61
|
+
* if (result.freshness === 'fresh') useValue(result.snapshot!.spec.value);
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @see spec/rfcs/RFC-0018-in-soul-journey-pattern.md §10.1 OQ-5 + OQ-6
|
|
65
|
+
* @see spec/schemas/metric-snapshot.v1.schema.json
|
|
66
|
+
*/
|
|
67
|
+
/** Default staleness threshold per OQ-5 resolution (30 days). */
|
|
68
|
+
export const DEFAULT_STALENESS_THRESHOLD_DAYS = 30;
|
|
69
|
+
/**
|
|
70
|
+
* Retrieve the **latest** MetricSnapshot for the given journey + metricId pair
|
|
71
|
+
* and classify it as fresh, stale, or missing.
|
|
72
|
+
*
|
|
73
|
+
* Selection: when multiple snapshots match, the one with the most recent
|
|
74
|
+
* `spec.recordedAt` is returned (latest-wins). This covers the case where
|
|
75
|
+
* the operator's pipeline emits snapshots on a periodic schedule.
|
|
76
|
+
*
|
|
77
|
+
* Staleness: `ageInDays = (now - recordedAt) / (1000 * 60 * 60 * 24)`.
|
|
78
|
+
* When `ageInDays > thresholdDays`, the result carries:
|
|
79
|
+
* - `freshness: 'stale'`
|
|
80
|
+
* - `decision: 'journey-metric-stale'`
|
|
81
|
+
*
|
|
82
|
+
* This Decision routes through RFC-0035 G0 (non-blocking batch review):
|
|
83
|
+
* the Cκ scorer treats a stale metric as an unknown input (same behavior as
|
|
84
|
+
* `freshness: 'missing'`), NOT as a hard fail. The pipeline continues.
|
|
85
|
+
*
|
|
86
|
+
* @param journey Path-style journey URI (e.g. 'spry-engage/onboarding')
|
|
87
|
+
* @param metricId Metric identifier (e.g. 'completion-rate')
|
|
88
|
+
* @param options Snapshot collection + optional per-Soul config
|
|
89
|
+
*/
|
|
90
|
+
export function getLatestMetricSnapshot(journey, metricId, options) {
|
|
91
|
+
const { snapshots, stalenessConfig, now } = options;
|
|
92
|
+
const thresholdDays = stalenessConfig?.thresholdDays ?? DEFAULT_STALENESS_THRESHOLD_DAYS;
|
|
93
|
+
const nowMs = now ? new Date(now).getTime() : Date.now();
|
|
94
|
+
// Filter to snapshots matching this journey + metricId pair.
|
|
95
|
+
const matching = snapshots.filter((s) => s.metadata.journey === journey && s.metadata.metricId === metricId);
|
|
96
|
+
if (matching.length === 0) {
|
|
97
|
+
return {
|
|
98
|
+
journey,
|
|
99
|
+
metricId,
|
|
100
|
+
freshness: 'missing',
|
|
101
|
+
thresholdDays,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// Select the most recent by recordedAt (latest-wins).
|
|
105
|
+
const latest = matching.reduce((best, candidate) => {
|
|
106
|
+
const bestMs = new Date(best.spec.recordedAt).getTime();
|
|
107
|
+
const candidateMs = new Date(candidate.spec.recordedAt).getTime();
|
|
108
|
+
return candidateMs > bestMs ? candidate : best;
|
|
109
|
+
});
|
|
110
|
+
const recordedAtMs = new Date(latest.spec.recordedAt).getTime();
|
|
111
|
+
// Guard: a future-dated recordedAt (recordedAt > now) would yield a negative
|
|
112
|
+
// ageInDays, making the metric appear perpetually fresh and suppressing the
|
|
113
|
+
// journey-metric-stale Decision. Clamp negative ages to stale so a
|
|
114
|
+
// misconfigured analytics pipeline cannot silently defeat staleness checks.
|
|
115
|
+
const rawAgeInDays = (nowMs - recordedAtMs) / (1000 * 60 * 60 * 24);
|
|
116
|
+
const ageInDays = rawAgeInDays < 0 ? thresholdDays + 1 : rawAgeInDays;
|
|
117
|
+
if (ageInDays > thresholdDays) {
|
|
118
|
+
return {
|
|
119
|
+
journey,
|
|
120
|
+
metricId,
|
|
121
|
+
freshness: 'stale',
|
|
122
|
+
snapshot: latest,
|
|
123
|
+
decision: 'journey-metric-stale',
|
|
124
|
+
ageInDays,
|
|
125
|
+
thresholdDays,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
journey,
|
|
130
|
+
metricId,
|
|
131
|
+
freshness: 'fresh',
|
|
132
|
+
snapshot: latest,
|
|
133
|
+
ageInDays,
|
|
134
|
+
thresholdDays,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** Eρ₅ multiplier for each impact tier. */
|
|
138
|
+
export const ERHO5_MULTIPLIERS = {
|
|
139
|
+
warn: 1.0,
|
|
140
|
+
'reduced-25': 0.75,
|
|
141
|
+
'reduced-50': 0.5,
|
|
142
|
+
'effective-block': 0.0,
|
|
143
|
+
};
|
|
144
|
+
/** Default graduated thresholds per OQ-6 resolution. */
|
|
145
|
+
export const DEFAULT_GRADUATED_THRESHOLDS = {
|
|
146
|
+
warnAt: 0,
|
|
147
|
+
reduced25At: 30,
|
|
148
|
+
reduced50At: 60,
|
|
149
|
+
effectiveBlockAt: 90,
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Compute the Eρ₅ impact and Decision for an overdue accessibility audit.
|
|
153
|
+
*
|
|
154
|
+
* Implements RFC-0018 §10.1 OQ-6 graduated Eρ₅ degradation:
|
|
155
|
+
*
|
|
156
|
+
* Policy `graduated` (default):
|
|
157
|
+
* - 0 ≤ daysOverdue < 30 → `warn` (multiplier 1.0)
|
|
158
|
+
* - 30 ≤ daysOverdue < 60 → `reduced-25` (multiplier 0.75)
|
|
159
|
+
* - 60 ≤ daysOverdue < 90 → `reduced-50` (multiplier 0.50)
|
|
160
|
+
* - daysOverdue ≥ 90 → `effective-block` (multiplier 0.0)
|
|
161
|
+
*
|
|
162
|
+
* Policy `binary-30d` (SOC2/HIPAA strict):
|
|
163
|
+
* - daysOverdue < 30 → no impact (multiplier 1.0, no Decision)
|
|
164
|
+
* - daysOverdue ≥ 30 → `effective-block` (multiplier 0.0)
|
|
165
|
+
*
|
|
166
|
+
* Policy `hard-block` (HIPAA/PCI-DSS ultra-strict):
|
|
167
|
+
* - daysOverdue < 0 → no impact (multiplier 1.0, no Decision; not yet due)
|
|
168
|
+
* - daysOverdue ≥ 0 → `effective-block` (multiplier 0.0) — cadence+0d, no grace
|
|
169
|
+
*
|
|
170
|
+
* When `daysOverdue < 0`, returns multiplier 1.0 and `decision: null`
|
|
171
|
+
* regardless of policy (audit is not yet due). At daysOverdue ≥ 0 each policy
|
|
172
|
+
* emits its Decision (no implicit grace day).
|
|
173
|
+
*/
|
|
174
|
+
export function computeAuditOverdueErho5(options) {
|
|
175
|
+
const { soulId, journeyId, daysOverdue, policy = 'graduated', graduatedThresholds } = options;
|
|
176
|
+
// Strictly negative daysOverdue means the audit is not yet due — no impact
|
|
177
|
+
// regardless of policy. Note: daysOverdue === 0 means "exactly at cadence
|
|
178
|
+
// boundary (cadence+0d)" and is intentionally NOT caught here so the
|
|
179
|
+
// policy-specific logic below can apply. In particular, `hard-block`
|
|
180
|
+
// specifies "no grace at cadence+0d", meaning it must fire at daysOverdue=0.
|
|
181
|
+
if (daysOverdue < 0) {
|
|
182
|
+
return {
|
|
183
|
+
soulId,
|
|
184
|
+
journeyId,
|
|
185
|
+
daysOverdue,
|
|
186
|
+
policy,
|
|
187
|
+
impact: 'warn',
|
|
188
|
+
erho5Multiplier: 1.0,
|
|
189
|
+
decision: null,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if (policy === 'hard-block') {
|
|
193
|
+
return {
|
|
194
|
+
soulId,
|
|
195
|
+
journeyId,
|
|
196
|
+
daysOverdue,
|
|
197
|
+
policy,
|
|
198
|
+
impact: 'effective-block',
|
|
199
|
+
erho5Multiplier: ERHO5_MULTIPLIERS['effective-block'],
|
|
200
|
+
decision: 'journey-audit-overdue-blocking',
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (policy === 'binary-30d') {
|
|
204
|
+
const threshold = 30;
|
|
205
|
+
if (daysOverdue < threshold) {
|
|
206
|
+
// SOC2/HIPAA grace window: warn only, no Eρ₅ impact.
|
|
207
|
+
return {
|
|
208
|
+
soulId,
|
|
209
|
+
journeyId,
|
|
210
|
+
daysOverdue,
|
|
211
|
+
policy,
|
|
212
|
+
impact: 'warn',
|
|
213
|
+
erho5Multiplier: 1.0,
|
|
214
|
+
decision: 'journey-audit-overdue-warn',
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
soulId,
|
|
219
|
+
journeyId,
|
|
220
|
+
daysOverdue,
|
|
221
|
+
policy,
|
|
222
|
+
impact: 'effective-block',
|
|
223
|
+
erho5Multiplier: ERHO5_MULTIPLIERS['effective-block'],
|
|
224
|
+
decision: 'journey-audit-overdue-blocking',
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
// policy === 'graduated' (default)
|
|
228
|
+
// Guard: NaN daysOverdue (e.g. from a division by zero or bad caller) must
|
|
229
|
+
// not fall through to the warn/1.0 return at the bottom of the graduated
|
|
230
|
+
// path, producing a fail-open result. Treat non-finite values as
|
|
231
|
+
// effective-block (conservative) so the pipeline aborts rather than silently
|
|
232
|
+
// continuing with an unknown overdue duration.
|
|
233
|
+
if (!Number.isFinite(daysOverdue)) {
|
|
234
|
+
return {
|
|
235
|
+
soulId,
|
|
236
|
+
journeyId,
|
|
237
|
+
daysOverdue,
|
|
238
|
+
policy,
|
|
239
|
+
impact: 'effective-block',
|
|
240
|
+
erho5Multiplier: ERHO5_MULTIPLIERS['effective-block'],
|
|
241
|
+
decision: 'journey-audit-overdue-blocking',
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const thresholds = {
|
|
245
|
+
warnAt: graduatedThresholds?.warnAt ?? DEFAULT_GRADUATED_THRESHOLDS.warnAt,
|
|
246
|
+
reduced25At: graduatedThresholds?.reduced25At ?? DEFAULT_GRADUATED_THRESHOLDS.reduced25At,
|
|
247
|
+
reduced50At: graduatedThresholds?.reduced50At ?? DEFAULT_GRADUATED_THRESHOLDS.reduced50At,
|
|
248
|
+
effectiveBlockAt: graduatedThresholds?.effectiveBlockAt ?? DEFAULT_GRADUATED_THRESHOLDS.effectiveBlockAt,
|
|
249
|
+
};
|
|
250
|
+
if (daysOverdue >= thresholds.effectiveBlockAt) {
|
|
251
|
+
return {
|
|
252
|
+
soulId,
|
|
253
|
+
journeyId,
|
|
254
|
+
daysOverdue,
|
|
255
|
+
policy,
|
|
256
|
+
impact: 'effective-block',
|
|
257
|
+
erho5Multiplier: ERHO5_MULTIPLIERS['effective-block'],
|
|
258
|
+
decision: 'journey-audit-overdue-blocking',
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
if (daysOverdue >= thresholds.reduced50At) {
|
|
262
|
+
return {
|
|
263
|
+
soulId,
|
|
264
|
+
journeyId,
|
|
265
|
+
daysOverdue,
|
|
266
|
+
policy,
|
|
267
|
+
impact: 'reduced-50',
|
|
268
|
+
erho5Multiplier: ERHO5_MULTIPLIERS['reduced-50'],
|
|
269
|
+
decision: 'journey-audit-overdue-graduated',
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
if (daysOverdue >= thresholds.reduced25At) {
|
|
273
|
+
return {
|
|
274
|
+
soulId,
|
|
275
|
+
journeyId,
|
|
276
|
+
daysOverdue,
|
|
277
|
+
policy,
|
|
278
|
+
impact: 'reduced-25',
|
|
279
|
+
erho5Multiplier: ERHO5_MULTIPLIERS['reduced-25'],
|
|
280
|
+
decision: 'journey-audit-overdue-graduated',
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
// daysOverdue >= warnAt (default 0) but below reduced25At
|
|
284
|
+
return {
|
|
285
|
+
soulId,
|
|
286
|
+
journeyId,
|
|
287
|
+
daysOverdue,
|
|
288
|
+
policy,
|
|
289
|
+
impact: 'warn',
|
|
290
|
+
erho5Multiplier: 1.0,
|
|
291
|
+
decision: 'journey-audit-overdue-warn',
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Numeric strictness order for cadence values.
|
|
296
|
+
* Higher = stricter (shorter audit interval).
|
|
297
|
+
* Used by `resolveStrictestCadence` to pick the UNION result.
|
|
298
|
+
*/
|
|
299
|
+
export const AUDIT_CADENCE_STRICTNESS = {
|
|
300
|
+
continuous: 4,
|
|
301
|
+
'release-gated': 3,
|
|
302
|
+
quarterly: 2,
|
|
303
|
+
annually: 1,
|
|
304
|
+
};
|
|
305
|
+
/**
|
|
306
|
+
* Resolve the effective audit cadence by applying the strictest constraint
|
|
307
|
+
* from the journey declaration and all active RFC-0022 compliance postures.
|
|
308
|
+
*
|
|
309
|
+
* This implements RFC-0018 AC #6 + RFC-0030 OQ-13.3 UNION precedent:
|
|
310
|
+
* the strictest constraint among all active postures and the journey's own
|
|
311
|
+
* declaration wins.
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* // Journey declares 'annually', but SOC2 posture requires 'quarterly'
|
|
315
|
+
* resolveStrictestCadence({
|
|
316
|
+
* journeyCadence: 'annually',
|
|
317
|
+
* postureCadences: ['quarterly'],
|
|
318
|
+
* })
|
|
319
|
+
* // → 'quarterly' (posture wins — stricter)
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* // Journey declares 'continuous' (strictest possible)
|
|
323
|
+
* resolveStrictestCadence({
|
|
324
|
+
* journeyCadence: 'continuous',
|
|
325
|
+
* postureCadences: ['quarterly', 'annually'],
|
|
326
|
+
* })
|
|
327
|
+
* // → 'continuous' (journey wins — already strictest)
|
|
328
|
+
*/
|
|
329
|
+
export function resolveStrictestCadence(options) {
|
|
330
|
+
const { journeyCadence, postureCadences } = options;
|
|
331
|
+
const all = [journeyCadence, ...postureCadences];
|
|
332
|
+
// UNION = strictest (highest strictness number wins).
|
|
333
|
+
return all.reduce((strictest, candidate) => {
|
|
334
|
+
const currentOrder = AUDIT_CADENCE_STRICTNESS[strictest] ?? 0;
|
|
335
|
+
const candidateOrder = AUDIT_CADENCE_STRICTNESS[candidate] ?? 0;
|
|
336
|
+
return candidateOrder > currentOrder ? candidate : strictest;
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Policy strictness order (higher = stricter).
|
|
341
|
+
*/
|
|
342
|
+
export const GRACE_POLICY_STRICTNESS = {
|
|
343
|
+
graduated: 1,
|
|
344
|
+
'binary-30d': 2,
|
|
345
|
+
'hard-block': 3,
|
|
346
|
+
};
|
|
347
|
+
/**
|
|
348
|
+
* Resolve the effective grace policy by picking the STRICTEST among the
|
|
349
|
+
* soul-level policy and all active RFC-0022 compliance posture policies.
|
|
350
|
+
*
|
|
351
|
+
* RFC-0022 + RFC-0018 AC #6: multi-posture UNION → strictest applies.
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* // Soul defaults to 'graduated'; SOC2 posture requires 'binary-30d'
|
|
355
|
+
* resolveStrictestGracePolicy({
|
|
356
|
+
* soulPolicy: 'graduated',
|
|
357
|
+
* posturesPolicies: ['binary-30d'],
|
|
358
|
+
* })
|
|
359
|
+
* // → 'binary-30d' (posture wins — stricter)
|
|
360
|
+
*/
|
|
361
|
+
export function resolveStrictestGracePolicy(options) {
|
|
362
|
+
const { soulPolicy = 'graduated', posturesPolicies } = options;
|
|
363
|
+
const all = [soulPolicy, ...posturesPolicies];
|
|
364
|
+
return all.reduce((strictest, candidate) => {
|
|
365
|
+
const currentOrder = GRACE_POLICY_STRICTNESS[strictest] ?? 0;
|
|
366
|
+
const candidateOrder = GRACE_POLICY_STRICTNESS[candidate] ?? 0;
|
|
367
|
+
return candidateOrder > currentOrder ? candidate : strictest;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
//# sourceMappingURL=metric-snapshot.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
|