feat: thothctl workspace preprocessing CLI and file-ingress contracts (P2)

This commit is contained in:
2026-08-11 18:40:11 +02:00
parent f7c2b69837
commit 17f2e48463
15 changed files with 1431 additions and 8 deletions
+52
View File
@@ -19,6 +19,7 @@ import (
"github.com/aritmolab/thothii/tools/thothctl/internal/output"
"github.com/aritmolab/thothii/tools/thothctl/internal/pi"
"github.com/aritmolab/thothii/tools/thothctl/internal/serverops"
"github.com/aritmolab/thothii/tools/thothctl/internal/workspaceops"
)
const usage = `Usage: thothctl --installation <absolute-path>/thothii-installation.yaml <command>
@@ -50,6 +51,14 @@ Commands:
pi maintenance recover --yes
Verify a terminal installation, remove stale lifecycle files, and clear maintenance.
pi logs Show the latest 200 sanitized core log lines (bounded; no follow mode).
workspace inspect --workspace ID [--json]
Inspect the active registry snapshot for one workspace.
workspace preprocess dwh --workspace ID [--resume RUN] [--json]
workspace schema suggest-fks --workspace ID [--from-sql FILE]... [--assume COLUMN=TABLE]... [--output FILE] [--json]
workspace schema check --workspace ID [--annotations FILE --reviewed-candidates sha256:HEX] [--json]
workspace index-schema --workspace ID [--json]
workspace preprocess evidence --workspace ID [--dry-run] [--resume RUN] [--json]
workspace preprocess run --workspace ID [--resume RUN] [--json]
`
func main() {
@@ -154,6 +163,8 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
}
fmt.Fprintf(stdout, "Removed %d stopped app containers; verified %d preserved paths.\n", len(removal.Targets), removal.Preserved)
return 0
case "workspace":
return workspaceCommand(ctx, installation, runner, commandArgs, secretValues, stdout, stderr)
default:
return commandUsageError(stderr, fmt.Sprintf("unknown command %q", command))
}
@@ -171,6 +182,47 @@ func writeRemovalTargets(outputWriter io.Writer, project string, targets []serve
}
}
func workspaceCommand(ctx context.Context, installation config.Installation, runner compose.Runner, args []string, secretValues []string, stdout, stderr io.Writer) int {
request, err := workspaceops.Parse(args)
if err != nil {
return commandUsageError(stderr, err.Error())
}
result, err := workspaceops.Execute(ctx, installation, runner, request)
if err != nil {
return workspaceFailure(stderr, err, secretValues)
}
if request.JSONMode() {
encoder := json.NewEncoder(stdout)
encoder.SetEscapeHTML(false)
if encodeErr := encoder.Encode(result); encodeErr != nil {
fmt.Fprintln(stderr, "thothctl: workspace result could not be written")
return 1
}
} else {
fmt.Fprint(stdout, workspaceops.Human(result))
}
switch result.Status {
case "blocked":
return 3
case "failed":
return 1
default:
return 0
}
}
func workspaceFailure(stderr io.Writer, err error, secretValues []string) int {
message := output.Sanitize(err.Error(), secretValues)
var operationErr *workspaceops.OperationError
if errors.As(err, &operationErr) && operationErr.Detail() != "" {
detail := output.SanitizeDetail(operationErr.Detail(), secretValues)
fmt.Fprintf(stderr, "thothctl: %s: %s\n", message, detail)
} else {
fmt.Fprintf(stderr, "thothctl: %s\n", message)
}
return 1
}
func serverOperationFailure(stderr io.Writer, err error, secretValues []string) int {
message := output.Sanitize(err.Error(), secretValues)
var operationErr *serverops.OperationError