@cassiomc1/forgeloop 1.14.0 → 2.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/CONTRIBUTING.md +11 -7
  2. package/DOCS_INDEX.md +2 -1
  3. package/ENG/c-development-eng.md +2 -2
  4. package/ENG/cpp-development-eng.md +2 -2
  5. package/ENG/dotnet-aspnetcore-development-eng.md +7 -1
  6. package/ENG/flutter-development-eng.md +4 -2
  7. package/ENG/go-development-eng.md +2 -2
  8. package/ENG/java-development-eng.md +3 -3
  9. package/ENG/nodejs-backend-development-eng.md +8 -4
  10. package/ENG/php-development-eng.md +3 -3
  11. package/ENG/python-development-eng.md +205 -0
  12. package/ENG/rust-development-eng.md +42 -1
  13. package/ENG/sql-development-eng.md +4 -4
  14. package/ENG/swift-development-eng.md +5 -4
  15. package/ENG/test-code-eng.md +1 -1
  16. package/ENG/typescript-development-eng.md +4 -4
  17. package/GUIDE_ROUTER.md +6 -0
  18. package/LICENSE +133 -21
  19. package/LOOP_SYSTEM_DESIGN.md +10 -10
  20. package/README.md +28 -39
  21. package/THIRD_PARTY_NOTICES.md +3 -4
  22. package/completions/_forgeloop +11 -3
  23. package/completions/forgeloop.bash +19 -3
  24. package/completions/forgeloop.fish +61 -1
  25. package/docs/AGENT_PROTOCOL_SUMMARY.md +50 -40
  26. package/docs/ARTIFACT_REFERENCE.md +50 -37
  27. package/docs/CLI_REFERENCE.md +147 -3
  28. package/docs/DOCUMENTATION_GUIDE.md +6 -4
  29. package/docs/GETTING_STARTED.md +3 -3
  30. package/docs/PACKAGE_CONTENTS.md +14 -13
  31. package/docs/RELEASE_CHECKLIST.md +81 -10
  32. package/docs/SELF_HOSTED_TESTS.md +121 -0
  33. package/docs/documentation-manifest.json +25 -12
  34. package/package.json +15 -10
  35. package/schemas/task-bundle.schema.json +7 -2
  36. package/scripts/generate-agent-protocol-summary.mjs +2 -2
  37. package/scripts/run-mcp-clean-tests.mjs +30 -0
  38. package/src/adapters/agent-browser/provider.js +14 -11
  39. package/src/adapters/emulated-services/provider.js +11 -4
  40. package/src/adapters/temporary-paths.js +18 -0
  41. package/src/cli.js +146 -210
  42. package/src/commands/action-show.js +5 -2
  43. package/src/commands/advance.js +5 -1
  44. package/src/commands/baseline.js +13 -11
  45. package/src/commands/checkpoint-revalidate.js +24 -8
  46. package/src/commands/complete.js +19 -3
  47. package/src/commands/contract-create.js +3 -1
  48. package/src/commands/doctor.js +36 -13
  49. package/src/commands/gate-record.js +20 -31
  50. package/src/commands/gate-revalidate.js +11 -13
  51. package/src/commands/handoff-list.js +6 -1
  52. package/src/commands/handoff-show.js +6 -1
  53. package/src/commands/migrate-protocol.js +2 -2
  54. package/src/commands/model-route.js +9 -1
  55. package/src/commands/next.js +7 -1
  56. package/src/commands/policy-diff.js +25 -4
  57. package/src/commands/progress.js +17 -11
  58. package/src/commands/record-diagnosis.js +3 -0
  59. package/src/commands/semantic-plan.js +26 -2
  60. package/src/commands/status.js +6 -1
  61. package/src/commands/storage-backup.js +15 -0
  62. package/src/commands/storage-migrate.js +5 -0
  63. package/src/commands/storage-migration-resume.js +5 -0
  64. package/src/commands/storage-migration-status.js +44 -0
  65. package/src/commands/storage-restore-resume.js +7 -0
  66. package/src/commands/storage-restore.js +8 -0
  67. package/src/commands/task-abandon.js +1 -1
  68. package/src/commands/task-list.js +8 -43
  69. package/src/commands/task-migrate.js +3 -3
  70. package/src/commands/task-recover.js +10 -2
  71. package/src/commands/task-scope.js +18 -2
  72. package/src/commands/task-show.js +16 -2
  73. package/src/commands/validate-protocol.js +29 -14
  74. package/src/commands/validate-receipt.js +8 -1
  75. package/src/config/guides.json +4 -0
  76. package/src/core/action-ledger-projection.js +10 -3
  77. package/src/core/actions.js +38 -75
  78. package/src/core/activation.js +11 -3
  79. package/src/core/approvals.js +17 -51
  80. package/src/core/artifact-registry.js +59 -2
  81. package/src/core/artifacts.js +81 -27
  82. package/src/core/attachment-paths.js +45 -0
  83. package/src/core/attestation-coverage.js +6 -1
  84. package/src/core/attestation-verifier.js +21 -2
  85. package/src/core/attestation.js +17 -6
  86. package/src/core/audit.js +6 -1
  87. package/src/core/bundles.js +160 -66
  88. package/src/core/checkpoint-revalidation.js +12 -10
  89. package/src/core/cli-command-definitions.js +109 -7
  90. package/src/core/code-manifest.js +9 -3
  91. package/src/core/command-executors.js +182 -126
  92. package/src/core/command-input.js +21 -1
  93. package/src/core/completion-artifacts.js +71 -29
  94. package/src/core/completion-ownership.js +19 -8
  95. package/src/core/completion-recovery-rebind.js +11 -1
  96. package/src/core/completion.js +60 -9
  97. package/src/core/conformance.js +2 -1
  98. package/src/core/continuity-lint.js +13 -2
  99. package/src/core/continuity-reconciliation.js +16 -3
  100. package/src/core/continuity.js +16 -8
  101. package/src/core/contract-bootstrap-recovery.js +42 -41
  102. package/src/core/contract-revision.js +8 -9
  103. package/src/core/decision/artifact.js +2 -1
  104. package/src/core/decision/events.js +9 -6
  105. package/src/core/decision/resolver.js +19 -11
  106. package/src/core/decision/service.js +63 -38
  107. package/src/core/decision/task-bindings.js +6 -1
  108. package/src/core/diagnosis-model.js +11 -0
  109. package/src/core/diagnosis.js +10 -1
  110. package/src/core/diagnostic-projection.js +29 -12
  111. package/src/core/diagnostic-record.js +28 -3
  112. package/src/core/efficiency.js +6 -1
  113. package/src/core/events.js +316 -97
  114. package/src/core/execution-prerequisites.js +22 -13
  115. package/src/core/execution.js +26 -40
  116. package/src/core/filesystem.js +11 -0
  117. package/src/core/gate-artifact.js +105 -19
  118. package/src/core/gate-provenance.js +6 -5
  119. package/src/core/handoff.js +26 -14
  120. package/src/core/history.js +39 -35
  121. package/src/core/hypothesis-projection.js +32 -0
  122. package/src/core/information-gain-projection.js +104 -100
  123. package/src/core/inspect.js +12 -3
  124. package/src/core/integration-invocation-policy.js +10 -0
  125. package/src/core/integration-resources.js +41 -34
  126. package/src/core/ledger-event-collection.js +189 -0
  127. package/src/core/ledger-relations.js +23 -0
  128. package/src/core/native-storage.js +12 -0
  129. package/src/core/next-action-continuity.js +31 -9
  130. package/src/core/next-action-phases.js +1 -1
  131. package/src/core/next-action-review-phase.js +2 -2
  132. package/src/core/next-action-verification-phase.js +2 -1
  133. package/src/core/next-action.js +21 -2
  134. package/src/core/phase.js +138 -93
  135. package/src/core/policy-engine.js +12 -13
  136. package/src/core/preflight-consistency.js +72 -59
  137. package/src/core/preflight-loaders.js +19 -12
  138. package/src/core/preflight-model.js +6 -1
  139. package/src/core/preflight.js +29 -12
  140. package/src/core/progress.js +21 -10
  141. package/src/core/protocol-info.js +3 -0
  142. package/src/core/protocol-migration.js +7 -6
  143. package/src/core/reconcile-closure.js +162 -24
  144. package/src/core/recovery-history.js +66 -47
  145. package/src/core/reflection.js +72 -77
  146. package/src/core/responsibility.js +25 -13
  147. package/src/core/route-artifact.js +13 -3
  148. package/src/core/runtime-context.js +5 -0
  149. package/src/core/schema-validation.js +8 -3
  150. package/src/core/settlement.js +12 -1
  151. package/src/core/signing/bundle-file.js +53 -0
  152. package/src/core/signing/bundle-reference.js +15 -0
  153. package/src/core/signing/sigstore.js +60 -33
  154. package/src/core/signing/statement-file.js +27 -0
  155. package/src/core/storage-runtime-registry.js +41 -0
  156. package/src/core/structural-quality/artifacts.js +20 -14
  157. package/src/core/structural-quality/service.js +81 -56
  158. package/src/core/target-layout.js +1 -1
  159. package/src/core/task-claim-state.js +52 -18
  160. package/src/core/task-command.js +21 -1
  161. package/src/core/task-conflict-inspection.js +112 -107
  162. package/src/core/task-descriptor.js +29 -22
  163. package/src/core/task-discovery.js +288 -90
  164. package/src/core/task-lock.js +66 -306
  165. package/src/core/task-migration-validation.js +5 -5
  166. package/src/core/task-migration.js +17 -287
  167. package/src/core/task-snapshot.js +51 -8
  168. package/src/core/task-storage-migration.js +18 -0
  169. package/src/core/templates.js +20 -8
  170. package/src/core/test-intelligence/prune.js +5 -1
  171. package/src/core/trace.js +154 -99
  172. package/src/core/trajectory-evaluation.js +15 -3
  173. package/src/core/trajectory-metrics.js +47 -32
  174. package/src/core/transaction.js +16 -288
  175. package/src/core/verification-scope.js +20 -12
  176. package/src/core/work-state.js +100 -59
  177. package/src/repository-index/binary-manager.js +7 -5
  178. package/src/storage/artifact-bytes.js +22 -0
  179. package/src/storage/attachment-binding.js +11 -0
  180. package/src/storage/attachment-files.js +82 -0
  181. package/src/storage/attachment-references.js +36 -0
  182. package/src/storage/backup.js +53 -0
  183. package/src/storage/capabilities.js +31 -0
  184. package/src/storage/connection-owner.js +119 -0
  185. package/src/storage/connection.js +376 -0
  186. package/src/storage/existing-project-scope.js +45 -0
  187. package/src/storage/export-destination.js +44 -0
  188. package/src/storage/exporter.js +284 -0
  189. package/src/storage/file-durability.js +46 -0
  190. package/src/storage/importer.js +461 -0
  191. package/src/storage/index.js +74 -0
  192. package/src/storage/leases.js +63 -0
  193. package/src/storage/ledger-event-snapshot.js +150 -0
  194. package/src/storage/ledger-relations.js +220 -0
  195. package/src/storage/legacy-maintenance-boundary.js +18 -0
  196. package/src/storage/legacy-signature.js +115 -0
  197. package/src/storage/maintenance-handoff.js +129 -0
  198. package/src/storage/maintenance-owner.js +42 -0
  199. package/src/storage/maintenance.js +310 -0
  200. package/src/storage/metadata-json.js +35 -0
  201. package/src/storage/migration-activate.js +59 -0
  202. package/src/storage/migration-archive.js +57 -0
  203. package/src/storage/migration-attachment-root.js +61 -0
  204. package/src/storage/migration-candidate.js +398 -0
  205. package/src/storage/migration-publication.js +143 -0
  206. package/src/storage/migration-rollback-admission.js +54 -0
  207. package/src/storage/migration-rollback-preparation.js +132 -0
  208. package/src/storage/migration-rollback-publication.js +183 -0
  209. package/src/storage/migration-rollback-release.js +66 -0
  210. package/src/storage/migration-rollback-source-stage.js +103 -0
  211. package/src/storage/migration-rollback.js +43 -0
  212. package/src/storage/migration-source-partition.js +56 -0
  213. package/src/storage/migration-source.js +255 -0
  214. package/src/storage/migration-terminal.js +39 -0
  215. package/src/storage/migration-validation.js +75 -0
  216. package/src/storage/migration.js +43 -0
  217. package/src/storage/operational-attachments.js +49 -0
  218. package/src/storage/operational-context.js +50 -0
  219. package/src/storage/project-backup.js +98 -0
  220. package/src/storage/project-bootstrap.js +24 -0
  221. package/src/storage/project-boundary.js +79 -0
  222. package/src/storage/project-read-snapshot.js +18 -0
  223. package/src/storage/project-replacement.js +137 -0
  224. package/src/storage/project-restore.js +203 -0
  225. package/src/storage/record-definitions.js +45 -0
  226. package/src/storage/repository.js +507 -0
  227. package/src/storage/restore-journal.js +62 -0
  228. package/src/storage/restore-layout.js +30 -0
  229. package/src/storage/restore-preparation.js +84 -0
  230. package/src/storage/restore-replacement-archive.js +162 -0
  231. package/src/storage/restore-replacement-owner.js +31 -0
  232. package/src/storage/restore-replacement-preparation.js +128 -0
  233. package/src/storage/restore-replacement.js +142 -0
  234. package/src/storage/restore-terminal.js +32 -0
  235. package/src/storage/runtime.js +31 -0
  236. package/src/storage/schema.js +214 -0
  237. package/src/storage/singleton-import.js +84 -0
  238. package/src/storage/snapshot-continuity.js +16 -0
  239. package/src/storage/snapshot.js +36 -0
  240. package/src/storage/storage-marker.js +79 -0
  241. package/src/storage/task-guards.js +107 -0
  242. package/src/storage/topology.js +42 -0
  243. package/src/storage/transaction.js +179 -0
  244. package/src/storage/unit-of-work.js +663 -0
  245. package/src/storage/windows-process-incarnation.js +206 -0
  246. package/.forgeloop/forgeloop.gitignore +0 -4
  247. package/LICENSE-DOCS.md +0 -13
  248. package/src/core/transaction-maintenance.js +0 -70
@@ -1,7 +1,9 @@
1
+ import { ledgerEventsOfTypes } from "./ledger-event-collection.js";
2
+ import { ledgerRelationMap, ledgerRelationSet, ledgerGroupedSet } from "./ledger-relations.js";
1
3
  import { buildTaskTrace } from "./trace.js";
2
4
  import { readEvents } from "./events.js";
3
5
  import { findTaskById } from "./task-discovery.js";
4
- import { projectHypothesisStates } from "./hypothesis-projection.js";
6
+ import { projectHypothesisStates, summarizeOpenHypotheses } from "./hypothesis-projection.js";
5
7
  import {
6
8
  buildInformationGainProjection,
7
9
  evaluateStructuredDiagnosticStall,
@@ -64,11 +66,9 @@ export function evaluateInterventionEffectiveness(trace, surfacesByCycle) {
64
66
  }
65
67
 
66
68
  function failedRequirementSurfacesFromEvents(events = [], state = null) {
67
- const byCycle = new Map();
69
+ const byCycle = ledgerGroupedSet();
68
70
  const record = (cycle, requirement) => {
69
- if (!requirement) return;
70
- if (!byCycle.has(cycle)) byCycle.set(cycle, new Set());
71
- byCycle.get(cycle).add(requirement);
71
+ if (requirement) byCycle.add(cycle, requirement);
72
72
  };
73
73
  for (const event of events) {
74
74
  if (event.event !== "VERIFICATION_RECORDED") continue;
@@ -95,88 +95,83 @@ function signatureSetsByCycle(trace) {
95
95
  return byCycle;
96
96
  }
97
97
 
98
- export function deriveDiagnosticContext(events = [], state = null) {
99
- const cycle = state?.verificationCycle ?? null;
100
- const taskEvents = events.filter((event) => !state?.taskId || event.taskId === state.taskId);
101
-
102
- // Canonical failure-signature hashes for the active verification cycle.
103
- const activeFailureSignatures = [...new Set(
104
- taskEvents
105
- .filter((event) => event.event === "VERIFICATION_RECORDED"
106
- && ["failed", "blocked"].includes(event.details?.status)
107
- && (cycle === null || event.details?.verificationCycle === cycle))
108
- .map((event) => {
109
- const d = event.details;
110
- return computeFailureSignature({
111
- requirement: d.requirement ?? d.id ?? d.checkId,
112
- status: d.status,
113
- exitCode: Number.isInteger(d.exitCode) ? d.exitCode : null,
114
- failureToken: typeof d.failureToken === "string" && d.failureToken
115
- ? d.failureToken
116
- : (typeof d.details?.failureToken === "string" ? d.details.failureToken : null),
117
- });
118
- }),
119
- )].sort();
120
-
121
- const activeFailedRequirements = [...new Set(
122
- [
123
- ...taskEvents
124
- .filter((event) => event.event === "VERIFICATION_RECORDED"
125
- && ["failed", "blocked"].includes(event.details?.status)
126
- && (cycle === null || event.details?.verificationCycle === cycle))
127
- .map((event) => event.details?.requirement ?? event.details?.id ?? event.details?.checkId),
128
- ...((state?.checks ?? []))
129
- .filter((check) => ["failed", "blocked"].includes(check.status)
130
- && (cycle === null || check.details?.verificationCycle === cycle))
131
- .map((check) => check.requirement ?? check.id ?? check.checkId),
132
- ].filter(Boolean),
133
- )].sort();
98
+ function* contextEvents(events, types, taskId) {
99
+ for (const event of ledgerEventsOfTypes(events, types)) if (!taskId || event.taskId === taskId) yield event;
100
+ }
134
101
 
135
- const projection = projectHypothesisStates(taskEvents);
136
- const openHypotheses = [...projection.openHypotheses].sort();
102
+ function contextFailureSignature(details) {
103
+ return computeFailureSignature({ requirement: details.requirement ?? details.id ?? details.checkId,
104
+ status: details.status, exitCode: Number.isInteger(details.exitCode) ? details.exitCode : null,
105
+ failureToken: typeof details.failureToken === "string" && details.failureToken
106
+ ? details.failureToken : (typeof details.details?.failureToken === "string" ? details.details.failureToken : null) });
107
+ }
137
108
 
138
- const interventions = taskEvents.filter((event) => event.event === "INTERVENTION_RECORDED");
139
- const latestIntervention = interventions.at(-1)?.details?.intervention?.id ?? null;
109
+ function activeFailure(details, cycle, observedCycle) {
110
+ return ["failed", "blocked"].includes(details?.status) && (cycle === null || observedCycle === cycle);
111
+ }
140
112
 
141
- // doNotRepeat requires semantic repetition AND at least two completed
142
- // post-intervention verification cycles AND unchanged failure surface.
143
- const surfacesByCycle = failedRequirementSurfacesFromEvents(taskEvents, state);
144
- const completedCycles = [...new Set(
145
- taskEvents.filter((event) => event.event === "VERIFICATION_STARTED")
146
- .map((event) => event.details?.verificationCycle)
147
- .filter(Number.isInteger),
148
- )].sort((a, b) => a - b);
113
+ function activeContextFailures(events, state, cycle) {
114
+ const signatures = new Set();
115
+ const requirements = new Set();
116
+ for (const event of contextEvents(events, ["VERIFICATION_RECORDED"], state?.taskId)) {
117
+ const details = event.details;
118
+ if (!activeFailure(details, cycle, details?.verificationCycle)) continue;
119
+ signatures.add(contextFailureSignature(details));
120
+ const requirement = details.requirement ?? details.id ?? details.checkId;
121
+ if (requirement) requirements.add(requirement);
122
+ }
123
+ for (const check of state?.checks ?? []) {
124
+ if (!activeFailure(check, cycle, check.details?.verificationCycle)) continue;
125
+ const requirement = check.requirement ?? check.id ?? check.checkId;
126
+ if (requirement) requirements.add(requirement);
127
+ }
128
+ return { activeFailureSignatures: [...signatures].sort(), activeFailedRequirements: [...requirements].sort() };
129
+ }
149
130
 
150
- const fingerprintGroups = new Map();
151
- for (const event of interventions) {
131
+ function contextInterventions(events, taskId) {
132
+ const groups = ledgerRelationMap();
133
+ const lastCycles = ledgerRelationMap();
134
+ const invalidCycles = ledgerRelationSet();
135
+ let latestIntervention = null;
136
+ for (const event of contextEvents(events, ["INTERVENTION_RECORDED"], taskId)) {
137
+ latestIntervention = event.details?.intervention?.id ?? null;
152
138
  const fingerprint = event.details?.interventionSemanticFingerprint;
153
139
  if (!fingerprint) continue;
154
- if (!fingerprintGroups.has(fingerprint)) fingerprintGroups.set(fingerprint, []);
155
- fingerprintGroups.get(fingerprint).push(event);
140
+ groups.set(fingerprint, (groups.get(fingerprint) ?? 0) + 1);
141
+ const cycle = Number(event.details?.verificationCycle ?? 1);
142
+ if (!Number.isFinite(cycle)) invalidCycles.add(fingerprint);
143
+ else lastCycles.set(fingerprint, Math.max(lastCycles.get(fingerprint) ?? -Infinity, cycle));
156
144
  }
145
+ return { groups, lastCycles, invalidCycles, latestIntervention };
146
+ }
157
147
 
148
+ export function deriveDiagnosticContext(events = [], state = null) {
149
+ const cycle = state?.verificationCycle ?? null;
150
+ const failures = activeContextFailures(events, state, cycle);
151
+ const openHypotheses = summarizeOpenHypotheses(events, state?.taskId).sort();
152
+ const relevant = { *[Symbol.iterator]() {
153
+ yield* contextEvents(events, ["VERIFICATION_RECORDED"], state?.taskId);
154
+ } };
155
+ const surfacesByCycle = failedRequirementSurfacesFromEvents(relevant, state);
156
+ const cycles = new Set();
157
+ for (const event of contextEvents(events, ["VERIFICATION_STARTED"], state?.taskId)) {
158
+ if (Number.isInteger(event.details?.verificationCycle)) cycles.add(event.details.verificationCycle);
159
+ }
160
+ const completedCycles = [...cycles].sort((a, b) => a - b);
161
+ const { groups, lastCycles, invalidCycles, latestIntervention } = contextInterventions(events, state?.taskId);
158
162
  const doNotRepeat = [];
159
- for (const [fingerprint, group] of fingerprintGroups.entries()) {
160
- if (group.length < 2) continue;
161
- const lastInterventionCycle = Math.max(...group.map((event) => event.details?.verificationCycle ?? 1));
162
- const postCycles = completedCycles.filter((completed) => completed > lastInterventionCycle);
163
- if (postCycles.length < 2) continue;
164
- const firstSurface = [...(surfacesByCycle.get(lastInterventionCycle) ?? [])].sort();
165
- const latestSurface = [...(surfacesByCycle.get(postCycles.at(-1)) ?? [])].sort();
166
- if (JSON.stringify(firstSurface) === JSON.stringify(latestSurface)) {
167
- doNotRepeat.push(fingerprint);
168
- }
163
+ for (const [fingerprint, countOfInterventions] of groups) {
164
+ if (countOfInterventions < 2 || invalidCycles.has(fingerprint)) continue;
165
+ const lastCycle = lastCycles.get(fingerprint);
166
+ let count = 0;
167
+ let latestCycle = null;
168
+ for (const completed of completedCycles) if (completed > lastCycle) { count++; latestCycle = completed; }
169
+ if (count < 2) continue;
170
+ const firstSurface = [...(surfacesByCycle.values(lastCycle))].sort();
171
+ const latestSurface = [...(surfacesByCycle.values(latestCycle))].sort();
172
+ if (JSON.stringify(firstSurface) === JSON.stringify(latestSurface)) doNotRepeat.push(fingerprint);
169
173
  }
170
- doNotRepeat.sort();
171
-
172
- return {
173
- activeFailureSignatures,
174
- activeFailedRequirements,
175
- openHypotheses,
176
- latestIntervention,
177
- nextExperiment: null,
178
- doNotRepeat,
179
- };
174
+ return { ...failures, openHypotheses, latestIntervention, nextExperiment: null, doNotRepeat: doNotRepeat.sort() };
180
175
  }
181
176
 
182
177
  export async function buildTaskReflection({ target, packageRoot, taskId = null } = {}) {
@@ -10,7 +10,9 @@ import { resolveTaskClaimState } from "./task-claim-state.js";
10
10
  import { normalizeWriteClaims } from "./task-scope.js";
11
11
  import { taskResponsibilityPath } from "./task-paths.js";
12
12
  import { ensureWithin, fileExists } from "./filesystem.js";
13
- import { appendProtocolEvent } from "./events.js";
13
+ import { getOperationalStore, operationalArtifactExists } from "../storage/operational-context.js";
14
+ import { withNativeReadScope } from "./native-storage.js";
15
+ import { appendProtocolEvent, withEventLedgerAudit } from "./events.js";
14
16
  import { withTaskTransaction } from "./transaction.js";
15
17
  import { assertTaskMutationAllowed } from "./task-claim-state.js";
16
18
  import { assertWorkspaceBinding } from "./workspace-binding.js";
@@ -118,17 +120,19 @@ export async function validateResponsibilityContract(value, packageRoot = getPac
118
120
  }
119
121
 
120
122
  export async function readResponsibility(target, { taskId, packageRoot = getPackageRoot() } = {}) {
121
- const relativePath = taskResponsibilityPath(taskId);
122
- if (!(await fileExists(ensureWithin(target, relativePath)))) return null;
123
- try {
124
- const artifact = await readJsonArtifact(target, relativePath, "responsibility", packageRoot);
125
- const value = await validateResponsibilityContract(artifact.value, packageRoot);
126
- if (value.taskId !== taskId) throw responsibilityError("E_RESPONSIBILITY_INVALID", "Responsibility taskId does not match its task namespace", [relativePath]);
127
- return { ...artifact, value };
128
- } catch (error) {
129
- if (error.code === "E_RESPONSIBILITY_INVALID") throw error;
130
- throw responsibilityError("E_RESPONSIBILITY_INVALID", `Responsibility contract is invalid: ${error.message}`, [relativePath]);
131
- }
123
+ return withNativeReadScope(target, async () => {
124
+ const relativePath = taskResponsibilityPath(taskId);
125
+ if (!(operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath)))) return null;
126
+ try {
127
+ const artifact = await readJsonArtifact(target, relativePath, "responsibility", packageRoot);
128
+ const value = await validateResponsibilityContract(artifact.value, packageRoot);
129
+ if (value.taskId !== taskId) throw responsibilityError("E_RESPONSIBILITY_INVALID", "Responsibility taskId does not match its task namespace", [relativePath]);
130
+ return { ...artifact, value };
131
+ } catch (error) {
132
+ if (error.code === "E_RESPONSIBILITY_INVALID") throw error;
133
+ throw responsibilityError("E_RESPONSIBILITY_INVALID", `Responsibility contract is invalid: ${error.message}`, [relativePath]);
134
+ }
135
+ });
132
136
  }
133
137
 
134
138
  async function currentResponsibilityInputs(target, { taskId, packageRoot }) {
@@ -173,6 +177,14 @@ export function validateResponsibilityChecks(responsibility, state) {
173
177
  }
174
178
 
175
179
  export async function resolveResponsibilityStatus(target, { taskId, packageRoot = getPackageRoot() } = {}) {
180
+ const read = () => resolveLoadedResponsibilityStatus(target, { taskId, packageRoot });
181
+ return withNativeReadScope(target, () => {
182
+ if (getOperationalStore(target)) return withEventLedgerAudit(target, packageRoot, { taskId }, read);
183
+ return read();
184
+ });
185
+ }
186
+
187
+ async function resolveLoadedResponsibilityStatus(target, { taskId, packageRoot }) {
176
188
  const artifact = await readResponsibility(target, { taskId, packageRoot });
177
189
  if (!artifact) return { status: "NOT_APPLICABLE", taskId, path: taskResponsibilityPath(taskId), responsibility: null, errors: [] };
178
190
  let inputs;
@@ -245,7 +257,7 @@ export async function setResponsibilityContract(target, options = {}) {
245
257
  return withTaskTransaction({ target, taskId, packageRoot, operation: "responsibility-set", recordCommitEvent: true }, async () => {
246
258
  await assertTaskMutationAllowed(target, { taskId, packageRoot });
247
259
  await assertWorkspaceBinding(target, { taskId, packageRoot, operation: "responsibility-set" });
248
- if (await fileExists(ensureWithin(target, relativePath))) {
260
+ if (operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath))) {
249
261
  throw responsibilityError("E_RESPONSIBILITY_INVALID", "Responsibility contract is immutable during a pass", [relativePath]);
250
262
  }
251
263
  const state = await readWorkState(target, { packageRoot, taskId });
@@ -4,17 +4,24 @@ import { readContract } from "./contract.js";
4
4
  import { ensureResumableState, synchronizePersistedRouteState } from "./resumability.js";
5
5
  import { readWorkState } from "./work-state.js";
6
6
  import { taskArtifactPath } from "./task-paths.js";
7
+ import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
7
8
 
8
9
 
9
10
 
10
11
  export async function persistRoute(target, route, packageRoot, options = {}) {
12
+ if (!options.taskId) return persistRouteInTransaction(target, route, packageRoot, options);
13
+ return withTaskTransaction({ target, taskId: options.taskId, packageRoot, operation: "route" }, () => persistRouteInTransaction(target, route, packageRoot, options));
14
+ }
15
+
16
+ async function persistRouteInTransaction(target, route, packageRoot, options) {
11
17
  assertRouteInvariants(route);
12
- const { contractFingerprint, ...writeOptions } = options;
18
+ const { contractFingerprint: suppliedFingerprint, ...writeOptions } = options;
19
+ let contractFingerprint = suppliedFingerprint;
13
20
  let contractArtifact = null;
14
- if (contractFingerprint === undefined) {
21
+ if (contractFingerprint === undefined || !options.taskId) {
15
22
  try {
16
23
  contractArtifact = await readContract(target, packageRoot, options);
17
- contractFingerprint = contractArtifact.fingerprint;
24
+ if (contractFingerprint === undefined) contractFingerprint = contractArtifact.fingerprint;
18
25
  } catch (error) {
19
26
  if (error.code !== "ARTIFACT_MISSING") throw error;
20
27
  }
@@ -24,6 +31,9 @@ export async function persistRoute(target, route, packageRoot, options = {}) {
24
31
  : { ...route, contractFingerprint };
25
32
  assertRouteInvariants(value);
26
33
  const taskId = options.taskId ?? contractArtifact?.value?.taskId ?? null;
34
+ if (taskId && !(await getTaskTransaction(target))) {
35
+ return withTaskTransaction({ target, taskId, packageRoot, operation: "route" }, () => persistRouteInTransaction(target, route, packageRoot, { ...options, taskId }));
36
+ }
27
37
  let existingState = null;
28
38
  if (taskId) {
29
39
  try {
@@ -1,3 +1,4 @@
1
+ import { attachStorageConnectionOwner } from "./storage-runtime-registry.js";
1
2
  import {
2
3
  E_VERIFICATION_ISOLATION_UNAVAILABLE,
3
4
  isVerificationExecutionAdapter,
@@ -220,5 +221,9 @@ export function createForgeLoopContext(options = {}) {
220
221
  if (options?.securityReviewProviders !== undefined) {
221
222
  configureSecurityReviewProviders(context, options.securityReviewProviders);
222
223
  }
224
+ if (options?.persistentStorage !== undefined && typeof options.persistentStorage !== "boolean") {
225
+ throw Object.assign(new Error("persistentStorage must be a boolean"), { code: "E_STORAGE_RUNTIME_CONFIGURATION_INVALID" });
226
+ }
227
+ if (options?.persistentStorage === true) context.close = attachStorageConnectionOwner(context);
223
228
  return Object.freeze(context);
224
229
  }
@@ -123,7 +123,6 @@ function validate(value, schema, location, errors, rootSchema = schema) {
123
123
  return candidateErrors.length === 0;
124
124
  });
125
125
  if (matches.length !== 1) errors.push(`${location}: expected exactly one matching schema`);
126
- return;
127
126
  }
128
127
 
129
128
  if (schema.type && !typeMatches(value, schema.type)) {
@@ -213,6 +212,12 @@ export async function readSchema(name, packageRoot = getPackageRoot()) {
213
212
  return schema;
214
213
  }
215
214
 
215
+ function schemaHealthVersion(schema, name) {
216
+ const versions = schema?.properties?.schemaVersion?.enum ?? null;
217
+ const version = schema?.properties?.schemaVersion?.const ?? (name === "task-bundle" && JSON.stringify(versions) === "[1,2]" ? 2 : null);
218
+ return version;
219
+ }
220
+
216
221
  export async function inspectSchemaHealth(packageRoot = getPackageRoot()) {
217
222
  const schemas = [];
218
223
  for (const name of SHIPPED_SCHEMA_NAMES) {
@@ -223,13 +228,13 @@ export async function inspectSchemaHealth(packageRoot = getPackageRoot()) {
223
228
  assertJsonBytes(bytes, filename);
224
229
  const schema = JSON.parse(bytes.toString("utf8"));
225
230
  assertJsonLimits(schema, filename);
226
- const version = schema?.properties?.schemaVersion?.const ?? null;
231
+ const version = schemaHealthVersion(schema, name);
227
232
  let status = "valid";
228
233
  let error = null;
229
234
  if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
230
235
  status = "invalid";
231
236
  error = "schema root must be an object";
232
- } else if (version !== 1) {
237
+ } else if (version !== 1 && !(name === "task-bundle" && version === 2)) {
233
238
  status = "unsupported-version";
234
239
  error = `schemaVersion ${version ?? "missing"} is not supported`;
235
240
  }
@@ -1,3 +1,4 @@
1
+ import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
1
2
  import { readContract } from "./contract.js";
2
3
  import { appendProtocolEvent, validateEventLedger } from "./events.js";
3
4
  import {
@@ -18,7 +19,17 @@ export {
18
19
  normalizeDecisionText,
19
20
  };
20
21
 
21
- export async function recordDecisionCriterion({
22
+ export async function recordDecisionCriterion(options) {
23
+ normalizeDecisionCriterionInput(options);
24
+ if (await getTaskTransaction(options.target)) return recordCriterion(options);
25
+ const contract = await readContract(options.target, options.packageRoot, { taskId: options.taskId, contractPath: options.contractPath });
26
+ const taskId = options.taskId ?? contract?.value?.taskId;
27
+ if (!taskId) return recordCriterion(options);
28
+ return withTaskTransaction({ target: options.target, packageRoot: options.packageRoot,
29
+ taskId, operation: "record-decision-criterion" }, () => recordCriterion({ ...options, taskId }));
30
+ }
31
+
32
+ async function recordCriterion({
22
33
  target,
23
34
  packageRoot,
24
35
  decision,
@@ -0,0 +1,53 @@
1
+ import { isOperationalArtifactPath, needsExistingProjectScope, withExistingProjectScope } from "../../storage/existing-project-scope.js";
2
+ import { constants } from "node:fs";
3
+ import { mkdtemp, open, rm } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { assertSafePath, ensureWithin } from "../filesystem.js";
7
+ import { getOperationalStore, readOperationalText } from "../../storage/operational-context.js";
8
+ import { signatureReferenceId } from "./bundle-reference.js";
9
+
10
+ /** External signing finishes before immutable bytes enter the prepared commit. */
11
+ export async function withSigningBundleFile(target, destinationPath, statementPath, callback) {
12
+ if (await needsExistingProjectScope(target)) {
13
+ return withExistingProjectScope(target, () => withSigningBundleFile(target, destinationPath, statementPath, callback));
14
+ }
15
+ const store = getOperationalStore(target);
16
+ if (!store) {
17
+ if (isOperationalArtifactPath(destinationPath)) throw Object.assign(new Error("Operational signature bundles require canonical SQLite preparation"), { code: "E_STORAGE_OPERATION_UNSUPPORTED" });
18
+ await assertSafePath(target, destinationPath);
19
+ await callback(ensureWithin(target, destinationPath));
20
+ return { path: destinationPath };
21
+ }
22
+ const transaction = store.transaction;
23
+ if (!transaction || store.db.isTransaction) {
24
+ throw Object.assign(new Error("SQLite signing requires an active preparation outside the native writer"), { code: "E_ATTESTATION_SIGNATURE_INVALID" });
25
+ }
26
+ const source = readOperationalText(target, statementPath);
27
+ if (!source.selected || source.text === null) {
28
+ throw Object.assign(new Error("SQLite signing requires a canonical statement"), { code: "E_ATTESTATION_STATEMENT_INVALID" });
29
+ }
30
+ const directory = await mkdtemp(path.join(os.tmpdir(), "forgeloop-signing-bundle-"));
31
+ try {
32
+ const filename = path.join(directory, "bundle.json");
33
+ await callback(filename);
34
+ if (readOperationalText(target, statementPath).text !== source.text) {
35
+ throw Object.assign(new Error("Canonical statement changed during signing"), { code: "E_ATTESTATION_STATEMENT_INVALID" });
36
+ }
37
+ let handle;
38
+ try {
39
+ await assertSafePath(directory, "bundle.json");
40
+ handle = await open(filename, constants.O_RDONLY | constants.O_NOFOLLOW);
41
+ } catch {
42
+ throw Object.assign(new Error("Signer did not produce a safe signature bundle"), { code: "E_ATTESTATION_SIGNATURE_INVALID" });
43
+ }
44
+ try {
45
+ if (!(await handle.stat()).isFile()) throw new Error("Signer output must be a regular file");
46
+ const attachment = await transaction.stageAttachment({
47
+ referenceId: signatureReferenceId(source.text),
48
+ readable: handle.createReadStream({ autoClose: false }),
49
+ });
50
+ return { path: attachment.path, attachment };
51
+ } finally { await handle.close(); }
52
+ } finally { await rm(directory, { recursive: true, force: true }); }
53
+ }
@@ -0,0 +1,15 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+
3
+ /** Bind signature discovery to the exact statement bytes, not mutable filenames. */
4
+ export function signatureReferencePrefix(statementText) {
5
+ return `sigstore:${createHash("sha256").update(statementText, "utf8").digest("hex")}:`;
6
+ }
7
+
8
+ export function signatureReferenceId(statementText) {
9
+ return `${signatureReferencePrefix(statementText)}${randomUUID()}`;
10
+ }
11
+
12
+ /** Historical logical paths remain addressable without changing ledger bytes. */
13
+ export function legacyAttachmentReferenceId(relativePath) {
14
+ return `legacy-path:${createHash("sha256").update(relativePath, "utf8").digest("hex")}`;
15
+ }
@@ -1,13 +1,30 @@
1
+ import { needsExistingProjectScope, withExistingProjectScope } from "../../storage/existing-project-scope.js";
1
2
  import { execFile } from "node:child_process";
2
3
  import { promisify } from "node:util";
3
4
 
4
5
  import path from "node:path";
5
6
  import { ensureWithin, assertSafePath } from "../filesystem.js";
7
+ import { withSigningStatementFile } from "./statement-file.js";
8
+ import { withSigningBundleFile } from "./bundle-file.js";
9
+ import { getOperationalStore } from "../../storage/operational-context.js";
10
+ import { withOperationalAttachmentFile } from "../../storage/operational-attachments.js";
6
11
 
7
12
  const execFileAsync = promisify(execFile);
8
13
  const DEFAULT_TIMEOUT_MS = 30_000;
9
14
  const MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
10
15
 
16
+ async function withExistingSigningReadScope(target, read) {
17
+ if (await needsExistingProjectScope(target)) return withExistingProjectScope(target, read, { readOnly: true });
18
+ return read();
19
+ }
20
+
21
+ function signingTaskId(target, statementPath) {
22
+ const store = getOperationalStore(target);
23
+ const taskKey = /^\.forgeloop\/task-state\/([a-f0-9]{64})\/attestations\/statement\.json$/u.exec(statementPath)?.[1];
24
+ const taskId = store && taskKey ? store.taskRow(taskKey)?.task_id : null;
25
+ return taskId;
26
+ }
27
+
11
28
  export function createSigstoreSigningProvider({
12
29
  command = "cosign",
13
30
  execFileImpl = execFileAsync,
@@ -21,7 +38,7 @@ export function createSigstoreSigningProvider({
21
38
  killSignal: "SIGTERM",
22
39
  maxBuffer: MAX_OUTPUT_BYTES,
23
40
  });
24
- return {
41
+ const provider = {
25
42
  name: "sigstore",
26
43
  async detect() {
27
44
  try {
@@ -43,13 +60,13 @@ export function createSigstoreSigningProvider({
43
60
  try {
44
61
  await assertSafePath(target, statementPath);
45
62
  await assertSafePath(target, destinationPath);
46
- await run([
63
+ const signed = await withSigningBundleFile(target, destinationPath, statementPath, bundleAbsolute => withSigningStatementFile(target, statementPath, statementAbsolute => run([
47
64
  "attest-blob",
48
- "--statement", ensureWithin(target, statementPath),
49
- "--bundle", ensureWithin(target, destinationPath),
65
+ "--statement", statementAbsolute,
66
+ "--bundle", bundleAbsolute,
50
67
  "--yes",
51
- ], { cwd: path.resolve(target) });
52
- return { status: "VALID", path: destinationPath };
68
+ ], { cwd: path.resolve(target) })));
69
+ return { status: "VALID", ...signed };
53
70
  } catch (error) {
54
71
  const unavailable = error.code === "ENOENT" || error.cause?.code === "ENOENT";
55
72
  const timedOut = error.code === "ETIMEDOUT" || error.killed === true || error.signal === "SIGTERM";
@@ -67,33 +84,42 @@ export function createSigstoreSigningProvider({
67
84
  async verify({ target, statementPath, bundlePath, policy = {} } = {}) {
68
85
  if (!bundlePath) return { status: "UNSIGNED", signer: null };
69
86
  try {
70
- await assertSafePath(target, statementPath);
71
- await assertSafePath(target, bundlePath);
72
- const statementAbsolute = ensureWithin(target, statementPath);
73
- const bundleAbsolute = ensureWithin(target, bundlePath);
74
- const args = [
75
- "verify-blob-attestation",
76
- "--statement", statementAbsolute,
77
- "--bundle", bundleAbsolute,
78
- ];
79
- const identities = Array.isArray(policy.identities)
80
- ? [...new Set(policy.identities.filter((value) => typeof value === "string" && value.length > 0))]
81
- : [];
82
- if (policy.identity) args.push("--certificate-identity", policy.identity);
83
- else if (identities.length === 1) args.push("--certificate-identity", identities[0]);
84
- else if (identities.length > 1) {
85
- const exactAlternatives = identities
86
- .map((value) => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"))
87
- .join("|");
88
- args.push("--certificate-identity-regexp", `^(?:${exactAlternatives})$`);
89
- }
90
- if (policy.issuer) args.push("--certificate-oidc-issuer", policy.issuer);
91
- if (policy.trustedRoot) {
92
- await assertSafePath(target, policy.trustedRoot);
93
- args.push("--trusted-root", ensureWithin(target, policy.trustedRoot));
94
- }
95
- await run(args, { cwd: path.resolve(target) });
96
- return { status: "VALID", signer: policy.identity ?? (identities.length === 1 ? identities[0] : null) };
87
+ return await withExistingSigningReadScope(target, async () => {
88
+ await assertSafePath(target, statementPath);
89
+ await assertSafePath(target, bundlePath);
90
+ const taskId = signingTaskId(target, statementPath);
91
+ const statementAbsolute = ensureWithin(target, statementPath);
92
+ const bundleAbsolute = ensureWithin(target, bundlePath);
93
+ const args = [
94
+ "verify-blob-attestation",
95
+ "--statement", statementAbsolute,
96
+ "--bundle", bundleAbsolute,
97
+ ];
98
+ const identities = Array.isArray(policy.identities)
99
+ ? [...new Set(policy.identities.filter((value) => typeof value === "string" && value.length > 0))]
100
+ : [];
101
+ if (policy.identity) args.push("--certificate-identity", policy.identity);
102
+ else if (identities.length === 1) args.push("--certificate-identity", identities[0]);
103
+ else if (identities.length > 1) {
104
+ const exactAlternatives = identities
105
+ .map((value) => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"))
106
+ .join("|");
107
+ args.push("--certificate-identity-regexp", `^(?:${exactAlternatives})$`);
108
+ }
109
+ if (policy.issuer) args.push("--certificate-oidc-issuer", policy.issuer);
110
+ if (policy.trustedRoot) {
111
+ await assertSafePath(target, policy.trustedRoot);
112
+ args.push("--trusted-root", ensureWithin(target, policy.trustedRoot));
113
+ }
114
+ await withSigningStatementFile(target, statementPath, statementFilename => {
115
+ args[args.indexOf("--statement") + 1] = statementFilename;
116
+ return withOperationalAttachmentFile(target, bundlePath, taskId, bundleFilename => {
117
+ args[args.indexOf("--bundle") + 1] = bundleFilename;
118
+ return run(args, { cwd: path.resolve(target) });
119
+ });
120
+ });
121
+ return { status: "VALID", signer: policy.identity ?? (identities.length === 1 ? identities[0] : null) };
122
+ });
97
123
  } catch (error) {
98
124
  if (error.code === "E_ATTESTATION_SIGNER_UNAVAILABLE") throw error;
99
125
  const unavailable = error.code === "ENOENT" || error.cause?.code === "ENOENT";
@@ -126,6 +152,7 @@ export function createSigstoreSigningProvider({
126
152
  }
127
153
  },
128
154
  };
155
+ return provider;
129
156
  }
130
157
 
131
158
  /**
@@ -0,0 +1,27 @@
1
+ import { needsExistingProjectScope, withExistingProjectScope } from "../../storage/existing-project-scope.js";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { assertSafePath, ensureWithin, writeFileAtomic } from "../filesystem.js";
6
+ import { getOperationalStore, readOperationalText } from "../../storage/operational-context.js";
7
+
8
+ /** Present selected canonical bytes to external signers without a legacy mirror. */
9
+ export async function withSigningStatementFile(target, statementPath, callback) {
10
+ if (await needsExistingProjectScope(target)) {
11
+ return withExistingProjectScope(target, () => withSigningStatementFile(target, statementPath, callback), { readOnly: true });
12
+ }
13
+ await assertSafePath(target, statementPath);
14
+ const source = readOperationalText(target, statementPath);
15
+ if (!source.selected) return callback(ensureWithin(target, statementPath));
16
+ const invalid = message => Object.assign(new Error(message), { code: "E_ATTESTATION_STATEMENT_INVALID" });
17
+ if (source.text === null) throw invalid("Selected attestation statement is missing");
18
+ if (getOperationalStore(target).db.isTransaction) throw invalid("External signing cannot run inside a SQLite write transaction");
19
+ const directory = await mkdtemp(path.join(os.tmpdir(), "forgeloop-signing-statement-"));
20
+ try {
21
+ const filename = path.join(directory, "statement.json");
22
+ await writeFileAtomic(filename, source.text);
23
+ const result = await callback(filename);
24
+ if (readOperationalText(target, statementPath).text !== source.text) throw invalid("Attestation statement changed during external signing");
25
+ return result;
26
+ } finally { await rm(directory, { recursive: true, force: true }); }
27
+ }
@@ -0,0 +1,41 @@
1
+ const owners = new WeakMap();
2
+ function closed(message) { return Object.assign(new Error(message), { code: "E_STORAGE_RUNTIME_CLOSED" }); }
3
+
4
+ /** Register lazy ownership without importing a SQLite implementation. */
5
+ export function attachStorageConnectionOwner(context) {
6
+ let owner;
7
+ let loading;
8
+ let closing = false;
9
+ let closePromise;
10
+ const pending = new Set();
11
+ const getOwner = () => loading ??= import("../storage/connection-owner.js").then(module => {
12
+ owner = module.createStorageConnectionOwner();
13
+ return owner;
14
+ });
15
+ const proxy = {
16
+ run(target, callback, options) {
17
+ if (closing) return Promise.reject(closed("Persistent storage runtime is closing or closed"));
18
+ const request = getOwner().then(value => value.run(target, callback, options));
19
+ pending.add(request);
20
+ const finished = () => { pending.delete(request); };
21
+ request.then(finished, finished);
22
+ return request;
23
+ },
24
+ close() {
25
+ if (owner?.isActive()) return Promise.reject(closed("Close the runtime outside an active command"));
26
+ if (closePromise) return closePromise;
27
+ closing = true;
28
+ closePromise = (async () => {
29
+ await Promise.all([...pending].map(request => request.catch(() => undefined)));
30
+ if (loading) await (await loading).close();
31
+ })();
32
+ return closePromise;
33
+ },
34
+ };
35
+ owners.set(context, proxy);
36
+ return proxy.close;
37
+ }
38
+
39
+ export function getStorageConnectionOwner(context) {
40
+ return context && typeof context === "object" ? owners.get(context) ?? null : null;
41
+ }