@spunto/build 0.14.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.14.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",
@@ -1480,12 +1480,12 @@ export function buildStartScript(params: StartScriptParams): { script: string; h
1480
1480
  // The functions below power the *new* model where the agent (not the container) drives setup:
1481
1481
  // `buildContainerScript` is the passive CMD (serve code-server + stay alive, no setup, no status
1482
1482
  // machinery), and `buildSetupPlan` returns the ordered phases the agent execs one by one, each
1483
- // wrapped in a native OTel span. The legacy `buildWorkerScript`/`buildSetupScript`/`buildStartScript`
1483
+ // wrapped in its own try/catch. The legacy `buildWorkerScript`/`buildSetupScript`/`buildStartScript`
1484
1484
  // above are left untouched — the runner picks the path by flag, defaulting to legacy. See RFC 0017.
1485
1485
 
1486
- /** 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`. */
1487
1487
  export type SetupPhase = {
1488
- /** Timing-key / span-suffix (`setup.<id>`) and the phase's short identity. */
1488
+ /** Timing key and the phase's short identity. */
1489
1489
  id: "credentials" | "shell" | "dotfiles" | "skills" | "clone" | "postCreate" | "postStart"
1490
1490
  /** Human label for logs. */
1491
1491
  label: string
@@ -1500,7 +1500,7 @@ export type SetupPhase = {
1500
1500
 
1501
1501
  // The passive container CMD. Same "serve + stay alive" blocks as buildStartScript (SSH, feature
1502
1502
  // entrypoints, env persistence, extensions, code-server settings + restart loop) — MINUS the setup
1503
- // 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
1504
1504
  // now). Ends by signalling readiness (`.mp-container-up`) then `wait`. Blocks are duplicated from
1505
1505
  // buildStartScript on purpose: the legacy path stays byte-identical (prod default) until it's
1506
1506
  // deleted once WORKER_SETUP_ORCHESTRATOR=agent becomes the default. See RFC 0017 §2.1.
@@ -1714,9 +1714,6 @@ export function buildWorkerScript(params: {
1714
1714
  " local now; now=\"$(_mp_now)\"",
1715
1715
  " if [ -s \"$_MP_TFILE\" ]; then printf ',' >> \"$_MP_TFILE\"; fi",
1716
1716
  " printf '\"%s\":%s' \"$1\" \"$now\" >> \"$_MP_TFILE\" || true",
1717
- // Native OTel: emit this phase transition as a real OTLP span through the node
1718
- // collector (|| true + suspends set -e inside the fn — telemetry never breaks setup).
1719
- " _mp_span_on_stamp \"$1\" \"$now\" || true",
1720
1717
  "}",
1721
1718
  // mp-status.json lives in the home dir (persistent volume), not /tmp — when a
1722
1719
  // DinD feature starts dockerd in the background, /tmp's mount visibility can
@@ -1733,70 +1730,14 @@ export function buildWorkerScript(params: {
1733
1730
  " printf '%s' \"$j\" > " + workerStatusPath("/home/vscode") + " || true",
1734
1731
  "}",
1735
1732
  "",
1736
- // ── Native OTel setup spans (emitted by this script, not backdated in the API) ──
1737
- // Each phase is emitted as a real OTLP span to the node agent's collector (the
1738
- // auto-injected OTEL_EXPORTER_OTLP_ENDPOINT); the agent forwards to /otlp where org.id
1739
- // /node.id are stamped server-side. MP_SETUP_TRACEPARENT (the job.worker.spawn span,
1740
- // injected by the runner) gives the traceId + parent span, so the whole `worker.setup`
1741
- // subtree nests under the spawn trace. No trace context (or no curl) → silently skipped.
1742
- "MP_TRACE_ID=''; MP_JOB_SPAN=''",
1743
- "if [ -n \"${MP_SETUP_TRACEPARENT:-}\" ]; then",
1744
- " MP_TRACE_ID=$(printf '%s' \"$MP_SETUP_TRACEPARENT\" | cut -d- -f2)",
1745
- " MP_JOB_SPAN=$(printf '%s' \"$MP_SETUP_TRACEPARENT\" | cut -d- -f3)",
1746
- "fi",
1747
- "MP_SETUP_SPAN=''; MP_SETUP_START=''; MP_PREV_PHASE=''; MP_PREV_TS=''",
1748
1733
  // Failure-trap state. _MP_DONE flips to 1 once setup+start reach `ready` (see buildStartScript),
1749
1734
  // disarming _mp_fail so a normal container stop / code-server hiccup after ready never looks like
1750
1735
  // a setup failure. _MP_LAST holds the most recent status JSON (set by _mp) for the error re-emit.
1751
1736
  "_MP_DONE=0; _MP_LAST=''",
1752
- "_mp_rand_id() { od -An -tx1 -N8 /dev/urandom 2>/dev/null | tr -d ' \\n'; }",
1753
- "_mp_phase_span_name() {",
1754
- " case \"$1\" in",
1755
- " initializing) echo 'setup.initialize' ;;",
1756
- " credentials) echo 'setup.credentials' ;;",
1757
- " shell) echo 'setup.shell' ;;",
1758
- " cloning) echo 'setup.clone' ;;",
1759
- " postCreate) echo 'setup.postCreate' ;;",
1760
- " postStart) echo 'setup.postStart' ;;",
1761
- " *) echo \"setup.$1\" ;;",
1762
- " esac",
1763
- "}",
1764
- // Emit one OTLP/HTTP JSON span, backgrounded + short timeout so it never adds latency
1765
- // to (or blocks) the setup path. Args: name start_ms end_ms span_id parent_span_id phase.
1766
- "_mp_span_emit() {",
1767
- " if [ -z \"$MP_TRACE_ID\" ] || [ -z \"${OTEL_EXPORTER_OTLP_ENDPOINT:-}\" ]; then return 0; fi",
1768
- " if ! command -v curl >/dev/null 2>&1; then return 0; fi",
1769
- // Optional $7 = error message and $8 = exit code turn this into a native ERROR span
1770
- // (OTel status code 2 + a setup.exit_code attribute) — that's what makes a failed
1771
- // clone/postCreate/postStart show up red in the waterfall instead of as a hole.
1772
- " 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=''",
1773
- " if [ -n \"$phase\" ]; then attrs=\"{\\\"key\\\":\\\"setup.phase\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"$phase\\\"}},{\\\"key\\\":\\\"setup.duration_ms\\\",\\\"value\\\":{\\\"intValue\\\":\\\"$dur\\\"}}\"; fi",
1774
- " if [ -n \"$ecode\" ]; then [ -n \"$attrs\" ] && attrs=\"$attrs,\"; attrs=\"${attrs}{\\\"key\\\":\\\"setup.exit_code\\\",\\\"value\\\":{\\\"intValue\\\":\\\"$ecode\\\"}}\"; fi",
1775
- " if [ -n \"$errmsg\" ]; then status=\",\\\"status\\\":{\\\"code\\\":2,\\\"message\\\":\\\"$errmsg\\\"}\"; fi",
1776
- " 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}]}]}]}\"",
1777
- " ( curl -s -m 3 -o /dev/null -X POST \"$OTEL_EXPORTER_OTLP_ENDPOINT/v1/traces\" -H 'Content-Type: application/json' -d \"$body\" || true ) &",
1778
- "}",
1779
- // Called on each new phase stamp. The first stamp opens the `worker.setup` window; every
1780
- // later stamp closes the previous phase as a child span; `ready` also emits the parent.
1781
- "_mp_span_on_stamp() {",
1782
- " if [ -z \"$MP_TRACE_ID\" ]; then return 0; fi",
1783
- " local key=\"$1\" now=\"$2\"",
1784
- " if [ -z \"$MP_SETUP_SPAN\" ]; then",
1785
- " MP_SETUP_SPAN=$(_mp_rand_id); MP_SETUP_START=\"$now\"; MP_PREV_PHASE=\"$key\"; MP_PREV_TS=\"$now\"; return 0",
1786
- " fi",
1787
- " _mp_span_emit \"$(_mp_phase_span_name \"$MP_PREV_PHASE\")\" \"$MP_PREV_TS\" \"$now\" \"$(_mp_rand_id)\" \"$MP_SETUP_SPAN\" \"$MP_PREV_PHASE\"",
1788
- " if [ \"$key\" = ready ]; then",
1789
- " _mp_span_emit 'worker.setup' \"$MP_SETUP_START\" \"$now\" \"$MP_SETUP_SPAN\" \"$MP_JOB_SPAN\" ''",
1790
- " else",
1791
- " MP_PREV_PHASE=\"$key\"; MP_PREV_TS=\"$now\"",
1792
- " fi",
1793
- "}",
1794
- // ── Native error spans on setup failure (the direct goal of this refactor) ──
1737
+ // ── Setup failure trap ──
1795
1738
  // set -e makes any uncaught failure abort the CMD; before PR #115 that just left the worker
1796
- // stuck at its last status with a hole in the trace. This EXIT trap turns that same failure
1797
- // into: (1) a real OTLP span in ERROR (status code 2 + exit code + the in-flight phase), so the
1798
- // failing clone/postCreate/postStart shows up red under worker.setup, and (2) a phase=error
1799
- // 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
1800
1741
  // non-zero exit and only before `ready` (_MP_DONE guards the normal path); the short sleep lets
1801
1742
  // the 1.5s poll read the error status before the container exits.
1802
1743
  "_mp_fail() {",
@@ -1806,26 +1747,13 @@ export function buildWorkerScript(params: {
1806
1747
  " [ \"$_MP_DONE\" = 1 ] && return 0",
1807
1748
  " [ \"$code\" = 0 ] && return 0",
1808
1749
  " _MP_DONE=1",
1809
- " local phase now msg j",
1810
- " phase=\"$MP_PREV_PHASE\"",
1811
- " [ -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')",
1812
1752
  " [ -z \"$phase\" ] && phase=initializing",
1813
- " now=\"$(_mp_now)\"",
1814
1753
  // A phase that knows *why* it failed (e.g. a clone on a branch that doesn't exist) sets
1815
1754
  // MP_FAIL_MSG before exiting, so the UI shows that instead of the generic phase message.
1816
1755
  " msg=\"${MP_FAIL_MSG:-setup phase $phase failed (exit $code)}\"",
1817
1756
  " echo \"[setup] ✗ $msg\" >&2",
1818
- // Best-effort error spans: close the failing phase (child of worker.setup) then worker.setup
1819
- // itself in ERROR. On a restart (postStart failure) the setup window was never opened, so emit
1820
- // a single worker.setup error span under the job span instead.
1821
- " if [ -n \"$MP_TRACE_ID\" ]; then",
1822
- " if [ -n \"$MP_SETUP_SPAN\" ]; then",
1823
- " _mp_span_emit \"$(_mp_phase_span_name \"$phase\")\" \"${MP_PREV_TS:-$now}\" \"$now\" \"$(_mp_rand_id)\" \"$MP_SETUP_SPAN\" \"$phase\" \"$msg\" \"$code\"",
1824
- " _mp_span_emit 'worker.setup' \"${MP_SETUP_START:-$now}\" \"$now\" \"$MP_SETUP_SPAN\" \"$MP_JOB_SPAN\" '' \"$msg\" \"$code\"",
1825
- " else",
1826
- " _mp_span_emit 'worker.setup' \"${MP_PREV_TS:-$now}\" \"$now\" \"$(_mp_rand_id)\" \"$MP_JOB_SPAN\" \"$phase\" \"$msg\" \"$code\"",
1827
- " fi",
1828
- " fi",
1829
1757
  // Flip .mp-status.json to phase=error (preserving the rest) so monitorSetup surfaces it.
1830
1758
  " j=\"$_MP_LAST\"",
1831
1759
  " if [ -n \"$j\" ]; then",
@@ -1855,10 +1783,10 @@ export function buildWorkerScript(params: {
1855
1783
 
1856
1784
  // ─── buildSetupPlan (RFC 0017) ─────────────────────────────────────────────────
1857
1785
  //
1858
- // 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
1859
1787
  // WORKER_SETUP_ORCHESTRATOR=agent. Reuses the exact bash blocks from buildSetupScript/buildStartScript
1860
- // (clone strategies, oh-my-zsh, dotfiles, lifecycle) MINUS the `_mp`/`_mp_stamp`/OTLP machinery — the
1861
- // 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
1862
1790
  // (mirrors the legacy `mkStatus(...)` writes) so the DB/UI progression is identical. See RFC 0017 §2.2.
1863
1791
 
1864
1792
  export function buildSetupPlan(params: SetupScriptParams): {