Add full shell, replaceable Omics adapter and bilingual interaction

Implement approved specification #32 and tickets #33-#37. Keep host authentication server-verified and pin session interaction language. Compile scoped base selectors for browser compatibility and retain full gutters during CSS pruning.
This commit is contained in:
Codex
2026-09-13 14:26:39 +02:00
parent 2d1b714ebe
commit d8a29bfbdd
207 changed files with 8570 additions and 2164 deletions
@@ -0,0 +1,77 @@
package main
import (
"bytes"
"context"
"os"
"path/filepath"
"testing"
"github.com/aritmolab/thothii/tools/tht/internal/config"
)
func TestInstallationGeneratePublishesShellWithoutDockerOrModelChanges(t *testing.T) {
fixture := newCLIFixture(t, "")
fixture.setEnvironment(t)
fixture.generateModelProjection(t)
descriptor, err := os.ReadFile(fixture.installationPath)
if err != nil {
t.Fatal(err)
}
descriptor = append(descriptor, []byte("shell:\n mode: full\n defaultLocale: fr-CA\n")...)
if err := os.WriteFile(fixture.installationPath, descriptor, 0o600); err != nil {
t.Fatal(err)
}
installation, err := config.Load(fixture.installationPath)
if err != nil {
t.Fatal(err)
}
preserved := make(map[string][]byte)
for _, path := range []string{fixture.installationPath, fixture.envFile, installation.GeneratedModelCatalogPath(), installation.GeneratedPiModelsPath(), installation.GeneratedPiSettingsPath()} {
preserved[path], err = os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
}
var stdout, stderr bytes.Buffer
args := []string{"--installation", fixture.installationPath, "installation", "generate"}
if code := run(context.Background(), args, &stdout, &stderr); code != 0 {
t.Fatalf("installation generate exit = %d: %s", code, stderr.String())
}
public, err := os.ReadFile(installation.GeneratedFrontendConfigPath())
if err != nil || !bytes.Contains(public, []byte(`"mode": "full"`)) || !bytes.Contains(public, []byte(`"defaultLocale": "fr-CA"`)) || bytes.Contains(public, []byte(`"adapter"`)) {
t.Fatalf("generated public shell = %s, %v", public, err)
}
for path, before := range preserved {
after, err := os.ReadFile(path)
if err != nil || !bytes.Equal(before, after) {
t.Fatalf("installation generate changed authored configuration or model projection %s: %v", path, err)
}
}
before, err := os.Stat(installation.GeneratedDirectory())
if err != nil {
t.Fatal(err)
}
if code := run(context.Background(), args, &stdout, &stderr); code != 0 {
t.Fatalf("repeated installation generate exit = %d: %s", code, stderr.String())
}
after, err := os.Stat(installation.GeneratedDirectory())
if err != nil || !os.SameFile(before, after) {
t.Fatalf("identical installation generate replaced the generation: %v", err)
}
if _, err := os.Stat(fixture.argsFile); !os.IsNotExist(err) {
t.Fatalf("installation generate invoked Docker: %v", err)
}
}
func TestInstallationGenerateRejectsArgumentsBeforePublication(t *testing.T) {
fixture := newCLIFixture(t, "")
fixture.setEnvironment(t)
var stdout, stderr bytes.Buffer
if code := run(context.Background(), []string{"--installation", fixture.installationPath, "installation", "generate", "--build"}, &stdout, &stderr); code != 2 {
t.Fatalf("exit = %d, want 2: %s", code, stderr.String())
}
if _, err := os.Stat(filepath.Join(fixture.root, "generated")); !os.IsNotExist(err) {
t.Fatalf("invalid arguments published projections: %v", err)
}
}
+38
View File
@@ -41,10 +41,13 @@ descriptor in the current project tree.
Commands:
setup [--configure-only] [--installation-id ID] [--profile local|server]
[--shell-mode full|embedded] [--shell-default-locale BCP47-TAG] [--shell-adapter omics-portal]
Create or validate the local non-secret installation configuration.
installation migrate --output PATH --session-default PROVIDER/MODEL
--embedding-id PROVIDER/MODEL --embedding-dimensions N
Create a review-only schema-v2 candidate from all three legacy model sources.
installation generate
Publish runtime projections from the descriptor without invoking Docker.
version [--json] Show the host CLI build identity.
auth configure --mode local|oidc ...
Configure local users or OIDC group mapping; see tht auth for exact options.
@@ -127,6 +130,9 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
return versionCommand(commandArgs, stdout, stderr)
}
if command == "installation" {
if len(commandArgs) > 0 && commandArgs[0] == "generate" {
return installationGenerationCommand(installationPath, commandArgs[1:], stdout, stderr)
}
return installationMigrationCommand(installationPath, commandArgs, stdout, stderr)
}
return commandUsageError(stderr, fmt.Sprintf("unknown command %q", command))
@@ -258,6 +264,32 @@ func isBootstrapCommand(command string) bool {
return command == "help" || command == "setup" || command == "version" || command == "installation"
}
func installationGenerationCommand(installationPath string, args []string, stdout, stderr io.Writer) int {
if len(args) != 0 {
return commandUsageError(stderr, "installation generate does not accept arguments")
}
workingDirectory, err := os.Getwd()
if err != nil {
return commandUsageError(stderr, "current directory is unavailable")
}
installationPath, err = config.Resolve(installationPath, os.Getenv, workingDirectory)
if err != nil {
fmt.Fprintf(stderr, "tht: %s\n", output.Sanitize(err.Error(), nil))
return 2
}
installation, err := config.Load(installationPath)
if err != nil {
fmt.Fprintf(stderr, "tht: %s\n", output.Sanitize(err.Error(), nil))
return 2
}
if err := modelprojection.Generate(installation); err != nil {
fmt.Fprintf(stderr, "tht: %s\n", output.Sanitize(err.Error(), nil))
return 2
}
fmt.Fprintf(stdout, "Runtime projections generated in %s.\n", installation.GeneratedDirectory())
return 0
}
func installationMigrationCommand(installationPath string, args []string, stdout, stderr io.Writer) int {
if installationPath == "" {
return commandUsageError(stderr, "installation migrate requires --installation with the legacy descriptor")
@@ -395,6 +427,12 @@ func parseSetupArgs(args []string) (setup.Request, error) {
target = &request.InstallationID
case "--profile":
target = &request.Profile
case "--shell-mode":
target = &request.Answers.ShellMode
case "--shell-default-locale":
target = &request.Answers.ShellDefaultLocale
case "--shell-adapter":
target = &request.Answers.ShellAdapter
case "--workspace-remote":
target = &request.Answers.WorkspaceRemote
case "--workspace-branch":
+4
View File
@@ -362,6 +362,7 @@ func TestParseSetupArgsAcceptsExplicitNonInteractiveAnswers(t *testing.T) {
"--workspace-remote", "https://git.example.invalid/workspaces.git", "--workspace-branch", "release",
"--workspace-access", "https", "--secrets-file", "/tmp/thothii.secrets", "--pi-auth-file", "/tmp/pi-auth.json",
"--git-credentials-file", "/tmp/git-credentials", "--git-ca-file", "/tmp/git-ca.pem",
"--shell-mode", "full", "--shell-default-locale", "it", "--shell-adapter", "omics-portal",
})
if err != nil {
t.Fatal(err)
@@ -372,6 +373,9 @@ func TestParseSetupArgsAcceptsExplicitNonInteractiveAnswers(t *testing.T) {
if request.Answers.WorkspaceBranch != "release" || request.Answers.GitCAFile != "/tmp/git-ca.pem" {
t.Fatalf("setup answers = %#v", request.Answers)
}
if request.Answers.ShellMode != "full" || request.Answers.ShellDefaultLocale != "it" || request.Answers.ShellAdapter != "omics-portal" {
t.Fatalf("setup shell answers = %#v", request.Answers)
}
}
func TestRunPiStatusUsesInstallationEnvironmentWithoutFlag(t *testing.T) {