diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f194d83..6f301e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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/3e44eb72add2a33fb84bbc5a0142af9c1828feb1/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/3e8ad57097eea39260ae67102997bdbd316faf17/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. diff --git a/UPSTREAM.json b/UPSTREAM.json index aab8d5e..cf66d50 100644 --- a/UPSTREAM.json +++ b/UPSTREAM.json @@ -12,7 +12,7 @@ }, "files": { ".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957", - "CONTRIBUTING.md": "cb76f4adb6c3ea2b4b1287ca6099bf0b38f56552649389fc8b42fd95320cb792", + "CONTRIBUTING.md": "563a299dd1dc84775c77db39f80372ab37dc4499dabc097521bc969df222aa16", "README.md": "125b47671a68556df382f19756fb61fa18925606cbbaf54d6bc9df8872b36870", "assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63", "assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5", @@ -34,6 +34,8 @@ "boatstack/assets/templates/test-plan.md": "6db8a9f27dd171fb80222a501cae50eb051e7278c04703fa43b5ff86dd4d2df4", "boatstack/atomic_unix.go": "89f2723361591de2bb8bd22ce7e34ec529d3278509f0df78fd5c4a7d4140fbe9", "boatstack/atomic_windows.go": "cefd775cbe7e7c3bd8a3f5673b11cdd784c6d3ebd6de7dcb8f39406b0bee511f", + "boatstack/capability.go": "9d9a75086e88bb1d9d4170821436c686002fe3a125e6ee7d23eea5779aac5cfe", + "boatstack/capability_test.go": "e8322903a843970d7f0317d2629466532cb05c3ffcb44b9423adf4219faa8021", "boatstack/changelog.go": "c5e1f31440b44d61e6037ad27af0333540af3545d655e35819a0241cbbebd8ec", "boatstack/changelog_test.go": "ce792f23a7fe1e09fb3096cd1314130a6ab69321d4877b12a8e994027541baf7", "boatstack/cmd/boatstack-helper/main.go": "43f070dc71f924ea6466b4190c43811d7884cece7d9d44df433c9beeb6982053", @@ -99,7 +101,7 @@ "boatstack/update_publication.go": "c8b7bd38019b1cbf8c523652e9e20e8c971c7e48b2f3972a0ac638648326fe63", "boatstack/update_publication_test.go": "c5f32578db53be65e35452d5e8b4520884354e4e80a370dcec19860ea644d091", "boatstack/update_test.go": "bf5f19f8499db6dd7356917d867b113b790548ab89bfdea59e2adf4999a82a6a", - "boatstack/visual_evidence.go": "90a68d554e10ff4fb7afa45000912cdedd4cdf93b3d279055b50844401924f01", + "boatstack/visual_evidence.go": "4d69f98adb6087d4830e6703273ae6e03b28ec8b3c496a6e432fd5e0e54d8a2f", "boatstack/visual_evidence_test.go": "0fe8f5154ef4398dfeba5e7f7b387b2d279ed75635ea35d93ff392269f2cc6d0", "boatstack/workspace.go": "91b343400b3506a6f516c28fabc3f1575f22024a5b19f934a020be660a20482e", "boatstack/workspace_sync.go": "0cc2f03fd1aa57c66b3d603d5ef30aa820f14ab60771a795cf10c46f3c9a71b8", @@ -109,10 +111,10 @@ "docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6", "docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79", "docs/configuration.md": "4d8f207b415a5a1e3b9b1698ee7bb1221aa0e5496a061bb8054294df2f347ad1", - "docs/evidence-engineered-coding.md": "5d3732ff40bec63ff8ed89f60621347520e3607c00438115b9051d85368a2c82", + "docs/evidence-engineered-coding.md": "fde47bd7e90da1211c80c060db4e5ece89b1aa412ad9f6ce23220be9f3d2cbec", "docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3", "docs/getting-started.md": "f314270c5ed1a55bbef5f3ddbcb5596693dbee9374e5f0d3df8838cefbd68052", - "docs/public-claims.json": "c4ed79593ff0acd86e4c1602bc269e8ccc9fe6400f8c798d714c78d599aa47a3", + "docs/public-claims.json": "9263cd94c70383be68215fee3ef3b38277c719b483266b9ee33efb9b2e0cb202", "docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907", "docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6", "docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6", @@ -126,7 +128,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": "24f89e5c01a81b707c8de9b815b891210841a444d868056306009e43803ca37f", + "labs/diagram-json/plan.lock.json": "6c0c2c72b9da22c4af9687f781284656e174464159d76f6a076f8c0a24f7039d", "labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d", "labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed", @@ -186,9 +188,11 @@ "release-notes/2026-07-22-value-translation-boundary.md": "9cf168ff7caaf3906b78533935bdfb2c86e753984ed1e5ec8204cb373d083390", "release-notes/2026-07-23-bootstrap-safe-update-repair.md": "d8e66c46ae05e3d228dfea45879f4d1166d5e3a253ac24bababd4c3e396c214b", "release-notes/2026-07-23-canonical-update-ownership.md": "7f34f890b252493797519389b23f1b56ec7ec16db7547aac8ea196f75f3b8c2c", + "release-notes/2026-07-23-evidence-capability-substrate.md": "a3e6e364536f826f299243932494096c27458e2621a5ce55465c5db280b53758", "release-notes/2026-07-23-explicit-source-plan.md": "ec1f97434f9263f6db4bc3b83ae83213053cd2bc6b7465e4fb2d67cbea5cf731", "release-notes/2026-07-23-ignore-ambiguous-deliveries.md": "9b1b9fd48db340b91fcced1c282297de0ecad8744723f2b1fdd94033927a88c4", "release-notes/2026-07-23-labkit-publishing-pipeline.md": "b3bfc5f28baf3961fb187380ff66e5383186c05a933bc6ca162416ce61149218", + "release-notes/2026-07-23-labkit-standalone-package.md": "50ae9ba89fdada6e04a9200ea26d53a2a04484a1759cdea663687cc9b42fb93c", "release-notes/2026-07-23-managed-pr-task-graph-layout.md": "e2d67f15cc6a1eb200d6f13f81d891b30eae1bd51182d768dda561fcfdc69435", "release-notes/2026-07-23-recoverable-repository-sync.md": "3afc4f6220ae76df3bd6dcd15fc180135274c808729e9c512a2c53462ec690c2", "release-notes/2026-07-23-shipped-feature-candidate-resolution.md": "bd8ee8e7f3f216b356b121a83ef10cb0c8131a90b9ab803edebf23b882d9cf89" @@ -196,7 +200,7 @@ "generator": "operatorstack/intelligence-flow:boatstack-distribution", "schema_version": 1, "source": { - "commit": "3e44eb72add2a33fb84bbc5a0142af9c1828feb1", + "commit": "3e8ad57097eea39260ae67102997bdbd316faf17", "path": "labs/12-product-engineering-loop", "repository": "operatorstack/intelligence-flow" } diff --git a/boatstack/capability.go b/boatstack/capability.go new file mode 100644 index 0000000..b192c5e --- /dev/null +++ b/boatstack/capability.go @@ -0,0 +1,70 @@ +package boatstack + +import ( + "fmt" + "strings" +) + +// Capability describes a named producer of PR evidence. It is the generic spine +// that concrete evidence types register on: detection ("can this repository +// produce the evidence?"), capture orchestration ("what command runs it, and +// when?"), and provisioning ("if it is unavailable, how do we help?") all read +// this registry instead of hard-coding a single evidence type. +// +// Boatstack ships the contract in this registry, not the capture harness. The +// harness is authored in the user's repository and invoked through the resolved +// repository command; Boatstack only records what the command must satisfy. +type Capability struct { + // Name is the canonical capability identifier, e.g. "visual". + Name string + // CommandAliases are the project.commands keys that satisfy the capability, + // in priority order. The first non-empty command wins. + CommandAliases []string + // AdmittedStages are the delivery stages in which a capture attempt may run. + AdmittedStages []string + // RetryClass is the operation retry class recorded for a capture attempt. + RetryClass string +} + +// CapabilityResolution is the portable capability cut for one capability: it +// reports whether the repository owns a command that produces this evidence. +type CapabilityResolution struct { + Name string `json:"name"` + Kind string `json:"kind"` // repository-command | unavailable + Command string `json:"command,omitempty"` +} + +// capabilityRegistry holds every evidence capability Boatstack knows about. +// Adding a provider is a single entry here plus its tenant-specific manifest +// contract (see visual_evidence.go for the first tenant). +var capabilityRegistry = map[string]Capability{ + "visual": { + Name: "visual", + CommandAliases: []string{"visual", "screenshot", "e2e"}, + AdmittedStages: []string{"BUILD", "TEST_PASSED"}, + RetryClass: "IDEMPOTENT_EXTERNAL", + }, +} + +// LookupCapability returns the registered capability metadata for a name. +func LookupCapability(name string) (Capability, bool) { + capability, ok := capabilityRegistry[strings.TrimSpace(name)] + return capability, ok +} + +// ResolveCapability performs the repository-owned capability cut: it selects the +// first project command alias that is set. It is the generic detection primitive +// shared by capture orchestration and provisioning. Kind is "repository-command" +// when the repository owns a command, otherwise "unavailable". +func ResolveCapability(name string, config ProjectConfig) (CapabilityResolution, error) { + capability, ok := LookupCapability(name) + if !ok { + return CapabilityResolution{}, fmt.Errorf("unknown evidence capability %q", name) + } + for _, alias := range capability.CommandAliases { + if command := strings.TrimSpace(config.Project.Commands[alias]); command != "" { + return CapabilityResolution{Name: capability.Name, Kind: "repository-command", Command: command}, nil + } + } + return CapabilityResolution{Name: capability.Name, Kind: "unavailable"}, nil +} diff --git a/boatstack/capability_test.go b/boatstack/capability_test.go new file mode 100644 index 0000000..8e30e72 --- /dev/null +++ b/boatstack/capability_test.go @@ -0,0 +1,61 @@ +package boatstack + +import "testing" + +func TestResolveCapabilitySelectsRepositoryCommandByAliasPriority(t *testing.T) { + config := testConfig() + delete(config.Project.Commands, "visual") + delete(config.Project.Commands, "screenshot") + delete(config.Project.Commands, "e2e") + + // Lowest-priority alias still resolves the repository-owned cut. + config.Project.Commands["e2e"] = "npm run e2e" + resolution, err := ResolveCapability("visual", config) + if err != nil { + t.Fatalf("resolve visual: %v", err) + } + if resolution.Kind != "repository-command" || resolution.Command != "npm run e2e" || resolution.Name != "visual" { + t.Fatalf("alias fallback did not resolve: %#v", resolution) + } + + // A higher-priority alias wins over a lower one. + config.Project.Commands["visual"] = "npm run capture:visual" + resolution, err = ResolveCapability("visual", config) + if err != nil { + t.Fatalf("resolve visual: %v", err) + } + if resolution.Command != "npm run capture:visual" { + t.Fatalf("higher-priority alias did not win: %#v", resolution) + } +} + +func TestResolveCapabilityReportsUnavailableWithoutCommand(t *testing.T) { + config := testConfig() + delete(config.Project.Commands, "visual") + delete(config.Project.Commands, "screenshot") + delete(config.Project.Commands, "e2e") + + resolution, err := ResolveCapability("visual", config) + if err != nil { + t.Fatalf("resolve visual: %v", err) + } + if resolution.Kind != "unavailable" || resolution.Command != "" { + t.Fatalf("expected unavailable resolution: %#v", resolution) + } +} + +func TestResolveCapabilityRejectsUnknownCapability(t *testing.T) { + if _, err := ResolveCapability("does-not-exist", testConfig()); err == nil { + t.Fatal("expected error for unknown capability") + } +} + +func TestLookupCapabilityExposesRegisteredMetadata(t *testing.T) { + capability, ok := LookupCapability("visual") + if !ok { + t.Fatal("visual capability is not registered") + } + if len(capability.CommandAliases) == 0 || capability.RetryClass == "" || len(capability.AdmittedStages) == 0 { + t.Fatalf("visual capability metadata is incomplete: %#v", capability) + } +} diff --git a/boatstack/visual_evidence.go b/boatstack/visual_evidence.go index ca4de7e..1f22727 100644 --- a/boatstack/visual_evidence.go +++ b/boatstack/visual_evidence.go @@ -77,13 +77,17 @@ type PRVisualCaptureCapability struct { Command string `json:"command,omitempty"` } -// ResolvePRVisualCaptureCapability implements the portable capability cut. It -// selects repository-owned tooling before host or machine-local capabilities. +// ResolvePRVisualCaptureCapability implements the portable capability cut for the +// visual capability. It selects repository-owned tooling (via the generic +// ResolveCapability spine) before host or machine-local capabilities. The +// browser-specific fallbacks below the repository cut are visual-only. func ResolvePRVisualCaptureCapability(repo string, config ProjectConfig, hostBrowser bool, suppliedLaunch string, expectedReceipt PRVisualCapabilityReceipt) (PRVisualCaptureCapability, error) { - for _, name := range []string{"visual", "screenshot", "e2e"} { - if command := strings.TrimSpace(config.Project.Commands[name]); command != "" { - return PRVisualCaptureCapability{Kind: "repository-command", Command: command}, nil - } + resolution, err := ResolveCapability("visual", config) + if err != nil { + return PRVisualCaptureCapability{}, err + } + if resolution.Kind == "repository-command" { + return PRVisualCaptureCapability{Kind: "repository-command", Command: resolution.Command}, nil } if hostBrowser { return PRVisualCaptureCapability{Kind: "host-browser"}, nil diff --git a/docs/evidence-engineered-coding.md b/docs/evidence-engineered-coding.md index 6a033b8..fa2699a 100644 --- a/docs/evidence-engineered-coding.md +++ b/docs/evidence-engineered-coding.md @@ -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 [`3e44eb72add2a33fb84bbc5a0142af9c1828feb1`](https://github.com/operatorstack/intelligence-flow/tree/3e44eb72add2a33fb84bbc5a0142af9c1828feb1/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 [`3e8ad57097eea39260ae67102997bdbd316faf17`](https://github.com/operatorstack/intelligence-flow/tree/3e8ad57097eea39260ae67102997bdbd316faf17/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. diff --git a/docs/public-claims.json b/docs/public-claims.json index 564f1e8..0ff1446 100644 --- a/docs/public-claims.json +++ b/docs/public-claims.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "source_commit": "3e44eb72add2a33fb84bbc5a0142af9c1828feb1", + "source_commit": "3e8ad57097eea39260ae67102997bdbd316faf17", "statuses": ["verified", "observed", "still_being_evaluated"], "claims": [ { @@ -12,7 +12,7 @@ "readable_evidence": "why-these-steps.md#portable-workflow-and-state", "implementation": ["../boatstack/export.go", "../boatstack/references/artifacts.md", "../boatstack/references/workflow.md"], "verification": ["../boatstack/export_test.go"], - "last_verified_version": "source:3e44eb72add2a33fb84bbc5a0142af9c1828feb1" + "last_verified_version": "source:3e8ad57097eea39260ae67102997bdbd316faf17" }, { "id": "human-decisions", @@ -23,7 +23,7 @@ "readable_evidence": "why-these-steps.md#human-decisions", "implementation": ["../boatstack/references/workflow.md", "../boatstack/plan.go"], "verification": ["../boatstack/plan_test.go", "../boatstack/planning_test.go"], - "last_verified_version": "source:3e44eb72add2a33fb84bbc5a0142af9c1828feb1" + "last_verified_version": "source:3e8ad57097eea39260ae67102997bdbd316faf17" }, { "id": "validation-provenance", @@ -34,7 +34,7 @@ "readable_evidence": "why-these-steps.md#validation-provenance", "implementation": ["validation-and-evidence.md", "../boatstack/plan.go"], "verification": ["../boatstack/plan_test.go"], - "last_verified_version": "source:3e44eb72add2a33fb84bbc5a0142af9c1828feb1" + "last_verified_version": "source:3e8ad57097eea39260ae67102997bdbd316faf17" }, { "id": "irreversible-operations", @@ -46,7 +46,7 @@ "readable_evidence": "why-these-steps.md#irreversible-operations", "implementation": ["safety.md", "../boatstack/safety.go", "../boatstack/hooks.go"], "verification": ["../boatstack/safety_test.go", "../boatstack/hooks_test.go"], - "last_verified_version": "source:3e44eb72add2a33fb84bbc5a0142af9c1828feb1" + "last_verified_version": "source:3e8ad57097eea39260ae67102997bdbd316faf17" }, { "id": "reviewer-ready-pr", @@ -57,7 +57,7 @@ "readable_evidence": "why-these-steps.md#reviewer-ready-pr", "implementation": ["../boatstack/pr.go", "getting-started.md"], "verification": ["../boatstack/pr_test.go"], - "last_verified_version": "source:3e44eb72add2a33fb84bbc5a0142af9c1828feb1" + "last_verified_version": "source:3e8ad57097eea39260ae67102997bdbd316faf17" }, { "id": "phase-scoped-delivery", @@ -68,7 +68,7 @@ "readable_evidence": "why-these-steps.md#phase-scoped-delivery", "implementation": ["../boatstack/delivery.go", "../boatstack/safety.go", "../boatstack/hooks.go", "../boatstack/references/workflow.md"], "verification": ["../boatstack/delivery_test.go", "../boatstack/pr_test.go"], - "last_verified_version": "source:3e44eb72add2a33fb84bbc5a0142af9c1828feb1" + "last_verified_version": "source:3e8ad57097eea39260ae67102997bdbd316faf17" }, { "id": "model-neutral-contract", @@ -79,7 +79,7 @@ "readable_evidence": "why-these-steps.md#model-choice-and-budget", "implementation": ["research-and-design.md", "../boatstack/references/workflow.md"], "verification": ["../boatstack/export_test.go", "../boatstack/planning_test.go"], - "last_verified_version": "source:3e44eb72add2a33fb84bbc5a0142af9c1828feb1" + "last_verified_version": "source:3e8ad57097eea39260ae67102997bdbd316faf17" }, { "id": "cross-model-failures", @@ -90,7 +90,7 @@ "readable_evidence": "why-these-steps.md#model-choice-and-budget", "implementation": ["research-and-design.md"], "verification": ["benchmark-corpus-audit.md", "benchmark-submission-audit.md"], - "last_verified_version": "source:3e44eb72add2a33fb84bbc5a0142af9c1828feb1" + "last_verified_version": "source:3e8ad57097eea39260ae67102997bdbd316faf17" }, { "id": "lower-cost-outcomes", @@ -101,7 +101,7 @@ "readable_evidence": "why-these-steps.md#model-choice-and-budget", "implementation": ["research-and-design.md"], "verification": ["benchmark-corpus-audit.md", "benchmark-submission-audit.md"], - "last_verified_version": "source:3e44eb72add2a33fb84bbc5a0142af9c1828feb1" + "last_verified_version": "source:3e8ad57097eea39260ae67102997bdbd316faf17" }, { "id": "git-worktree-activation", @@ -112,7 +112,7 @@ "readable_evidence": "why-these-steps.md#git-worktree-activation", "implementation": ["../boatstack/runtime_cache.go", "../boatstack/hooks.go"], "verification": ["../boatstack/runtime_cache_test.go", "../boatstack/hooks_test.go"], - "last_verified_version": "source:3e44eb72add2a33fb84bbc5a0142af9c1828feb1" + "last_verified_version": "source:3e8ad57097eea39260ae67102997bdbd316faf17" }, { "id": "visible-updates", @@ -123,7 +123,7 @@ "readable_evidence": "why-these-steps.md#visible-updates", "implementation": ["../boatstack/update.go", "../boatstack/init.go"], "verification": ["../boatstack/update_test.go", "../boatstack/init_test.go", "../boatstack/export_test.go"], - "last_verified_version": "source:3e44eb72add2a33fb84bbc5a0142af9c1828feb1" + "last_verified_version": "source:3e8ad57097eea39260ae67102997bdbd316faf17" } ] } diff --git a/labs/diagram-json/plan.lock.json b/labs/diagram-json/plan.lock.json index e12145a..c275ad7 100644 --- a/labs/diagram-json/plan.lock.json +++ b/labs/diagram-json/plan.lock.json @@ -6,7 +6,7 @@ "plan_path": "labs/diagram-json/plan.md", "plan_sha256": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "schema_version": 1, - "source_commit": "3e44eb72add2a33fb84bbc5a0142af9c1828feb1", + "source_commit": "3e8ad57097eea39260ae67102997bdbd316faf17", "source_plan_path": "labs/diagram-json/source-plan.md", "source_plan_sha256": "e10593ddaa7522ab80cc991d0a09399257139799e37f737794cd49d68a39985b", "spec_path": "labs/diagram-json/spec.md", diff --git a/release-notes/2026-07-23-evidence-capability-substrate.md b/release-notes/2026-07-23-evidence-capability-substrate.md new file mode 100644 index 0000000..4840198 --- /dev/null +++ b/release-notes/2026-07-23-evidence-capability-substrate.md @@ -0,0 +1,9 @@ +### Evidence capabilities are now a shared, extensible substrate + +Boatstack's PR evidence detection is no longer hard-wired to a single evidence +type. A generic evidence-capability registry now backs the "can this repository +produce the evidence?" cut, and visual evidence is its first tenant. Nothing +changes in how visual evidence behaves today — the same repository commands, +statuses, and fingerprints — but the shared spine means future evidence types +(and the upcoming capture and provisioning flows) plug in through one registry +instead of a bespoke path each time. diff --git a/release-notes/2026-07-23-labkit-standalone-package.md b/release-notes/2026-07-23-labkit-standalone-package.md new file mode 100644 index 0000000..c669130 --- /dev/null +++ b/release-notes/2026-07-23-labkit-standalone-package.md @@ -0,0 +1,8 @@ +### Publishing toolkit moved to a standalone package + +The `labkit` toolkit that projects Boatstack into this repository moved from a +submodule of the monorepo's Python package to a standalone top-level `labkit/` +project, invoked as `python -m labkit`. This only changes where the publishing +tooling lives and how the sync workflow installs it; the projected files are +byte-for-byte identical (asserted by the golden reproduction test), with no +effect on Boatstack's runtime, CLI, skill, or public contract.