Skip to content

Commit ae843ab

Browse files
Sync Boatstack from Intelligence Flow Labs @ 2359acff52f0
1 parent 364e6da commit ae843ab

29 files changed

Lines changed: 826 additions & 160 deletions

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/e43a4ed28726995d848266cb39bb20902bbb1574/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/2359acff52f0c7a7568fbd7daf1b62a79a86f7b5/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: 31 additions & 28 deletions
Large diffs are not rendered by default.

boatstack/attach.go

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ type AttachOptions struct {
2121

2222
// AttachResult is the deterministic outcome of an attach request.
2323
type AttachResult struct {
24-
SchemaVersion int `json:"schema_version"`
25-
VerificationStatus string `json:"verification_status"` // VERIFIED | BLOCKED
26-
Mode string `json:"mode,omitempty"`
27-
RepoID string `json:"repo_id,omitempty"`
28-
RepoRoot string `json:"repo_root,omitempty"`
29-
ControlRoot string `json:"control_root,omitempty"`
30-
WorktreeID string `json:"worktree_id,omitempty"`
31-
Reason string `json:"reason"`
24+
SchemaVersion int `json:"schema_version"`
25+
VerificationStatus string `json:"verification_status"` // VERIFIED | BLOCKED
26+
Mode string `json:"mode,omitempty"`
27+
RepoID string `json:"repo_id,omitempty"`
28+
RepoRoot string `json:"repo_root,omitempty"`
29+
ControlRoot string `json:"control_root,omitempty"`
30+
WorktreeID string `json:"worktree_id,omitempty"`
31+
Reason string `json:"reason"`
32+
FeatureMigrations []DetachedFeatureMigration `json:"feature_migrations,omitempty"`
3233
}
3334

3435
func blockedAttach(reason string) AttachResult {
@@ -62,12 +63,22 @@ func AttachDetached(opts AttachOptions) (AttachResult, error) {
6263

6364
ctx := detachedContextFromIdentity(stateRoot, identity)
6465

65-
// Synthesize configuration from the repository (test command, default branch,
66-
// context) exactly as embedded init does.
67-
config := defaultConfig(root, detectTestCommand(root))
68-
rawConfig, err := MarshalJSON(config)
66+
// Prefer the repository's declared source configuration during explicit
67+
// reattachment. Falling back to discovery is valid only when no source exists.
68+
configPath := filepath.Join(root, sourceConfigName)
69+
config, rawConfig, err := LoadConfig(configPath)
70+
if os.IsNotExist(err) {
71+
config = defaultConfig(root, detectTestCommand(root))
72+
rawConfig, err = MarshalJSON(config)
73+
}
6974
if err != nil {
70-
return blockedAttach(err.Error()), nil
75+
return blockedAttach("Boatstack could not load the repository source configuration: " + err.Error()), nil
76+
}
77+
imports, migrationResults, migrationErr := planDetachedFeatureImports(root, ctx)
78+
if migrationErr != nil {
79+
result := blockedAttach("Boatstack refused detached feature migration: " + migrationErr.Error())
80+
result.FeatureMigrations = migrationResults
81+
return result, nil
7182
}
7283

7384
// Generate the controller bundle and write it under the external control root.
@@ -86,6 +97,10 @@ func AttachDetached(opts AttachOptions) (AttachResult, error) {
8697
if err := os.WriteFile(ctx.SourceConfigPath(), rawConfig, 0o644); err != nil {
8798
return blockedAttach(err.Error()), nil
8899
}
100+
migrationResults, err = applyDetachedFeatureImports(imports, migrationResults)
101+
if err != nil {
102+
return blockedAttach("Boatstack could not import embedded feature state: " + err.Error()), nil
103+
}
89104

90105
// Write the binding and index it in the registry.
91106
binding := DetachedBinding{
@@ -132,6 +147,7 @@ func AttachDetached(opts AttachOptions) (AttachResult, error) {
132147
RepoRoot: root,
133148
ControlRoot: ctx.controlRoot,
134149
WorktreeID: identity.WorktreeID,
150+
FeatureMigrations: migrationResults,
135151
Reason: "Attached Boatstack in detached mode. The repository was not modified; all controller state lives under the external control root.",
136152
}, nil
137153
}

boatstack/cmd/boatstack-helper/main.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ func checkPlanCommand(arguments []string) int {
459459
readinessFingerprint := ""
460460
if version, _ := check.Plan["schema_version"].(float64); version >= 3 {
461461
readiness, readinessErr := boatstack.CheckPlanReadiness(*plan)
462-
repo, _ := boatstack.ResolveRepository(filepath.Dir(*plan))
462+
repo, _ := boatstack.ResolveControllerRepository(filepath.Dir(*plan))
463463
if readinessErr != nil {
464464
boatstack.RecordFlowAttribution(repo, "readiness", deliverycontrol.CostQuery, true, readinessErr.Error())
465465
return fail(readinessErr)
@@ -1102,7 +1102,16 @@ func doctorCommand(arguments []string) int {
11021102
if err := boatstack.DoctorRepairHint(boatstack.Doctor(*repo)); err != nil {
11031103
return fail(err)
11041104
}
1105+
root, err := boatstack.ResolveRepository(*repo)
1106+
if err != nil {
1107+
return fail(err)
1108+
}
1109+
ctx, err := boatstack.ResolveWorkspaceContext(root)
1110+
if err != nil {
1111+
return fail(err)
1112+
}
11051113
fmt.Printf("PASS: Boatstack %s installation and generated adapters are healthy\n", boatstack.Version)
1114+
fmt.Printf("SUPERVISION_MODE=%s\nCONTROLLER_ROOT=%s\nHEALTH=VERIFIED\n", ctx.Mode, ctx.ExportRoot())
11061115
hosts, err := boatstack.DoctorHookHosts(*repo)
11071116
if err != nil {
11081117
return fail(err)

boatstack/context.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func ProjectOperatorContext(repoPath, operation, host string) (OperatorContext,
3737
}
3838
out := OperatorContext{
3939
SchemaVersion: detachedSchemaVersion, Mode: string(SupervisionEmbedded),
40-
RepoRoot: root, Operation: operation, Host: host,
40+
RepoRoot: root, ControlRoot: root, Operation: operation, Host: host,
4141
}
4242

4343
if ctx, ok, verifyErr := detachedContextFor(root); verifyErr != nil {

boatstack/delivery.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ func archiveDeliveryReceipt(repo, feature, sliceID, gate, observationID string)
560560
}
561561

562562
func appendChangeObservation(repo string, observation ChangeObservation) error {
563-
path := filepath.Join(repo, ".product-loop", "features", observation.Feature, "changes.md")
563+
path := filepath.Join(WorkspaceFor(repo).FeatureDir(observation.Feature), "changes.md")
564564
existing, err := os.ReadFile(path)
565565
if err != nil && !os.IsNotExist(err) {
566566
return err
@@ -577,7 +577,7 @@ func appendChangeObservation(repo string, observation ChangeObservation) error {
577577
}
578578

579579
func nextChangeObservationID(repo, feature string, fallback int) string {
580-
path := filepath.Join(repo, ".product-loop", "features", feature, "changes.md")
580+
path := filepath.Join(WorkspaceFor(repo).FeatureDir(feature), "changes.md")
581581
value, err := os.ReadFile(path)
582582
if err != nil {
583583
return fmt.Sprintf("CHG-%03d", fallback)
@@ -625,7 +625,7 @@ func RecordChangeObservation(options ChangeObservationOptions) (ChangeObservatio
625625
evidenceHash := SHA256Bytes([]byte(strings.TrimSpace(options.Evidence)))
626626
mechanismHash := SHA256Bytes([]byte(strings.TrimSpace(options.Mechanism)))
627627
if repairClass {
628-
changePath := filepath.Join(repo, ".product-loop", "features", options.Feature, "changes.md")
628+
changePath := filepath.Join(WorkspaceFor(repo).FeatureDir(options.Feature), "changes.md")
629629
if prior, readErr := os.ReadFile(changePath); readErr == nil {
630630
for _, block := range strings.Split(string(prior), "\n## ") {
631631
if strings.Contains(block, "- Classification: `"+classification+"`") &&
@@ -905,7 +905,7 @@ func resolveAddressableSliceByBranch(state DeliveryState, branch string) (int, D
905905
}
906906

907907
func checkDeliveryPlanLock(repo, feature string, state DeliveryState) error {
908-
lockPath := filepath.Join(repo, ".product-loop", "features", feature, "plan.lock.json")
908+
lockPath := filepath.Join(WorkspaceFor(repo).FeatureDir(feature), "plan.lock.json")
909909
lockHash, err := SHA256File(lockPath)
910910
if err != nil {
911911
return fmt.Errorf("managed delivery requires its current plan lock: %w", err)
@@ -1109,7 +1109,7 @@ func RecordDeliveryGate(options DeliveryGateOptions) (DeliveryGateReceipt, error
11091109
}
11101110
evidencePath := strings.TrimSpace(options.EvidencePath)
11111111
if evidencePath == "" {
1112-
evidencePath = featureEvidencePath(filepath.Join(repo, ".product-loop", "features", options.Feature))
1112+
evidencePath = featureEvidencePath(WorkspaceFor(repo).FeatureDir(options.Feature))
11131113
} else if !filepath.IsAbs(evidencePath) {
11141114
evidencePath = filepath.Join(repo, evidencePath)
11151115
}
@@ -1129,7 +1129,7 @@ func RecordDeliveryGate(options DeliveryGateOptions) (DeliveryGateReceipt, error
11291129
if recorded := deliveryEvidenceGateStatus(string(evidenceValue), gateLabel, slice.ID, explicit); recorded != status {
11301130
return DeliveryGateReceipt{}, fmt.Errorf("evidence ledger must mark the %s gate for delivery slice %s as %s; found %q", gate, slice.ID, status, recorded)
11311131
}
1132-
relEvidence, err := repositoryRelativePath(repo, evidencePath)
1132+
relEvidence, err := repositoryRelativePath(WorkspaceFor(repo).ExportRoot(), evidencePath)
11331133
if err != nil {
11341134
return DeliveryGateReceipt{}, err
11351135
}
@@ -1451,7 +1451,7 @@ type DiscardDeliveryResult struct {
14511451
// orphan, so discard-delivery must clear it. It refuses a dir carrying a
14521452
// plan.lock.json (a registered, live feature) so it never touches active work.
14531453
func discardOrphanFeatureArtifacts(repo, feature string) (DiscardDeliveryResult, bool, error) {
1454-
dir := filepath.Join(repo, ".product-loop", "features", feature)
1454+
dir := WorkspaceFor(repo).FeatureDir(feature)
14551455
info, statErr := os.Stat(dir)
14561456
if os.IsNotExist(statErr) {
14571457
return DiscardDeliveryResult{}, false, nil

0 commit comments

Comments
 (0)