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
+238
View File
@@ -0,0 +1,238 @@
// SPDX-License-Identifier: AGPL-3.0-only
package releaseartifact
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"time"
"unicode/utf8"
)
const (
evidenceSchema = "himesan.release-evidence.v1"
evidenceManifestName = "RELEASE-EVIDENCE.json"
maximumEvidenceSize = 1 << 20
)
var requiredEvidenceFiles = []string{
"benchmark-methodology.md",
"development-supervisor.md",
"legal-review.md",
"native-platforms.md",
"security.md",
"signing-and-recovery.md",
"vanity-imports.md",
}
// EvidenceIdentity binds human-reviewed release evidence to one source state.
type EvidenceIdentity struct {
Repository string
Version string
Commit string
Tree string
ReviewedBy string
ReviewedAt string
}
type evidenceManifest struct {
SchemaVersion string `json:"schema_version"`
Repository string `json:"repository"`
Version string `json:"version"`
Commit string `json:"commit"`
Tree string `json:"tree"`
ReviewStatus string `json:"review_status"`
ReviewedBy string `json:"reviewed_by"`
ReviewedAt string `json:"reviewed_at"`
Files []evidenceFile `json:"files"`
}
type evidenceFile struct {
Path string `json:"path"`
SHA256 string `json:"sha256"`
}
// RequiredEvidenceFiles returns the fixed v1 human-review document set.
func RequiredEvidenceFiles() []string {
return append([]string(nil), requiredEvidenceFiles...)
}
// WriteEvidenceManifest seals the reviewed evidence documents without
// modifying them. The manifest is immutable and fails if it already exists.
func WriteEvidenceManifest(directory string, identity EvidenceIdentity) (string, error) {
if err := validateEvidenceIdentity(identity, true); err != nil {
return "", err
}
files, err := inspectEvidenceFiles(directory)
if err != nil {
return "", err
}
manifest := evidenceManifest{
SchemaVersion: evidenceSchema,
Repository: identity.Repository,
Version: identity.Version,
Commit: identity.Commit,
Tree: identity.Tree,
ReviewStatus: "reviewed",
ReviewedBy: identity.ReviewedBy,
ReviewedAt: identity.ReviewedAt,
Files: files,
}
contents, err := indentedJSON(manifest)
if err != nil {
return "", err
}
path := filepath.Join(directory, evidenceManifestName)
if err := writeExclusive(path, contents, 0o444); err != nil {
return "", fmt.Errorf("write release evidence manifest: %w", err)
}
return digest(contents), nil
}
// VerifyEvidenceManifest proves that the sealed evidence is for the expected
// source state and that every reviewed byte remains unchanged.
func VerifyEvidenceManifest(directory string, expected EvidenceIdentity) error {
if err := validateEvidenceIdentity(expected, false); err != nil {
return err
}
manifestPath := filepath.Join(directory, evidenceManifestName)
contents, err := readEvidenceFile(manifestPath, true)
if err != nil {
return fmt.Errorf("read release evidence manifest: %w", err)
}
decoder := json.NewDecoder(bytes.NewReader(contents))
decoder.DisallowUnknownFields()
var manifest evidenceManifest
if err := decoder.Decode(&manifest); err != nil {
return fmt.Errorf("decode release evidence manifest: %w", err)
}
if err := requireJSONEOF(decoder); err != nil {
return fmt.Errorf("decode release evidence manifest: %w", err)
}
if manifest.SchemaVersion != evidenceSchema {
return fmt.Errorf("release evidence schema = %q, want %q", manifest.SchemaVersion, evidenceSchema)
}
if manifest.ReviewStatus != "reviewed" {
return errors.New("release evidence review status must be reviewed")
}
actual := EvidenceIdentity{
Repository: manifest.Repository,
Version: manifest.Version,
Commit: manifest.Commit,
Tree: manifest.Tree,
ReviewedBy: manifest.ReviewedBy,
ReviewedAt: manifest.ReviewedAt,
}
if err := validateEvidenceIdentity(actual, true); err != nil {
return err
}
if actual.Repository != expected.Repository || actual.Version != expected.Version ||
actual.Commit != expected.Commit || actual.Tree != expected.Tree {
return errors.New("release evidence repository, version, commit, or tree does not match the candidate")
}
observed, err := inspectEvidenceFiles(directory)
if err != nil {
return err
}
if len(manifest.Files) != len(observed) {
return fmt.Errorf("release evidence manifest contains %d files, want %d", len(manifest.Files), len(observed))
}
for index := range observed {
if manifest.Files[index] != observed[index] {
return fmt.Errorf("release evidence file %q is missing, reordered, or has a changed digest", observed[index].Path)
}
}
return nil
}
func inspectEvidenceFiles(directory string) ([]evidenceFile, error) {
info, err := os.Lstat(directory)
if err != nil {
return nil, fmt.Errorf("inspect evidence directory: %w", err)
}
if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
return nil, errors.New("evidence directory must be a real directory, not a symlink")
}
files := make([]evidenceFile, 0, len(requiredEvidenceFiles))
for _, name := range requiredEvidenceFiles {
contents, err := readEvidenceFile(filepath.Join(directory, name), false)
if err != nil {
return nil, fmt.Errorf("inspect %s: %w", name, err)
}
if !utf8.Valid(contents) || bytes.IndexByte(contents, 0) >= 0 {
return nil, fmt.Errorf("%s must be NUL-free UTF-8 text", name)
}
if !strings.Contains(string(contents), "# ") {
return nil, fmt.Errorf("%s must contain a Markdown heading", name)
}
files = append(files, evidenceFile{Path: name, SHA256: digest(contents)})
}
sort.Slice(files, func(i, j int) bool { return files[i].Path < files[j].Path })
return files, nil
}
func readEvidenceFile(path string, manifest bool) ([]byte, error) {
info, err := os.Lstat(path)
if err != nil {
return nil, err
}
if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
return nil, errors.New("must be a regular file, not a symlink")
}
if info.Size() == 0 || info.Size() > maximumEvidenceSize {
return nil, fmt.Errorf("size must be between 1 and %d bytes", maximumEvidenceSize)
}
contents, err := os.ReadFile(path)
if err != nil {
return nil, err
}
if manifest && len(contents) > maximumEvidenceSize {
return nil, errors.New("manifest exceeds the size limit")
}
return contents, nil
}
func validateEvidenceIdentity(identity EvidenceIdentity, requireReview bool) error {
if strings.TrimSpace(identity.Repository) == "" || strings.ContainsAny(identity.Repository, "\r\n\x00") {
return errors.New("evidence repository is required and must be one line")
}
if !releaseVersionPattern.MatchString(identity.Version) {
return errors.New("evidence version must be a canonical v-prefixed semantic version")
}
if !gitObjectPattern.MatchString(identity.Commit) || !gitObjectPattern.MatchString(identity.Tree) {
return errors.New("evidence commit and tree must be full lowercase Git object identities")
}
if !requireReview {
return nil
}
if strings.TrimSpace(identity.ReviewedBy) == "" || strings.ContainsAny(identity.ReviewedBy, "\r\n\x00") {
return errors.New("evidence reviewer is required and must be one line")
}
reviewedAt, err := time.Parse(time.RFC3339, identity.ReviewedAt)
if err != nil {
return fmt.Errorf("evidence review time must be RFC3339: %w", err)
}
if reviewedAt.After(time.Now().UTC().Add(5 * time.Minute)) {
return errors.New("evidence review time cannot be in the future")
}
return nil
}
func requireJSONEOF(decoder *json.Decoder) error {
var extra any
if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) {
if err == nil {
return errors.New("multiple JSON values are not allowed")
}
return err
}
return nil
}