Skip to content

RE: Blueprint materialization (Phases A–C) — Egeria projection of recovered architecture - #43

Merged
dwolfson merged 7 commits into
odpi:mainfrom
dwolfson:re/docs-consolidation-part-2
Sep 3, 2026
Merged

dwolfson merged 7 commits into
odpi:mainfrom
dwolfson:re/docs-consolidation-part-2

Conversation

@dwolfson

@dwolfson dwolfson commented Sep 3, 2026

Copy link
Copy Markdown
Member

Summary

Completes docs/blueprint-materialization-plan.md end to end: turns a curator's
ACCEPTED verdict on a candidate architecture blueprint into a real Egeria
SolutionBlueprint, with member/child attachment via the outbox, and a
dedicated Curate UI to review and act on it.

Phases

  • Phase A — BlueprintMaterializer (find-or-create the SolutionBlueprint
    element), registry migration (architecture_component_verdicts.verdict_target,
    new architecture_materialized_blueprints table).
  • Phase A.5 — Live-measured SolutionLinkingWire's multi-link behavior
    before trusting it in the outbox (confirmed: duplicates on retry). Project
    owner decision: defer wire enqueueing to its own follow-up rather than ship
    it with the confirmed risk.
  • Phase B — Blueprint verdict route (/api/curate/blueprint-verdicts/...),
    member/child attachment via egeria_outbox.enqueue_blueprint_members.
  • Phase C — New Curate sub-tab (🏛 Architecture Verdicts) with accept/reject
    controls for candidate blueprints and components; Analysis's own rendering
    of the same data is now read-only (verdict controls relocated to Curate per
    the outside-in reconciliation in curate-evidence-based-decisions-plan.md).

Two follow-up fixes, found live-verifying Phase C

Testing

Full suite: 3653 passed, 92 skipped, 0 failed. New tests: blueprint
materializer (21), curate blueprint-verdicts route (20), outbox
enqueue_blueprint_members (2), data.blueprints reader (10).

Not in this PR

Wire enqueueing (deferred, tracked in Backlog.md item 1), the
AnnotationReview relationship-type measurement gap (Backlog item 5),
ComponentMaterializer's equivalent contentStatus gap (separate,
still-open follow-up).

🤖 Generated with Claude Code

dwolfson and others added 7 commits September 3, 2026 13:45
…confirmed

docs/blueprint-materialization-plan.md's own gate: don't ship wire enqueueing
on an assumption from pyegeria's docstring, measure it. Ran the new
scripts/arch-spike/measure_wire_multi_link.py against the live quickstart
platform: two identical link_solution_linking_wire() calls between the same
two throwaway SolutionComponents returned two different relationship GUIDs
(f4179125-..., 8f18b426-...) — the definitive test per this session's own
reference_egeria_dedup_and_link_patterns memory note ("if attach returns an
identifier it is multi-link; if it returns nothing it is uni-link").
Confirms the docstring's own claim rather than contradicting it, and still
worth having measured directly — the two relationships this codebase
already relies on were each independently measured uni-link despite
looking, on paper, like they might not be.

Coordinated before writing: messaged all 8 live peer sessions, all cleared;
confirmed the earlier republish batch's outbox was fully drained (0
'running' rows, not just a process check) and the platform functionally
settled before trusting a negative-duplication result would have meant
anything. Cleanup used delete_solution_component(cascade_delete=True) on
both throwaway components, not a by-ends detach, which peers flagged would
have silently removed both wires and corrupted the evidence before it
could be read back.

Confirmatory read-back (get_component_related_elements) came back empty —
not a contradiction, a wrong instrument: that method's response has no
generic wire/relationship list. The return-value test alone was sufficient;
noted the likely correct method (get_relationships_with_property_value) for
a future attempt, un-tried here.

Updated docs/reference_egeria_dedup_and_link_patterns.md with the
measurement (the memory note's own third relationship-type entry).

The gate's actual ship/defer decision (Decision 5, option a vs. deferring
wire enqueueing to a follow-up) is NOT made here — the measurement answers
"is the risk real," not "do we ship with it." Left open for the project
owner.

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…flag AnnotationReview

Project owner's decision, asked directly per the plan's own gate: defer wire
enqueueing to its own follow-up. Phase B ships blueprint materialization and
member/child attachment; enqueue_blueprint_wires/_create_solution_linking_wire/
OutboxClients.solution_architect are out of scope for it. A materialized
blueprint renders without its wire diagram until wire enqueueing lands
separately.

Sharper framing of the risk itself, from a peer session's review after the
measurement landed: this isn't "wires need their own idempotency layer" as
an add-on. apply_element's create-blind safety for every other outbox kind
rests on qualifiedName search-then-adopt (why qualified_name is NOT NULL on
the table) — relationships have no qualifiedName. Wires would be the first
element kind the outbox's central idempotency invariant structurally cannot
cover at all, not the first one that merely lacks a layer.

New Backlog item 5, from the same review: egeria_outbox.py's own
_create_annotation_link docstring already names AnnotationReview (Phase 3,
not built) as a second unmeasured relationship type queued for the same
create-blind path. Verified the docstring reference is real before writing
it down. Not fixed — nothing calls it yet, no live bug — flagged as the
next candidate for the same measure-before-relying-on-it treatment.

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…outbox

docs/blueprint-materialization-plan.md Phase B, scoped per the project
owner's Phase A.5 decision to exclude wires — enqueue_blueprint_wires,
_create_solution_linking_wire, and OutboxClients.solution_architect are
NOT part of this commit; a materialized blueprint attaches its members and
child blueprints but renders without its wire diagram until that lands as
its own follow-up.

egeria_outbox.py: enqueue_blueprint_members — one CollectionMembership row
per member component or child blueprint, reusing element_kind=
"collection_membership" unchanged (parallel to enqueue_collection_members,
not a call to it, since that function hardcodes entity_type="investigation"
and a blueprint's own resource is a repo).

curate.py: BlueprintVerdictCreate/list_blueprint_verdicts/
add_blueprint_verdict, mirroring the existing component-verdict route
exactly. _materialize_blueprint_if_accepted: looks up the candidate_blueprint
finding, materializes the blueprint element synchronously
(BlueprintMaterializer), resolves member/child GUIDs (never raising for an
unmaterialized one — Decision 2's enforcement point), enqueues attachment
for whatever IS ready, and reports status="partial" plus which
members/children are still missing when it isn't everything. Same
non-fatal-but-visible contract as the component route: the verdict saves
regardless of whether Egeria was reachable.

Fixed a real gap the tests caught, not scoped in the plan: the EXISTING
list_component_verdicts route never filtered to verdict_target="component"
— get_component_verdicts' own docstring says it returns both mixed and
leaves filtering to the caller, and only the new blueprint route filtered
its own side. A blueprint verdict's key (perspective::cluster_name) would
never practically collide with a real scope_locator, but the endpoint's
name promises component verdicts specifically. Added the mirroring filter.

20 new route tests (tests/test_web.py::TestCurateBlueprintVerdictsRouter) —
add/list, verdict validation, rejected-never-materializes, missing-cluster
error, full materialization, partial materialization (Decision 2), two-level
child-blueprint resolution (Decision 1), oversized flag passthrough,
Egeria-unreachable-still-saves. Plus 2 direct unit tests for
enqueue_blueprint_members (tests/test_egeria_outbox.py).

Full suite: 3615 passed, 92 skipped, 0 failed (excluding the one test
needing a currently-unreachable-by-design-choice live Egeria smoke check,
unrelated to this change).

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…remain

Records what bce70ca shipped and points at what's left — Phase C
(frontend, in Curate per the outside-in reconciliation, not Analysis) and
the deferred wire-enqueueing follow-up.

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
Per the outside-in reconciliation in curate-evidence-based-decisions-plan.md
§D item 10: candidate-blueprint and component verdict controls move to a
new Curate sub-tab, not Analysis's results panel — Analysis stays a
read-only evidence display.

Backend:
- repo_survey_definition_adapter.py: new _candidate_blueprints_results
  reader, wired as _architecture_recovery_results' new data.blueprints key.
  Reads kind="architecture_blueprints" candidate_blueprint findings,
  disambiguated by (perspective, cluster_name), latest survey wins, merged
  with verdict/materialization state via curate.py's existing key shape.
- tests/test_candidate_blueprints_reader.py: 10 new unit tests.

Frontend (index.html):
- New Curate sub-tab "🏛 Architecture Verdicts" (repo-only), mirroring
  _automateSubnavHtml's pattern (_curateSubnavHtml/_setCurateSubTab).
  Renders candidate blueprints (accept/reject, oversized-cluster warning,
  unmaterialized-member/child warning on partial materialization) and
  components still awaiting a verdict (reuses the existing _archRow/
  _archVerdictControlsHtml chain).
- New _archInteractiveMode module flag (false by default) gates the
  accept/reject/change controls in the shared architecture-recovery
  renderer — Analysis's call site stays false (read-only facts), Curate's
  render pass sets it true (editable). Existing renderCuratePanel renamed
  to renderCurateGeneralPanel; loadCuratePanel is now a small dispatcher
  so existing mutation handlers (addCurateTag, saveCurateGroup, ...) don't
  need to change.

Live-verified against sqlglot (18 components, 3 candidate blueprints)
through the actual UI: accepted a blueprint and a component, confirmed the
verdict badge/"change" control render in Curate and the same payload
renders no interactive controls in Analysis. Found, not caused by this
change: BlueprintMaterializer.materialize_blueprint_element (Phase A) fails
Egeria's request-body validation on a real accept — verdict still saves
correctly (report-then-curate split held); tracked as Backlog item 6, not
fixed here.

Full suite: 3652 passed, 92 skipped, 0 failed.

docs/blueprint-materialization-plan.md and docs/Backlog.md updated to
record Phase C done and the two divergences from the original plan (flat
blueprint list instead of nested tree; the materialization bug found live).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…og item 6)

materialize_blueprint_element sent class: "NewSolutionElementRequestBody"
with initialStatus: "DRAFT", per SolutionArchitect.create_solution_blueprint's
own docstring — but that class was never a real pydantic model. pyegeria
validates every create-blueprint body against a bare
TypeAdapter(NewElementRequestBody), whose class field is
Literal["NewElementRequestBody"], so this failed local pydantic validation
before any HTTP call — a PyegeriaInvalidParameterException that read like
an Egeria-side rejection but wasn't one. Confirmed live 2026-09-03
accepting a real candidate blueprint against sqlglot.

Root cause was worse than the missing model, per egeria-python's own
review (ISSUE-84, egeria-python-65, PR odpi/egeria-python#337):
NewSolutionElementRequestBody/initialStatus never appeared in any .http
ground-truth file either — the real, already-modeled mechanism is
contentStatus inside properties (ReferenceableProperties, base of
SolutionBlueprintProperties/SolutionComponentProperties). That fix isn't
adopted here yet — needs live confirmation that contentStatus at creation
time actually takes effect server-side, blocked right now by unrelated
platform instability. This commit ships the immediate, verified-safe fix:
class: "NewElementRequestBody", no initialStatus — the same body shape
ComponentMaterializer already uses successfully. Blueprints materialize
ACTIVE, not Draft, same pre-existing gap ComponentMaterializer already has.

Two new/replaced tests in test_blueprint_materializer.py, including one
that validates the actual body against pyegeria's real
TypeAdapter(NewElementRequestBody) rather than only asserting the mock
call shape — the kind of test that would have caught this before it
reached a live accept.

Backlog.md item 6 updated with the fix and the deferred contentStatus
follow-up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…us materialization)

Follow-up to the previous commit's ACTIVE-only fallback. egeria-python-65
reviewed ISSUE-84 and found the real mechanism (odpi/egeria-python#337):
contentStatus is a plain field on ReferenceableProperties (base of
SolutionBlueprintProperties), settable inside properties on the existing
NewElementRequestBody — no separate request-body class was ever needed.

Confirmed live, twice, each independently read back by guid rather than
trusted from the create call's own return:
- Standalone probe (guid 1c6550a9-77cd-49ac-a791-ffb9fa56f3fc, then
  8755a5ac-7941-4880-a735-72010d779a73 to check the properties-block
  shape) — properties.contentStatus == "DRAFT" via
  get_solution_blueprint_by_guid.
- A real accept through the actual Curate UI's own API endpoint
  (POST /api/curate/blueprint-verdicts/repo/sqlglot, not a direct library
  call) materialized guid 809025b5-cca9-4e9a-a2f7-3a5104138f67,
  independently re-queried and confirmed the same.

Both checks: properties.contentStatus round-trips as "DRAFT" correctly;
elementHeader.status stays "ACTIVE" — a separate, unrelated instance-status
axis (OMRS soft-delete/active at the repository level), not the same thing
as the content-maturity axis contentStatus represents. The design doc's
own wording ("ContentStatus = Draft") names the field this now sends.

architecture-recovery.md §10 Phase 2's "All at ContentStatus = Draft" is
now achieved for blueprints. ComponentMaterializer's identical gap (still
NewElementRequestBody with no contentStatus) is NOT fixed here — separate,
still-open follow-up, out of scope for this module.

One new regression test (test_content_status_draft). Full suite: 3653
passed, 92 skipped, 1 deselected (a pre-existing, unrelated live-Egeria
test flaky only during the platform's connection-pool-exhaustion window
this session also found and reported), 0 failed.

Backlog.md item 6 updated with the full resolution.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
@dwolfson
dwolfson merged commit 9613d0b into odpi:main Sep 3, 2026
1 check passed
dwolfson referenced this pull request in dwolfson/trellis Sep 3, 2026
…ranch

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
dwolfson added a commit that referenced this pull request Sep 13, 2026
RE tests: the malformed-usage test talks to a file store, not localhost:5025
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.

1 participant