@spunto/build 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spunto/build",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Spunto's shared Build engine — the devcontainer image protocol and VS Code extension registry clients, with no database, no HTTP framework and no UI.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -103,16 +103,6 @@ type CloneRepo = {
103
103
  branch?: string
104
104
  }
105
105
 
106
- /**
107
- * How a feature's entrypoint is started: without the OpenTelemetry variables the platform gives the
108
- * workload. An entrypoint is infrastructure — dockerd, containerd — and Docker instruments itself as
109
- * soon as `OTEL_EXPORTER_OTLP_ENDPOINT` exists: every internal gRPC call of a docker-in-docker
110
- * daemon became a span (~60 000 per quarter hour for three sandboxes, 2026-10-18), drowning the
111
- * project's own services in its organization's telemetry. The project's processes keep them.
112
- */
113
- export const ENTRYPOINT_WITHOUT_OTEL =
114
- "env -u OTEL_EXPORTER_OTLP_ENDPOINT -u OTEL_EXPORTER_OTLP_TRACES_ENDPOINT -u OTEL_EXPORTER_OTLP_METRICS_ENDPOINT -u OTEL_EXPORTER_OTLP_LOGS_ENDPOINT -u OTEL_EXPORTER_OTLP_PROTOCOL -u OTEL_EXPORTER_OTLP_HEADERS -u OTEL_RESOURCE_ATTRIBUTES -u OTEL_SERVICE_NAME"
115
-
116
106
  /**
117
107
  * Branch actually checked out for a repo: the worker-level choice (made at creation, applies to
118
108
  * every repo of the project) wins over the project-level default carried by the repository itself.
@@ -1150,7 +1140,7 @@ export function buildSetupScript(params: SetupScriptParams): { script: string }
1150
1140
  " while IFS= read -r _ep; do",
1151
1141
  ' if [ -f "$_ep" ]; then',
1152
1142
  ' echo "Starting entrypoint: $_ep"',
1153
- ' ' + ENTRYPOINT_WITHOUT_OTEL + ' "$_ep" &',
1143
+ ' "$_ep" &',
1154
1144
  " fi",
1155
1145
  " done < " + FEATURE_ENTRYPOINTS_FILE,
1156
1146
  "fi",
@@ -1316,7 +1306,7 @@ export function buildStartScript(params: StartScriptParams): { script: string; h
1316
1306
  " while IFS= read -r _ep; do",
1317
1307
  ' if [ -f "$_ep" ]; then',
1318
1308
  ' echo "Starting entrypoint: $_ep"',
1319
- ' ' + ENTRYPOINT_WITHOUT_OTEL + ' "$_ep" &',
1309
+ ' "$_ep" &',
1320
1310
  " fi",
1321
1311
  " done < " + FEATURE_ENTRYPOINTS_FILE,
1322
1312
  "fi",
@@ -1490,12 +1480,12 @@ export function buildStartScript(params: StartScriptParams): { script: string; h
1490
1480
  // The functions below power the *new* model where the agent (not the container) drives setup:
1491
1481
  // `buildContainerScript` is the passive CMD (serve code-server + stay alive, no setup, no status
1492
1482
  // machinery), and `buildSetupPlan` returns the ordered phases the agent execs one by one, each
1493
- // wrapped in a native OTel span. The legacy `buildWorkerScript`/`buildSetupScript`/`buildStartScript`
1483
+ // wrapped in its own try/catch. The legacy `buildWorkerScript`/`buildSetupScript`/`buildStartScript`
1494
1484
  // above are left untouched — the runner picks the path by flag, defaulting to legacy. See RFC 0017.
1495
1485
 
1496
- /** One setup phase the agent execs via `docker exec`, wrapped in a native OTel span. */
1486
+ /** One setup phase the agent execs via `docker exec`. */
1497
1487
  export type SetupPhase = {
1498
- /** Timing-key / span-suffix (`setup.<id>`) and the phase's short identity. */
1488
+ /** Timing key and the phase's short identity. */
1499
1489
  id: "credentials" | "shell" | "dotfiles" | "skills" | "clone" | "postCreate" | "postStart"
1500
1490
  /** Human label for logs. */
1501
1491
  label: string
@@ -1510,7 +1500,7 @@ export type SetupPhase = {
1510
1500
 
1511
1501
  // The passive container CMD. Same "serve + stay alive" blocks as buildStartScript (SSH, feature
1512
1502
  // entrypoints, env persistence, extensions, code-server settings + restart loop) — MINUS the setup
1513
- // status machinery, the lifecycle commands and any OTLP/timing emission (the agent owns all of that
1503
+ // status machinery, the lifecycle commands and any timing emission (the agent owns all of that
1514
1504
  // now). Ends by signalling readiness (`.mp-container-up`) then `wait`. Blocks are duplicated from
1515
1505
  // buildStartScript on purpose: the legacy path stays byte-identical (prod default) until it's
1516
1506
  // deleted once WORKER_SETUP_ORCHESTRATOR=agent becomes the default. See RFC 0017 §2.1.
@@ -1569,7 +1559,7 @@ export function buildContainerScript(params: StartScriptParams): { script: strin
1569
1559
  " while IFS= read -r _ep; do",
1570
1560
  ' if [ -f "$_ep" ]; then',
1571
1561
  ' echo "Starting entrypoint: $_ep"',
1572
- ' ' + ENTRYPOINT_WITHOUT_OTEL + ' "$_ep" &',
1562
+ ' "$_ep" &',
1573
1563
  " fi",
1574
1564
  " done < " + FEATURE_ENTRYPOINTS_FILE,
1575
1565
  "fi",
@@ -1724,9 +1714,6 @@ export function buildWorkerScript(params: {
1724
1714
  " local now; now=\"$(_mp_now)\"",
1725
1715
  " if [ -s \"$_MP_TFILE\" ]; then printf ',' >> \"$_MP_TFILE\"; fi",
1726
1716
  " printf '\"%s\":%s' \"$1\" \"$now\" >> \"$_MP_TFILE\" || true",
1727
- // Native OTel: emit this phase transition as a real OTLP span through the node
1728
- // collector (|| true + suspends set -e inside the fn — telemetry never breaks setup).
1729
- " _mp_span_on_stamp \"$1\" \"$now\" || true",
1730
1717
  "}",
1731
1718
  // mp-status.json lives in the home dir (persistent volume), not /tmp — when a
1732
1719
  // DinD feature starts dockerd in the background, /tmp's mount visibility can
@@ -1743,70 +1730,14 @@ export function buildWorkerScript(params: {
1743
1730
  " printf '%s' \"$j\" > " + workerStatusPath("/home/vscode") + " || true",
1744
1731
  "}",
1745
1732
  "",
1746
- // ── Native OTel setup spans (emitted by this script, not backdated in the API) ──
1747
- // Each phase is emitted as a real OTLP span to the node agent's collector (the
1748
- // auto-injected OTEL_EXPORTER_OTLP_ENDPOINT); the agent forwards to /otlp where org.id
1749
- // /node.id are stamped server-side. MP_SETUP_TRACEPARENT (the job.worker.spawn span,
1750
- // injected by the runner) gives the traceId + parent span, so the whole `worker.setup`
1751
- // subtree nests under the spawn trace. No trace context (or no curl) → silently skipped.
1752
- "MP_TRACE_ID=''; MP_JOB_SPAN=''",
1753
- "if [ -n \"${MP_SETUP_TRACEPARENT:-}\" ]; then",
1754
- " MP_TRACE_ID=$(printf '%s' \"$MP_SETUP_TRACEPARENT\" | cut -d- -f2)",
1755
- " MP_JOB_SPAN=$(printf '%s' \"$MP_SETUP_TRACEPARENT\" | cut -d- -f3)",
1756
- "fi",
1757
- "MP_SETUP_SPAN=''; MP_SETUP_START=''; MP_PREV_PHASE=''; MP_PREV_TS=''",
1758
1733
  // Failure-trap state. _MP_DONE flips to 1 once setup+start reach `ready` (see buildStartScript),
1759
1734
  // disarming _mp_fail so a normal container stop / code-server hiccup after ready never looks like
1760
1735
  // a setup failure. _MP_LAST holds the most recent status JSON (set by _mp) for the error re-emit.
1761
1736
  "_MP_DONE=0; _MP_LAST=''",
1762
- "_mp_rand_id() { od -An -tx1 -N8 /dev/urandom 2>/dev/null | tr -d ' \\n'; }",
1763
- "_mp_phase_span_name() {",
1764
- " case \"$1\" in",
1765
- " initializing) echo 'setup.initialize' ;;",
1766
- " credentials) echo 'setup.credentials' ;;",
1767
- " shell) echo 'setup.shell' ;;",
1768
- " cloning) echo 'setup.clone' ;;",
1769
- " postCreate) echo 'setup.postCreate' ;;",
1770
- " postStart) echo 'setup.postStart' ;;",
1771
- " *) echo \"setup.$1\" ;;",
1772
- " esac",
1773
- "}",
1774
- // Emit one OTLP/HTTP JSON span, backgrounded + short timeout so it never adds latency
1775
- // to (or blocks) the setup path. Args: name start_ms end_ms span_id parent_span_id phase.
1776
- "_mp_span_emit() {",
1777
- " if [ -z \"$MP_TRACE_ID\" ] || [ -z \"${OTEL_EXPORTER_OTLP_ENDPOINT:-}\" ]; then return 0; fi",
1778
- " if ! command -v curl >/dev/null 2>&1; then return 0; fi",
1779
- // Optional $7 = error message and $8 = exit code turn this into a native ERROR span
1780
- // (OTel status code 2 + a setup.exit_code attribute) — that's what makes a failed
1781
- // clone/postCreate/postStart show up red in the waterfall instead of as a hole.
1782
- " local name=\"$1\" s_ns=$(( $2 * 1000000 )) e_ns=$(( $3 * 1000000 )) sid=\"$4\" pid=\"$5\" phase=\"$6\" errmsg=\"${7:-}\" ecode=\"${8:-}\" dur=$(( $3 - $2 )) attrs='' status=''",
1783
- " if [ -n \"$phase\" ]; then attrs=\"{\\\"key\\\":\\\"setup.phase\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"$phase\\\"}},{\\\"key\\\":\\\"setup.duration_ms\\\",\\\"value\\\":{\\\"intValue\\\":\\\"$dur\\\"}}\"; fi",
1784
- " if [ -n \"$ecode\" ]; then [ -n \"$attrs\" ] && attrs=\"$attrs,\"; attrs=\"${attrs}{\\\"key\\\":\\\"setup.exit_code\\\",\\\"value\\\":{\\\"intValue\\\":\\\"$ecode\\\"}}\"; fi",
1785
- " if [ -n \"$errmsg\" ]; then status=\",\\\"status\\\":{\\\"code\\\":2,\\\"message\\\":\\\"$errmsg\\\"}\"; fi",
1786
- " local body=\"{\\\"resourceSpans\\\":[{\\\"resource\\\":{\\\"attributes\\\":[{\\\"key\\\":\\\"service.name\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"spunto-worker\\\"}},{\\\"key\\\":\\\"spunto.worker.slug\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"${WORKER_SLUG:-}\\\"}}]},\\\"scopeSpans\\\":[{\\\"scope\\\":{\\\"name\\\":\\\"spunto.setup\\\"},\\\"spans\\\":[{\\\"traceId\\\":\\\"$MP_TRACE_ID\\\",\\\"spanId\\\":\\\"$sid\\\",\\\"parentSpanId\\\":\\\"$pid\\\",\\\"name\\\":\\\"$name\\\",\\\"kind\\\":1,\\\"startTimeUnixNano\\\":\\\"$s_ns\\\",\\\"endTimeUnixNano\\\":\\\"$e_ns\\\",\\\"attributes\\\":[$attrs]$status}]}]}]}\"",
1787
- " ( curl -s -m 3 -o /dev/null -X POST \"$OTEL_EXPORTER_OTLP_ENDPOINT/v1/traces\" -H 'Content-Type: application/json' -d \"$body\" || true ) &",
1788
- "}",
1789
- // Called on each new phase stamp. The first stamp opens the `worker.setup` window; every
1790
- // later stamp closes the previous phase as a child span; `ready` also emits the parent.
1791
- "_mp_span_on_stamp() {",
1792
- " if [ -z \"$MP_TRACE_ID\" ]; then return 0; fi",
1793
- " local key=\"$1\" now=\"$2\"",
1794
- " if [ -z \"$MP_SETUP_SPAN\" ]; then",
1795
- " MP_SETUP_SPAN=$(_mp_rand_id); MP_SETUP_START=\"$now\"; MP_PREV_PHASE=\"$key\"; MP_PREV_TS=\"$now\"; return 0",
1796
- " fi",
1797
- " _mp_span_emit \"$(_mp_phase_span_name \"$MP_PREV_PHASE\")\" \"$MP_PREV_TS\" \"$now\" \"$(_mp_rand_id)\" \"$MP_SETUP_SPAN\" \"$MP_PREV_PHASE\"",
1798
- " if [ \"$key\" = ready ]; then",
1799
- " _mp_span_emit 'worker.setup' \"$MP_SETUP_START\" \"$now\" \"$MP_SETUP_SPAN\" \"$MP_JOB_SPAN\" ''",
1800
- " else",
1801
- " MP_PREV_PHASE=\"$key\"; MP_PREV_TS=\"$now\"",
1802
- " fi",
1803
- "}",
1804
- // ── Native error spans on setup failure (the direct goal of this refactor) ──
1737
+ // ── Setup failure trap ──
1805
1738
  // set -e makes any uncaught failure abort the CMD; before PR #115 that just left the worker
1806
- // stuck at its last status with a hole in the trace. This EXIT trap turns that same failure
1807
- // into: (1) a real OTLP span in ERROR (status code 2 + exit code + the in-flight phase), so the
1808
- // failing clone/postCreate/postStart shows up red under worker.setup, and (2) a phase=error
1809
- // status write so the API poller flips the worker to `error` with a message. It fires only on a
1739
+ // stuck at its last status. This EXIT trap turns that same failure into a phase=error status
1740
+ // write so the API poller flips the worker to `error` with a message. It fires only on a
1810
1741
  // non-zero exit and only before `ready` (_MP_DONE guards the normal path); the short sleep lets
1811
1742
  // the 1.5s poll read the error status before the container exits.
1812
1743
  "_mp_fail() {",
@@ -1816,26 +1747,13 @@ export function buildWorkerScript(params: {
1816
1747
  " [ \"$_MP_DONE\" = 1 ] && return 0",
1817
1748
  " [ \"$code\" = 0 ] && return 0",
1818
1749
  " _MP_DONE=1",
1819
- " local phase now msg j",
1820
- " phase=\"$MP_PREV_PHASE\"",
1821
- " [ -z \"$phase\" ] && phase=$(printf '%s' \"$_MP_LAST\" | sed -n 's/.*\"phase\":\"\\([^\"]*\\)\".*/\\1/p')",
1750
+ " local phase msg j",
1751
+ " phase=$(printf '%s' \"$_MP_LAST\" | sed -n 's/.*\"phase\":\"\\([^\"]*\\)\".*/\\1/p')",
1822
1752
  " [ -z \"$phase\" ] && phase=initializing",
1823
- " now=\"$(_mp_now)\"",
1824
1753
  // A phase that knows *why* it failed (e.g. a clone on a branch that doesn't exist) sets
1825
1754
  // MP_FAIL_MSG before exiting, so the UI shows that instead of the generic phase message.
1826
1755
  " msg=\"${MP_FAIL_MSG:-setup phase $phase failed (exit $code)}\"",
1827
1756
  " echo \"[setup] ✗ $msg\" >&2",
1828
- // Best-effort error spans: close the failing phase (child of worker.setup) then worker.setup
1829
- // itself in ERROR. On a restart (postStart failure) the setup window was never opened, so emit
1830
- // a single worker.setup error span under the job span instead.
1831
- " if [ -n \"$MP_TRACE_ID\" ]; then",
1832
- " if [ -n \"$MP_SETUP_SPAN\" ]; then",
1833
- " _mp_span_emit \"$(_mp_phase_span_name \"$phase\")\" \"${MP_PREV_TS:-$now}\" \"$now\" \"$(_mp_rand_id)\" \"$MP_SETUP_SPAN\" \"$phase\" \"$msg\" \"$code\"",
1834
- " _mp_span_emit 'worker.setup' \"${MP_SETUP_START:-$now}\" \"$now\" \"$MP_SETUP_SPAN\" \"$MP_JOB_SPAN\" '' \"$msg\" \"$code\"",
1835
- " else",
1836
- " _mp_span_emit 'worker.setup' \"${MP_PREV_TS:-$now}\" \"$now\" \"$(_mp_rand_id)\" \"$MP_JOB_SPAN\" \"$phase\" \"$msg\" \"$code\"",
1837
- " fi",
1838
- " fi",
1839
1757
  // Flip .mp-status.json to phase=error (preserving the rest) so monitorSetup surfaces it.
1840
1758
  " j=\"$_MP_LAST\"",
1841
1759
  " if [ -n \"$j\" ]; then",
@@ -1865,10 +1783,10 @@ export function buildWorkerScript(params: {
1865
1783
 
1866
1784
  // ─── buildSetupPlan (RFC 0017) ─────────────────────────────────────────────────
1867
1785
  //
1868
- // The ordered phases the agent execs one by one (each in its own `docker exec` + OTel span) when
1786
+ // The ordered phases the agent execs one by one (each in its own `docker exec`) when
1869
1787
  // WORKER_SETUP_ORCHESTRATOR=agent. Reuses the exact bash blocks from buildSetupScript/buildStartScript
1870
- // (clone strategies, oh-my-zsh, dotfiles, lifecycle) MINUS the `_mp`/`_mp_stamp`/OTLP machinery — the
1871
- // agent owns status + spans now. Each phase carries the SetupStatus to persist at its start/end
1788
+ // (clone strategies, oh-my-zsh, dotfiles, lifecycle) MINUS the `_mp`/`_mp_stamp` machinery — the
1789
+ // agent owns status now. Each phase carries the SetupStatus to persist at its start/end
1872
1790
  // (mirrors the legacy `mkStatus(...)` writes) so the DB/UI progression is identical. See RFC 0017 §2.2.
1873
1791
 
1874
1792
  export function buildSetupPlan(params: SetupScriptParams): {