From 83546149e43dc4ec5a24de6fcf6f5b2b86143ac2 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Thu, 19 Mar 2026 17:58:35 +0000 Subject: [PATCH] packer (WIP) --- ...d-shell-and-packer-contract-consumption.md | 166 ++++++++++++++++++ .../java/p/packer/PackerWorkspaceService.java | 6 + .../packer/dtos/PackerEmittedArtifactDTO.java | 22 +++ .../dtos/PackerPackExecutionSummaryDTO.java | 25 +++ .../p/packer/dtos/PackerPackSummaryDTO.java | 22 +++ .../dtos/PackerPackValidationAssetDTO.java | 18 ++ .../dtos/PackerPackValidationSummaryDTO.java | 26 +++ .../packer/messages/PackWorkspaceRequest.java | 11 ++ .../packer/messages/PackWorkspaceResult.java | 20 +++ .../messages/PackWorkspaceSummaryRequest.java | 11 ++ .../messages/PackWorkspaceSummaryResult.java | 20 +++ .../ValidatePackWorkspaceRequest.java | 11 ++ .../messages/ValidatePackWorkspaceResult.java | 24 +++ .../FileSystemPackerWorkspaceService.java | 15 ++ 14 files changed, 397 insertions(+) create mode 100644 docs/studio/pull-requests/PR-11-pack-wizard-shell-and-packer-contract-consumption.md create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerEmittedArtifactDTO.java create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackExecutionSummaryDTO.java create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackSummaryDTO.java create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackValidationAssetDTO.java create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackValidationSummaryDTO.java create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceRequest.java create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceResult.java create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceSummaryRequest.java create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceSummaryResult.java create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ValidatePackWorkspaceRequest.java create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ValidatePackWorkspaceResult.java diff --git a/docs/studio/pull-requests/PR-11-pack-wizard-shell-and-packer-contract-consumption.md b/docs/studio/pull-requests/PR-11-pack-wizard-shell-and-packer-contract-consumption.md new file mode 100644 index 00000000..f739bf66 --- /dev/null +++ b/docs/studio/pull-requests/PR-11-pack-wizard-shell-and-packer-contract-consumption.md @@ -0,0 +1,166 @@ +# PR-11 Pack Wizard Shell and Packer Contract Consumption + +Domain owner: `docs/studio` +Cross-domain impact: `docs/packer` + +## Briefing + +The `Pack` action in the `Assets` workspace is now closed as a Studio wizard over packer-owned operations. + +The Studio decision already fixes the operational boundary: + +- Studio is the shell; +- packer owns summary, validation, pack execution, progress, and result semantics; +- the wizard has four phases: + `Summary`, `Validation`, `Packing`, `Result`; +- validation runs only on the current `registered + included in build` set; +- blocking diagnostics stop the flow before execution; +- the first wave is non-cancelable. + +The packer-side API PR also defines the contracts that Studio must consume: + +- pack summary +- pack validation +- pack execution + +This PR implements the Studio side of that contract. + +References: + +- [`../decisions/Pack Wizard in Assets Workspace Decision.md`](../decisions/Pack%20Wizard%20in%20Assets%20Workspace%20Decision.md) +- [`../../packer/pull-requests/PR-28-pack-wizard-public-contracts-summary-validation-and-execution.md`](../../packer/pull-requests/PR-28-pack-wizard-public-contracts-summary-validation-and-execution.md) + +## Objective + +Deliver the `Pack` wizard shell in the `Assets` workspace and bind it to the public packer contracts for summary, validation, progress, and result. + +## Dependencies + +- [`../decisions/Pack Wizard in Assets Workspace Decision.md`](../decisions/Pack%20Wizard%20in%20Assets%20Workspace%20Decision.md) +- [`./PR-05d-assets-activity-progress-and-logs-integration.md`](./PR-05d-assets-activity-progress-and-logs-integration.md) +- [`./PR-07c-asset-details-and-form-lifecycle.md`](./PR-07c-asset-details-and-form-lifecycle.md) +- [`./PR-07d-asset-mutation-and-structural-sync-orchestration.md`](./PR-07d-asset-mutation-and-structural-sync-orchestration.md) +- cross-domain reference: + [`../../packer/pull-requests/PR-28-pack-wizard-public-contracts-summary-validation-and-execution.md`](../../packer/pull-requests/PR-28-pack-wizard-public-contracts-summary-validation-and-execution.md) + +## Scope + +- add a real `Pack` action entry point in the `Assets` workspace action bar +- open a dedicated modal wizard from that button +- implement the wizard shell with four explicit phases: + - `Summary` + - `Validation` + - `Packing` + - `Result` +- consume packer summary API for the first phase +- consume packer validation API for the second phase +- consume packer pack execution API for the third phase +- bind packer operation progress to the wizard progress UI +- render result data from the final pack execution response +- show blocking diagnostics by default in `Validation` +- allow developer drill-down on per-asset validation entries +- keep `Packing` non-editable and non-cancelable in the first wave +- keep companion artifacts in secondary drill-down in `Result` +- allow a `dumb` future-facing export/copy button without real behavior if the shell needs a visible reminder + +## Non-Goals + +- no local Studio recomputation of build-set semantics +- no local Studio validation engine for pack gating +- no fake timer-based progress +- no cancellation in the first wave +- no direct implementation of packer semantics inside Studio +- no asset-details-local `Pack`; this remains a workspace-level flow + +## Execution Method + +1. Wire the `Pack` action in the workspace action bar to open the new wizard. + +2. Build a dedicated wizard shell control for the `Assets` workspace. + The shell should own: + - phase navigation + - modal presentation + - loading states + - error surfaces + - binding to packer responses and progress events + +3. Implement the `Summary` phase as packer-backed preflight. + Rules: + - request pack summary on open; + - do not reconstruct counts from navigator rows; + - show the canonical artifact name `assets.pa`; + - show the build-set counts returned by packer. + +4. Implement the `Validation` phase as packer-backed gate inspection. + Rules: + - call the validation API explicitly; + - render aggregate counts and per-asset entries; + - show blocking diagnostics by default; + - allow drill-down to inspect more context on each asset; + - block advance into `Packing` when validation fails. + +5. Implement the `Packing` phase as an operational waiting surface. + Rules: + - call pack execution explicitly only after successful validation; + - show progress bar and current-step text from packer-driven state; + - keep the modal non-editable; + - do not expose real cancel behavior in the first wave. + +6. Implement the `Result` phase as packer-result rendering. + Rules: + - show final status; + - show total assets packed; + - show elapsed time when available from the contract; + - keep `assets.pa` visible in the main summary; + - move companion artifacts into secondary drill-down. + +7. Integrate the operation with Studio progress and activity surfaces where appropriate. + The wizard-local progress surface is primary during execution, but existing global operational surfaces should remain coherent with the same operation. + +8. Keep the implementation boundary strict. + Studio may orchestrate calls and render states, but it must not decide: + - what the pack set is; + - what counts as blocking; + - how `asset_table` ordering is determined; + - or what artifacts were emitted beyond what packer reports. + +## Acceptance Criteria + +- clicking `Pack` in the `Assets` workspace opens a dedicated modal wizard +- the wizard has explicit `Summary`, `Validation`, `Packing`, and `Result` phases +- `Summary` is populated from packer summary data rather than local UI reconstruction +- `Validation` is populated from packer validation data +- validation failure blocks transition into `Packing` +- blocking diagnostics are visible by default in the validation phase +- per-asset drill-down exists for developer inspection +- `Packing` shows packer-driven progress and remains non-editable +- the first wave does not expose real cancellation +- `Result` renders final packer result data and keeps companion artifacts secondary +- the Studio implementation remains a shell over packer contracts rather than a second semantic engine + +## Validation + +- unit tests for wizard phase state transitions +- unit tests for mapping summary response into the `Summary` phase +- unit tests for validation gating and blocked advance into `Packing` +- unit tests for per-asset validation drill-down state +- unit tests for progress binding from packer operation state into the wizard progress UI +- unit tests for result rendering from pack execution response +- smoke test for: + - open wizard + - load summary + - run validation + - stop on blockers + - advance on valid state + - show packing progress + - render final result + +## Affected Artifacts + +- `docs/studio/specs/4. Assets Workspace Specification.md` if the wizard shell behavior needs more explicit normative wording +- `prometeu-studio/src/main/java/p/studio/workspaces/assets/AssetWorkspace.java` +- `prometeu-studio/src/main/java/p/studio/workspaces/assets/**` +- `prometeu-studio/src/main/java/p/studio/workspaces/assets/wizards/**` +- `prometeu-studio/src/main/java/p/studio/events/**` if new UI-local orchestration events are needed +- `prometeu-studio/src/main/resources/i18n/messages.properties` +- `prometeu-studio/src/test/java/p/studio/workspaces/assets/**` diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/PackerWorkspaceService.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/PackerWorkspaceService.java index ae264ed9..00148d44 100644 --- a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/PackerWorkspaceService.java +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/PackerWorkspaceService.java @@ -11,6 +11,12 @@ public interface PackerWorkspaceService { GetAssetActionsResult getAssetActions(GetAssetActionsRequest request); + PackWorkspaceSummaryResult getPackWorkspaceSummary(PackWorkspaceSummaryRequest request); + + ValidatePackWorkspaceResult validatePackWorkspace(ValidatePackWorkspaceRequest request); + + PackWorkspaceResult packWorkspace(PackWorkspaceRequest request); + CreateAssetResult createAsset(CreateAssetRequest request); RegisterAssetResult registerAsset(RegisterAssetRequest request); diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerEmittedArtifactDTO.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerEmittedArtifactDTO.java new file mode 100644 index 00000000..f1cbe32b --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerEmittedArtifactDTO.java @@ -0,0 +1,22 @@ +package p.packer.dtos; + +import java.nio.file.Path; +import java.util.Objects; + +public record PackerEmittedArtifactDTO( + String label, + Path path, + boolean canonical, + long sizeBytes) { + + public PackerEmittedArtifactDTO { + label = Objects.requireNonNull(label, "label").trim(); + path = Objects.requireNonNull(path, "path").toAbsolutePath().normalize(); + if (label.isBlank()) { + throw new IllegalArgumentException("label must not be blank"); + } + if (sizeBytes < 0L) { + throw new IllegalArgumentException("sizeBytes must not be negative"); + } + } +} diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackExecutionSummaryDTO.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackExecutionSummaryDTO.java new file mode 100644 index 00000000..414b9a83 --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackExecutionSummaryDTO.java @@ -0,0 +1,25 @@ +package p.packer.dtos; + +import java.util.List; +import java.util.Objects; + +public record PackerPackExecutionSummaryDTO( + String canonicalArtifactName, + int packedAssetCount, + long elapsedMillis, + List emittedArtifacts) { + + public PackerPackExecutionSummaryDTO { + canonicalArtifactName = Objects.requireNonNull(canonicalArtifactName, "canonicalArtifactName").trim(); + emittedArtifacts = List.copyOf(Objects.requireNonNull(emittedArtifacts, "emittedArtifacts")); + if (canonicalArtifactName.isBlank()) { + throw new IllegalArgumentException("canonicalArtifactName must not be blank"); + } + if (packedAssetCount < 0) { + throw new IllegalArgumentException("packedAssetCount must not be negative"); + } + if (elapsedMillis < 0L) { + throw new IllegalArgumentException("elapsedMillis must not be negative"); + } + } +} diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackSummaryDTO.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackSummaryDTO.java new file mode 100644 index 00000000..5437647e --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackSummaryDTO.java @@ -0,0 +1,22 @@ +package p.packer.dtos; + +import java.util.Objects; + +public record PackerPackSummaryDTO( + int includedRegisteredAssetCount, + int outsideBuildSetAssetCount, + String canonicalArtifactName) { + + public PackerPackSummaryDTO { + if (includedRegisteredAssetCount < 0) { + throw new IllegalArgumentException("includedRegisteredAssetCount must not be negative"); + } + if (outsideBuildSetAssetCount < 0) { + throw new IllegalArgumentException("outsideBuildSetAssetCount must not be negative"); + } + canonicalArtifactName = Objects.requireNonNull(canonicalArtifactName, "canonicalArtifactName").trim(); + if (canonicalArtifactName.isBlank()) { + throw new IllegalArgumentException("canonicalArtifactName must not be blank"); + } + } +} diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackValidationAssetDTO.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackValidationAssetDTO.java new file mode 100644 index 00000000..b3c54e4c --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackValidationAssetDTO.java @@ -0,0 +1,18 @@ +package p.packer.dtos; + +import java.util.List; +import java.util.Objects; + +public record PackerPackValidationAssetDTO( + PackerAssetSummaryDTO asset, + boolean blocked, + List diagnostics) { + + public PackerPackValidationAssetDTO { + Objects.requireNonNull(asset, "asset"); + diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); + if (blocked && diagnostics.stream().noneMatch(PackerDiagnosticDTO::blocking)) { + throw new IllegalArgumentException("blocked validation asset must include at least one blocking diagnostic"); + } + } +} diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackValidationSummaryDTO.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackValidationSummaryDTO.java new file mode 100644 index 00000000..e657895a --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerPackValidationSummaryDTO.java @@ -0,0 +1,26 @@ +package p.packer.dtos; + +public record PackerPackValidationSummaryDTO( + int totalAssetsInScope, + int validAssetCount, + int blockedAssetCount, + boolean canPack) { + + public PackerPackValidationSummaryDTO { + if (totalAssetsInScope < 0) { + throw new IllegalArgumentException("totalAssetsInScope must not be negative"); + } + if (validAssetCount < 0) { + throw new IllegalArgumentException("validAssetCount must not be negative"); + } + if (blockedAssetCount < 0) { + throw new IllegalArgumentException("blockedAssetCount must not be negative"); + } + if (validAssetCount + blockedAssetCount > totalAssetsInScope) { + throw new IllegalArgumentException("validAssetCount + blockedAssetCount must not exceed totalAssetsInScope"); + } + if (canPack && blockedAssetCount > 0) { + throw new IllegalArgumentException("canPack must be false when blockedAssetCount is greater than zero"); + } + } +} diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceRequest.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceRequest.java new file mode 100644 index 00000000..b63dbffd --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceRequest.java @@ -0,0 +1,11 @@ +package p.packer.messages; + +import java.util.Objects; + +public record PackWorkspaceRequest( + PackerProjectContext project) { + + public PackWorkspaceRequest { + Objects.requireNonNull(project, "project"); + } +} diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceResult.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceResult.java new file mode 100644 index 00000000..349c6342 --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceResult.java @@ -0,0 +1,20 @@ +package p.packer.messages; + +import p.packer.dtos.PackerPackExecutionSummaryDTO; + +import java.util.Objects; + +public record PackWorkspaceResult( + PackerOperationStatus status, + String summary, + PackerPackExecutionSummaryDTO result) { + + public PackWorkspaceResult { + Objects.requireNonNull(status, "status"); + summary = Objects.requireNonNull(summary, "summary").trim(); + Objects.requireNonNull(result, "result"); + if (summary.isBlank()) { + throw new IllegalArgumentException("summary must not be blank"); + } + } +} diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceSummaryRequest.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceSummaryRequest.java new file mode 100644 index 00000000..87944eb4 --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceSummaryRequest.java @@ -0,0 +1,11 @@ +package p.packer.messages; + +import java.util.Objects; + +public record PackWorkspaceSummaryRequest( + PackerProjectContext project) { + + public PackWorkspaceSummaryRequest { + Objects.requireNonNull(project, "project"); + } +} diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceSummaryResult.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceSummaryResult.java new file mode 100644 index 00000000..c269dbf1 --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/PackWorkspaceSummaryResult.java @@ -0,0 +1,20 @@ +package p.packer.messages; + +import p.packer.dtos.PackerPackSummaryDTO; + +import java.util.Objects; + +public record PackWorkspaceSummaryResult( + PackerOperationStatus status, + String summary, + PackerPackSummaryDTO packSummary) { + + public PackWorkspaceSummaryResult { + Objects.requireNonNull(status, "status"); + summary = Objects.requireNonNull(summary, "summary").trim(); + Objects.requireNonNull(packSummary, "packSummary"); + if (summary.isBlank()) { + throw new IllegalArgumentException("summary must not be blank"); + } + } +} diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ValidatePackWorkspaceRequest.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ValidatePackWorkspaceRequest.java new file mode 100644 index 00000000..b507e31a --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ValidatePackWorkspaceRequest.java @@ -0,0 +1,11 @@ +package p.packer.messages; + +import java.util.Objects; + +public record ValidatePackWorkspaceRequest( + PackerProjectContext project) { + + public ValidatePackWorkspaceRequest { + Objects.requireNonNull(project, "project"); + } +} diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ValidatePackWorkspaceResult.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ValidatePackWorkspaceResult.java new file mode 100644 index 00000000..892891d1 --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ValidatePackWorkspaceResult.java @@ -0,0 +1,24 @@ +package p.packer.messages; + +import p.packer.dtos.PackerPackValidationAssetDTO; +import p.packer.dtos.PackerPackValidationSummaryDTO; + +import java.util.List; +import java.util.Objects; + +public record ValidatePackWorkspaceResult( + PackerOperationStatus status, + String summary, + PackerPackValidationSummaryDTO validation, + List assets) { + + public ValidatePackWorkspaceResult { + Objects.requireNonNull(status, "status"); + summary = Objects.requireNonNull(summary, "summary").trim(); + Objects.requireNonNull(validation, "validation"); + assets = List.copyOf(Objects.requireNonNull(assets, "assets")); + if (summary.isBlank()) { + throw new IllegalArgumentException("summary must not be blank"); + } + } +} diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java index 525ca12f..8e4bd12c 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java @@ -139,6 +139,21 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe return actionReadService.getAssetActions(request); } + @Override + public PackWorkspaceSummaryResult getPackWorkspaceSummary(PackWorkspaceSummaryRequest request) { + throw new UnsupportedOperationException("pack workspace summary is not implemented yet"); + } + + @Override + public ValidatePackWorkspaceResult validatePackWorkspace(ValidatePackWorkspaceRequest request) { + throw new UnsupportedOperationException("pack workspace validation is not implemented yet"); + } + + @Override + public PackWorkspaceResult packWorkspace(PackWorkspaceRequest request) { + throw new UnsupportedOperationException("pack workspace execution is not implemented yet"); + } + @Override public CreateAssetResult createAsset(CreateAssetRequest request) { final CreateAssetRequest safeRequest = Objects.requireNonNull(request, "request");