packer (WIP)
This commit is contained in:
parent
c5b10907c9
commit
83546149e4
@ -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/**`
|
||||
@ -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);
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<PackerEmittedArtifactDTO> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package p.packer.dtos;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public record PackerPackValidationAssetDTO(
|
||||
PackerAssetSummaryDTO asset,
|
||||
boolean blocked,
|
||||
List<PackerDiagnosticDTO> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package p.packer.messages;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record PackWorkspaceRequest(
|
||||
PackerProjectContext project) {
|
||||
|
||||
public PackWorkspaceRequest {
|
||||
Objects.requireNonNull(project, "project");
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package p.packer.messages;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record PackWorkspaceSummaryRequest(
|
||||
PackerProjectContext project) {
|
||||
|
||||
public PackWorkspaceSummaryRequest {
|
||||
Objects.requireNonNull(project, "project");
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package p.packer.messages;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record ValidatePackWorkspaceRequest(
|
||||
PackerProjectContext project) {
|
||||
|
||||
public ValidatePackWorkspaceRequest {
|
||||
Objects.requireNonNull(project, "project");
|
||||
}
|
||||
}
|
||||
@ -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<PackerPackValidationAssetDTO> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user