@kungfu-tech/buildchain 2.14.17-alpha.2 → 2.14.17-alpha.3
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/site/buildchain-contract.json +4 -4
- package/dist/site/buildchain-site.json +7 -7
- package/dist/site/kfd-claims.json +2 -2
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/node-api-registry.json +1 -1
- package/dist/site/page-registry.json +2 -2
- package/dist/site/public-surface-audit.json +1 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/docs/toolkit-observability.md +9 -0
- package/package.json +1 -1
- package/packages/core/candidate-timeline.js +126 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"product": {
|
|
5
5
|
"name": "Buildchain",
|
|
6
6
|
"package": "@kungfu-tech/buildchain",
|
|
7
|
-
"version": "2.14.17-alpha.
|
|
7
|
+
"version": "2.14.17-alpha.3",
|
|
8
8
|
"repository": "https://github.com/kungfu-systems/buildchain"
|
|
9
9
|
},
|
|
10
10
|
"majorLine": "v2",
|
|
@@ -585,7 +585,7 @@
|
|
|
585
585
|
"manual entries carry source file digests so downstream sites and agents can detect stale hand-written documentation"
|
|
586
586
|
],
|
|
587
587
|
"breakingDigest": "sha256:7d0d2819e3a3e72989d9c57b5efe9d0bc0a79bc0f2c82a0c7b9d6c5a211a91f2",
|
|
588
|
-
"auditDigest": "sha256:
|
|
588
|
+
"auditDigest": "sha256:b82e0c379eaea6027098d67d67e4919be312c40b3f87c88e4cf13da13d003c99"
|
|
589
589
|
},
|
|
590
590
|
{
|
|
591
591
|
"contractVersion": 1,
|
|
@@ -607,7 +607,7 @@
|
|
|
607
607
|
"agents can discover supported Node APIs without importing internal file paths"
|
|
608
608
|
],
|
|
609
609
|
"breakingDigest": "sha256:48f925608d3e2131d90936b07dc2a30341204cae3e6e785c0f77d61ad755c945",
|
|
610
|
-
"auditDigest": "sha256:
|
|
610
|
+
"auditDigest": "sha256:69d0887ab986216d4308a490a08eae01eb869277f620fba6397723aa072f4918"
|
|
611
611
|
},
|
|
612
612
|
{
|
|
613
613
|
"contractVersion": 1,
|
|
@@ -2916,5 +2916,5 @@
|
|
|
2916
2916
|
}
|
|
2917
2917
|
],
|
|
2918
2918
|
"compatibilityDigest": "sha256:cdf588422ec6b460bd6f631400efa6e361a045e052c8967d6c50fadd7aa4dd90",
|
|
2919
|
-
"contractDigest": "sha256:
|
|
2919
|
+
"contractDigest": "sha256:978eebe7528ea563ff62a2fe1e5b46861b36c510f8e4fda634f9e6a1acb5cdc7"
|
|
2920
2920
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-site-bundle",
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"publishedAt": "2026-07-
|
|
4
|
+
"generatedAt": "2026-07-23T02:11:02.318Z",
|
|
5
|
+
"publishedAt": "2026-07-23T02:11:02.318Z",
|
|
6
6
|
"reproducible": true,
|
|
7
7
|
"timestampPolicy": "ci-injected",
|
|
8
8
|
"deterministicInputs": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"declared Buildchain surface manifest contract"
|
|
20
20
|
],
|
|
21
21
|
"sourceDateEpoch": "0",
|
|
22
|
-
"sourceRevision": "
|
|
22
|
+
"sourceRevision": "d8c1eb88881153d74398da51d70f9fd7101c50a0",
|
|
23
23
|
"timestampPolicyDetails": {
|
|
24
24
|
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
25
|
"timestampFields": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"package": {
|
|
39
39
|
"name": "@kungfu-tech/buildchain",
|
|
40
|
-
"version": "2.14.17-alpha.
|
|
40
|
+
"version": "2.14.17-alpha.3",
|
|
41
41
|
"versionSource": "package.json#version"
|
|
42
42
|
},
|
|
43
43
|
"source": {
|
|
@@ -1996,7 +1996,7 @@
|
|
|
1996
1996
|
],
|
|
1997
1997
|
"maturity": "stable",
|
|
1998
1998
|
"sourcePath": "docs/toolkit-observability.md",
|
|
1999
|
-
"digest": "sha256:
|
|
1999
|
+
"digest": "sha256:b2ffc3b2520ca7879309b7607b2eaa1aceb647503e893887159a6916de14e2d7",
|
|
2000
2000
|
"headings": [
|
|
2001
2001
|
{
|
|
2002
2002
|
"level": 1,
|
|
@@ -2034,7 +2034,7 @@
|
|
|
2034
2034
|
"anchor": "release-gate"
|
|
2035
2035
|
}
|
|
2036
2036
|
],
|
|
2037
|
-
"markdown": "# Toolkit Observability\n\nBuildchain ships a small logging toolkit for repository workflows and project\nscripts. The goal is to separate time spent in Buildchain's framework from time\nspent in the consumer's own build, test, packaging, and publish steps.\n\n## Choose API or CLI\n\n`@kungfu-tech/buildchain` is not only a CLI package. It exports ESM toolkit APIs\nthat project scripts can import directly:\n\n```js\nimport { createBuildchainLogger } from \"@kungfu-tech/buildchain/logging\";\n```\n\nUse the API inside JavaScript or TypeScript build code. Do not spawn\n`buildchain`, download the standalone binary, or shell out through `npx` from\ncode that can import the package. The CLI is for GitHub Actions steps, shell\nscripts, and non-JavaScript tools.\n\nWhen a script runs inside `buildchain lifecycle run`, the lifecycle runner sets\n`BUILDCHAIN_LOG_PATH` and `BUILDCHAIN_LOG_RUN_ID`. Imported loggers pick up those\nenvironment variables automatically, so events emitted deep inside the build are\ngrouped into the same lifecycle summary.\n\nOutside a Buildchain lifecycle or GitHub Actions run, the logger defaults to\nconsole output unless a path is provided. Pass `path` when local scripts should\nwrite a reusable JSONL log:\n\n```js\nconst logger = createBuildchainLogger({\n path: \".buildchain/logs/native-build.jsonl\",\n source: \"user\",\n component: \"native-build\",\n});\n```\n\n## Library API\n\n```js\nimport {\n createBuildchainLogger,\n verifyBuildchainLogEvents,\n} from \"@kungfu-tech/buildchain/logging\";\n\nconst logger = createBuildchainLogger({\n source: \"user\",\n component: \"native-build\",\n});\n\nlogger.mark(\"configure.ready\", {\n phase: \"configure\",\n attributes: { preset: \"release\" },\n});\n\nawait logger.span(\"native.compile\", {\n phase: \"build\",\n attributes: { target: \"release\" },\n}, async () => {\n await compile();\n});\n\nlogger.spanSync(\"native.archive\", {\n phase: \"build\",\n attributes: { tool: \"libtool\" },\n}, () => {\n archiveStaticLibraries();\n});\n\nlogger.spawnSync(\"native.build\", \"make\", [\"-j20\"], {\n stdio: \"inherit\",\n}, {\n phase: \"build\",\n attributes: { requestedJobs: 20 },\n});\n\nconst report = verifyBuildchainLogEvents({\n path: logger.path,\n minEvents: 3,\n requirePhases: [\"configure\", \"build\"],\n requireEvents: [\n \"configure.ready\",\n \"native.compile.start\",\n \"native.compile.end\",\n ],\n});\n\nif (!report.ok) {\n throw new Error(\"Buildchain observability verification failed\");\n}\n```\n\nUse the API when a build script has internal stages that are invisible to the\nouter workflow. Keep secret values out of attributes; known sensitive keys are\nredacted, but callers should still avoid logging private material.\n\n## Candidate timelines and critical-path-safe timing\n\nLifecycle diagnostics retain their existing summed-duration tables for\ncompatibility. Do not interpret those tables as elapsed time when spans are\nnested or jobs run in parallel. Use the candidate timeline contract when a\nchange must be followed from pull-request admission through Merge Queue attempts\nto its final merge:\n\n```js\nimport {\n createCandidateTimeline,\n formatCandidateTimelineReport,\n} from \"@kungfu-tech/buildchain/candidate-timeline\";\n\nconst timeline = createCandidateTimeline({\n candidate: {\n repository: \"owner/repository\",\n baseBranch: \"dev/v4/v4.0\",\n sourceSha,\n pullRequest: 123,\n },\n events,\n});\n\nconsole.log(formatCandidateTimelineReport(timeline));\n```\n\nEach event binds a stable event id to one attempt id and may also bind a Gate\nid, merge-group SHA, workflow run, platform, partition, cache outcome, execution\nboundary, and parent span. Terminal execution states (`success`, `failure`, and\n`cancelled`) require start and completion timestamps. Non-executed states use\n`skipped`, `dependency-blocked`, or `not-required` without invented timings.\nRecord the clock and timestamp precision explicitly: GitHub Actions timestamps\nare normally provider wall-clock observations with one-second precision, while\nan in-process span may also carry a monotonic duration with millisecond or\nbetter precision.\n\nThe `buildchain.candidate-timeline/v1` artifact reports an independent critical\npath for every attempt. Its critical-path duration is the attempt's observed\nwall-clock envelope; its active duration and per-phase durations are interval\nunions. This prevents nested or parallel spans from being added twice, and it\ndoes not combine a failed or dequeued attempt with a later retry. Missing\nrequired measurements make that attempt `incomplete` instead of producing fake\nprecision.\n\nShell and workflow consumers can generate the same machine artifact plus a\ncompact report:\n\n```sh\nbuildchain candidate timeline \\\n --input .buildchain/candidate-timeline-input.json \\\n --output .buildchain/candidate-timeline.json\n```\n\nThe input and output should contain only bounded correlation facts and timing\nreceipts. Do not include tokens, environment dumps, full commands, raw process\ndumps, or private absolute paths.\n\nCommonJS scripts should import Buildchain's ESM surfaces dynamically:\n\n```js\nconst { createBuildchainLogger } = await import(\"@kungfu-tech/buildchain/logging\");\nconst { collectRunnerDiagnostics } = await import(\"@kungfu-tech/buildchain/diagnostics\");\n```\n\n## Diagnostics API\n\nThe diagnostics surface collects local, non-telemetry build facts that are\nuseful when a native build is slow or flaky:\n\n```js\nimport {\n collectBuildchainDiagnostics,\n collectCacheDiagnostics,\n collectCompilerCacheDiagnostics,\n collectRunnerDiagnostics,\n collectToolDiagnostics,\n detectRequestedParallelism,\n startProcessSampler,\n summarizeDiagnosticsArtifacts,\n summarizeLifecycleObservability,\n summarizeProcessSamples,\n validateAnchoredPackageRelease,\n writeDiagnosticsArtifact,\n} from \"@kungfu-tech/buildchain/diagnostics\";\n\nconst lifecycleObservability = summarizeLifecycleObservability({\n logPath: \".buildchain/logs/events.jsonl\",\n});\nconst buildCommand = \"make\";\nconst buildArgs = [\"-j20\"];\nconst requestedParallelism = detectRequestedParallelism({\n command: buildCommand,\n args: buildArgs,\n});\nconst processSampler = startProcessSampler({\n intervalMs: 15000,\n label: \"native-build\",\n command: buildCommand,\n args: buildArgs,\n});\n// Run the long native build while the sampler is active.\nconst processSummary = summarizeProcessSamples({\n requestedParallelism: requestedParallelism.value,\n samples: processSampler.stop(),\n});\nconst cacheDiagnostics = collectCacheDiagnostics({ cwd: process.cwd() });\n\nwriteDiagnosticsArtifact(\".buildchain/artifacts/diagnostics.json\", {\n contract: \"consumer-build-diagnostics\",\n buildchain: collectBuildchainDiagnostics({ cwd: process.cwd() }),\n runner: collectRunnerDiagnostics(),\n tools: collectToolDiagnostics({ cwd: process.cwd() }),\n cache: cacheDiagnostics,\n lifecycleObservability,\n process: processSummary,\n});\n```\n\n`collectCacheDiagnostics()` includes package-manager/workspace context, selected\ncache directory stats, and compiler-cache stats from `ccache --show-stats\n--json` plus `sccache --show-stats --stats-format json` when those tools are\npresent. If a ccache build does not support JSON stats, Buildchain falls back to\nplain `ccache --show-stats` and parses the text counters. Missing cache tools\nare recorded as unavailable instead of failing the diagnostics artifact. Call\n`collectCompilerCacheDiagnostics()` directly when a consumer script only needs\ncompiler cache data. Native diagnostics also expose `compilerCaches` and\n`nativeCacheDirs` as top-level fields in each diagnostics artifact and aggregate\nsummary, so reviewers do not have to dig through nested cache sections first.\n\nProcess samples are intentionally summarized before they become long-lived\nartifacts. The summary records requested parallelism, the source of that value\n(`command`, `process-tree`, `env:MAKEFLAGS`,\n`env:CMAKE_BUILD_PARALLEL_LEVEL`, or `explicit`), observed active process\nconcurrency, elapsed sample time, total sampled CPU, and conservative command\ncategories such as `compiler`, `archive`, `linker`, `build-tool`, and `cache`.\nThe sampler detects common `make -j N`, `ninja -j N`, CMake/MSBuild/Xcode job\nflags, and MAKEFLAGS. This lets native projects distinguish \"we asked for\n`make -j20`\" from \"the build graph only kept two active compiler or archive\nchildren busy during the sampled window\" without storing environment dumps.\n\nNative repositories can opt into a reusable diagnostics profile in\n`buildchain.toml`:\n\n```toml\n[diagnostics.native]\nenabled = true\nsample_process_tree = true\ncompiler_cache = \"auto\"\nexpected_tools = [\"ccache\", \"sccache\", \"clang\", \"cl\", \"cmake\", \"ninja\"]\nartifact_dirs = [\"build\", \"dist\", \"build/Release\"]\ncache_dirs = [\".ccache\", \".sccache\"]\n```\n\nWhen enabled, diagnostics artifacts include the normalized profile, selected\ntool versions, compiler-cache stats, and configured artifact/cache directory\nstats. The profile is data-driven: Buildchain does not assume a specific\nproject such as libnode. The reusable build workflow also exposes\n`sample-process-tree`, which wraps the build lifecycle with the process sampler\nand carries the generated summary into the final diagnostics artifact. Custom\nworkflows can call `buildchain sample process-tree` directly when they need a\ndifferent command boundary.\n\nAnchored/manual package projects can also run one higher-level release-shape\ncheck instead of assembling lower-level config calls:\n\n```js\nconst anchoredReport = validateAnchoredPackageRelease({\n cwd: process.cwd(),\n requireManifest: true,\n requirePackageSetOrder: \"platforms-first-main-last\",\n requireTrustedPublishing: true,\n});\n\nif (!anchoredReport.ok) {\n throw new Error(\"Anchored package release contract failed\");\n}\n```\n\nIn an actual publish job, make that check source-lock aware:\n\n```js\nconst publishReady = validateAnchoredPackageRelease({\n cwd: process.cwd(),\n requirePublishGateSourceLock: true,\n});\n```\n\nThe source-lock inputs are read from `BUILDCHAIN_PUBLISH_SOURCE_REF`,\n`BUILDCHAIN_PUBLISH_SOURCE_SHA`, and `BUILDCHAIN_PUBLISH_SOURCE_LOCKED`, which\nthe reusable build workflow emits after resolving `publish-source-ref`. That\nturns direct channel-branch publication from `alpha/*` or `release/*` into a\nhard failure, while `publish-gate/alpha/<line>/<version>` and\n`publish-gate/release/<line>/<version>` also validate the requested consumer\nversion against configured version files and the anchor manifest.\n\n`buildchain lifecycle run` writes this small diagnostics artifact next to the\nplatform manifest by default. The per-platform diagnostics upload includes the\ncompact `diagnostics.json`, `diagnostics-manifest.json`, lifecycle\n`events.jsonl`, and, when process sampling is enabled, copied\n`process-summary.json` and `process-samples.jsonl` sidecars. The sidecar\nmanifest records each uploaded diagnostics file with its relative path, byte\ncount, and sha256 hash. It is intended to stay small enough to download without\nfetching large binary packages, and it should not include full environment dumps\nor secret-looking values.\n\nUse `summarizeDiagnosticsArtifacts()` when a matrix build uploads one\ndiagnostics artifact per platform. The summary keeps each platform's lifecycle\nstage table, adds a lifecycle total duration, carries the top slow spans, and\naggregates warning/error counts plus the slowest platforms. Per-platform rows\nalso include compact runner facts, checked tool versions/missing tools, package\nmanager/cache directory details, compiler cache availability, and a compact\nprocess sampler view with requested parallelism, observed max active processes,\nthe ratio between them, sample count, process categories, and top sampled\ncommand basenames. That gives release reviewers a small cross-platform timing,\nrunner, tool, cache, and concurrency view without downloading full build outputs\nor process sidecars.\n\nThe reusable build workflow writes that rollup as `diagnostics-summary.json` and\nuploads it in a separate aggregate diagnostics summary artifact. Consumers can\nread the `build-diagnostics-summary-artifact` output when they need only timing,\nwarning/error, runner, cache, and process-sampler context instead of the build\nsummary or binary artifacts. Per-platform rows keep the diagnostics `links`\nobject, including the binary artifact name, manifest artifact name, diagnostics\nartifact name, platform id, diagnostics sidecar manifest path, manifest path,\nsummary path, and process sidecar paths when present. They also keep compact\nrunner/tool/cache summaries so reviewers can tell whether a slow row ran on the\nexpected runner, missed an expected tool, or lacked useful compiler-cache stats.\nWhen the downloaded platform diagnostics include a sibling\n`diagnostics-manifest.json`, `summarizeDiagnosticsArtifacts()` carries a compact\n`diagnosticsManifest` section for that platform and verifies the manifest's\n`diagnostics.json` byte count and sha256. Missing or mismatched sidecar manifests\nincrement `diagnosticsManifestWarningCount`, so reviewers can distinguish\ndiagnostics sidecar drift from lifecycle warnings or build failures.\nThe same summary carries `diagnosticsContract` per platform and aggregates\n`diagnosticsContractWarningCount` when a downloaded `diagnostics.json` does not\nmatch `BUILDCHAIN_DIAGNOSTICS_CONTRACT`.\n\nThe diagnostics SDK also exports stable contract constants such as\n`BUILDCHAIN_DIAGNOSTICS_SUMMARY_CONTRACT`,\n`BUILDCHAIN_DIAGNOSTICS_MANIFEST_CONTRACT`,\n`BUILDCHAIN_PROCESS_SAMPLE_REPORT_CONTRACT`,\n`BUILDCHAIN_PROCESS_SAMPLE_SUMMARY_CONTRACT`, and\n`BUILDCHAIN_ANCHORED_PACKAGE_RELEASE_VALIDATION_CONTRACT` from\n`@kungfu-tech/buildchain/diagnostics`; consumers should compare against those\nconstants instead of hardcoding contract strings.\n\nThe CLI exposes the same aggregation for shell and workflow steps:\n\n```bash\nbuildchain diagnostics summary \\\n .buildchain/artifacts/linux-x64/diagnostics.json \\\n .buildchain/artifacts/macos-arm64/diagnostics.json \\\n --output .buildchain/artifacts/diagnostics-summary.json \\\n --json\n```\n\nOmit `--json` when the workflow log should show a compact platform table with\nlifecycle stages, artifact scan/upload time, total time, requested jobs,\nobserved active processes, warnings, and errors.\n\nFor long native build commands, the CLI can also sample the child process tree\nwhile preserving the wrapped command's exit code:\n\n```bash\nbuildchain sample process-tree \\\n --label native-build \\\n --interval-ms 15000 \\\n --output .buildchain/diagnostics/process-samples.jsonl \\\n --summary-output .buildchain/diagnostics/process-summary.json \\\n -- \\\n make -j20\n```\n\nThe JSONL sample file stores timestamped process-tree snapshots with full\nredacted command lines, command basenames, CPU percentages when available,\nelapsed time, and requested parallelism context. Unix and macOS snapshots read\nthe full `ps args` field instead of truncated `comm` names. Windows snapshots\nread `Win32_Process` command lines through PowerShell so the sampler no longer\nreturns an empty process set on Windows runners. The summary file records\nobserved concurrency, total sampled CPU, command categories, and top command\nbasenames. This is intended for diagnosing low-utilization tails such as\narchive/link phases without logging full environment dumps.\n\nThe lifecycle observability summary is stage-wide, not just final-step timing:\nwhen install and build write to the same Buildchain log, the final platform\nmanifest can show both stages and the slowest spans.\n\n## CLI Logging\n\n```bash\nbuildchain mark \\\n --event native.configure \\\n --phase configure \\\n --component cmake \\\n --attribute preset=release\n\nbuildchain span \\\n --event native.build \\\n --phase build \\\n --component cmake \\\n -- cmake --build build --config Release\n\nbuildchain log summary --json\nbuildchain verify observability-log .buildchain/logs/events.jsonl --min-events 4\n```\n\nEvery event records a timestamp. `span` records duration and preserves the\nwrapped command's exit code.\n\n## Release Gate\n\nBuildchain's own binary distribution lane verifies required log events before\nuploading release assets. Consumers can apply the same pattern:\n\n```bash\nbuildchain verify observability-log .buildchain/logs/events.jsonl \\\n --require-phase build \\\n --require-phase package \\\n --require-component workflow \\\n --require-event native.build.start \\\n --require-event native.build.end\n```\n\nThis makes missing instrumentation a release failure instead of a dashboard\nafterthought."
|
|
2037
|
+
"markdown": "# Toolkit Observability\n\nBuildchain ships a small logging toolkit for repository workflows and project\nscripts. The goal is to separate time spent in Buildchain's framework from time\nspent in the consumer's own build, test, packaging, and publish steps.\n\n## Choose API or CLI\n\n`@kungfu-tech/buildchain` is not only a CLI package. It exports ESM toolkit APIs\nthat project scripts can import directly:\n\n```js\nimport { createBuildchainLogger } from \"@kungfu-tech/buildchain/logging\";\n```\n\nUse the API inside JavaScript or TypeScript build code. Do not spawn\n`buildchain`, download the standalone binary, or shell out through `npx` from\ncode that can import the package. The CLI is for GitHub Actions steps, shell\nscripts, and non-JavaScript tools.\n\nWhen a script runs inside `buildchain lifecycle run`, the lifecycle runner sets\n`BUILDCHAIN_LOG_PATH` and `BUILDCHAIN_LOG_RUN_ID`. Imported loggers pick up those\nenvironment variables automatically, so events emitted deep inside the build are\ngrouped into the same lifecycle summary.\n\nOutside a Buildchain lifecycle or GitHub Actions run, the logger defaults to\nconsole output unless a path is provided. Pass `path` when local scripts should\nwrite a reusable JSONL log:\n\n```js\nconst logger = createBuildchainLogger({\n path: \".buildchain/logs/native-build.jsonl\",\n source: \"user\",\n component: \"native-build\",\n});\n```\n\n## Library API\n\n```js\nimport {\n createBuildchainLogger,\n verifyBuildchainLogEvents,\n} from \"@kungfu-tech/buildchain/logging\";\n\nconst logger = createBuildchainLogger({\n source: \"user\",\n component: \"native-build\",\n});\n\nlogger.mark(\"configure.ready\", {\n phase: \"configure\",\n attributes: { preset: \"release\" },\n});\n\nawait logger.span(\"native.compile\", {\n phase: \"build\",\n attributes: { target: \"release\" },\n}, async () => {\n await compile();\n});\n\nlogger.spanSync(\"native.archive\", {\n phase: \"build\",\n attributes: { tool: \"libtool\" },\n}, () => {\n archiveStaticLibraries();\n});\n\nlogger.spawnSync(\"native.build\", \"make\", [\"-j20\"], {\n stdio: \"inherit\",\n}, {\n phase: \"build\",\n attributes: { requestedJobs: 20 },\n});\n\nconst report = verifyBuildchainLogEvents({\n path: logger.path,\n minEvents: 3,\n requirePhases: [\"configure\", \"build\"],\n requireEvents: [\n \"configure.ready\",\n \"native.compile.start\",\n \"native.compile.end\",\n ],\n});\n\nif (!report.ok) {\n throw new Error(\"Buildchain observability verification failed\");\n}\n```\n\nUse the API when a build script has internal stages that are invisible to the\nouter workflow. Keep secret values out of attributes; known sensitive keys are\nredacted, but callers should still avoid logging private material.\n\n## Candidate timelines and critical-path-safe timing\n\nLifecycle diagnostics retain their existing summed-duration tables for\ncompatibility. Do not interpret those tables as elapsed time when spans are\nnested or jobs run in parallel. Use the candidate timeline contract when a\nchange must be followed from pull-request admission through Merge Queue attempts\nto its final merge:\n\n```js\nimport {\n createCandidateTimeline,\n formatCandidateTimelineReport,\n} from \"@kungfu-tech/buildchain/candidate-timeline\";\n\nconst timeline = createCandidateTimeline({\n candidate: {\n repository: \"owner/repository\",\n baseBranch: \"dev/v4/v4.0\",\n sourceSha,\n pullRequest: 123,\n },\n events,\n});\n\nconsole.log(formatCandidateTimelineReport(timeline));\n```\n\nEach event binds a stable event id to one attempt id and may also bind a Gate\nid, merge-group SHA, workflow run, platform, partition, cache outcome, execution\nboundary, and parent span. Terminal execution states (`success`, `failure`, and\n`cancelled`) require start and completion timestamps. Non-executed states use\n`skipped`, `dependency-blocked`, or `not-required` without invented timings.\nRecord the clock and timestamp precision explicitly: GitHub Actions timestamps\nare normally provider wall-clock observations with one-second precision, while\nan in-process span may also carry a monotonic duration with millisecond or\nbetter precision.\n\nThe `buildchain.candidate-timeline/v1` artifact reports an independent critical\npath for every attempt. Its critical-path duration is the attempt's observed\nwall-clock envelope; its active duration and per-phase durations are interval\nunions. This prevents nested or parallel spans from being added twice, and it\ndoes not combine a failed or dequeued attempt with a later retry. Missing\nrequired measurements make that attempt `incomplete` instead of producing fake\nprecision.\n\nEach attempt also reports measured execution lanes, lane skew, cache outcome\ncounts, the ten longest actionable spans, and one falsifiable next optimization\ntarget. Queue residence, whole-workflow envelopes, and job parents are excluded\nfrom the actionable ranking so they cannot hide the build or qualification\nstage that can actually be changed. The target is an observation, not a causal\nclaim: repeat the same source-bound cohort and disprove it by reducing that span\nbelow the next measured span without increasing attempt elapsed time or\nfailures.\n\nShell and workflow consumers can generate the same machine artifact plus a\ncompact report:\n\n```sh\nbuildchain candidate timeline \\\n --input .buildchain/candidate-timeline-input.json \\\n --output .buildchain/candidate-timeline.json\n```\n\nThe input and output should contain only bounded correlation facts and timing\nreceipts. Do not include tokens, environment dumps, full commands, raw process\ndumps, or private absolute paths.\n\nCommonJS scripts should import Buildchain's ESM surfaces dynamically:\n\n```js\nconst { createBuildchainLogger } = await import(\"@kungfu-tech/buildchain/logging\");\nconst { collectRunnerDiagnostics } = await import(\"@kungfu-tech/buildchain/diagnostics\");\n```\n\n## Diagnostics API\n\nThe diagnostics surface collects local, non-telemetry build facts that are\nuseful when a native build is slow or flaky:\n\n```js\nimport {\n collectBuildchainDiagnostics,\n collectCacheDiagnostics,\n collectCompilerCacheDiagnostics,\n collectRunnerDiagnostics,\n collectToolDiagnostics,\n detectRequestedParallelism,\n startProcessSampler,\n summarizeDiagnosticsArtifacts,\n summarizeLifecycleObservability,\n summarizeProcessSamples,\n validateAnchoredPackageRelease,\n writeDiagnosticsArtifact,\n} from \"@kungfu-tech/buildchain/diagnostics\";\n\nconst lifecycleObservability = summarizeLifecycleObservability({\n logPath: \".buildchain/logs/events.jsonl\",\n});\nconst buildCommand = \"make\";\nconst buildArgs = [\"-j20\"];\nconst requestedParallelism = detectRequestedParallelism({\n command: buildCommand,\n args: buildArgs,\n});\nconst processSampler = startProcessSampler({\n intervalMs: 15000,\n label: \"native-build\",\n command: buildCommand,\n args: buildArgs,\n});\n// Run the long native build while the sampler is active.\nconst processSummary = summarizeProcessSamples({\n requestedParallelism: requestedParallelism.value,\n samples: processSampler.stop(),\n});\nconst cacheDiagnostics = collectCacheDiagnostics({ cwd: process.cwd() });\n\nwriteDiagnosticsArtifact(\".buildchain/artifacts/diagnostics.json\", {\n contract: \"consumer-build-diagnostics\",\n buildchain: collectBuildchainDiagnostics({ cwd: process.cwd() }),\n runner: collectRunnerDiagnostics(),\n tools: collectToolDiagnostics({ cwd: process.cwd() }),\n cache: cacheDiagnostics,\n lifecycleObservability,\n process: processSummary,\n});\n```\n\n`collectCacheDiagnostics()` includes package-manager/workspace context, selected\ncache directory stats, and compiler-cache stats from `ccache --show-stats\n--json` plus `sccache --show-stats --stats-format json` when those tools are\npresent. If a ccache build does not support JSON stats, Buildchain falls back to\nplain `ccache --show-stats` and parses the text counters. Missing cache tools\nare recorded as unavailable instead of failing the diagnostics artifact. Call\n`collectCompilerCacheDiagnostics()` directly when a consumer script only needs\ncompiler cache data. Native diagnostics also expose `compilerCaches` and\n`nativeCacheDirs` as top-level fields in each diagnostics artifact and aggregate\nsummary, so reviewers do not have to dig through nested cache sections first.\n\nProcess samples are intentionally summarized before they become long-lived\nartifacts. The summary records requested parallelism, the source of that value\n(`command`, `process-tree`, `env:MAKEFLAGS`,\n`env:CMAKE_BUILD_PARALLEL_LEVEL`, or `explicit`), observed active process\nconcurrency, elapsed sample time, total sampled CPU, and conservative command\ncategories such as `compiler`, `archive`, `linker`, `build-tool`, and `cache`.\nThe sampler detects common `make -j N`, `ninja -j N`, CMake/MSBuild/Xcode job\nflags, and MAKEFLAGS. This lets native projects distinguish \"we asked for\n`make -j20`\" from \"the build graph only kept two active compiler or archive\nchildren busy during the sampled window\" without storing environment dumps.\n\nNative repositories can opt into a reusable diagnostics profile in\n`buildchain.toml`:\n\n```toml\n[diagnostics.native]\nenabled = true\nsample_process_tree = true\ncompiler_cache = \"auto\"\nexpected_tools = [\"ccache\", \"sccache\", \"clang\", \"cl\", \"cmake\", \"ninja\"]\nartifact_dirs = [\"build\", \"dist\", \"build/Release\"]\ncache_dirs = [\".ccache\", \".sccache\"]\n```\n\nWhen enabled, diagnostics artifacts include the normalized profile, selected\ntool versions, compiler-cache stats, and configured artifact/cache directory\nstats. The profile is data-driven: Buildchain does not assume a specific\nproject such as libnode. The reusable build workflow also exposes\n`sample-process-tree`, which wraps the build lifecycle with the process sampler\nand carries the generated summary into the final diagnostics artifact. Custom\nworkflows can call `buildchain sample process-tree` directly when they need a\ndifferent command boundary.\n\nAnchored/manual package projects can also run one higher-level release-shape\ncheck instead of assembling lower-level config calls:\n\n```js\nconst anchoredReport = validateAnchoredPackageRelease({\n cwd: process.cwd(),\n requireManifest: true,\n requirePackageSetOrder: \"platforms-first-main-last\",\n requireTrustedPublishing: true,\n});\n\nif (!anchoredReport.ok) {\n throw new Error(\"Anchored package release contract failed\");\n}\n```\n\nIn an actual publish job, make that check source-lock aware:\n\n```js\nconst publishReady = validateAnchoredPackageRelease({\n cwd: process.cwd(),\n requirePublishGateSourceLock: true,\n});\n```\n\nThe source-lock inputs are read from `BUILDCHAIN_PUBLISH_SOURCE_REF`,\n`BUILDCHAIN_PUBLISH_SOURCE_SHA`, and `BUILDCHAIN_PUBLISH_SOURCE_LOCKED`, which\nthe reusable build workflow emits after resolving `publish-source-ref`. That\nturns direct channel-branch publication from `alpha/*` or `release/*` into a\nhard failure, while `publish-gate/alpha/<line>/<version>` and\n`publish-gate/release/<line>/<version>` also validate the requested consumer\nversion against configured version files and the anchor manifest.\n\n`buildchain lifecycle run` writes this small diagnostics artifact next to the\nplatform manifest by default. The per-platform diagnostics upload includes the\ncompact `diagnostics.json`, `diagnostics-manifest.json`, lifecycle\n`events.jsonl`, and, when process sampling is enabled, copied\n`process-summary.json` and `process-samples.jsonl` sidecars. The sidecar\nmanifest records each uploaded diagnostics file with its relative path, byte\ncount, and sha256 hash. It is intended to stay small enough to download without\nfetching large binary packages, and it should not include full environment dumps\nor secret-looking values.\n\nUse `summarizeDiagnosticsArtifacts()` when a matrix build uploads one\ndiagnostics artifact per platform. The summary keeps each platform's lifecycle\nstage table, adds a lifecycle total duration, carries the top slow spans, and\naggregates warning/error counts plus the slowest platforms. Per-platform rows\nalso include compact runner facts, checked tool versions/missing tools, package\nmanager/cache directory details, compiler cache availability, and a compact\nprocess sampler view with requested parallelism, observed max active processes,\nthe ratio between them, sample count, process categories, and top sampled\ncommand basenames. That gives release reviewers a small cross-platform timing,\nrunner, tool, cache, and concurrency view without downloading full build outputs\nor process sidecars.\n\nThe reusable build workflow writes that rollup as `diagnostics-summary.json` and\nuploads it in a separate aggregate diagnostics summary artifact. Consumers can\nread the `build-diagnostics-summary-artifact` output when they need only timing,\nwarning/error, runner, cache, and process-sampler context instead of the build\nsummary or binary artifacts. Per-platform rows keep the diagnostics `links`\nobject, including the binary artifact name, manifest artifact name, diagnostics\nartifact name, platform id, diagnostics sidecar manifest path, manifest path,\nsummary path, and process sidecar paths when present. They also keep compact\nrunner/tool/cache summaries so reviewers can tell whether a slow row ran on the\nexpected runner, missed an expected tool, or lacked useful compiler-cache stats.\nWhen the downloaded platform diagnostics include a sibling\n`diagnostics-manifest.json`, `summarizeDiagnosticsArtifacts()` carries a compact\n`diagnosticsManifest` section for that platform and verifies the manifest's\n`diagnostics.json` byte count and sha256. Missing or mismatched sidecar manifests\nincrement `diagnosticsManifestWarningCount`, so reviewers can distinguish\ndiagnostics sidecar drift from lifecycle warnings or build failures.\nThe same summary carries `diagnosticsContract` per platform and aggregates\n`diagnosticsContractWarningCount` when a downloaded `diagnostics.json` does not\nmatch `BUILDCHAIN_DIAGNOSTICS_CONTRACT`.\n\nThe diagnostics SDK also exports stable contract constants such as\n`BUILDCHAIN_DIAGNOSTICS_SUMMARY_CONTRACT`,\n`BUILDCHAIN_DIAGNOSTICS_MANIFEST_CONTRACT`,\n`BUILDCHAIN_PROCESS_SAMPLE_REPORT_CONTRACT`,\n`BUILDCHAIN_PROCESS_SAMPLE_SUMMARY_CONTRACT`, and\n`BUILDCHAIN_ANCHORED_PACKAGE_RELEASE_VALIDATION_CONTRACT` from\n`@kungfu-tech/buildchain/diagnostics`; consumers should compare against those\nconstants instead of hardcoding contract strings.\n\nThe CLI exposes the same aggregation for shell and workflow steps:\n\n```bash\nbuildchain diagnostics summary \\\n .buildchain/artifacts/linux-x64/diagnostics.json \\\n .buildchain/artifacts/macos-arm64/diagnostics.json \\\n --output .buildchain/artifacts/diagnostics-summary.json \\\n --json\n```\n\nOmit `--json` when the workflow log should show a compact platform table with\nlifecycle stages, artifact scan/upload time, total time, requested jobs,\nobserved active processes, warnings, and errors.\n\nFor long native build commands, the CLI can also sample the child process tree\nwhile preserving the wrapped command's exit code:\n\n```bash\nbuildchain sample process-tree \\\n --label native-build \\\n --interval-ms 15000 \\\n --output .buildchain/diagnostics/process-samples.jsonl \\\n --summary-output .buildchain/diagnostics/process-summary.json \\\n -- \\\n make -j20\n```\n\nThe JSONL sample file stores timestamped process-tree snapshots with full\nredacted command lines, command basenames, CPU percentages when available,\nelapsed time, and requested parallelism context. Unix and macOS snapshots read\nthe full `ps args` field instead of truncated `comm` names. Windows snapshots\nread `Win32_Process` command lines through PowerShell so the sampler no longer\nreturns an empty process set on Windows runners. The summary file records\nobserved concurrency, total sampled CPU, command categories, and top command\nbasenames. This is intended for diagnosing low-utilization tails such as\narchive/link phases without logging full environment dumps.\n\nThe lifecycle observability summary is stage-wide, not just final-step timing:\nwhen install and build write to the same Buildchain log, the final platform\nmanifest can show both stages and the slowest spans.\n\n## CLI Logging\n\n```bash\nbuildchain mark \\\n --event native.configure \\\n --phase configure \\\n --component cmake \\\n --attribute preset=release\n\nbuildchain span \\\n --event native.build \\\n --phase build \\\n --component cmake \\\n -- cmake --build build --config Release\n\nbuildchain log summary --json\nbuildchain verify observability-log .buildchain/logs/events.jsonl --min-events 4\n```\n\nEvery event records a timestamp. `span` records duration and preserves the\nwrapped command's exit code.\n\n## Release Gate\n\nBuildchain's own binary distribution lane verifies required log events before\nuploading release assets. Consumers can apply the same pattern:\n\n```bash\nbuildchain verify observability-log .buildchain/logs/events.jsonl \\\n --require-phase build \\\n --require-phase package \\\n --require-component workflow \\\n --require-event native.build.start \\\n --require-event native.build.end\n```\n\nThis makes missing instrumentation a release failure instead of a dashboard\nafterthought."
|
|
2038
2038
|
},
|
|
2039
2039
|
{
|
|
2040
2040
|
"id": "manual:versioning",
|
|
@@ -2682,7 +2682,7 @@
|
|
|
2682
2682
|
"path": "docs/toolkit-observability.md",
|
|
2683
2683
|
"plane": "use",
|
|
2684
2684
|
"exists": true,
|
|
2685
|
-
"digest": "sha256:
|
|
2685
|
+
"digest": "sha256:b2ffc3b2520ca7879309b7607b2eaa1aceb647503e893887159a6916de14e2d7"
|
|
2686
2686
|
},
|
|
2687
2687
|
{
|
|
2688
2688
|
"id": "site-bundle-contract",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"contract": "kungfu-buildchain-public-surface-reverse-audit",
|
|
22
22
|
"path": "dist/site/public-surface-audit.json",
|
|
23
23
|
"status": "passed",
|
|
24
|
-
"sha256": "
|
|
24
|
+
"sha256": "d5cebf22f557ed52c39c223058ce46eaaa4a91947d6a74e30a67051ad48b7e65",
|
|
25
25
|
"summary": {
|
|
26
26
|
"cliCommandCount": 86,
|
|
27
27
|
"workflowCount": 51,
|
|
@@ -228,7 +228,7 @@
|
|
|
228
228
|
"contract": "kungfu-buildchain-public-surface-reverse-audit",
|
|
229
229
|
"path": "dist/site/public-surface-audit.json",
|
|
230
230
|
"status": "passed",
|
|
231
|
-
"sha256": "
|
|
231
|
+
"sha256": "d5cebf22f557ed52c39c223058ce46eaaa4a91947d6a74e30a67051ad48b7e65",
|
|
232
232
|
"summary": {
|
|
233
233
|
"cliCommandCount": 86,
|
|
234
234
|
"workflowCount": 51,
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
"title": "Toolkit observability",
|
|
178
178
|
"path": "docs/toolkit-observability.md",
|
|
179
179
|
"plane": "use",
|
|
180
|
-
"digest": "sha256:
|
|
180
|
+
"digest": "sha256:b2ffc3b2520ca7879309b7607b2eaa1aceb647503e893887159a6916de14e2d7",
|
|
181
181
|
"capabilityGroup": "observability-diagnostics",
|
|
182
182
|
"audience": [
|
|
183
183
|
"developer",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"specifier": "@kungfu-tech/buildchain/candidate-timeline",
|
|
113
113
|
"export": "./candidate-timeline",
|
|
114
114
|
"target": "./packages/core/candidate-timeline.js",
|
|
115
|
-
"digest": "sha256:
|
|
115
|
+
"digest": "sha256:0b79dbbb523c7adbd969a0b4bec73d9adabf280319fc9a73b6912f4b95a9d6e8",
|
|
116
116
|
"summary": "Source-bound candidate event normalization, per-attempt critical-path-safe aggregation, and compact reporting APIs.",
|
|
117
117
|
"capabilityGroup": "observability-diagnostics",
|
|
118
118
|
"audience": [
|
|
@@ -1930,7 +1930,7 @@
|
|
|
1930
1930
|
],
|
|
1931
1931
|
"maturity": "stable",
|
|
1932
1932
|
"sourcePath": "docs/toolkit-observability.md",
|
|
1933
|
-
"digest": "sha256:
|
|
1933
|
+
"digest": "sha256:b2ffc3b2520ca7879309b7607b2eaa1aceb647503e893887159a6916de14e2d7",
|
|
1934
1934
|
"headings": [
|
|
1935
1935
|
{
|
|
1936
1936
|
"level": 1,
|
|
@@ -1968,7 +1968,7 @@
|
|
|
1968
1968
|
"anchor": "release-gate"
|
|
1969
1969
|
}
|
|
1970
1970
|
],
|
|
1971
|
-
"markdown": "# Toolkit Observability\n\nBuildchain ships a small logging toolkit for repository workflows and project\nscripts. The goal is to separate time spent in Buildchain's framework from time\nspent in the consumer's own build, test, packaging, and publish steps.\n\n## Choose API or CLI\n\n`@kungfu-tech/buildchain` is not only a CLI package. It exports ESM toolkit APIs\nthat project scripts can import directly:\n\n```js\nimport { createBuildchainLogger } from \"@kungfu-tech/buildchain/logging\";\n```\n\nUse the API inside JavaScript or TypeScript build code. Do not spawn\n`buildchain`, download the standalone binary, or shell out through `npx` from\ncode that can import the package. The CLI is for GitHub Actions steps, shell\nscripts, and non-JavaScript tools.\n\nWhen a script runs inside `buildchain lifecycle run`, the lifecycle runner sets\n`BUILDCHAIN_LOG_PATH` and `BUILDCHAIN_LOG_RUN_ID`. Imported loggers pick up those\nenvironment variables automatically, so events emitted deep inside the build are\ngrouped into the same lifecycle summary.\n\nOutside a Buildchain lifecycle or GitHub Actions run, the logger defaults to\nconsole output unless a path is provided. Pass `path` when local scripts should\nwrite a reusable JSONL log:\n\n```js\nconst logger = createBuildchainLogger({\n path: \".buildchain/logs/native-build.jsonl\",\n source: \"user\",\n component: \"native-build\",\n});\n```\n\n## Library API\n\n```js\nimport {\n createBuildchainLogger,\n verifyBuildchainLogEvents,\n} from \"@kungfu-tech/buildchain/logging\";\n\nconst logger = createBuildchainLogger({\n source: \"user\",\n component: \"native-build\",\n});\n\nlogger.mark(\"configure.ready\", {\n phase: \"configure\",\n attributes: { preset: \"release\" },\n});\n\nawait logger.span(\"native.compile\", {\n phase: \"build\",\n attributes: { target: \"release\" },\n}, async () => {\n await compile();\n});\n\nlogger.spanSync(\"native.archive\", {\n phase: \"build\",\n attributes: { tool: \"libtool\" },\n}, () => {\n archiveStaticLibraries();\n});\n\nlogger.spawnSync(\"native.build\", \"make\", [\"-j20\"], {\n stdio: \"inherit\",\n}, {\n phase: \"build\",\n attributes: { requestedJobs: 20 },\n});\n\nconst report = verifyBuildchainLogEvents({\n path: logger.path,\n minEvents: 3,\n requirePhases: [\"configure\", \"build\"],\n requireEvents: [\n \"configure.ready\",\n \"native.compile.start\",\n \"native.compile.end\",\n ],\n});\n\nif (!report.ok) {\n throw new Error(\"Buildchain observability verification failed\");\n}\n```\n\nUse the API when a build script has internal stages that are invisible to the\nouter workflow. Keep secret values out of attributes; known sensitive keys are\nredacted, but callers should still avoid logging private material.\n\n## Candidate timelines and critical-path-safe timing\n\nLifecycle diagnostics retain their existing summed-duration tables for\ncompatibility. Do not interpret those tables as elapsed time when spans are\nnested or jobs run in parallel. Use the candidate timeline contract when a\nchange must be followed from pull-request admission through Merge Queue attempts\nto its final merge:\n\n```js\nimport {\n createCandidateTimeline,\n formatCandidateTimelineReport,\n} from \"@kungfu-tech/buildchain/candidate-timeline\";\n\nconst timeline = createCandidateTimeline({\n candidate: {\n repository: \"owner/repository\",\n baseBranch: \"dev/v4/v4.0\",\n sourceSha,\n pullRequest: 123,\n },\n events,\n});\n\nconsole.log(formatCandidateTimelineReport(timeline));\n```\n\nEach event binds a stable event id to one attempt id and may also bind a Gate\nid, merge-group SHA, workflow run, platform, partition, cache outcome, execution\nboundary, and parent span. Terminal execution states (`success`, `failure`, and\n`cancelled`) require start and completion timestamps. Non-executed states use\n`skipped`, `dependency-blocked`, or `not-required` without invented timings.\nRecord the clock and timestamp precision explicitly: GitHub Actions timestamps\nare normally provider wall-clock observations with one-second precision, while\nan in-process span may also carry a monotonic duration with millisecond or\nbetter precision.\n\nThe `buildchain.candidate-timeline/v1` artifact reports an independent critical\npath for every attempt. Its critical-path duration is the attempt's observed\nwall-clock envelope; its active duration and per-phase durations are interval\nunions. This prevents nested or parallel spans from being added twice, and it\ndoes not combine a failed or dequeued attempt with a later retry. Missing\nrequired measurements make that attempt `incomplete` instead of producing fake\nprecision.\n\nShell and workflow consumers can generate the same machine artifact plus a\ncompact report:\n\n```sh\nbuildchain candidate timeline \\\n --input .buildchain/candidate-timeline-input.json \\\n --output .buildchain/candidate-timeline.json\n```\n\nThe input and output should contain only bounded correlation facts and timing\nreceipts. Do not include tokens, environment dumps, full commands, raw process\ndumps, or private absolute paths.\n\nCommonJS scripts should import Buildchain's ESM surfaces dynamically:\n\n```js\nconst { createBuildchainLogger } = await import(\"@kungfu-tech/buildchain/logging\");\nconst { collectRunnerDiagnostics } = await import(\"@kungfu-tech/buildchain/diagnostics\");\n```\n\n## Diagnostics API\n\nThe diagnostics surface collects local, non-telemetry build facts that are\nuseful when a native build is slow or flaky:\n\n```js\nimport {\n collectBuildchainDiagnostics,\n collectCacheDiagnostics,\n collectCompilerCacheDiagnostics,\n collectRunnerDiagnostics,\n collectToolDiagnostics,\n detectRequestedParallelism,\n startProcessSampler,\n summarizeDiagnosticsArtifacts,\n summarizeLifecycleObservability,\n summarizeProcessSamples,\n validateAnchoredPackageRelease,\n writeDiagnosticsArtifact,\n} from \"@kungfu-tech/buildchain/diagnostics\";\n\nconst lifecycleObservability = summarizeLifecycleObservability({\n logPath: \".buildchain/logs/events.jsonl\",\n});\nconst buildCommand = \"make\";\nconst buildArgs = [\"-j20\"];\nconst requestedParallelism = detectRequestedParallelism({\n command: buildCommand,\n args: buildArgs,\n});\nconst processSampler = startProcessSampler({\n intervalMs: 15000,\n label: \"native-build\",\n command: buildCommand,\n args: buildArgs,\n});\n// Run the long native build while the sampler is active.\nconst processSummary = summarizeProcessSamples({\n requestedParallelism: requestedParallelism.value,\n samples: processSampler.stop(),\n});\nconst cacheDiagnostics = collectCacheDiagnostics({ cwd: process.cwd() });\n\nwriteDiagnosticsArtifact(\".buildchain/artifacts/diagnostics.json\", {\n contract: \"consumer-build-diagnostics\",\n buildchain: collectBuildchainDiagnostics({ cwd: process.cwd() }),\n runner: collectRunnerDiagnostics(),\n tools: collectToolDiagnostics({ cwd: process.cwd() }),\n cache: cacheDiagnostics,\n lifecycleObservability,\n process: processSummary,\n});\n```\n\n`collectCacheDiagnostics()` includes package-manager/workspace context, selected\ncache directory stats, and compiler-cache stats from `ccache --show-stats\n--json` plus `sccache --show-stats --stats-format json` when those tools are\npresent. If a ccache build does not support JSON stats, Buildchain falls back to\nplain `ccache --show-stats` and parses the text counters. Missing cache tools\nare recorded as unavailable instead of failing the diagnostics artifact. Call\n`collectCompilerCacheDiagnostics()` directly when a consumer script only needs\ncompiler cache data. Native diagnostics also expose `compilerCaches` and\n`nativeCacheDirs` as top-level fields in each diagnostics artifact and aggregate\nsummary, so reviewers do not have to dig through nested cache sections first.\n\nProcess samples are intentionally summarized before they become long-lived\nartifacts. The summary records requested parallelism, the source of that value\n(`command`, `process-tree`, `env:MAKEFLAGS`,\n`env:CMAKE_BUILD_PARALLEL_LEVEL`, or `explicit`), observed active process\nconcurrency, elapsed sample time, total sampled CPU, and conservative command\ncategories such as `compiler`, `archive`, `linker`, `build-tool`, and `cache`.\nThe sampler detects common `make -j N`, `ninja -j N`, CMake/MSBuild/Xcode job\nflags, and MAKEFLAGS. This lets native projects distinguish \"we asked for\n`make -j20`\" from \"the build graph only kept two active compiler or archive\nchildren busy during the sampled window\" without storing environment dumps.\n\nNative repositories can opt into a reusable diagnostics profile in\n`buildchain.toml`:\n\n```toml\n[diagnostics.native]\nenabled = true\nsample_process_tree = true\ncompiler_cache = \"auto\"\nexpected_tools = [\"ccache\", \"sccache\", \"clang\", \"cl\", \"cmake\", \"ninja\"]\nartifact_dirs = [\"build\", \"dist\", \"build/Release\"]\ncache_dirs = [\".ccache\", \".sccache\"]\n```\n\nWhen enabled, diagnostics artifacts include the normalized profile, selected\ntool versions, compiler-cache stats, and configured artifact/cache directory\nstats. The profile is data-driven: Buildchain does not assume a specific\nproject such as libnode. The reusable build workflow also exposes\n`sample-process-tree`, which wraps the build lifecycle with the process sampler\nand carries the generated summary into the final diagnostics artifact. Custom\nworkflows can call `buildchain sample process-tree` directly when they need a\ndifferent command boundary.\n\nAnchored/manual package projects can also run one higher-level release-shape\ncheck instead of assembling lower-level config calls:\n\n```js\nconst anchoredReport = validateAnchoredPackageRelease({\n cwd: process.cwd(),\n requireManifest: true,\n requirePackageSetOrder: \"platforms-first-main-last\",\n requireTrustedPublishing: true,\n});\n\nif (!anchoredReport.ok) {\n throw new Error(\"Anchored package release contract failed\");\n}\n```\n\nIn an actual publish job, make that check source-lock aware:\n\n```js\nconst publishReady = validateAnchoredPackageRelease({\n cwd: process.cwd(),\n requirePublishGateSourceLock: true,\n});\n```\n\nThe source-lock inputs are read from `BUILDCHAIN_PUBLISH_SOURCE_REF`,\n`BUILDCHAIN_PUBLISH_SOURCE_SHA`, and `BUILDCHAIN_PUBLISH_SOURCE_LOCKED`, which\nthe reusable build workflow emits after resolving `publish-source-ref`. That\nturns direct channel-branch publication from `alpha/*` or `release/*` into a\nhard failure, while `publish-gate/alpha/<line>/<version>` and\n`publish-gate/release/<line>/<version>` also validate the requested consumer\nversion against configured version files and the anchor manifest.\n\n`buildchain lifecycle run` writes this small diagnostics artifact next to the\nplatform manifest by default. The per-platform diagnostics upload includes the\ncompact `diagnostics.json`, `diagnostics-manifest.json`, lifecycle\n`events.jsonl`, and, when process sampling is enabled, copied\n`process-summary.json` and `process-samples.jsonl` sidecars. The sidecar\nmanifest records each uploaded diagnostics file with its relative path, byte\ncount, and sha256 hash. It is intended to stay small enough to download without\nfetching large binary packages, and it should not include full environment dumps\nor secret-looking values.\n\nUse `summarizeDiagnosticsArtifacts()` when a matrix build uploads one\ndiagnostics artifact per platform. The summary keeps each platform's lifecycle\nstage table, adds a lifecycle total duration, carries the top slow spans, and\naggregates warning/error counts plus the slowest platforms. Per-platform rows\nalso include compact runner facts, checked tool versions/missing tools, package\nmanager/cache directory details, compiler cache availability, and a compact\nprocess sampler view with requested parallelism, observed max active processes,\nthe ratio between them, sample count, process categories, and top sampled\ncommand basenames. That gives release reviewers a small cross-platform timing,\nrunner, tool, cache, and concurrency view without downloading full build outputs\nor process sidecars.\n\nThe reusable build workflow writes that rollup as `diagnostics-summary.json` and\nuploads it in a separate aggregate diagnostics summary artifact. Consumers can\nread the `build-diagnostics-summary-artifact` output when they need only timing,\nwarning/error, runner, cache, and process-sampler context instead of the build\nsummary or binary artifacts. Per-platform rows keep the diagnostics `links`\nobject, including the binary artifact name, manifest artifact name, diagnostics\nartifact name, platform id, diagnostics sidecar manifest path, manifest path,\nsummary path, and process sidecar paths when present. They also keep compact\nrunner/tool/cache summaries so reviewers can tell whether a slow row ran on the\nexpected runner, missed an expected tool, or lacked useful compiler-cache stats.\nWhen the downloaded platform diagnostics include a sibling\n`diagnostics-manifest.json`, `summarizeDiagnosticsArtifacts()` carries a compact\n`diagnosticsManifest` section for that platform and verifies the manifest's\n`diagnostics.json` byte count and sha256. Missing or mismatched sidecar manifests\nincrement `diagnosticsManifestWarningCount`, so reviewers can distinguish\ndiagnostics sidecar drift from lifecycle warnings or build failures.\nThe same summary carries `diagnosticsContract` per platform and aggregates\n`diagnosticsContractWarningCount` when a downloaded `diagnostics.json` does not\nmatch `BUILDCHAIN_DIAGNOSTICS_CONTRACT`.\n\nThe diagnostics SDK also exports stable contract constants such as\n`BUILDCHAIN_DIAGNOSTICS_SUMMARY_CONTRACT`,\n`BUILDCHAIN_DIAGNOSTICS_MANIFEST_CONTRACT`,\n`BUILDCHAIN_PROCESS_SAMPLE_REPORT_CONTRACT`,\n`BUILDCHAIN_PROCESS_SAMPLE_SUMMARY_CONTRACT`, and\n`BUILDCHAIN_ANCHORED_PACKAGE_RELEASE_VALIDATION_CONTRACT` from\n`@kungfu-tech/buildchain/diagnostics`; consumers should compare against those\nconstants instead of hardcoding contract strings.\n\nThe CLI exposes the same aggregation for shell and workflow steps:\n\n```bash\nbuildchain diagnostics summary \\\n .buildchain/artifacts/linux-x64/diagnostics.json \\\n .buildchain/artifacts/macos-arm64/diagnostics.json \\\n --output .buildchain/artifacts/diagnostics-summary.json \\\n --json\n```\n\nOmit `--json` when the workflow log should show a compact platform table with\nlifecycle stages, artifact scan/upload time, total time, requested jobs,\nobserved active processes, warnings, and errors.\n\nFor long native build commands, the CLI can also sample the child process tree\nwhile preserving the wrapped command's exit code:\n\n```bash\nbuildchain sample process-tree \\\n --label native-build \\\n --interval-ms 15000 \\\n --output .buildchain/diagnostics/process-samples.jsonl \\\n --summary-output .buildchain/diagnostics/process-summary.json \\\n -- \\\n make -j20\n```\n\nThe JSONL sample file stores timestamped process-tree snapshots with full\nredacted command lines, command basenames, CPU percentages when available,\nelapsed time, and requested parallelism context. Unix and macOS snapshots read\nthe full `ps args` field instead of truncated `comm` names. Windows snapshots\nread `Win32_Process` command lines through PowerShell so the sampler no longer\nreturns an empty process set on Windows runners. The summary file records\nobserved concurrency, total sampled CPU, command categories, and top command\nbasenames. This is intended for diagnosing low-utilization tails such as\narchive/link phases without logging full environment dumps.\n\nThe lifecycle observability summary is stage-wide, not just final-step timing:\nwhen install and build write to the same Buildchain log, the final platform\nmanifest can show both stages and the slowest spans.\n\n## CLI Logging\n\n```bash\nbuildchain mark \\\n --event native.configure \\\n --phase configure \\\n --component cmake \\\n --attribute preset=release\n\nbuildchain span \\\n --event native.build \\\n --phase build \\\n --component cmake \\\n -- cmake --build build --config Release\n\nbuildchain log summary --json\nbuildchain verify observability-log .buildchain/logs/events.jsonl --min-events 4\n```\n\nEvery event records a timestamp. `span` records duration and preserves the\nwrapped command's exit code.\n\n## Release Gate\n\nBuildchain's own binary distribution lane verifies required log events before\nuploading release assets. Consumers can apply the same pattern:\n\n```bash\nbuildchain verify observability-log .buildchain/logs/events.jsonl \\\n --require-phase build \\\n --require-phase package \\\n --require-component workflow \\\n --require-event native.build.start \\\n --require-event native.build.end\n```\n\nThis makes missing instrumentation a release failure instead of a dashboard\nafterthought."
|
|
1971
|
+
"markdown": "# Toolkit Observability\n\nBuildchain ships a small logging toolkit for repository workflows and project\nscripts. The goal is to separate time spent in Buildchain's framework from time\nspent in the consumer's own build, test, packaging, and publish steps.\n\n## Choose API or CLI\n\n`@kungfu-tech/buildchain` is not only a CLI package. It exports ESM toolkit APIs\nthat project scripts can import directly:\n\n```js\nimport { createBuildchainLogger } from \"@kungfu-tech/buildchain/logging\";\n```\n\nUse the API inside JavaScript or TypeScript build code. Do not spawn\n`buildchain`, download the standalone binary, or shell out through `npx` from\ncode that can import the package. The CLI is for GitHub Actions steps, shell\nscripts, and non-JavaScript tools.\n\nWhen a script runs inside `buildchain lifecycle run`, the lifecycle runner sets\n`BUILDCHAIN_LOG_PATH` and `BUILDCHAIN_LOG_RUN_ID`. Imported loggers pick up those\nenvironment variables automatically, so events emitted deep inside the build are\ngrouped into the same lifecycle summary.\n\nOutside a Buildchain lifecycle or GitHub Actions run, the logger defaults to\nconsole output unless a path is provided. Pass `path` when local scripts should\nwrite a reusable JSONL log:\n\n```js\nconst logger = createBuildchainLogger({\n path: \".buildchain/logs/native-build.jsonl\",\n source: \"user\",\n component: \"native-build\",\n});\n```\n\n## Library API\n\n```js\nimport {\n createBuildchainLogger,\n verifyBuildchainLogEvents,\n} from \"@kungfu-tech/buildchain/logging\";\n\nconst logger = createBuildchainLogger({\n source: \"user\",\n component: \"native-build\",\n});\n\nlogger.mark(\"configure.ready\", {\n phase: \"configure\",\n attributes: { preset: \"release\" },\n});\n\nawait logger.span(\"native.compile\", {\n phase: \"build\",\n attributes: { target: \"release\" },\n}, async () => {\n await compile();\n});\n\nlogger.spanSync(\"native.archive\", {\n phase: \"build\",\n attributes: { tool: \"libtool\" },\n}, () => {\n archiveStaticLibraries();\n});\n\nlogger.spawnSync(\"native.build\", \"make\", [\"-j20\"], {\n stdio: \"inherit\",\n}, {\n phase: \"build\",\n attributes: { requestedJobs: 20 },\n});\n\nconst report = verifyBuildchainLogEvents({\n path: logger.path,\n minEvents: 3,\n requirePhases: [\"configure\", \"build\"],\n requireEvents: [\n \"configure.ready\",\n \"native.compile.start\",\n \"native.compile.end\",\n ],\n});\n\nif (!report.ok) {\n throw new Error(\"Buildchain observability verification failed\");\n}\n```\n\nUse the API when a build script has internal stages that are invisible to the\nouter workflow. Keep secret values out of attributes; known sensitive keys are\nredacted, but callers should still avoid logging private material.\n\n## Candidate timelines and critical-path-safe timing\n\nLifecycle diagnostics retain their existing summed-duration tables for\ncompatibility. Do not interpret those tables as elapsed time when spans are\nnested or jobs run in parallel. Use the candidate timeline contract when a\nchange must be followed from pull-request admission through Merge Queue attempts\nto its final merge:\n\n```js\nimport {\n createCandidateTimeline,\n formatCandidateTimelineReport,\n} from \"@kungfu-tech/buildchain/candidate-timeline\";\n\nconst timeline = createCandidateTimeline({\n candidate: {\n repository: \"owner/repository\",\n baseBranch: \"dev/v4/v4.0\",\n sourceSha,\n pullRequest: 123,\n },\n events,\n});\n\nconsole.log(formatCandidateTimelineReport(timeline));\n```\n\nEach event binds a stable event id to one attempt id and may also bind a Gate\nid, merge-group SHA, workflow run, platform, partition, cache outcome, execution\nboundary, and parent span. Terminal execution states (`success`, `failure`, and\n`cancelled`) require start and completion timestamps. Non-executed states use\n`skipped`, `dependency-blocked`, or `not-required` without invented timings.\nRecord the clock and timestamp precision explicitly: GitHub Actions timestamps\nare normally provider wall-clock observations with one-second precision, while\nan in-process span may also carry a monotonic duration with millisecond or\nbetter precision.\n\nThe `buildchain.candidate-timeline/v1` artifact reports an independent critical\npath for every attempt. Its critical-path duration is the attempt's observed\nwall-clock envelope; its active duration and per-phase durations are interval\nunions. This prevents nested or parallel spans from being added twice, and it\ndoes not combine a failed or dequeued attempt with a later retry. Missing\nrequired measurements make that attempt `incomplete` instead of producing fake\nprecision.\n\nEach attempt also reports measured execution lanes, lane skew, cache outcome\ncounts, the ten longest actionable spans, and one falsifiable next optimization\ntarget. Queue residence, whole-workflow envelopes, and job parents are excluded\nfrom the actionable ranking so they cannot hide the build or qualification\nstage that can actually be changed. The target is an observation, not a causal\nclaim: repeat the same source-bound cohort and disprove it by reducing that span\nbelow the next measured span without increasing attempt elapsed time or\nfailures.\n\nShell and workflow consumers can generate the same machine artifact plus a\ncompact report:\n\n```sh\nbuildchain candidate timeline \\\n --input .buildchain/candidate-timeline-input.json \\\n --output .buildchain/candidate-timeline.json\n```\n\nThe input and output should contain only bounded correlation facts and timing\nreceipts. Do not include tokens, environment dumps, full commands, raw process\ndumps, or private absolute paths.\n\nCommonJS scripts should import Buildchain's ESM surfaces dynamically:\n\n```js\nconst { createBuildchainLogger } = await import(\"@kungfu-tech/buildchain/logging\");\nconst { collectRunnerDiagnostics } = await import(\"@kungfu-tech/buildchain/diagnostics\");\n```\n\n## Diagnostics API\n\nThe diagnostics surface collects local, non-telemetry build facts that are\nuseful when a native build is slow or flaky:\n\n```js\nimport {\n collectBuildchainDiagnostics,\n collectCacheDiagnostics,\n collectCompilerCacheDiagnostics,\n collectRunnerDiagnostics,\n collectToolDiagnostics,\n detectRequestedParallelism,\n startProcessSampler,\n summarizeDiagnosticsArtifacts,\n summarizeLifecycleObservability,\n summarizeProcessSamples,\n validateAnchoredPackageRelease,\n writeDiagnosticsArtifact,\n} from \"@kungfu-tech/buildchain/diagnostics\";\n\nconst lifecycleObservability = summarizeLifecycleObservability({\n logPath: \".buildchain/logs/events.jsonl\",\n});\nconst buildCommand = \"make\";\nconst buildArgs = [\"-j20\"];\nconst requestedParallelism = detectRequestedParallelism({\n command: buildCommand,\n args: buildArgs,\n});\nconst processSampler = startProcessSampler({\n intervalMs: 15000,\n label: \"native-build\",\n command: buildCommand,\n args: buildArgs,\n});\n// Run the long native build while the sampler is active.\nconst processSummary = summarizeProcessSamples({\n requestedParallelism: requestedParallelism.value,\n samples: processSampler.stop(),\n});\nconst cacheDiagnostics = collectCacheDiagnostics({ cwd: process.cwd() });\n\nwriteDiagnosticsArtifact(\".buildchain/artifacts/diagnostics.json\", {\n contract: \"consumer-build-diagnostics\",\n buildchain: collectBuildchainDiagnostics({ cwd: process.cwd() }),\n runner: collectRunnerDiagnostics(),\n tools: collectToolDiagnostics({ cwd: process.cwd() }),\n cache: cacheDiagnostics,\n lifecycleObservability,\n process: processSummary,\n});\n```\n\n`collectCacheDiagnostics()` includes package-manager/workspace context, selected\ncache directory stats, and compiler-cache stats from `ccache --show-stats\n--json` plus `sccache --show-stats --stats-format json` when those tools are\npresent. If a ccache build does not support JSON stats, Buildchain falls back to\nplain `ccache --show-stats` and parses the text counters. Missing cache tools\nare recorded as unavailable instead of failing the diagnostics artifact. Call\n`collectCompilerCacheDiagnostics()` directly when a consumer script only needs\ncompiler cache data. Native diagnostics also expose `compilerCaches` and\n`nativeCacheDirs` as top-level fields in each diagnostics artifact and aggregate\nsummary, so reviewers do not have to dig through nested cache sections first.\n\nProcess samples are intentionally summarized before they become long-lived\nartifacts. The summary records requested parallelism, the source of that value\n(`command`, `process-tree`, `env:MAKEFLAGS`,\n`env:CMAKE_BUILD_PARALLEL_LEVEL`, or `explicit`), observed active process\nconcurrency, elapsed sample time, total sampled CPU, and conservative command\ncategories such as `compiler`, `archive`, `linker`, `build-tool`, and `cache`.\nThe sampler detects common `make -j N`, `ninja -j N`, CMake/MSBuild/Xcode job\nflags, and MAKEFLAGS. This lets native projects distinguish \"we asked for\n`make -j20`\" from \"the build graph only kept two active compiler or archive\nchildren busy during the sampled window\" without storing environment dumps.\n\nNative repositories can opt into a reusable diagnostics profile in\n`buildchain.toml`:\n\n```toml\n[diagnostics.native]\nenabled = true\nsample_process_tree = true\ncompiler_cache = \"auto\"\nexpected_tools = [\"ccache\", \"sccache\", \"clang\", \"cl\", \"cmake\", \"ninja\"]\nartifact_dirs = [\"build\", \"dist\", \"build/Release\"]\ncache_dirs = [\".ccache\", \".sccache\"]\n```\n\nWhen enabled, diagnostics artifacts include the normalized profile, selected\ntool versions, compiler-cache stats, and configured artifact/cache directory\nstats. The profile is data-driven: Buildchain does not assume a specific\nproject such as libnode. The reusable build workflow also exposes\n`sample-process-tree`, which wraps the build lifecycle with the process sampler\nand carries the generated summary into the final diagnostics artifact. Custom\nworkflows can call `buildchain sample process-tree` directly when they need a\ndifferent command boundary.\n\nAnchored/manual package projects can also run one higher-level release-shape\ncheck instead of assembling lower-level config calls:\n\n```js\nconst anchoredReport = validateAnchoredPackageRelease({\n cwd: process.cwd(),\n requireManifest: true,\n requirePackageSetOrder: \"platforms-first-main-last\",\n requireTrustedPublishing: true,\n});\n\nif (!anchoredReport.ok) {\n throw new Error(\"Anchored package release contract failed\");\n}\n```\n\nIn an actual publish job, make that check source-lock aware:\n\n```js\nconst publishReady = validateAnchoredPackageRelease({\n cwd: process.cwd(),\n requirePublishGateSourceLock: true,\n});\n```\n\nThe source-lock inputs are read from `BUILDCHAIN_PUBLISH_SOURCE_REF`,\n`BUILDCHAIN_PUBLISH_SOURCE_SHA`, and `BUILDCHAIN_PUBLISH_SOURCE_LOCKED`, which\nthe reusable build workflow emits after resolving `publish-source-ref`. That\nturns direct channel-branch publication from `alpha/*` or `release/*` into a\nhard failure, while `publish-gate/alpha/<line>/<version>` and\n`publish-gate/release/<line>/<version>` also validate the requested consumer\nversion against configured version files and the anchor manifest.\n\n`buildchain lifecycle run` writes this small diagnostics artifact next to the\nplatform manifest by default. The per-platform diagnostics upload includes the\ncompact `diagnostics.json`, `diagnostics-manifest.json`, lifecycle\n`events.jsonl`, and, when process sampling is enabled, copied\n`process-summary.json` and `process-samples.jsonl` sidecars. The sidecar\nmanifest records each uploaded diagnostics file with its relative path, byte\ncount, and sha256 hash. It is intended to stay small enough to download without\nfetching large binary packages, and it should not include full environment dumps\nor secret-looking values.\n\nUse `summarizeDiagnosticsArtifacts()` when a matrix build uploads one\ndiagnostics artifact per platform. The summary keeps each platform's lifecycle\nstage table, adds a lifecycle total duration, carries the top slow spans, and\naggregates warning/error counts plus the slowest platforms. Per-platform rows\nalso include compact runner facts, checked tool versions/missing tools, package\nmanager/cache directory details, compiler cache availability, and a compact\nprocess sampler view with requested parallelism, observed max active processes,\nthe ratio between them, sample count, process categories, and top sampled\ncommand basenames. That gives release reviewers a small cross-platform timing,\nrunner, tool, cache, and concurrency view without downloading full build outputs\nor process sidecars.\n\nThe reusable build workflow writes that rollup as `diagnostics-summary.json` and\nuploads it in a separate aggregate diagnostics summary artifact. Consumers can\nread the `build-diagnostics-summary-artifact` output when they need only timing,\nwarning/error, runner, cache, and process-sampler context instead of the build\nsummary or binary artifacts. Per-platform rows keep the diagnostics `links`\nobject, including the binary artifact name, manifest artifact name, diagnostics\nartifact name, platform id, diagnostics sidecar manifest path, manifest path,\nsummary path, and process sidecar paths when present. They also keep compact\nrunner/tool/cache summaries so reviewers can tell whether a slow row ran on the\nexpected runner, missed an expected tool, or lacked useful compiler-cache stats.\nWhen the downloaded platform diagnostics include a sibling\n`diagnostics-manifest.json`, `summarizeDiagnosticsArtifacts()` carries a compact\n`diagnosticsManifest` section for that platform and verifies the manifest's\n`diagnostics.json` byte count and sha256. Missing or mismatched sidecar manifests\nincrement `diagnosticsManifestWarningCount`, so reviewers can distinguish\ndiagnostics sidecar drift from lifecycle warnings or build failures.\nThe same summary carries `diagnosticsContract` per platform and aggregates\n`diagnosticsContractWarningCount` when a downloaded `diagnostics.json` does not\nmatch `BUILDCHAIN_DIAGNOSTICS_CONTRACT`.\n\nThe diagnostics SDK also exports stable contract constants such as\n`BUILDCHAIN_DIAGNOSTICS_SUMMARY_CONTRACT`,\n`BUILDCHAIN_DIAGNOSTICS_MANIFEST_CONTRACT`,\n`BUILDCHAIN_PROCESS_SAMPLE_REPORT_CONTRACT`,\n`BUILDCHAIN_PROCESS_SAMPLE_SUMMARY_CONTRACT`, and\n`BUILDCHAIN_ANCHORED_PACKAGE_RELEASE_VALIDATION_CONTRACT` from\n`@kungfu-tech/buildchain/diagnostics`; consumers should compare against those\nconstants instead of hardcoding contract strings.\n\nThe CLI exposes the same aggregation for shell and workflow steps:\n\n```bash\nbuildchain diagnostics summary \\\n .buildchain/artifacts/linux-x64/diagnostics.json \\\n .buildchain/artifacts/macos-arm64/diagnostics.json \\\n --output .buildchain/artifacts/diagnostics-summary.json \\\n --json\n```\n\nOmit `--json` when the workflow log should show a compact platform table with\nlifecycle stages, artifact scan/upload time, total time, requested jobs,\nobserved active processes, warnings, and errors.\n\nFor long native build commands, the CLI can also sample the child process tree\nwhile preserving the wrapped command's exit code:\n\n```bash\nbuildchain sample process-tree \\\n --label native-build \\\n --interval-ms 15000 \\\n --output .buildchain/diagnostics/process-samples.jsonl \\\n --summary-output .buildchain/diagnostics/process-summary.json \\\n -- \\\n make -j20\n```\n\nThe JSONL sample file stores timestamped process-tree snapshots with full\nredacted command lines, command basenames, CPU percentages when available,\nelapsed time, and requested parallelism context. Unix and macOS snapshots read\nthe full `ps args` field instead of truncated `comm` names. Windows snapshots\nread `Win32_Process` command lines through PowerShell so the sampler no longer\nreturns an empty process set on Windows runners. The summary file records\nobserved concurrency, total sampled CPU, command categories, and top command\nbasenames. This is intended for diagnosing low-utilization tails such as\narchive/link phases without logging full environment dumps.\n\nThe lifecycle observability summary is stage-wide, not just final-step timing:\nwhen install and build write to the same Buildchain log, the final platform\nmanifest can show both stages and the slowest spans.\n\n## CLI Logging\n\n```bash\nbuildchain mark \\\n --event native.configure \\\n --phase configure \\\n --component cmake \\\n --attribute preset=release\n\nbuildchain span \\\n --event native.build \\\n --phase build \\\n --component cmake \\\n -- cmake --build build --config Release\n\nbuildchain log summary --json\nbuildchain verify observability-log .buildchain/logs/events.jsonl --min-events 4\n```\n\nEvery event records a timestamp. `span` records duration and preserves the\nwrapped command's exit code.\n\n## Release Gate\n\nBuildchain's own binary distribution lane verifies required log events before\nuploading release assets. Consumers can apply the same pattern:\n\n```bash\nbuildchain verify observability-log .buildchain/logs/events.jsonl \\\n --require-phase build \\\n --require-phase package \\\n --require-component workflow \\\n --require-event native.build.start \\\n --require-event native.build.end\n```\n\nThis makes missing instrumentation a release failure instead of a dashboard\nafterthought."
|
|
1972
1972
|
},
|
|
1973
1973
|
{
|
|
1974
1974
|
"id": "manual:versioning",
|
|
@@ -3748,7 +3748,7 @@
|
|
|
3748
3748
|
"pageRegistryPath": "dist/site/page-registry.json",
|
|
3749
3749
|
"cliRegistryDigest": "cc76cd0f061103d8ab78ee97dd0b7f0d28d64489f79b7ed06c94d8fef28bc200",
|
|
3750
3750
|
"workflowRegistryDigest": "a954f67672292e16be1f4f663a98d0c69285f7359a43d5d6333b3ffcf6463c35",
|
|
3751
|
-
"pageRegistryDigest": "
|
|
3751
|
+
"pageRegistryDigest": "14f3c6c7762af10ea2e186759f00291e29b4b719484c9732fd85493f67421cea"
|
|
3752
3752
|
},
|
|
3753
3753
|
"comparison": {
|
|
3754
3754
|
"missingCliRegistry": [],
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-publication-release-registry",
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"publishedAt": "2026-07-
|
|
4
|
+
"generatedAt": "2026-07-23T02:11:02.318Z",
|
|
5
|
+
"publishedAt": "2026-07-23T02:11:02.318Z",
|
|
6
6
|
"reproducible": true,
|
|
7
7
|
"timestampPolicy": "ci-injected",
|
|
8
8
|
"deterministicInputs": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"declared Buildchain surface manifest contract"
|
|
20
20
|
],
|
|
21
21
|
"sourceDateEpoch": "0",
|
|
22
|
-
"sourceRevision": "
|
|
22
|
+
"sourceRevision": "d8c1eb88881153d74398da51d70f9fd7101c50a0",
|
|
23
23
|
"timestampPolicyDetails": {
|
|
24
24
|
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
25
|
"timestampFields": [
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"package": {
|
|
34
34
|
"name": "@kungfu-tech/buildchain",
|
|
35
|
-
"version": "2.14.17-alpha.
|
|
35
|
+
"version": "2.14.17-alpha.3",
|
|
36
36
|
"versionSource": "package.json#version"
|
|
37
37
|
},
|
|
38
38
|
"sourceKind": "package-site-bundle",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-site-manifest",
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"publishedAt": "2026-07-
|
|
4
|
+
"generatedAt": "2026-07-23T02:11:02.318Z",
|
|
5
|
+
"publishedAt": "2026-07-23T02:11:02.318Z",
|
|
6
6
|
"reproducible": true,
|
|
7
7
|
"timestampPolicy": "ci-injected",
|
|
8
8
|
"deterministicInputs": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"declared Buildchain surface manifest contract"
|
|
20
20
|
],
|
|
21
21
|
"sourceDateEpoch": "0",
|
|
22
|
-
"sourceRevision": "
|
|
22
|
+
"sourceRevision": "d8c1eb88881153d74398da51d70f9fd7101c50a0",
|
|
23
23
|
"timestampPolicyDetails": {
|
|
24
24
|
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
25
|
"timestampFields": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"package": {
|
|
39
39
|
"name": "@kungfu-tech/buildchain",
|
|
40
|
-
"version": "2.14.17-alpha.
|
|
40
|
+
"version": "2.14.17-alpha.3",
|
|
41
41
|
"versionSource": "package.json#version"
|
|
42
42
|
},
|
|
43
43
|
"entrypoint": "buildchain-site.json",
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
"path": "docs/toolkit-observability.md",
|
|
150
150
|
"plane": "use",
|
|
151
151
|
"exists": true,
|
|
152
|
-
"digest": "sha256:
|
|
152
|
+
"digest": "sha256:b2ffc3b2520ca7879309b7607b2eaa1aceb647503e893887159a6916de14e2d7"
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
"id": "site-bundle-contract",
|
|
@@ -139,6 +139,15 @@ does not combine a failed or dequeued attempt with a later retry. Missing
|
|
|
139
139
|
required measurements make that attempt `incomplete` instead of producing fake
|
|
140
140
|
precision.
|
|
141
141
|
|
|
142
|
+
Each attempt also reports measured execution lanes, lane skew, cache outcome
|
|
143
|
+
counts, the ten longest actionable spans, and one falsifiable next optimization
|
|
144
|
+
target. Queue residence, whole-workflow envelopes, and job parents are excluded
|
|
145
|
+
from the actionable ranking so they cannot hide the build or qualification
|
|
146
|
+
stage that can actually be changed. The target is an observation, not a causal
|
|
147
|
+
claim: repeat the same source-bound cohort and disprove it by reducing that span
|
|
148
|
+
below the next measured span without increasing attempt elapsed time or
|
|
149
|
+
failures.
|
|
150
|
+
|
|
142
151
|
Shell and workflow consumers can generate the same machine artifact plus a
|
|
143
152
|
compact report:
|
|
144
153
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "2.14.17-alpha.
|
|
3
|
+
"version": "2.14.17-alpha.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
|
|
6
6
|
"repository": "https://github.com/kungfu-systems/buildchain",
|
|
@@ -184,6 +184,118 @@ function groupIntervalSummary(events, field) {
|
|
|
184
184
|
);
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
function laneId(event) {
|
|
188
|
+
if (event.attributes?.laneId) return String(event.attributes.laneId);
|
|
189
|
+
const platform = event.gate?.platform;
|
|
190
|
+
const partition = event.gate?.partition;
|
|
191
|
+
if (partition !== undefined) {
|
|
192
|
+
return `${platform || "unknown-platform"}/partition-${partition}`;
|
|
193
|
+
}
|
|
194
|
+
if (platform && event.gate?.id) return `${platform}/${event.gate.id}`;
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function laneSummary(events) {
|
|
199
|
+
const lanes = new Map();
|
|
200
|
+
for (const event of events) {
|
|
201
|
+
const id = laneId(event);
|
|
202
|
+
if (!id) continue;
|
|
203
|
+
if (!lanes.has(id)) lanes.set(id, []);
|
|
204
|
+
lanes.get(id).push(event);
|
|
205
|
+
}
|
|
206
|
+
return [...lanes.entries()]
|
|
207
|
+
.map(([id, selected]) => {
|
|
208
|
+
const measured = selected.filter(
|
|
209
|
+
(event) => event.timing.measured && event.criticalPathEligible,
|
|
210
|
+
);
|
|
211
|
+
const intervals = measured.map(interval);
|
|
212
|
+
const starts = intervals.map(([start]) => start);
|
|
213
|
+
const ends = intervals.map(([, end]) => end);
|
|
214
|
+
const startedAtMs = starts.length ? Math.min(...starts) : null;
|
|
215
|
+
const completedAtMs = ends.length ? Math.max(...ends) : null;
|
|
216
|
+
return {
|
|
217
|
+
id,
|
|
218
|
+
eventCount: selected.length,
|
|
219
|
+
measuredEventCount: measured.length,
|
|
220
|
+
startedAt:
|
|
221
|
+
startedAtMs === null ? null : new Date(startedAtMs).toISOString(),
|
|
222
|
+
completedAt:
|
|
223
|
+
completedAtMs === null ? null : new Date(completedAtMs).toISOString(),
|
|
224
|
+
durationMs:
|
|
225
|
+
startedAtMs === null || completedAtMs === null
|
|
226
|
+
? null
|
|
227
|
+
: completedAtMs - startedAtMs,
|
|
228
|
+
activeIntervalUnionMs: intervalUnionMs(intervals),
|
|
229
|
+
};
|
|
230
|
+
})
|
|
231
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function optimizationSummary(events) {
|
|
235
|
+
const lanes = laneSummary(events);
|
|
236
|
+
const measuredLanes = lanes.filter((lane) => lane.durationMs !== null);
|
|
237
|
+
const orderedLanes = [...measuredLanes].sort(
|
|
238
|
+
(left, right) =>
|
|
239
|
+
right.durationMs - left.durationMs || left.id.localeCompare(right.id),
|
|
240
|
+
);
|
|
241
|
+
const actionable = events
|
|
242
|
+
.filter(
|
|
243
|
+
(event) =>
|
|
244
|
+
event.timing.measured &&
|
|
245
|
+
event.criticalPathEligible &&
|
|
246
|
+
!["queue", "workflow", "job", "runner-wait"].includes(event.category) &&
|
|
247
|
+
![
|
|
248
|
+
"pr-admission",
|
|
249
|
+
"queue-residence",
|
|
250
|
+
"authoritative-build",
|
|
251
|
+
"gate-fanout",
|
|
252
|
+
].includes(event.phase),
|
|
253
|
+
)
|
|
254
|
+
.sort(
|
|
255
|
+
(left, right) =>
|
|
256
|
+
right.timing.durationMs - left.timing.durationMs ||
|
|
257
|
+
left.id.localeCompare(right.id),
|
|
258
|
+
);
|
|
259
|
+
const cache = {};
|
|
260
|
+
for (const event of events) {
|
|
261
|
+
if (!event.cache?.layer || !event.cache?.outcome) continue;
|
|
262
|
+
const key = `${event.cache.layer}:${event.cache.outcome}`;
|
|
263
|
+
cache[key] = (cache[key] || 0) + 1;
|
|
264
|
+
}
|
|
265
|
+
const dominantSpans = actionable.slice(0, 10).map((event) =>
|
|
266
|
+
compactObject({
|
|
267
|
+
eventId: event.id,
|
|
268
|
+
phase: event.phase,
|
|
269
|
+
category: event.category,
|
|
270
|
+
durationMs: event.timing.durationMs,
|
|
271
|
+
lane: laneId(event),
|
|
272
|
+
gateId: event.gate?.id,
|
|
273
|
+
}),
|
|
274
|
+
);
|
|
275
|
+
const target = dominantSpans[0];
|
|
276
|
+
return {
|
|
277
|
+
lanes,
|
|
278
|
+
laneSkew: {
|
|
279
|
+
measuredLaneCount: measuredLanes.length,
|
|
280
|
+
slowestLane: orderedLanes[0]?.id || null,
|
|
281
|
+
fastestLane: orderedLanes.at(-1)?.id || null,
|
|
282
|
+
skewMs:
|
|
283
|
+
orderedLanes.length > 1
|
|
284
|
+
? orderedLanes[0].durationMs - orderedLanes.at(-1).durationMs
|
|
285
|
+
: null,
|
|
286
|
+
},
|
|
287
|
+
cacheOutcomes: cache,
|
|
288
|
+
dominantSpans,
|
|
289
|
+
nextOptimizationTarget: target
|
|
290
|
+
? {
|
|
291
|
+
...target,
|
|
292
|
+
falsifier:
|
|
293
|
+
"Repeat the same source-bound cohort and reduce this span below the next measured span without increasing attempt elapsed time or failures.",
|
|
294
|
+
}
|
|
295
|
+
: null,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
187
299
|
function attemptOrder(left, right) {
|
|
188
300
|
const leftIndex = left.attempt.index;
|
|
189
301
|
const rightIndex = right.attempt.index;
|
|
@@ -227,6 +339,7 @@ function summarizeAttempt(events) {
|
|
|
227
339
|
measuredEventCount: ordered.filter((event) => event.timing.measured).length,
|
|
228
340
|
phases: groupIntervalSummary(ordered, "phase"),
|
|
229
341
|
categories: groupIntervalSummary(ordered, "category"),
|
|
342
|
+
optimization: optimizationSummary(ordered),
|
|
230
343
|
criticalPath: {
|
|
231
344
|
method: "attempt-wall-clock-envelope",
|
|
232
345
|
status: unmeasured.length
|
|
@@ -344,6 +457,19 @@ export function formatCandidateTimelineReport(timeline) {
|
|
|
344
457
|
` phase=${phase} union=${summary.intervalUnionMs}ms measured=${summary.measuredEventCount}/${summary.eventCount}`,
|
|
345
458
|
);
|
|
346
459
|
}
|
|
460
|
+
const optimization = attempt.optimization;
|
|
461
|
+
lines.push(
|
|
462
|
+
` lane-skew=${optimization.laneSkew.skewMs === null ? "unknown" : `${optimization.laneSkew.skewMs}ms`} slowest=${optimization.laneSkew.slowestLane || "unknown"}`,
|
|
463
|
+
);
|
|
464
|
+
const cache = Object.entries(optimization.cacheOutcomes)
|
|
465
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
466
|
+
.map(([key, count]) => `${key}=${count}`)
|
|
467
|
+
.join(",");
|
|
468
|
+
lines.push(` cache=${cache || "unobserved"}`);
|
|
469
|
+
const target = optimization.nextOptimizationTarget;
|
|
470
|
+
lines.push(
|
|
471
|
+
` next-target=${target ? `${target.eventId} ${target.durationMs}ms` : "unobserved"}`,
|
|
472
|
+
);
|
|
347
473
|
}
|
|
348
474
|
return lines.join("\n");
|
|
349
475
|
}
|