Skip to content

Commit f02b03a

Browse files
Sync Boatstack from Intelligence Flow Labs @ aefd94091a7e (#52)
Co-authored-by: operator-stack-publisher[bot] <operator-stack-publisher[bot]@users.noreply.github.com>
1 parent 73e33fe commit f02b03a

14 files changed

Lines changed: 558 additions & 31 deletions

File tree

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Contributing
44

5-
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/e3430a3bb67d28b0f3bd540d90787f88dc018056/labs/12-product-engineering-loop).
5+
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/aefd94091a7e1e52b2bb6bd8da177905dde07cd7/labs/12-product-engineering-loop).
66

77
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.
88

UPSTREAM.json

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
"files": {
1414
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
15-
"CONTRIBUTING.md": "196fa0c593ee9f0c2148af1f40651a74a61aefcc8c453c411190ecaf5c2d64a2",
15+
"CONTRIBUTING.md": "640e496e306313eb79700c7c99d11bca233b75f771779d44280f5c2e638bcc6f",
1616
"README.md": "590821a4cf3c4b196d3cfb39c972ccbebef7d4d558df817c1f1e88b4fd7ceb07",
1717
"assets/boatstack-journey.svg": "9f016fbfd2881675551a44dafa282cbc7510a7091131005be490c2dbe5e4e051",
1818
"assets/boatstack-mark.svg": "ec96165583b15cfd446c27049d49217973f3e9b1defa5771cc08eec0c9542ce4",
@@ -34,29 +34,32 @@
3434
"boatstack/atomic_windows.go": "cefd775cbe7e7c3bd8a3f5673b11cdd784c6d3ebd6de7dcb8f39406b0bee511f",
3535
"boatstack/changelog.go": "c5e1f31440b44d61e6037ad27af0333540af3545d655e35819a0241cbbebd8ec",
3636
"boatstack/changelog_test.go": "ce792f23a7fe1e09fb3096cd1314130a6ab69321d4877b12a8e994027541baf7",
37-
"boatstack/cmd/boatstack-helper/main.go": "0b682aa4fc528c6c4e736ae6afa2a8c7c33aab65dca49ce1f5340521357225be",
37+
"boatstack/cmd/boatstack-helper/main.go": "3ba7d63640e6e29ab8df1612b05ae7e616b12041b59c44748cfc74f22ba2100b",
3838
"boatstack/cmd/boatstack-helper/main_test.go": "ff73003b6a5157202fa09ddf1129fb13c3d79702b2e05a8721ce5a11bf5ab779",
3939
"boatstack/delivery.go": "565bc8465252a798124a29874e1aeffff16e9027d1a86715e11b3e4bc0743bd2",
4040
"boatstack/delivery_test.go": "2545c26389d330a92c11e7b09fb2342101db5d09a825a2a9e81b4ea9eea9407b",
41-
"boatstack/export.go": "16bce2e599baa62b1fb46806c5f126215e32a196e1909f57cbdf1852e81ba82b",
41+
"boatstack/export.go": "52124d6d813588ff743ce3352c7392b2ef10f9c51c86ae1422e3053c7e0a697c",
4242
"boatstack/export_test.go": "b38875e004c1a0003f8e377b25ac14e9f4898cf949e91f3d9bfd9531a2867d76",
4343
"boatstack/go.mod": "6086ef1b2a83f5696190dca692c653925f27b61f652f659fd3fca43ed54a1641",
4444
"boatstack/go.sum": "26c315c867b11b886f3c9402fce7f341f6a9115a5d61f54afbb5e1b1fb5f6017",
4545
"boatstack/hooks.go": "2b7d899f92efef8a68a160c423a46bcd6fd37daa0eba2ac8ad44ffa3dc5f88c9",
4646
"boatstack/hooks_test.go": "d71271c0b9ea59b907cc0407a7173d3542281fb2984e6e3c3062ac39b131ec59",
47-
"boatstack/init.go": "c29904ff437aa063eb1ecb1870aa2e6b019afb09210c227dce85e34ef00c7ea8",
47+
"boatstack/init.go": "018252a3112818191d69ee78dbf8816664e6ef7affde2cb576dbca7a67459c9e",
4848
"boatstack/init_test.go": "fa48be69d07691fa7842224ec831e5f290504d8b6565263c9735d2dabd9b43b8",
4949
"boatstack/init_transaction.go": "112456c4e1c4db54c4137bcf4f7a9a9e63399a6f5971e9b3dc952d0c4b2aa4b6",
5050
"boatstack/integrations.go": "75b39ce2e662fccd66bf4b9bff0e097a4db558f23b3aa1d9bc83a5fc6373444c",
51+
"boatstack/migrate.go": "eaf589e2b266238068e42c6d78e01dc040266d28e342cb24f09e33e8541749b3",
52+
"boatstack/migrate_test.go": "b5c2dad2ab762aa202c5c99a8445774bedd982d280ebbb8dd713e7e9f43299d2",
5153
"boatstack/next.go": "df4164280ecd77a417a02fe4bc3b1f16d7012ea22b6fc8988faf8c71f41bc8e6",
5254
"boatstack/next_test.go": "57828f76257ad7344c383084a97399f6523cbbced28ac2bbe4c14b9da84ccf32",
5355
"boatstack/plan.go": "2497e1d32d88a8e433bc5996b57d175bda4e6ba334cf2510efb7c84fd88a98f3",
5456
"boatstack/plan_test.go": "006cdc6681f77e579c5a0f709e30ede759c337132d4f2f5193b7b79b29bd7149",
55-
"boatstack/planning.go": "1237ae552a72d5312c1ef61e30f430976f526d5473d91c7fbfcb9148eb7079b9",
57+
"boatstack/planning.go": "4b86ae9dc16393f099ca26812bf3e62909fee42a541b33c33275cc16a80262aa",
5658
"boatstack/planning_test.go": "6b156a64182ed76d4c3d392b4c5a26abe5d8b81cea27ee12ac7c4627c827e186",
5759
"boatstack/pr.go": "ba537fc35cc8d2aaf584c5ba6de1194fdd1c4255dfb227a09478457752e2b9ad",
5860
"boatstack/pr_test.go": "ae23130d9d96cf214cf272227aa572dd09e2fe3adac22921949f541ba99ecb23",
5961
"boatstack/references/artifacts.md": "8f2e79b8af4bd3ad2e32aa3e8c07f10812555d0a3247e4991db75cc1cda395c3",
62+
"boatstack/references/config-schema.md": "894c001601246ad87411756902aa7b2962314e4395e2075f94d5c7c93c99e013",
6063
"boatstack/references/failure-moves.md": "1d35126348d0b681976e8819665e16fd745fd65eca271492603cb80aab75bf49",
6164
"boatstack/references/host-hook-contracts.md": "1382213ad004389de6da5a03af43ec28ace6329c9e7a3f07148566cf2ea12727",
6265
"boatstack/references/irreversible-operation-boundary.md": "631743991ace65977586e4537f8dd50f8ae88f8e16f27cf7baad93b2791a73df",
@@ -76,17 +79,17 @@
7679
"boatstack/testdata/reviewer-pr-body.md": "4c64e3788e5d61a377aeb0f797f7fc8d2316ab6e49572d15636eea7ba9e34ac4",
7780
"boatstack/testdata/safety/safe_apply.py.txt": "c9ec7fb932cf21b6aa8df597c4d4c54d6ec65e796240e49118d699f583383975",
7881
"boatstack/testdata/safety/unsafe_apply.py.txt": "42db1751865cc15c4dd69a03146b5deca8f21f916d258e433b27bbef5f884ab1",
79-
"boatstack/update.go": "d538c3f44aa869a2c7c20b9552f51fadbe4ec82ebf40f7130bc90ed3b04690f3",
82+
"boatstack/update.go": "7a7654e1eccd593a5e1840af06c83098cca8fdf11313f93931258ed011059a95",
8083
"boatstack/update_test.go": "aa0c2ca97038aad661c46321600034e206e88639f89e0216b3c4cf597312cbae",
8184
"boatstack/workspace.go": "91b343400b3506a6f516c28fabc3f1575f22024a5b19f934a020be660a20482e",
8285
"boatstack/workspace_test.go": "d99ee417954f529a333b8330c32104d96b0368c1b07223a42cbf304d5363ad0d",
8386
"docs/account-recovery-walkthrough.md": "676034974594a7d1a559b24dbed31d7ccc429eb81404b203ca07bbdaa19ec3d3",
8487
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
8588
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
86-
"docs/evidence-engineered-coding.md": "2b9d1824f5a5a88ca0c521e0f89dd320d863a75a8a0f3ebe08860adf6b10cac6",
89+
"docs/evidence-engineered-coding.md": "12f3116604c0fe2a60e79463a063161a8f57fa1da0c0f8d0baffb2e915397f6a",
8790
"docs/generated-files.md": "136422baf0c7fc2bd5100cfe0ebdb3d9d0705dfd7e7d54bf745dd1037e63492c",
8891
"docs/getting-started.md": "eacc814fdffdfa3c7d8052b7cd99a79c04da5c75d88d8b44f3fb68d9afec0316",
89-
"docs/public-claims.json": "696c580ce9147afe25b07392b550858e335719baa0cae1cce717907947f5dfdd",
92+
"docs/public-claims.json": "c16189d867ffbb18bbee414e2325c15a93d8b0f48b4fa7d8b57f44188f97724d",
9093
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
9194
"docs/research-and-design.md": "d65c66e323037bda5d45aacef5d48afa6bf93da55901378891d235aca3a5684f",
9295
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
@@ -100,7 +103,7 @@
100103
"labs/diagram-json/compiled/evidence.md": "1ba1c989ade070a8ef9a508fbd788d100d7292f2dbacbb2bce895468019f619d",
101104
"labs/diagram-json/compiled/tasks.json": "88f60851abf79d851e9fccc754ff3040034ae595306bc87d64784c19eb403e71",
102105
"labs/diagram-json/compiled/test-matrix.json": "424657ff505768e50fa113801fd8363364a18269d5297480907a993d44063a39",
103-
"labs/diagram-json/plan.lock.json": "35268055d4a6b4fe357a462ded9a87e0a6a9b85a276aa9878c3e887ec41a2092",
106+
"labs/diagram-json/plan.lock.json": "38a8a7dcb9d432f21f790f5f3c29c09a5209fd799380ae4cf3d3073851481b65",
104107
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
105108
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
106109
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
@@ -132,6 +135,7 @@
132135
"release-notes/2026-07-19-new-worktree-intent.md": "969beaf8ef7c0635fb45a816cf762a7f66e5ea20b45847a564ee53153863b120",
133136
"release-notes/2026-07-19-optional-repository-changelog.md": "7ae6bed436c269335ac5055e04438489451bef00836fecb3e5fcb06a18501853",
134137
"release-notes/2026-07-19-valid-skill-frontmatter.md": "a0a002f7891474d7d1eea49e3234cae5ddbd941158726b83555dc48474181a92",
138+
"release-notes/2026-07-20-config-schema-migration.md": "99be8fcc408689f559c78bdf39f7161fa7d948846bfc136e3fceed5015841be0",
135139
"release-notes/2026-07-20-gemini-cli-integration.md": "d829b4fafb3a02dd766e018558b20bf1bc948dd5e337911d6a7c8b54c83c9942",
136140
"release-notes/2026-07-20-managed-workspace-lifecycle.md": "e84e9c5a393ca88f9fc14d1f2a99ee9a2a1c525a16d141e9f9981bc9354d8f73",
137141
"release-notes/2026-07-20-portability-diagram-dark-theme.md": "b06bebec1361b21ef6deb8c8f43168d189803a32588aeb803ea4b441525a77e2",
@@ -140,7 +144,7 @@
140144
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
141145
"schema_version": 1,
142146
"source": {
143-
"commit": "e3430a3bb67d28b0f3bd540d90787f88dc018056",
147+
"commit": "aefd94091a7e1e52b2bb6bd8da177905dde07cd7",
144148
"path": "labs/12-product-engineering-loop",
145149
"repository": "operatorstack/intelligence-flow"
146150
}

boatstack/cmd/boatstack-helper/main.go

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package main
22

33
import (
4+
"encoding/json"
45
"flag"
56
"fmt"
67
"io"
78
"os"
9+
"path/filepath"
810
"sort"
911
"strings"
1012
"time"
@@ -513,6 +515,67 @@ func checkSafetyCommand(arguments []string) int {
513515
return 0
514516
}
515517

518+
type MigrateConfigReport struct {
519+
Status string `json:"status"`
520+
Message string `json:"message,omitempty"`
521+
FromVersion int `json:"from_version"`
522+
ToVersion int `json:"to_version"`
523+
Changed bool `json:"changed"`
524+
}
525+
526+
func migrateConfigCommand(arguments []string) int {
527+
flags := flag.NewFlagSet("migrate-config", flag.ContinueOnError)
528+
repo := flags.String("repo", ".", "repository whose configuration should be migrated")
529+
check := flags.Bool("check", false, "dry-run check mode")
530+
if err := flags.Parse(arguments); err != nil {
531+
return 2
532+
}
533+
configPath := filepath.Join(*repo, ".boatstack-project.json")
534+
raw, err := os.ReadFile(configPath)
535+
if err != nil {
536+
report := MigrateConfigReport{
537+
Status: "FAIL",
538+
Message: fmt.Sprintf("failed to read config: %v", err),
539+
}
540+
value, _ := json.Marshal(report)
541+
fmt.Print(string(value))
542+
return 1
543+
}
544+
upgraded, fromVer, toVer, changed, err := boatstack.MigrateConfigBytes(raw)
545+
if err != nil {
546+
report := MigrateConfigReport{
547+
Status: "FAIL",
548+
Message: fmt.Sprintf("migration failed: %v", err),
549+
}
550+
value, _ := json.Marshal(report)
551+
fmt.Print(string(value))
552+
return 1
553+
}
554+
if changed && !*check {
555+
if err := os.WriteFile(configPath, upgraded, 0o644); err != nil {
556+
report := MigrateConfigReport{
557+
Status: "FAIL",
558+
Message: fmt.Sprintf("failed to write migrated config: %v", err),
559+
}
560+
value, _ := json.Marshal(report)
561+
fmt.Print(string(value))
562+
return 1
563+
}
564+
}
565+
report := MigrateConfigReport{
566+
Status: "PASS",
567+
FromVersion: fromVer,
568+
ToVersion: toVer,
569+
Changed: changed,
570+
}
571+
value, err := json.Marshal(report)
572+
if err != nil {
573+
return fail(err)
574+
}
575+
fmt.Print(string(value))
576+
return 0
577+
}
578+
516579
func prContextCommand(arguments []string) int {
517580
flags := flag.NewFlagSet("pr-context", flag.ContinueOnError)
518581
repo := flags.String("repo", ".", "repository whose branch should be projected")
@@ -665,7 +728,7 @@ func workspaceStatusCommand(arguments []string) int {
665728

666729
func run() int {
667730
if len(os.Args) < 2 {
668-
fmt.Fprintln(os.Stderr, "usage: boatstack-helper <init|update|check-update|release-classify|next-patch|export|check-source-plan|planning-write|check-plan|record-approval|activate-plan|delivery-status|next-status|run-preflight|record-change|record-delivery-gate|check-safety|safety-hook|diagnose-hook|pr-context|check-pr|publish-pr|workspace-cut|workspace-cleanup|workspace-status|doctor|version>")
731+
fmt.Fprintln(os.Stderr, "usage: boatstack-helper <init|update|check-update|release-classify|next-patch|export|check-source-plan|planning-write|check-plan|record-approval|activate-plan|delivery-status|next-status|run-preflight|record-change|record-delivery-gate|check-safety|migrate-config|safety-hook|diagnose-hook|pr-context|check-pr|publish-pr|workspace-cut|workspace-cleanup|workspace-status|doctor|version>")
669732
return 2
670733
}
671734
switch os.Args[1] {
@@ -723,6 +786,8 @@ func run() int {
723786
return workspaceCleanupCommand(os.Args[2:])
724787
case "workspace-status":
725788
return workspaceStatusCommand(os.Args[2:])
789+
case "migrate-config":
790+
return migrateConfigCommand(os.Args[2:])
726791
case "version":
727792
fmt.Printf("Boatstack %s (%s)\n", boatstack.Version, boatstack.SourceCommit)
728793
return 0

boatstack/export.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,14 @@ func LoadConfig(path string) (ProjectConfig, []byte, error) {
9696
}
9797

9898
func ValidateConfig(config ProjectConfig) error {
99-
if config.SchemaVersion != 1 {
100-
return fmt.Errorf("project config schema_version must be 1")
99+
if config.SchemaVersion < 1 {
100+
return fmt.Errorf("project config schema_version must be >= 1")
101+
}
102+
if config.SchemaVersion > currentSchemaVersion() {
103+
return fmt.Errorf("config was written by a newer Boatstack; update Boatstack")
104+
}
105+
if config.SchemaVersion < currentSchemaVersion() {
106+
return fmt.Errorf("config schema is behind; run /boatstack-update")
101107
}
102108
if strings.TrimSpace(config.Project.Name) == "" {
103109
return fmt.Errorf("project.name is required")
@@ -217,7 +223,7 @@ func BuildExportBundle(configPath string, config ProjectConfig, rawConfig []byte
217223
}
218224
}
219225

220-
for _, name := range []string{"workflow.md", "artifacts.md", "failure-moves.md", "irreversible-operation-boundary.md", "host-hook-contracts.md"} {
226+
for _, name := range []string{"workflow.md", "artifacts.md", "failure-moves.md", "irreversible-operation-boundary.md", "host-hook-contracts.md", "config-schema.md"} {
221227
value, err := readCanonical("references/" + name)
222228
if err != nil {
223229
return ExportBundle{}, err

boatstack/init.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,26 @@ func RunInit(options InitOptions) (returnErr error) {
313313
}
314314
var config ProjectConfig
315315
var rawConfig []byte
316+
var migrationFrom, migrationTo int
317+
var migrationChanged bool
316318
if configExists {
317-
config, rawConfig, err = LoadConfig(configPath)
319+
var err error
320+
rawConfig, err = os.ReadFile(configPath)
318321
if err != nil {
322+
return err
323+
}
324+
var upgraded []byte
325+
upgraded, migrationFrom, migrationTo, migrationChanged, err = MigrateConfigBytes(rawConfig)
326+
if err != nil {
327+
return fmt.Errorf("failed to migrate project config: %w", err)
328+
}
329+
if migrationChanged {
330+
rawConfig = upgraded
331+
}
332+
if err := DecodeJSON("load project configuration", configPath, rawConfig, &config); err != nil {
333+
return fmt.Errorf("existing Boatstack config is invalid: %w", err)
334+
}
335+
if err := ValidateConfig(config); err != nil {
319336
return fmt.Errorf("existing Boatstack config is invalid: %w", err)
320337
}
321338
} else {
@@ -508,7 +525,11 @@ func RunInit(options InitOptions) (returnErr error) {
508525
return scopeErr
509526
}
510527
fmt.Fprintf(options.Output, "\nPASS: Boatstack updated to %s on a dedicated infrastructure branch.\n", Version)
511-
fmt.Fprintln(options.Output, "PASS: no product files changed.")
528+
if migrationChanged {
529+
fmt.Fprintf(options.Output, "PASS: migrated .boatstack-project.json from schema version %d to %d.\n", migrationFrom, migrationTo)
530+
} else {
531+
fmt.Fprintln(options.Output, "PASS: no product files changed.")
532+
}
512533
fmt.Fprintln(options.Output, "Changed Boatstack paths:")
513534
for _, path := range changed {
514535
fmt.Fprintln(options.Output, " "+path)

boatstack/migrate.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package boatstack
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
)
8+
9+
const CurrentConfigSchemaVersion = 1
10+
11+
var currentConfigSchemaVersionOverride = CurrentConfigSchemaVersion
12+
13+
type configMigration struct {
14+
from int
15+
to int
16+
apply func(map[string]any) (map[string]any, error)
17+
}
18+
19+
var configMigrations []configMigration
20+
21+
func currentSchemaVersion() int {
22+
return currentConfigSchemaVersionOverride
23+
}
24+
25+
// MigrateConfigBytes migrates raw JSON configuration bytes to the latest CurrentConfigSchemaVersion.
26+
// It returns the upgraded JSON bytes, the original schema version, the final schema version,
27+
// a boolean indicating whether the content actually changed, and any error encountered.
28+
func MigrateConfigBytes(raw []byte) (upgraded []byte, from, to int, changed bool, err error) {
29+
if len(strings.TrimSpace(string(raw))) == 0 {
30+
return raw, 0, 0, false, nil
31+
}
32+
33+
var partial map[string]any
34+
if err := json.Unmarshal(raw, &partial); err != nil {
35+
return nil, 0, 0, false, fmt.Errorf("failed to parse config JSON: %w", err)
36+
}
37+
38+
targetVer := currentSchemaVersion()
39+
40+
var fromVer int
41+
if v, exists := partial["schema_version"]; exists {
42+
switch val := v.(type) {
43+
case float64:
44+
fromVer = int(val)
45+
case int:
46+
fromVer = val
47+
default:
48+
return nil, 0, 0, false, fmt.Errorf("schema_version must be an integer")
49+
}
50+
} else {
51+
// Default to 1 if schema_version is missing
52+
fromVer = 1
53+
}
54+
55+
if fromVer > targetVer {
56+
return nil, fromVer, 0, false, fmt.Errorf("config was written by a newer Boatstack; update Boatstack")
57+
}
58+
59+
if fromVer == targetVer {
60+
return raw, fromVer, fromVer, false, nil
61+
}
62+
63+
currentVer := fromVer
64+
data := partial
65+
66+
for currentVer < targetVer {
67+
var found *configMigration
68+
for i := range configMigrations {
69+
if configMigrations[i].from == currentVer {
70+
found = &configMigrations[i]
71+
break
72+
}
73+
}
74+
75+
if found == nil {
76+
return nil, fromVer, 0, false, fmt.Errorf("no migration found from version %d to %d", currentVer, currentVer+1)
77+
}
78+
79+
var err error
80+
data, err = found.apply(data)
81+
if err != nil {
82+
return nil, fromVer, 0, false, fmt.Errorf("failed to apply migration from %d to %d: %w", found.from, found.to, err)
83+
}
84+
85+
if found.to <= currentVer {
86+
return nil, fromVer, 0, false, fmt.Errorf("invalid migration path from %d to %d", found.from, found.to)
87+
}
88+
89+
currentVer = found.to
90+
}
91+
92+
data["schema_version"] = targetVer
93+
94+
upgraded, err = json.MarshalIndent(data, "", " ")
95+
if err != nil {
96+
return nil, fromVer, 0, false, fmt.Errorf("failed to marshal migrated config: %w", err)
97+
}
98+
upgraded = append(upgraded, '\n')
99+
100+
return upgraded, fromVer, targetVer, true, nil
101+
}

0 commit comments

Comments
 (0)