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
18 changes: 16 additions & 2 deletions .github/tests/go_source_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type metadata struct {
Path string `json:"path"`
Imports []string `json:"imports"`
RuntimeConsumers []string `json:"runtime_consumers"`
Vocabulary []string `json:"vocabulary"`
}

func main() {
Expand All @@ -35,11 +36,11 @@ func main() {
if filename == "--" {
continue
}
parsed, err := parser.ParseFile(token.NewFileSet(), filename, nil, parser.AllErrors)
parsed, err := parser.ParseFile(token.NewFileSet(), filename, nil, parser.AllErrors|parser.ParseComments)
if err != nil {
fatalf("parse %s: %v", filename, err)
}
item := metadata{Path: filename, Imports: []string{}, RuntimeConsumers: []string{}}
item := metadata{Path: filename, Imports: []string{}, RuntimeConsumers: []string{}, Vocabulary: []string{}}
kernelAliases := map[string]bool{}
for _, spec := range parsed.Imports {
importPath, err := strconv.Unquote(spec.Path.Value)
Expand All @@ -58,20 +59,33 @@ func main() {
}
ast.Inspect(parsed, func(node ast.Node) bool {
switch value := node.(type) {
case *ast.ImportSpec:
return false
Comment thread
bigboateng marked this conversation as resolved.
Comment on lines +62 to +63

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Import-attached comments bypass the vocabulary guard

Invariant: every forbidden domain token in kernel source comments must be detected. A production file containing import "fmt" // repository adapter followed by a normal fmt use passes domain_vocabulary_hits: returning false for *ast.ImportSpec skips its Doc and Comment children, while fmt contributes no import-path hit. The previous raw-source check rejected this token, so the bypass is introduced here and makes the domain-neutrality CI guard unsound. Add a fixture with an import-line comment and require a repository hit; inspect import comments while excluding only the path literal.

Confidence: 0.99

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:
item.Vocabulary = append(item.Vocabulary, value.Name)
if kernelAliases["."] && runtimeSymbols[value.Name] {
item.RuntimeConsumers = append(item.RuntimeConsumers, value.Name)
}
case *ast.BasicLit:
if value.Kind == token.STRING {
decoded, err := strconv.Unquote(value.Value)
if err == nil {
item.Vocabulary = append(item.Vocabulary, decoded)
}
}
case *ast.Comment:
item.Vocabulary = append(item.Vocabulary, value.Text)
}
return true
})
sort.Strings(item.Imports)
sort.Strings(item.RuntimeConsumers)
sort.Strings(item.Vocabulary)
results = append(results, item)
}
if err := json.NewEncoder(os.Stdout).Encode(results); err != nil {
Expand Down
93 changes: 84 additions & 9 deletions .github/tests/test_repository_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@
REPO = Path(__file__).resolve().parents[2]
RUNTIME = REPO / "boatstack"
CONFIG = REPO / "project.example.json"
DOMAIN_NEUTRAL_ROOTS = (
("pullrequest", "pull request"),
("codingagent", "coding agent"),
("github", "github"),
("repository", "repository"),
("worktree", "worktree"),
("publication", "publication"),
("branch", "branch"),
("git", "git"),
)
DOMAIN_NEUTRAL_PHRASE = re.compile(r"\b(?:pull\s+request|coding\s+agent)\b", re.IGNORECASE)
GO_IDENTIFIER = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
GO_IDENTIFIER_PART = re.compile(r"[A-Z]?[a-z]+|[A-Z]+(?![a-z])|[0-9]+")


def go_source_metadata(paths: list[Path]) -> list[dict[str, object]]:
Expand All @@ -36,6 +49,48 @@ def go_source_metadata(paths: list[Path]) -> list[dict[str, object]]:
return json.loads(result.stdout)


def identifier_domain_token(identifier: str) -> str | None:
for component in identifier.split("_"):
for part in GO_IDENTIFIER_PART.findall(component):
normalized = part.lower()
for root, token in DOMAIN_NEUTRAL_ROOTS:
if normalized == root or normalized.startswith(root):
return token
Comment on lines +56 to +58

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Prefix matching rejects neutral branching tests

Invariant: the domain-neutrality verifier must accept identifiers using branch in its ordinary control-flow sense. Adding boatstack/kernel/relation_test.go with func TestBranchingCandidates(t *testing.T) now fails CI: Branching is split as one component and startswith("branch") classifies it as a Git branch reference. This is introduced because the patch extends vocabulary scanning to _test.go files. The observable impact is that valid kernel tests cannot merge. Add a fixture expecting no hits for TestBranchingCandidates, and match semantic identifier components rather than arbitrary root prefixes.

Confidence: 0.96

return None


def import_domain_token(import_path: str) -> str | None:
components = import_path.split("/")
if components and components[0] == "github.com":
components = components[1:]
for component in components:
for identifier in re.split(r"[.-]", component):
token = identifier_domain_token(identifier)
if token is not None:
return token
return None


def domain_vocabulary_hits(paths: list[Path]) -> list[tuple[Path, str]]:
hits: list[tuple[Path, str]] = []
for path, metadata in zip(paths, go_source_metadata(paths), strict=True):
for import_path in metadata["imports"]:
token = import_domain_token(import_path)
if token is not None:
hits.append((path, token))
for source in metadata["vocabulary"]:
phrase = DOMAIN_NEUTRAL_PHRASE.search(source)
if phrase is not None:
hits.append((path, phrase.group(0).lower()))
continue
for identifier in GO_IDENTIFIER.findall(source):
token = identifier_domain_token(identifier)
if token is not None:
hits.append((path, token))
break
return hits


class RepositoryContract(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
Expand Down Expand Up @@ -415,15 +470,35 @@ 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"
kernel_files = sorted(kernel.glob("*.go"))
production_files = [
path for path in kernel_files if not path.name.endswith("_test.go")
]
source = "\n".join(path.read_text() for path in production_files)
for token in (
"git", "repository", "worktree", "branch", "pull request",
"coding agent", "publication",
):
self.assertNotIn(token, source.lower(), token)
self.assertEqual([], domain_vocabulary_hits(kernel_files))
with tempfile.TemporaryDirectory() as temporary:
fixture = Path(temporary) / "domain_leak_test.go"
for source, token in (
('package kernel\nconst fixtureDomain = "pull request"\n', "pull request"),
("package kernel\ntype gitClient struct{}\n", "git"),
("package kernel\nvar testRepository string\n", "repository"),
("package kernel\ntype worktreeManager struct{}\n", "worktree"),
('package kernel\nconst provider = "github"\n', "github"),
("package kernel\ntype githubClient struct{}\n", "github"),
("package kernel\ntype gitclient struct{}\n", "git"),
("package kernel\ntype repositoryclient struct{}\n", "repository"),
("package kernel\ntype worktreemanager struct{}\n", "worktree"),
("package kernel\ntype branchmanager struct{}\n", "branch"),
("package kernel\ntype publicationqueue struct{}\n", "publication"),
("package kernel\ntype pullrequesthandler struct{}\n", "pull request"),
("package kernel\ntype codingagentpolicy struct{}\n", "coding agent"),
):
with self.subTest(token=token):
fixture.write_text(source)
self.assertEqual([(fixture, token)], domain_vocabulary_hits([fixture]))
fixture.write_text('package kernel\nimport "github.com/example/provider"\n')
self.assertEqual([], domain_vocabulary_hits([fixture]))
fixture.write_text(
'package kernel\nimport vcs "github.com/go-git/go-git/v5"\nvar _ = vcs.PlainClone\n'
)
self.assertEqual([(fixture, "git")], domain_vocabulary_hits([fixture]))
fixture.write_text("package kernel\nfunc TestDigitalSignature() {}\n")
self.assertEqual([], domain_vocabulary_hits([fixture]))

boatstack_packages = "github.com/operatorstack/boatstack/boatstack/"
kernel_package = boatstack_packages + "kernel"
Expand Down
107 changes: 93 additions & 14 deletions boatstack/kernel/conformance/conformance.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ type Scenario struct {
ChangeObservation func()
RebindObjective func(kernel.Objective)
BumpStateRevision func()
RetargetProgram func(kernel.ProgramIdentity)
AdvanceClock func(time.Duration)
IndependentLocker func() kernel.Locker
VerifyCommitted func(Snapshot, Snapshot, kernel.Receipt) error
InterruptNextOperator func()
Expand Down Expand Up @@ -87,6 +89,8 @@ func (suite KernelConformance) Run(t *testing.T) {
t.Run("stale_prescription_precedes_effects", suite.stalePrescriptionPrecedesEffects)
t.Run("authority_denial_fails_closed", suite.authorityDenialFailsClosed)
t.Run("future_authority_fails_closed", suite.futureAuthorityFailsClosed)
t.Run("expired_authority_fails_closed", suite.expiredAuthorityFailsClosed)
t.Run("authority_expiry_invalidates_prescription_before_effects", suite.authorityExpiryInvalidatesPrescription)
t.Run("capability_classifier_cannot_be_weakened", suite.capabilityClassifierCannotBeWeakened)
t.Run("targeted_and_untargeted_share_one_relation", suite.targetedAndUntargetedShareRelation)
t.Run("interrupted_operator_requires_explicit_recovery", suite.interruptedOperatorRequiresExplicitRecovery)
Expand Down Expand Up @@ -161,19 +165,42 @@ func (suite KernelConformance) stateRevisionInvalidatesPrescription(t *testing.T
}

func (suite KernelConformance) programFingerprintInvalidatesPrescription(t *testing.T) {
fixture, runtime := suite.fresh(t, SetupBound)
transition := fixture.Scenario.AdvanceTransitions[0]
request, prescription := resolve(t, runtime, fixture.Scenario, transition, &fixture.Scenario.Objective, fixture.Scenario.Authority)
alternate, err := kernel.NewRuntime(fixture.Scenario.AlternateProgram, fixture.Domain, fixture.Operator, fixture.CapabilityClassifier, fixture.Store, fixture.Locker, fixture.Clock)
if err != nil {
t.Fatal(err)
}
before := fixture.Scenario.Snapshot()
_, err = alternate.Apply(context.Background(), kernel.ApplyRequest{ResolveRequest: request, Prescription: prescription})
after := fixture.Scenario.Snapshot()
if unchangedErr := refusedApplyMutationError(before, after, transition); err == nil || unchangedErr != nil {
t.Fatalf("control-law program-fingerprint-freshness: error=%v mutation=%v before=%#v after=%#v", err, unchangedErr, before, after)
}
t.Run("executable_mismatch", func(t *testing.T) {
fixture, runtime := suite.fresh(t, SetupBound)
transition := fixture.Scenario.AdvanceTransitions[0]
request, prescription := resolve(t, runtime, fixture.Scenario, transition, &fixture.Scenario.Objective, fixture.Scenario.Authority)
alternate, err := kernel.NewRuntime(fixture.Scenario.AlternateProgram, fixture.Domain, fixture.Operator, fixture.CapabilityClassifier, fixture.Store, fixture.Locker, fixture.Clock)
if err != nil {
t.Fatal(err)
}
before := fixture.Scenario.Snapshot()
_, err = alternate.Apply(context.Background(), kernel.ApplyRequest{ResolveRequest: request, Prescription: prescription})
after := fixture.Scenario.Snapshot()
if unchangedErr := refusedApplyMutationError(before, after, transition); err == nil || unchangedErr != nil {
t.Fatalf("control-law executable-program-freshness: error=%v mutation=%v before=%#v after=%#v", err, unchangedErr, before, after)
}
})
t.Run("prescription_fingerprint", func(t *testing.T) {
fixture, runtime := suite.fresh(t, SetupBound)
transition := fixture.Scenario.AdvanceTransitions[0]
request, prescription := resolve(t, runtime, fixture.Scenario, transition, &fixture.Scenario.Objective, fixture.Scenario.Authority)
beforeRetarget := fixture.Scenario.Snapshot()
fixture.Scenario.RetargetProgram(fixture.Scenario.AlternateProgram.Identity())
afterRetarget := fixture.Scenario.Snapshot()
if err := retargetProgramError(beforeRetarget, afterRetarget, fixture.Scenario.AlternateProgram.Identity()); err != nil {
t.Fatal(err)
}
alternate, err := kernel.NewRuntime(fixture.Scenario.AlternateProgram, fixture.Domain, fixture.Operator, fixture.CapabilityClassifier, fixture.Store, fixture.Locker, fixture.Clock)
if err != nil {
t.Fatal(err)
}
before := fixture.Scenario.Snapshot()
Comment thread
bigboateng marked this conversation as resolved.
_, err = alternate.Apply(context.Background(), kernel.ApplyRequest{ResolveRequest: request, Prescription: prescription})
after := fixture.Scenario.Snapshot()
if unchangedErr := refusedApplyMutationError(before, after, transition); !kernel.IsStale(err) || unchangedErr != nil {
t.Fatalf("control-law prescription-program-freshness: error=%v mutation=%v before=%#v after=%#v", err, unchangedErr, before, after)
}
})
}

func (suite KernelConformance) stalePrescriptionPrecedesEffects(t *testing.T) {
Expand Down Expand Up @@ -219,6 +246,58 @@ func (suite KernelConformance) futureAuthorityFailsClosed(t *testing.T) {
}
}

func (suite KernelConformance) expiredAuthorityFailsClosed(t *testing.T) {
fixture, runtime := suite.fresh(t, SetupBound)
transition := fixture.Scenario.AdvanceTransitions[0]
authority := fixture.Scenario.Authority
authority.Receipts = append([]kernel.AuthorityReceipt(nil), authority.Receipts...)
authority.Receipts[0].ExpiresAt = fixture.Clock.Now()
resolution, err := resolveWithoutMutation(context.Background(), runtime, fixture.Scenario, kernel.ResolveRequest{InstanceID: fixture.Scenario.InstanceID, Objective: &fixture.Scenario.Objective, Authority: authority, Requested: transition})
if err != nil || resolution.Decision.Kind != kernel.Refused {
t.Fatalf("control-law expired-authority: decision=%#v error=%v", resolution.Decision, err)
}
}

func (suite KernelConformance) authorityExpiryInvalidatesPrescription(t *testing.T) {
fixture, runtime := suite.fresh(t, SetupBound)
transition := fixture.Scenario.AdvanceTransitions[0]
authority := fixture.Scenario.Authority
authority.Receipts = append([]kernel.AuthorityReceipt(nil), authority.Receipts...)
authority.Receipts[0].ExpiresAt = fixture.Clock.Now().Add(time.Hour)
request, prescription := resolve(t, runtime, fixture.Scenario, transition, &fixture.Scenario.Objective, authority)
duration := 2 * time.Hour
beforeClock := fixture.Clock.Now()
beforeAdvance := fixture.Scenario.Snapshot()
fixture.Scenario.AdvanceClock(duration)
afterClock := fixture.Clock.Now()
afterAdvance := fixture.Scenario.Snapshot()
if err := clockAdvanceError(beforeAdvance, afterAdvance, beforeClock, afterClock, duration); err != nil {
t.Fatal(err)
}
before := fixture.Scenario.Snapshot()
_, err := runtime.Apply(context.Background(), kernel.ApplyRequest{ResolveRequest: request, Prescription: prescription})
after := fixture.Scenario.Snapshot()
if unchangedErr := refusedApplyMutationError(before, after, transition); err == nil || unchangedErr != nil {
t.Fatalf("control-law apply-time-authority-expiry: error=%v mutation=%v before=%#v after=%#v", err, unchangedErr, before, after)
}
}

func retargetProgramError(before, after Snapshot, program kernel.ProgramIdentity) error {
expected := before
expected.State.Program = program
if !reflect.DeepEqual(after, expected) {
return fmt.Errorf("control-law program-retarget fixture changed evidence outside State.Program: before=%#v after=%#v", before, after)
}
return nil
}

func clockAdvanceError(before, after Snapshot, beforeTime, afterTime time.Time, duration time.Duration) error {
if !reflect.DeepEqual(after, before) || !afterTime.Equal(beforeTime.Add(duration)) {
return fmt.Errorf("control-law clock-advance fixture changed snapshot evidence or advanced by the wrong duration: before=%#v after=%#v before_time=%v after_time=%v duration=%v", before, after, beforeTime, afterTime, duration)
}
return nil
}

func (suite KernelConformance) capabilityClassifierCannotBeWeakened(t *testing.T) {
fixture := suite.fixture(t, SetupBound)
transition := fixture.Scenario.AdvanceTransitions[0]
Expand Down Expand Up @@ -653,7 +732,7 @@ func (suite KernelConformance) fixture(t testing.TB, setup Setup) KernelConforma
t.Fatal("kernel conformance requires a fresh fixture factory")
}
fixture := suite.New(t, setup)
if fixture.Domain == nil || fixture.Operator == nil || fixture.CapabilityClassifier == nil || fixture.Store == nil || fixture.Locker == nil || fixture.Clock == nil || fixture.Scenario.Snapshot == nil || fixture.Scenario.ChangeObservation == nil || fixture.Scenario.RebindObjective == nil || fixture.Scenario.BumpStateRevision == nil || fixture.Scenario.IndependentLocker == nil || fixture.Scenario.VerifyCommitted == nil || fixture.Scenario.InterruptNextOperator == nil || fixture.Scenario.PanicNextOperator == nil || fixture.Scenario.FailNextCommit == nil || fixture.Scenario.RetargetInstance == nil || fixture.Scenario.InstanceID == "" || fixture.Scenario.BindTransition == "" || len(fixture.Scenario.AdvanceTransitions) == 0 || fixture.Scenario.MaintenanceTransition == "" || fixture.Scenario.RecoveryTransition == "" || fixture.Scenario.RecoveryCapability.Validate() != nil || fixture.Scenario.ExtraCapability.Validate() != nil {
if fixture.Domain == nil || fixture.Operator == nil || fixture.CapabilityClassifier == nil || fixture.Store == nil || fixture.Locker == nil || fixture.Clock == nil || fixture.Scenario.Snapshot == nil || fixture.Scenario.ChangeObservation == nil || fixture.Scenario.RebindObjective == nil || fixture.Scenario.BumpStateRevision == nil || fixture.Scenario.RetargetProgram == nil || fixture.Scenario.AdvanceClock == nil || fixture.Scenario.IndependentLocker == nil || fixture.Scenario.VerifyCommitted == nil || fixture.Scenario.InterruptNextOperator == nil || fixture.Scenario.PanicNextOperator == nil || fixture.Scenario.FailNextCommit == nil || fixture.Scenario.RetargetInstance == nil || fixture.Scenario.InstanceID == "" || fixture.Scenario.BindTransition == "" || len(fixture.Scenario.AdvanceTransitions) == 0 || fixture.Scenario.MaintenanceTransition == "" || fixture.Scenario.RecoveryTransition == "" || fixture.Scenario.RecoveryCapability.Validate() != nil || fixture.Scenario.ExtraCapability.Validate() != nil {
t.Fatal("kernel conformance fixture is incomplete")
}
if fixture.Scenario.RevisedObjective.Validate() != nil || fixture.Scenario.RevisedObjective.ID != fixture.Scenario.Objective.ID || fixture.Scenario.RevisedObjective.Revision <= fixture.Scenario.Objective.Revision || fixture.Scenario.RevisedObjective.Fingerprint == fixture.Scenario.Objective.Fingerprint {
Expand Down
25 changes: 25 additions & 0 deletions boatstack/kernel/conformance/conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,31 @@ func TestIntegerBoundFixtureIncludesCommittedHistory(t *testing.T) {
}
}

func TestRetargetProgramCheckRejectsAdditionalFreshnessChanges(t *testing.T) {
fixture := newIntegerFixture(SetupBound)
before := fixture.Scenario.Snapshot()
fixture.Scenario.RetargetProgram(fixture.Scenario.AlternateProgram.Identity())
fixture.Scenario.BumpStateRevision()
after := fixture.Scenario.Snapshot()
if err := retargetProgramError(before, after, fixture.Scenario.AlternateProgram.Identity()); err == nil {
t.Fatal("expected revision-changing program hook to be rejected")
}
}

func TestAdvanceClockCheckRejectsAdditionalFreshnessChanges(t *testing.T) {
fixture := newIntegerFixture(SetupBound)
duration := time.Hour
beforeTime := fixture.Clock.Now()
before := fixture.Scenario.Snapshot()
fixture.Scenario.AdvanceClock(duration)
fixture.Scenario.ChangeObservation()
afterTime := fixture.Clock.Now()
after := fixture.Scenario.Snapshot()
if err := clockAdvanceError(before, after, beforeTime, afterTime, duration); err == nil {
t.Fatal("expected observation-changing clock hook to be rejected")
}
}

func TestResolveWithoutMutationRejectsEffectfulLoad(t *testing.T) {
fixture := newIntegerFixture(SetupBound)
base := fixture.Store.(*MemoryStateStore)
Expand Down
Loading