Skip to content

feat(releases): read and apply the AI-DLC 2.11 scope settings - #581

Merged
JWThewes merged 12 commits into
mainfrom
feat/aidlc-211-scope-settings
Oct 20, 2026
Merged

JWThewes merged 12 commits into
mainfrom
feat/aidlc-211-scope-settings

Conversation

@eipasteur

@eipasteur eipasteur commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

In short

AI-DLC 2.11 gives each scope four settings: Guard Policy (whether a change to an already-approved document needs re-approval: strict, relaxed or off), Plan Approval (whether the code plan needs a human approval), Collaborators (whether support agents help the lead agent) and, for three scopes, project type (existing code). Today the platform drops all four when it imports a release, so a 2.11 run would ignore them: enterprise would lose its strict guard, express would still stop for a plan approval its authors turned off, and a scope asking for one agent would run several. The promotion check did not notice.

This PR first makes the promotion check refuse any release that sets a field the platform cannot apply, then teaches the platform the four settings and applies them. 2.11 itself is still not promotable after this PR; that is #583.

Stacked on #521 (fix/release-promotion).

What changes

  • No silent loss. A release or fork that sets an execution-relevant field the platform does not read is refused at promotion, naming the field.
  • The importer reads the four settings. Each one stays blocked until the commit that applies it, so no intermediate state promotes a setting that is ignored. The importer revision moves to 3; the catalogs of 2.3.3 to 2.9.0 are byte-identical under it.
  • The settings are applied. Guard Policy maps onto the existing change-control gate (off removes the re-approval, nothing else); Plan Approval off removes the plan approval step; project type tells the agent it works on existing code. No stage loses a human validation gate, whatever the settings.
  • Collaborators off runs one agent. A stage that would have had support agents runs with its lead agent only, and the timeline names each support agent withdrawn.
  • A test proves the annex reaches every prompt that needs it, using the real prompt builders for every kind of session.

Behaviour by release

Pin Change
unpinned / 2.3.3 None.
2.8.2 / 2.9.0 None: same promotion results, same plans, same prompts.
customer forks A fork setting a field the platform cannot apply is refused by name. A fork using the four 2.11 settings promotes and they are applied.
2.11.0 Still not promotable (needs #583); its settings are now read and applied as written.

Validation

  • A fixture carries the real frontmatter of the eleven 2.11.0 scopes (with their upstream file hashes); the mapping and policy tests run on it.
  • Tests cover: the new refusal for releases and forks; every existing release still promotes; catalogs unchanged under the new importer revision; each setting's effect on every scope and stage; no human gate moved; collaborators off on every affected stage.
  • On a test deployment, a 2.11 express intent ran without a plan approval step, and a classic intent ran with it and without support agents.
  • The commits are small and each passes its tests; the full suites, lint and builds pass.

Limitations and follow-ups

@eipasteur eipasteur changed the title feat(releases): read and honour the AI-DLC 2.11 scope settings feat(releases): read and apply the AI-DLC 2.11 scope settings Oct 19, 2026
@JWThewes
JWThewes added this pull request to stack #584 October 19, 2026 15:07
Base automatically changed from fix/release-promotion to main October 20, 2026 14:43
…blocker

An execution-relevant authored field no adapter consumes is how a release
silently loses a setting. The closure already records the field's name, so the
promotion guard now names it as a blocker instead of leaving it to
certification, and a fork authoring the same field is held the same way.

That changes what the evidence collector decides, so the fidelity evidence
revision goes from 4 to 5: a list stored by the previous build carries a
revision that looks complete while holding a verdict reached without this
blocker, and promotion must re-read the immutable closure for such a record
rather than trust it. The numeric-revision case is pinned alongside the
no-revision one the registry already covered.
…ers a body

`annex-reach.js` is a claim about the prompt BUILDERS: the promotion guard stops
blocking an engine command found in a reaching block, so every session the
runtime renders that block into has to receive the answer. The guard and the
builders already share the module, but sharing a set is not delivering it — a
builder can gate on a part the set does not name, or stop gating on one it does,
and the existing cases assert each builder in isolation with hand-built parts.

Walk the blocks of the real 2.9.0 catalog instead, and for each claimed block
build the real prompt of each session that renders it: the lead stage prompt,
the reviewer prompt `runReviewer` really builds, the support, link and
integrator briefs from the shared persona tail, and the compose prompt. Pin that
every session kind was exercised, and that the block types no builder gates on
(RULE, SCOPE, ARTIFACT, SENSOR) stay outside the set.

The role sets are read with the CATALOG block shape — `supportAgents` and a
`reviewer` string, not the resolved plan's `supportAgentRefs` /
`reviewer.reviewerAgent`. Reading the plan's names leaves both sets empty and
silently reduces the whole check to lead sessions, so the enumeration is
asserted non-empty with the dual-role overlap that makes the secondary sessions
reachable.

Behaviour is unchanged; this is a test.
`guard_policy`, `plan_approval`, `collaborators` and `existing_code` reach the
SCOPE block, and the importer revision and pinned mapper fingerprint move with
the mapper so a new import cannot be published under the old revision's prefix.
An existing pin's closure is already-mapped bytes behind an immutable digest and
is never re-mapped; `changeControl` reads `guard_policy` first and
`change_control` second, so a 2.8.2 or 2.9.0 scope maps to the bytes it maps to
today.

Mapping a field removes its `unmapped` promotion blocker, and nothing else here
makes the setting work, so each of the four is classified in the capability
registry in the same change — every value `unsupported` with no handler, until
the PR that resolves it onto the runtime names its seam. A fork authoring any of
them therefore still holds promotion rather than running with a setting the
platform drops; `change_control` gains the `off` value for the same reason, so a
2.11 scope's plan is rejected by its classification rather than by an enum it
was never taught.

`existing_code` is the one authored as a YAML boolean, so the registry declares
the authored type and the analyzer compares the real parsed value. Upstream's
`existing_code: true` imports and keeps its type; a quoted `"true"`, which the
mapper would carry to a consumer comparing with `true`, is refused at import.
…on bump

The golden digests of every published release are reproduced under their own
recorded revision, so the bump can only change bodies imported from now on.
…boundary

The enum check compared `String(value)`, so a singleton array passed every
string-valued policy enum and a quoted boolean passed the project type. The
comment assumed the import analyzer had already checked the parsed type, but a
user SCOPE overlay never reaches that analyzer: the building-block API keeps
intrinsic attributes verbatim and the release resolver reads the pinned row as
saved, so the plan is that block's first and only judge.

The registry already declares each entry's authored type for the analyzer, so
carry it on the plan's policy rows too and check it before comparing the enum
text. Consumers compare with `===`, so an accepted `['strict']` matched no
change-control branch and an accepted `'true'` produced no project-type
instruction - both now fail the plan closed instead.
Classifying a field as a scope policy also added its plan key to the predicate
that decides whether a stage gets a resolved policy at all. The four 2.11
settings are deliberately classified `unsupported` until their handlers ship,
yet a scope - or a user SCOPE overlay, which the promotion guard never sees -
authoring only `collaborators` turned `policy: null` into a full policy. That
switched on the unrelated defaults and the enablement gate the ensemble
topology reads, so an overlay declaring collaborators off acquired a
collaborator and a change-control ritual it never asked for.

Split the registry's derived key lists: one names every policy field the plan
KNOWS, so its type and value are still checked, and one names the subset whose
handling is no longer `unsupported`. Validation reads the first, activation the
second, so a field becomes able to change a run in the same change that gives
it a consumer. `guard_policy` stays an activator because the mapper resolves
change control from it, which has had a consumer since 2.6.18.
…tation

Activation reads the FIELD's handling, so a field with a consumer switches a
stage policy on for every one of its legal values. `change_control` has had a
consumer since 2.6.18 and its third value `off` is classified `unsupported`,
which left that value legal, activating, and uninterpreted: the plan carried
`policy.changeControl: 'off'` and the runtime's change-control section entered
on truthiness and fell through every implemented outcome, running the stage
with no comparison at all. A release authoring the value is held by the
promotion guard, but an immutable user SCOPE overlay is stored verbatim and
never meets that guard.

Carry the uninterpreted subset of each policy field's values on the same
registry rows that carry its enum, and refuse such a value where the overlay
enters execution: a user-authored block declaring one fails the plan with
`policy_value_uninterpreted` instead of resolving a policy. A release closure's
own scope is unchanged - it still imports, still plans, and is still held at
promotion - so the complete 2.11 body remains importable and inspectable.

The runtime asks the same registry question before acting. A change control it
does not interpret now fails the stage with `change_control_uninterpreted`
rather than proceeding as the absence of change control, which keeps the
section's "three outcomes and no fourth" contract true for a value that
reached a policy by another route.
…ype from the scope

The three settings A3 carries onto the SCOPE block reach the per-stage policy,
and their registry classifications move from `unsupported` to the seams that
honour them, so a release authoring them stops holding promotion for them.
`collaborators` keeps its blocker: its handler is not here.

Guard Policy resolves ONE effective value, the new name first and the legacy
`change_control` second, and only then translates `off` to no change control.
An imported closure carries both names, but a user SCOPE overlay is stored
verbatim and never passes through the mapper, so a forked scope may declare the
new name alone or both disagreeing — resolving it in the plan is what keeps a
caller from having to synchronize an internal alias. `off` means no reopen and
no change-control prose, and nothing else.

Plan Approval `off` withdraws `policy.planApproval` only; the stage's
`humanValidation` is untouched for every scope and stage. The authored project
type reaches `policy.existingCode`, and the stage prompt states the brownfield
reading it implies, which is the seam the registry now names.

Classifying these three also has to keep an `unmapped` gap refused, in the same
change. Such a gap records that the importer of that closure stored no value for
the field: the information is absent from the catalog, not merely unhandled by
this build. Were it matched against the registry like an authored value, adding
an entry here would clear an old closure's blocker, and a deployment could
promote and run a revision-2 closure as though it carried the strict guard its
source authored. The value is unconditionally unhonoured; clearing it requires
re-importing the source under the newer revision, which is what actually
recovers it.
`collaborators: off` dispatched the full support roster: the switch reached the
SCOPE block and stopped there. It now reaches the per-stage policy, and the
ensemble runner resolves NO topology for it — this runtime's existing
single-session contract, where the lead produces every declared stage output
itself. A shortened roster would instead leave some declared outputs unowned.

The registry entry moves from `unsupported` to that seam in the same change, so
the value stops holding promotion exactly when it starts working, and the one
mode set both the runner's gate and the plan's record of the downgrade read is
exported once. The stage's authored `mode` and `supportAgentRefs` stay on the
plan: the filter is a policy effect, not a topology rewrite.
…raws

A stage that runs with three fewer sessions than its block declares leaves a
record naming each one, the way a removed sensor already does, so the downgrade
reaches the timeline instead of being a silently shorter roster.
The withdrawal audit tested the mode set alone, while a subagent stage receives
support sessions only when the closure ships the hub-and-spoke protocol file and
its support refs resolve to AGENT blocks - conditions the topology resolver
checks and the audit did not. A fork that imports and promotes cleanly without
that protocol therefore had three personas named as withdrawn on a stage that
was already a single session, and `runStage` serialized that claim into
`v2.policy.applied`.

Move the complete eligibility test into the plan module as `supportSessionRefs`
and have both the topology resolver and the audit call it, so the two can no
longer disagree about whether a session existed to withdraw.
@eipasteur
eipasteur force-pushed the feat/aidlc-211-scope-settings branch from e441f55 to 2a8a42b Compare October 20, 2026 16:57
@JWThewes
JWThewes merged commit eabd5a2 into main Oct 20, 2026
5 checks passed
@JWThewes
JWThewes deleted the feat/aidlc-211-scope-settings branch October 20, 2026 16:58
@jeromevdl jeromevdl added area/agents AgentCore runtime: CLI drivers, MCP tools, workspace and Git operations area/backend Lambda APIs, lambda/shared, v2-orchestrator size/L < 3000 changed lines (lockfiles excluded) labels Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/agents AgentCore runtime: CLI drivers, MCP tools, workspace and Git operations area/backend Lambda APIs, lambda/shared, v2-orchestrator size/L < 3000 changed lines (lockfiles excluded)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants