packer (WIP)

This commit is contained in:
bQUARKz 2026-03-19 17:58:35 +00:00
parent c5b10907c9
commit 83546149e4
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
14 changed files with 397 additions and 0 deletions

View File

@ -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/**`

View File

@ -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);

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -0,0 +1,11 @@
package p.packer.messages;
import java.util.Objects;
public record PackWorkspaceRequest(
PackerProjectContext project) {
public PackWorkspaceRequest {
Objects.requireNonNull(project, "project");
}
}

View File

@ -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");
}
}
}

View File

@ -0,0 +1,11 @@
package p.packer.messages;
import java.util.Objects;
public record PackWorkspaceSummaryRequest(
PackerProjectContext project) {
public PackWorkspaceSummaryRequest {
Objects.requireNonNull(project, "project");
}
}

View File

@ -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");
}
}
}

View File

@ -0,0 +1,11 @@
package p.packer.messages;
import java.util.Objects;
public record ValidatePackWorkspaceRequest(
PackerProjectContext project) {
public ValidatePackWorkspaceRequest {
Objects.requireNonNull(project, "project");
}
}

View File

@ -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");
}
}
}

View File

@ -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");