Skip to content

feat(dice-storage-autoconfigure): opt-in metamodel wiring - #88

Merged
jimador merged 10 commits into
feat/metamodel-drift-storefrom
feat/metamodel-autoconfigure
Sep 9, 2026
Merged

jimador merged 10 commits into
feat/metamodel-drift-storefrom
feat/metamodel-autoconfigure

Conversation

@jimador

@jimador jimador commented Aug 30, 2026 •

Copy link
Copy Markdown
Collaborator

PR 6, closing the metamodel train (#83 → #84 → #85 → #86 → #87 → #88). The governance loop becomes usable from a Spring Boot app, and it gets an operator. MetamodelAutoConfiguration in dice-storage-autoconfigure registers only when the host declares a DeclaredSchemaSource bean — declaring your schema is the opt-in — and then wires the loop: version store, drift-report store, observed-schema source, both differ roles, quarantine policy, DriftSweepCapable, drift runner, and the metamodel SchemaCatalog. Every wired collaborator is @ConditionalOnMissingBean. embabel.dice.metamodel.enabled=false removes the beans; drift.mode is off or observe (default); backend selection follows embabel.dice.store.type. Nothing in the wiring runs a check or moves a proposition: a check happens when the host calls DriftCheckRunner.run(), a quarantine when the host calls DriftSweepCapable.sweep().

Changed in this review round:

  • The operator surface. GovernanceOperationsService in dice is the single entry point — read (latest reports, global and per context, plus the declaration in force and the version the last completed sweep reconciled against), run (a check answering the full impact a sweep would evaluate, declaredDiff included), release (one call per proposition, context-checked before it writes). GovernanceController serves the five operations on /api/v1/metamodel; GovernanceTools exposes the same five as @LlmTool tools; both call the one service. EXPERIMENTAL, marked @ApiStatus.Experimental in code across the stack. The routes activate only when the host writes @Import(DiceRestConfiguration) and a DeclaredSchemaSource is declared — the same activation as every other DICE controller, carried by a deferred import so the condition sees autoconfigured beans. GovernanceTools is host-constructed like DiscoveryTools; no auto-configuration registers a controller or a tool object. Per-report release is deliberately absent: nothing in the model ties a proposition to the report whose application held it, and the doc says what closing that needs.
  • ObserveOnlyDriftCheckRunner is deleted and DriftMode reduces to OFF | OBSERVE — the runner is report-only structurally, so the decorator had nothing left to downgrade.
  • The sweep bean announces status transitions to every DiceEventListener on the context; the derived-ownership schema wiring from feat(dice-storage): Drivine drift-report store and observed-schema source #87 is registered here; the HTTP gate re-checks the declared schema directly.

Breaking changes: none. A host with no DeclaredSchemaSource bean sees no metamodel beans at all, proven by a context probe test.

flowchart TD
    A{DeclaredSchemaSource bean?} -- no --> Z[no metamodel beans at all]
    A -- yes --> B{metamodel.enabled}
    B -- false --> Z
    B -- true --> C[stores, observed schema, differs, policy, sweep, runner]
    C --> F[GovernanceOperationsService; REST via host Import of DiceRestConfiguration; tools host-constructed]
    F --> G[read reports and declared version]
    F --> H[run a check on demand]
    F --> I[release one proposition from quarantine]
    J[host calls DriftSweepCapable.sweep] --> K[quarantine, deliberate and evented]
Loading

Landing

  • Build the union of this stack's tip and the extraction-run stack's tip (Extraction runs: proposition lineage #101) before either lands; the two share six recurring conflict files with recorded resolutions; DiceStorageAutoConfiguration.kt conflicts too and is resolved by hand from the union worktree's committed adaptation.
  • Whichever stack lands second restacks and applies one adaptation: ExtractionRunSchema joins this stack's DiceStorageSchema derived-ownership contract (object declaration, override on specs(), bookkeeping relationship types, catalog and test beans). DiceStorageSchemaRegistrationTest fails until it is applied — that is the guard working.
  • No consumer migration is required to land: the four-argument undoSingleCollapse stays as a deprecated overload (Source-revision provenance: authoritative collector fold and undo #92). Downstream callers move to CollapseUndoCommand when convenient.

jimador added a commit that referenced this pull request Aug 31, 2026
Rewrite both specs in the repo's documentation voice. Delivery status
now reports the open PR train (#83-#88) per PR and corrects entries
that were marked planned after the implementing PRs opened.
@jimador
jimador force-pushed the feat/metamodel-autoconfigure branch from a3cb28b to 68caa2d Compare August 31, 2026 01:29
@jimador
jimador force-pushed the feat/metamodel-autoconfigure branch from 68caa2d to f340946 Compare August 31, 2026 04:54
@jimador
jimador force-pushed the feat/metamodel-autoconfigure branch from f340946 to 2c37e41 Compare August 31, 2026 05:23
@jimador
jimador force-pushed the feat/metamodel-autoconfigure branch from 2c37e41 to 161a549 Compare August 31, 2026 05:50
@jimador
jimador force-pushed the feat/metamodel-autoconfigure branch from 161a549 to 3b8518c Compare August 31, 2026 06:35
@jimador
jimador force-pushed the feat/metamodel-autoconfigure branch 2 times, most recently from 23e6426 to edce572 Compare September 4, 2026 03:47
@jimador
jimador force-pushed the feat/metamodel-autoconfigure branch from edce572 to e8c5c97 Compare September 4, 2026 04:31
@igordayen

Copy link
Copy Markdown

From CODEX:

  1. dice/src/main/kotlin/com/embabel/dice/agent/GovernanceTools.kt:77

The new governance tools are exposed with generic names like run_drift_check, latest_drift_reports, and
release_quarantined_proposition.

Example:

  • another module also exposes run_drift_check
  • a host registers both tool lists in one agent/MCP server
  • both tools now have the same public name

At that point the tool registry has to either reject one, overwrite one, or leave selection ambiguous.

So for this PR, the practical fix is to prefix each tool explicitly, for example:

  • embabel_dice_declared_schema_version
  • embabel_dice_latest_drift_reports
  • embabel_dice_run_drift_check

If dotted tool names are valid in the target tool registry, embabel.dice.run_drift_check is clearer. If not, use underscores. The key point is that the prefix must reach Tool.Definition.name, not only metadata or docs.

There is also PR on embabel-agent that flags non-unique tool names and prefixes tool names with agent name,
see:
embabel/embabel-agent#1833

  1. dice-storage-autoconfigure/src/main/kotlin/com/embabel/dice/storage/autoconfigure/MetamodelAutoConfiguration.kt:127

The configuration prefixes are repeated directly in annotations:

  • embabel.dice.metamodel
  • embabel.dice.metamodel.drift
  • embabel.dice.store

This is not a behavior bug, but it is easy to mistype later. Spring does not provide a class-level prefix that child
@ConditionalOnProperty annotations inherit, so I would define constants instead:

private const val DICE_PREFIX = "embabel.dice"
private const val METAMODEL_PREFIX = "$DICE_PREFIX.metamodel"
private const val DRIFT_PREFIX = "$METAMODEL_PREFIX.drift"
private const val STORE_PREFIX = "$DICE_PREFIX.store"

Then use those constants in @ConfigurationProperties and @ConditionalOnProperty. That keeps the public property names in one place.

  1. dice-storage-autoconfigure/src/test/kotlin/com/embabel/dice/storage/autoconfigure/MetamodelAutoConfigurationIntegrationTest.kt:163

The comment says _DrivineSchema is currently reported as drift, but PR 87 already added INFRASTRUCTURE_LABELS and excludes _DrivineSchema.

So the comment is stale. It now tells the reader the opposite of the current behavior.

@igordayen igordayen left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jimador - looks good, a few comments from my side and Codex, thank you

Comment thread dice-storage-autoconfigure/pom.xml Outdated
@jimador
jimador force-pushed the feat/metamodel-autoconfigure branch from e8c5c97 to 7edd5d7 Compare September 7, 2026 03:35
@jimador

jimador commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author

On Codex items 2 and 3:

  1. Fixed in 9e021e9. The prefixes are const vals on one internal object, and the nine annotations across the two metamodel files reference it.
  2. Fixed in 7edd5d7. The comment now says DrivineObservedSchemaSource keeps _DrivineSchema out through INFRASTRUCTURE_LABELS, and the test asserts it.

Item 1 (tool name prefixes) is answered separately.

@igordayen igordayen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jimador - looks good, thank you

@jimador
jimador force-pushed the feat/metamodel-autoconfigure branch from 7edd5d7 to 5bdd94a Compare September 8, 2026 20:30
@jimador
jimador force-pushed the feat/metamodel-autoconfigure branch from 5bdd94a to fc3b6b0 Compare September 8, 2026 20:47
@jimador
jimador force-pushed the feat/metamodel-autoconfigure branch from fc3b6b0 to 633776f Compare September 8, 2026 20:59
MetamodelAutoConfiguration activates only when the host declares a
DeclaredSchemaSource bean — declaring your schema is the opt-in, the way
providing a DataSource turns on JPA. Real @autoConfiguration with every
default @ConditionalOnMissingBean so consumer beans win in either
registration order; drift runner wired against the base PropositionStore
port. embabel.dice.metamodel properties: enabled kill switch and
drift.mode off|observe|quarantine — observe is the default, quarantine
never is. Also excludes Drivine's _DrivineSchema inventory label from
observed schema by verified shape.

Refs #45; stacks on feat/metamodel-drift-store. Completes the metamodel
train.

Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
Comment and doc text only; no code change.

Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
The runner is evaluate-and-report only, so ObserveOnlyDriftCheckRunner
had nothing left to refuse and is gone, and DriftMode reduces to OFF and
OBSERVE: off suppresses the runner bean, and no property DICE reads can
move a proposition. The wiring fixes follow the reviewer's findings: the
application's event listener reaches every governance collaborator that
can emit a status transition, so a deliberate quarantine drives
ProjectionLineageStaleCascade; Drivine-backed governance beans register
only under the graph backend, so a DeclaredSchemaSource under the
default in-memory backend starts cleanly; and MetamodelDiffer and
DeclaredObservedDiffer resolve independently, so distinct consumer beans
are both honored.

Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
Drift reports, the declared version, and quarantine release were
reachable only from a debugger: the report store's bounded reads had no
caller and release existed as an SPI with no door. One
GovernanceOperationsService now answers reads (latest reports global
and per context, the current declared version), runs a check that
returns the full impact a sweep would evaluate, and releases a held
proposition through the recorded-prior-status path, refusing across
contexts. GovernanceController exposes it under /api/v1/metamodel and
GovernanceTools exposes it to agents, both following the discovery
surface's shape, wired in the autoconfigure module under the governance
conditions and overridable per bean. Per-report release is absent: a
DriftReport carries no identity a reason could name, and that modeling
belongs to its own slice.

Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
AGENTS.md described a quarantine drift mode that does not exist and
contradicted the autoconfiguration's override behavior; it now states
OFF and OBSERVE and the metamodel catalog exception, and the operator
surface entry carries the EXPERIMENTAL marker with its opt-in
conditions. The observed-schema wiring adapts to the derived-ownership
rework: a DiceOwnedSchema bean builds from the registered
DiceStorageSchema beans, overridable per bean, and the Drivine source
takes it through its constructor.

Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
The controller's condition reached the opt-in only through the service
bean, so a host hand-wiring GovernanceOperationsService without a
declared schema got six public routes. The HTTP surface now requires
the DeclaredSchemaSource bean and the enabled property itself, matching
the metamodel wiring's idiom, and the condition tests enumerate
resolved URLs from a live handler mapping, so a route that exists is a
route the test sees.

Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
Declaring a schema opts a host into versioning; it must not opt them
into HTTP or agent tools. The controller now activates only through the
host's explicit DiceRestConfiguration import, joining the one REST
activation idiom, and the governance HTTP autoconfiguration is gone. A
conditional on an imported class is answered before autoconfigured
beans exist, so the import rides a lowest-precedence deferred selector,
pinned by tests covering import-with-schema, import-without-schema and
no-import. GovernanceTools is host-constructed the way DiscoveryTools
is, so the host chooses what an agent can call. The branch's public
governance types carry ApiStatus.Experimental, and the wiring doc
states the deliberate narrowings: release is per proposition, and only
a host call to DriftSweepCapable.sweep applies a quarantine.

Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
embabel-agent-dependencies manages neither mockito-kotlin nor the JetBrains annotations, so the root pom now carries both versions and the module poms declare the artifacts without one.

Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
The metamodel auto-configuration and its properties read every prefix from DicePropertyPrefixes, so the property names live in one place.

Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
DrivineObservedSchemaSource keeps _DrivineSchema out of the observed types through INFRASTRUCTURE_LABELS. The test comment said the opposite, and the test now asserts the exclusion.

Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
@jimador
jimador force-pushed the feat/metamodel-autoconfigure branch from 633776f to 408705f Compare September 9, 2026 00:08
@jimador
jimador merged commit 55c5bb4 into main Sep 9, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants