From 0e779b026003f282c0a80c484c0f6d3cf72e7a18 Mon Sep 17 00:00:00 2001 From: Bennett Elder Date: Fri, 25 Sep 2026 16:58:23 -0400 Subject: [PATCH 1/2] fix(scripts): derive backend.assemblyDir from id and version --- .../reference/02-authoring-guide.md | 4 +++- scripts/build-extension.sh | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.claude/skills/duplo-extension-dev/reference/02-authoring-guide.md b/.claude/skills/duplo-extension-dev/reference/02-authoring-guide.md index 9f372f4..b858b60 100644 --- a/.claude/skills/duplo-extension-dev/reference/02-authoring-guide.md +++ b/.claude/skills/duplo-extension-dev/reference/02-authoring-guide.md @@ -81,7 +81,9 @@ Ties the bundle together. For a typed extension (see the hello-world manifest): ``` Critical: `archetype` must be `"typed"` so the loader takes the DLL path; the five `*Type` names must be the fully-qualified type names in your DLL; `backend.assemblyDir` is `//backend`. **Bump `version` on -every backend code change** ([06](06-registration.md#reloading-changed-code--bump-manifestversion)). +every backend code change** ([06](06-registration.md#reloading-changed-code--bump-manifestversion)) — you don't +have to bump `assemblyDir` with it, since `build-extension.sh` derives that from `id` and `version` when it +writes the bundle manifest. It prints a `NOTE:` if the checked-in value disagreed. ### Naming diff --git a/scripts/build-extension.sh b/scripts/build-extension.sh index da36b75..3a00ce9 100755 --- a/scripts/build-extension.sh +++ b/scripts/build-extension.sh @@ -214,8 +214,19 @@ echo " host provides $(wc -l < "$PROVIDED" | tr -d ' ') assemblies (excluded echo "==> Assembling bundle" PKG="$DIR/dist/pkg" rm -rf "$PKG"; mkdir -p "$PKG/backend" "$PKG/fe" "$PKG/skills" -# Bundle manifest with the real host SDK version pinned in. -jq --arg v "$SDK_VER" '.sdkVersion = $v' "$DIR/manifest.json" > "$PKG/manifest.json" +# Bundle manifest with the real host SDK version pinned in, and backend.assemblyDir derived from id+version. +# The host stages the bundle to /// but loads the entry assembly from +# assemblyDir, so the two have to agree. Maintained by hand they drift as soon as you bump version alone, and +# what that breaks depends on the target. A host that still has the old version on disk keeps running the OLD +# backend DLL and says nothing. A host that's never seen it refuses the install with "Extension entry assembly +# not found: ...//backend/". It's always //backend, so compute it here instead +# of trusting the checked-in value. +jq --arg v "$SDK_VER" \ + '.sdkVersion = $v | .backend.assemblyDir = (.id + "/" + .version + "/backend")' \ + "$DIR/manifest.json" > "$PKG/manifest.json" +_ad_src=$(jq -r '.backend.assemblyDir // ""' "$MANIFEST") +_ad_out=$(jq -r '.backend.assemblyDir' "$PKG/manifest.json") +[ "$_ad_src" = "$_ad_out" ] || echo " NOTE: stale backend.assemblyDir '$_ad_src' in $MANIFEST — bundled as '$_ad_out'" # Ship only backend assemblies the host does NOT already provide, plus each kept DLL's sidecars. Native # runtimes/ are host-provided too (Mongo/AWS natives load via the host's Default-ALC copies), so skip them. kept=0; dropped=0 From 33b5057b22bdac8b390cd14c5f31aa3687c201b5 Mon Sep 17 00:00:00 2001 From: Bennett Elder Date: Tue, 29 Sep 2026 15:07:11 -0400 Subject: [PATCH 2/2] fix(scripts): derive skills[].folder from id and version --- .../reference/02-authoring-guide.md | 9 ++-- scripts/build-extension.sh | 41 ++++++++++++++----- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/.claude/skills/duplo-extension-dev/reference/02-authoring-guide.md b/.claude/skills/duplo-extension-dev/reference/02-authoring-guide.md index b858b60..c0b9bb3 100644 --- a/.claude/skills/duplo-extension-dev/reference/02-authoring-guide.md +++ b/.claude/skills/duplo-extension-dev/reference/02-authoring-guide.md @@ -80,10 +80,11 @@ Ties the bundle together. For a typed extension (see the hello-world manifest): } ``` Critical: `archetype` must be `"typed"` so the loader takes the DLL path; the five `*Type` names must be the -fully-qualified type names in your DLL; `backend.assemblyDir` is `//backend`. **Bump `version` on -every backend code change** ([06](06-registration.md#reloading-changed-code--bump-manifestversion)) — you don't -have to bump `assemblyDir` with it, since `build-extension.sh` derives that from `id` and `version` when it -writes the bundle manifest. It prints a `NOTE:` if the checked-in value disagreed. +fully-qualified type names in your DLL; `backend.assemblyDir` is `//backend` and each +`skills[].folder` is `//skills/`. **Bump `version` on every backend code change** +([06](06-registration.md#reloading-changed-code--bump-manifestversion)) — you don't have to bump either path +with it, since `build-extension.sh` derives both from `id` and `version` when it writes the bundle manifest. +It prints a `NOTE:` if a checked-in value disagreed. ### Naming diff --git a/scripts/build-extension.sh b/scripts/build-extension.sh index 3a00ce9..e9fe60b 100755 --- a/scripts/build-extension.sh +++ b/scripts/build-extension.sh @@ -214,19 +214,40 @@ echo " host provides $(wc -l < "$PROVIDED" | tr -d ' ') assemblies (excluded echo "==> Assembling bundle" PKG="$DIR/dist/pkg" rm -rf "$PKG"; mkdir -p "$PKG/backend" "$PKG/fe" "$PKG/skills" -# Bundle manifest with the real host SDK version pinned in, and backend.assemblyDir derived from id+version. -# The host stages the bundle to /// but loads the entry assembly from -# assemblyDir, so the two have to agree. Maintained by hand they drift as soon as you bump version alone, and -# what that breaks depends on the target. A host that still has the old version on disk keeps running the OLD -# backend DLL and says nothing. A host that's never seen it refuses the install with "Extension entry assembly -# not found: ...//backend/". It's always //backend, so compute it here instead -# of trusting the checked-in value. -jq --arg v "$SDK_VER" \ - '.sdkVersion = $v | .backend.assemblyDir = (.id + "/" + .version + "/backend")' \ - "$DIR/manifest.json" > "$PKG/manifest.json" +# Bundle manifest with the real host SDK version pinned in, and both // paths derived from +# id+version. The host stages the bundle to /// but reads the entry assembly +# from backend.assemblyDir and each provisioning skill from skills[].folder, so all three have to agree. +# Maintained by hand they drift the moment you bump version alone, and what that breaks depends on the target: +# +# backend.assemblyDir a host that still has the old version on disk keeps running the OLD backend DLL and +# says nothing; a host that's never seen it refuses the install outright with +# "Extension entry assembly not found: ...//backend/". +# skills[].folder quieter in both directions. The loader's seeder returns early on a missing directory, +# so a fresh host loads fine with the skill silently absent — Agent-mode provisioning +# then fails later with a confusing "no skill" — and a host holding the old version +# seeds the OLD skill. +# +# Both are always //…, so compute them here instead of trusting the checked-in values. The skill +# tail varies per entry (skills/), so only the first two segments are replaced; a manifest with no +# skills array is left alone rather than given an empty one. +jq --arg v "$SDK_VER" ' + .sdkVersion = $v + | .backend.assemblyDir = (.id + "/" + .version + "/backend") + | .id as $i | .version as $r + | if (.skills | type) == "array" then + .skills = [ .skills[] + | if ((.folder | split("/") | length) > 2) + then .folder = ($i + "/" + $r + "/" + (.folder | split("/") | .[2:] | join("/"))) + else . end ] + else . end +' "$DIR/manifest.json" > "$PKG/manifest.json" +# Report drift rather than repairing it silently, so the checked-in value gets fixed at the source too. _ad_src=$(jq -r '.backend.assemblyDir // ""' "$MANIFEST") _ad_out=$(jq -r '.backend.assemblyDir' "$PKG/manifest.json") [ "$_ad_src" = "$_ad_out" ] || echo " NOTE: stale backend.assemblyDir '$_ad_src' in $MANIFEST — bundled as '$_ad_out'" +_sk_src=$(jq -r '[.skills[]?.folder] | join(", ")' "$MANIFEST") +_sk_out=$(jq -r '[.skills[]?.folder] | join(", ")' "$PKG/manifest.json") +[ "$_sk_src" = "$_sk_out" ] || echo " NOTE: stale skills[].folder '$_sk_src' in $MANIFEST — bundled as '$_sk_out'" # Ship only backend assemblies the host does NOT already provide, plus each kept DLL's sidecars. Native # runtimes/ are host-provided too (Mongo/AWS natives load via the host's Default-ALC copies), so skip them. kept=0; dropped=0