@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,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC-0009 §13 + RFC-0018 OQ-10 — Tessellation§13RuleRegistry.
|
|
3
|
+
*
|
|
4
|
+
* Provides a concrete registration mechanism for §13 drift-detection rules,
|
|
5
|
+
* resolving RFC-0018 OQ-10 (2026-05-28, full rubric):
|
|
6
|
+
*
|
|
7
|
+
* "4th rule in same §13 engine + concrete registration mechanism spec'd:
|
|
8
|
+
* Tessellation§13RuleRegistry.register(rule). Industry research:
|
|
9
|
+
* unified-engine-with-plugin-rules (Sonar, Semgrep, CodeQL, Snyk,
|
|
10
|
+
* Dependabot, Renovate) is the modern de-facto pattern."
|
|
11
|
+
*
|
|
12
|
+
* Standard rule interface: `{ name, description, scan(target): DriftEvent[], severity }`
|
|
13
|
+
* §13 dispatcher fans out all registered rules in parallel; aggregates Decisions
|
|
14
|
+
* for catalog routing.
|
|
15
|
+
*
|
|
16
|
+
* ### Composition with RFC-0028 OQ-7.2
|
|
17
|
+
*
|
|
18
|
+
* RFC-0028 OQ-7.2 (2026-05-27 resolution) establishes the structural-vs-statistical
|
|
19
|
+
* drift detection pairing:
|
|
20
|
+
*
|
|
21
|
+
* - **Structural drift** (this registry, CI authoring-time): BLOCKS PR via
|
|
22
|
+
* `Decision: <rule-specific-name>` when `severity === 'high'`.
|
|
23
|
+
* - **Statistical drift** (runtime PPA `SoulDriftDetected`): SURFACES via
|
|
24
|
+
* RFC-0035 G0 non-blocking pipeline contract.
|
|
25
|
+
*
|
|
26
|
+
* Rules register their `severity` to indicate which path applies:
|
|
27
|
+
* `'high'` = structural-blocking; `'medium'` | `'warning'` = surfaces non-blocking.
|
|
28
|
+
*
|
|
29
|
+
* @see spec/rfcs/RFC-0009-tessellated-design-intent-documents.md §13
|
|
30
|
+
* @see spec/rfcs/RFC-0018-in-soul-journey-pattern.md §10.1 OQ-8 + OQ-10
|
|
31
|
+
* @see spec/rfcs/RFC-0028-engineering-axis-substrate-enforcement.md OQ-7.2
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Severity level for a drift event / rule.
|
|
35
|
+
*
|
|
36
|
+
* Per RFC-0028 OQ-7.2:
|
|
37
|
+
* - `'high'` — structural drift; BLOCKS PR at CI authoring-time when configured.
|
|
38
|
+
* - `'medium'` — surfaces non-blocking via RFC-0035 G0 catalog route.
|
|
39
|
+
* - `'warning'` — informational; surfaces for operator batch review.
|
|
40
|
+
*/
|
|
41
|
+
export type DriftSeverity = 'high' | 'medium' | 'warning';
|
|
42
|
+
/**
|
|
43
|
+
* A single drift event emitted by a registered §13 rule.
|
|
44
|
+
*
|
|
45
|
+
* All rules emit events of this shape. The `rule` field discriminates
|
|
46
|
+
* the originating rule; consumers narrow on it before reading `details`.
|
|
47
|
+
*
|
|
48
|
+
* Events are aggregated by the §13 dispatcher and forwarded to the
|
|
49
|
+
* RFC-0035 Decision Catalog for routing.
|
|
50
|
+
*/
|
|
51
|
+
export interface DriftEvent {
|
|
52
|
+
/**
|
|
53
|
+
* Canonical rule name — matches the `name` field of the `TessellationRule`
|
|
54
|
+
* that emitted this event. Used for Decision Catalog routing and operator
|
|
55
|
+
* attribution.
|
|
56
|
+
*/
|
|
57
|
+
rule: string;
|
|
58
|
+
/**
|
|
59
|
+
* RFC-3339 UTC timestamp at detection time.
|
|
60
|
+
*/
|
|
61
|
+
timestamp: string;
|
|
62
|
+
/**
|
|
63
|
+
* Human-readable one-line summary; safe for operator surfaces (TUI, Slack).
|
|
64
|
+
*/
|
|
65
|
+
message: string;
|
|
66
|
+
/**
|
|
67
|
+
* Effective severity for this event instance. Rules may downgrade from
|
|
68
|
+
* their registered `severity` based on per-org configuration (e.g. default
|
|
69
|
+
* `medium` can be escalated to `high` via journey-config.yaml
|
|
70
|
+
* `driftDetection.severityOverride`).
|
|
71
|
+
*/
|
|
72
|
+
severity: DriftSeverity;
|
|
73
|
+
/**
|
|
74
|
+
* Free-form structured payload; rule-specific. Consumers narrow via `rule`.
|
|
75
|
+
*/
|
|
76
|
+
details: unknown;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Standard interface for §13 tessellation drift-detection rules.
|
|
80
|
+
*
|
|
81
|
+
* RFC-0018 OQ-10 resolution specifies this shape:
|
|
82
|
+
* `{ name, description, scan(target): DriftEvent[], severity }`
|
|
83
|
+
*
|
|
84
|
+
* Rules MUST be stateless — all context is passed through `target`.
|
|
85
|
+
* The registry fans out `scan(target)` calls in parallel; the order of
|
|
86
|
+
* rule registration does not affect dispatch order (all fire concurrently).
|
|
87
|
+
*/
|
|
88
|
+
export interface TessellationRule<TTarget = RuleScanTarget> {
|
|
89
|
+
/**
|
|
90
|
+
* Canonical stable rule name. Used as the `rule` field on emitted events
|
|
91
|
+
* and as the Decision Catalog routing key. SHOULD be kebab-case.
|
|
92
|
+
*
|
|
93
|
+
* Examples: `'soul-slug-ast-scan'`, `'cross-soul-provenance'`,
|
|
94
|
+
* `'journey-state-id-drift'`.
|
|
95
|
+
*/
|
|
96
|
+
readonly name: string;
|
|
97
|
+
/**
|
|
98
|
+
* Human-readable description of what this rule detects. Shown in
|
|
99
|
+
* operator TUI and Slack notifications.
|
|
100
|
+
*/
|
|
101
|
+
readonly description: string;
|
|
102
|
+
/**
|
|
103
|
+
* Default severity level for events emitted by this rule.
|
|
104
|
+
*
|
|
105
|
+
* Per RFC-0028 OQ-7.2:
|
|
106
|
+
* - `'high'` → structural blocking (BLOCKS PR)
|
|
107
|
+
* - `'medium'` → non-blocking, surfaces via RFC-0035 G0 catalog
|
|
108
|
+
* - `'warning'` → informational batch-review queue
|
|
109
|
+
*/
|
|
110
|
+
readonly severity: DriftSeverity;
|
|
111
|
+
/**
|
|
112
|
+
* Scan the supplied target for drift.
|
|
113
|
+
*
|
|
114
|
+
* Called once per tick by the §13 dispatcher. MUST be synchronous or
|
|
115
|
+
* return a Promise. Stateless — all state must be captured in closure
|
|
116
|
+
* at construction time. MUST NOT throw; surface errors as `DriftEvent`
|
|
117
|
+
* with an `error` details payload and `severity: 'warning'`.
|
|
118
|
+
*
|
|
119
|
+
* @param target All inputs available to rules in this dispatch cycle.
|
|
120
|
+
* @returns Zero or more `DriftEvent` objects. Empty array = no drift.
|
|
121
|
+
*/
|
|
122
|
+
scan(target: TTarget): DriftEvent[] | Promise<DriftEvent[]>;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The shared scan-target passed to every registered rule during a §13
|
|
126
|
+
* dispatch cycle.
|
|
127
|
+
*
|
|
128
|
+
* Rules destructure only the fields they need. Optional fields are absent
|
|
129
|
+
* when the caller did not provide them (no-op for rules that depend on them).
|
|
130
|
+
*
|
|
131
|
+
* This is the context object that evolves across RFC phases:
|
|
132
|
+
* - Phase 4.2 (AISDLC-317): `substrateFiles`, `tessellation`, `tessellatedDid`
|
|
133
|
+
* - Phase 4.2 (AISDLC-317): `provenance`
|
|
134
|
+
* - Phase 3 (AISDLC-467): `journeysBySoul`, `journeyStatus`
|
|
135
|
+
*
|
|
136
|
+
* New rule families add fields here as phases land. Existing rules
|
|
137
|
+
* destructure only what they need; unknown fields are ignored — forward-
|
|
138
|
+
* compatible with zero changes to existing rule implementations.
|
|
139
|
+
*/
|
|
140
|
+
export interface RuleScanTarget {
|
|
141
|
+
/** DID URI of the parent Tessellation being scanned. */
|
|
142
|
+
tessellatedDid: string;
|
|
143
|
+
/** Substrate files to scan (for AST-scan based rules). */
|
|
144
|
+
substrateFiles?: SubstrateFileEntry[];
|
|
145
|
+
/** Provenance entries to audit (for cross-soul provenance rule). */
|
|
146
|
+
provenance?: unknown[];
|
|
147
|
+
/**
|
|
148
|
+
* Journey declarations keyed by soulId — the in-memory representation
|
|
149
|
+
* of all active journeys across the tessellation.
|
|
150
|
+
*
|
|
151
|
+
* Shape matches `JourneyContext.journeysBySoul` from `journey-sa2-router.ts`
|
|
152
|
+
* (RFC-0018 Phase 2). Rules read state IDs from `journey.states[]`.
|
|
153
|
+
*/
|
|
154
|
+
journeysBySoul?: Record<string, ActiveJourneyDeclaration[]>;
|
|
155
|
+
/**
|
|
156
|
+
* Journey lifecycle status keyed by soulId+journeyId. Used by the
|
|
157
|
+
* JourneyStateIdDriftRule to detect removed journeys.
|
|
158
|
+
*
|
|
159
|
+
* Key format: `<soul-id>/<journey-id>`. Missing entry = assume active.
|
|
160
|
+
*/
|
|
161
|
+
journeyStatus?: Record<string, JourneyLifecycleStatus>;
|
|
162
|
+
}
|
|
163
|
+
/** A substrate file entry for AST-scan based rules. */
|
|
164
|
+
export interface SubstrateFileEntry {
|
|
165
|
+
/** Workspace-relative path (forward slashes). */
|
|
166
|
+
path: string;
|
|
167
|
+
/** Full file contents as UTF-8. */
|
|
168
|
+
contents: string;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Minimal journey declaration shape for rule consumption.
|
|
172
|
+
* Mirrors `JourneyDeclaration` from `journey-sa2-router.ts` but
|
|
173
|
+
* is defined here to avoid a circular import across RFC phase packages.
|
|
174
|
+
* The drift registry ships in AISDLC-467 (Phase 3); the SA2 router
|
|
175
|
+
* ships in AISDLC-466 (Phase 2) — they must not create import cycles.
|
|
176
|
+
*/
|
|
177
|
+
export interface ActiveJourneyDeclaration {
|
|
178
|
+
/** Journey identifier (kebab-case, unique within parent scope). */
|
|
179
|
+
id: string;
|
|
180
|
+
/**
|
|
181
|
+
* Declared state identifiers for this journey. Rules scan substrate
|
|
182
|
+
* code for references to these IDs.
|
|
183
|
+
*
|
|
184
|
+
* Each state has an `id` (kebab-case, unique within the journey) plus
|
|
185
|
+
* optional metadata. The drift rule only cares about `id`.
|
|
186
|
+
*/
|
|
187
|
+
states: Array<{
|
|
188
|
+
id: string;
|
|
189
|
+
[key: string]: unknown;
|
|
190
|
+
}>;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Journey lifecycle status — whether a journey is currently active or
|
|
194
|
+
* has been removed (deprecated / archived).
|
|
195
|
+
*
|
|
196
|
+
* Per RFC-0018 §6.2 and OQ-8 resolution: application code referencing
|
|
197
|
+
* a state in a removed journey is a drift signal.
|
|
198
|
+
*/
|
|
199
|
+
export type JourneyLifecycleStatus = 'active' | 'removed';
|
|
200
|
+
/**
|
|
201
|
+
* Tessellation§13RuleRegistry — the registration + parallel dispatch
|
|
202
|
+
* mechanism for RFC-0009 §13 drift-detection rules.
|
|
203
|
+
*
|
|
204
|
+
* RFC-0018 OQ-10 resolution (2026-05-28) specifies:
|
|
205
|
+
* "concrete registration mechanism: Tessellation§13RuleRegistry.register(rule)
|
|
206
|
+
* … §13 dispatcher fans out all registered rules in parallel; aggregates
|
|
207
|
+
* Decisions for catalog routing."
|
|
208
|
+
*
|
|
209
|
+
* ### Usage
|
|
210
|
+
*
|
|
211
|
+
* ```ts
|
|
212
|
+
* import { createTessellation13Registry } from './tessellation/rule-registry.js';
|
|
213
|
+
* import { SoulSlugAstScanRule } from './tessellation/soul-slug-ast-scan-rule.js';
|
|
214
|
+
*
|
|
215
|
+
* const registry = createTessellation13Registry();
|
|
216
|
+
* registry.register(new SoulSlugAstScanRule());
|
|
217
|
+
* const events = await registry.dispatch(target);
|
|
218
|
+
* ```
|
|
219
|
+
*
|
|
220
|
+
* ### Parallelism
|
|
221
|
+
*
|
|
222
|
+
* `dispatch()` fans out `scan(target)` calls via `Promise.allSettled` so
|
|
223
|
+
* that a slow or failing rule does not block others. Settled rejections
|
|
224
|
+
* are converted to `DriftEvent` with `severity: 'warning'` so they surface
|
|
225
|
+
* as operator-visible noise rather than silently dropping.
|
|
226
|
+
*/
|
|
227
|
+
export interface Tessellation13Registry {
|
|
228
|
+
/**
|
|
229
|
+
* Register a rule with the §13 dispatcher.
|
|
230
|
+
*
|
|
231
|
+
* Rules are registered once at startup and remain for the lifetime of
|
|
232
|
+
* the registry. Duplicate names are allowed (the registry does not
|
|
233
|
+
* de-duplicate); the second `register()` call adds a second instance,
|
|
234
|
+
* which is intentional for testing / override scenarios.
|
|
235
|
+
*
|
|
236
|
+
* @param rule A `TessellationRule` implementation.
|
|
237
|
+
*/
|
|
238
|
+
register(rule: TessellationRule): void;
|
|
239
|
+
/**
|
|
240
|
+
* Return all currently registered rules, in registration order.
|
|
241
|
+
*
|
|
242
|
+
* Useful for operator-facing diagnostics (TUI rule list, Slack digest)
|
|
243
|
+
* and for test assertions that verify which rules are wired up.
|
|
244
|
+
*/
|
|
245
|
+
getRegisteredRules(): ReadonlyArray<TessellationRule>;
|
|
246
|
+
/**
|
|
247
|
+
* Fan out all registered rules in parallel and aggregate the results.
|
|
248
|
+
*
|
|
249
|
+
* Returns the union of all `DriftEvent[]` arrays emitted by registered
|
|
250
|
+
* rules. Rules that throw are caught; their error is surfaced as a
|
|
251
|
+
* `DriftEvent` with `severity: 'warning'` so operators can investigate
|
|
252
|
+
* without silently dropping findings.
|
|
253
|
+
*
|
|
254
|
+
* When no rules are registered, returns an empty array immediately.
|
|
255
|
+
*
|
|
256
|
+
* @param target The shared scan-target for this dispatch cycle.
|
|
257
|
+
* @returns Aggregated drift events from all registered rules.
|
|
258
|
+
*/
|
|
259
|
+
dispatch(target: RuleScanTarget): Promise<DriftEvent[]>;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Create a new `Tessellation13Registry` instance.
|
|
263
|
+
*
|
|
264
|
+
* Each call returns an independent registry. The typical pattern is to
|
|
265
|
+
* create ONE registry per orchestrator startup and register all §13
|
|
266
|
+
* rules into it.
|
|
267
|
+
*/
|
|
268
|
+
export declare function createTessellation13Registry(): Tessellation13Registry;
|
|
269
|
+
//# sourceMappingURL=rule-registry.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC-0009 §13 + RFC-0018 OQ-10 — Tessellation§13RuleRegistry.
|
|
3
|
+
*
|
|
4
|
+
* Provides a concrete registration mechanism for §13 drift-detection rules,
|
|
5
|
+
* resolving RFC-0018 OQ-10 (2026-05-28, full rubric):
|
|
6
|
+
*
|
|
7
|
+
* "4th rule in same §13 engine + concrete registration mechanism spec'd:
|
|
8
|
+
* Tessellation§13RuleRegistry.register(rule). Industry research:
|
|
9
|
+
* unified-engine-with-plugin-rules (Sonar, Semgrep, CodeQL, Snyk,
|
|
10
|
+
* Dependabot, Renovate) is the modern de-facto pattern."
|
|
11
|
+
*
|
|
12
|
+
* Standard rule interface: `{ name, description, scan(target): DriftEvent[], severity }`
|
|
13
|
+
* §13 dispatcher fans out all registered rules in parallel; aggregates Decisions
|
|
14
|
+
* for catalog routing.
|
|
15
|
+
*
|
|
16
|
+
* ### Composition with RFC-0028 OQ-7.2
|
|
17
|
+
*
|
|
18
|
+
* RFC-0028 OQ-7.2 (2026-05-27 resolution) establishes the structural-vs-statistical
|
|
19
|
+
* drift detection pairing:
|
|
20
|
+
*
|
|
21
|
+
* - **Structural drift** (this registry, CI authoring-time): BLOCKS PR via
|
|
22
|
+
* `Decision: <rule-specific-name>` when `severity === 'high'`.
|
|
23
|
+
* - **Statistical drift** (runtime PPA `SoulDriftDetected`): SURFACES via
|
|
24
|
+
* RFC-0035 G0 non-blocking pipeline contract.
|
|
25
|
+
*
|
|
26
|
+
* Rules register their `severity` to indicate which path applies:
|
|
27
|
+
* `'high'` = structural-blocking; `'medium'` | `'warning'` = surfaces non-blocking.
|
|
28
|
+
*
|
|
29
|
+
* @see spec/rfcs/RFC-0009-tessellated-design-intent-documents.md §13
|
|
30
|
+
* @see spec/rfcs/RFC-0018-in-soul-journey-pattern.md §10.1 OQ-8 + OQ-10
|
|
31
|
+
* @see spec/rfcs/RFC-0028-engineering-axis-substrate-enforcement.md OQ-7.2
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Create a new `Tessellation13Registry` instance.
|
|
35
|
+
*
|
|
36
|
+
* Each call returns an independent registry. The typical pattern is to
|
|
37
|
+
* create ONE registry per orchestrator startup and register all §13
|
|
38
|
+
* rules into it.
|
|
39
|
+
*/
|
|
40
|
+
export function createTessellation13Registry() {
|
|
41
|
+
const rules = [];
|
|
42
|
+
return {
|
|
43
|
+
register(rule) {
|
|
44
|
+
rules.push(rule);
|
|
45
|
+
},
|
|
46
|
+
getRegisteredRules() {
|
|
47
|
+
return rules;
|
|
48
|
+
},
|
|
49
|
+
async dispatch(target) {
|
|
50
|
+
if (rules.length === 0)
|
|
51
|
+
return [];
|
|
52
|
+
const now = new Date().toISOString();
|
|
53
|
+
// Fan out in parallel — all rules execute concurrently.
|
|
54
|
+
// Wrap each scan() call in a new Promise so synchronous throws are
|
|
55
|
+
// caught by Promise.allSettled just as async rejections are.
|
|
56
|
+
const settled = await Promise.allSettled(rules.map((rule) => new Promise((resolve, reject) => {
|
|
57
|
+
try {
|
|
58
|
+
const result = rule.scan(target);
|
|
59
|
+
if (result instanceof Promise) {
|
|
60
|
+
result.then(resolve, reject);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
resolve(result);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
reject(err);
|
|
68
|
+
}
|
|
69
|
+
})));
|
|
70
|
+
const events = [];
|
|
71
|
+
for (let i = 0; i < settled.length; i++) {
|
|
72
|
+
const result = settled[i];
|
|
73
|
+
const rule = rules[i];
|
|
74
|
+
if (result.status === 'fulfilled') {
|
|
75
|
+
events.push(...result.value);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Rule threw — surface as a warning so operators can investigate.
|
|
79
|
+
events.push({
|
|
80
|
+
rule: rule.name,
|
|
81
|
+
timestamp: now,
|
|
82
|
+
message: `§13 rule '${rule.name}' threw during scan: ${String(result.reason)}`,
|
|
83
|
+
severity: 'warning',
|
|
84
|
+
details: { error: String(result.reason) },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return events;
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=rule-registry.js.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC-0009 §13 Rule #1 — SoulSlugAstScanRule.
|
|
3
|
+
*
|
|
4
|
+
* Implements `TessellationRule` for the Tessellation§13RuleRegistry so Rule #1
|
|
5
|
+
* is dispatchable via `registry.register(rule)` (AISDLC-489, AISDLC-467 AC#3
|
|
6
|
+
* follow-up).
|
|
7
|
+
*
|
|
8
|
+
* Wraps the existing soul-slug AST scan logic from `tessellation-drift.ts` as a
|
|
9
|
+
* first-class `TessellationRule` instance. The implementation is a thin adapter
|
|
10
|
+
* that delegates to the same two-pattern regex scan used by `detectTessellationDrift()`
|
|
11
|
+
* internally, preserving the existing detection semantics unchanged.
|
|
12
|
+
*
|
|
13
|
+
* ### Scan strategy
|
|
14
|
+
*
|
|
15
|
+
* Line-by-line regex over `target.substrateFiles`:
|
|
16
|
+
* 1. `'<slug>'` or `"<slug>"` — bare string-literal match.
|
|
17
|
+
* 2. `soul === '<slug>'` / `soulId === '<slug>'` — soul-discriminating branch.
|
|
18
|
+
*
|
|
19
|
+
* Soul slugs are supplied at construction time via `soulSlugs`. The registry
|
|
20
|
+
* dispatcher passes the shared `RuleScanTarget` to all rules; callers building
|
|
21
|
+
* the target should set `target.soulSlugs` to the tessellation's soul ID list.
|
|
22
|
+
* When `target.soulSlugs` is present it takes precedence; otherwise the rule
|
|
23
|
+
* falls back to the `soulSlugs` supplied at construction time.
|
|
24
|
+
*
|
|
25
|
+
* ### Severity
|
|
26
|
+
*
|
|
27
|
+
* Default `'warning'` (matches the pre-registry `TessellationDriftDetectedEvent`
|
|
28
|
+
* `severity: 'warning'` field in the original `tessellation-drift.ts`).
|
|
29
|
+
*
|
|
30
|
+
* @see spec/rfcs/RFC-0009-tessellated-design-intent-documents.md §7.2 Rule #1
|
|
31
|
+
* @see orchestrator/src/tessellation-drift.ts (original Rule #1 logic)
|
|
32
|
+
*/
|
|
33
|
+
import type { TessellationRule, DriftEvent, DriftSeverity, RuleScanTarget } from './rule-registry.js';
|
|
34
|
+
/**
|
|
35
|
+
* A single soul-slug AST scan finding.
|
|
36
|
+
*/
|
|
37
|
+
export interface SoulSlugAstScanFinding {
|
|
38
|
+
/** Path of the substrate file that contained the soul-leaking literal. */
|
|
39
|
+
filePath: string;
|
|
40
|
+
/** Soul slug whose name leaked into shared substrate. */
|
|
41
|
+
soulSlug: string;
|
|
42
|
+
/** 1-based line number in `filePath`. */
|
|
43
|
+
line: number;
|
|
44
|
+
/**
|
|
45
|
+
* Discriminator for which pattern triggered the finding:
|
|
46
|
+
* - `'string-literal'` — bare `'<slug>'` (or `"<slug>"`) appeared in substrate
|
|
47
|
+
* - `'soul-conditional'` — `soul === '<slug>'` / similar branched on soul
|
|
48
|
+
*/
|
|
49
|
+
pattern: 'string-literal' | 'soul-conditional';
|
|
50
|
+
/** The raw matching substring (trimmed, max 200 chars) for operator inspection. */
|
|
51
|
+
excerpt: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Structured details payload for soul-slug-ast-scan drift events.
|
|
55
|
+
*/
|
|
56
|
+
export interface SoulSlugAstScanDetails {
|
|
57
|
+
rule: 'soul-slug-ast-scan';
|
|
58
|
+
findings: SoulSlugAstScanFinding[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* SoulSlugAstScanRule — RFC-0009 §13 Rule #1.
|
|
62
|
+
*
|
|
63
|
+
* Scans substrate files for soul-slug string literals and soul-discriminating
|
|
64
|
+
* conditionals. Emits a `DriftEvent` when any soul slug leaks into shared
|
|
65
|
+
* substrate code.
|
|
66
|
+
*
|
|
67
|
+
* ### Registration
|
|
68
|
+
*
|
|
69
|
+
* ```ts
|
|
70
|
+
* const registry = createTessellation13Registry();
|
|
71
|
+
* registry.register(new SoulSlugAstScanRule(['soul-a', 'soul-b']));
|
|
72
|
+
* // With target-side soul slugs (takes precedence):
|
|
73
|
+
* // target.soulSlugs = tessellation.souls.map(s => s.soulId);
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare class SoulSlugAstScanRule implements TessellationRule {
|
|
77
|
+
readonly name = "soul-slug-ast-scan";
|
|
78
|
+
readonly description = "Scans shared substrate files for soul-slug string literals and soul-discriminating conditionals (RFC-0009 \u00A77.2 Rule #1)";
|
|
79
|
+
readonly severity: DriftSeverity;
|
|
80
|
+
private readonly constructionSlugs;
|
|
81
|
+
/**
|
|
82
|
+
* @param soulSlugs Default set of soul slugs to scan for. When the scan
|
|
83
|
+
* target carries `soulSlugs`, those take precedence.
|
|
84
|
+
* Pass an empty array when you always supply slugs via the target.
|
|
85
|
+
* @param severity Default `'warning'` (matches pre-registry behaviour).
|
|
86
|
+
*/
|
|
87
|
+
constructor(soulSlugs?: readonly string[], severity?: DriftSeverity);
|
|
88
|
+
scan(target: RuleScanTarget): DriftEvent[];
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=soul-slug-ast-scan-rule.d.ts.map
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC-0009 §13 Rule #1 — SoulSlugAstScanRule.
|
|
3
|
+
*
|
|
4
|
+
* Implements `TessellationRule` for the Tessellation§13RuleRegistry so Rule #1
|
|
5
|
+
* is dispatchable via `registry.register(rule)` (AISDLC-489, AISDLC-467 AC#3
|
|
6
|
+
* follow-up).
|
|
7
|
+
*
|
|
8
|
+
* Wraps the existing soul-slug AST scan logic from `tessellation-drift.ts` as a
|
|
9
|
+
* first-class `TessellationRule` instance. The implementation is a thin adapter
|
|
10
|
+
* that delegates to the same two-pattern regex scan used by `detectTessellationDrift()`
|
|
11
|
+
* internally, preserving the existing detection semantics unchanged.
|
|
12
|
+
*
|
|
13
|
+
* ### Scan strategy
|
|
14
|
+
*
|
|
15
|
+
* Line-by-line regex over `target.substrateFiles`:
|
|
16
|
+
* 1. `'<slug>'` or `"<slug>"` — bare string-literal match.
|
|
17
|
+
* 2. `soul === '<slug>'` / `soulId === '<slug>'` — soul-discriminating branch.
|
|
18
|
+
*
|
|
19
|
+
* Soul slugs are supplied at construction time via `soulSlugs`. The registry
|
|
20
|
+
* dispatcher passes the shared `RuleScanTarget` to all rules; callers building
|
|
21
|
+
* the target should set `target.soulSlugs` to the tessellation's soul ID list.
|
|
22
|
+
* When `target.soulSlugs` is present it takes precedence; otherwise the rule
|
|
23
|
+
* falls back to the `soulSlugs` supplied at construction time.
|
|
24
|
+
*
|
|
25
|
+
* ### Severity
|
|
26
|
+
*
|
|
27
|
+
* Default `'warning'` (matches the pre-registry `TessellationDriftDetectedEvent`
|
|
28
|
+
* `severity: 'warning'` field in the original `tessellation-drift.ts`).
|
|
29
|
+
*
|
|
30
|
+
* @see spec/rfcs/RFC-0009-tessellated-design-intent-documents.md §7.2 Rule #1
|
|
31
|
+
* @see orchestrator/src/tessellation-drift.ts (original Rule #1 logic)
|
|
32
|
+
*/
|
|
33
|
+
// ── Internal helpers ───────────────────────────────────────────────────
|
|
34
|
+
/**
|
|
35
|
+
* Validate a soul slug. Mirrors the validator in `tessellation-drift.ts`:
|
|
36
|
+
* lowercase alphanumeric + dashes, 1-64 chars.
|
|
37
|
+
*/
|
|
38
|
+
function isValidSlug(slug) {
|
|
39
|
+
return /^[a-z0-9-]+$/.test(slug) && slug.length >= 1 && slug.length <= 64;
|
|
40
|
+
}
|
|
41
|
+
/** Escape a string for safe use inside a RegExp pattern. */
|
|
42
|
+
function escapeRegex(s) {
|
|
43
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Scan one substrate file for soul-leakage patterns.
|
|
47
|
+
*
|
|
48
|
+
* Same two-pattern strategy as the internal `scanSubstrateFile` in
|
|
49
|
+
* `tessellation-drift.ts` — adapted to `SubstrateFileEntry` (which uses
|
|
50
|
+
* `contents` + `path`, the same field names) and extended return shape.
|
|
51
|
+
*/
|
|
52
|
+
function scanSubstrateFileForSlugs(file, soulSlugs) {
|
|
53
|
+
const findings = [];
|
|
54
|
+
if (file.contents.length === 0 || soulSlugs.length === 0)
|
|
55
|
+
return findings;
|
|
56
|
+
const lines = file.contents.split('\n');
|
|
57
|
+
for (const slug of soulSlugs) {
|
|
58
|
+
if (!isValidSlug(slug))
|
|
59
|
+
continue;
|
|
60
|
+
const esc = escapeRegex(slug);
|
|
61
|
+
// Pattern 1: bare string literal (single or double quote, exact match).
|
|
62
|
+
const literalRe = new RegExp(`(['"])${esc}\\1`);
|
|
63
|
+
// Pattern 2: soul-discriminating conditional. Permissive on the soul
|
|
64
|
+
// identifier name so it catches `soul`, `soulId`, `soul_id`, etc.
|
|
65
|
+
const condRe = new RegExp(`(?:soul[A-Za-z_]*)\\s*===\\s*(['"])${esc}\\1`);
|
|
66
|
+
for (let i = 0; i < lines.length; i++) {
|
|
67
|
+
const line = lines[i];
|
|
68
|
+
const condMatch = line.match(condRe);
|
|
69
|
+
if (condMatch) {
|
|
70
|
+
findings.push({
|
|
71
|
+
filePath: file.path,
|
|
72
|
+
soulSlug: slug,
|
|
73
|
+
line: i + 1,
|
|
74
|
+
pattern: 'soul-conditional',
|
|
75
|
+
excerpt: line.trim().slice(0, 200),
|
|
76
|
+
});
|
|
77
|
+
continue; // Don't double-report same line as bare literal.
|
|
78
|
+
}
|
|
79
|
+
if (literalRe.test(line)) {
|
|
80
|
+
findings.push({
|
|
81
|
+
filePath: file.path,
|
|
82
|
+
soulSlug: slug,
|
|
83
|
+
line: i + 1,
|
|
84
|
+
pattern: 'string-literal',
|
|
85
|
+
excerpt: line.trim().slice(0, 200),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return findings;
|
|
91
|
+
}
|
|
92
|
+
// ── Rule implementation ────────────────────────────────────────────────
|
|
93
|
+
/**
|
|
94
|
+
* SoulSlugAstScanRule — RFC-0009 §13 Rule #1.
|
|
95
|
+
*
|
|
96
|
+
* Scans substrate files for soul-slug string literals and soul-discriminating
|
|
97
|
+
* conditionals. Emits a `DriftEvent` when any soul slug leaks into shared
|
|
98
|
+
* substrate code.
|
|
99
|
+
*
|
|
100
|
+
* ### Registration
|
|
101
|
+
*
|
|
102
|
+
* ```ts
|
|
103
|
+
* const registry = createTessellation13Registry();
|
|
104
|
+
* registry.register(new SoulSlugAstScanRule(['soul-a', 'soul-b']));
|
|
105
|
+
* // With target-side soul slugs (takes precedence):
|
|
106
|
+
* // target.soulSlugs = tessellation.souls.map(s => s.soulId);
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export class SoulSlugAstScanRule {
|
|
110
|
+
name = 'soul-slug-ast-scan';
|
|
111
|
+
description = 'Scans shared substrate files for soul-slug string literals and soul-discriminating conditionals (RFC-0009 §7.2 Rule #1)';
|
|
112
|
+
severity;
|
|
113
|
+
constructionSlugs;
|
|
114
|
+
/**
|
|
115
|
+
* @param soulSlugs Default set of soul slugs to scan for. When the scan
|
|
116
|
+
* target carries `soulSlugs`, those take precedence.
|
|
117
|
+
* Pass an empty array when you always supply slugs via the target.
|
|
118
|
+
* @param severity Default `'warning'` (matches pre-registry behaviour).
|
|
119
|
+
*/
|
|
120
|
+
constructor(soulSlugs = [], severity = 'warning') {
|
|
121
|
+
this.constructionSlugs = soulSlugs;
|
|
122
|
+
this.severity = severity;
|
|
123
|
+
}
|
|
124
|
+
scan(target) {
|
|
125
|
+
const { substrateFiles, tessellatedDid } = target;
|
|
126
|
+
// Accept soul slugs from target if provided (forward-compat field), else
|
|
127
|
+
// fall back to slugs supplied at construction time.
|
|
128
|
+
const soulSlugs = target.soulSlugs ?? this.constructionSlugs;
|
|
129
|
+
// No-op when no substrate files or no soul slugs to scan for.
|
|
130
|
+
if (!substrateFiles || substrateFiles.length === 0)
|
|
131
|
+
return [];
|
|
132
|
+
if (soulSlugs.length === 0)
|
|
133
|
+
return [];
|
|
134
|
+
const now = new Date().toISOString();
|
|
135
|
+
const allFindings = [];
|
|
136
|
+
for (const file of substrateFiles) {
|
|
137
|
+
const findings = scanSubstrateFileForSlugs(file, soulSlugs);
|
|
138
|
+
if (findings.length > 0)
|
|
139
|
+
allFindings.push(...findings);
|
|
140
|
+
}
|
|
141
|
+
if (allFindings.length === 0)
|
|
142
|
+
return [];
|
|
143
|
+
const involved = new Set(allFindings.map((f) => f.soulSlug));
|
|
144
|
+
return [
|
|
145
|
+
{
|
|
146
|
+
rule: this.name,
|
|
147
|
+
timestamp: now,
|
|
148
|
+
message: `AST scan: ${allFindings.length} soul-name leakage hit(s) across ${involved.size} soul(s) in shared substrate (tessellation: ${tessellatedDid})`,
|
|
149
|
+
severity: this.severity,
|
|
150
|
+
details: {
|
|
151
|
+
rule: 'soul-slug-ast-scan',
|
|
152
|
+
findings: allFindings,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=soul-slug-ast-scan-rule.js.map
|