Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions .github/tests/go_source_metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package main

import (
"encoding/json"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path"
"sort"
"strconv"
)

const kernelPackage = "github.com/operatorstack/boatstack/boatstack/kernel"

var runtimeSymbols = map[string]bool{
"NewRuntime": true,
"Runtime": true,
"Store": true,
"Domain": true,
"Operator": true,
"Receipt": true,
}

type metadata struct {
Path string `json:"path"`
Imports []string `json:"imports"`
RuntimeConsumers []string `json:"runtime_consumers"`
}

func main() {
results := make([]metadata, 0, len(os.Args)-1)
for _, filename := range os.Args[1:] {
if filename == "--" {
continue
}
parsed, err := parser.ParseFile(token.NewFileSet(), filename, nil, parser.AllErrors)
if err != nil {
fatalf("parse %s: %v", filename, err)
}
item := metadata{Path: filename, Imports: []string{}, RuntimeConsumers: []string{}}
kernelAliases := map[string]bool{}
for _, spec := range parsed.Imports {
importPath, err := strconv.Unquote(spec.Path.Value)
if err != nil {
fatalf("decode import in %s: %v", filename, err)
}
item.Imports = append(item.Imports, importPath)
if importPath != kernelPackage || (spec.Name != nil && spec.Name.Name == "_") {
continue
}
alias := path.Base(importPath)
if spec.Name != nil {
alias = spec.Name.Name
}
kernelAliases[alias] = true
}
ast.Inspect(parsed, func(node ast.Node) bool {
switch value := node.(type) {
case *ast.SelectorExpr:
alias, ok := value.X.(*ast.Ident)
if ok && kernelAliases[alias.Name] && runtimeSymbols[value.Sel.Name] {
item.RuntimeConsumers = append(item.RuntimeConsumers, value.Sel.Name)
}
case *ast.Ident:
if kernelAliases["."] && runtimeSymbols[value.Name] {
item.RuntimeConsumers = append(item.RuntimeConsumers, value.Name)
}
}
return true
})
sort.Strings(item.Imports)
sort.Strings(item.RuntimeConsumers)
results = append(results, item)
}
if err := json.NewEncoder(os.Stdout).Encode(results); err != nil {
fatalf("encode metadata: %v", err)
}
}

func fatalf(format string, args ...any) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
os.Exit(1)
}
110 changes: 108 additions & 2 deletions .github/tests/test_repository_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@
CONFIG = REPO / "project.example.json"


def go_source_metadata(paths: list[Path]) -> list[dict[str, object]]:
result = subprocess.run(
[
"go",
"run",
str(REPO / ".github" / "tests" / "go_source_metadata.go"),
"--",
*map(str, paths),
],
cwd=REPO,
text=True,
capture_output=True,
)
if result.returncode != 0:
raise RuntimeError(result.stdout + result.stderr)
return json.loads(result.stdout)


class RepositoryContract(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
Expand Down Expand Up @@ -174,6 +192,7 @@ def test_codex_review_is_secret_scoped_read_only_and_structured(self) -> None:
self.assertFalse((REPO / "UPSTREAM.json").exists())

def test_release_builds_six_checksum_bound_v2_runtimes(self) -> None:
ci = (REPO / ".github" / "workflows" / "ci.yml").read_text()
release = (REPO / ".github" / "workflows" / "release.yml").read_text()
automatic = (REPO / ".github" / "workflows" / "auto-release.yml").read_text()
for asset in (
Expand All @@ -189,7 +208,10 @@ def test_release_builds_six_checksum_bound_v2_runtimes(self) -> None:
self.assertIn(symbol, release)
self.assertIn('source_commit="$(git rev-parse HEAD)"', release)
self.assertIn("sha256sum", release)
self.assertIn('workflows: ["Verify Boatstack distribution"]', automatic)
ci_name = re.search(r"(?m)^name:\s*(.+?)\s*$", ci)
self.assertIsNotNone(ci_name)
self.assertEqual(ci_name.group(1), "CI")
self.assertIn(f'workflows: ["{ci_name.group(1)}"]', automatic)

def test_manual_release_is_prerelease_only_and_exact_source_bound(self) -> None:
# control-law: branch-prerelease-publishes-only-an-exact-new-rc-source
Expand Down Expand Up @@ -387,13 +409,45 @@ def test_public_tree_excludes_private_context_and_v1_operating_guidance(self) ->

def test_general_kernel_is_domain_neutral_and_owns_shared_control_laws(self) -> None:
kernel = REPO / "boatstack" / "kernel"
source = "\n".join(path.read_text() for path in sorted(kernel.glob("*.go")))
kernel_files = sorted(kernel.glob("*.go"))
source = "\n".join(path.read_text() for path in kernel_files)
for token in (
"git", "repository", "worktree", "branch", "pull request",
"coding agent", "publication",
):
self.assertNotIn(token, source.lower(), token)

boatstack_packages = "github.com/operatorstack/boatstack/boatstack/"
kernel_package = boatstack_packages + "kernel"
kernel_metadata = go_source_metadata(kernel_files)
for path, metadata in zip(kernel_files, kernel_metadata, strict=True):
invalid = [
import_path
for import_path in metadata["imports"]
if import_path.startswith(boatstack_packages)
and import_path != kernel_package
]
self.assertEqual([], invalid, f"kernel dependency direction: {path}")

kernel_tests = sorted(kernel.glob("*_test.go"))
test_source = "\n".join(path.read_text() for path in kernel_tests)
self.assertNotRegex(test_source, r'\bexec\.Command\(\s*"git"')
self.assertNotRegex(
test_source,
r'\bexec\.CommandContext\([^,\n]+,\s*"git"',
)
self.assertNotIn("testRepository", test_source)
self.assertNotIn("softwaredelivery", test_source.lower())
test_metadata = go_source_metadata(kernel_tests)
for path, metadata in zip(kernel_tests, test_metadata, strict=True):
self.assertFalse(
any(
"/softwaredelivery" in import_path
for import_path in metadata["imports"]
),
f"kernel test fixture imports software delivery: {path}",
)

runtime = (kernel / "runtime.go").read_text()
software_relation = (
REPO / "boatstack" / "internal" / "softwaredelivery" /
Expand Down Expand Up @@ -431,6 +485,58 @@ def test_general_kernel_is_domain_neutral_and_owns_shared_control_laws(self) ->
"./internal/effects", "./internal/surfaces",
):
self.assertNotIn(retired, component_ci)
self.assertIn("run: go test -race ./...", component_ci)
self.assertIn(
"run: go test -race ./kernel ./internal/softwaredelivery/effects",
component_ci,
)

def test_go_import_parser_accepts_legal_import_forms(self) -> None:
with tempfile.TemporaryDirectory() as directory:
commented = Path(directory) / "commented.go"
commented.write_text(
'package consumer\n\nimport /* migration T5 */ '
'"github.com/operatorstack/boatstack/boatstack/kernel"\n'
)
line_broken = Path(directory) / "line_broken.go"
line_broken.write_text(
'package consumer\n\nimport\n b "\\u0067ithub.com/operatorstack/'
'boatstack/boatstack/internal/buildinfo"\n'
)
self.assertEqual(
[
"github.com/operatorstack/boatstack/boatstack/kernel",
"github.com/operatorstack/boatstack/boatstack/internal/buildinfo",
],
[
metadata["imports"][0]
for metadata in go_source_metadata([commented, line_broken])
],
)

@unittest.expectedFailure
def test_kernel_runtime_has_no_production_consumer_yet(self) -> None:
# Migration task T5 must replace this marker with a permanent positive
# production-reachability assertion when the generic runtime is adopted.
production_files = [
path
for path in sorted((REPO / "boatstack").rglob("*.go"))
if not path.name.endswith("_test.go")
and path.parent != REPO / "boatstack" / "kernel"
]
consumers = [
str(path.relative_to(REPO))
for path, metadata in zip(
production_files,
go_source_metadata(production_files),
strict=True,
)
if metadata["runtime_consumers"]
]
self.assertTrue(
consumers,
"migration T5 has not connected the generic kernel runtime to production code",
)

def test_documented_cli_verbs_are_registered_v2_surfaces(self) -> None:
documents = [
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/auto-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Publish verified Boatstack release

on:
workflow_run:
workflows: ["Verify Boatstack distribution"]
workflows: ["CI"]
types: [completed]

permissions:
Expand Down
20 changes: 19 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Boatstack-owned control plane.
name: Verify Boatstack distribution
name: CI
Comment thread
bigboateng marked this conversation as resolved.

on:
pull_request:
Expand Down Expand Up @@ -60,7 +60,12 @@ jobs:
- name: Show toolchain
run: go version
working-directory: boatstack
- name: Test runtime with race detector
if: matrix.os == 'ubuntu-latest'
run: go test -race ./...
working-directory: boatstack
- name: Test runtime
if: matrix.os == 'macos-latest'
run: go test ./...
working-directory: boatstack
- name: Build helper
Expand All @@ -70,6 +75,19 @@ jobs:
shell: bash
run: bash -n install.sh

race-critical:
name: race-critical
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: boatstack/go.mod
cache-dependency-path: boatstack/go.sum
- name: Test concurrency-bearing packages
working-directory: boatstack
run: go test -race ./kernel ./internal/softwaredelivery/effects

# Windows `go test` is dominated by per-process spawn latency (each test spawns
# several `git` processes; the suite is ~330 tests run serially), so the full
# suite takes ~15 min on Windows vs ~1 min on Unix. In-process t.Parallel() is
Expand Down
3 changes: 3 additions & 0 deletions release-notes/2026-08-12-kernel-architecture-guards.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Guard the kernel architecture boundary

Repository checks now prevent software-delivery dependencies and fixtures from entering the general kernel, exercise race detection on Linux, and keep the pending production-runtime migration visible.