Prepare Sandwich Hime v1 release candidate source

This commit is contained in:
2026-08-23 16:25:02 -04:00
parent c11552b87a
commit efd5b997ed
64 changed files with 5276 additions and 156 deletions
+213
View File
@@ -0,0 +1,213 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Command himesan-release creates deterministic unsigned artifacts and native
// verification receipts. Signing and notarization intentionally remain outside
// this command and outside unattended runner authority.
package main
import (
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"gamertan.com/sandwich-hime/internal/releaseartifact"
)
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintf(os.Stderr, "himesan-release: %v\n", err)
os.Exit(1)
}
}
func run(arguments []string) error {
if len(arguments) == 0 {
return errors.New("usage: himesan-release <package|receipt|evidence-manifest|verify-evidence|verify-native|extract-macos|finalize-macos> [options]")
}
switch arguments[0] {
case "package":
return runPackage(arguments[1:])
case "receipt":
return runReceipt(arguments[1:])
case "evidence-manifest":
return runEvidenceManifest(arguments[1:])
case "verify-evidence":
return runVerifyEvidence(arguments[1:])
case "verify-native":
return runVerifyNative(arguments[1:])
case "extract-macos":
return runExtractMacOS(arguments[1:])
case "finalize-macos":
return runFinalizeMacOS(arguments[1:])
default:
return fmt.Errorf("unknown command %q", arguments[0])
}
}
func runVerifyNative(arguments []string) error {
flags := flag.NewFlagSet("verify-native", flag.ContinueOnError)
var directory string
var expected releaseartifact.NativeReceiptExpectation
flags.StringVar(&directory, "directory", "", "four-lane native receipt directory")
flags.StringVar(&expected.Repository, "repository", "", "repository identity")
flags.StringVar(&expected.Commit, "commit", "", "source commit")
flags.StringVar(&expected.Tree, "tree", "", "source tree")
if err := flags.Parse(arguments); err != nil {
return err
}
summary, err := releaseartifact.VerifyNativeReceiptSet(directory, expected)
if err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode(summary)
}
func runExtractMacOS(arguments []string) error {
flags := flag.NewFlagSet("extract-macos", flag.ContinueOnError)
var archive, checksum, output string
flags.StringVar(&archive, "archive", "", "unsigned Darwin/arm64 archive")
flags.StringVar(&checksum, "sha256", "", "approved archive SHA-256")
flags.StringVar(&output, "output", "", "empty extraction parent directory")
if err := flags.Parse(arguments); err != nil {
return err
}
root, err := releaseartifact.ExtractVerifiedMacOSPackage(archive, checksum, output)
if err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode(map[string]string{"root": root, "unsigned_archive_sha256": checksum})
}
func runFinalizeMacOS(arguments []string) error {
flags := flag.NewFlagSet("finalize-macos", flag.ContinueOnError)
var options releaseartifact.MacOSSigningOptions
flags.StringVar(&options.Directory, "directory", "", "extracted signed distribution directory")
flags.StringVar(&options.UnsignedArchiveSHA256, "unsigned-archive-sha256", "", "approved unsigned archive SHA-256")
flags.StringVar(&options.Identity, "identity", "", "Developer ID identity")
flags.StringVar(&options.Identifier, "identifier", "", "signed binary identifier")
flags.StringVar(&options.FinalizedAt, "finalized-at", "", "RFC3339 finalization time")
if err := flags.Parse(arguments); err != nil {
return err
}
if err := releaseartifact.FinalizeSignedMacOSDistribution(options); err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode(map[string]any{"valid": true, "directory": options.Directory})
}
func runEvidenceManifest(arguments []string) error {
flags := flag.NewFlagSet("evidence-manifest", flag.ContinueOnError)
var directory string
var identity releaseartifact.EvidenceIdentity
flags.StringVar(&directory, "directory", "", "reviewed evidence directory")
bindEvidenceIdentityFlags(flags, &identity, true)
if err := flags.Parse(arguments); err != nil {
return err
}
checksum, err := releaseartifact.WriteEvidenceManifest(directory, identity)
if err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode(map[string]string{"manifest": filepath.Join(directory, "RELEASE-EVIDENCE.json"), "sha256": checksum})
}
func runVerifyEvidence(arguments []string) error {
flags := flag.NewFlagSet("verify-evidence", flag.ContinueOnError)
var directory string
var identity releaseartifact.EvidenceIdentity
flags.StringVar(&directory, "directory", "", "sealed evidence directory")
bindEvidenceIdentityFlags(flags, &identity, false)
if err := flags.Parse(arguments); err != nil {
return err
}
if err := releaseartifact.VerifyEvidenceManifest(directory, identity); err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode(map[string]any{"valid": true, "files": releaseartifact.RequiredEvidenceFiles()})
}
func bindEvidenceIdentityFlags(flags *flag.FlagSet, identity *releaseartifact.EvidenceIdentity, review bool) {
flags.StringVar(&identity.Repository, "repository", "", "canonical repository identity")
flags.StringVar(&identity.Version, "version", "", "candidate semantic version")
flags.StringVar(&identity.Commit, "commit", "", "source commit")
flags.StringVar(&identity.Tree, "tree", "", "source tree")
if review {
flags.StringVar(&identity.ReviewedBy, "reviewed-by", "", "human reviewer identity")
flags.StringVar(&identity.ReviewedAt, "reviewed-at", "", "RFC3339 review time")
}
}
func runPackage(arguments []string) error {
flags := flag.NewFlagSet("package", flag.ContinueOnError)
var options releaseartifact.PackageOptions
flags.StringVar(&options.Version, "version", "", "candidate semantic version")
flags.StringVar(&options.Commit, "commit", "", "source commit")
flags.StringVar(&options.Tree, "tree", "", "source tree")
flags.StringVar(&options.GoVersion, "go-version", "", "Go toolchain identity")
flags.StringVar(&options.GOOS, "goos", "", "target operating system")
flags.StringVar(&options.GOARCH, "goarch", "", "target architecture")
flags.StringVar(&options.BinaryPath, "binary", "", "unsigned native binary")
flags.StringVar(&options.LicensePath, "license", "LICENSE", "license text")
flags.StringVar(&options.ReleaseNotes, "release-notes", "RELEASE.md", "release notes")
flags.StringVar(&options.OutputDirectory, "output", "", "output directory")
flags.Int64Var(&options.SourceDateEpoch, "source-date-epoch", 0, "fixed Unix timestamp")
if err := flags.Parse(arguments); err != nil {
return err
}
result, err := releaseartifact.Package(options)
if err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode(result)
}
func runReceipt(arguments []string) error {
flags := flag.NewFlagSet("receipt", flag.ContinueOnError)
var receipt releaseartifact.Receipt
var output, gates, generatedFiles string
flags.StringVar(&output, "output", "", "receipt output path")
flags.StringVar(&receipt.Repository, "repository", "", "repository identity")
flags.StringVar(&receipt.Commit, "commit", "", "source commit")
flags.StringVar(&receipt.Tree, "tree", "", "source tree")
flags.StringVar(&receipt.GOOS, "goos", "", "native operating system")
flags.StringVar(&receipt.GOARCH, "goarch", "", "native architecture")
flags.StringVar(&receipt.GoVersion, "go-version", "", "Go toolchain identity")
flags.StringVar(&receipt.RunnerVersion, "runner-version", "", "Gitea Runner version")
flags.StringVar(&receipt.RunnerName, "runner-name", "", "runner identity")
flags.StringVar(&receipt.UnsignedArtifactSHA, "artifact-sha256", "", "optional unsigned artifact digest")
flags.StringVar(&receipt.CompletedAt, "completed-at", "", "RFC3339 completion time")
flags.StringVar(&gates, "gates", "", "comma-separated successful gates")
flags.StringVar(&generatedFiles, "generated-files", "", "comma-separated generated output paths")
if err := flags.Parse(arguments); err != nil {
return err
}
if output == "" {
return errors.New("output is required")
}
receipt.SuccessfulGates = splitList(gates)
digest, err := releaseartifact.DigestFiles(splitList(generatedFiles))
if err != nil {
return err
}
receipt.GeneratedDigest = digest
checksum, err := releaseartifact.WriteReceipt(output, receipt)
if err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode(map[string]string{"receipt": output, "sha256": checksum})
}
func splitList(value string) []string {
var values []string
for _, item := range strings.Split(value, ",") {
item = strings.TrimSpace(item)
if item != "" {
values = append(values, item)
}
}
return values
}
+145
View File
@@ -0,0 +1,145 @@
// SPDX-License-Identifier: AGPL-3.0-only
package main
import (
"bytes"
"context"
"encoding/json"
"os"
"path/filepath"
"reflect"
"sort"
"testing"
"gamertan.com/sandwich-hime/internal/testpath"
)
type contractSchema struct {
AdditionalProperties bool `json:"additionalProperties"`
Required []string `json:"required"`
Properties map[string]json.RawMessage `json:"properties"`
}
func TestV1CLIHelpContract(t *testing.T) {
t.Parallel()
want, err := os.ReadFile(filepath.Join("..", "..", "contracts", "himesan-cli-help-v1.txt"))
if err != nil {
t.Fatal(err)
}
want = bytes.TrimPrefix(want, []byte("# SPDX-License-Identifier: AGPL-3.0-only\n\n"))
var output bytes.Buffer
printHelp(&output)
if !bytes.Equal(output.Bytes(), want) {
t.Fatalf("CLI help contract drifted\n--- want ---\n%s--- got ---\n%s", want, output.Bytes())
}
}
func TestV1VersionJSONSchemaMatchesOutput(t *testing.T) {
t.Parallel()
schema := readContractSchema(t, "himesan-version-output-v1.schema.json")
var stdout, stderr bytes.Buffer
if code := run(context.Background(), []string{"version", "--json"}, &stdout, &stderr); code != 0 {
t.Fatalf("version exit code = %d: %s", code, stderr.String())
}
var output map[string]any
if err := json.Unmarshal(stdout.Bytes(), &output); err != nil {
t.Fatal(err)
}
assertObjectShape(t, output, schema, "version output")
}
func TestV1OperationJSONSchemaMatchesSuccessAndDiagnosticOutput(t *testing.T) {
t.Parallel()
schema := readContractSchema(t, "himesan-operation-output-v1.schema.json")
directory := testpath.TempDir(t)
source := filepath.Join(directory, "page.sando")
if err := os.WriteFile(source, []byte("<?sando go\npackage views\nfunc Page()\n?>\n<p>page</p>\n"), 0o600); err != nil {
t.Fatal(err)
}
var stdout, stderr bytes.Buffer
if code := run(context.Background(), []string{"check", "--json", source}, &stdout, &stderr); code != 1 {
t.Fatalf("missing-output check exit code = %d, want 1: %s", code, stderr.String())
}
var output map[string]any
if err := json.Unmarshal(stdout.Bytes(), &output); err != nil {
t.Fatal(err)
}
assertObjectShape(t, output, schema, "operation output")
resultSchema := nestedSchema(t, schema.Properties["result"])
result, ok := output["result"].(map[string]any)
if !ok {
t.Fatalf("result = %T, want object", output["result"])
}
assertObjectShape(t, result, resultSchema, "operation result")
files, ok := result["files"].([]any)
if !ok || len(files) != 1 {
t.Fatalf("files = %#v, want one item", result["files"])
}
filesProperty := rawObject(t, resultSchema.Properties["files"])
fileSchema := nestedSchema(t, filesProperty["items"])
assertObjectShape(t, files[0].(map[string]any), fileSchema, "file result")
diagnostics, ok := result["diagnostics"].([]any)
if !ok || len(diagnostics) == 0 {
t.Fatalf("diagnostics = %#v, want at least one item", result["diagnostics"])
}
diagnosticsProperty := rawObject(t, resultSchema.Properties["diagnostics"])
diagnosticSchema := nestedSchema(t, diagnosticsProperty["items"])
assertObjectShape(t, diagnostics[0].(map[string]any), diagnosticSchema, "diagnostic")
}
func readContractSchema(t *testing.T, name string) contractSchema {
t.Helper()
contents, err := os.ReadFile(filepath.Join("..", "..", "contracts", name))
if err != nil {
t.Fatal(err)
}
var schema contractSchema
if err := json.Unmarshal(contents, &schema); err != nil {
t.Fatalf("decode %s: %v", name, err)
}
if schema.AdditionalProperties || len(schema.Properties) == 0 {
t.Fatalf("%s is not a closed object schema", name)
}
return schema
}
func nestedSchema(t *testing.T, raw json.RawMessage) contractSchema {
t.Helper()
var schema contractSchema
if err := json.Unmarshal(raw, &schema); err != nil {
t.Fatal(err)
}
return schema
}
func rawObject(t *testing.T, raw json.RawMessage) map[string]json.RawMessage {
t.Helper()
var object map[string]json.RawMessage
if err := json.Unmarshal(raw, &object); err != nil {
t.Fatal(err)
}
return object
}
func assertObjectShape(t *testing.T, actual map[string]any, schema contractSchema, label string) {
t.Helper()
actualKeys := make([]string, 0, len(actual))
for key := range actual {
actualKeys = append(actualKeys, key)
if _, declared := schema.Properties[key]; !declared {
t.Fatalf("%s emitted undeclared property %q", label, key)
}
}
sort.Strings(actualKeys)
for _, required := range schema.Required {
if _, present := actual[required]; !present {
t.Fatalf("%s omitted required property %q (got %v)", label, required, actualKeys)
}
}
if len(actual) == 0 || reflect.ValueOf(actual).IsNil() {
t.Fatalf("%s is empty", label)
}
}
+2 -1
View File
@@ -12,6 +12,7 @@ import (
"testing"
"gamertan.com/sandwich-hime/internal/compiler"
"gamertan.com/sandwich-hime/internal/testpath"
)
func TestRunHelpVersionAndUnknownCommand(t *testing.T) {
@@ -67,7 +68,7 @@ func TestRunHelpVersionAndUnknownCommand(t *testing.T) {
func TestGenerateCheckBlessAndJSONDiagnostics(t *testing.T) {
t.Parallel()
directory := t.TempDir()
directory := testpath.TempDir(t)
sourcePath := filepath.Join(directory, "hello.sando")
source := "<?sando go\npackage views\nfunc Hello(name string)\n?>\n<p><?= name ?></p>\n"
if err := os.WriteFile(sourcePath, []byte(source), 0o600); err != nil {