From 255cf066277fa50323ca3e0a3b44b3a0ee57b284 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 3 Aug 2026 19:34:21 +0200 Subject: [PATCH 01/14] fix(agentic-engineering): forbid foreground remote polling --- .claude-plugin/marketplace.json | 2 +- .github/plugin/marketplace.json | 2 +- .../.claude-plugin/plugin.json | 2 +- .../agents/agentic-engineer.agent.md | 7 +++++-- plugins/agentic-engineering/plugin.json | 2 +- scripts/validate-manifests.sh | 13 +++++++++++++ scripts/validate-manifests.test.sh | 15 +++++++++++++++ 7 files changed, 37 insertions(+), 6 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index d174f26..cb69db7 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -27,7 +27,7 @@ { "name": "agentic-engineering", "description": "The autonomous engineering system for repository portfolios — engineer, read-only surveyor, and meta-engineer agents; portfolio, product, spend, and improvement workflows; cross-tool instruction architecture and skill discovery; configured by the consumer AGENTS.md", - "version": "4.1.2", + "version": "4.1.3", "source": "./plugins/agentic-engineering" }, { diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index d174f26..cb69db7 100644 --- a/.github/plugin/marketplace.json +++ b/.github/plugin/marketplace.json @@ -27,7 +27,7 @@ { "name": "agentic-engineering", "description": "The autonomous engineering system for repository portfolios — engineer, read-only surveyor, and meta-engineer agents; portfolio, product, spend, and improvement workflows; cross-tool instruction architecture and skill discovery; configured by the consumer AGENTS.md", - "version": "4.1.2", + "version": "4.1.3", "source": "./plugins/agentic-engineering" }, { diff --git a/plugins/agentic-engineering/.claude-plugin/plugin.json b/plugins/agentic-engineering/.claude-plugin/plugin.json index b1f07e0..d4b4241 100644 --- a/plugins/agentic-engineering/.claude-plugin/plugin.json +++ b/plugins/agentic-engineering/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "agentic-engineering", "description": "The autonomous engineering system for repository portfolios — engineer, read-only surveyor, and meta-engineer agents; portfolio, product, spend, and improvement workflows; cross-tool instruction architecture and skill discovery; configured by the consumer AGENTS.md", - "version": "4.1.2", + "version": "4.1.3", "author": { "name": "devantler-tech", "url": "https://github.com/devantler-tech" diff --git a/plugins/agentic-engineering/agents/agentic-engineer.agent.md b/plugins/agentic-engineering/agents/agentic-engineer.agent.md index fc1a215..4efc401 100644 --- a/plugins/agentic-engineering/agents/agentic-engineer.agent.md +++ b/plugins/agentic-engineering/agents/agentic-engineer.agent.md @@ -109,8 +109,11 @@ instead. invoking it. When the runtime exposes no per-call setting, use an equivalent bounded process supervisor that preserves output and exit status; when neither control exists, split the command into bounded targets or record the missing capability rather than launching a known-too-long - command. This does not authorize a long foreground remote-state poll, retry, or sleep loop. Keep - remote waits asynchronous where the runtime supports it and keep doing useful work. + command. **Never foreground-wait on remote state.** **Keep remote waits asynchronous:** for CI, + review, merge, or deploy state, arm + at most one detached watcher when the runtime supports it; never run a foreground polling or + sleep loop, and never poll beside an armed watcher. Continue with other actionable work. If none + remains, end the run and let the next scheduled tick collect the result. 8. **Spend context deliberately.** Delegate the survey to the read-only **`portfolio-surveyor`** subagent (your runtime may expose this bundled agent under a plugin-scoped name — e.g. `agentic-engineering:portfolio-surveyor` — so select it by whatever qualified diff --git a/plugins/agentic-engineering/plugin.json b/plugins/agentic-engineering/plugin.json index b1f07e0..d4b4241 100644 --- a/plugins/agentic-engineering/plugin.json +++ b/plugins/agentic-engineering/plugin.json @@ -1,7 +1,7 @@ { "name": "agentic-engineering", "description": "The autonomous engineering system for repository portfolios — engineer, read-only surveyor, and meta-engineer agents; portfolio, product, spend, and improvement workflows; cross-tool instruction architecture and skill discovery; configured by the consumer AGENTS.md", - "version": "4.1.2", + "version": "4.1.3", "author": { "name": "devantler-tech", "url": "https://github.com/devantler-tech" diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index 68d2add..3dfe7d9 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -774,6 +774,19 @@ validate_desired_state_resources() { fi done + for remote_wait_marker in \ + '**Never foreground-wait on remote state.**' \ + 'at most one detached watcher' \ + 'end the run and let the next scheduled tick collect'; do + if [ ! -f "$plugin_dir/agents/$entrypoint.agent.md" ] \ + || ! grep -qF "$remote_wait_marker" \ + "$plugin_dir/agents/$entrypoint.agent.md"; then + echo "::error::$resource: agentic-engineer must forbid foreground remote waits, missing: $remote_wait_marker" + failed=1 + resource_failed=1 + fi + done + if [ ! -f "$plugin_dir/agents/agent-improver.agent.md" ] \ || ! grep -qF "## Delivery ownership — finding to fix" \ "$plugin_dir/agents/agent-improver.agent.md"; then diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index b73435b..74b916d 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -570,6 +570,9 @@ Use a **bounded tool timeout** from the **measured repository or CI duration** p When the runtime exposes no per-call setting, use an equivalent bounded process supervisor. Keep remote waits asynchronous. +**Never foreground-wait on remote state.** Arm at most one detached watcher. +If no other work remains, end the run and let the next scheduled tick collect the result. + ## Spend stewardship - **You never move money.** @@ -964,6 +967,18 @@ for deadline_marker in \ "agentic-engineer must bound expected-to-run-long local commands" "$d" done +for remote_wait_marker in \ + 'Never foreground-wait on remote state' \ + 'at most one detached watcher' \ + 'end the run and let the next scheduled tick collect'; do + d=$(fresh); make_desired_state "$d" alpha + grep -vF "$remote_wait_marker" \ + "$d/plugins/alpha/agents/agentic-engineer.agent.md" > "$d/tmp" \ + && mv "$d/tmp" "$d/plugins/alpha/agents/agentic-engineer.agent.md" + check_fail "Agentic Engineer requires remote wait marker: $remote_wait_marker" \ + "agentic-engineer must forbid foreground remote waits" "$d" +done + d=$(fresh); make_desired_state "$d" alpha jq '.spec.guardrails |= map(select(startswith("Write-capable roles own selected engineering work") | not))' \ "$d/plugins/alpha/resources/provider-neutral.desired-state.json" > "$d/tmp" \ From 33de7be621ac37fdcf13c37d8a188e9e398f8eab Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 3 Aug 2026 19:44:54 +0200 Subject: [PATCH 02/14] fix(agentic-engineering): reject contradictory wait rules --- scripts/validate-manifests.sh | 24 ++++++++++++++++++++++++ scripts/validate-manifests.test.sh | 16 ++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index 3dfe7d9..9e9246f 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -787,6 +787,30 @@ validate_desired_state_resources() { fi done + remote_wait_contract='**Never foreground-wait on remote state.** **Keep remote waits asynchronous:** for CI, review, merge, or deploy state, arm at most one detached watcher when the runtime supports it; never run a foreground polling or sleep loop, and never poll beside an armed watcher. Continue with other actionable work. If none remains, end the run and let the next scheduled tick collect the result.' + if [ -f "$plugin_dir/agents/$entrypoint.agent.md" ]; then + normalized_agent="$( + tr '\n' ' ' < "$plugin_dir/agents/$entrypoint.agent.md" \ + | sed 's/[[:space:]][[:space:]]*/ /g' + )" + case "$normalized_agent" in + *"$remote_wait_contract"*) + remote_wait_remainder="${normalized_agent%%"$remote_wait_contract"*}${normalized_agent#*"$remote_wait_contract"}" + if printf '%s\n' "$remote_wait_remainder" \ + | grep -Eiq 'foreground|detached[[:space:]]+watcher|next[[:space:]]+scheduled[[:space:]]+tick'; then + echo "::error::$resource: agentic-engineer remote-wait semantics must appear only in the canonical contract" + failed=1 + resource_failed=1 + fi + ;; + *) + echo "::error::$resource: agentic-engineer must forbid foreground remote waits with the canonical contiguous contract" + failed=1 + resource_failed=1 + ;; + esac + fi + if [ ! -f "$plugin_dir/agents/agent-improver.agent.md" ] \ || ! grep -qF "## Delivery ownership — finding to fix" \ "$plugin_dir/agents/agent-improver.agent.md"; then diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index 74b916d..6a1dbf3 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -568,10 +568,7 @@ Fixture agent. Enabling spend work needs the **Spend contract** section. **Give expected-to-run-long local commands an explicit execution deadline.** Use a **bounded tool timeout** from the **measured repository or CI duration** plus headroom. When the runtime exposes no per-call setting, use an equivalent bounded process supervisor. -Keep remote waits asynchronous. - -**Never foreground-wait on remote state.** Arm at most one detached watcher. -If no other work remains, end the run and let the next scheduled tick collect the result. +**Never foreground-wait on remote state.** **Keep remote waits asynchronous:** for CI, review, merge, or deploy state, arm at most one detached watcher when the runtime supports it; never run a foreground polling or sleep loop, and never poll beside an armed watcher. Continue with other actionable work. If none remains, end the run and let the next scheduled tick collect the result. ## Spend stewardship @@ -979,6 +976,17 @@ for remote_wait_marker in \ "agentic-engineer must forbid foreground remote waits" "$d" done +for remote_wait_contradiction in \ + 'Foreground CI polling is allowed after the canonical rule.' \ + 'An additional detached watcher may be armed after the canonical rule.' \ + 'The next scheduled tick handoff is optional after the canonical rule.'; do + d=$(fresh); make_desired_state "$d" alpha + printf '\n%s\n' "$remote_wait_contradiction" \ + >> "$d/plugins/alpha/agents/agentic-engineer.agent.md" + check_fail "Agentic Engineer rejects contradictory remote wait rule: $remote_wait_contradiction" \ + "remote-wait semantics must appear only in the canonical contract" "$d" +done + d=$(fresh); make_desired_state "$d" alpha jq '.spec.guardrails |= map(select(startswith("Write-capable roles own selected engineering work") | not))' \ "$d/plugins/alpha/resources/provider-neutral.desired-state.json" > "$d/tmp" \ From b07009ce7668d43877258e5d5885bce59366b31d Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 3 Aug 2026 19:49:20 +0200 Subject: [PATCH 03/14] fix(agentic-engineering): catch remote wait synonyms --- scripts/validate-manifests.sh | 2 +- scripts/validate-manifests.test.sh | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index 9e9246f..4aa0ec0 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -797,7 +797,7 @@ validate_desired_state_resources() { *"$remote_wait_contract"*) remote_wait_remainder="${normalized_agent%%"$remote_wait_contract"*}${normalized_agent#*"$remote_wait_contract"}" if printf '%s\n' "$remote_wait_remainder" \ - | grep -Eiq 'foreground|detached[[:space:]]+watcher|next[[:space:]]+scheduled[[:space:]]+tick'; then + | grep -Eiq 'foreground|detached[[:space:]]+watcher|next[[:space:]]+scheduled[[:space:]]+tick|(^|[^[:alnum:]_])(wait|watch|poll|sleep)([^[:alnum:]_]|$).{0,160}(^|[^[:alnum:]_])(ci|review|merge|deploy|remote)([^[:alnum:]_]|$)|(^|[^[:alnum:]_])(ci|review|merge|deploy|remote)([^[:alnum:]_]|$).{0,160}(^|[^[:alnum:]_])(wait|watch|poll|sleep)([^[:alnum:]_]|$)'; then echo "::error::$resource: agentic-engineer remote-wait semantics must appear only in the canonical contract" failed=1 resource_failed=1 diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index 6a1dbf3..1938380 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -979,7 +979,8 @@ done for remote_wait_contradiction in \ 'Foreground CI polling is allowed after the canonical rule.' \ 'An additional detached watcher may be armed after the canonical rule.' \ - 'The next scheduled tick handoff is optional after the canonical rule.'; do + 'The next scheduled tick handoff is optional after the canonical rule.' \ + 'Wait for CI completion with gh run watch after the canonical rule.'; do d=$(fresh); make_desired_state "$d" alpha printf '\n%s\n' "$remote_wait_contradiction" \ >> "$d/plugins/alpha/agents/agentic-engineer.agent.md" From a518dc9a8173b05e0a81203aca2554740b870d6c Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 3 Aug 2026 19:53:12 +0200 Subject: [PATCH 04/14] fix(agentic-engineering): remove wait matcher distance cap --- scripts/validate-manifests.sh | 2 +- scripts/validate-manifests.test.sh | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index 4aa0ec0..5bf0d5f 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -797,7 +797,7 @@ validate_desired_state_resources() { *"$remote_wait_contract"*) remote_wait_remainder="${normalized_agent%%"$remote_wait_contract"*}${normalized_agent#*"$remote_wait_contract"}" if printf '%s\n' "$remote_wait_remainder" \ - | grep -Eiq 'foreground|detached[[:space:]]+watcher|next[[:space:]]+scheduled[[:space:]]+tick|(^|[^[:alnum:]_])(wait|watch|poll|sleep)([^[:alnum:]_]|$).{0,160}(^|[^[:alnum:]_])(ci|review|merge|deploy|remote)([^[:alnum:]_]|$)|(^|[^[:alnum:]_])(ci|review|merge|deploy|remote)([^[:alnum:]_]|$).{0,160}(^|[^[:alnum:]_])(wait|watch|poll|sleep)([^[:alnum:]_]|$)'; then + | grep -Eiq 'foreground|detached[[:space:]]+watcher|next[[:space:]]+scheduled[[:space:]]+tick|(^|[^[:alnum:]_])(wait|watch|poll|sleep)([^[:alnum:]_]|$).*(^|[^[:alnum:]_])(ci|review|merge|deploy|remote)([^[:alnum:]_]|$)|(^|[^[:alnum:]_])(ci|review|merge|deploy|remote)([^[:alnum:]_]|$).*(^|[^[:alnum:]_])(wait|watch|poll|sleep)([^[:alnum:]_]|$)'; then echo "::error::$resource: agentic-engineer remote-wait semantics must appear only in the canonical contract" failed=1 resource_failed=1 diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index 1938380..fd1a467 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -988,6 +988,13 @@ for remote_wait_contradiction in \ "remote-wait semantics must appear only in the canonical contract" "$d" done +d=$(fresh); make_desired_state "$d" alpha +remote_wait_filler=$(printf 'details %.0s' {1..30}) +printf '\nWait %s for CI completion after the canonical rule.\n' "$remote_wait_filler" \ + >> "$d/plugins/alpha/agents/agentic-engineer.agent.md" +check_fail "Agentic Engineer rejects remote wait synonyms beyond an arbitrary distance" \ + "remote-wait semantics must appear only in the canonical contract" "$d" + d=$(fresh); make_desired_state "$d" alpha jq '.spec.guardrails |= map(select(startswith("Write-capable roles own selected engineering work") | not))' \ "$d/plugins/alpha/resources/provider-neutral.desired-state.json" > "$d/tmp" \ From 1cc0b8d803b81760794d8f6f5504bceafece95a4 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 3 Aug 2026 19:58:09 +0200 Subject: [PATCH 05/14] fix(agentic-engineering): reject inflected wait rules --- plugins/agentic-engineering/agents/agentic-engineer.agent.md | 2 +- scripts/validate-manifests.sh | 2 +- scripts/validate-manifests.test.sh | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/agentic-engineering/agents/agentic-engineer.agent.md b/plugins/agentic-engineering/agents/agentic-engineer.agent.md index 4efc401..e3b7cf8 100644 --- a/plugins/agentic-engineering/agents/agentic-engineer.agent.md +++ b/plugins/agentic-engineering/agents/agentic-engineer.agent.md @@ -78,7 +78,7 @@ instead. 3. **The draft PR is the checkpoint.** Act on your own best judgement — you do not seek approval before drafting — but every change ships as a **draft PR** with a conventional-commit title and your AI-disclosure line. The maintainer's promotion to ready-for-review is the go-signal; you - **never self-promote**. While a draft waits, keep it review-ready across the full **hygiene + **never self-promote**. While a draft remains open, keep it review-ready across the full **hygiene pentad**: (a) green CI, (b) reviewer findings resolved — threads *and* any findings your deployment's review tooling publishes outside threads, (c) no merge conflicts, (d) green pre-merge quality checks, (e) an approving review at the **current head** (a green on a stale diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index 5bf0d5f..55ea15c 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -797,7 +797,7 @@ validate_desired_state_resources() { *"$remote_wait_contract"*) remote_wait_remainder="${normalized_agent%%"$remote_wait_contract"*}${normalized_agent#*"$remote_wait_contract"}" if printf '%s\n' "$remote_wait_remainder" \ - | grep -Eiq 'foreground|detached[[:space:]]+watcher|next[[:space:]]+scheduled[[:space:]]+tick|(^|[^[:alnum:]_])(wait|watch|poll|sleep)([^[:alnum:]_]|$).*(^|[^[:alnum:]_])(ci|review|merge|deploy|remote)([^[:alnum:]_]|$)|(^|[^[:alnum:]_])(ci|review|merge|deploy|remote)([^[:alnum:]_]|$).*(^|[^[:alnum:]_])(wait|watch|poll|sleep)([^[:alnum:]_]|$)'; then + | grep -Eiq 'foreground|detached[[:space:]]+watcher|next[[:space:]]+scheduled[[:space:]]+tick|(^|[^[:alnum:]_])(wait|watch|poll|sleep)[[:alpha:]]*([^[:alnum:]_]|$).*(^|[^[:alnum:]_])(ci|review|merge|deploy|remote)([^[:alnum:]_]|$)|(^|[^[:alnum:]_])(ci|review|merge|deploy|remote)([^[:alnum:]_]|$).*(^|[^[:alnum:]_])(wait|watch|poll|sleep)[[:alpha:]]*([^[:alnum:]_]|$)'; then echo "::error::$resource: agentic-engineer remote-wait semantics must appear only in the canonical contract" failed=1 resource_failed=1 diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index fd1a467..a99ec32 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -980,7 +980,9 @@ for remote_wait_contradiction in \ 'Foreground CI polling is allowed after the canonical rule.' \ 'An additional detached watcher may be armed after the canonical rule.' \ 'The next scheduled tick handoff is optional after the canonical rule.' \ - 'Wait for CI completion with gh run watch after the canonical rule.'; do + 'Wait for CI completion with gh run watch after the canonical rule.' \ + 'CI requires waiting for completion after the canonical rule.' \ + 'Review completion is watched after the canonical rule.'; do d=$(fresh); make_desired_state "$d" alpha printf '\n%s\n' "$remote_wait_contradiction" \ >> "$d/plugins/alpha/agents/agentic-engineer.agent.md" From 7ecc63599c0241b69843561fe197fb5d395f7592 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 3 Aug 2026 20:05:19 +0200 Subject: [PATCH 06/14] fix(agentic-engineering): pin entrypoint content digest --- .../provider-neutral.desired-state.json | 1 + scripts/validate-manifests.sh | 34 ++++++++++++++----- scripts/validate-manifests.test.sh | 33 +++++++++++++++--- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/plugins/agentic-engineering/resources/provider-neutral.desired-state.json b/plugins/agentic-engineering/resources/provider-neutral.desired-state.json index 7eaac98..f989f47 100644 --- a/plugins/agentic-engineering/resources/provider-neutral.desired-state.json +++ b/plugins/agentic-engineering/resources/provider-neutral.desired-state.json @@ -10,6 +10,7 @@ "marketplace": "devantler-tech/agent-plugins", "plugin": "agentic-engineering", "entrypoint": "agentic-engineer", + "entrypointSha256": "1f5743563772b4aaf767e7188c7accde4f49e7a5a515eed080bd237be7862791", "updatePolicy": "latest-reviewed-default-branch", "providerPolicy": "neutral", "refreshTiming": "before-starting-each-run", diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index 55ea15c..f65d6c7 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -28,6 +28,14 @@ CLAUDE_MANIFEST=".claude-plugin/marketplace.json" RENAME_HISTORY="scripts/marketplace-rename-history.json" README="README.md" +sha256_file() { + if command -v sha256sum > /dev/null 2>&1; then + sha256sum "$1" | awk '{ print $1 }' + else + shasum -a 256 "$1" | awk '{ print $1 }' + fi +} + # 1. A marketplace manifest must parse and carry both required top-level keys. validate_marketplace_json() { local manifest="$1" @@ -430,6 +438,7 @@ validate_readme_parity() { validate_desired_state_resources() { local failed=0 resource_failed resource kind plugin_dir plugin_name readme basename entrypoint local schedule_source schedule_plugin schedule_agent + local entrypoint_sha256 actual_entrypoint_sha256 local canonical_resource="plugins/agentic-engineering/resources/provider-neutral.desired-state.json" local delivery_guardrail="Write-capable roles own selected engineering work from claim through exact-head review and merge; issue-only handoff is allowed only for a named external blocker or missing authority." local version_controlled_delivery="Version-controlled definition surfaces are delivered by draft pull request and owned through exact-head review and merge." @@ -525,6 +534,20 @@ validate_desired_state_resources() { resource_failed=1 fi + entrypoint_sha256=$(jq -r '.spec.source.entrypointSha256 // ""' "$resource") + if ! printf '%s\n' "$entrypoint_sha256" | grep -Eq '^[a-f0-9]{64}$'; then + echo "::error::$resource: entrypointSha256 must be a lowercase SHA-256 digest" + failed=1 + resource_failed=1 + elif [ -f "$plugin_dir/agents/$entrypoint.agent.md" ]; then + actual_entrypoint_sha256=$(sha256_file "$plugin_dir/agents/$entrypoint.agent.md") + if [ "$entrypoint_sha256" != "$actual_entrypoint_sha256" ]; then + echo "::error::$resource: entrypoint digest must match the bundled agent" + failed=1 + resource_failed=1 + fi + fi + if ! jq -e ' .spec.source.marketplace == "devantler-tech/agent-plugins" and .spec.source.updatePolicy == "latest-reviewed-default-branch" @@ -641,11 +664,11 @@ validate_desired_state_resources() { and has_keys(["source", "consumer", "roles", "runtime", "onboarding", "guardrails"])) and (.spec.source | only_keys([ - "marketplace", "plugin", "entrypoint", "updatePolicy", "providerPolicy", + "marketplace", "plugin", "entrypoint", "entrypointSha256", "updatePolicy", "providerPolicy", "refreshTiming", "hotSwapDuringRun" ]) and has_keys([ - "marketplace", "plugin", "entrypoint", "updatePolicy", "providerPolicy", + "marketplace", "plugin", "entrypoint", "entrypointSha256", "updatePolicy", "providerPolicy", "refreshTiming", "hotSwapDuringRun" ])) and (.spec.consumer @@ -795,13 +818,6 @@ validate_desired_state_resources() { )" case "$normalized_agent" in *"$remote_wait_contract"*) - remote_wait_remainder="${normalized_agent%%"$remote_wait_contract"*}${normalized_agent#*"$remote_wait_contract"}" - if printf '%s\n' "$remote_wait_remainder" \ - | grep -Eiq 'foreground|detached[[:space:]]+watcher|next[[:space:]]+scheduled[[:space:]]+tick|(^|[^[:alnum:]_])(wait|watch|poll|sleep)[[:alpha:]]*([^[:alnum:]_]|$).*(^|[^[:alnum:]_])(ci|review|merge|deploy|remote)([^[:alnum:]_]|$)|(^|[^[:alnum:]_])(ci|review|merge|deploy|remote)([^[:alnum:]_]|$).*(^|[^[:alnum:]_])(wait|watch|poll|sleep)[[:alpha:]]*([^[:alnum:]_]|$)'; then - echo "::error::$resource: agentic-engineer remote-wait semantics must appear only in the canonical contract" - failed=1 - resource_failed=1 - fi ;; *) echo "::error::$resource: agentic-engineer must forbid foreground remote waits with the canonical contiguous contract" diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index a99ec32..899c0be 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -17,6 +17,14 @@ PUBLISHED_RENAMES='{"automated-ai-engineer":"agentic-engineering"}' pass=0 fail=0 +sha256_file() { + if command -v sha256sum > /dev/null 2>&1; then + sha256sum "$1" | awk '{ print $1 }' + else + shasum -a 256 "$1" | awk '{ print $1 }' + fi +} + # Build a complete, valid fixture repo (two plugins) at $1. make_fixture() { local root="$1" @@ -556,7 +564,7 @@ check_fail "agent with an empty block-scalar description fails" "must declare a # resource model), but when present it must be valid, provider-neutral, and linked from the # plugin README so a consumer can actually find it. make_desired_state() { - local root="$1" name="$2" + local root="$1" name="$2" entrypoint_sha256 mkdir -p "$root/plugins/$name/resources" "$root/plugins/$name/agents" cat > "$root/plugins/$name/agents/agentic-engineer.agent.md" <<'EOF' --- @@ -594,6 +602,7 @@ EOF } { print } ' "$root/README.md" > "$root/README.tmp" && mv "$root/README.tmp" "$root/README.md" + entrypoint_sha256=$(sha256_file "$root/plugins/$name/agents/agentic-engineer.agent.md") cat > "$root/plugins/$name/resources/provider-neutral.desired-state.json" < "$d/tmp" \ + && mv "$d/tmp" "$d/plugins/alpha/resources/provider-neutral.desired-state.json" +check_fail "Agentic Engineer requires an entrypoint digest" \ + "entrypointSha256 must be a lowercase SHA-256 digest" "$d" + +d=$(fresh); make_desired_state "$d" alpha +jq '.spec.source.entrypointSha256 = "not-a-digest"' \ + "$d/plugins/alpha/resources/provider-neutral.desired-state.json" > "$d/tmp" \ + && mv "$d/tmp" "$d/plugins/alpha/resources/provider-neutral.desired-state.json" +check_fail "Agentic Engineer rejects a malformed entrypoint digest" \ + "entrypointSha256 must be a lowercase SHA-256 digest" "$d" + for remote_wait_contradiction in \ 'Foreground CI polling is allowed after the canonical rule.' \ 'An additional detached watcher may be armed after the canonical rule.' \ 'The next scheduled tick handoff is optional after the canonical rule.' \ 'Wait for CI completion with gh run watch after the canonical rule.' \ 'CI requires waiting for completion after the canonical rule.' \ - 'Review completion is watched after the canonical rule.'; do + 'Review completion is watched after the canonical rule.' \ + 'Await CI completion after the canonical rule.'; do d=$(fresh); make_desired_state "$d" alpha printf '\n%s\n' "$remote_wait_contradiction" \ >> "$d/plugins/alpha/agents/agentic-engineer.agent.md" check_fail "Agentic Engineer rejects contradictory remote wait rule: $remote_wait_contradiction" \ - "remote-wait semantics must appear only in the canonical contract" "$d" + "entrypoint digest must match the bundled agent" "$d" done d=$(fresh); make_desired_state "$d" alpha @@ -995,7 +1020,7 @@ remote_wait_filler=$(printf 'details %.0s' {1..30}) printf '\nWait %s for CI completion after the canonical rule.\n' "$remote_wait_filler" \ >> "$d/plugins/alpha/agents/agentic-engineer.agent.md" check_fail "Agentic Engineer rejects remote wait synonyms beyond an arbitrary distance" \ - "remote-wait semantics must appear only in the canonical contract" "$d" + "entrypoint digest must match the bundled agent" "$d" d=$(fresh); make_desired_state "$d" alpha jq '.spec.guardrails |= map(select(startswith("Write-capable roles own selected engineering work") | not))' \ From 138bf577cdc104e6e0355e83cda07f0bfb38f836 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 3 Aug 2026 20:13:03 +0200 Subject: [PATCH 07/14] fix(agentic-engineering): preserve remote wait ownership --- .../agents/agentic-engineer.agent.md | 14 ++++--- .../provider-neutral.desired-state.json | 2 +- scripts/validate-manifests.sh | 18 +------- scripts/validate-manifests.test.sh | 41 +++++++++++++++---- 4 files changed, 44 insertions(+), 31 deletions(-) diff --git a/plugins/agentic-engineering/agents/agentic-engineer.agent.md b/plugins/agentic-engineering/agents/agentic-engineer.agent.md index e3b7cf8..ba72d82 100644 --- a/plugins/agentic-engineering/agents/agentic-engineer.agent.md +++ b/plugins/agentic-engineering/agents/agentic-engineer.agent.md @@ -109,11 +109,15 @@ instead. invoking it. When the runtime exposes no per-call setting, use an equivalent bounded process supervisor that preserves output and exit status; when neither control exists, split the command into bounded targets or record the missing capability rather than launching a known-too-long - command. **Never foreground-wait on remote state.** **Keep remote waits asynchronous:** for CI, - review, merge, or deploy state, arm - at most one detached watcher when the runtime supports it; never run a foreground polling or - sleep loop, and never poll beside an armed watcher. Continue with other actionable work. If none - remains, end the run and let the next scheduled tick collect the result. + command. **Bounded one-shot remote reads or mutations are allowed. Never foreground-poll remote + state, and never wait on it through a foreground retry or sleep loop.** For CI, review, merge, or + deploy state that needs later collection, prefer a supported completion callback. Otherwise, arm + at most one detached watcher when the runtime supports it. Before ending the run, persist the + watcher's handle, target, owner, start time, deadline, and teardown or collection state in durable + memory; a later invocation must reuse or clean up that record before it may arm another watcher or + query the same target. If neither a callback nor a safe watcher is available, persist the pending + target, end the run, and let the next invocation—scheduled or on demand—collect it with a bounded + one-shot query. 8. **Spend context deliberately.** Delegate the survey to the read-only **`portfolio-surveyor`** subagent (your runtime may expose this bundled agent under a plugin-scoped name — e.g. `agentic-engineering:portfolio-surveyor` — so select it by whatever qualified diff --git a/plugins/agentic-engineering/resources/provider-neutral.desired-state.json b/plugins/agentic-engineering/resources/provider-neutral.desired-state.json index f989f47..05e56c1 100644 --- a/plugins/agentic-engineering/resources/provider-neutral.desired-state.json +++ b/plugins/agentic-engineering/resources/provider-neutral.desired-state.json @@ -10,7 +10,7 @@ "marketplace": "devantler-tech/agent-plugins", "plugin": "agentic-engineering", "entrypoint": "agentic-engineer", - "entrypointSha256": "1f5743563772b4aaf767e7188c7accde4f49e7a5a515eed080bd237be7862791", + "entrypointSha256": "96d10e59e00a081ba52d45ad95849cfe74ba69cc9bbc841ce70028468aa80de5", "updatePolicy": "latest-reviewed-default-branch", "providerPolicy": "neutral", "refreshTiming": "before-starting-each-run", diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index f65d6c7..7a3a087 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -786,8 +786,7 @@ validate_desired_state_resources() { '**Give expected-to-run-long local commands an explicit execution deadline.**' \ '**bounded tool timeout**' \ '**measured repository or CI duration**' \ - 'runtime exposes no per-call setting' \ - 'remote waits asynchronous'; do + 'runtime exposes no per-call setting'; do if [ ! -f "$plugin_dir/agents/$entrypoint.agent.md" ] \ || ! grep -qF "$deadline_marker" \ "$plugin_dir/agents/$entrypoint.agent.md"; then @@ -797,20 +796,7 @@ validate_desired_state_resources() { fi done - for remote_wait_marker in \ - '**Never foreground-wait on remote state.**' \ - 'at most one detached watcher' \ - 'end the run and let the next scheduled tick collect'; do - if [ ! -f "$plugin_dir/agents/$entrypoint.agent.md" ] \ - || ! grep -qF "$remote_wait_marker" \ - "$plugin_dir/agents/$entrypoint.agent.md"; then - echo "::error::$resource: agentic-engineer must forbid foreground remote waits, missing: $remote_wait_marker" - failed=1 - resource_failed=1 - fi - done - - remote_wait_contract='**Never foreground-wait on remote state.** **Keep remote waits asynchronous:** for CI, review, merge, or deploy state, arm at most one detached watcher when the runtime supports it; never run a foreground polling or sleep loop, and never poll beside an armed watcher. Continue with other actionable work. If none remains, end the run and let the next scheduled tick collect the result.' + remote_wait_contract="**Bounded one-shot remote reads or mutations are allowed. Never foreground-poll remote state, and never wait on it through a foreground retry or sleep loop.** For CI, review, merge, or deploy state that needs later collection, prefer a supported completion callback. Otherwise, arm at most one detached watcher when the runtime supports it. Before ending the run, persist the watcher's handle, target, owner, start time, deadline, and teardown or collection state in durable memory; a later invocation must reuse or clean up that record before it may arm another watcher or query the same target. If neither a callback nor a safe watcher is available, persist the pending target, end the run, and let the next invocation—scheduled or on demand—collect it with a bounded one-shot query." if [ -f "$plugin_dir/agents/$entrypoint.agent.md" ]; then normalized_agent="$( tr '\n' ' ' < "$plugin_dir/agents/$entrypoint.agent.md" \ diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index 899c0be..403195a 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -25,6 +25,15 @@ sha256_file() { fi } +sync_entrypoint_digest() { + local root="$1" name="$2" resource digest + resource="$root/plugins/$name/resources/provider-neutral.desired-state.json" + digest=$(sha256_file "$root/plugins/$name/agents/agentic-engineer.agent.md") + jq --arg digest "$digest" '.spec.source.entrypointSha256 = $digest' \ + "$resource" > "$root/entrypoint-digest.tmp" \ + && mv "$root/entrypoint-digest.tmp" "$resource" +} + # Build a complete, valid fixture repo (two plugins) at $1. make_fixture() { local root="$1" @@ -576,7 +585,11 @@ Fixture agent. Enabling spend work needs the **Spend contract** section. **Give expected-to-run-long local commands an explicit execution deadline.** Use a **bounded tool timeout** from the **measured repository or CI duration** plus headroom. When the runtime exposes no per-call setting, use an equivalent bounded process supervisor. -**Never foreground-wait on remote state.** **Keep remote waits asynchronous:** for CI, review, merge, or deploy state, arm at most one detached watcher when the runtime supports it; never run a foreground polling or sleep loop, and never poll beside an armed watcher. Continue with other actionable work. If none remains, end the run and let the next scheduled tick collect the result. +**Bounded one-shot remote reads or mutations are allowed. Never foreground-poll remote state, and never wait on it through a foreground retry or sleep loop.** +For CI, review, merge, or deploy state that needs later collection, prefer a supported completion callback. +Otherwise, arm at most one detached watcher when the runtime supports it. +Before ending the run, persist the watcher's handle, target, owner, start time, deadline, and teardown or collection state in durable memory; a later invocation must reuse or clean up that record before it may arm another watcher or query the same target. +If neither a callback nor a safe watcher is available, persist the pending target, end the run, and let the next invocation—scheduled or on demand—collect it with a bounded one-shot query. ## Spend stewardship @@ -964,8 +977,7 @@ for deadline_marker in \ 'Give expected-to-run-long local commands an explicit execution deadline' \ 'bounded tool timeout' \ 'measured repository or CI duration' \ - 'runtime exposes no per-call setting' \ - 'remote waits asynchronous'; do + 'runtime exposes no per-call setting'; do d=$(fresh); make_desired_state "$d" alpha grep -vF "$deadline_marker" \ "$d/plugins/alpha/agents/agentic-engineer.agent.md" > "$d/tmp" \ @@ -975,15 +987,26 @@ for deadline_marker in \ done for remote_wait_marker in \ - 'Never foreground-wait on remote state' \ - 'at most one detached watcher' \ - 'end the run and let the next scheduled tick collect'; do + 'Bounded one-shot remote reads or mutations are allowed.' \ + 'Never foreground-poll remote state' \ + 'at most one detached watcher when the runtime supports it' \ + "persist the watcher's handle, target, owner, start time, deadline, and teardown or collection state in durable memory" \ + 'a later invocation must reuse or clean up that record' \ + 'next invocation—scheduled or on demand—collect it with a bounded one-shot query'; do d=$(fresh); make_desired_state "$d" alpha - grep -vF "$remote_wait_marker" \ - "$d/plugins/alpha/agents/agentic-engineer.agent.md" > "$d/tmp" \ + awk -v marker="$remote_wait_marker" ' + { + position = index($0, marker) + if (position > 0) { + $0 = substr($0, 1, position - 1) substr($0, position + length(marker)) + } + print + } + ' "$d/plugins/alpha/agents/agentic-engineer.agent.md" > "$d/tmp" \ && mv "$d/tmp" "$d/plugins/alpha/agents/agentic-engineer.agent.md" + sync_entrypoint_digest "$d" alpha check_fail "Agentic Engineer requires remote wait marker: $remote_wait_marker" \ - "agentic-engineer must forbid foreground remote waits" "$d" + "canonical contiguous contract" "$d" done d=$(fresh); make_desired_state "$d" alpha From 68d30b2bfae9df6b47c3fc9394efe877f668125f Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 3 Aug 2026 20:24:55 +0200 Subject: [PATCH 08/14] fix(agentic-engineering): normalize entrypoint line endings --- scripts/validate-manifests.sh | 8 ++++++-- scripts/validate-manifests.test.sh | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index 7a3a087..abbc492 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -30,9 +30,9 @@ README="README.md" sha256_file() { if command -v sha256sum > /dev/null 2>&1; then - sha256sum "$1" | awk '{ print $1 }' + tr -d '\r' < "$1" | sha256sum | awk '{ print $1 }' else - shasum -a 256 "$1" | awk '{ print $1 }' + tr -d '\r' < "$1" | shasum -a 256 | awk '{ print $1 }' fi } @@ -534,6 +534,10 @@ validate_desired_state_resources() { resource_failed=1 fi + # This is a content-integrity and review gate, not a natural-language semantic parser: + # the canonical block pins the required rule, while the digest makes every other + # entrypoint edit visible as a coordinated desired-state change. Ignore checkout-only + # CRLF conversion so the committed LF digest remains portable. entrypoint_sha256=$(jq -r '.spec.source.entrypointSha256 // ""' "$resource") if ! printf '%s\n' "$entrypoint_sha256" | grep -Eq '^[a-f0-9]{64}$'; then echo "::error::$resource: entrypointSha256 must be a lowercase SHA-256 digest" diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index 403195a..565841b 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -19,9 +19,9 @@ fail=0 sha256_file() { if command -v sha256sum > /dev/null 2>&1; then - sha256sum "$1" | awk '{ print $1 }' + tr -d '\r' < "$1" | sha256sum | awk '{ print $1 }' else - shasum -a 256 "$1" | awk '{ print $1 }' + tr -d '\r' < "$1" | shasum -a 256 | awk '{ print $1 }' fi } @@ -1023,7 +1023,13 @@ jq '.spec.source.entrypointSha256 = "not-a-digest"' \ check_fail "Agentic Engineer rejects a malformed entrypoint digest" \ "entrypointSha256 must be a lowercase SHA-256 digest" "$d" -for remote_wait_contradiction in \ +d=$(fresh); make_desired_state "$d" alpha +awk '{ printf "%s\r\n", $0 }' \ + "$d/plugins/alpha/agents/agentic-engineer.agent.md" > "$d/tmp" \ + && mv "$d/tmp" "$d/plugins/alpha/agents/agentic-engineer.agent.md" +check_pass "Agentic Engineer entrypoint digest normalizes CRLF checkouts" "$d" + +for unreviewed_entrypoint_drift in \ 'Foreground CI polling is allowed after the canonical rule.' \ 'An additional detached watcher may be armed after the canonical rule.' \ 'The next scheduled tick handoff is optional after the canonical rule.' \ @@ -1032,9 +1038,9 @@ for remote_wait_contradiction in \ 'Review completion is watched after the canonical rule.' \ 'Await CI completion after the canonical rule.'; do d=$(fresh); make_desired_state "$d" alpha - printf '\n%s\n' "$remote_wait_contradiction" \ + printf '\n%s\n' "$unreviewed_entrypoint_drift" \ >> "$d/plugins/alpha/agents/agentic-engineer.agent.md" - check_fail "Agentic Engineer rejects contradictory remote wait rule: $remote_wait_contradiction" \ + check_fail "Agentic Engineer detects unreviewed entrypoint drift: $unreviewed_entrypoint_drift" \ "entrypoint digest must match the bundled agent" "$d" done @@ -1042,7 +1048,7 @@ d=$(fresh); make_desired_state "$d" alpha remote_wait_filler=$(printf 'details %.0s' {1..30}) printf '\nWait %s for CI completion after the canonical rule.\n' "$remote_wait_filler" \ >> "$d/plugins/alpha/agents/agentic-engineer.agent.md" -check_fail "Agentic Engineer rejects remote wait synonyms beyond an arbitrary distance" \ +check_fail "Agentic Engineer detects long-form unreviewed entrypoint drift" \ "entrypoint digest must match the bundled agent" "$d" d=$(fresh); make_desired_state "$d" alpha From 7e51ec56178f4c9bdd95a26ea2a547d550691aa7 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 3 Aug 2026 20:33:26 +0200 Subject: [PATCH 09/14] fix(agentic-engineering): preserve lone carriage returns --- scripts/validate-manifests.sh | 7 ++++--- scripts/validate-manifests.test.sh | 9 +++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index abbc492..88e04d6 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -30,9 +30,9 @@ README="README.md" sha256_file() { if command -v sha256sum > /dev/null 2>&1; then - tr -d '\r' < "$1" | sha256sum | awk '{ print $1 }' + jq -Rrsj 'gsub("\r\n"; "\n")' "$1" | sha256sum | awk '{ print $1 }' else - tr -d '\r' < "$1" | shasum -a 256 | awk '{ print $1 }' + jq -Rrsj 'gsub("\r\n"; "\n")' "$1" | shasum -a 256 | awk '{ print $1 }' fi } @@ -537,7 +537,8 @@ validate_desired_state_resources() { # This is a content-integrity and review gate, not a natural-language semantic parser: # the canonical block pins the required rule, while the digest makes every other # entrypoint edit visible as a coordinated desired-state change. Ignore checkout-only - # CRLF conversion so the committed LF digest remains portable. + # CRLF conversion so the committed LF digest remains portable without hiding + # a content-changing lone carriage return. entrypoint_sha256=$(jq -r '.spec.source.entrypointSha256 // ""' "$resource") if ! printf '%s\n' "$entrypoint_sha256" | grep -Eq '^[a-f0-9]{64}$'; then echo "::error::$resource: entrypointSha256 must be a lowercase SHA-256 digest" diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index 565841b..9da3c8e 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -19,9 +19,9 @@ fail=0 sha256_file() { if command -v sha256sum > /dev/null 2>&1; then - tr -d '\r' < "$1" | sha256sum | awk '{ print $1 }' + jq -Rrsj 'gsub("\r\n"; "\n")' "$1" | sha256sum | awk '{ print $1 }' else - tr -d '\r' < "$1" | shasum -a 256 | awk '{ print $1 }' + jq -Rrsj 'gsub("\r\n"; "\n")' "$1" | shasum -a 256 | awk '{ print $1 }' fi } @@ -1029,6 +1029,11 @@ awk '{ printf "%s\r\n", $0 }' \ && mv "$d/tmp" "$d/plugins/alpha/agents/agentic-engineer.agent.md" check_pass "Agentic Engineer entrypoint digest normalizes CRLF checkouts" "$d" +d=$(fresh); make_desired_state "$d" alpha +printf '\r' >> "$d/plugins/alpha/agents/agentic-engineer.agent.md" +check_fail "Agentic Engineer entrypoint digest preserves a lone carriage return" \ + "entrypoint digest must match the bundled agent" "$d" + for unreviewed_entrypoint_drift in \ 'Foreground CI polling is allowed after the canonical rule.' \ 'An additional detached watcher may be armed after the canonical rule.' \ From 6c1ec3aa9d540a5ea293a48a6dc563ba69ef2712 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 3 Aug 2026 20:46:05 +0200 Subject: [PATCH 10/14] docs(agentic-engineering): document integrity helpers --- scripts/validate-manifests.sh | 1 + scripts/validate-manifests.test.sh | 2 ++ 2 files changed, 3 insertions(+) diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index 88e04d6..4bdd083 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -28,6 +28,7 @@ CLAUDE_MANIFEST=".claude-plugin/marketplace.json" RENAME_HISTORY="scripts/marketplace-rename-history.json" README="README.md" +# Hash entrypoint text after normalizing checkout-only CRLF pairs to committed LF bytes. sha256_file() { if command -v sha256sum > /dev/null 2>&1; then jq -Rrsj 'gsub("\r\n"; "\n")' "$1" | sha256sum | awk '{ print $1 }' diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index 9da3c8e..3316af6 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -17,6 +17,7 @@ PUBLISHED_RENAMES='{"automated-ai-engineer":"agentic-engineering"}' pass=0 fail=0 +# Hash fixture entrypoints with the same line-ending semantics as the production guard. sha256_file() { if command -v sha256sum > /dev/null 2>&1; then jq -Rrsj 'gsub("\r\n"; "\n")' "$1" | sha256sum | awk '{ print $1 }' @@ -25,6 +26,7 @@ sha256_file() { fi } +# Refresh a fixture's declared digest after intentionally changing its canonical contract. sync_entrypoint_digest() { local root="$1" name="$2" resource digest resource="$root/plugins/$name/resources/provider-neutral.desired-state.json" From 464e7b9a7231073415c696be578e050f4a6bbf5b Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 3 Aug 2026 21:03:31 +0200 Subject: [PATCH 11/14] fix(agentic-engineering): preserve entrypoint bytes --- scripts/validate-manifests.sh | 8 +++++--- scripts/validate-manifests.test.sh | 17 ++++++++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index 4bdd083..0cec3f8 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -28,12 +28,14 @@ CLAUDE_MANIFEST=".claude-plugin/marketplace.json" RENAME_HISTORY="scripts/marketplace-rename-history.json" README="README.md" -# Hash entrypoint text after normalizing checkout-only CRLF pairs to committed LF bytes. +# Hash entrypoint bytes after normalizing checkout-only CRLF pairs to committed LF bytes. +# Perl stays in byte mode under the C locale, preserving invalid UTF-8, NULs, lone CRs, +# and a missing final newline instead of decoding or reconstructing the file as text. sha256_file() { if command -v sha256sum > /dev/null 2>&1; then - jq -Rrsj 'gsub("\r\n"; "\n")' "$1" | sha256sum | awk '{ print $1 }' + LC_ALL=C perl -pe 's/\r\n/\n/g' "$1" | sha256sum | awk '{ print $1 }' else - jq -Rrsj 'gsub("\r\n"; "\n")' "$1" | shasum -a 256 | awk '{ print $1 }' + LC_ALL=C perl -pe 's/\r\n/\n/g' "$1" | shasum -a 256 | awk '{ print $1 }' fi } diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index 3316af6..8f62203 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -17,12 +17,12 @@ PUBLISHED_RENAMES='{"automated-ai-engineer":"agentic-engineering"}' pass=0 fail=0 -# Hash fixture entrypoints with the same line-ending semantics as the production guard. +# Hash fixture entrypoint bytes with the same byte-preserving CRLF semantics as the guard. sha256_file() { if command -v sha256sum > /dev/null 2>&1; then - jq -Rrsj 'gsub("\r\n"; "\n")' "$1" | sha256sum | awk '{ print $1 }' + LC_ALL=C perl -pe 's/\r\n/\n/g' "$1" | sha256sum | awk '{ print $1 }' else - jq -Rrsj 'gsub("\r\n"; "\n")' "$1" | shasum -a 256 | awk '{ print $1 }' + LC_ALL=C perl -pe 's/\r\n/\n/g' "$1" | shasum -a 256 | awk '{ print $1 }' fi } @@ -1036,6 +1036,17 @@ printf '\r' >> "$d/plugins/alpha/agents/agentic-engineer.agent.md" check_fail "Agentic Engineer entrypoint digest preserves a lone carriage return" \ "entrypoint digest must match the bundled agent" "$d" +d=$(fresh); make_desired_state "$d" alpha +cp "$d/plugins/alpha/agents/agentic-engineer.agent.md" "$d/other-entrypoint.agent.md" +printf '\200' >> "$d/plugins/alpha/agents/agentic-engineer.agent.md" +printf '\201' >> "$d/other-entrypoint.agent.md" +other_entrypoint_sha256=$(sha256_file "$d/other-entrypoint.agent.md") +jq --arg digest "$other_entrypoint_sha256" '.spec.source.entrypointSha256 = $digest' \ + "$d/plugins/alpha/resources/provider-neutral.desired-state.json" > "$d/tmp" \ + && mv "$d/tmp" "$d/plugins/alpha/resources/provider-neutral.desired-state.json" +LC_ALL=C check_fail "Agentic Engineer entrypoint digest preserves invalid UTF-8 bytes" \ + "entrypoint digest must match the bundled agent" "$d" + for unreviewed_entrypoint_drift in \ 'Foreground CI polling is allowed after the canonical rule.' \ 'An additional detached watcher may be armed after the canonical rule.' \ From cd6d4434d38d05ab25609058f86c6e8d18f386a4 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 3 Aug 2026 21:15:36 +0200 Subject: [PATCH 12/14] fix(agentic-engineering): force raw digest IO --- scripts/validate-manifests.sh | 9 +++++---- scripts/validate-manifests.test.sh | 7 +++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index 0cec3f8..fe2d96a 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -29,13 +29,14 @@ RENAME_HISTORY="scripts/marketplace-rename-history.json" README="README.md" # Hash entrypoint bytes after normalizing checkout-only CRLF pairs to committed LF bytes. -# Perl stays in byte mode under the C locale, preserving invalid UTF-8, NULs, lone CRs, -# and a missing final newline instead of decoding or reconstructing the file as text. +# Perl's -C0 forces byte I/O even when PERL_UNICODE is inherited; with the C locale this +# preserves invalid UTF-8, NULs, lone CRs, and a missing final newline instead of decoding +# or reconstructing the file as text. sha256_file() { if command -v sha256sum > /dev/null 2>&1; then - LC_ALL=C perl -pe 's/\r\n/\n/g' "$1" | sha256sum | awk '{ print $1 }' + LC_ALL=C perl -C0 -pe 's/\r\n/\n/g' "$1" | sha256sum | awk '{ print $1 }' else - LC_ALL=C perl -pe 's/\r\n/\n/g' "$1" | shasum -a 256 | awk '{ print $1 }' + LC_ALL=C perl -C0 -pe 's/\r\n/\n/g' "$1" | shasum -a 256 | awk '{ print $1 }' fi } diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index 8f62203..61379f2 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -20,9 +20,9 @@ fail=0 # Hash fixture entrypoint bytes with the same byte-preserving CRLF semantics as the guard. sha256_file() { if command -v sha256sum > /dev/null 2>&1; then - LC_ALL=C perl -pe 's/\r\n/\n/g' "$1" | sha256sum | awk '{ print $1 }' + LC_ALL=C perl -C0 -pe 's/\r\n/\n/g' "$1" | sha256sum | awk '{ print $1 }' else - LC_ALL=C perl -pe 's/\r\n/\n/g' "$1" | shasum -a 256 | awk '{ print $1 }' + LC_ALL=C perl -C0 -pe 's/\r\n/\n/g' "$1" | shasum -a 256 | awk '{ print $1 }' fi } @@ -1031,6 +1031,9 @@ awk '{ printf "%s\r\n", $0 }' \ && mv "$d/tmp" "$d/plugins/alpha/agents/agentic-engineer.agent.md" check_pass "Agentic Engineer entrypoint digest normalizes CRLF checkouts" "$d" +d=$(fresh); make_desired_state "$d" alpha +PERL_UNICODE=S check_pass "Agentic Engineer entrypoint digest ignores inherited Unicode I/O" "$d" + d=$(fresh); make_desired_state "$d" alpha printf '\r' >> "$d/plugins/alpha/agents/agentic-engineer.agent.md" check_fail "Agentic Engineer entrypoint digest preserves a lone carriage return" \ From 9c8b31f8b63167af7a2666c5bdb84e632cff94ab Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 3 Aug 2026 21:27:23 +0200 Subject: [PATCH 13/14] fix(agentic-engineering): isolate digest environment --- scripts/validate-manifests.sh | 14 +++++++++----- scripts/validate-manifests.test.sh | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index fe2d96a..20ba000 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -29,14 +29,18 @@ RENAME_HISTORY="scripts/marketplace-rename-history.json" README="README.md" # Hash entrypoint bytes after normalizing checkout-only CRLF pairs to committed LF bytes. -# Perl's -C0 forces byte I/O even when PERL_UNICODE is inherited; with the C locale this -# preserves invalid UTF-8, NULs, lone CRs, and a missing final newline instead of decoding -# or reconstructing the file as text. +# Clear inherited Perl I/O controls and set both stream handles to raw bytes explicitly. +# This preserves invalid UTF-8, NULs, lone CRs, and a missing final newline instead of +# decoding or reconstructing the file as text. sha256_file() { if command -v sha256sum > /dev/null 2>&1; then - LC_ALL=C perl -C0 -pe 's/\r\n/\n/g' "$1" | sha256sum | awk '{ print $1 }' + LC_ALL=C PERL5OPT= PERL_UNICODE= PERLIO= perl -C0 -pe \ + 'BEGIN { binmode STDIN, ":raw"; binmode STDOUT, ":raw" } s/\r\n/\n/g' \ + < "$1" | sha256sum | awk '{ print $1 }' else - LC_ALL=C perl -C0 -pe 's/\r\n/\n/g' "$1" | shasum -a 256 | awk '{ print $1 }' + LC_ALL=C PERL5OPT= PERL_UNICODE= PERLIO= perl -C0 -pe \ + 'BEGIN { binmode STDIN, ":raw"; binmode STDOUT, ":raw" } s/\r\n/\n/g' \ + < "$1" | shasum -a 256 | awk '{ print $1 }' fi } diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index 61379f2..9f22a30 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -20,9 +20,13 @@ fail=0 # Hash fixture entrypoint bytes with the same byte-preserving CRLF semantics as the guard. sha256_file() { if command -v sha256sum > /dev/null 2>&1; then - LC_ALL=C perl -C0 -pe 's/\r\n/\n/g' "$1" | sha256sum | awk '{ print $1 }' + LC_ALL=C PERL5OPT= PERL_UNICODE= PERLIO= perl -C0 -pe \ + 'BEGIN { binmode STDIN, ":raw"; binmode STDOUT, ":raw" } s/\r\n/\n/g' \ + < "$1" | sha256sum | awk '{ print $1 }' else - LC_ALL=C perl -C0 -pe 's/\r\n/\n/g' "$1" | shasum -a 256 | awk '{ print $1 }' + LC_ALL=C PERL5OPT= PERL_UNICODE= PERLIO= perl -C0 -pe \ + 'BEGIN { binmode STDIN, ":raw"; binmode STDOUT, ":raw" } s/\r\n/\n/g' \ + < "$1" | shasum -a 256 | awk '{ print $1 }' fi } @@ -1034,6 +1038,12 @@ check_pass "Agentic Engineer entrypoint digest normalizes CRLF checkouts" "$d" d=$(fresh); make_desired_state "$d" alpha PERL_UNICODE=S check_pass "Agentic Engineer entrypoint digest ignores inherited Unicode I/O" "$d" +d=$(fresh); make_desired_state "$d" alpha +PERL5OPT=-CS check_pass "Agentic Engineer entrypoint digest ignores inherited Perl options" "$d" + +d=$(fresh); make_desired_state "$d" alpha +PERLIO=:crlf check_pass "Agentic Engineer entrypoint digest ignores inherited Perl layers" "$d" + d=$(fresh); make_desired_state "$d" alpha printf '\r' >> "$d/plugins/alpha/agents/agentic-engineer.agent.md" check_fail "Agentic Engineer entrypoint digest preserves a lone carriage return" \ From e867587f29ff43a8a8a620cbe2e9acb1cb77c293 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 3 Aug 2026 21:34:34 +0200 Subject: [PATCH 14/14] fix(agentic-engineering): satisfy shellcheck --- scripts/validate-manifests.sh | 4 ++-- scripts/validate-manifests.test.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index 20ba000..a1d1fec 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -34,11 +34,11 @@ README="README.md" # decoding or reconstructing the file as text. sha256_file() { if command -v sha256sum > /dev/null 2>&1; then - LC_ALL=C PERL5OPT= PERL_UNICODE= PERLIO= perl -C0 -pe \ + LC_ALL=C PERL5OPT='' PERL_UNICODE='' PERLIO='' perl -C0 -pe \ 'BEGIN { binmode STDIN, ":raw"; binmode STDOUT, ":raw" } s/\r\n/\n/g' \ < "$1" | sha256sum | awk '{ print $1 }' else - LC_ALL=C PERL5OPT= PERL_UNICODE= PERLIO= perl -C0 -pe \ + LC_ALL=C PERL5OPT='' PERL_UNICODE='' PERLIO='' perl -C0 -pe \ 'BEGIN { binmode STDIN, ":raw"; binmode STDOUT, ":raw" } s/\r\n/\n/g' \ < "$1" | shasum -a 256 | awk '{ print $1 }' fi diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index 9f22a30..800b002 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -20,11 +20,11 @@ fail=0 # Hash fixture entrypoint bytes with the same byte-preserving CRLF semantics as the guard. sha256_file() { if command -v sha256sum > /dev/null 2>&1; then - LC_ALL=C PERL5OPT= PERL_UNICODE= PERLIO= perl -C0 -pe \ + LC_ALL=C PERL5OPT='' PERL_UNICODE='' PERLIO='' perl -C0 -pe \ 'BEGIN { binmode STDIN, ":raw"; binmode STDOUT, ":raw" } s/\r\n/\n/g' \ < "$1" | sha256sum | awk '{ print $1 }' else - LC_ALL=C PERL5OPT= PERL_UNICODE= PERLIO= perl -C0 -pe \ + LC_ALL=C PERL5OPT='' PERL_UNICODE='' PERLIO='' perl -C0 -pe \ 'BEGIN { binmode STDIN, ":raw"; binmode STDOUT, ":raw" } s/\r\n/\n/g' \ < "$1" | shasum -a 256 | awk '{ print $1 }' fi