From 20162666aac8ad44d5649e551de07b28dccdb745 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Sun, 6 Sep 2026 17:21:21 +0200 Subject: [PATCH] feat(agentic-engineering)!: require explicit spend enablement Ship a required default-off boolean, bind onboarding and dispatch to the same effective desired state, and preserve all Spend contract prerequisites. --- .claude-plugin/marketplace.json | 2 +- .github/plugin/marketplace.json | 2 +- docs/adr/0007-explicit-spend-enablement.md | 36 ++++++++++++ docs/resources.md | 15 +++-- .../.claude-plugin/plugin.json | 2 +- plugins/agentic-engineering/README.md | 51 ++++++++++++++--- .../agents/agentic-engineer.agent.md | 8 ++- plugins/agentic-engineering/plugin.json | 2 +- .../provider-neutral.desired-state.json | 7 ++- scripts/validate-manifests.sh | 31 +++++++++- scripts/validate-manifests.test.sh | 57 ++++++++++++++++++- 11 files changed, 186 insertions(+), 27 deletions(-) create mode 100644 docs/adr/0007-explicit-spend-enablement.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 95ba08f..4d8e52f 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.4.29", + "version": "5.0.0", "source": "./plugins/agentic-engineering" }, { diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index 95ba08f..4d8e52f 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.4.29", + "version": "5.0.0", "source": "./plugins/agentic-engineering" }, { diff --git a/docs/adr/0007-explicit-spend-enablement.md b/docs/adr/0007-explicit-spend-enablement.md new file mode 100644 index 0000000..21cb432 --- /dev/null +++ b/docs/adr/0007-explicit-spend-enablement.md @@ -0,0 +1,36 @@ +# ADR 0007: Explicit spend enablement + +## Context + +The engineer's Spend contract describes deployment facts and authority boundaries. Treating the +presence of those facts as activation makes a complete onboarding document enable cost work without +a distinct maintainer choice. The shipped desired state needs an explicit disabled state that can +be validated independently of contract completeness. + +## Decision + +The desired-state schema requires the boolean +`spec.roles["agentic-engineer"].spendStewardshipEnabled`, shipped as `false`. Only the maintainer may +opt in by setting literal `true`; a resolving Spend contract remains a separate prerequisite. +Missing, malformed, or unreadable enablement disables spend analysis and decisions while ordinary +operate and advance engineering continues. + +The consumer declares one full effective desired-state document in its Spend contract. If no +effective document is declared, use the shipped disabled default. The engineer resolves that source and value once +during preflight and keeps them fixed for the run. Consumers that retain an exact upstream mirror +declare a separate full effective document through native configuration. The plugin defines no +partial-override merge or search through arbitrary settings. + +Onboarding preserves the configured value and reports its source and unresolved prerequisites. Thin +scheduler pointers refer to the same field and the engineer's canonical policy. A flag does not +relax the private-channel, protected-outcomes, or money-moving boundaries. + +## Consequences + +Version 5 is a breaking desired-state change: version 4 documents must add the field and refresh +their entrypoint digest and scheduler pointers. Existing spend deployments need explicit opt-in; +contract presence and historical activity do not supply it. Consumer adoption follows the merged +plugin revision, while runtime-managed caches remain the responsibility of native runtime controls. + +The manifest validator accepts both boolean states and rejects missing or malformed values. Tests +also pin the actual shipped default and require both onboarding and dispatch to consume the field. diff --git a/docs/resources.md b/docs/resources.md index 4710d3e..eb54d38 100644 --- a/docs/resources.md +++ b/docs/resources.md @@ -12,16 +12,18 @@ trust, cadence, memory, and maintainer-channel configuration. It also requires t unsupported native capabilities instead of silently weakening the deployment. The manifest carries separate thin schedule prompts for the Agentic Engineer and the Agent Improver; each resolves its cadence and deployment facts from the canonical consumer instructions. Spend stewardship has no -schedule of its own — it runs inside the engineer's loop when the consumer declares a `Spend contract` -section. -Existing installations must complete **all three** migrations, in order, before their next scheduled +schedule of its own. It runs inside the engineer's loop only after explicit maintainer opt-in through +`spec.roles["agentic-engineer"].spendStewardshipEnabled` and a resolving `Spend contract`. +Existing installations must complete **all four** migrations, in order, before their next scheduled run — the [version 2 checklist](../plugins/agentic-engineering/README.md#migrating-from-automated-ai-engineer) (plugin identity), **then** the [version 3 checklist](../plugins/agentic-engineering/README.md#migrating-to-version-3) (retire the `finops-engineer` schedule and adopt the `Spend contract` section), **then** the [version 4 checklist](../plugins/agentic-engineering/README.md#migrating-to-version-4) -(the `automated-ai-engineer` → `agentic-engineer` entrypoint rename). Stopping early would resume +(the `automated-ai-engineer` → `agentic-engineer` entrypoint rename), **then** the +[version 5 checklist](../plugins/agentic-engineering/README.md#migrating-to-version-5) +(explicit default-off spend enablement). Stopping early would resume unattended writes with the retired FinOps schedule still armed, or with a schedule pointing at an entrypoint that no longer resolves. @@ -102,5 +104,6 @@ improves the engineer itself from measured evidence) — alongside its engineeri [ADR 0005](../docs/adr/0005-merge-spend-stewardship-into-the-engineer.md)). Same delivery rules; the consuming deployment must define the five contract sections (Portfolio map, Trust gate, Cadence, Memory, Maintainer channels) in its `AGENTS.md` — plus **Agent definition locations** and -**Authority model** if it enables `agent-improver`, and **Spend contract** if it wants the engineer to -steward spend (see the [plugin README](../plugins/agentic-engineering/README.md)). +**Authority model** if it enables `agent-improver`, and **Spend contract** plus explicit maintainer +opt-in if it wants the engineer to steward spend (see the +[plugin README](../plugins/agentic-engineering/README.md)). diff --git a/plugins/agentic-engineering/.claude-plugin/plugin.json b/plugins/agentic-engineering/.claude-plugin/plugin.json index 8c326f1..600ee60 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.4.29", + "version": "5.0.0", "author": { "name": "devantler-tech", "url": "https://github.com/devantler-tech" diff --git a/plugins/agentic-engineering/README.md b/plugins/agentic-engineering/README.md index f02c259..67ad429 100644 --- a/plugins/agentic-engineering/README.md +++ b/plugins/agentic-engineering/README.md @@ -18,6 +18,36 @@ separate FinOps role and schedule. See Version 4 renames that entrypoint from `automated-ai-engineer` to `agentic-engineer`. See [ADR 0006](../../docs/adr/0006-rename-agentic-engineer-entrypoint.md). +Version 5 requires explicit, default-off spend enablement. See +[ADR 0007](../../docs/adr/0007-explicit-spend-enablement.md). + +## Migrating to version 5 + +The desired-state schema now requires the boolean +`spec.roles["agentic-engineer"].spendStewardshipEnabled`. Start with `false`; the presence of a +complete Spend contract does not opt a deployment in. Older documents fail validation, and the +engineer treats missing or malformed enablement as disabled while continuing ordinary engineering. + +1. Refresh the complete desired-state document from the reviewed plugin revision, including its + entrypoint digest and scheduler pointers. Keep the shipped flag `false` unless the maintainer + explicitly enables spend stewardship. +2. Declare the path to one full effective desired-state JSON document in the consumer's + `AGENTS.md` **Spend contract**. That document supplies the flag for every lane. If the consumer + keeps a byte-identical upstream mirror, retain it and declare a separate full effective document + through its native configuration; do not edit the mirror or invent a partial-override merge. +3. Reconcile the native scheduler from the updated pointers and verify preflight reports the + effective document, boolean value, and any unresolved Spend contract prerequisites. Keep a + disabled deployment disabled during reconciliation. + +Without a declared document, preflight uses the shipped `false` default. A declared document that +cannot be read or validated also disables spend and reports the gap. Resolve the document and flag +once per run; never search unrelated settings for an enabling value or switch sources mid-run. + +Only the maintainer may set the flag to literal `true`. This permits spend analysis and decisions +only when the Spend contract also resolves; it does not bypass the private decision channel, +protected-outcomes floor, or authority boundaries. Setting it back to `false` disables the cost +dimension on the next preflight. There is no additional spend schedule. + ## Migrating to version 4 Version 4 renames the primary engineer's agent entrypoint from `automated-ai-engineer` to @@ -60,8 +90,8 @@ required before the next scheduled run: rather than silently deploying two writers over one concern. A consumer that keeps its FinOps definition as a separate agent is not broken by this release — but it -is no longer the shape this plugin describes, and the engineer will not perform spend work until a -`Spend contract` section resolves. +is no longer the shape this plugin describes. Current spend enablement follows the explicit flag and +resolving `Spend contract` described in [*Migrating to version 5*](#migrating-to-version-5). ## Migrating from `automated-ai-engineer` @@ -82,9 +112,10 @@ complete the plugin-name change manually before the next scheduled run: 3. Copy the [provider-neutral desired state](resources/provider-neutral.desired-state.json) into the consumer workspace and reconcile its native agents and schedules. Preserve the consumer's canonical `AGENTS.md`; do not copy its organization-specific facts into this plugin. -4. Before re-enabling unattended writes, verify that the installed plugin reports version `4.0.0` or - later — the current major, so **[*Migrating to version 3*](#migrating-to-version-3) and - [*Migrating to version 4*](#migrating-to-version-4) must both be complete too**; a stop at `2.0.0` +4. Before re-enabling unattended writes, verify that the installed plugin reports version `5.0.0` or + later, so **[*Migrating to version 3*](#migrating-to-version-3), + [*Migrating to version 4*](#migrating-to-version-4), and + [*Migrating to version 5*](#migrating-to-version-5) must all be complete too**; a stop at `2.0.0` would resume writes with the retired FinOps schedule still armed, and a stop at `3.0.0` with a schedule pointing at an entrypoint that no longer resolves — and that it exposes `agentic-engineer`, `portfolio-surveyor`, and `agent-improver`, and that every @@ -99,8 +130,8 @@ the read-only preflight loads the new namespace successfully. Three agents: - **`agentic-engineer`** — the actor that runs the survey → select → act → report loop, operates - the portfolio, advances the oldest actionable issue, and — when the consumer declares a **Spend - contract** — stewards the portfolio's running cost in the same loop. + the portfolio, advances the oldest actionable issue, and — after explicit maintainer opt-in and a + resolving **Spend contract** — stewards the portfolio's running cost in the same loop. - **`portfolio-surveyor`** — a delegated, read-only agent that returns a compact current-state digest. - **`agent-improver`** — a meta-engineer that evaluates deployed instances and improves their shared definition from evidence. @@ -175,10 +206,12 @@ Enabling `agent-improver` adds two required sections: - **Authority model** — the separate boundaries for tightening and loosening prose and enforcement guardrails. -Enabling the engineer's spend stewardship additionally requires **Spend contract**, which names the +Enabling the engineer's spend stewardship requires literal `true` in +`spec.roles["agentic-engineer"].spendStewardshipEnabled` and **Spend contract**, which names the +single effective desired-state document, cost evidence sources and which are actually wired, the protected-outcomes floor and who may change it, the run procedure for a cost pass, the private channel a financial decision goes to, and the -cadence a cost pass runs on. Absent, the engineer runs normally with the **cost dimension failed +cadence a cost pass runs on. Disabled or unresolved, the engineer runs normally with the **cost dimension failed closed** — it does no spend analysis rather than guessing a floor, a price, or a channel. The `Memory` section must also name the scorecard and open verification-hypothesis store used by the diff --git a/plugins/agentic-engineering/agents/agentic-engineer.agent.md b/plugins/agentic-engineering/agents/agentic-engineer.agent.md index b8f5d5a..1cf5d32 100644 --- a/plugins/agentic-engineering/agents/agentic-engineer.agent.md +++ b/plugins/agentic-engineering/agents/agentic-engineer.agent.md @@ -12,7 +12,7 @@ description: >- protected outcome) — everything shipped as draft PRs self-promoted on genuine readiness, and never a money-moving act. Requires the consuming repository's AGENTS.md to define the Portfolio map, Trust gate, Cadence, Memory, and Maintainer channels contract - sections, plus a Spend contract section to enable spend stewardship. Use on a + sections, plus explicit opt-in and a Spend contract for spend stewardship. Use on a schedule or on request whenever a portfolio of repositories should be maintained or advanced. skills: @@ -48,7 +48,7 @@ You are parameterized, not hard-coded: the consuming repository's canonical inst maintainer's **interactive-session marker** (the literal a PR body carries when it came from the maintainer's own hand-driven session, which the surveyor reads to tell that PR from your own). -One further section is **conditionally** required, and it is what turns spend stewardship on: +One further section is **conditionally** required when spend stewardship is explicitly enabled: - **Spend contract** — the deployment's money facts: where cost evidence comes from and which of those sources are actually wired, the **protected-outcomes floor** (the declared list of outcomes @@ -164,7 +164,9 @@ instead. ## Spend stewardship — the money side of the same portfolio -Enabled only when the **Spend contract** section resolves. Running cost is a product property like +**Spend stewardship is explicitly opt-in.** During preflight, read `spec.roles["agentic-engineer"].spendStewardshipEnabled` from the single effective desired-state document declared in the consumer **Spend contract**. If no effective document is declared, use the shipped `false` default. An unreadable or invalid declared document, a missing field, or a non-boolean value disables spend and reports the gap. Only literal `true` plus a resolving **Spend contract** enables spend analysis and decisions; it bypasses no private-channel, protected-outcomes, or authority requirement. Only the maintainer may opt in. Never infer enablement from contract presence or past activity, and never change the source or value during a run. While disabled, continue ordinary operate and advance engineering. + +Running cost is a product property like performance or security, so you own it in the same loop, with the same evidence discipline — and you never own the act of spending. diff --git a/plugins/agentic-engineering/plugin.json b/plugins/agentic-engineering/plugin.json index 8c326f1..600ee60 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.4.29", + "version": "5.0.0", "author": { "name": "devantler-tech", "url": "https://github.com/devantler-tech" diff --git a/plugins/agentic-engineering/resources/provider-neutral.desired-state.json b/plugins/agentic-engineering/resources/provider-neutral.desired-state.json index d1602a3..4bc8987 100644 --- a/plugins/agentic-engineering/resources/provider-neutral.desired-state.json +++ b/plugins/agentic-engineering/resources/provider-neutral.desired-state.json @@ -27,7 +27,7 @@ "executable": true } ], - "entrypointSha256": "9d45e8ae019f75944d70c78f8c82481d2b9af798792c4c008cfe61fa494b8441", + "entrypointSha256": "a3d01aa54b2af445aa4a6d07eb406d401824be7db08d5ec4c2f914db7fc9bf39", "updatePolicy": "latest-reviewed-default-branch", "providerPolicy": "neutral", "refreshTiming": "before-starting-each-run", @@ -55,6 +55,7 @@ "roles": { "agentic-engineer": { "enabled": true, + "spendStewardshipEnabled": false, "mode": "scheduled-and-on-demand" }, "portfolio-surveyor": { @@ -79,7 +80,7 @@ "schedules": { "agentic-engineer": { "definitionFrom": "plugin:agentic-engineering/agentic-engineer", - "bootstrapPrompt": "Load native persistent memory, read the consumer's canonical AGENTS.md, load the latest reviewed agentic-engineering plugin, and invoke its agentic-engineer entrypoint. Follow the consumer contract and fail closed on missing deployment facts, including spend stewardship when the Spend contract section does not resolve. Do not duplicate the role in this scheduler prompt." + "bootstrapPrompt": "Load native persistent memory, read the consumer's canonical AGENTS.md, load the latest reviewed agentic-engineering plugin, and invoke its agentic-engineer entrypoint. Resolve spec.roles[\"agentic-engineer\"].spendStewardshipEnabled during preflight using the entrypoint's explicit opt-in contract. Follow the consumer contract and fail closed on missing deployment facts. Do not duplicate the role in this scheduler prompt." }, "agent-improver": { "definitionFrom": "plugin:agentic-engineering/agent-improver", @@ -112,7 +113,7 @@ "steps": [ "Resolve the canonical consumer repository from the current workspace.", "Install or refresh agentic-engineering from the declared marketplace using the runtime's native mechanism. If full plugins are unsupported, load the canonical agent and skill files plus the referenced runtime assets from the declared source without creating divergent copies, including scripts/classify-default-branch-ci-runs.sh and scripts/forge-readonly-guard.sh for the portfolio-surveyor, plus scripts/surveyor-forge-readonly.sh only where the runtime presents a candidate command as structured JSON on standard input rather than as an argument, and verify every declared SHA-256 and executable requirement before use. forge-readonly-guard.sh --command is the portable contract; the wrapper is an input adapter for it, never a second policy. Register the guard at the surveyor's own pre-execution point only, directly or through that adapter, using the runtime's own agent-scoped mechanism; plugin-shipped agent definitions cannot carry that registration themselves, so it is always consumer-side wiring. A deployment that has not installed those assets or has not registered the guard for that agent fails closed (forge reads are QUERY-UNKNOWN).", - "Read AGENTS.md and verify every required consumer contract section. Enable agent-improver only when both additional sections are present, and enable the agentic-engineer's spend stewardship only when the Spend contract section resolves its cost evidence sources, protected-outcomes floor, run procedure, and private decision channel; otherwise run the engineer with the cost dimension failed closed.", + "Read AGENTS.md and verify every required consumer contract section. Enable agent-improver only when both additional sections are present. Preserve spec.roles[\"agentic-engineer\"].spendStewardshipEnabled from the single effective desired-state document declared in Spend contract, or the shipped false default when none is declared. Apply the engineer entrypoint's explicit opt-in contract before spend work; onboarding never infers or grants maintainer opt-in. Report the effective source, flag value, and unresolved prerequisites while continuing ordinary operate and advance engineering.", "Map the declared roles onto native agent capabilities, preserve portfolio-surveyor as read-only, and grant least privilege for each role. Export a disabling GH_TELEMETRY (0 or false) in the environment the surveyor's shell inherits: the read-only forge guard treats a missing value as unproven and refuses every command, including reads, so a runtime that enforces the guard without this variable leaves the surveyor unable to run any forge query at all. It cannot be supplied inside the command string, which the guard also refuses.", "Allocate a unique branch namespace for every deployed writer instance and record it in the consumer contract before enabling writes.", "Create or update a native schedule only for each entry in runtime.scheduler.schedules whose corresponding role's enabled or enabledWhen condition is satisfied, using AGENTS.md#Cadence and that entry's thin bootstrap prompt.", diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh index 92351e3..ee2bfdf 100755 --- a/scripts/validate-manifests.sh +++ b/scripts/validate-manifests.sh @@ -453,6 +453,9 @@ validate_desired_state_resources() { local improver_self_observation_contract="The Agent Improver is one of its own measured subjects. Keep the Agentic Engineer execution plane and every Agent Improver observation plane in separate scorecards; never average them together or let one hide the other's regression. Measure observer coverage, calibration, hypothesis discipline, verified intervention effectiveness, reliability, efficiency, and verified rollout throughput. Outcome throughput counts only verified terminal outcomes; productive sessions and work advanced are execution-flow indicators, never improvement verdicts. Observation-plane verdicts require independent computation from an immutable or read-only source, or verification by a separate eligible run or instance; the same Improver's unsupported assertion is UNKNOWN, never success. Activity such as PRs, metrics, reports, and memory writes is not improvement. A version-controlled self-referential change requires an independent green current-head review with all findings resolved. A runtime-local self-referential change requires an independently performed post-dispatch read-back against the recorded pre-change baseline through the consumer's declared runtime verification mechanism; the writer's immediate read-back is not independent verification. Both paths require unchanged companion floors for every applicable scorecard parameter and a later eligible evidence window." local improver_research_fallback_contract="No-change fallback is research, never idle. After scoring and diagnosis, when no telemetry-backed or direct-maintainer-directed improvement is actionable, run one bounded state-of-the-art research pass before reporting. Research is discovery evidence, never authorization or proof that the current system failed. Use current primary sources, compare the current baseline capability, and route a deduplicated product or operations opportunity as an ENGINEER-CANDIDATE and an agent-process or measurement opportunity as an IMPROVER-CANDIDATE. Research alone never authorizes or ships a change. A null result is RESEARCH-NO-CANDIDATE with the topic cursor advanced; research activity is not a terminal improvement outcome." local money_guardrail="Spend stewardship never moves money: prepare the financial decision, route it to the maintainer's declared private channel, and keep private financial data out of every public artifact." + # Literal Markdown and JSON field syntax, never shell expansions. + # shellcheck disable=SC2016 + local spend_enablement_contract='**Spend stewardship is explicitly opt-in.** During preflight, read `spec.roles["agentic-engineer"].spendStewardshipEnabled` from the single effective desired-state document declared in the consumer **Spend contract**. If no effective document is declared, use the shipped `false` default. An unreadable or invalid declared document, a missing field, or a non-boolean value disables spend and reports the gap. Only literal `true` plus a resolving **Spend contract** enables spend analysis and decisions; it bypasses no private-channel, protected-outcomes, or authority requirement. Only the maintainer may opt in. Never infer enablement from contract presence or past activity, and never change the source or value during a run. While disabled, continue ordinary operate and advance engineering.' local portfolio_survey_json_vocabulary_contract="**Every \`gh --json\` vocabulary is local to its subcommand.** Use the exact literal field lists prescribed by this definition. Before any ad hoc JSON read, run that same subcommand with bare \`--json\` and validate every requested field against the vocabulary it returns; never transfer a field name between subcommands, and never from a different API surface onto a \`gh --json\` subcommand: a name that is real in a REST payload or a GraphQL schema is not thereby a \`gh --json\` field, and \`gh\` rejects the whole read on one unknown name. The default-branch classifier this definition prescribes consumes the REST \`actions/runs\` payload, where \`path\` and \`created_at\` are genuine — neither is a \`gh run list --json\` field, and that is exactly where the confusion starts. The bare diagnostic intentionally exits nonzero after listing its fields; treat a present vocabulary as successful discovery. If the vocabulary is missing or malformed, or the validated read fails, mark the affected evidence \`QUERY-UNKNOWN\` and report the query error — never translate it to an empty result." local portfolio_survey_recovery_contract="**Mandatory-query recovery is bounded and resumable.** Process mandatory surfaces in deterministic batches of at most eight candidates. Treat every successful batch as an immutable checkpoint. On failure, partition only the failed batch into two deterministic contiguous halves (the first half gets the extra candidate when the count is odd), execute both halves, and recursively partition each failed half until only failed singleton candidates remain. Never re-run a successful half. Continue unaffected batches and mark only failed singleton candidates \`QUERY-UNKNOWN\`; never discard completed evidence or collapse it into portfolio-wide \`QUERY-UNKNOWN\`." local portfolio_survey_global_failure_contract="Known candidate-independent failures—exhausted query budget, invalid authentication, or a forge-wide transport failure—must fail the affected mandatory surface closed immediately without splitting. Partition only candidate-specific, shape-specific, or partial failures." @@ -857,7 +860,8 @@ validate_desired_state_resources() { | only_keys(["agentic-engineer", "portfolio-surveyor", "agent-improver"]) and has_keys(["agentic-engineer", "portfolio-surveyor", "agent-improver"])) and (.spec.roles["agentic-engineer"] - | only_keys(["enabled", "mode"]) and has_keys(["enabled", "mode"])) + | only_keys(["enabled", "mode", "spendStewardshipEnabled"]) + and has_keys(["enabled", "mode", "spendStewardshipEnabled"])) and (.spec.roles["portfolio-surveyor"] | only_keys(["enabled", "mode", "definitionSha256"]) and has_keys(["enabled", "mode", "definitionSha256"])) @@ -939,6 +943,20 @@ validate_desired_state_resources() { resource_failed=1 fi + if ! jq -e '.spec.roles["agentic-engineer"].spendStewardshipEnabled | type == "boolean"' \ + "$resource" > /dev/null; then + echo "::error::$resource: spendStewardshipEnabled must be a boolean" + failed=1 + resource_failed=1 + fi + + if [ ! -f "$plugin_dir/agents/$entrypoint.agent.md" ] \ + || ! tr '\n' ' ' < "$plugin_dir/agents/$entrypoint.agent.md" | grep -qF "$spend_enablement_contract"; then + echo "::error::$resource: agentic-engineer must enforce the explicit spend enablement contract" + failed=1 + resource_failed=1 + fi + # Spend stewardship is merged into the entrypoint rather than a separate role, so the # entrypoint itself must carry the mandate, its conditional contract section, and the # money boundary that used to live in a standalone FinOps definition. @@ -1227,6 +1245,17 @@ validate_desired_state_resources() { resource_failed=1 fi + if ! jq -e ' + def consumes_spend_flag: + contains("spec.roles[\"agentic-engineer\"].spendStewardshipEnabled"); + (.spec.runtime.scheduler.schedules["agentic-engineer"].bootstrapPrompt | consumes_spend_flag) + and any(.spec.onboarding.steps[]; consumes_spend_flag) + ' "$resource" > /dev/null; then + echo "::error::$resource: onboarding and engineer schedule must consume spendStewardshipEnabled" + failed=1 + resource_failed=1 + fi + if [ ! -f "$readme" ] || ! grep -qF "feature-flag mechanism" "$readme"; then echo "::error::$resource: $readme must document the required feature-flag mechanism" failed=1 diff --git a/scripts/validate-manifests.test.sh b/scripts/validate-manifests.test.sh index 1e2c82e..f43ffd1 100755 --- a/scripts/validate-manifests.test.sh +++ b/scripts/validate-manifests.test.sh @@ -626,6 +626,8 @@ If neither a callback nor a safe watcher is available, persist the pending targe ## Spend stewardship +**Spend stewardship is explicitly opt-in.** During preflight, read `spec.roles["agentic-engineer"].spendStewardshipEnabled` from the single effective desired-state document declared in the consumer **Spend contract**. If no effective document is declared, use the shipped `false` default. An unreadable or invalid declared document, a missing field, or a non-boolean value disables spend and reports the gap. Only literal `true` plus a resolving **Spend contract** enables spend analysis and decisions; it bypasses no private-channel, protected-outcomes, or authority requirement. Only the maintainer may opt in. Never infer enablement from contract presence or past activity, and never change the source or value during a run. While disabled, continue ordinary operate and advance engineering. + - **You never move money.** - Private financial data never reaches a public artifact. EOF @@ -749,6 +751,7 @@ EOF "roles": { "agentic-engineer": { "enabled": true, + "spendStewardshipEnabled": false, "mode": "scheduled-and-on-demand" }, "portfolio-surveyor": { @@ -773,7 +776,7 @@ EOF "schedules": { "agentic-engineer": { "definitionFrom": "plugin:$name/agentic-engineer", - "bootstrapPrompt": "Load native memory and AGENTS.md, then invoke the installed agentic-engineer entrypoint." + "bootstrapPrompt": "Load native memory and AGENTS.md, honor spec.roles[\"agentic-engineer\"].spendStewardshipEnabled from the effective desired state, then invoke the installed agentic-engineer entrypoint." }, "agent-improver": { "definitionFrom": "plugin:$name/agent-improver", @@ -806,6 +809,7 @@ EOF "steps": [ "Resolve the canonical consumer repository.", "Load the plugin and validate the consumer contract.", + "Preserve spec.roles[\"agentic-engineer\"].spendStewardshipEnabled from the effective desired state; never infer opt-in.", "Create a native schedule only for entries in runtime.scheduler.schedules whose corresponding roles are enabled by the consumer contract.", "Apply the runtime wiring without duplicating the role." ], @@ -1287,6 +1291,57 @@ check_fail "desired-state resource missing Agent Improver schedule prompt fails" # Spend stewardship is merged into the entrypoint, so a resurrected standalone FinOps role — the # exact drift this merge removes — must fail rather than quietly reintroduce a second writer. +if jq -e '.spec.roles["agentic-engineer"].spendStewardshipEnabled == false' \ + "$REPO_ROOT/plugins/agentic-engineering/resources/provider-neutral.desired-state.json" >/dev/null; then + echo " ✓ shipped spend stewardship defaults off"; pass=$((pass + 1)) +else + echo " ✗ shipped spend stewardship must default off"; fail=$((fail + 1)) +fi + +for enabled in false true; do + d=$(fresh); make_desired_state "$d" alpha + jq --argjson enabled "$enabled" '.spec.roles["agentic-engineer"].spendStewardshipEnabled = $enabled' \ + "$d/plugins/alpha/resources/provider-neutral.desired-state.json" > "$d/tmp" \ + && mv "$d/tmp" "$d/plugins/alpha/resources/provider-neutral.desired-state.json" + check_pass "spend enablement accepts boolean $enabled" "$d" +done + +for enabled in null '"true"' 1 '{}' '[]'; do + d=$(fresh); make_desired_state "$d" alpha + jq --argjson enabled "$enabled" '.spec.roles["agentic-engineer"].spendStewardshipEnabled = $enabled' \ + "$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 "spend enablement rejects $enabled" "spendStewardshipEnabled must be a boolean" "$d" +done + +d=$(fresh); make_desired_state "$d" alpha +jq 'del(.spec.roles["agentic-engineer"].spendStewardshipEnabled)' \ + "$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 "spend enablement is required" "spendStewardshipEnabled must be a boolean" "$d" + +for surface in onboarding schedule; do + d=$(fresh); make_desired_state "$d" alpha + if [ "$surface" = onboarding ]; then + expression='.spec.onboarding.steps |= map(select(contains("spendStewardshipEnabled") | not))' + else + expression='.spec.runtime.scheduler.schedules["agentic-engineer"].bootstrapPrompt = "Load native memory and AGENTS.md, then invoke the installed agentic-engineer entrypoint."' + fi + jq "$expression" "$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 "$surface must consume spend enablement" "onboarding and engineer schedule must consume spendStewardshipEnabled" "$d" +done + +d=$(fresh); make_desired_state "$d" alpha +# Match literal Markdown backticks, never shell expansions. +# shellcheck disable=SC2016 +sed 's/Only literal `true` plus a resolving/Any flag value plus a resolving/' \ + "$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 "spend enablement cannot bypass explicit opt-in even with a refreshed digest" \ + "agentic-engineer must enforce the explicit spend enablement contract" "$d" + d=$(fresh); make_desired_state "$d" alpha jq '.spec.roles["finops-engineer"] = {"enabledWhen": "x", "definitionFrom": "y", "mode": "z"}' \ "$d/plugins/alpha/resources/provider-neutral.desired-state.json" > "$d/tmp" \