Skip to content

Commit 197b0cc

Browse files
Sync Boatstack from Intelligence Flow Labs @ d950cb53d33f
1 parent 90a92d8 commit 197b0cc

16 files changed

Lines changed: 328 additions & 104 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/6149e9333caa3d936f69e03015252b712cf453ee/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/d950cb53d33f4f2571a62ce05e10a85a3da7756b/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: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"canonical_context": {
3-
"characters": 89360,
4-
"estimated_tokens": 22340,
3+
"characters": 91083,
4+
"estimated_tokens": 22771,
55
"estimator": "ceil(total characters / 4); compactness signal, not provider billing",
66
"files": [
77
"product-engineering-loop/references/workflow.md",
@@ -12,7 +12,7 @@
1212
},
1313
"files": {
1414
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
15-
"CONTRIBUTING.md": "5972bb34fea69f854044bd649c6e5a61e0668461946cfcb67e0ce10fa87cc914",
15+
"CONTRIBUTING.md": "cfeb3c333bd52524eccb76ccdda2b39c7b4944b15b92c0bd422f5cc9a94d740d",
1616
"README.md": "3ce3e95e511089b44e946a44b8d5f4f81d019ece5336db65b2cab1f9dc4d4dad",
1717
"assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63",
1818
"assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5",
@@ -111,8 +111,8 @@
111111
"boatstack/init_transaction.go": "112456c4e1c4db54c4137bcf4f7a9a9e63399a6f5971e9b3dc952d0c4b2aa4b6",
112112
"boatstack/insight.go": "7ec492b65043f7b10dae9dd55104d22bc56775759b8c3fb288545dede01b9f89",
113113
"boatstack/insight_conformance_test.go": "6093b5496338e52995c231847d3938c4fa97b0f6b4ccbc49161b625d69e9c90e",
114-
"boatstack/installation_repair.go": "c9698e134094873642a60300342b3d1c2be30dc86b15dc73aa859605e83d2019",
115-
"boatstack/installation_repair_test.go": "ae5a5ea1110836bd78cf20ade863a4d32cfd63d282559f92786f57b31869bd14",
114+
"boatstack/installation_repair.go": "f6889e3d21102d2aedfe1af900945d0a3ea2a772b3fddad787cef99803ef91aa",
115+
"boatstack/installation_repair_test.go": "3194d3e8a737d5df09fcba2baef5f7b665db678eae181abbd2c35c462365477e",
116116
"boatstack/integrations.go": "75b39ce2e662fccd66bf4b9bff0e097a4db558f23b3aa1d9bc83a5fc6373444c",
117117
"boatstack/internal/deliverycontrol/advise.go": "4f3a53a785a34f6c34858236a57d4114091141a463b51e5aaefae3336557ae5a",
118118
"boatstack/internal/deliverycontrol/advise_test.go": "6e0d3bda302cb5d26dd42a0253281ac6d1bb6749496b1a7df9c2aa9f53ea13a1",
@@ -157,9 +157,9 @@
157157
"boatstack/next_response.go": "11decf2e3b236cbaa183980946ec17ffbbbb1af9c08bd11a466a8487bf229d5f",
158158
"boatstack/next_response_conformance_test.go": "be4f3bc7507abfb0ae9f86310eb29e34b166dcc40b6fa103e05babb81f2bd928",
159159
"boatstack/next_test.go": "6b5ec46ecf1a197d7644846cecbb6d99873a06b7c4e5562772b5016fa0a4cb11",
160-
"boatstack/operation.go": "86c6d1a82cfd0b3f2aadef044a8047fb49c612131ef8ba5f44e92c9f18e45162",
161-
"boatstack/operation_test.go": "59d3dc37319aa4d334c0cacbe886e2f757842e6a28dee8781448e528fecbde11",
162-
"boatstack/paths.go": "e1f3b42cda713997b6b6dcd585e0b8d06d0b1c82e61a9afaa33a14c223818c40",
160+
"boatstack/operation.go": "a51ee8d20cfb98d0e595e1420ed3277275229cf3ab0f2afa4c690c648a464853",
161+
"boatstack/operation_test.go": "7c1c7f1b69359457a133b0b0d82310b9c5190eaff6f19738c3ca839807ca0a79",
162+
"boatstack/paths.go": "84b56d031389dd872dfb88d08cfbdf178befe6df3ef2ac6d75369da8d827e8c6",
163163
"boatstack/plan.go": "a130c9e587f97a280b6cfa531bcaab46fc6e58772f27921031be03eb98f04439",
164164
"boatstack/plan_test.go": "1b01e7d9d7794eb11c998e19a2f3532d3b8509d984eca934cf5f1662a0a7e573",
165165
"boatstack/plan_validation.go": "48c4fd061257f821ed54a0892b291cc04450e93e3db48f358f8c6599b21f9e41",
@@ -186,11 +186,11 @@
186186
"boatstack/reexec_windows.go": "f5335c8c28cb4e89048b058b1c4d12f78644f99acb4f6167ff60e622dfb9e742",
187187
"boatstack/references/artifacts.md": "7f410a5c99273bdf45d70e86c226ac4d13d78a7bee563ec77a07793b8d46fb33",
188188
"boatstack/references/config-schema.md": "1da1326cef1573d60e5df6fb3e0ae656f324fb4f7ad1df6f57c5c51664616b46",
189-
"boatstack/references/failure-moves.md": "b65ef72035afa6ad0dce589a0b38f84bc40cde3864c9ecf973f08fc687f001c3",
189+
"boatstack/references/failure-moves.md": "e990abca11035f412039d1366bff1bf052d1ff229cbd5d0be97f1e608684fe6d",
190190
"boatstack/references/host-hook-contracts.md": "2a89d44d0e418a53f2e3b6300fed957cdf878f45ea97ce24b55b66065f0eaa1d",
191191
"boatstack/references/irreversible-operation-boundary.md": "e0076f0fea3bf729b2e9bdf353eaeaaf7cdafabfaf26b8d9b27287e5414c2441",
192192
"boatstack/references/portability.md": "fb683095991bb0cb06ec56fb8884c49038b283172a7d2f8b203483b7cacb4bae",
193-
"boatstack/references/workflow.md": "5e43b1f82d9be55166f70606b34f20d1fdc61ebe1183338a405503d0975ec59a",
193+
"boatstack/references/workflow.md": "b64c7d3168fa99055fdae798357110f11b496ce8a6f2a6237f17523ce6eeac48",
194194
"boatstack/release.go": "82dcb4ca59e8c79a68d5333d650f90e64abd448d04e0c6f504fdf07f42b5ed76",
195195
"boatstack/release_test.go": "5cf2d76fe9b836a91ca68eba53d5585e2c4be5b9421aaf939ea0723063a24690",
196196
"boatstack/repair_budget_conformance_test.go": "05793600dac06bbf39075b15bc1262a9bd1e916738d801dcd6ff515771262b4c",
@@ -200,7 +200,7 @@
200200
"boatstack/run.go": "3127e8c054b80e41413011e423ba9463fe06a7e6c9e1e71750ab98587871d89d",
201201
"boatstack/run_test.go": "5b291510fa90cefdc26eb89e18a3443385456a6ebc73408325ac1945b7c084d6",
202202
"boatstack/runtime.go": "cd77c34910de45ca382275df1a36ce39fe88497169618096009962d2bcb8f591",
203-
"boatstack/runtime_cache.go": "3bff97f1a7db1389cecd00c5ac3584e5202d2caf9c6ea42a0ba427ab538aeac7",
203+
"boatstack/runtime_cache.go": "6f6b023170cce982bf155e7c2fc7752cca2f7acff771967b4a523c1c13ea876f",
204204
"boatstack/runtime_cache_test.go": "b981467ddc9f0f562da6bff5de7a80a9fe5a433a0317541d1e48df268546ac85",
205205
"boatstack/runtime_provenance_test.go": "1d52f1e6b0691cf4667729cc9b9f3c55c128f0aa3321f3a2843a9aa6fd0e73dc",
206206
"boatstack/safety.go": "f0af393b87bbc82d4b9fea3ebf40eee8674bed16b17aa0e9ea18e35be5810625",
@@ -216,7 +216,7 @@
216216
"boatstack/testdata/reviewer-pr-body.md": "4c64e3788e5d61a377aeb0f797f7fc8d2316ab6e49572d15636eea7ba9e34ac4",
217217
"boatstack/testdata/safety/safe_apply.py.txt": "c9ec7fb932cf21b6aa8df597c4d4c54d6ec65e796240e49118d699f583383975",
218218
"boatstack/testdata/safety/unsafe_apply.py.txt": "42db1751865cc15c4dd69a03146b5deca8f21f916d258e433b27bbef5f884ab1",
219-
"boatstack/update.go": "042c7e8141423e2cdb71c92f93cf73cc81d916116d76ab9928c56cf18bc6827a",
219+
"boatstack/update.go": "cebd3a3300055fe1fa7646e8c8182662a066771afd44e35cb5d64530f52e26e8",
220220
"boatstack/update_publication.go": "5c1ac8445345c6546d165b9321a736453b313ced96a0059465b8ad637498bac6",
221221
"boatstack/update_publication_test.go": "c5f32578db53be65e35452d5e8b4520884354e4e80a370dcec19860ea644d091",
222222
"boatstack/update_test.go": "bf5f19f8499db6dd7356917d867b113b790548ab89bfdea59e2adf4999a82a6a",
@@ -235,14 +235,14 @@
235235
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
236236
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
237237
"docs/configuration.md": "ed62dfd360377f7a15826ef5a2d3c9798d25c272168c65480961f9e0d011ccf1",
238-
"docs/evidence-engineered-coding.md": "d5d34eee3d7af847ab4eabf3a7be21709b7b0619b44bde505f744aa6a337129c",
238+
"docs/evidence-engineered-coding.md": "cae640d8fcb89edab69ecbee5148b336b4cb77c752cab79e6ce63925669876c3",
239239
"docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3",
240240
"docs/getting-started.md": "51c2823f21e35140d31e6d5083dc4b89fddd24721ac6acc474154a4da53ee9f8",
241-
"docs/public-claims.json": "78230b15a4c7716e2ebbd3b864795cbbad8c25e48e2f7c1e50e9ad97dae23947",
241+
"docs/public-claims.json": "6f93df0664dbaf0fee6aed003559ac6b0e606753cafd76298d68623a6c5c9f2c",
242242
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
243243
"docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6",
244244
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
245-
"docs/troubleshooting.md": "321d79a990de3eac0931efbf5446a3fbdb5f8fce06319bf2adf1e7aabf763519",
245+
"docs/troubleshooting.md": "10841d10b51d551cc49d6f9d1c7ee997a8aaf091a10e8f74a570b5cf445d6ada",
246246
"docs/validation-and-evidence.md": "e7d91ad49c6adb44784ebe7d94feceb6abd445857f9a0716f0758bf6b55296c5",
247247
"docs/why-these-steps.md": "cbe0d769db11ef15bb1dff888009378d6783776ad020e6f5139847a1dd62fa09",
248248
"install.ps1": "f48d0f26a26e806b780d10fa916c261ff9f84ab39758cf9e229f647836845e86",
@@ -252,7 +252,7 @@
252252
"labs/diagram-json/compiled/evidence.md": "1ba1c989ade070a8ef9a508fbd788d100d7292f2dbacbb2bce895468019f619d",
253253
"labs/diagram-json/compiled/tasks.json": "88f60851abf79d851e9fccc754ff3040034ae595306bc87d64784c19eb403e71",
254254
"labs/diagram-json/compiled/test-matrix.json": "424657ff505768e50fa113801fd8363364a18269d5297480907a993d44063a39",
255-
"labs/diagram-json/plan.lock.json": "7d5c5cb554ec51e856437b4f359e45cd8b584262a7aca50f2b0d5b855124549f",
255+
"labs/diagram-json/plan.lock.json": "4e3130b0203c93077a3dfdaaf167dcd6adf50155d4477a41046f018c14461ddf",
256256
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
257257
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
258258
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
@@ -399,13 +399,14 @@
399399
"release-notes/2026-07-30-plan-approved-scenarios-escalate-to-require.md": "21c3f2be51b834fc2eb7662236db2549e80b1a1a0665a0721e773b7736e2c079",
400400
"release-notes/2026-07-30-visual-attach-retry-prescription.md": "38b610685a7a62c0341ba21b273b42c24d82ca803de6d8f694754156eaa606a9",
401401
"release-notes/2026-07-30-visual-evidence-survives-preview-commit.md": "71d19e9fabb40e8cb1e939f26bf288911d227979926f43f337046c6cf6b66551",
402+
"release-notes/2026-08-01-detached-update-recovery.md": "3c8e5b36ba5a561f0dccd67863f93981be7820c434ac37ed2aeeb708aa1d0fff",
402403
"release-notes/2026-08-01-independent-insight-captures.md": "5d18f26fced9589fc7cfd70eac1bfe28038f99b921a5ea7766c310d6595349c3",
403404
"release-notes/2026-08-01-readable-projection-history.md": "93c4db5c3935b4c4c251f2f8a5dd2925a7241f34f6dd7bb9602badca6ad367bc"
404405
},
405406
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
406407
"schema_version": 1,
407408
"source": {
408-
"commit": "6149e9333caa3d936f69e03015252b712cf453ee",
409+
"commit": "d950cb53d33f4f2571a62ce05e10a85a3da7756b",
409410
"path": "labs/12-product-engineering-loop",
410411
"repository": "operatorstack/intelligence-flow"
411412
}

boatstack/installation_repair.go

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,22 +62,57 @@ type InstallationRepairResult struct {
6262
}
6363

6464
func installedVersion(repo string) (string, error) {
65-
for _, candidate := range []string{
66-
filepath.Join(repo, ".product-loop", "bin", "install.lock.json"),
67-
filepath.Join(repo, ".product-loop", "generated.lock.json"),
65+
var failures []string
66+
type candidate struct {
67+
label string
68+
value []byte
69+
err error
70+
}
71+
localPath := filepath.Join(repo, ".product-loop", "bin", "install.lock.json")
72+
localValue, localErr := os.ReadFile(localPath)
73+
committedValue, committedErr := exec.Command("git", "-C", repo, "show", "HEAD:.product-loop/generated.lock.json").Output()
74+
for _, candidate := range []candidate{
75+
{label: "install.lock.json", value: localValue, err: localErr},
76+
{label: "committed generated.lock.json", value: committedValue, err: committedErr},
6877
} {
69-
value, err := os.ReadFile(candidate)
70-
if err != nil {
78+
if candidate.err != nil {
7179
continue
7280
}
7381
var identity struct {
7482
BoatstackVersion string `json:"boatstack_version"`
83+
SourceCommit string `json:"source_commit"`
84+
Runtime struct {
85+
SourceCommit string `json:"source_commit"`
86+
} `json:"runtime"`
87+
}
88+
if err := json.Unmarshal(candidate.value, &identity); err != nil {
89+
failures = append(failures, candidate.label+" is malformed")
90+
continue
7591
}
76-
if json.Unmarshal(value, &identity) == nil && strings.TrimSpace(identity.BoatstackVersion) != "" {
77-
return normalizedVersion(identity.BoatstackVersion)
92+
if strings.TrimSpace(identity.BoatstackVersion) == "" {
93+
failures = append(failures, candidate.label+" has no version")
94+
continue
95+
}
96+
sourceCommit := strings.TrimSpace(identity.SourceCommit)
97+
if sourceCommit == "" {
98+
sourceCommit = strings.TrimSpace(identity.Runtime.SourceCommit)
7899
}
100+
if sourceCommit == "" || strings.EqualFold(sourceCommit, "unknown") {
101+
failures = append(failures, candidate.label+" has invalid source commit")
102+
continue
103+
}
104+
version, err := normalizedVersion(identity.BoatstackVersion)
105+
if err != nil {
106+
failures = append(failures, candidate.label+" has invalid version")
107+
continue
108+
}
109+
return version, nil
110+
}
111+
detail := strings.Join(failures, "; ")
112+
if detail != "" {
113+
detail = ": " + detail
79114
}
80-
return "", fmt.Errorf("installed Boatstack version cannot be established from owned provenance")
115+
return "", fmt.Errorf("installed Boatstack version cannot be established from owned provenance%s", detail)
81116
}
82117

83118
func updateDirection(installed, target string) (string, error) {

boatstack/installation_repair_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,84 @@ func TestRepairPreservesIntegrationFallbackWhenInstallLockIsMissing(t *testing.T
297297
}
298298
}
299299

300+
func TestRepairFallsBackToCommittedPinWhenLocalProvenanceIsInvalid(t *testing.T) {
301+
now := time.Date(2026, 7, 23, 12, 0, 0, 0, time.UTC)
302+
withUpdateGlobals(t, "v0.4.0", now, func() (ReleaseInfo, error) { return ReleaseInfo{}, nil })
303+
for name, value := range map[string][]byte{
304+
"development identity": []byte(`{"boatstack_version":"dev","source_commit":"unknown"}`),
305+
"unknown source": []byte(`{"boatstack_version":"v0.4.0","source_commit":"unknown"}`),
306+
"malformed identity": []byte("{\n"),
307+
} {
308+
t.Run(name, func(t *testing.T) {
309+
repo, _ := updateInstalledRepo(t)
310+
lockPath := filepath.Join(repo, ".product-loop", "bin", "install.lock.json")
311+
if err := os.WriteFile(lockPath, value, 0o644); err != nil {
312+
t.Fatal(err)
313+
}
314+
// Recovery authority comes from the committed pin, not a potentially
315+
// drifted generated file in the worktree.
316+
if err := os.WriteFile(filepath.Join(repo, ".product-loop", "generated.lock.json"), []byte("{\n"), 0o644); err != nil {
317+
t.Fatal(err)
318+
}
319+
installed, err := installedVersion(repo)
320+
if err != nil || installed != "v0.4.0" {
321+
t.Fatalf("committed pin did not recover version identity: version=%q err=%v", installed, err)
322+
}
323+
config, _, err := LoadConfig(filepath.Join(repo, ".boatstack-project.json"))
324+
if err != nil {
325+
t.Fatal(err)
326+
}
327+
result, err := ClassifyInstallationRepair(repo, config.Adapters, false)
328+
if err != nil {
329+
t.Fatal(err)
330+
}
331+
if result.VerificationStatus != "REPAIR_AVAILABLE" || result.InstalledVersion != "v0.4.0" {
332+
t.Fatalf("invalid local provenance was not safely repairable: %#v", result)
333+
}
334+
})
335+
}
336+
}
337+
338+
func TestDetachedUpdateRepairsInvalidLocalProvenanceEndToEnd(t *testing.T) {
339+
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
340+
withUpdateGlobals(t, "v0.4.0", now, func() (ReleaseInfo, error) { return ReleaseInfo{}, nil })
341+
repo, _ := updateInstalledRepo(t)
342+
t.Setenv(stateRootEnv, t.TempDir())
343+
invalidateWorkspaceCache()
344+
if _, err := AttachDetached(AttachOptions{Repo: repo}); err != nil {
345+
t.Fatal(err)
346+
}
347+
runGit(t, repo, "switch", "-c", "chore/update-boatstack-v0.5.0")
348+
if err := os.WriteFile(filepath.Join(repo, ".product-loop", "bin", "install.lock.json"), []byte(`{"boatstack_version":"dev","source_commit":"unknown"}`), 0o644); err != nil {
349+
t.Fatal(err)
350+
}
351+
Version = "v0.5.0"
352+
SourceCommit = "update-test-0.5.0"
353+
if err := RunUpdate(InitOptions{Repo: repo, Repair: true, Yes: true, Input: strings.NewReader(""), Output: &bytes.Buffer{}}); err != nil {
354+
t.Fatal(err)
355+
}
356+
receipts, err := operationReceipts(repo)
357+
if err != nil {
358+
t.Fatal(err)
359+
}
360+
found := false
361+
for _, receipt := range receipts {
362+
if receipt.Kind == "install-update" && receipt.State == OperationSucceeded {
363+
found = true
364+
}
365+
}
366+
if !found {
367+
t.Fatalf("detached repair did not persist a successful update operation: %#v", receipts)
368+
}
369+
directory, err := WorkspaceFor(repo).OperationDir()
370+
if err != nil {
371+
t.Fatal(err)
372+
}
373+
if strings.HasPrefix(directory, repo+string(filepath.Separator)) {
374+
t.Fatalf("detached update receipt entered the repository: %s", directory)
375+
}
376+
}
377+
300378
func TestRepairReconstructsCorruptGeneratedProvenance(t *testing.T) {
301379
now := time.Date(2026, 7, 23, 12, 0, 0, 0, time.UTC)
302380
withUpdateGlobals(t, "v0.4.0", now, func() (ReleaseInfo, error) { return ReleaseInfo{}, nil })

boatstack/operation.go

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -133,24 +133,22 @@ func pruneLegacyOperationLedger(repo string) {
133133
_ = os.RemoveAll(legacy)
134134
}
135135

136-
func operationPath(repo, operationID string) (string, error) {
136+
func operationOwnedPath(repo, operationID string) (controllerPath, error) {
137137
id, err := safeCacheSegment(operationID, "operation id")
138138
if err != nil {
139-
return "", err
140-
}
141-
directory, err := operationDirectory(repo)
142-
if err != nil {
143-
return "", err
139+
return controllerPath{}, err
144140
}
145-
path := filepath.Join(directory, id+".json")
146-
gitDir, err := worktreeGitDir(repo)
141+
ctx := WorkspaceFor(repo)
142+
directory, err := ctx.OperationDir()
147143
if err != nil {
148-
return "", err
144+
return controllerPath{}, err
149145
}
150-
if err := rejectSymlinkComponents(gitDir, path); err != nil {
151-
return "", err
152-
}
153-
return path, nil
146+
return ctx.worktreeOwnedPath(filepath.Join(directory, id+".json"))
147+
}
148+
149+
func operationPath(repo, operationID string) (string, error) {
150+
owned, err := operationOwnedPath(repo, operationID)
151+
return owned.path, err
154152
}
155153

156154
func operationID(kind, target, fingerprint string) string {
@@ -223,18 +221,15 @@ func saveOperation(repo string, receipt OperationReceipt) error {
223221
}
224222

225223
func withOperationLock(repo, id string, apply func() error) error {
226-
path, err := operationPath(repo, id)
224+
path, err := operationOwnedPath(repo, id)
227225
if err != nil {
228226
return err
229227
}
230-
lock := strings.TrimSuffix(path, ".json") + ".lock"
231-
gitDir, err := worktreeGitDir(repo)
228+
lockPath, err := path.Sibling(strings.TrimSuffix(filepath.Base(path.path), ".json") + ".lock")
232229
if err != nil {
233230
return err
234231
}
235-
if err := rejectSymlinkComponents(gitDir, lock); err != nil {
236-
return err
237-
}
232+
lock := lockPath.path
238233
if err := os.MkdirAll(filepath.Dir(lock), 0o700); err != nil {
239234
return err
240235
}

0 commit comments

Comments
 (0)