fix(thothctl): harden workspace result and candidate publication

This commit is contained in:
2026-08-11 03:39:48 +02:00
parent ad80180381
commit fcc45520ad
8 changed files with 459 additions and 53 deletions
@@ -458,7 +458,19 @@ func validateIngress(payload []byte, expected inputEnvelope) error {
return nil
}
// ResultProjector is an optional host-side projection applied to the validated
// child result before any host-side candidate is published. It is deliberately
// a callback so workspaceops does not depend on the CLI's redaction policy.
type ResultProjector func(Result) (Result, error)
// Run preserves the original API for callers that do not need a public projection.
func Run(ctx context.Context, installation config.Installation, runner compose.Runner, command Command, stdin io.Reader) (Result, error) {
return RunWithProjector(ctx, installation, runner, command, stdin, nil)
}
// RunWithProjector validates the optional projected envelope before publishing
// any host export. This ordering is part of the workspace boundary contract.
func RunWithProjector(ctx context.Context, installation config.Installation, runner compose.Runner, command Command, stdin io.Reader, projector ResultProjector) (Result, error) {
env, generated, e := makeInput(command)
if e != nil {
return Result{}, e
@@ -536,6 +548,13 @@ func Run(ctx context.Context, installation config.Installation, runner compose.R
return Result{}, runErr
}
}
if projector != nil {
projected, projectErr := projector(result)
if projectErr != nil || validateResult(projected, env.WorkspaceID, operationName(command)) != nil {
return Result{}, errors.New("invalid workspace result")
}
result = projected
}
if hasExport {
if export == nil {
return Result{}, errors.New("invalid candidate export")