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