@ai-sdlc/orchestrator 0.9.0 → 0.10.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.
@@ -12,6 +12,20 @@
12
12
  * orchestrator version that ran init, with an inline opt-out hint.
13
13
  * - Skip `.cursor/mcp.json` unless Cursor is detected (project-local
14
14
  * `.cursor/`, user-global `~/.cursor/`) or `--cursor` is passed.
15
+ *
16
+ * AISDLC-143 enhancements (Q4(b) of the quality-gate redesign):
17
+ * - Default invocation is now an interactive WIZARD (DoR / attestation /
18
+ * classifier / branch-protection prompts) so adopters get a guided
19
+ * bootstrap instead of having to read the docs to discover features.
20
+ * - `--yes` short-circuits all prompts to "accept defaults" (CI/scripts).
21
+ * - `--with-X` flags opt into individual features without prompting.
22
+ * - `--add <feature>` extends an already-initialized repo with a single
23
+ * feature, idempotently — re-running on an initialized repo is safe.
24
+ * - `.github/workflows/ai-sdlc-gate.yml` is scaffolded UNCONDITIONALLY
25
+ * so every adopter gets the `ai-sdlc/pr-ready` rollup check on day one
26
+ * (Q1: prescriptive default).
27
+ * - A "next steps" summary closes the run with operator action items
28
+ * conditional on which features were chosen.
15
29
  */
16
30
  import { Command } from 'commander';
17
31
  import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync } from 'node:fs';
@@ -20,7 +34,8 @@ import { detectAgentsDetailed, installMcpServer } from './mcp-setup.js';
20
34
  import { detectWorkspace, generateWorkspaceYaml } from './workspace-detect.js';
21
35
  import { detectGitRemote, applyRemoteToPipelineYaml } from './git-remote.js';
22
36
  import { resolveVersions, formatVersionBlock } from '../versions.js';
23
- const PIPELINE_YAML = `apiVersion: ai-sdlc.io/v1alpha1
37
+ import { applyFeatureSelection, buildProductionAdapters, ensureClaudeMdPointer, renderNextSteps, resolveFeatureSelection, } from './init-features.js';
38
+ export const PIPELINE_YAML = `apiVersion: ai-sdlc.io/v1alpha1
24
39
  kind: Pipeline
25
40
  metadata:
26
41
  name: default
@@ -48,6 +63,23 @@ spec:
48
63
  - name: review
49
64
  qualityGates:
50
65
  - default-gates
66
+ # backlog: holds settings specific to the backlog-task (/ai-sdlc execute)
67
+ # workflow. These were formerly in a separate pipeline-backlog.yaml file
68
+ # (deprecated by AISDLC-245.5). Slash commands + pipeline-cli readers
69
+ # prefer this canonical location.
70
+ backlog:
71
+ branching:
72
+ pattern: 'ai-sdlc/{issueIdLower}-{slug}'
73
+ targetBranch: main
74
+ cleanup: on-merge
75
+ pullRequest:
76
+ titleTemplate: 'feat: {issueTitle} ({issueId})'
77
+ descriptionSections:
78
+ - summary
79
+ - changes
80
+ - closes
81
+ includeProvenance: true
82
+ closeKeyword: References
51
83
  `;
52
84
  export const AGENT_ROLE_TIERS = ['coding', 'research', 'meta'];
53
85
  const AGENT_ROLE_YAML_CODING = `apiVersion: ai-sdlc.io/v1alpha1
@@ -301,6 +333,45 @@ function initWorkspaceRoot(workspacePath, repos, configDirName, dryRun) {
301
333
  console.log(` created workspace.yaml`);
302
334
  }
303
335
  }
336
+ /**
337
+ * Build the WizardFlags struct from Commander's parsed options. Pulled
338
+ * out into a function so tests can drive the wizard pipeline directly
339
+ * without going through Commander's stateful option store.
340
+ */
341
+ export function buildWizardFlags(opts) {
342
+ const addRaw = typeof opts.add === 'string' ? opts.add : undefined;
343
+ let add;
344
+ if (addRaw === 'dor' ||
345
+ addRaw === 'attestation' ||
346
+ addRaw === 'classifier' ||
347
+ addRaw === 'branch-protection') {
348
+ add = addRaw;
349
+ }
350
+ return {
351
+ yes: !!opts.yes,
352
+ withDor: !!opts.withDor,
353
+ withAttestation: !!opts.withAttestation,
354
+ withClassifier: !!opts.withClassifier,
355
+ withBranchProtection: !!opts.withBranchProtection,
356
+ add,
357
+ dryRun: !!opts.dryRun,
358
+ };
359
+ }
360
+ /** Validate a `--add` arg and return either the normalized value or null+error. */
361
+ function validateAddArg(addRaw) {
362
+ if (addRaw === undefined || addRaw === null || addRaw === false)
363
+ return { ok: true };
364
+ if (typeof addRaw !== 'string')
365
+ return { ok: false, error: `--add must be a string` };
366
+ const allowed = ['dor', 'attestation', 'classifier', 'branch-protection'];
367
+ if (!allowed.includes(addRaw)) {
368
+ return {
369
+ ok: false,
370
+ error: `--add: unknown feature '${addRaw}'. Expected one of: ${allowed.join(', ')}.`,
371
+ };
372
+ }
373
+ return { ok: true, value: addRaw };
374
+ }
304
375
  export const initCommand = new Command('init')
305
376
  .description('Initialize AI-SDLC configuration in the current project')
306
377
  .option('--dry-run', 'Show what would be created without writing files')
@@ -308,6 +379,13 @@ export const initCommand = new Command('init')
308
379
  .option('--cursor', 'Force-install Cursor MCP config even if Cursor is not detected')
309
380
  .option('-d, --dir <path>', 'Config directory name', '.ai-sdlc')
310
381
  .option('--role <tier>', `Agent-role tool tier: ${AGENT_ROLE_TIERS.join(' | ')} (default: coding)`, 'coding')
382
+ // ── AISDLC-143 wizard flags ─────────────────────────────────────────
383
+ .option('-y, --yes', 'Accept all defaults (non-interactive; CI/scripts)')
384
+ .option('--with-dor', 'Scaffold Definition-of-Ready gate config + workflow')
385
+ .option('--with-attestation', 'Scaffold attestation infrastructure (audit-only)')
386
+ .option('--with-classifier', 'Scaffold review classifier config stub')
387
+ .option('--with-branch-protection', 'Apply recommended branch-protection rule to main (requires gh)')
388
+ .option('--add <feature>', 'Extend an already-initialized repo with a single feature: dor | attestation | classifier | branch-protection')
311
389
  .action(async (opts) => {
312
390
  const projectDir = process.cwd();
313
391
  const configDirName = opts.dir ?? '.ai-sdlc';
@@ -320,6 +398,41 @@ export const initCommand = new Command('init')
320
398
  }
321
399
  const tier = tierInput;
322
400
  const cursorOptIn = !!opts.cursor;
401
+ // ── Validate --add early so we error before doing any work. ────────
402
+ const addCheck = validateAddArg(opts.add);
403
+ if (!addCheck.ok) {
404
+ console.error(`Error: ${addCheck.error}`);
405
+ process.exitCode = 1;
406
+ return;
407
+ }
408
+ const flags = buildWizardFlags(opts);
409
+ // ── --add path: extend an already-initialized repo ────────────────
410
+ // AC #7: skip the "always-scaffold-baseline" path entirely; the
411
+ // wizard dispatcher's `--add` branch knows to write only the chosen
412
+ // feature's templates (no pipeline.yaml, no MCP setup, no workspace
413
+ // detection). This is the safe re-run path on a repo that already
414
+ // ran `ai-sdlc init` once.
415
+ if (flags.add) {
416
+ const adapters = buildProductionAdapters();
417
+ const selection = await resolveFeatureSelection(flags, adapters);
418
+ console.log(`Extending AI-SDLC config with --add ${flags.add}:`);
419
+ console.log('');
420
+ const result = await applyFeatureSelection(projectDir, selection, flags, adapters);
421
+ renderNextSteps(selection, result, adapters);
422
+ // Reviewer feedback (round 2, suggestion #5): when the operator
423
+ // explicitly requested branch-protection via a non-interactive flag
424
+ // (--add branch-protection here; --yes / --with-branch-protection
425
+ // in runWizardStage) and the apply failed (gh missing, not
426
+ // authenticated, etc.), surface a non-zero exit so CI scripts can
427
+ // detect failure instead of seeing the silent log line.
428
+ if (flags.add === 'branch-protection' &&
429
+ result.branchProtection &&
430
+ !result.branchProtection.applied &&
431
+ result.branchProtection.error) {
432
+ process.exitCode = 1;
433
+ }
434
+ return;
435
+ }
323
436
  // ── version provenance (AC #1) ────────────────────────────────────
324
437
  const versions = resolveVersions({ workDir: projectDir });
325
438
  console.log(formatVersionBlock(versions));
@@ -377,6 +490,12 @@ export const initCommand = new Command('init')
377
490
  }
378
491
  }
379
492
  console.log(`\nAI-SDLC workspace initialized in ${projectDir}/`);
493
+ // ── AISDLC-143 wizard (workspace root) ────────────────────────
494
+ // The wizard runs ONCE at the workspace root: the baseline gate
495
+ // workflow + per-feature workflows live at `<workspace-root>/.github/`,
496
+ // not in each child repo. Children share the same CI from the
497
+ // root since GHA workflows always live at the repo root anyway.
498
+ await runWizardStage(projectDir, flags);
380
499
  console.log(`Run 'ai-sdlc health' to verify your configuration.`);
381
500
  }
382
501
  else {
@@ -399,7 +518,45 @@ export const initCommand = new Command('init')
399
518
  }
400
519
  }
401
520
  console.log(`\nAI-SDLC config initialized in ${join(projectDir, configDirName)}/`);
521
+ // ── AISDLC-143 wizard (single-repo) ──────────────────────────
522
+ await runWizardStage(projectDir, flags);
402
523
  console.log(`Run 'ai-sdlc health' to verify your configuration.`);
403
524
  }
404
525
  });
526
+ /**
527
+ * Run the AISDLC-143 wizard stage: prompt the user (or short-circuit on
528
+ * --yes / --with-X), apply the chosen feature templates, append the
529
+ * CLAUDE.md pointer, and render the "next steps" summary.
530
+ *
531
+ * Pulled out of the inline action body so both the single-repo and
532
+ * workspace-root branches share the same wiring. Adapters are built
533
+ * once here (production = real disk writes; tests inject stubs by
534
+ * calling `applyFeatureSelection`/`renderNextSteps` directly).
535
+ */
536
+ async function runWizardStage(projectDir, flags) {
537
+ const adapters = buildProductionAdapters();
538
+ console.log('');
539
+ console.log('━━━ Feature wizard ━━━');
540
+ console.log('');
541
+ const selection = await resolveFeatureSelection(flags, adapters);
542
+ console.log('');
543
+ console.log('Scaffolding selected features:');
544
+ const result = await applyFeatureSelection(projectDir, selection, flags, adapters);
545
+ ensureClaudeMdPointer(projectDir, adapters, flags.dryRun);
546
+ renderNextSteps(selection, result, adapters);
547
+ // Reviewer feedback (round 2, suggestion #5): when branch-protection
548
+ // was non-interactively requested (--with-branch-protection or --yes,
549
+ // both used by CI scripts) and the apply failed (gh missing, not
550
+ // authenticated, etc.), surface a non-zero exit so CI can detect
551
+ // failure instead of seeing the silent log line. Interactive prompt
552
+ // answers don't trip this — the human already saw the error and can
553
+ // re-run `ai-sdlc init --add branch-protection` themselves.
554
+ const branchProtectionRequestedNonInteractively = flags.yes || flags.withBranchProtection;
555
+ if (branchProtectionRequestedNonInteractively &&
556
+ result.branchProtection &&
557
+ !result.branchProtection.applied &&
558
+ result.branchProtection.error) {
559
+ process.exitCode = 1;
560
+ }
561
+ }
405
562
  //# sourceMappingURL=init.js.map
@@ -5,6 +5,19 @@
5
5
  * `designAuthority` principal on the resolved DesignSystemBinding, and
6
6
  * parses the explicit signal type from issue labels (preferred) or
7
7
  * structured comment markers.
8
+ *
9
+ * AISDLC-171 / RFC-0009 §13 OQ-8 anchor:
10
+ * HC_design fires ONLY when one of the issue's participants (author or
11
+ * commenter) is a principal listed in
12
+ * `DesignSystemBinding.spec.stewardship.designAuthority.principals`.
13
+ * Per RFC-0008 §14.2, non-principal participants' design opinions are
14
+ * routed through HC_consensus (not HC_design) — this prevents anyone
15
+ * from emitting full-weight HC_design signals just by labeling an
16
+ * issue. To distinguish "no DSB at all" from "DSB exists but no
17
+ * principal participated" at the breakdown surface, see
18
+ * `principalsDeclared` on `DesignAuthoritySignal` (set by
19
+ * `buildDesignAuthoritySignal` in `admission-enrichment.ts`) and
20
+ * `pillarBreakdown.shared.hcComposite.designAuthorityConfigured`.
8
21
  */
9
22
  import type { DesignSystemBinding } from '@ai-sdlc/reference';
10
23
  import type { DesignAuthoritySignalType } from './admission-score.js';
@@ -5,6 +5,19 @@
5
5
  * `designAuthority` principal on the resolved DesignSystemBinding, and
6
6
  * parses the explicit signal type from issue labels (preferred) or
7
7
  * structured comment markers.
8
+ *
9
+ * AISDLC-171 / RFC-0009 §13 OQ-8 anchor:
10
+ * HC_design fires ONLY when one of the issue's participants (author or
11
+ * commenter) is a principal listed in
12
+ * `DesignSystemBinding.spec.stewardship.designAuthority.principals`.
13
+ * Per RFC-0008 §14.2, non-principal participants' design opinions are
14
+ * routed through HC_consensus (not HC_design) — this prevents anyone
15
+ * from emitting full-weight HC_design signals just by labeling an
16
+ * issue. To distinguish "no DSB at all" from "DSB exists but no
17
+ * principal participated" at the breakdown surface, see
18
+ * `principalsDeclared` on `DesignAuthoritySignal` (set by
19
+ * `buildDesignAuthoritySignal` in `admission-enrichment.ts`) and
20
+ * `pillarBreakdown.shared.hcComposite.designAuthorityConfigured`.
8
21
  */
9
22
  /**
10
23
  * Label-encoded design signal types. Short, stable slugs keep GitHub
package/dist/execute.js CHANGED
@@ -45,11 +45,14 @@ export async function executePipeline(issueId, options = {}) {
45
45
  (options.useStructuredLogger ? createStructuredConsoleLogger() : createLogger());
46
46
  const auditLog = options.auditLog ?? createDefaultAuditLog(workDir);
47
47
  const metricStore = options.metricStore;
48
- // RFC-0010 §6/§7 Phase 2: parallelism opt-in. When AI_SDLC_PARALLELISM is unset (default),
49
- // execution proceeds serially exactly as today. When set to 'experimental' or 'on', a
50
- // WorktreePoolManager is instantiated for the worker-pool dispatcher landing in Phase 3
51
- // (RFC-0010 §9). For now the manager is constructed but not yet routed through Phase 2
52
- // ships the wire-in surface; Phase 3 wires the worker pool to consume it.
48
+ // RFC-0010 §6/§7 Phase 2: parallelism. Per AISDLC-116 (maintainer directive 2026-05-01),
49
+ // AI_SDLC_PARALLELISM now defaults to 'on' corpus-driven (no parallelism-related incidents
50
+ // in the trailing observation window) rather than calendar-driven. Explicit
51
+ // 'experimental' is preserved for callers pinning the pre-promotion mode; explicit
52
+ // 'off' / 'disabled' / 'false' / '0' is the opt-out path. When the resolved mode is
53
+ // not 'off', a WorktreePoolManager is instantiated for the worker-pool dispatcher
54
+ // (RFC-0010 §9). The manager is constructed but not yet routed through — Phase 2
55
+ // shipped the wire-in surface; Phase 3 wires the worker pool to consume it.
53
56
  const parallelismMode = readParallelismMode();
54
57
  let worktreePool;
55
58
  if (parallelismMode !== 'off') {
@@ -51,7 +51,9 @@ export interface DiffSummary {
51
51
  * Apply the default classifier ruleset from RFC §12.3 to a diff summary. Used as the
52
52
  * fallback when no LLM classifier is configured, and as the seed prompt for LLM-based
53
53
  * classifiers. Always returns confident: true with confidence: 1.0 because these are
54
- * deterministic rules.
54
+ * deterministic rules. AISDLC-145 added the docs denylist + widened
55
+ * auth/lockfile/CI predicates to close downgrade vectors flagged by the
56
+ * AISDLC-141 security reviewer.
55
57
  */
56
58
  export declare function defaultRulesetDecision(diff: DiffSummary): ClassifierOutput;
57
59
  export interface CalibrationLogEntry {
@@ -146,11 +146,52 @@ export function validateClassifierOutput(value) {
146
146
  }
147
147
  return { ok: true, value: obj };
148
148
  }
149
+ // ── AISDLC-145 path-classification helpers ─────────────────────────────────────────────
150
+ //
151
+ // These predicates are duplicated verbatim in the pipeline-cli copy
152
+ // (`pipeline-cli/src/classifier/classifier.ts`). Keep them in sync — drift
153
+ // here silently shifts which reviewers fire between callers. A future
154
+ // consolidation task should extract a single `@ai-sdlc/classifier-ruleset`
155
+ // package both sides import; tracked as a follow-up to AISDLC-145.
156
+ /** Renderable-docs / image extensions allowed in the docs-only branch. */
157
+ const DOCS_EXTENSIONS_RE = /\.(md|rst|txt|png|jpe?g|svg|gif|ico|pdf)$/i;
158
+ /**
159
+ * Filenames that look secret-y or executable-y and must NEVER be classified
160
+ * as docs even if they sit under `docs/`. Hits include `.env`, `.env.local`,
161
+ * `private-key.pem`, `signing.key`, `install.sh`, `Dockerfile`, `Dockerfile.prod`,
162
+ * `package-lock.json`, etc. Anchored on the basename so path-prefix doesn't
163
+ * matter.
164
+ */
165
+ const DOCS_DENYLIST_RE = /(?:^|\/)(\.env(?:\..+)?|.+\.pem|.+\.key|.+\.sh|Dockerfile.*|.+\.lock)$/i;
166
+ /** True iff the path is safe to treat as documentation-only (no security review). */
167
+ function isDocsLikePath(p) {
168
+ if (DOCS_DENYLIST_RE.test(p))
169
+ return false;
170
+ return DOCS_EXTENSIONS_RE.test(p);
171
+ }
172
+ /** Auth-tier secret files (env vars, private keys) — treated as auth-touching. */
173
+ function isSecretFilePath(p) {
174
+ return /(?:^|\/)(\.env(?:\..+)?|.+\.pem|.+\.key)$/i.test(p);
175
+ }
176
+ /** Supply-chain lockfile detection (widened in AISDLC-145). */
177
+ function isLockfilePath(p) {
178
+ return /(?:^|\/)(package(-lock)?\.json|requirements\.txt|pnpm-lock\.yaml|yarn\.lock|Cargo\.lock|poetry\.lock|Pipfile\.lock|Gemfile\.lock|composer\.lock|go\.sum|bun\.lockb)$/i.test(p);
179
+ }
180
+ /** CI-config detection (widened beyond GitHub Actions in AISDLC-145). */
181
+ function isCiPath(p) {
182
+ if (p.startsWith('.github/workflows/'))
183
+ return true;
184
+ if (p.startsWith('.circleci/'))
185
+ return true;
186
+ return /(?:^|\/)(\.gitlab-ci\.yml|Jenkinsfile|azure-pipelines\.yml)$/i.test(p);
187
+ }
149
188
  /**
150
189
  * Apply the default classifier ruleset from RFC §12.3 to a diff summary. Used as the
151
190
  * fallback when no LLM classifier is configured, and as the seed prompt for LLM-based
152
191
  * classifiers. Always returns confident: true with confidence: 1.0 because these are
153
- * deterministic rules.
192
+ * deterministic rules. AISDLC-145 added the docs denylist + widened
193
+ * auth/lockfile/CI predicates to close downgrade vectors flagged by the
194
+ * AISDLC-141 security reviewer.
154
195
  */
155
196
  export function defaultRulesetDecision(diff) {
156
197
  if (diff.filesChanged === 0) {
@@ -161,7 +202,15 @@ export function defaultRulesetDecision(diff) {
161
202
  confidence: 1,
162
203
  };
163
204
  }
164
- const allDocs = diff.paths.every((p) => /\.(md|rst|txt)$/i.test(p) || p.startsWith('docs/'));
205
+ // AISDLC-145 hardening: the docs branch is a security DOWNGRADE — it skips
206
+ // both `testing` and `security` reviewers. So the predicate must be
207
+ // conservative: a docs-like file is one whose extension is in the safe set
208
+ // (renderable docs / images) AND is NOT on the unconditional denylist of
209
+ // executable-or-secret-looking filenames. Pre-145 the rule was just
210
+ // `p.startsWith('docs/')`, which let `docs/install.sh`, `docs/.env`,
211
+ // `docs/private-key.pem`, `docs/Dockerfile`, etc. silently bypass the
212
+ // security reviewer. See the AISDLC-141 reviewer findings.
213
+ const allDocs = diff.paths.every((p) => isDocsLikePath(p));
165
214
  if (allDocs) {
166
215
  return {
167
216
  reviewers: ['critic'],
@@ -170,9 +219,16 @@ export function defaultRulesetDecision(diff) {
170
219
  confidence: 0.95,
171
220
  };
172
221
  }
173
- const touchesAuth = diff.paths.some((p) => /(?:^|\/)(auth|crypto|secrets?)\b/i.test(p));
174
- const touchesLockfiles = diff.paths.some((p) => /(?:^|\/)(package(-lock)?\.json|requirements\.txt|pnpm-lock\.yaml|yarn\.lock|Cargo\.lock|poetry\.lock|Pipfile\.lock)$/i.test(p));
175
- const touchesCi = diff.paths.some((p) => p.startsWith('.github/workflows/'));
222
+ // AISDLC-145: widen auth/secret detection. The pre-145 regex
223
+ // `(auth|crypto|secrets?)` missed common identity/authn paths
224
+ // (`oauth/`, `iam/`, `jwt/`, `session/`, `login.ts`, `rbac/`, `tokens.ts`,
225
+ // `credentials.ts`, `password.ts`, `signin/`, `signup/`). Those still ran 3
226
+ // reviewers via the default branch but never got the opus model bump.
227
+ // Also: `.env*`, `*.pem`, `*.key` files are treated as auth-tier — they
228
+ // contain or directly grant credentials.
229
+ const touchesAuth = diff.paths.some((p) => /(?:^|\/)(auth|oauth|crypto|secrets?|iam|jwt|session|login|rbac|tokens?|credentials?|password|signin|signup)\b/i.test(p) || isSecretFilePath(p));
230
+ const touchesLockfiles = diff.paths.some((p) => isLockfilePath(p));
231
+ const touchesCi = diff.paths.some((p) => isCiPath(p));
176
232
  if (touchesAuth) {
177
233
  return {
178
234
  reviewers: ['testing', 'critic', 'security'],
@@ -36,9 +36,31 @@ export interface SharedDimensions {
36
36
  * populates this once the SA-1/SA-2/SA-3 decomposition lands.
37
37
  */
38
38
  saAlpha3?: number;
39
- /** Per-channel HC breakdown with the tanh composite for reference. */
39
+ /**
40
+ * Per-channel HC breakdown with the tanh composite for reference.
41
+ *
42
+ * `designAuthorityConfigured` (AISDLC-171) — diagnostic flag set when
43
+ * the resolved DSB declares any `stewardship.designAuthority.principals`
44
+ * entries, regardless of whether one of them participated in the issue.
45
+ * Lets operators distinguish three pillarBreakdown states for the design
46
+ * channel:
47
+ * - `design = 0` and `configured === undefined` → no DSB resolved
48
+ * (preDesignSystem). HC_design intentionally inert.
49
+ * - `design = 0` and `configured === true` → DSB declares design
50
+ * authority but no principal participated as author/commenter.
51
+ * HC_design intentionally 0 per RFC-0008 §14.2 (only principals
52
+ * emit full-weight HC_design signals; non-principal opinions route
53
+ * through HC_consensus, not HC_design).
54
+ * - `design ≠ 0` and `configured === true` → a principal
55
+ * participated; signal weight reflects label-derived signalType.
56
+ *
57
+ * The `false` case (DSB exists but `principals` is empty) is also
58
+ * surfaced for completeness — a DSB without designAuthority principals
59
+ * cannot ever fire HC_design and operators should know.
60
+ */
40
61
  hcComposite: HcChannelBreakdown & {
41
62
  value: number;
63
+ designAuthorityConfigured?: boolean;
42
64
  };
43
65
  }
44
66
  export interface TensionFlag {
@@ -70,6 +70,15 @@ export function computePillarBreakdown(composite) {
70
70
  decision: b.humanCurve.hcDecision,
71
71
  design: b.humanCurve.hcDesign,
72
72
  value: b.humanCurve.hcComposite,
73
+ // AISDLC-171: only include the flag when the underlying signal
74
+ // populated it (i.e., a DSB was resolved). Leaving it `undefined`
75
+ // when no DSB was supplied keeps the preDesignSystem state
76
+ // distinct from the "configured but inactive" state at the API
77
+ // surface — operators inspecting `pillarBreakdown.shared` can
78
+ // tell the three states apart without reading the DSB themselves.
79
+ ...(b.humanCurve.designAuthorityConfigured !== undefined
80
+ ? { designAuthorityConfigured: b.humanCurve.designAuthorityConfigured }
81
+ : {}),
73
82
  },
74
83
  };
75
84
  const tensions = detectTensions({ product, design, engineering, shared, tensions: [] });