You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by "Colafornia (via GitHub)" <gi...@apache.org> on 2026/08/20 07:33:14 UTC

[PR] refactor(storage): centralize root writer lifecycle [maka]

Colafornia opened a new pull request, #3295:
URL: https://github.com/apache/maka/pull/3295

   ## Summary
   
   Add a root-scoped storage composition that owns the Runtime Host writer lifecycle.
   
   The composition now:
   
   - validates the root lease before opening writers
   - reuses one composition and pending open operation per root lease
   - opens existing domain writers in one defined order
   - closes opened writers in reverse order after partial initialization failures
   - makes `close()` idempotent
   - removes writer lifecycle management from Runtime Host domain coordinators
   
   Domain writer interfaces, authorization checks, and persistence behavior remain unchanged. This PR does not introduce a repository framework or merge domain interfaces.
   
   The Runtime Host still bootstraps runtime policy immediately after opening its stores and before opening the remaining writers.
   
   ## Verification
   
   - `npm --workspace @maka/storage run typecheck` — passed
   - `git diff --check` — passed
   - `npm --workspace @maka/storage test` — blocked because the sandbox denied access to `/Users/colafornia/Library/Caches/Maka/runtime-hosts`
   - Runtime Host typecheck — blocked because this worktree resolved `@maka/storage` through an adjacent checkout with stale build output
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org


Re: [PR] refactor(storage): centralize root writer lifecycle [maka]

Posted by "Astro-Han (via GitHub)" <gi...@apache.org>.
Astro-Han commented on code in PR #3295:
URL: https://github.com/apache/maka/pull/3295#discussion_r3819881712


##########
packages/storage/src/storage-writer-composition.ts:
##########
@@ -0,0 +1,182 @@
+import { openInteractiveArtifactStoreForWrite } from './artifact-stores.js';
+import { openInteractiveDailyReviewAuthorityForWrite } from './daily-review-authority.js';
+import { openInteractiveDeepResearchStoreForWrite } from './deep-research-authority.js';
+import { openInteractiveExecutionStoresForWrite } from './execution-stores.js';
+import { openInteractiveGoalAuthorityForWrite } from './goal-authority.js';
+import { openInteractiveLongTermMemoryStoreForWrite } from './long-term-memory-store.js';
+import { openInteractiveMemoryBundleStoreForWrite } from './memory-bundle-store.js';
+import { openInteractivePlanStoreForWrite } from './plan-authority.js';
+import { openInteractiveProjectCatalogForWrite } from './project-catalog-authority.js';
+import { assertStorageRootLease, type StorageRootLease } from './root-authority.js';
+import { openInteractiveRuntimePolicyStoresForWrite } from './runtime-policy-stores.js';
+import { openInteractiveScheduledTaskStoreForWrite } from './scheduled-task-store.js';
+import { openInteractiveShellRunStoreForWrite } from './shell-run-authority.js';
+import { openInteractiveTaskLedgerStoreForWrite } from './task-ledger-authority.js';
+import { openInteractiveUsageStoresForWrite } from './usage-stores.js';
+
+export interface OpenStorageWriterCompositionOptions {
+  /** Runs only when this call starts a new composition; cache and single-flight reuse skip it. */
+  afterRuntimePolicyOpened?: (
+    stores: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>,
+  ) => void | Promise<void>;
+}
+
+export interface StorageWriterComposition {
+  readonly execution: Awaited<ReturnType<typeof openInteractiveExecutionStoresForWrite>>;
+  readonly projectCatalog: Awaited<ReturnType<typeof openInteractiveProjectCatalogForWrite>>;
+  readonly runtimePolicy: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>;
+  readonly scheduledTasks: Awaited<ReturnType<typeof openInteractiveScheduledTaskStoreForWrite>>;
+  readonly plan: Awaited<ReturnType<typeof openInteractivePlanStoreForWrite>>;
+  readonly deepResearch: Awaited<ReturnType<typeof openInteractiveDeepResearchStoreForWrite>>;
+  readonly dailyReview: Awaited<ReturnType<typeof openInteractiveDailyReviewAuthorityForWrite>>;
+  readonly goal: Awaited<ReturnType<typeof openInteractiveGoalAuthorityForWrite>>;
+  readonly memoryBundle: Awaited<ReturnType<typeof openInteractiveMemoryBundleStoreForWrite>>;
+  readonly longTermMemory: Awaited<ReturnType<typeof openInteractiveLongTermMemoryStoreForWrite>>;
+  readonly taskLedger: Awaited<ReturnType<typeof openInteractiveTaskLedgerStoreForWrite>>;
+  readonly artifacts: Awaited<ReturnType<typeof openInteractiveArtifactStoreForWrite>>;
+  readonly usage: Awaited<ReturnType<typeof openInteractiveUsageStoresForWrite>>;
+  readonly shellRuns: Awaited<ReturnType<typeof openInteractiveShellRunStoreForWrite>>;
+  close(): Promise<void>;
+}
+
+const compositions = new WeakMap<object, StorageWriterComposition>();
+const openings = new WeakMap<object, Promise<StorageWriterComposition>>();
+
+export async function openStorageWriterComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions = {},
+): Promise<StorageWriterComposition> {
+  await assertStorageRootLease(lease, 'interactive', 'write');
+  const existing = compositions.get(lease);

Review Comment:
   [P2] Evict the composition from the cache when `close()` starts, not when it finishes. `close()` runs `closeInReverse(closes).finally(() => compositions.delete(lease))`, so the entry stays reachable for the entire teardown — an `openStorageWriterComposition` call that arrives mid-close takes this early return and receives a composition whose SQLite handles are already being closed, with no signal that anything is wrong. The new holder then calls `close()` and gets the memoized `closeTask`, so it silently no-ops while believing it released its writers. Reasoned from the code path at this head, not reproduced. Delete the map entry synchronously at the top of `close()` — before awaiting anything — so an overlapping open builds a fresh composition instead of adopting a dying one. Regression test: start `close()`, do not await it, call `openStorageWriterComposition` on the same lease, assert the returned composition is not the closing one and that its writers are usable.



##########
packages/storage/src/storage-writer-composition.ts:
##########
@@ -0,0 +1,182 @@
+import { openInteractiveArtifactStoreForWrite } from './artifact-stores.js';
+import { openInteractiveDailyReviewAuthorityForWrite } from './daily-review-authority.js';
+import { openInteractiveDeepResearchStoreForWrite } from './deep-research-authority.js';
+import { openInteractiveExecutionStoresForWrite } from './execution-stores.js';
+import { openInteractiveGoalAuthorityForWrite } from './goal-authority.js';
+import { openInteractiveLongTermMemoryStoreForWrite } from './long-term-memory-store.js';
+import { openInteractiveMemoryBundleStoreForWrite } from './memory-bundle-store.js';
+import { openInteractivePlanStoreForWrite } from './plan-authority.js';
+import { openInteractiveProjectCatalogForWrite } from './project-catalog-authority.js';
+import { assertStorageRootLease, type StorageRootLease } from './root-authority.js';
+import { openInteractiveRuntimePolicyStoresForWrite } from './runtime-policy-stores.js';
+import { openInteractiveScheduledTaskStoreForWrite } from './scheduled-task-store.js';
+import { openInteractiveShellRunStoreForWrite } from './shell-run-authority.js';
+import { openInteractiveTaskLedgerStoreForWrite } from './task-ledger-authority.js';
+import { openInteractiveUsageStoresForWrite } from './usage-stores.js';
+
+export interface OpenStorageWriterCompositionOptions {
+  /** Runs only when this call starts a new composition; cache and single-flight reuse skip it. */
+  afterRuntimePolicyOpened?: (
+    stores: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>,
+  ) => void | Promise<void>;
+}
+
+export interface StorageWriterComposition {
+  readonly execution: Awaited<ReturnType<typeof openInteractiveExecutionStoresForWrite>>;
+  readonly projectCatalog: Awaited<ReturnType<typeof openInteractiveProjectCatalogForWrite>>;
+  readonly runtimePolicy: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>;
+  readonly scheduledTasks: Awaited<ReturnType<typeof openInteractiveScheduledTaskStoreForWrite>>;
+  readonly plan: Awaited<ReturnType<typeof openInteractivePlanStoreForWrite>>;
+  readonly deepResearch: Awaited<ReturnType<typeof openInteractiveDeepResearchStoreForWrite>>;
+  readonly dailyReview: Awaited<ReturnType<typeof openInteractiveDailyReviewAuthorityForWrite>>;
+  readonly goal: Awaited<ReturnType<typeof openInteractiveGoalAuthorityForWrite>>;
+  readonly memoryBundle: Awaited<ReturnType<typeof openInteractiveMemoryBundleStoreForWrite>>;
+  readonly longTermMemory: Awaited<ReturnType<typeof openInteractiveLongTermMemoryStoreForWrite>>;
+  readonly taskLedger: Awaited<ReturnType<typeof openInteractiveTaskLedgerStoreForWrite>>;
+  readonly artifacts: Awaited<ReturnType<typeof openInteractiveArtifactStoreForWrite>>;
+  readonly usage: Awaited<ReturnType<typeof openInteractiveUsageStoresForWrite>>;
+  readonly shellRuns: Awaited<ReturnType<typeof openInteractiveShellRunStoreForWrite>>;
+  close(): Promise<void>;
+}
+
+const compositions = new WeakMap<object, StorageWriterComposition>();
+const openings = new WeakMap<object, Promise<StorageWriterComposition>>();
+
+export async function openStorageWriterComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions = {},
+): Promise<StorageWriterComposition> {
+  await assertStorageRootLease(lease, 'interactive', 'write');
+  const existing = compositions.get(lease);
+  if (existing) return existing;
+  const opening = openings.get(lease);
+  if (opening) return opening;
+  const pending = createComposition(lease, options);
+  openings.set(lease, pending);
+  try {
+    return await pending;
+  } finally {
+    if (openings.get(lease) === pending) openings.delete(lease);
+  }
+}
+
+async function createComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions,
+): Promise<StorageWriterComposition> {
+  const closes: Array<() => void | Promise<void>> = [];
+  let composition!: StorageWriterComposition;
+  let closeTask: Promise<void> | undefined;
+  const close = () =>

Review Comment:
   [P2] Decide whether a shared composition may be closed by any holder, and make the code say so. This `close` tears down all fourteen writers unconditionally, but the composition it belongs to is handed to every caller holding the same lease — so the first caller to finish revokes writers the others are still using, and their own `close()` resolves against the same memoized `closeTask` without doing or reporting anything. Thirteen of those writers were per-caller before this PR, so this is a new lifetime coupling rather than an existing one being centralized. Confirmed by reading code at this head; production currently reaches it through a single root owner, so the window requires one teardown to overlap the next open — which `createExecutionRuntimeHostCompositionSource` can produce, since it constructs a composition per context rather than once per process. Either reference-count the holders and only tear down on the last release, or remove `close` from the shared object and l
 et the root owner own the teardown. Regression test: open twice on one lease, close the first, assert the second holder's writers still work or that its `close()` reports the composition was already closed — the current test closes the same handle twice and cannot distinguish the two designs.



##########
packages/runtime-host/src/server/execution-composition.ts:
##########
@@ -1398,24 +1344,20 @@ export async function createExecutionRuntimeHostComposition(
           state: () => requireMemory(memory).recover(),
         },
         drain: [() => memoryExtraction?.beginDrain(), () => memory?.beginDrain()],
-        close: [
-          () => memoryExtraction?.close(),
-          () => memory?.close(),
-          () => longTermMemoryStore?.close(),
-        ],
+        close: [() => memoryExtraction?.close(), () => memory?.close()],
         releaseConnection: [
           (connectionId) => requireMemory(memory).releaseConnection(connectionId),
         ],
       }),
       createRuntimeHostDomainModule({
         id: 'plan',
         handlers: [plans.handlers],
-        close: [() => openedPlanStore.close()],
+        close: [],

Review Comment:
   [P3] Drop the domain modules' now-empty `close: []` arrays rather than leaving them as placeholders. Five modules keep `close: []` purely because their store teardown moved into `storage.close()`, which reads as "this module has nothing to close" when the truth is "something else closes it now". The next person adding a store to one of these modules has a ready-made slot inviting them to reintroduce exactly the per-module teardown this PR consolidated. Confirmed by reading code at this head. Omit the key where there is nothing to close. No regression test needed; this is a readability change.



##########
packages/storage/src/storage-writer-composition.ts:
##########
@@ -0,0 +1,182 @@
+import { openInteractiveArtifactStoreForWrite } from './artifact-stores.js';
+import { openInteractiveDailyReviewAuthorityForWrite } from './daily-review-authority.js';
+import { openInteractiveDeepResearchStoreForWrite } from './deep-research-authority.js';
+import { openInteractiveExecutionStoresForWrite } from './execution-stores.js';
+import { openInteractiveGoalAuthorityForWrite } from './goal-authority.js';
+import { openInteractiveLongTermMemoryStoreForWrite } from './long-term-memory-store.js';
+import { openInteractiveMemoryBundleStoreForWrite } from './memory-bundle-store.js';
+import { openInteractivePlanStoreForWrite } from './plan-authority.js';
+import { openInteractiveProjectCatalogForWrite } from './project-catalog-authority.js';
+import { assertStorageRootLease, type StorageRootLease } from './root-authority.js';
+import { openInteractiveRuntimePolicyStoresForWrite } from './runtime-policy-stores.js';
+import { openInteractiveScheduledTaskStoreForWrite } from './scheduled-task-store.js';
+import { openInteractiveShellRunStoreForWrite } from './shell-run-authority.js';
+import { openInteractiveTaskLedgerStoreForWrite } from './task-ledger-authority.js';
+import { openInteractiveUsageStoresForWrite } from './usage-stores.js';
+
+export interface OpenStorageWriterCompositionOptions {
+  /** Runs only when this call starts a new composition; cache and single-flight reuse skip it. */
+  afterRuntimePolicyOpened?: (
+    stores: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>,
+  ) => void | Promise<void>;
+}
+
+export interface StorageWriterComposition {
+  readonly execution: Awaited<ReturnType<typeof openInteractiveExecutionStoresForWrite>>;
+  readonly projectCatalog: Awaited<ReturnType<typeof openInteractiveProjectCatalogForWrite>>;
+  readonly runtimePolicy: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>;
+  readonly scheduledTasks: Awaited<ReturnType<typeof openInteractiveScheduledTaskStoreForWrite>>;
+  readonly plan: Awaited<ReturnType<typeof openInteractivePlanStoreForWrite>>;
+  readonly deepResearch: Awaited<ReturnType<typeof openInteractiveDeepResearchStoreForWrite>>;
+  readonly dailyReview: Awaited<ReturnType<typeof openInteractiveDailyReviewAuthorityForWrite>>;
+  readonly goal: Awaited<ReturnType<typeof openInteractiveGoalAuthorityForWrite>>;
+  readonly memoryBundle: Awaited<ReturnType<typeof openInteractiveMemoryBundleStoreForWrite>>;
+  readonly longTermMemory: Awaited<ReturnType<typeof openInteractiveLongTermMemoryStoreForWrite>>;
+  readonly taskLedger: Awaited<ReturnType<typeof openInteractiveTaskLedgerStoreForWrite>>;
+  readonly artifacts: Awaited<ReturnType<typeof openInteractiveArtifactStoreForWrite>>;
+  readonly usage: Awaited<ReturnType<typeof openInteractiveUsageStoresForWrite>>;
+  readonly shellRuns: Awaited<ReturnType<typeof openInteractiveShellRunStoreForWrite>>;
+  close(): Promise<void>;
+}
+
+const compositions = new WeakMap<object, StorageWriterComposition>();
+const openings = new WeakMap<object, Promise<StorageWriterComposition>>();
+
+export async function openStorageWriterComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions = {},
+): Promise<StorageWriterComposition> {
+  await assertStorageRootLease(lease, 'interactive', 'write');
+  const existing = compositions.get(lease);
+  if (existing) return existing;
+  const opening = openings.get(lease);
+  if (opening) return opening;
+  const pending = createComposition(lease, options);
+  openings.set(lease, pending);
+  try {
+    return await pending;
+  } finally {
+    if (openings.get(lease) === pending) openings.delete(lease);
+  }
+}
+
+async function createComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions,
+): Promise<StorageWriterComposition> {
+  const closes: Array<() => void | Promise<void>> = [];
+  let composition!: StorageWriterComposition;
+  let closeTask: Promise<void> | undefined;
+  const close = () =>
+    (closeTask ??= closeInReverse(closes).finally(() => {
+      if (compositions.get(lease) === composition) compositions.delete(lease);
+    }));
+  const failOpen = async (error: unknown): Promise<never> => {
+    try {
+      await close();
+    } catch (closeError) {
+      throw new AggregateError([error, closeError], 'Unable to open storage composition');
+    }
+    throw error;
+  };
+  const openWriter = async <T>(
+    operation: () => Promise<T>,
+    closeWriter?: (writer: T) => void | Promise<void>,
+  ): Promise<T> => {
+    try {
+      const writer = await operation();
+      if (closeWriter) closes.push(() => closeWriter(writer));
+      return writer;
+    } catch (error) {
+      return failOpen(error);
+    }
+  };
+
+  const execution = await openWriter(
+    () => openInteractiveExecutionStoresForWrite(lease),
+    (writer) => writer.sessionStore.close?.(),
+  );
+  try {
+    await execution.sessionStore.ready();
+  } catch (error) {
+    await failOpen(error);
+  }
+  const projectCatalog = await openWriter(
+    () => openInteractiveProjectCatalogForWrite(lease),
+    closeWriter,
+  );
+  const runtimePolicy = await openWriter(() => openInteractiveRuntimePolicyStoresForWrite(lease));
+  try {
+    await options.afterRuntimePolicyOpened?.(runtimePolicy);

Review Comment:
   [P2] Do not let a caller's bootstrap option be decided by which call won the race. `afterRuntimePolicyOpened` runs only for the call that starts a new composition — the comment at line 18 says as much — but `execution-composition.ts` uses this hook to run `ensureBootstrapRuntimePolicy` gated on `options.bootstrapRuntimePolicy !== false`. Two callers on one lease disagreeing about that flag therefore get whichever one opened first: a caller that passed `bootstrapRuntimePolicy: false` can have bootstrap performed anyway, and a caller that wanted it can silently not get it. Documenting an order-dependent side effect does not make it safe; the hook is the one option here whose effect is not idempotent. Confirmed by reading code at this head. Either reject a reuse whose options differ from the composition's, or lift bootstrap out of the open path so it runs per caller. Regression test: open with the hook, then open again with a hook that must not run, assert the mismatch is refused
  rather than ignored.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org


Re: [PR] refactor(storage): centralize root writer lifecycle [maka]

Posted by "Astro-Han (via GitHub)" <gi...@apache.org>.
Astro-Han merged PR #3295:
URL: https://github.com/apache/maka/pull/3295


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org


Re: [PR] refactor(storage): centralize root writer lifecycle [maka]

Posted by "Colafornia (via GitHub)" <gi...@apache.org>.
Colafornia commented on code in PR #3295:
URL: https://github.com/apache/maka/pull/3295#discussion_r3820158196


##########
packages/runtime-host/src/server/execution-composition.ts:
##########
@@ -1398,24 +1344,20 @@ export async function createExecutionRuntimeHostComposition(
           state: () => requireMemory(memory).recover(),
         },
         drain: [() => memoryExtraction?.beginDrain(), () => memory?.beginDrain()],
-        close: [
-          () => memoryExtraction?.close(),
-          () => memory?.close(),
-          () => longTermMemoryStore?.close(),
-        ],
+        close: [() => memoryExtraction?.close(), () => memory?.close()],
         releaseConnection: [
           (connectionId) => requireMemory(memory).releaseConnection(connectionId),
         ],
       }),
       createRuntimeHostDomainModule({
         id: 'plan',
         handlers: [plans.handlers],
-        close: [() => openedPlanStore.close()],
+        close: [],

Review Comment:
   Fixed. Removed every empty `close` property present at this head: plan, project-catalog, and session. There were three current occurrences rather than five.



##########
packages/storage/src/storage-writer-composition.ts:
##########
@@ -0,0 +1,182 @@
+import { openInteractiveArtifactStoreForWrite } from './artifact-stores.js';
+import { openInteractiveDailyReviewAuthorityForWrite } from './daily-review-authority.js';
+import { openInteractiveDeepResearchStoreForWrite } from './deep-research-authority.js';
+import { openInteractiveExecutionStoresForWrite } from './execution-stores.js';
+import { openInteractiveGoalAuthorityForWrite } from './goal-authority.js';
+import { openInteractiveLongTermMemoryStoreForWrite } from './long-term-memory-store.js';
+import { openInteractiveMemoryBundleStoreForWrite } from './memory-bundle-store.js';
+import { openInteractivePlanStoreForWrite } from './plan-authority.js';
+import { openInteractiveProjectCatalogForWrite } from './project-catalog-authority.js';
+import { assertStorageRootLease, type StorageRootLease } from './root-authority.js';
+import { openInteractiveRuntimePolicyStoresForWrite } from './runtime-policy-stores.js';
+import { openInteractiveScheduledTaskStoreForWrite } from './scheduled-task-store.js';
+import { openInteractiveShellRunStoreForWrite } from './shell-run-authority.js';
+import { openInteractiveTaskLedgerStoreForWrite } from './task-ledger-authority.js';
+import { openInteractiveUsageStoresForWrite } from './usage-stores.js';
+
+export interface OpenStorageWriterCompositionOptions {
+  /** Runs only when this call starts a new composition; cache and single-flight reuse skip it. */
+  afterRuntimePolicyOpened?: (
+    stores: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>,
+  ) => void | Promise<void>;
+}
+
+export interface StorageWriterComposition {
+  readonly execution: Awaited<ReturnType<typeof openInteractiveExecutionStoresForWrite>>;
+  readonly projectCatalog: Awaited<ReturnType<typeof openInteractiveProjectCatalogForWrite>>;
+  readonly runtimePolicy: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>;
+  readonly scheduledTasks: Awaited<ReturnType<typeof openInteractiveScheduledTaskStoreForWrite>>;
+  readonly plan: Awaited<ReturnType<typeof openInteractivePlanStoreForWrite>>;
+  readonly deepResearch: Awaited<ReturnType<typeof openInteractiveDeepResearchStoreForWrite>>;
+  readonly dailyReview: Awaited<ReturnType<typeof openInteractiveDailyReviewAuthorityForWrite>>;
+  readonly goal: Awaited<ReturnType<typeof openInteractiveGoalAuthorityForWrite>>;
+  readonly memoryBundle: Awaited<ReturnType<typeof openInteractiveMemoryBundleStoreForWrite>>;
+  readonly longTermMemory: Awaited<ReturnType<typeof openInteractiveLongTermMemoryStoreForWrite>>;
+  readonly taskLedger: Awaited<ReturnType<typeof openInteractiveTaskLedgerStoreForWrite>>;
+  readonly artifacts: Awaited<ReturnType<typeof openInteractiveArtifactStoreForWrite>>;
+  readonly usage: Awaited<ReturnType<typeof openInteractiveUsageStoresForWrite>>;
+  readonly shellRuns: Awaited<ReturnType<typeof openInteractiveShellRunStoreForWrite>>;
+  close(): Promise<void>;
+}
+
+const compositions = new WeakMap<object, StorageWriterComposition>();
+const openings = new WeakMap<object, Promise<StorageWriterComposition>>();
+
+export async function openStorageWriterComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions = {},
+): Promise<StorageWriterComposition> {
+  await assertStorageRootLease(lease, 'interactive', 'write');
+  const existing = compositions.get(lease);
+  if (existing) return existing;
+  const opening = openings.get(lease);
+  if (opening) return opening;
+  const pending = createComposition(lease, options);
+  openings.set(lease, pending);
+  try {
+    return await pending;
+  } finally {
+    if (openings.get(lease) === pending) openings.delete(lease);
+  }
+}
+
+async function createComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions,
+): Promise<StorageWriterComposition> {
+  const closes: Array<() => void | Promise<void>> = [];
+  let composition!: StorageWriterComposition;
+  let closeTask: Promise<void> | undefined;
+  const close = () =>
+    (closeTask ??= closeInReverse(closes).finally(() => {
+      if (compositions.get(lease) === composition) compositions.delete(lease);
+    }));
+  const failOpen = async (error: unknown): Promise<never> => {
+    try {
+      await close();
+    } catch (closeError) {
+      throw new AggregateError([error, closeError], 'Unable to open storage composition');
+    }
+    throw error;
+  };
+  const openWriter = async <T>(
+    operation: () => Promise<T>,
+    closeWriter?: (writer: T) => void | Promise<void>,
+  ): Promise<T> => {
+    try {
+      const writer = await operation();
+      if (closeWriter) closes.push(() => closeWriter(writer));
+      return writer;
+    } catch (error) {
+      return failOpen(error);
+    }
+  };
+
+  const execution = await openWriter(
+    () => openInteractiveExecutionStoresForWrite(lease),
+    (writer) => writer.sessionStore.close?.(),
+  );
+  try {
+    await execution.sessionStore.ready();
+  } catch (error) {
+    await failOpen(error);
+  }
+  const projectCatalog = await openWriter(
+    () => openInteractiveProjectCatalogForWrite(lease),
+    closeWriter,
+  );
+  const runtimePolicy = await openWriter(() => openInteractiveRuntimePolicyStoresForWrite(lease));
+  try {
+    await options.afterRuntimePolicyOpened?.(runtimePolicy);

Review Comment:
   Fixed by refusing composition reuse. Every accepted open owns a new lifecycle and runs its own `afterRuntimePolicyOpened` callback; a second call on the same lease is rejected instead of silently inheriting the first option. Reopen is allowed after close completes.



##########
packages/storage/src/storage-writer-composition.ts:
##########
@@ -0,0 +1,180 @@
+import { openInteractiveArtifactStoreForWrite } from './artifact-stores.js';
+import { openInteractiveDailyReviewAuthorityForWrite } from './daily-review-authority.js';
+import { openInteractiveDeepResearchStoreForWrite } from './deep-research-authority.js';
+import { openInteractiveExecutionStoresForWrite } from './execution-stores.js';
+import { openInteractiveGoalAuthorityForWrite } from './goal-authority.js';
+import { openInteractiveLongTermMemoryStoreForWrite } from './long-term-memory-store.js';
+import { openInteractiveMemoryBundleStoreForWrite } from './memory-bundle-store.js';
+import { openInteractivePlanStoreForWrite } from './plan-authority.js';
+import { openInteractiveProjectCatalogForWrite } from './project-catalog-authority.js';
+import { assertStorageRootLease, type StorageRootLease } from './root-authority.js';
+import { openInteractiveRuntimePolicyStoresForWrite } from './runtime-policy-stores.js';
+import { openInteractiveScheduledTaskStoreForWrite } from './scheduled-task-store.js';
+import { openInteractiveShellRunStoreForWrite } from './shell-run-authority.js';
+import { openInteractiveTaskLedgerStoreForWrite } from './task-ledger-authority.js';
+import { openInteractiveUsageStoresForWrite } from './usage-stores.js';
+
+export interface OpenStorageWriterCompositionOptions {
+  /** Runs after the runtime-policy stores open and before the remaining writers open. */
+  afterRuntimePolicyOpened?: (
+    stores: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>,
+  ) => void | Promise<void>;
+}
+
+export interface StorageWriterComposition {
+  readonly execution: Awaited<ReturnType<typeof openInteractiveExecutionStoresForWrite>>;
+  readonly projectCatalog: Awaited<ReturnType<typeof openInteractiveProjectCatalogForWrite>>;
+  readonly runtimePolicy: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>;
+  readonly scheduledTasks: Awaited<ReturnType<typeof openInteractiveScheduledTaskStoreForWrite>>;
+  readonly plan: Awaited<ReturnType<typeof openInteractivePlanStoreForWrite>>;
+  readonly deepResearch: Awaited<ReturnType<typeof openInteractiveDeepResearchStoreForWrite>>;
+  readonly dailyReview: Awaited<ReturnType<typeof openInteractiveDailyReviewAuthorityForWrite>>;
+  readonly goal: Awaited<ReturnType<typeof openInteractiveGoalAuthorityForWrite>>;
+  readonly memoryBundle: Awaited<ReturnType<typeof openInteractiveMemoryBundleStoreForWrite>>;
+  readonly longTermMemory: Awaited<ReturnType<typeof openInteractiveLongTermMemoryStoreForWrite>>;
+  readonly taskLedger: Awaited<ReturnType<typeof openInteractiveTaskLedgerStoreForWrite>>;
+  readonly artifacts: Awaited<ReturnType<typeof openInteractiveArtifactStoreForWrite>>;
+  readonly usage: Awaited<ReturnType<typeof openInteractiveUsageStoresForWrite>>;
+  readonly shellRuns: Awaited<ReturnType<typeof openInteractiveShellRunStoreForWrite>>;
+  close(): Promise<void>;
+}
+
+const activeCompositions = new WeakSet<object>();
+
+export async function openStorageWriterComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions = {},
+): Promise<StorageWriterComposition> {
+  if (activeCompositions.has(lease)) {
+    throw new Error('Storage writer composition is already active for this lease');
+  }
+  await assertStorageRootLease(lease, 'interactive', 'write');
+  if (activeCompositions.has(lease)) {
+    throw new Error('Storage writer composition is already active for this lease');
+  }
+  activeCompositions.add(lease);
+  try {
+    return await createComposition(lease, options);
+  } catch (error) {
+    activeCompositions.delete(lease);
+    throw error;
+  }
+}
+
+async function createComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions,
+): Promise<StorageWriterComposition> {
+  const closes: Array<() => void | Promise<void>> = [];
+  let closeTask: Promise<void> | undefined;
+  const close = () =>
+    (closeTask ??= closeInReverse(closes).finally(() => {
+      activeCompositions.delete(lease);

Review Comment:
   Chose fail-closed ownership. The active lease marker is now removed only after every writer closes successfully. If any close rejects, later opens on that lease remain rejected because a writer may still hold its handle. A regression test forces a real SQLite close failure and verifies both the aggregate close error and the rejected reopen.



##########
packages/storage/src/storage-writer-composition.ts:
##########
@@ -0,0 +1,182 @@
+import { openInteractiveArtifactStoreForWrite } from './artifact-stores.js';
+import { openInteractiveDailyReviewAuthorityForWrite } from './daily-review-authority.js';
+import { openInteractiveDeepResearchStoreForWrite } from './deep-research-authority.js';
+import { openInteractiveExecutionStoresForWrite } from './execution-stores.js';
+import { openInteractiveGoalAuthorityForWrite } from './goal-authority.js';
+import { openInteractiveLongTermMemoryStoreForWrite } from './long-term-memory-store.js';
+import { openInteractiveMemoryBundleStoreForWrite } from './memory-bundle-store.js';
+import { openInteractivePlanStoreForWrite } from './plan-authority.js';
+import { openInteractiveProjectCatalogForWrite } from './project-catalog-authority.js';
+import { assertStorageRootLease, type StorageRootLease } from './root-authority.js';
+import { openInteractiveRuntimePolicyStoresForWrite } from './runtime-policy-stores.js';
+import { openInteractiveScheduledTaskStoreForWrite } from './scheduled-task-store.js';
+import { openInteractiveShellRunStoreForWrite } from './shell-run-authority.js';
+import { openInteractiveTaskLedgerStoreForWrite } from './task-ledger-authority.js';
+import { openInteractiveUsageStoresForWrite } from './usage-stores.js';
+
+export interface OpenStorageWriterCompositionOptions {
+  /** Runs only when this call starts a new composition; cache and single-flight reuse skip it. */
+  afterRuntimePolicyOpened?: (
+    stores: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>,
+  ) => void | Promise<void>;
+}
+
+export interface StorageWriterComposition {
+  readonly execution: Awaited<ReturnType<typeof openInteractiveExecutionStoresForWrite>>;
+  readonly projectCatalog: Awaited<ReturnType<typeof openInteractiveProjectCatalogForWrite>>;
+  readonly runtimePolicy: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>;
+  readonly scheduledTasks: Awaited<ReturnType<typeof openInteractiveScheduledTaskStoreForWrite>>;
+  readonly plan: Awaited<ReturnType<typeof openInteractivePlanStoreForWrite>>;
+  readonly deepResearch: Awaited<ReturnType<typeof openInteractiveDeepResearchStoreForWrite>>;
+  readonly dailyReview: Awaited<ReturnType<typeof openInteractiveDailyReviewAuthorityForWrite>>;
+  readonly goal: Awaited<ReturnType<typeof openInteractiveGoalAuthorityForWrite>>;
+  readonly memoryBundle: Awaited<ReturnType<typeof openInteractiveMemoryBundleStoreForWrite>>;
+  readonly longTermMemory: Awaited<ReturnType<typeof openInteractiveLongTermMemoryStoreForWrite>>;
+  readonly taskLedger: Awaited<ReturnType<typeof openInteractiveTaskLedgerStoreForWrite>>;
+  readonly artifacts: Awaited<ReturnType<typeof openInteractiveArtifactStoreForWrite>>;
+  readonly usage: Awaited<ReturnType<typeof openInteractiveUsageStoresForWrite>>;
+  readonly shellRuns: Awaited<ReturnType<typeof openInteractiveShellRunStoreForWrite>>;
+  close(): Promise<void>;
+}
+
+const compositions = new WeakMap<object, StorageWriterComposition>();
+const openings = new WeakMap<object, Promise<StorageWriterComposition>>();
+
+export async function openStorageWriterComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions = {},
+): Promise<StorageWriterComposition> {
+  await assertStorageRootLease(lease, 'interactive', 'write');
+  const existing = compositions.get(lease);

Review Comment:
   Fixed by enforcing one active composition per lease. An open that overlaps an active or closing composition now rejects, so it cannot adopt a dying object. I did not evict at close start because the execution and usage writer caches could still return handles being closed. The regression test starts `close()` without awaiting it, verifies rejection, then verifies a usable reopen after close completes.



##########
packages/storage/src/storage-writer-composition.ts:
##########
@@ -0,0 +1,182 @@
+import { openInteractiveArtifactStoreForWrite } from './artifact-stores.js';
+import { openInteractiveDailyReviewAuthorityForWrite } from './daily-review-authority.js';
+import { openInteractiveDeepResearchStoreForWrite } from './deep-research-authority.js';
+import { openInteractiveExecutionStoresForWrite } from './execution-stores.js';
+import { openInteractiveGoalAuthorityForWrite } from './goal-authority.js';
+import { openInteractiveLongTermMemoryStoreForWrite } from './long-term-memory-store.js';
+import { openInteractiveMemoryBundleStoreForWrite } from './memory-bundle-store.js';
+import { openInteractivePlanStoreForWrite } from './plan-authority.js';
+import { openInteractiveProjectCatalogForWrite } from './project-catalog-authority.js';
+import { assertStorageRootLease, type StorageRootLease } from './root-authority.js';
+import { openInteractiveRuntimePolicyStoresForWrite } from './runtime-policy-stores.js';
+import { openInteractiveScheduledTaskStoreForWrite } from './scheduled-task-store.js';
+import { openInteractiveShellRunStoreForWrite } from './shell-run-authority.js';
+import { openInteractiveTaskLedgerStoreForWrite } from './task-ledger-authority.js';
+import { openInteractiveUsageStoresForWrite } from './usage-stores.js';
+
+export interface OpenStorageWriterCompositionOptions {
+  /** Runs only when this call starts a new composition; cache and single-flight reuse skip it. */
+  afterRuntimePolicyOpened?: (
+    stores: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>,
+  ) => void | Promise<void>;
+}
+
+export interface StorageWriterComposition {
+  readonly execution: Awaited<ReturnType<typeof openInteractiveExecutionStoresForWrite>>;
+  readonly projectCatalog: Awaited<ReturnType<typeof openInteractiveProjectCatalogForWrite>>;
+  readonly runtimePolicy: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>;
+  readonly scheduledTasks: Awaited<ReturnType<typeof openInteractiveScheduledTaskStoreForWrite>>;
+  readonly plan: Awaited<ReturnType<typeof openInteractivePlanStoreForWrite>>;
+  readonly deepResearch: Awaited<ReturnType<typeof openInteractiveDeepResearchStoreForWrite>>;
+  readonly dailyReview: Awaited<ReturnType<typeof openInteractiveDailyReviewAuthorityForWrite>>;
+  readonly goal: Awaited<ReturnType<typeof openInteractiveGoalAuthorityForWrite>>;
+  readonly memoryBundle: Awaited<ReturnType<typeof openInteractiveMemoryBundleStoreForWrite>>;
+  readonly longTermMemory: Awaited<ReturnType<typeof openInteractiveLongTermMemoryStoreForWrite>>;
+  readonly taskLedger: Awaited<ReturnType<typeof openInteractiveTaskLedgerStoreForWrite>>;
+  readonly artifacts: Awaited<ReturnType<typeof openInteractiveArtifactStoreForWrite>>;
+  readonly usage: Awaited<ReturnType<typeof openInteractiveUsageStoresForWrite>>;
+  readonly shellRuns: Awaited<ReturnType<typeof openInteractiveShellRunStoreForWrite>>;
+  close(): Promise<void>;
+}
+
+const compositions = new WeakMap<object, StorageWriterComposition>();
+const openings = new WeakMap<object, Promise<StorageWriterComposition>>();
+
+export async function openStorageWriterComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions = {},
+): Promise<StorageWriterComposition> {
+  await assertStorageRootLease(lease, 'interactive', 'write');
+  const existing = compositions.get(lease);
+  if (existing) return existing;
+  const opening = openings.get(lease);
+  if (opening) return opening;
+  const pending = createComposition(lease, options);
+  openings.set(lease, pending);
+  try {
+    return await pending;
+  } finally {
+    if (openings.get(lease) === pending) openings.delete(lease);
+  }
+}
+
+async function createComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions,
+): Promise<StorageWriterComposition> {
+  const closes: Array<() => void | Promise<void>> = [];
+  let composition!: StorageWriterComposition;
+  let closeTask: Promise<void> | undefined;
+  const close = () =>

Review Comment:
   Fixed by making the composition exclusive instead of reference-counted. A second open on the same lease now rejects while the first composition is opening, active, or closing; only the sole holder can call `close()`. This matches the production root-owner lifecycle and avoids per-holder facade objects. The regression test covers active reuse, close overlap, and usable reopen.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org


Re: [PR] refactor(storage): centralize root writer lifecycle [maka]

Posted by "Astro-Han (via GitHub)" <gi...@apache.org>.
Astro-Han commented on code in PR #3295:
URL: https://github.com/apache/maka/pull/3295#discussion_r3820102023


##########
packages/storage/src/storage-writer-composition.ts:
##########
@@ -0,0 +1,180 @@
+import { openInteractiveArtifactStoreForWrite } from './artifact-stores.js';
+import { openInteractiveDailyReviewAuthorityForWrite } from './daily-review-authority.js';
+import { openInteractiveDeepResearchStoreForWrite } from './deep-research-authority.js';
+import { openInteractiveExecutionStoresForWrite } from './execution-stores.js';
+import { openInteractiveGoalAuthorityForWrite } from './goal-authority.js';
+import { openInteractiveLongTermMemoryStoreForWrite } from './long-term-memory-store.js';
+import { openInteractiveMemoryBundleStoreForWrite } from './memory-bundle-store.js';
+import { openInteractivePlanStoreForWrite } from './plan-authority.js';
+import { openInteractiveProjectCatalogForWrite } from './project-catalog-authority.js';
+import { assertStorageRootLease, type StorageRootLease } from './root-authority.js';
+import { openInteractiveRuntimePolicyStoresForWrite } from './runtime-policy-stores.js';
+import { openInteractiveScheduledTaskStoreForWrite } from './scheduled-task-store.js';
+import { openInteractiveShellRunStoreForWrite } from './shell-run-authority.js';
+import { openInteractiveTaskLedgerStoreForWrite } from './task-ledger-authority.js';
+import { openInteractiveUsageStoresForWrite } from './usage-stores.js';
+
+export interface OpenStorageWriterCompositionOptions {
+  /** Runs after the runtime-policy stores open and before the remaining writers open. */
+  afterRuntimePolicyOpened?: (
+    stores: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>,
+  ) => void | Promise<void>;
+}
+
+export interface StorageWriterComposition {
+  readonly execution: Awaited<ReturnType<typeof openInteractiveExecutionStoresForWrite>>;
+  readonly projectCatalog: Awaited<ReturnType<typeof openInteractiveProjectCatalogForWrite>>;
+  readonly runtimePolicy: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>;
+  readonly scheduledTasks: Awaited<ReturnType<typeof openInteractiveScheduledTaskStoreForWrite>>;
+  readonly plan: Awaited<ReturnType<typeof openInteractivePlanStoreForWrite>>;
+  readonly deepResearch: Awaited<ReturnType<typeof openInteractiveDeepResearchStoreForWrite>>;
+  readonly dailyReview: Awaited<ReturnType<typeof openInteractiveDailyReviewAuthorityForWrite>>;
+  readonly goal: Awaited<ReturnType<typeof openInteractiveGoalAuthorityForWrite>>;
+  readonly memoryBundle: Awaited<ReturnType<typeof openInteractiveMemoryBundleStoreForWrite>>;
+  readonly longTermMemory: Awaited<ReturnType<typeof openInteractiveLongTermMemoryStoreForWrite>>;
+  readonly taskLedger: Awaited<ReturnType<typeof openInteractiveTaskLedgerStoreForWrite>>;
+  readonly artifacts: Awaited<ReturnType<typeof openInteractiveArtifactStoreForWrite>>;
+  readonly usage: Awaited<ReturnType<typeof openInteractiveUsageStoresForWrite>>;
+  readonly shellRuns: Awaited<ReturnType<typeof openInteractiveShellRunStoreForWrite>>;
+  close(): Promise<void>;
+}
+
+const activeCompositions = new WeakSet<object>();
+
+export async function openStorageWriterComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions = {},
+): Promise<StorageWriterComposition> {
+  if (activeCompositions.has(lease)) {
+    throw new Error('Storage writer composition is already active for this lease');
+  }
+  await assertStorageRootLease(lease, 'interactive', 'write');
+  if (activeCompositions.has(lease)) {
+    throw new Error('Storage writer composition is already active for this lease');
+  }
+  activeCompositions.add(lease);
+  try {
+    return await createComposition(lease, options);
+  } catch (error) {
+    activeCompositions.delete(lease);
+    throw error;
+  }
+}
+
+async function createComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions,
+): Promise<StorageWriterComposition> {
+  const closes: Array<() => void | Promise<void>> = [];
+  let closeTask: Promise<void> | undefined;
+  const close = () =>
+    (closeTask ??= closeInReverse(closes).finally(() => {
+      activeCompositions.delete(lease);

Review Comment:
   [P3] Decide what a *failed* close should do to the lease. `closeInReverse` collects every writer's error and throws an `AggregateError` at the end, but this `finally` releases the lease regardless — so after a partial close failure a fresh `openStorageWriterComposition` succeeds and opens the same SQLite files while a writer whose `close()` rejected may still hold its handle. The reopen then looks healthy and the double-open surfaces later as lock contention or a corrupt read rather than as the close failure that caused it. Confirmed by reading code at this head; not reproduced, since it needs a writer close to actually reject. Both answers are defensible — release and accept the risk, or keep the lease marked active so the caller must restart rather than silently double-open — but the code currently picks one without saying so. Whichever you choose, say it in a comment here. Regression test: a writer whose `close()` rejects; assert the chosen behaviour for the subsequent op
 en.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org


Re: [PR] refactor(storage): centralize root writer lifecycle [maka]

Posted by "Colafornia (via GitHub)" <gi...@apache.org>.
Colafornia commented on code in PR #3295:
URL: https://github.com/apache/maka/pull/3295#discussion_r3820158191


##########
packages/storage/src/storage-writer-composition.ts:
##########
@@ -0,0 +1,180 @@
+import { openInteractiveArtifactStoreForWrite } from './artifact-stores.js';
+import { openInteractiveDailyReviewAuthorityForWrite } from './daily-review-authority.js';
+import { openInteractiveDeepResearchStoreForWrite } from './deep-research-authority.js';
+import { openInteractiveExecutionStoresForWrite } from './execution-stores.js';
+import { openInteractiveGoalAuthorityForWrite } from './goal-authority.js';
+import { openInteractiveLongTermMemoryStoreForWrite } from './long-term-memory-store.js';
+import { openInteractiveMemoryBundleStoreForWrite } from './memory-bundle-store.js';
+import { openInteractivePlanStoreForWrite } from './plan-authority.js';
+import { openInteractiveProjectCatalogForWrite } from './project-catalog-authority.js';
+import { assertStorageRootLease, type StorageRootLease } from './root-authority.js';
+import { openInteractiveRuntimePolicyStoresForWrite } from './runtime-policy-stores.js';
+import { openInteractiveScheduledTaskStoreForWrite } from './scheduled-task-store.js';
+import { openInteractiveShellRunStoreForWrite } from './shell-run-authority.js';
+import { openInteractiveTaskLedgerStoreForWrite } from './task-ledger-authority.js';
+import { openInteractiveUsageStoresForWrite } from './usage-stores.js';
+
+export interface OpenStorageWriterCompositionOptions {
+  /** Runs after the runtime-policy stores open and before the remaining writers open. */
+  afterRuntimePolicyOpened?: (
+    stores: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>,
+  ) => void | Promise<void>;
+}
+
+export interface StorageWriterComposition {
+  readonly execution: Awaited<ReturnType<typeof openInteractiveExecutionStoresForWrite>>;
+  readonly projectCatalog: Awaited<ReturnType<typeof openInteractiveProjectCatalogForWrite>>;
+  readonly runtimePolicy: Awaited<ReturnType<typeof openInteractiveRuntimePolicyStoresForWrite>>;
+  readonly scheduledTasks: Awaited<ReturnType<typeof openInteractiveScheduledTaskStoreForWrite>>;
+  readonly plan: Awaited<ReturnType<typeof openInteractivePlanStoreForWrite>>;
+  readonly deepResearch: Awaited<ReturnType<typeof openInteractiveDeepResearchStoreForWrite>>;
+  readonly dailyReview: Awaited<ReturnType<typeof openInteractiveDailyReviewAuthorityForWrite>>;
+  readonly goal: Awaited<ReturnType<typeof openInteractiveGoalAuthorityForWrite>>;
+  readonly memoryBundle: Awaited<ReturnType<typeof openInteractiveMemoryBundleStoreForWrite>>;
+  readonly longTermMemory: Awaited<ReturnType<typeof openInteractiveLongTermMemoryStoreForWrite>>;
+  readonly taskLedger: Awaited<ReturnType<typeof openInteractiveTaskLedgerStoreForWrite>>;
+  readonly artifacts: Awaited<ReturnType<typeof openInteractiveArtifactStoreForWrite>>;
+  readonly usage: Awaited<ReturnType<typeof openInteractiveUsageStoresForWrite>>;
+  readonly shellRuns: Awaited<ReturnType<typeof openInteractiveShellRunStoreForWrite>>;
+  close(): Promise<void>;
+}
+
+const activeCompositions = new WeakSet<object>();
+
+export async function openStorageWriterComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions = {},
+): Promise<StorageWriterComposition> {
+  if (activeCompositions.has(lease)) {
+    throw new Error('Storage writer composition is already active for this lease');
+  }
+  await assertStorageRootLease(lease, 'interactive', 'write');
+  if (activeCompositions.has(lease)) {
+    throw new Error('Storage writer composition is already active for this lease');
+  }
+  activeCompositions.add(lease);
+  try {
+    return await createComposition(lease, options);
+  } catch (error) {
+    activeCompositions.delete(lease);
+    throw error;
+  }
+}
+
+async function createComposition(
+  lease: StorageRootLease<'interactive', 'write'>,
+  options: OpenStorageWriterCompositionOptions,
+): Promise<StorageWriterComposition> {
+  const closes: Array<() => void | Promise<void>> = [];
+  let closeTask: Promise<void> | undefined;
+  const close = () =>
+    (closeTask ??= closeInReverse(closes).finally(() => {
+      activeCompositions.delete(lease);

Review Comment:
   P3 is fixed too: a failed close now keeps the lease unavailable, with a regression test covering the behavior.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org