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
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Contributing

Boatstack is a generated content distribution. Propose changes to workflow semantics, templates, evidence rules, or generated presentation in [Intelligence Flow](https://github.com/operatorstack/intelligence-flow/tree/fbb9ecffc548440fadb5d32339115bd20166a29f/labs/12-product-engineering-loop).
Boatstack is a generated content distribution. Propose changes to workflow semantics, templates, evidence rules, or generated presentation in [Intelligence Flow](https://github.com/operatorstack/intelligence-flow/tree/2364eaedaefbe73d8996108dfac261c38b0ce2b3/labs/12-product-engineering-loop).

The Boatstack repository receives product/runtime changes through a generated pull request. Review the PR's `UPSTREAM.json`, tests, adapter diff, and context-size change; do not hand-edit generated output on `main`. `.github/workflows` is the exception: it is Boatstack's executable control plane, excluded from scheduled projection and changed only through a separate manually reviewed Boatstack PR.

Expand Down
19 changes: 11 additions & 8 deletions UPSTREAM.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"files": {
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
"CONTRIBUTING.md": "e016bb241bf61fdc8e96cac4d1cac7a674c545a2e788abeac663ce4235316c3f",
"CONTRIBUTING.md": "ea4d692766d22427d515ab944c9d692e862d63584c255fdecdb67e063cb32e32",
"README.md": "3ce3e95e511089b44e946a44b8d5f4f81d019ece5336db65b2cab1f9dc4d4dad",
"assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63",
"assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5",
Expand Down Expand Up @@ -115,6 +115,8 @@
"boatstack/internal/deliverycontrol/trajectorylog_test.go": "227dd6ed9ce181d517a37b67ef4d64dd93779a533eae804798ab54de35c7f13e",
"boatstack/internal/deliverycontrol/transition.go": "b43abb0e99d29697b27b0bb8ee2e2f5f31f3471a2983f25d18ae3564ee246775",
"boatstack/migrate.go": "eaf589e2b266238068e42c6d78e01dc040266d28e342cb24f09e33e8541749b3",
"boatstack/migrate_effect_grade.go": "bccb58e770001aa9554d8e7f151663d907f152508a61118f56fd90465ba6f32e",
"boatstack/migrate_effect_grade_test.go": "fea1d1057bc6d8eaf015e377864a3adab29ef5731f597fe0a38b96fa80355d14",
"boatstack/migrate_test.go": "9f4bda2fb158c5e54bcc0242dace1da3c1965f9846a213c573956a35b7d1724e",
"boatstack/mutation.go": "59fc9e92105d8ec20f854af9898cde037ab0e3e46c453794838dbfc65fecdd6d",
"boatstack/mutation_test.go": "68d5049c7f96c1ac558e4c781151f67e8deee2f8d6b9bf293b90d44e769ef7c6",
Expand Down Expand Up @@ -145,7 +147,7 @@
"boatstack/reexec_unix.go": "ff86157a9aa20c82a56fcd859b70669b7eacf4e0a9f61a4546ef33808437939e",
"boatstack/reexec_windows.go": "f5335c8c28cb4e89048b058b1c4d12f78644f99acb4f6167ff60e622dfb9e742",
"boatstack/references/artifacts.md": "5fa888ac519085d65cee1d04df5902761651bcf2d7af81711fa0f8ecd1fc0f59",
"boatstack/references/config-schema.md": "fa5e09d008101957cf5577e9031de256ab682711c3fa21fa9494cd600ddbd2f0",
"boatstack/references/config-schema.md": "eff8586850eca9941df1cea29ac7edb779277ed9bd6d938a1980db5028d28cf4",
"boatstack/references/failure-moves.md": "b65ef72035afa6ad0dce589a0b38f84bc40cde3864c9ecf973f08fc687f001c3",
"boatstack/references/host-hook-contracts.md": "2a89d44d0e418a53f2e3b6300fed957cdf878f45ea97ce24b55b66065f0eaa1d",
"boatstack/references/irreversible-operation-boundary.md": "e0076f0fea3bf729b2e9bdf353eaeaaf7cdafabfaf26b8d9b27287e5414c2441",
Expand All @@ -156,7 +158,7 @@
"boatstack/repair_state_test.go": "f3779ac47c3db3927175a545728d3b2e020dbc85f41394d8235753b52afc3739",
"boatstack/run.go": "3127e8c054b80e41413011e423ba9463fe06a7e6c9e1e71750ab98587871d89d",
"boatstack/run_test.go": "5b291510fa90cefdc26eb89e18a3443385456a6ebc73408325ac1945b7c084d6",
"boatstack/runtime.go": "11f741d45994c336e927121ede2481c94d5c33ed9c3beecf89f760450b304da3",
"boatstack/runtime.go": "7dc5d033ec11bbcf9a4561da66d4d6692a071bc79ac2fe4eb66feaced358d3c3",
"boatstack/runtime_cache.go": "e026ffc1906f7e1e98b768bae63e6658164d2826c07169c9121ce0f23c73faf8",
"boatstack/runtime_cache_test.go": "b981467ddc9f0f562da6bff5de7a80a9fe5a433a0317541d1e48df268546ac85",
"boatstack/runtime_provenance_test.go": "1d52f1e6b0691cf4667729cc9b9f3c55c128f0aa3321f3a2843a9aa6fd0e73dc",
Expand Down Expand Up @@ -188,10 +190,10 @@
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
"docs/configuration.md": "060775c73431f28bd16066bdf9e0f89034d2855c7ca0f5544f660d24b91211d0",
"docs/evidence-engineered-coding.md": "82b129eeacdcea2ccfca0eeb579c412a0d3938e3413d0f57c7c953f55762f25f",
"docs/evidence-engineered-coding.md": "0083581913336f9612f321997a3e9afb8b7f2549e41d6fbae122920174ae4779",
"docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3",
"docs/getting-started.md": "51c2823f21e35140d31e6d5083dc4b89fddd24721ac6acc474154a4da53ee9f8",
"docs/public-claims.json": "868026dabc8ae86dec955195d6f78aa45a5d74cb44ece1c873fa29e69ff8c8b1",
"docs/public-claims.json": "aa2ee5ecd6a3f29d5dd3c4e538d29931191606a52fad6433d43184af814334c3",
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
"docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6",
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
Expand All @@ -205,7 +207,7 @@
"labs/diagram-json/compiled/evidence.md": "1ba1c989ade070a8ef9a508fbd788d100d7292f2dbacbb2bce895468019f619d",
"labs/diagram-json/compiled/tasks.json": "88f60851abf79d851e9fccc754ff3040034ae595306bc87d64784c19eb403e71",
"labs/diagram-json/compiled/test-matrix.json": "424657ff505768e50fa113801fd8363364a18269d5297480907a993d44063a39",
"labs/diagram-json/plan.lock.json": "dc27d78e3c23888025cda5f56ce058e48e009584da08ea111d095b8ce2ccb29b",
"labs/diagram-json/plan.lock.json": "9c2377941197d6bd160f338e0bc55dd74f9118844291b5ee73a17867757a5edb",
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
Expand Down Expand Up @@ -324,12 +326,13 @@
"release-notes/2026-07-27-discoverable-planning-errors.md": "d8099d1a6cd1805c3fcd446d9fa95739dec93e57ff421ecf4f99562b143c9966",
"release-notes/2026-07-27-guard-dual-reward-corpus.md": "6bec0385c6c553f00517259821e502796ca1b1907aeab718a287560e3e0fa0d6",
"release-notes/2026-07-27-invalid-delivery-block-actionable.md": "8fac8e3921e2285291703efa46e624b72cb5bac1b8492beca4c4b633abb5ba16",
"release-notes/2026-07-27-read-only-inspection-pipelines.md": "0963286371e9a12592915c23a958dd013bf2a35e9fca6921691bc8bb3c3d8dc8"
"release-notes/2026-07-27-read-only-inspection-pipelines.md": "0963286371e9a12592915c23a958dd013bf2a35e9fca6921691bc8bb3c3d8dc8",
"release-notes/2026-07-27-sandboxed-migration-grading.md": "03cebc372bbdfed37cc70d18f3b6374d1aa5e585bafefa073dbcced58bd0336a"
},
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
"schema_version": 1,
"source": {
"commit": "fbb9ecffc548440fadb5d32339115bd20166a29f",
"commit": "2364eaedaefbe73d8996108dfac261c38b0ce2b3",
"path": "labs/12-product-engineering-loop",
"repository": "operatorstack/intelligence-flow"
}
Expand Down
92 changes: 92 additions & 0 deletions boatstack/migrate_effect_grade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package boatstack

import (
"bytes"
"os"
"os/exec"
"strings"
)

// MigrationEffectStatus is the verdict of grading a migration by its observed effect.
type MigrationEffectStatus string

const (
// MigrationEffectSkipped means the project declared no migration commands, so no
// effect was executed — a repository without a database is unaffected.
MigrationEffectSkipped MigrationEffectStatus = "SKIPPED"
// MigrationEffectPass means the migration applied and verified against the
// disposable database.
MigrationEffectPass MigrationEffectStatus = "PASS"
// MigrationEffectFail means applying or verifying the migration failed — real
// breakage the static guard cannot see, because a migration is inert as data.
MigrationEffectFail MigrationEffectStatus = "FAIL"
)

// MigrationEffectResult is the outcome of GradeMigrationEffect.
type MigrationEffectResult struct {
Status MigrationEffectStatus
Reason string
}

// GradeMigrationEffect grades a project's migrations by their OBSERVED EFFECT rather
// than by their SQL text. Sandboxed-Effect law: when an effect's safety cannot be
// certified statically, execute it in a disposable environment and read the oracle;
// never approximate it by reading the source. The guard keeps treating a committed
// migration as a data artifact (it is applied later by the controlled pipeline); this
// harness IS that controlled executor for grading purposes.
//
// It runs the project's configured apply_command, then verify_command, via `sh -c`
// with the caller-provided environment (which carries the disposable database
// coordinate, BOATSTACK_MIGRATE_DB). PASS iff both succeed; FAIL if either fails;
// SKIPPED when no apply_command is configured. The caller owns the disposable
// database and its guaranteed teardown (a fresh-per-run service container in CI, or a
// temp file removed by the test) — this function only executes and grades.
func GradeMigrationEffect(repo string, extraEnv []string) (MigrationEffectResult, error) {
config, _, err := LoadConfig(WorkspaceFor(repo).ProjectConfigPath())
if err != nil {
return MigrationEffectResult{}, err
}
apply := strings.TrimSpace(config.Project.Migration.ApplyCommand)
verify := strings.TrimSpace(config.Project.Migration.VerifyCommand)
if apply == "" {
return MigrationEffectResult{Status: MigrationEffectSkipped, Reason: "no migration apply_command is configured; effect grading skipped"}, nil
}
if out, runErr := runMigrationShell(repo, apply, extraEnv); runErr != nil {
return MigrationEffectResult{Status: MigrationEffectFail, Reason: "apply failed: " + firstOutputLine(out)}, nil
}
if verify != "" {
if out, runErr := runMigrationShell(repo, verify, extraEnv); runErr != nil {
return MigrationEffectResult{Status: MigrationEffectFail, Reason: "verify failed: " + firstOutputLine(out)}, nil
}
}
return MigrationEffectResult{Status: MigrationEffectPass, Reason: "migration applied and verified against the disposable database"}, nil
}

// runMigrationShell keeps stdout (authority-bearing) and stderr (diagnostic)
// separate. Grading needs only the exit status and a diagnostic line, so it reports
// stderr, falling back to stdout when a tool writes its error there.
func runMigrationShell(dir, command string, extraEnv []string) (string, error) {
cmd := exec.Command("sh", "-c", command)
cmd.Dir = dir
cmd.Env = append(os.Environ(), extraEnv...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
diagnostic := strings.TrimSpace(stderr.String())
if diagnostic == "" {
diagnostic = strings.TrimSpace(stdout.String())
}
return diagnostic, err
}

func firstOutputLine(s string) string {
s = strings.TrimSpace(s)
if index := strings.IndexByte(s, '\n'); index >= 0 {
s = s[:index]
}
if s == "" {
return "(no output)"
}
return s
}
96 changes: 96 additions & 0 deletions boatstack/migrate_effect_grade_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package boatstack

import (
"os"
"os/exec"
"path/filepath"
"testing"
)

// migrateGradeRepo builds a repo whose project config declares the given migration
// apply/verify commands. A disposable SQLite database is created under the test's
// temp dir (removed automatically when the test ends — the guaranteed-teardown
// invariant), seeded with one row, and returned as the BOATSTACK_MIGRATE_DB env the
// commands read.
func migrateGradeRepo(t *testing.T, apply, verify string) (repo string, env []string) {
t.Helper()
repo = t.TempDir()
if err := os.MkdirAll(filepath.Join(repo, ".product-loop", "features"), 0o755); err != nil {
t.Fatal(err)
}
config := testConfig()
config.Project.Migration = MigrationConfig{ApplyCommand: apply, VerifyCommand: verify}
value, err := MarshalJSON(config)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(repo, ".product-loop", "project.json"), value, 0o644); err != nil {
t.Fatal(err)
}
db := filepath.Join(t.TempDir(), "disposable.sqlite")
if out, seedErr := exec.Command("sqlite3", db, "CREATE TABLE accounts(id INTEGER); INSERT INTO accounts VALUES (1);").CombinedOutput(); seedErr != nil {
t.Fatalf("seed disposable db: %v: %s", seedErr, out)
}
return repo, []string{"BOATSTACK_MIGRATE_DB=" + db}
}

// Sandboxed-Effect law: a migration's safety is graded by EXECUTING it against a
// fresh, disposable database and reading the oracle — never approximated from its
// SQL text. The guard treats the same migration as inert data; this harness is the
// controlled executor. A repo that declares no migration commands is unaffected.
func TestMigrationEffectGradingSandbox(t *testing.T) {
if _, err := exec.LookPath("sqlite3"); err != nil {
t.Skip("sqlite3 not available")
}
if _, err := exec.LookPath("sh"); err != nil {
t.Skip("sh not available")
}

const apply = `sqlite3 "$BOATSTACK_MIGRATE_DB" < migrate.sql`
// Verify the invariant the migration must preserve: the seeded row still exists.
const verify = `test "$(sqlite3 "$BOATSTACK_MIGRATE_DB" 'SELECT count(*) FROM accounts')" = "1"`

t.Run("skips cleanly when no migration commands are declared", func(t *testing.T) {
repo, env := migrateGradeRepo(t, "", "")
result, err := GradeMigrationEffect(repo, env)
if err != nil {
t.Fatal(err)
}
if result.Status != MigrationEffectSkipped {
t.Fatalf("unconfigured repo did not skip: %+v", result)
}
})

t.Run("safe forward migration grades PASS", func(t *testing.T) {
repo, env := migrateGradeRepo(t, apply, verify)
// A declarative migration full of DDL — the guard treats this as data.
if err := os.WriteFile(filepath.Join(repo, "migrate.sql"),
[]byte("ALTER TABLE accounts ADD COLUMN active INTEGER DEFAULT 1;\n"), 0o644); err != nil {
t.Fatal(err)
}
result, err := GradeMigrationEffect(repo, env)
if err != nil {
t.Fatal(err)
}
if result.Status != MigrationEffectPass {
t.Fatalf("safe migration did not grade PASS: %+v", result)
}
})

t.Run("destructive migration grades FAIL against the disposable db", func(t *testing.T) {
repo, env := migrateGradeRepo(t, apply, verify)
// Dropping the populated table is inert as TEXT (the static guard allows it as
// a data artifact) but its EFFECT is caught by executing it in the sandbox.
if err := os.WriteFile(filepath.Join(repo, "migrate.sql"),
[]byte("DROP TABLE accounts;\n"), 0o644); err != nil {
t.Fatal(err)
}
result, err := GradeMigrationEffect(repo, env)
if err != nil {
t.Fatal(err)
}
if result.Status != MigrationEffectFail {
t.Fatalf("destructive migration was not caught by effect grading: %+v", result)
}
})
}
6 changes: 6 additions & 0 deletions boatstack/references/config-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ boatstack-config-field:project.default_branch
boatstack-config-field:project.context
boatstack-config-field:project.commands
boatstack-config-field:project.high_risk_paths
boatstack-config-field:project.migration
boatstack-config-field:project.migration.apply_command
boatstack-config-field:project.migration.verify_command
boatstack-config-field:workflow
boatstack-config-field:workflow.human_plan_approval
boatstack-config-field:workflow.independent_review_for_high_risk
Expand Down Expand Up @@ -62,6 +65,9 @@ This is the exhaustive serialization contract, not a list of recommended user ed
- `test` (string, required): The exact command to execute project-local tests.
- Other command names (string, optional): Additional repository-owned commands such as `build`, `lint`, or `typecheck`.
- `high_risk_paths` (array of strings, optional): Glob patterns of files requiring independent reviewer sign-off before shipping.
- `migration` (object, optional): Declares how migrations are graded by EFFECT against a disposable database, so a committed migration stays a data artifact for the guard while its real effect is executed and observed by a conformance harness. Both commands run via `sh -c` with the disposable database coordinate in the environment as `BOATSTACK_MIGRATE_DB`; when `apply_command` is absent, grading is skipped.
- `apply_command` (string, optional): The command that applies the migration set to the disposable database.
- `verify_command` (string, optional): The command that asserts the post-migration invariant; a non-zero exit grades the migration FAIL.

### workflow Fields

Expand Down
15 changes: 15 additions & 0 deletions boatstack/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ type Project struct {
Context []string `json:"context,omitempty"`
Commands map[string]string `json:"commands"`
HighRiskPaths []string `json:"high_risk_paths,omitempty"`
Migration MigrationConfig `json:"migration,omitempty"`
}

// MigrationConfig declares how a project APPLIES and VERIFIES its migrations against
// a disposable database, so their EFFECT can be graded by executing them
// (GradeMigrationEffect) rather than approximated from their SQL text. This is the
// Sandboxed-Effect law: the guard treats a committed migration as data (the
// data-artifact exemption), and the deploy pipeline — modelled here by a disposable
// database and these commands — is the controlled executor that observes the real
// effect. Both commands run via `sh -c` with the disposable database coordinate in
// the environment as BOATSTACK_MIGRATE_DB. When apply_command is absent, grading is
// skipped, so a repository without a database is unaffected.
type MigrationConfig struct {
ApplyCommand string `json:"apply_command,omitempty"`
VerifyCommand string `json:"verify_command,omitempty"`
}

type Workflow struct {
Expand Down
2 changes: 1 addition & 1 deletion docs/evidence-engineered-coding.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,6 @@ Delivery and system improvement also remain separate. A failed task may suggest

## What is evidence-backed

The current moves were derived from the Intelligence Flow benchmark corpus and product-repository studies. The generated source commit is [`fbb9ecffc548440fadb5d32339115bd20166a29f`](https://github.com/operatorstack/intelligence-flow/tree/fbb9ecffc548440fadb5d32339115bd20166a29f/labs/12-product-engineering-loop).
The current moves were derived from the Intelligence Flow benchmark corpus and product-repository studies. The generated source commit is [`2364eaedaefbe73d8996108dfac261c38b0ce2b3`](https://github.com/operatorstack/intelligence-flow/tree/2364eaedaefbe73d8996108dfac261c38b0ce2b3/labs/12-product-engineering-loop).

The evidence supports specific failure mechanisms and guardrails. It does not establish that Boatstack is optimal, that control-theory notation proves software quality, or that one workflow dominates every team. Those are evaluation questions, so the distribution preserves measurements, provenance, gaps, and negative results.
Loading
Loading