substrate-ai 0.20.5 → 0.20.7

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/cli/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest } from "../health-DHLR9Iz1.js";
2
+ import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDING_COUNTS, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts } from "../health-BIS34IYK.js";
3
3
  import { createLogger } from "../logger-KeHncl-f.js";
4
4
  import { createEventBus } from "../helpers-CElYrONe.js";
5
5
  import { AdapterRegistry, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, ConfigError, CostTrackerConfigSchema, DEFAULT_CONFIG, DoltClient, DoltNotInstalled, GlobalSettingsSchema, IngestionServer, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProvidersSchema, RoutingRecommender, STORY_METRICS, TelemetryConfigSchema, addTokenUsage, aggregateTokenUsageForRun, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDecision, createDoltClient, createPipelineRun, getActiveDecisions, getAllCostEntriesFiltered, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRunMetrics, loadParentRunDecisions, supersedeDecision, tagRunAsBaseline, updatePipelineRun } from "../dist-srr3BfCc.js";
6
6
  import "../adapter-registry-DXLMTmfD.js";
7
- import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-ofO9AWFc.js";
7
+ import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-BAc1zfMQ.js";
8
8
  import "../errors-CSTQNabo.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
@@ -3619,9 +3619,11 @@ async function runStatusAction(options) {
3619
3619
  if (run === void 0) run = await getLatestRun(adapter);
3620
3620
  }
3621
3621
  let workGraph;
3622
+ let manifestPerStoryState;
3622
3623
  const { manifest: resolvedManifest } = await resolveRunManifest(dbRoot, run?.id);
3623
3624
  if (resolvedManifest !== null) try {
3624
3625
  const manifestData = await resolvedManifest.read();
3626
+ manifestPerStoryState = manifestData.per_story_state;
3625
3627
  workGraph = buildWorkGraphFromManifest(manifestData.per_story_state);
3626
3628
  logger$12.debug({ runId: run?.id }, "status: workGraph built from manifest per_story_state");
3627
3629
  } catch {
@@ -3665,7 +3667,7 @@ async function runStatusAction(options) {
3665
3667
  logger$12.debug({ err }, "Work graph query failed, continuing without work graph data");
3666
3668
  }
3667
3669
  if (run === void 0) {
3668
- const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-CQTK6ltK.js");
3670
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-3-qy8XEI.js");
3669
3671
  const substrateDirPath = join(projectRoot, ".substrate");
3670
3672
  const processInfo = inspectProcessTree$1({
3671
3673
  projectRoot,
@@ -3708,6 +3710,8 @@ async function runStatusAction(options) {
3708
3710
  for (const [phase, secs] of Object.entries(parsed)) phaseBreakdown[phase] = Math.round(secs * 1e3);
3709
3711
  }
3710
3712
  } catch {}
3713
+ const verificationResult = manifestPerStoryState?.[row.story_key]?.verification_result;
3714
+ const verificationFindings = rollupFindingCounts(verificationResult);
3711
3715
  return {
3712
3716
  story_key: row.story_key,
3713
3717
  result: row.result,
@@ -3718,7 +3722,8 @@ async function runStatusAction(options) {
3718
3722
  output: row.output_tokens ?? 0
3719
3723
  },
3720
3724
  review_cycles: row.review_cycles ?? 0,
3721
- dispatches: row.dispatches ?? 0
3725
+ dispatches: row.dispatches ?? 0,
3726
+ verification_findings: verificationFindings
3722
3727
  };
3723
3728
  });
3724
3729
  let pipelineWallClockMs = 0;
@@ -5191,7 +5196,7 @@ async function runSupervisorAction(options, deps = {}) {
5191
5196
  await initSchema(expAdapter);
5192
5197
  const { runRunAction: runPipeline } = await import(
5193
5198
  /* @vite-ignore */
5194
- "../run-s6bRK0LF.js"
5199
+ "../run-Dif8PJRd.js"
5195
5200
  );
5196
5201
  const runStoryFn = async (opts) => {
5197
5202
  const exitCode = await runPipeline({
@@ -5953,6 +5958,14 @@ async function runMetricsAction(options) {
5953
5958
  phaseBreakdownMap[run$1.run_id] = raw !== void 0 ? raw : null;
5954
5959
  }
5955
5960
  } catch {}
5961
+ const findingCountsByStoryRun = new Map();
5962
+ const uniqueRunIds = Array.from(new Set(storyMetrics.map((sm) => sm.run_id).filter((id) => id !== "")));
5963
+ for (const uniqueRunId of uniqueRunIds) try {
5964
+ const { manifest } = await resolveRunManifest(dbRoot, uniqueRunId);
5965
+ if (manifest === null) continue;
5966
+ const data = await manifest.read();
5967
+ for (const [storyKey, entry] of Object.entries(data.per_story_state)) findingCountsByStoryRun.set(`${storyKey}:${uniqueRunId}`, rollupFindingCounts(entry.verification_result));
5968
+ } catch {}
5956
5969
  let factoryRuns = [];
5957
5970
  try {
5958
5971
  factoryRuns = await getFactoryRunSummaries(adapter, limit);
@@ -5965,10 +5978,14 @@ async function runMetricsAction(options) {
5965
5978
  type: "sdlc",
5966
5979
  phase_token_breakdown: phaseBreakdownMap[run$1.run_id] ?? null
5967
5980
  }));
5981
+ const storyMetricsWithFindings = storyMetrics.map((sm) => ({
5982
+ ...sm,
5983
+ verification_findings: findingCountsByStoryRun.get(`${sm.story_key}:${sm.run_id}`) ?? { ...ZERO_FINDING_COUNTS }
5984
+ }));
5968
5985
  const jsonPayload = {
5969
5986
  runs: runsWithBreakdown,
5970
5987
  graph_runs: factoryRuns,
5971
- story_metrics: storyMetrics
5988
+ story_metrics: storyMetricsWithFindings
5972
5989
  };
5973
5990
  if (doltMetrics !== void 0) if (aggregate) {
5974
5991
  const aggregateResults = doltMetrics.map((m) => ({
@@ -1,4 +1,4 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-DHLR9Iz1.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-BIS34IYK.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./dist-srr3BfCc.js";
4
4
  import "./decisions-C0pz9Clx.js";
@@ -4486,6 +4486,61 @@ var RunManifest = class RunManifest {
4486
4486
  }
4487
4487
  };
4488
4488
 
4489
+ //#endregion
4490
+ //#region packages/sdlc/dist/run-model/verification-findings-counts.js
4491
+ /**
4492
+ * Verification finding count roll-up — Story 55-3b.
4493
+ *
4494
+ * Collapses every finding across every check in a StoredVerificationSummary
4495
+ * into a `{error, warn, info}` triple, suitable for per-story surfacing in
4496
+ * the status/metrics CLI JSON payloads.
4497
+ *
4498
+ * Intentionally pure: no I/O, no logger, no throw. Fits cleanly in the
4499
+ * run-model package so both the status and metrics commands (and any
4500
+ * future consumer) can share a single implementation and one set of tests.
4501
+ */
4502
+ /** Zero-counts object used as the default return value and as the identity
4503
+ * element in consumer-side accumulations. */
4504
+ const ZERO_FINDING_COUNTS = Object.freeze({
4505
+ error: 0,
4506
+ warn: 0,
4507
+ info: 0
4508
+ });
4509
+ /**
4510
+ * Sum findings across every check in the summary, grouped by severity.
4511
+ *
4512
+ * Backward-compatible — when the summary is `undefined`, or a check has no
4513
+ * `findings` field (legacy manifests written before Story 55-2 migrated the
4514
+ * checks), the absent arrays contribute 0 to every severity. No severity
4515
+ * ever reports undefined.
4516
+ */
4517
+ function rollupFindingCounts(summary) {
4518
+ if (summary === void 0 || summary === null) return { ...ZERO_FINDING_COUNTS };
4519
+ let error = 0;
4520
+ let warn = 0;
4521
+ let info = 0;
4522
+ for (const check of summary.checks) {
4523
+ const findings = check.findings;
4524
+ if (findings === void 0) continue;
4525
+ for (const finding of findings) switch (finding.severity) {
4526
+ case "error":
4527
+ error += 1;
4528
+ break;
4529
+ case "warn":
4530
+ warn += 1;
4531
+ break;
4532
+ case "info":
4533
+ info += 1;
4534
+ break;
4535
+ }
4536
+ }
4537
+ return {
4538
+ error,
4539
+ warn,
4540
+ info
4541
+ };
4542
+ }
4543
+
4489
4544
  //#endregion
4490
4545
  //#region packages/sdlc/dist/run-model/supervisor-lock.js
4491
4546
  const defaultLogger = console;
@@ -5230,5 +5285,5 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
5230
5285
  }
5231
5286
 
5232
5287
  //#endregion
5233
- export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, createStateStore, detectCycles, extractTargetFilesFromStoryContent, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, renderFindings, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, runHealthAction, validateStoryKey };
5234
- //# sourceMappingURL=health-DHLR9Iz1.js.map
5288
+ export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDING_COUNTS, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, createStateStore, detectCycles, extractTargetFilesFromStoryContent, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, renderFindings, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, runHealthAction, validateStoryKey };
5289
+ //# sourceMappingURL=health-BIS34IYK.js.map
@@ -1,4 +1,4 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-DHLR9Iz1.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-BIS34IYK.js";
2
2
  import { createLogger } from "./logger-KeHncl-f.js";
3
3
  import { TypedEventBusImpl, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-CElYrONe.js";
4
4
  import { ADVISORY_NOTES, Categorizer, ConsumerAnalyzer, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, IngestionServer, LogTurnAnalyzer, OPERATIONAL_FINDING, Recommender, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, callLLM, createConfigSystem, createDatabaseAdapter$1, createDecision, createPipelineRun, createRequirement, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-srr3BfCc.js";
@@ -43840,4 +43840,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
43840
43840
 
43841
43841
  //#endregion
43842
43842
  export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveMaxReviewCycles, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict, wireNdjsonEmitter };
43843
- //# sourceMappingURL=run-ofO9AWFc.js.map
43843
+ //# sourceMappingURL=run-BAc1zfMQ.js.map
@@ -1,8 +1,8 @@
1
- import "./health-DHLR9Iz1.js";
1
+ import "./health-BIS34IYK.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-srr3BfCc.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-ofO9AWFc.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-BAc1zfMQ.js";
6
6
  import "./routing-CcBOCuC9.js";
7
7
  import "./decisions-C0pz9Clx.js";
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.20.5",
3
+ "version": "0.20.7",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -64,6 +64,96 @@ Use this exact format for each item:
64
64
  - The transport annotation `(queue: ...)` or `(api: ...)` or `(from story X-Y)` is optional but recommended when applicable
65
65
  - **The `## Interface Contracts` section is optional** — omit it entirely if the story has no cross-story schema dependencies
66
66
 
67
+ ## Runtime Verification Guidance
68
+
69
+ **Decide whether this story's artifact is runtime-dependent.** An artifact is runtime-dependent if correctness depends on execution — systemd units, container definitions (Podman Quadlet, Docker Compose), install scripts, migration runners, anything whose behavior is only observable by running it against a real host or ephemeral sandbox.
70
+
71
+ If the artifact is runtime-dependent, add a `## Runtime Probes` section to the story file. Each probe is a short shell command whose exit status answers "does this artifact actually work?".
72
+
73
+ **If the artifact is NOT runtime-dependent — TypeScript/JavaScript code + tests, type-only refactors, documentation, build or tsconfig edits — omit the `## Runtime Probes` section entirely.** Adding one for a static-output story produces a `pass` (skip) with no benefit. The default substrate self-development case (source code + tests) has no probes.
74
+
75
+ ### Probe YAML shape
76
+
77
+ Declare probes as a YAML list inside a single fenced `yaml` block directly under the `## Runtime Probes` heading. Each entry has this shape:
78
+
79
+ ```text
80
+ - name: <hyphen-separated-identifier> # required; unique within story
81
+ sandbox: host | twin # required; one of host | twin
82
+ command: <shell command line(s)> # required
83
+ timeout_ms: 60000 # optional; defaults to 60000
84
+ description: <optional context> # optional
85
+ ```
86
+
87
+ Required fields: `name`, `sandbox`, `command`. `timeout_ms` and `description` are optional. Probe names must be unique within one story.
88
+
89
+ ### Sandbox choice
90
+
91
+ - **`sandbox: twin`** — default for probes that mutate host state: starting services, binding ports, writing outside the project working directory, running privileged commands. Safer; ephemeral.
92
+ - **`sandbox: host`** — only when the probe is strictly read-only from the host's perspective (linting a file, parsing config, asserting a command exists, pulling an image into a local cache) OR when the host context itself is what the story needs to verify.
93
+ - **When in doubt, pick `twin`.**
94
+
95
+ ### Probe granularity
96
+
97
+ For stories with multiple runtime concerns (install + start + connect), declare **separate named probes per concern** rather than one monolithic probe. Finding messages reference probe names; granular probes produce actionable failures and let retries focus on the specific failure.
98
+
99
+ Probe names are hyphen-separated identifiers, not sentences: `dolt-image-pullable`, not `verify that the dolt image can be pulled`.
100
+
101
+ ### Examples by artifact class
102
+
103
+ **Systemd unit:**
104
+
105
+ ```yaml
106
+ - name: unit-is-active
107
+ sandbox: twin
108
+ command: systemctl is-active my-service.service
109
+ description: unit started and has not crashed
110
+ ```
111
+
112
+ **Container / Podman Quadlet** (catches the wrong-image-path class — strata Story 1-4):
113
+
114
+ ```yaml
115
+ - name: dolt-image-pullable
116
+ sandbox: host
117
+ command: podman pull ghcr.io/dolthub/dolt-sql-server:latest
118
+ description: image reference resolves and is pullable
119
+ ```
120
+
121
+ **Install script:**
122
+
123
+ ```yaml
124
+ - name: installer-exits-clean
125
+ sandbox: twin
126
+ command: bash ./install.sh --dry-run
127
+ - name: installed-binary-reports-version
128
+ sandbox: twin
129
+ command: /usr/local/bin/my-tool --version
130
+ ```
131
+
132
+ **Database migration:**
133
+
134
+ ```yaml
135
+ - name: migration-applies-cleanly
136
+ sandbox: twin
137
+ command: npm run migrate:up && npm run migrate:status
138
+ description: migration applies and schema_migrations reports the new version
139
+ ```
140
+
141
+ **Docker Compose:**
142
+
143
+ ```yaml
144
+ - name: compose-parses
145
+ sandbox: host
146
+ command: docker compose -f ./compose.yaml config --quiet
147
+ description: compose file is syntactically valid
148
+ - name: compose-service-starts
149
+ sandbox: twin
150
+ command: docker compose -f ./compose.yaml up -d api && docker compose -f ./compose.yaml ps api | grep -q running
151
+ ```
152
+
153
+ ### Framing
154
+
155
+ Treat the probes you draft as a **first pass** the human author will refine. Probes execute on a real host (or — for `sandbox: twin` — a real ephemeral sandbox), so command correctness matters. Prefer conservative commands that exit 0 only on true success and non-zero on any real failure.
156
+
67
157
  ## Scope Cap Guidance
68
158
 
69
159
  **Aim for 6-7 acceptance criteria and 7-8 tasks per story.**