@ai-sdlc/orchestrator 0.10.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/dist/adapters.d.ts +42 -3
  2. package/dist/adapters.js +133 -3
  3. package/dist/admission-composite.d.ts +112 -1
  4. package/dist/admission-composite.js +85 -4
  5. package/dist/admission-enrichment.d.ts +12 -3
  6. package/dist/admission-enrichment.js +64 -13
  7. package/dist/admission-score.d.ts +30 -0
  8. package/dist/admission-score.js +4 -1
  9. package/dist/backlog-adapter.d.ts +45 -0
  10. package/dist/backlog-adapter.js +145 -1
  11. package/dist/calibration.d.ts +81 -0
  12. package/dist/calibration.js +76 -0
  13. package/dist/cli/commands/init-features.d.ts +301 -2
  14. package/dist/cli/commands/init-features.js +634 -7
  15. package/dist/cli/commands/init-templates.d.ts +198 -1
  16. package/dist/cli/commands/init-templates.js +943 -1
  17. package/dist/cli/commands/init.d.ts +45 -0
  18. package/dist/cli/commands/init.js +147 -5
  19. package/dist/cli/commands/run.js +9 -1
  20. package/dist/cli/index.d.ts +11 -0
  21. package/dist/cli/index.js +42 -8
  22. package/dist/compliance/composer.d.ts +79 -0
  23. package/dist/compliance/composer.js +258 -0
  24. package/dist/compliance/errors.d.ts +64 -0
  25. package/dist/compliance/errors.js +85 -0
  26. package/dist/compliance/loader.d.ts +52 -0
  27. package/dist/compliance/loader.js +124 -0
  28. package/dist/compliance/types.d.ts +184 -0
  29. package/dist/compliance/types.js +41 -0
  30. package/dist/compliance-clearance.d.ts +269 -0
  31. package/dist/compliance-clearance.js +269 -0
  32. package/dist/config.js +17 -0
  33. package/dist/cost-tracker.d.ts +22 -0
  34. package/dist/cost-tracker.js +41 -0
  35. package/dist/database/adapters/external.js +5 -1
  36. package/dist/embedding/adapters/openai-text-embedding-3-small.d.ts +71 -0
  37. package/dist/embedding/adapters/openai-text-embedding-3-small.js +190 -0
  38. package/dist/embedding/consumers/tessellation-drift.d.ts +74 -0
  39. package/dist/embedding/consumers/tessellation-drift.js +76 -0
  40. package/dist/embedding/cross-provider.d.ts +78 -0
  41. package/dist/embedding/cross-provider.js +75 -0
  42. package/dist/embedding/deprecation.d.ts +151 -0
  43. package/dist/embedding/deprecation.js +229 -0
  44. package/dist/embedding/errors.d.ts +90 -0
  45. package/dist/embedding/errors.js +150 -0
  46. package/dist/embedding/index.d.ts +29 -0
  47. package/dist/embedding/index.js +24 -0
  48. package/dist/embedding/pipeline-load.d.ts +146 -0
  49. package/dist/embedding/pipeline-load.js +178 -0
  50. package/dist/embedding/registry.d.ts +45 -0
  51. package/dist/embedding/registry.js +61 -0
  52. package/dist/embedding/stale-vector.d.ts +110 -0
  53. package/dist/embedding/stale-vector.js +92 -0
  54. package/dist/embedding/storage/index.d.ts +51 -0
  55. package/dist/embedding/storage/index.js +43 -0
  56. package/dist/embedding/storage/jsonl-backend.d.ts +150 -0
  57. package/dist/embedding/storage/jsonl-backend.js +332 -0
  58. package/dist/embedding/storage/types.d.ts +135 -0
  59. package/dist/embedding/storage/types.js +13 -0
  60. package/dist/embedding/types.d.ts +180 -0
  61. package/dist/embedding/types.js +10 -0
  62. package/dist/execute.d.ts +29 -2
  63. package/dist/execute.js +171 -30
  64. package/dist/index.d.ts +14 -2
  65. package/dist/index.js +19 -2
  66. package/dist/journey/inheritance-validator.d.ts +396 -0
  67. package/dist/journey/inheritance-validator.js +370 -0
  68. package/dist/journey/state-id-drift-rule.d.ts +137 -0
  69. package/dist/journey/state-id-drift-rule.js +245 -0
  70. package/dist/journey-sa2-router.d.ts +395 -0
  71. package/dist/journey-sa2-router.js +308 -0
  72. package/dist/runners/review-agent.js +6 -2
  73. package/dist/runners/runner-registry.d.ts +36 -0
  74. package/dist/runners/runner-registry.js +90 -0
  75. package/dist/runtime/attestations.d.ts +173 -13
  76. package/dist/runtime/attestations.js +252 -40
  77. package/dist/runtime/index.d.ts +1 -1
  78. package/dist/runtime/index.js +1 -1
  79. package/dist/sa-scoring/layer3-llm.js +6 -1
  80. package/dist/sa-scoring/revision-proposal-config.d.ts +178 -0
  81. package/dist/sa-scoring/revision-proposal-config.js +198 -0
  82. package/dist/sa-scoring/revision-proposal.d.ts +285 -0
  83. package/dist/sa-scoring/revision-proposal.js +417 -0
  84. package/dist/signal-ingestion/adapters/community-thread.d.ts +43 -0
  85. package/dist/signal-ingestion/adapters/community-thread.js +55 -0
  86. package/dist/signal-ingestion/adapters/in-app-feedback.d.ts +67 -0
  87. package/dist/signal-ingestion/adapters/in-app-feedback.js +51 -0
  88. package/dist/signal-ingestion/adapters/manual.d.ts +78 -0
  89. package/dist/signal-ingestion/adapters/manual.js +112 -0
  90. package/dist/signal-ingestion/adapters/support-ticket.d.ts +47 -0
  91. package/dist/signal-ingestion/adapters/support-ticket.js +51 -0
  92. package/dist/signal-ingestion/classifier.d.ts +205 -0
  93. package/dist/signal-ingestion/classifier.js +494 -0
  94. package/dist/signal-ingestion/clustering-types.d.ts +36 -0
  95. package/dist/signal-ingestion/clustering-types.js +14 -0
  96. package/dist/signal-ingestion/clustering.d.ts +200 -0
  97. package/dist/signal-ingestion/clustering.js +413 -0
  98. package/dist/signal-ingestion/config.d.ts +351 -0
  99. package/dist/signal-ingestion/config.js +587 -0
  100. package/dist/signal-ingestion/d1.d.ts +252 -0
  101. package/dist/signal-ingestion/d1.js +235 -0
  102. package/dist/signal-ingestion/errors.d.ts +73 -0
  103. package/dist/signal-ingestion/errors.js +108 -0
  104. package/dist/signal-ingestion/governance-events.d.ts +181 -0
  105. package/dist/signal-ingestion/governance-events.js +189 -0
  106. package/dist/signal-ingestion/index.d.ts +35 -0
  107. package/dist/signal-ingestion/index.js +53 -0
  108. package/dist/signal-ingestion/manual-share-metric.d.ts +93 -0
  109. package/dist/signal-ingestion/manual-share-metric.js +106 -0
  110. package/dist/signal-ingestion/registry.d.ts +40 -0
  111. package/dist/signal-ingestion/registry.js +137 -0
  112. package/dist/signal-ingestion/residency.d.ts +227 -0
  113. package/dist/signal-ingestion/residency.js +238 -0
  114. package/dist/signal-ingestion/significance.d.ts +554 -0
  115. package/dist/signal-ingestion/significance.js +555 -0
  116. package/dist/signal-ingestion/types.d.ts +191 -0
  117. package/dist/signal-ingestion/types.js +8 -0
  118. package/dist/substrate/drift-composition.d.ts +270 -0
  119. package/dist/substrate/drift-composition.js +306 -0
  120. package/dist/substrate/drift-tui-surface.d.ts +61 -0
  121. package/dist/substrate/drift-tui-surface.js +102 -0
  122. package/dist/substrate/identity-class.d.ts +176 -0
  123. package/dist/substrate/identity-class.js +201 -0
  124. package/dist/tessellation/cross-soul-provenance-rule.d.ts +133 -0
  125. package/dist/tessellation/cross-soul-provenance-rule.js +171 -0
  126. package/dist/tessellation/inter-soul-embedding-distance-rule.d.ts +61 -0
  127. package/dist/tessellation/inter-soul-embedding-distance-rule.js +67 -0
  128. package/dist/tessellation/rule-registry.d.ts +269 -0
  129. package/dist/tessellation/rule-registry.js +92 -0
  130. package/dist/tessellation/soul-slug-ast-scan-rule.d.ts +90 -0
  131. package/dist/tessellation/soul-slug-ast-scan-rule.js +158 -0
  132. package/dist/tessellation-admission.d.ts +162 -0
  133. package/dist/tessellation-admission.js +146 -0
  134. package/dist/tessellation-drift.d.ts +246 -0
  135. package/dist/tessellation-drift.js +250 -0
  136. package/dist/validate-config.js +13 -0
  137. package/dist/validate-issue.js +2 -2
  138. package/dist/variant/cardinality-activation.d.ts +126 -0
  139. package/dist/variant/cardinality-activation.js +101 -0
  140. package/dist/variant/deprecation-lifecycle.d.ts +184 -0
  141. package/dist/variant/deprecation-lifecycle.js +208 -0
  142. package/dist/variant/drift-extension.d.ts +136 -0
  143. package/dist/variant/drift-extension.js +164 -0
  144. package/dist/variant/engineering-review.d.ts +185 -0
  145. package/dist/variant/engineering-review.js +142 -0
  146. package/dist/variant/index.d.ts +32 -0
  147. package/dist/variant/index.js +32 -0
  148. package/dist/variant/inheritance-validator.d.ts +165 -0
  149. package/dist/variant/inheritance-validator.js +139 -0
  150. package/dist/variant/internal-adopter/index.d.ts +11 -0
  151. package/dist/variant/internal-adopter/index.js +10 -0
  152. package/dist/variant/internal-adopter/products.d.ts +156 -0
  153. package/dist/variant/internal-adopter/products.js +366 -0
  154. package/dist/variant-admission.d.ts +316 -0
  155. package/dist/variant-admission.js +247 -0
  156. package/package.json +10 -8
@@ -4,7 +4,7 @@
4
4
  * into the dogfood pipeline.
5
5
  */
6
6
  import type { AiSdlcConfig } from './config.js';
7
- import { type IssueTracker, type AdapterRegistry, type ScanOptions, type ScanResult, type WebhookBridge, type GitAdapterFetcher, type GitResolveResult, type AuditLog, type AuditSink, type Sandbox, type SecretStore, type MemoryStore, type EventBus, type CIPipeline } from '@ai-sdlc/reference';
7
+ import { type IssueTracker, type SourceControl, type AdapterRegistry, type ScanOptions, type ScanResult, type WebhookBridge, type GitAdapterFetcher, type GitResolveResult, type AuditLog, type AuditSink, type Sandbox, type SecretStore, type MemoryStore, type EventBus, type CIPipeline } from '@ai-sdlc/reference';
8
8
  /**
9
9
  * Create an adapter registry pre-loaded with all built-in and community adapters.
10
10
  */
@@ -70,6 +70,45 @@ export declare function resolveIssueTrackerFromConfig(config: AiSdlcConfig, fall
70
70
  secretRef: string;
71
71
  };
72
72
  }): IssueTracker;
73
- export { createGitHubCIPipeline, createDockerSandbox, createLinearIssueTracker, resolveSecret, createGitLabSourceControl, createGitLabCIPipeline, createJiraIssueTracker, createBacklogMdIssueTracker, createWebhookServer, createGitHubWebhookProvider, verifyGitHubSignature, createGitLabWebhookProvider, verifyGitLabToken, createJiraWebhookProvider, createLinearWebhookProvider, verifyLinearSignature, createAdapterRegistry, validateAdapterMetadata, parseMetadataYaml, scanLocalAdapters, createStubCodeAnalysis, createStubMessenger, createStubDeploymentTarget, createStubGitLabCI, createStubGitLabSource, createStubJira, createStubBitbucket, createStubSonarQube, createStubSemgrep, createInMemoryAuditSink, createFileSink, createEnvSecretStore, createInMemoryMemoryStore, createInProcessEventBus, createStubSandbox, createCompositeIssueTracker, createWebhookBridge, parseGitAdapterRef, buildRawUrl, createGitAdapterFetcher, createStubGitAdapterFetcher, resolveGitAdapter, } from '@ai-sdlc/reference';
74
- export type { AdapterRegistry, AdapterMetadata, AdapterStability, AdapterFactory, MetadataValidationResult, ScanOptions, ScanResult, WebhookBridge, WebhookTransformer, GitAdapterReference, GitAdapterFetcher, GitResolveResult, CIPipeline, CodeAnalysis, Messenger, DeploymentTarget, EventBus, IssueTracker, LinearClientLike, IssueComment, AdapterInterfaces, EventStream, IssueFilter, CommitStatus, TestResults, CoverageReport, Finding, SeveritySummary, DeploymentStatus, StubCodeAnalysisConfig, StubCodeAnalysisAdapter, NotificationLogEntry, StubMessengerAdapter, StubDeploymentTargetAdapter, StubGitLabCIAdapter, StubGitLabSourceAdapter, StubJiraAdapter, StubBitbucketAdapter, StubSonarQubeConfig, StubSonarQubeAdapter, StubSemgrepConfig, StubSemgrepAdapter, AuditSink, AuditLog, InMemoryAuditSink, SecretStore, Sandbox, MemoryStore, InMemoryMemoryStore, InProcessEventBus, DockerSandboxConfig, BuildStatus, GitLabConfig, JiraConfig, BacklogMdConfig, BacklogFs, WebhookServer, WebhookServerConfig, WebhookProviderConfig, GitHubWebhookConfig, GitHubWebhookBridges, GitLabWebhookConfig, JiraWebhookConfig, LinearWebhookConfig, CompositeIssueTrackerConfig, BackendRoute, } from '@ai-sdlc/reference';
73
+ /**
74
+ * A no-op SourceControl adapter for local-only repositories (no remote).
75
+ *
76
+ * When an adopter has no 'origin' remote (or sets `type: local` in their
77
+ * AdapterBinding), the pipeline must not block on API calls. This adapter:
78
+ *
79
+ * - `createBranch` — delegates to the local git command-line (via execute.ts)
80
+ * so the branch checkout path works unchanged.
81
+ * - `createPR` — resolves immediately with a sentinel local PR object; the
82
+ * caller (execute.ts step 15) should check `pr.url` to detect the skip.
83
+ * - All other methods are no-ops.
84
+ *
85
+ * Callers detect local-only mode by checking `pr.url === 'local'` and skip
86
+ * tracker comments + Slack notifications for the PR URL.
87
+ */
88
+ export declare function createLocalSourceControl(): SourceControl;
89
+ /**
90
+ * Resolve a SourceControl adapter from the AdapterBinding whose
91
+ * `spec.interface === 'SourceControl'`.
92
+ *
93
+ * Resolution order (first wins):
94
+ * 1. `type: github` → existing GitHub adapter using `spec.config` or fallback.
95
+ * 2. `type: gitlab` → GitLab adapter with `spec.config.url` (required for
96
+ * self-hosted) and optional `spec.config.token.secretRef`.
97
+ * 3. `type: local` → local-only no-op adapter that skips push/create-pr.
98
+ * 4. No SourceControl AdapterBinding present → GitHub (current default, no regression).
99
+ *
100
+ * When multiple SourceControl bindings exist, the FIRST one wins. This is an
101
+ * intentional design choice — multiple SourceControl bindings are unusual and
102
+ * the pipeline has a single active remote. If this constraint is too tight,
103
+ * escalate to the Decision Catalog before changing it.
104
+ */
105
+ export declare function resolveSourceControlFromConfig(config: AiSdlcConfig, fallbackGitHubConfig: {
106
+ org: string;
107
+ repo: string;
108
+ token: {
109
+ secretRef: string;
110
+ };
111
+ }): SourceControl;
112
+ export { createGitHubCIPipeline, createGitHubSourceControl, createDockerSandbox, createLinearIssueTracker, resolveSecret, createGitLabSourceControl, createGitLabCIPipeline, createJiraIssueTracker, createBacklogMdIssueTracker, createWebhookServer, createGitHubWebhookProvider, verifyGitHubSignature, createGitLabWebhookProvider, verifyGitLabToken, createJiraWebhookProvider, createLinearWebhookProvider, verifyLinearSignature, createAdapterRegistry, validateAdapterMetadata, parseMetadataYaml, scanLocalAdapters, createStubCodeAnalysis, createStubMessenger, createStubDeploymentTarget, createStubGitLabCI, createStubGitLabSource, createStubJira, createStubBitbucket, createStubSonarQube, createStubSemgrep, createInMemoryAuditSink, createFileSink, createEnvSecretStore, createInMemoryMemoryStore, createInProcessEventBus, createStubSandbox, createCompositeIssueTracker, createWebhookBridge, parseGitAdapterRef, buildRawUrl, createGitAdapterFetcher, createStubGitAdapterFetcher, resolveGitAdapter, } from '@ai-sdlc/reference';
113
+ export type { AdapterRegistry, AdapterMetadata, AdapterStability, AdapterFactory, MetadataValidationResult, ScanOptions, ScanResult, WebhookBridge, WebhookTransformer, GitAdapterReference, GitAdapterFetcher, GitResolveResult, CIPipeline, SourceControl, CodeAnalysis, Messenger, DeploymentTarget, EventBus, IssueTracker, LinearClientLike, IssueComment, AdapterInterfaces, EventStream, IssueFilter, CommitStatus, TestResults, CoverageReport, Finding, SeveritySummary, DeploymentStatus, StubCodeAnalysisConfig, StubCodeAnalysisAdapter, NotificationLogEntry, StubMessengerAdapter, StubDeploymentTargetAdapter, StubGitLabCIAdapter, StubGitLabSourceAdapter, StubJiraAdapter, StubBitbucketAdapter, StubSonarQubeConfig, StubSonarQubeAdapter, StubSemgrepConfig, StubSemgrepAdapter, AuditSink, AuditLog, InMemoryAuditSink, SecretStore, Sandbox, MemoryStore, InMemoryMemoryStore, InProcessEventBus, DockerSandboxConfig, BuildStatus, GitLabConfig, JiraConfig, BacklogMdConfig, BacklogFs, WebhookServer, WebhookServerConfig, WebhookProviderConfig, GitHubWebhookConfig, GitHubWebhookBridges, GitLabWebhookConfig, JiraWebhookConfig, LinearWebhookConfig, CompositeIssueTrackerConfig, BackendRoute, } from '@ai-sdlc/reference';
75
114
  //# sourceMappingURL=adapters.d.ts.map
package/dist/adapters.js CHANGED
@@ -24,8 +24,10 @@ createWebhookBridge,
24
24
  createStubGitAdapterFetcher, createGitAdapterFetcher, resolveGitAdapter,
25
25
  // GitHub CI adapter
26
26
  createGitHubCIPipeline,
27
- // GitHub issue tracker
28
- createGitHubIssueTracker,
27
+ // GitHub source control + issue tracker
28
+ createGitHubSourceControl, createGitHubIssueTracker,
29
+ // GitLab source control
30
+ createGitLabSourceControl,
29
31
  // Composite issue tracker
30
32
  createCompositeIssueTracker,
31
33
  // Production adapters (for resolveIssueTrackerFromConfig)
@@ -228,10 +230,138 @@ export function resolveIssueTrackerFromConfig(config, fallbackGitHubConfig) {
228
230
  }
229
231
  return createCompositeIssueTracker({ backends });
230
232
  }
233
+ // ── Source-control resolution from config ────────────────────────────
234
+ /**
235
+ * A no-op SourceControl adapter for local-only repositories (no remote).
236
+ *
237
+ * When an adopter has no 'origin' remote (or sets `type: local` in their
238
+ * AdapterBinding), the pipeline must not block on API calls. This adapter:
239
+ *
240
+ * - `createBranch` — delegates to the local git command-line (via execute.ts)
241
+ * so the branch checkout path works unchanged.
242
+ * - `createPR` — resolves immediately with a sentinel local PR object; the
243
+ * caller (execute.ts step 15) should check `pr.url` to detect the skip.
244
+ * - All other methods are no-ops.
245
+ *
246
+ * Callers detect local-only mode by checking `pr.url === 'local'` and skip
247
+ * tracker comments + Slack notifications for the PR URL.
248
+ */
249
+ export function createLocalSourceControl() {
250
+ return {
251
+ async createBranch(input) {
252
+ // Branches are created via git CLI in execute.ts; this is a passthrough stub.
253
+ return { name: input.name, sha: '' };
254
+ },
255
+ async createPR(_input) {
256
+ // Sentinel URL signals to the pipeline that we are in local-only mode and
257
+ // no remote PR was created. The pipeline checks for this and skips the
258
+ // "PR created" comment + Slack notification (avoiding noise).
259
+ return {
260
+ id: 'local',
261
+ title: _input.title,
262
+ sourceBranch: _input.sourceBranch,
263
+ targetBranch: _input.targetBranch ?? 'main',
264
+ status: 'open',
265
+ author: 'local',
266
+ url: 'local',
267
+ };
268
+ },
269
+ async mergePR(_id, _strategy) {
270
+ return { sha: '', merged: false };
271
+ },
272
+ async getFileContents(_path, _ref) {
273
+ throw new Error('getFileContents: local-only source control has no remote to read from');
274
+ },
275
+ async listChangedFiles(_prId) {
276
+ return [];
277
+ },
278
+ async setCommitStatus(_sha, _status) {
279
+ /* no-op: no remote to post status to */
280
+ },
281
+ watchPREvents(_filter) {
282
+ return {
283
+ async *[Symbol.asyncIterator]() {
284
+ /* no-op: no remote events in local-only mode */
285
+ },
286
+ };
287
+ },
288
+ };
289
+ }
290
+ /**
291
+ * Resolve a SourceControl adapter from the AdapterBinding whose
292
+ * `spec.interface === 'SourceControl'`.
293
+ *
294
+ * Resolution order (first wins):
295
+ * 1. `type: github` → existing GitHub adapter using `spec.config` or fallback.
296
+ * 2. `type: gitlab` → GitLab adapter with `spec.config.url` (required for
297
+ * self-hosted) and optional `spec.config.token.secretRef`.
298
+ * 3. `type: local` → local-only no-op adapter that skips push/create-pr.
299
+ * 4. No SourceControl AdapterBinding present → GitHub (current default, no regression).
300
+ *
301
+ * When multiple SourceControl bindings exist, the FIRST one wins. This is an
302
+ * intentional design choice — multiple SourceControl bindings are unusual and
303
+ * the pipeline has a single active remote. If this constraint is too tight,
304
+ * escalate to the Decision Catalog before changing it.
305
+ */
306
+ export function resolveSourceControlFromConfig(config, fallbackGitHubConfig) {
307
+ const bindings = (config.adapterBindings ?? []).filter((b) => b.spec.interface === 'SourceControl');
308
+ if (bindings.length === 0) {
309
+ // No SourceControl binding — default to GitHub exactly as before (AC #2).
310
+ return createGitHubSourceControl(fallbackGitHubConfig);
311
+ }
312
+ // First binding wins (see JSDoc above for rationale).
313
+ const binding = bindings[0];
314
+ const cfg = binding.spec.config ?? {};
315
+ switch (binding.spec.type) {
316
+ case 'github': {
317
+ // Honor spec.config.token.secretRef when present (consistent with the GitLab case).
318
+ // Fall back to the fallback token so existing GitHub adopters are unaffected.
319
+ const tokenSecretRef = cfg.token?.secretRef ??
320
+ fallbackGitHubConfig.token.secretRef;
321
+ return createGitHubSourceControl({
322
+ org: cfg.org ?? fallbackGitHubConfig.org,
323
+ repo: cfg.repo ?? fallbackGitHubConfig.repo,
324
+ token: { secretRef: tokenSecretRef },
325
+ });
326
+ }
327
+ case 'gitlab': {
328
+ // `url` is required for self-hosted GitLab; defaults to gitlab.com for SaaS.
329
+ const baseUrl = cfg.url ?? 'https://gitlab.com';
330
+ // projectId is required — an empty value produces confusing API responses
331
+ // (encodeURIComponent('') → '' → the request hits /api/v4/projects/ which
332
+ // returns the list of all projects, not the configured one).
333
+ const projectId = cfg.projectId;
334
+ if (!projectId && projectId !== 0) {
335
+ throw new Error(`SourceControl AdapterBinding '${binding.metadata.name}' (type: gitlab) is missing a required ` +
336
+ `spec.config.projectId. Set it to the numeric project ID or the URL-encoded path ` +
337
+ `(e.g. "group%2Fsubgroup%2Fproject" or 12345).`);
338
+ }
339
+ // Token: use secretRef from config if present, else fall back to env GITLAB_TOKEN.
340
+ const tokenSecretRef = cfg.token?.secretRef ?? 'gitlab-token';
341
+ const gitLabCfg = {
342
+ baseUrl,
343
+ projectId,
344
+ token: { secretRef: tokenSecretRef },
345
+ };
346
+ return createGitLabSourceControl(gitLabCfg);
347
+ }
348
+ case 'local': {
349
+ return createLocalSourceControl();
350
+ }
351
+ default: {
352
+ // Unknown type — emit a warning so mistyped types (e.g. 'bitbucket') surface
353
+ // visibly, then fall back to GitHub so the pipeline keeps running.
354
+ console.warn(`[ai-sdlc] SourceControl AdapterBinding '${binding.metadata.name}' has unknown type ` +
355
+ `'${binding.spec.type}'. Falling back to the GitHub adapter. ` +
356
+ `Supported types: github, gitlab, local.`);
357
+ return createGitHubSourceControl(fallbackGitHubConfig);
358
+ }
359
+ }
360
+ }
231
361
  // Direct re-exports (passthrough)
232
362
  export {
233
363
  // Core adapters
234
- createGitHubCIPipeline, createDockerSandbox, createLinearIssueTracker, resolveSecret,
364
+ createGitHubCIPipeline, createGitHubSourceControl, createDockerSandbox, createLinearIssueTracker, resolveSecret,
235
365
  // Production adapters
236
366
  createGitLabSourceControl, createGitLabCIPipeline, createJiraIssueTracker,
237
367
  // Backlog.md adapter
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Admission-subset composite (RFC-0008 §A.6).
2
+ * Admission-subset composite (RFC-0008 §A.6 + RFC-0009 Phase 2.1).
3
3
  *
4
4
  * The admission gate uses a subset of the full PPA composite:
5
5
  *
@@ -23,10 +23,18 @@
23
23
  * The `override` bypass is preserved at position 1: when the input has
24
24
  * `override: true`, we return `composite = Infinity` without running
25
25
  * the admission math — identical to the legacy PPA behaviour.
26
+ *
27
+ * **RFC-0009 Phase 2.1 tessellation extension:**
28
+ * When `AdmissionCompositeOptions.tessellationContext` is provided, the
29
+ * composite routes SA and Eρ₄ (designSystemReadiness) through soul scope
30
+ * per RFC-0009 §6. See `tessellation-admission.ts` for the routing algorithm.
26
31
  */
27
32
  import type { PriorityConfig, PriorityInput, PriorityScore } from '@ai-sdlc/reference';
28
33
  import type { AdmissionInput } from './admission-score.js';
29
34
  import { type AdmissionHumanCurveResult } from './admission-hc.js';
35
+ import { type TessellationContext, type TessellatedSaResult } from './tessellation-admission.js';
36
+ import { type VariantContext, type VariantScopedSaResult } from './variant-admission.js';
37
+ import { type ComplianceClearanceContext, type ComplianceClearanceResult } from './compliance-clearance.js';
30
38
  /**
31
39
  * Result of the admission composite — returns a `PriorityScore`
32
40
  * (backward-compatible with `scoreIssueForAdmission` consumers) plus
@@ -44,6 +52,33 @@ export interface AdmissionComposite {
44
52
  designSystemReadiness: number;
45
53
  executionReality: number;
46
54
  humanCurve: AdmissionHumanCurveResult;
55
+ /**
56
+ * RFC-0009 Phase 2.1 — tessellation routing result.
57
+ * Present when `AdmissionCompositeOptions.tessellationContext` was
58
+ * supplied. Absent (undefined) when the single-DID path was used.
59
+ */
60
+ tessellation?: TessellatedSaResult;
61
+ /**
62
+ * RFC-0017 Phase 2 — variant routing result.
63
+ * Present when `AdmissionCompositeOptions.variantContext` was supplied.
64
+ * Absent (undefined) when the single-soul path was used (backward-compat).
65
+ *
66
+ * `variantScopedSa` represents the variant-resolved soul-alignment value
67
+ * that REPLACED the tessellation/soul-aggregate `soulAlignment` when the
68
+ * work item declared `targetedVariants` of one of the affected souls.
69
+ */
70
+ variant?: VariantScopedSaResult;
71
+ /**
72
+ * RFC-0009 Phase 4.1 — Eρ₅ Compliance Clearance result.
73
+ * Present when `AdmissionCompositeOptions.complianceClearanceContext`
74
+ * was supplied AND the adopter has opted in (`enabled: true`).
75
+ *
76
+ * When `er5 === 0` the composite is GATED to zero — the work item cannot
77
+ * proceed without resolving the violation. The `routingPath` field
78
+ * surfaces the decision branch for audit purposes; when the adopter has
79
+ * not opted in, the breakdown field is elided (backward-compat).
80
+ */
81
+ complianceClearance?: ComplianceClearanceResult;
47
82
  };
48
83
  }
49
84
  export interface AdmissionCompositeOptions {
@@ -62,8 +97,84 @@ export interface AdmissionCompositeOptions {
62
97
  * cannot extract. Only fields with defined values overwrite.
63
98
  */
64
99
  priorityInputOverrides?: Partial<PriorityInput>;
100
+ /**
101
+ * RFC-0009 Phase 2.1 — Tessellated DID context.
102
+ *
103
+ * When present, SA and Eρ₄ (designSystemReadiness) are computed using
104
+ * soul-scope routing per RFC-0009 §6 instead of the platform-aggregate
105
+ * DSB. The routing algorithm:
106
+ *
107
+ * resolveAffectedSouls(w) → scope filter from dep-graph snapshot
108
+ * |souls| == 0 → min over ALL souls (substrate-only degenerate)
109
+ * |souls| == 1 → score against that soul's DSB
110
+ * |souls| > 1 → crossSoulScoringRule (default `min`) over affected souls
111
+ *
112
+ * @see tessellation-admission.ts + spec/rfcs/RFC-0009 §6
113
+ */
114
+ tessellationContext?: TessellationContext;
115
+ /**
116
+ * RFC-0017 Phase 2 — variant-scope routing context.
117
+ *
118
+ * When present AND the work item declares `targetedVariants[]` of one of
119
+ * the affected souls, the composite's `soulAlignment` value is REFINED
120
+ * by per-variant Sα₁ + Sα₂ scoring per RFC-0017 §5.4:
121
+ *
122
+ * resolveTargetedVariants(w) → variant scope filter from work-item targeting
123
+ * |targets| == 0 → backward-compat: soul-aggregate Sα/Eρ₄ unchanged
124
+ * |targets| == 1 → variant's per-variant Sα₁ + Sα₂ replaces soul-aggregate
125
+ * |targets| > 1 → crossVariantAggregation (default `min`) over targeted variants
126
+ *
127
+ * Composes cleanly with `tessellationContext` — soul-scope tessellation runs
128
+ * first, then variant routing refines the resulting Sα/Eρ₄ to variant scope.
129
+ * Work items WITHOUT `targetedVariants` preserve the tessellation result
130
+ * unchanged (backward-compat per RFC-0017 §7).
131
+ *
132
+ * @see variant-admission.ts + spec/rfcs/RFC-0017 §5.4 + §6.2
133
+ */
134
+ variantContext?: VariantContext;
135
+ /**
136
+ * RFC-0009 Phase 4.1 — Eρ₅ Compliance Clearance context.
137
+ *
138
+ * When present AND `enabled: true`, the admission composite applies the
139
+ * Eρ₅ multiplier (categorical 0/1 gating) per RFC-0009 §7.1:
140
+ *
141
+ * Eρ₅ = 0 if any declared regime is violated → composite gated to 0
142
+ * Eρ₅ = 1 otherwise → composite unchanged
143
+ *
144
+ * When absent OR `enabled: false`, Eρ₅ is not applied — backward-compatible
145
+ * with Phase 2/3 behavior. The opt-in gate is intentional per RFC-0009 §10
146
+ * Phase 4; promotion to default-on is subject to ecosystem feedback.
147
+ *
148
+ * The regime set is the union of (a) each affected soul's
149
+ * `triad.engineering.complianceRegimes` (passed through
150
+ * `perSoulRegimes`) and (b) any RFC-0022 `CompliancePosture` regimes
151
+ * (passed through `posture`). Both pass the OQ-5 hard-regulatory scope
152
+ * filter; soft regimes are filtered at declaration time.
153
+ *
154
+ * Composes with `tessellationContext` — the soul scope from tessellation
155
+ * routing is used to select which souls' regimes apply to this work item.
156
+ *
157
+ * @see compliance-clearance.ts + spec/rfcs/RFC-0009 §7.1 + §10
158
+ * @see spec/rfcs/RFC-0022 (consumption surface)
159
+ */
160
+ complianceClearanceContext?: ComplianceClearanceContext;
65
161
  }
66
162
  export declare function computeAdmissionComposite(input: AdmissionInput, config?: PriorityConfig, options?: AdmissionCompositeOptions): AdmissionComposite;
163
+ /**
164
+ * Combine variant-scope Sα₁ (audience resonance) + Sα₂ (vibe coherence) into
165
+ * a single soul-alignment value for the admission composite's SA pillar.
166
+ *
167
+ * The legacy admission composite uses a single `soulAlignment` scalar that
168
+ * conceptually rolls Sα₁ + Sα₂ together (RFC-0008 §A.6 SA pillar). When
169
+ * variant routing splits them out per RFC-0017 §5.4, we recombine via the
170
+ * arithmetic mean — equal weighting matches the soul-aggregate combination
171
+ * implied by `pillar-breakdown.ts` and avoids favouring one Sα facet over
172
+ * the other when both are equally variant-bounded.
173
+ *
174
+ * Exported for unit testing + so downstream callers can replicate the
175
+ * combination logic if they need to surface the constituent pieces.
176
+ */
177
+ export declare function combineVariantSaForSoulAlignment(sa1: number, sa2: number): number;
67
178
  /**
68
179
  * Compute admission confidence in [0, 1] as a 50/50 blend of two
69
180
  * independent evidence channels:
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Admission-subset composite (RFC-0008 §A.6).
2
+ * Admission-subset composite (RFC-0008 §A.6 + RFC-0009 Phase 2.1).
3
3
  *
4
4
  * The admission gate uses a subset of the full PPA composite:
5
5
  *
@@ -23,10 +23,18 @@
23
23
  * The `override` bypass is preserved at position 1: when the input has
24
24
  * `override: true`, we return `composite = Infinity` without running
25
25
  * the admission math — identical to the legacy PPA behaviour.
26
+ *
27
+ * **RFC-0009 Phase 2.1 tessellation extension:**
28
+ * When `AdmissionCompositeOptions.tessellationContext` is provided, the
29
+ * composite routes SA and Eρ₄ (designSystemReadiness) through soul scope
30
+ * per RFC-0009 §6. See `tessellation-admission.ts` for the routing algorithm.
26
31
  */
27
32
  import { mapIssueToPriorityInput } from './admission-score.js';
28
33
  import { computeAutonomyFactor, computeDefectRiskFactor, computeReadinessFromDesignSystemContext, } from './admission-enrichment.js';
29
34
  import { computeAdmissionHumanCurve } from './admission-hc.js';
35
+ import { computeTessellatedScores, } from './tessellation-admission.js';
36
+ import { computeVariantScopedScores, } from './variant-admission.js';
37
+ import { computeComplianceClearance, } from './compliance-clearance.js';
30
38
  const DEFAULT_SIGNAL = 0.5;
31
39
  export function computeAdmissionComposite(input, config, options) {
32
40
  const timestamp = new Date().toISOString();
@@ -77,7 +85,7 @@ export function computeAdmissionComposite(input, config, options) {
77
85
  };
78
86
  }
79
87
  // ── SA (soul alignment) — SA-1 override (M5) or label-based fallback ──
80
- const soulAlignment = clamp01(options?.soulAlignmentOverride ?? priorityInput.soulAlignment ?? DEFAULT_SIGNAL);
88
+ const baseSoulAlignment = clamp01(options?.soulAlignmentOverride ?? priorityInput.soulAlignment ?? DEFAULT_SIGNAL);
81
89
  // ── D-pi_adjusted ─────────────────────────────────────────────
82
90
  const demand = priorityInput.demandSignal ?? DEFAULT_SIGNAL;
83
91
  const consensus = priorityInput.teamConsensus ?? DEFAULT_SIGNAL;
@@ -91,12 +99,49 @@ export function computeAdmissionComposite(input, config, options) {
91
99
  const complexity = priorityInput.complexity ?? 5;
92
100
  const baseExecutionReality = clamp01(1 - complexity / 10);
93
101
  const autonomyFactor = computeAutonomyFactor(input.autonomyContext);
94
- const designSystemReadiness = computeReadinessFromDesignSystemContext(input.designSystemContext);
102
+ const baseDesignSystemReadiness = computeReadinessFromDesignSystemContext(input.designSystemContext);
103
+ // ── RFC-0009 Phase 2.1: tessellation soul-scope routing ────────
104
+ // When tessellationContext is present, route SA and Eρ₄ through soul
105
+ // scope per RFC-0009 §6. When absent, single-DID path applies.
106
+ //
107
+ // Prefer the canonical workItemId (e.g. "AISDLC-313") over the GitHub-style
108
+ // "#313" so the dep-graph snapshot lookup matches the backlog task ID format.
109
+ const workItemId = input.workItemId ?? `#${input.issueNumber}`;
110
+ const tessellationResult = computeTessellatedScores(workItemId, baseSoulAlignment, baseDesignSystemReadiness, options?.tessellationContext);
111
+ // ── RFC-0017 Phase 2: variant-scope refinement ────────────────
112
+ // When `variantContext` is provided AND the work item declares
113
+ // `targetedVariants[]`, the per-variant Sα₁ + Sα₂ scores REFINE the
114
+ // soul-aggregate `soulAlignment` from tessellation. The combined SA score
115
+ // fed to the composite is the mean of the variant-resolved Sα₁ + Sα₂
116
+ // (both feed soul-alignment per RFC-0008 §A.6 SA pillar definition).
117
+ // When no targeting declared OR variantContext is absent, this is a
118
+ // passthrough — the tessellation result is preserved unchanged
119
+ // (backward-compat per RFC-0017 §7).
120
+ const variantResult = computeVariantScopedScores(workItemId, tessellationResult.soulAlignment, tessellationResult.soulAlignment, options?.variantContext);
121
+ const soulAlignment = variantResult.routingPath === 'no-variant-routing'
122
+ ? tessellationResult.soulAlignment
123
+ : combineVariantSaForSoulAlignment(variantResult.sa1, variantResult.sa2);
124
+ const designSystemReadiness = tessellationResult.er4;
95
125
  const executionReality = Math.min(baseExecutionReality * autonomyFactor, designSystemReadiness);
126
+ // ── RFC-0009 Phase 4.1: Eρ₅ Compliance Clearance ──────────────
127
+ // Categorical 0/1 gating per §7.1. When the adopter has opted in AND any
128
+ // declared regime is violated, the composite is gated to 0 (work item
129
+ // cannot proceed without resolving the violation). When the adopter has
130
+ // not opted in (default), this returns er5=1 and the composite is
131
+ // unchanged — full backward compatibility with Phase 2/3 behavior.
132
+ //
133
+ // Uses the same `affectedSoulIds` resolved by tessellation routing, so the
134
+ // regime check follows the work item's actual soul scope. Substrate-only
135
+ // work (affectedSoulIds = []) falls through to the `__platform` sentinel.
136
+ const complianceClearanceResult = computeComplianceClearance(workItemId, tessellationResult.affectedSoulIds, options?.complianceClearanceContext);
96
137
  // ── HC (tanh-compressed with HC_design) ───────────────────────
97
138
  const humanCurve = computeAdmissionHumanCurve(input);
98
139
  // ── §A.6 admission subset composite ───────────────────────────
99
- const composite = soulAlignment * demandPressureAdjusted * executionReality * (1 + humanCurve.hcComposite);
140
+ const composite = soulAlignment *
141
+ demandPressureAdjusted *
142
+ executionReality *
143
+ complianceClearanceResult.er5 *
144
+ (1 + humanCurve.hcComposite);
100
145
  const score = {
101
146
  composite,
102
147
  dimensions: {
@@ -116,6 +161,20 @@ export function computeAdmissionComposite(input, config, options) {
116
161
  confidence: computeAdmissionConfidence(input, priorityInput, options),
117
162
  timestamp,
118
163
  };
164
+ // Only include tessellation breakdown when the caller provided a context
165
+ // (non-tessellated path produces routingPath: 'non-tessellated' but we
166
+ // elide the field to preserve backward compat with callers that don't
167
+ // destructure the breakdown exhaustively).
168
+ const tessellationBreakdown = options?.tessellationContext !== undefined ? tessellationResult : undefined;
169
+ // Variant breakdown surfaced ONLY when the caller wired a variant context
170
+ // AND the work item actually targeted variants. Pure backward-compat for
171
+ // callers on the soul-aggregate path.
172
+ const variantBreakdown = options?.variantContext !== undefined && variantResult.routingPath !== 'no-variant-routing'
173
+ ? variantResult
174
+ : undefined;
175
+ // Compliance clearance breakdown surfaced ONLY when the adopter opted in
176
+ // (avoids polluting the breakdown shape for Phase 2/3 callers).
177
+ const complianceClearanceBreakdown = options?.complianceClearanceContext?.enabled === true ? complianceClearanceResult : undefined;
119
178
  return {
120
179
  score,
121
180
  breakdown: {
@@ -128,9 +187,31 @@ export function computeAdmissionComposite(input, config, options) {
128
187
  designSystemReadiness,
129
188
  executionReality,
130
189
  humanCurve,
190
+ ...(tessellationBreakdown !== undefined ? { tessellation: tessellationBreakdown } : {}),
191
+ ...(variantBreakdown !== undefined ? { variant: variantBreakdown } : {}),
192
+ ...(complianceClearanceBreakdown !== undefined
193
+ ? { complianceClearance: complianceClearanceBreakdown }
194
+ : {}),
131
195
  },
132
196
  };
133
197
  }
198
+ /**
199
+ * Combine variant-scope Sα₁ (audience resonance) + Sα₂ (vibe coherence) into
200
+ * a single soul-alignment value for the admission composite's SA pillar.
201
+ *
202
+ * The legacy admission composite uses a single `soulAlignment` scalar that
203
+ * conceptually rolls Sα₁ + Sα₂ together (RFC-0008 §A.6 SA pillar). When
204
+ * variant routing splits them out per RFC-0017 §5.4, we recombine via the
205
+ * arithmetic mean — equal weighting matches the soul-aggregate combination
206
+ * implied by `pillar-breakdown.ts` and avoids favouring one Sα facet over
207
+ * the other when both are equally variant-bounded.
208
+ *
209
+ * Exported for unit testing + so downstream callers can replicate the
210
+ * combination logic if they need to surface the constituent pieces.
211
+ */
212
+ export function combineVariantSaForSoulAlignment(sa1, sa2) {
213
+ return clamp01((sa1 + sa2) / 2);
214
+ }
134
215
  // ── Admission confidence (AISDLC-172) ──────────────────────────────────
135
216
  /**
136
217
  * `PriorityInput` fields that the admission mapper (`mapIssueToPriorityInput`)
@@ -92,15 +92,24 @@ export declare function complexityToAutonomyLevel(complexity: number): number;
92
92
  */
93
93
  export declare function computeAutonomyFactor(ctx: AutonomyContext | undefined): number;
94
94
  /**
95
- * Compute the C5 design-authority weight per §A.5.
95
+ * Compute the C5 design-authority weight per §A.5 (Source 1) plus
96
+ * the RFC-0008 §C5 Source 3 compliance-assessment signal.
96
97
  *
98
+ * Source 1 (principal participation):
97
99
  * non-authority → 0.0
98
100
  * authority + positive → +0.6 × modulation
99
101
  * authority + negative → -0.4 × modulation
100
102
  * authority + no type → +0.3 × modulation
101
103
  *
102
- * where `modulation = 1.2 - areaComplianceScore` when the score is
103
- * supplied, else `1.0` (unmodulated base weight).
104
+ * where `modulation = 1.2 - areaComplianceScore` when the score is
105
+ * supplied, else `1.0` (unmodulated base weight).
106
+ *
107
+ * Source 3 (compliance assessment — additive):
108
+ * `signal.complianceSignal` contributes directly when present (see
109
+ * `buildDesignAuthoritySignal`). This fires from DSB health data
110
+ * alone — no principal participation required — so a fully-loaded DSB
111
+ * with high token compliance produces `hcDesign > 0` regardless of
112
+ * whether a design-authority principal commented on the issue.
104
113
  */
105
114
  export declare function computeDesignAuthorityWeight(signal: DesignAuthoritySignal | undefined): number;
106
115
  /**
@@ -204,23 +204,65 @@ const DESIGN_AUTHORITY_BASE_WEIGHT = Object.freeze({
204
204
  unspecified: 0.3,
205
205
  });
206
206
  /**
207
- * Compute the C5 design-authority weight per §A.5.
207
+ * Compute the C5 design-authority weight per §A.5 (Source 1) plus
208
+ * the RFC-0008 §C5 Source 3 compliance-assessment signal.
208
209
  *
210
+ * Source 1 (principal participation):
209
211
  * non-authority → 0.0
210
212
  * authority + positive → +0.6 × modulation
211
213
  * authority + negative → -0.4 × modulation
212
214
  * authority + no type → +0.3 × modulation
213
215
  *
214
- * where `modulation = 1.2 - areaComplianceScore` when the score is
215
- * supplied, else `1.0` (unmodulated base weight).
216
+ * where `modulation = 1.2 - areaComplianceScore` when the score is
217
+ * supplied, else `1.0` (unmodulated base weight).
218
+ *
219
+ * Source 3 (compliance assessment — additive):
220
+ * `signal.complianceSignal` contributes directly when present (see
221
+ * `buildDesignAuthoritySignal`). This fires from DSB health data
222
+ * alone — no principal participation required — so a fully-loaded DSB
223
+ * with high token compliance produces `hcDesign > 0` regardless of
224
+ * whether a design-authority principal commented on the issue.
216
225
  */
217
226
  export function computeDesignAuthorityWeight(signal) {
218
- if (!signal || !signal.isDesignAuthority)
227
+ if (!signal)
228
+ return 0;
229
+ // Source 1: principal-participation weight.
230
+ const participationWeight = signal.isDesignAuthority
231
+ ? (() => {
232
+ const signalType = signal.signalType ?? 'unspecified';
233
+ const base = DESIGN_AUTHORITY_BASE_WEIGHT[signalType];
234
+ const modulation = signal.areaComplianceScore !== undefined ? 1.2 - signal.areaComplianceScore : 1;
235
+ return base * modulation;
236
+ })()
237
+ : 0;
238
+ // Source 3: compliance-assessment signal (additive).
239
+ const complianceWeight = signal.complianceSignal ?? 0;
240
+ return participationWeight + complianceWeight;
241
+ }
242
+ /**
243
+ * RFC-0008 §C5 Source 3 — map DSB token-compliance coverage to a
244
+ * small automatic signal for HC_design.
245
+ *
246
+ * >= 0.8 (80 %) → +0.3 (design system is healthy — small positive)
247
+ * < 0.4 (40 %) → −0.2 (design system is fragile — small negative)
248
+ * otherwise → 0.0 (neutral)
249
+ *
250
+ * The signal is intentionally small so it cannot outweigh Source 1
251
+ * (principal-participation, which carries base weights up to ±0.6).
252
+ * A DSB with missing status data returns 0 (graceful degradation).
253
+ */
254
+ function computeComplianceSignal(binding) {
255
+ const rawCoverage = binding.status?.tokenCompliance?.currentCoverage;
256
+ if (rawCoverage === undefined || Number.isNaN(rawCoverage))
219
257
  return 0;
220
- const signalType = signal.signalType ?? 'unspecified';
221
- const base = DESIGN_AUTHORITY_BASE_WEIGHT[signalType];
222
- const modulation = signal.areaComplianceScore !== undefined ? 1.2 - signal.areaComplianceScore : 1;
223
- return base * modulation;
258
+ // Normalise: values > 1 are percent (0-100), values ≤ 1 are already fractions.
259
+ const coverage = rawCoverage > 1 ? rawCoverage / 100 : rawCoverage;
260
+ const clamped = Math.min(1, Math.max(0, coverage));
261
+ if (clamped >= 0.8)
262
+ return 0.3;
263
+ if (clamped < 0.4)
264
+ return -0.2;
265
+ return 0;
224
266
  }
225
267
  function buildDesignAuthoritySignal(input, ctx) {
226
268
  if (!ctx.designSystemBinding)
@@ -228,23 +270,32 @@ function buildDesignAuthoritySignal(input, ctx) {
228
270
  // AISDLC-171: surface whether the DSB declares any design authority
229
271
  // principals at all. This is a diagnostic flag — it does NOT participate
230
272
  // in the HC_design weight calculation (per RFC-0008 §14.2, only
231
- // principals who participate as author/commenter can emit HC_design).
232
- // Surfacing it lets pillarBreakdown distinguish "DSB has no design
233
- // authority structure" from "DSB has design authority but no principal
234
- // participated in this issue".
273
+ // principals who participate as author/commenter can emit HC_design via
274
+ // Source 1). The flag lets pillarBreakdown distinguish "DSB has no
275
+ // design authority structure" from "DSB has design authority but no
276
+ // principal participated in this issue".
235
277
  const principalsDeclared = (ctx.designSystemBinding.spec.stewardship.designAuthority.principals?.length ?? 0) > 0;
236
278
  const { isDesignAuthority, signalType } = checkDesignAuthority({
237
279
  authorLogin: input.authorLogin,
238
280
  commenterLogins: input.commenterLogins,
239
281
  labels: input.labels,
240
282
  }, ctx.designSystemBinding);
283
+ // RFC-0008 §C5 Source 3: compliance-assessment auto-signal from DSB health.
284
+ // Fires regardless of principal participation — gives a fully-loaded DSB
285
+ // a non-zero hcDesign even when no design-authority principal commented.
286
+ const complianceSignal = computeComplianceSignal(ctx.designSystemBinding);
241
287
  if (!isDesignAuthority) {
242
- return { isDesignAuthority: false, principalsDeclared };
288
+ return {
289
+ isDesignAuthority: false,
290
+ principalsDeclared,
291
+ ...(complianceSignal !== 0 ? { complianceSignal } : {}),
292
+ };
243
293
  }
244
294
  return {
245
295
  isDesignAuthority: true,
246
296
  signalType,
247
297
  principalsDeclared,
298
+ ...(complianceSignal !== 0 ? { complianceSignal } : {}),
248
299
  ...(ctx.areaComplianceScore !== undefined
249
300
  ? { areaComplianceScore: ctx.areaComplianceScore }
250
301
  : {}),