Skip to content

feat(container-loader): support offline loads in loadFrozenContainerFromPendingState#27347

Draft
anthony-murphy wants to merge 6 commits into
microsoft:mainfrom
anthony-murphy:frozen-load-urls
Draft

feat(container-loader): support offline loads in loadFrozenContainerFromPendingState#27347
anthony-murphy wants to merge 6 commits into
microsoft:mainfrom
anthony-murphy:frozen-load-urls

Conversation

@anthony-murphy
Copy link
Copy Markdown
Contributor

Description

Adds an "offline" form of loadFrozenContainerFromPendingState that loads a frozen container entirely from the URL captured in pendingLocalState — no IRequest, IUrlResolver, or IDocumentServiceFactory is supplied by the caller.

ILoadFrozenContainerFromPendingStateProps becomes a discriminated union of two shapes, selected by the presence of the driver-wiring fields:

  • Online (existing behavior): caller supplies request, urlResolver, and documentServiceFactory. The supplied factory is wrapped in a frozen factory and the resolver re-resolves the request just as today.
  • Offline (new): caller omits all three. The entry point synthesizes an IResolvedUrl from pendingLocalState.url, an IUrlResolver whose resolve() returns that synthesized URL, and a no-inner frozen IDocumentServiceFactory. The existing pipeline (loadExistingContainer -> Loader.resolve -> Container.load) is reused unchanged — the identity guard in Loader.resolveCore is trivially satisfied because the same URL string is on both sides of the compare.

Mixing the two forms (some fields supplied, others omitted) is rejected at runtime with UsageError.

IContainer.getAbsoluteUrl throws on an offline-loaded container, since the resolver's external (request-form) URL cannot be reconstructed from the captured resolved URL alone. Fabricating a string in the wrong format would mislead any caller that passed it back into a real resolver.

No changes to Container or Loader were required; all new behavior is composed at the entry-point layer in createAndLoadContainerUtils.ts.

Reviewer Guidance

The review process is outlined on this wiki page.

  • The interface -> type change on ILoadFrozenContainerFromPendingStateProps is required because TS does not allow widening an inherited required field (request/urlResolver/documentServiceFactory) to optional via extends. The API is @legacy @alpha and was introduced one PR ago, so consumer impact is essentially zero.
  • The synthesized request.url carries a resolved-form URL where an external-form URL is normally expected. Nothing downstream inspects it before the synthesized resolver swallows it. Worth a sanity check that no future load-pipeline change starts depending on request.url having external-form structure.
  • Three new tests in loadFrozenContainerFromPendingState.spec.ts cover: offline happy path, getAbsoluteUrl throws, mixed-driver-wiring is rejected.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 19, 2026

Hi! Thank you for opening this PR. Want me to review it?

Based on the diff (831 lines, 16 files), I've queued these reviewers:

  • Correctness — logic errors, race conditions, lifecycle issues
  • Security — vulnerabilities, secret exposure, injection
  • API Compatibility — breaking changes, release tags, type design
  • Performance — algorithmic regressions, memory leaks
  • Testing — coverage gaps, hollow tests

How this works

  • Adjust the reviewer set by ticking/unticking boxes above. Reviewer toggles alone don't trigger anything.

  • Tick Start review below to dispatch the review fleet.

  • After review finishes, tick Start review again to request another run — it auto-resets after each dispatch.

  • This comment updates as new commits land; your reviewer selections are preserved.

  • Start review

Comment thread packages/loader/container-loader/src/createAndLoadContainerUtils.ts
Comment thread packages/loader/container-loader/src/createAndLoadContainerUtils.ts
…romPendingState

Add an offline form that synthesizes IResolvedUrl/IUrlResolver/IDocumentServiceFactory from the URL captured in pendingLocalState. Online callers (all three driver props supplied) behave as before. Mixing the two forms is rejected with UsageError. IContainer.getAbsoluteUrl throws on offline-loaded containers since the resolver's external URL format is unknown.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread packages/loader/container-loader/src/createAndLoadContainerUtils.ts
- Document the offline-form URL shape requirement on ILoadFrozenContainerFromPendingStateProps: pendingLocalState.url must satisfy tryParseCompatibleResolvedUrl's contract (protocol://<string>/.../..?<querystring>), and non-standard resolved-URL shapes surface as UsageError at load time. Addresses the open deep-review comment on the offline JSDoc.

- Add a missing local-server-tests case: capture-and-relay round-trip through a writable-offline frozen load (readOnly: false + no driver wiring), locking in the documented contract that local DDS submissions accrue in the runtime's pending-state manager and round-trip through getPendingLocalState() even when the driver wiring is fully synthesized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread packages/loader/container-loader/api-report/container-loader.legacy.alpha.api.md Outdated
Comment thread packages/loader/container-loader/src/createAndLoadContainerUtils.ts Outdated
…ps interfaces

- Add @internal validateAllOrNone<T>(obj, keys) to @fluidframework/core-utils,
  returning all | none | mixed for runtime validation of the compile-time
  AllOrNone<T> modifier.
- Decompose ICreateAndLoadContainerProps into reusable building blocks:
    * IContainerHostProps        (code loader + optional policy/observability)
    * IContainerDriverServices   (urlResolver + documentServiceFactory)
    * IContainerLoadDriverProps  (driver services + request)
  ICreateAndLoadContainerProps now composes IContainerHostProps and
  IContainerDriverServices; structural shape is unchanged.
- Replace the Omit<ILoadExistingContainerProps, ...> in
  ILoadFrozenContainerFromPendingStateProps with composition:
    IContainerHostProps & { pendingLocalState; readOnly? } & AllOrNone<IContainerLoadDriverProps>
- Wire the entry-point runtime check to validateAllOrNone instead of the
  hand-rolled all-defined/any-defined checks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread packages/loader/container-loader/src/createAndLoadContainerUtils.ts
anthony-murphy and others added 2 commits May 21, 2026 08:53
…eAndLoadContainerProps

- Synthesized IResolvedUrl on offline loads now uses the trailing path
  segment of tryParseCompatibleResolvedUrl's two-segment 'tenantId/docId'
  id, matching the single-doc-id-segment shape that production resolvers
  emit (see localResolver.ts:58, odspDriverUrlResolver.ts:167, etc.).
  Downstream consumers keyed on IContainer.resolvedUrl.id for telemetry
  or cache keys now see the same shape on offline-loaded and online-loaded
  containers.

- Add a spec assertion in loadFrozenContainerFromPendingState.spec.ts that
  pins frozenContainer.resolvedUrl.id to the expected single-segment value
  for the offline path, so the contract can't regress.

- Mark ICreateAndLoadContainerProps as @deprecated, pointing callers at the
  composable building blocks IContainerHostProps and IContainerDriverServices.
  Migrate the three extending props types (ILoadExistingContainerProps,
  ICreateDetachedContainerProps, IRehydrateDetachedContainerProps) to
  extend the building blocks directly. The member set is unchanged so the
  interfaces remain structurally identical.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread .changeset/quiet-shrews-marry.md
…rl hardening

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

🔗 No broken links found! ✅

Your attention to detail is admirable.

linkcheck output


> fluid-framework-docs-site@0.0.0 ci:check-links /home/runner/work/FluidFramework/FluidFramework/docs
> start-server-and-test "npm run serve -- --no-open" 3000 check-links

1: starting server using command "npm run serve -- --no-open"
and when url "[ 'http://127.0.0.1:3000' ]" is responding with HTTP status code 200
running tests using command "npm run check-links"


> fluid-framework-docs-site@0.0.0 serve
> docusaurus serve --no-open

[SUCCESS] Serving "build" directory at: http://localhost:3000/

> fluid-framework-docs-site@0.0.0 check-links
> linkcheck http://localhost:3000 --skip-file skipped-urls.txt

Crawling...

Stats:
  288859 links
    1925 destination URLs
    2175 URLs ignored
       0 warnings
       0 errors


@anthony-murphy
Copy link
Copy Markdown
Contributor Author

Deep Review

Reviewed commit 463f9aa on 2026-05-21.

Readiness: 8/10 — ALMOST READY

The prior cross-package changeset gap for AllOrNone / validateAllOrNone is resolved in 463f9aa by sweet-spiders-grow.md (versions @fluidframework/core-interfaces and @fluidframework/core-utils) and harden-try-parse-resolved-url.md (documents the tryParseCompatibleResolvedUrl non-throwing-contract restoration). All six prior inline threads are resolved. Three Tier 3 polish items remain — flagged inline.

Path to Ready

  • Resolve inline threads

Context for Reviewers

For human reviewer
  • Needs human judgment (markfields) — Whether AllOrNone<T> belongs as a publicly-exported @legacy @alpha primitive in core-interfaces or should be inlined at the single current call site. Author's own previously unresolved comment 3276855511 ("can we restructure the interfaces to be more composable") suggested the shape may move again; reconciling that with the published export is a taste/owner call.
  • Needs human judgment (markfields) — Offline + readOnly: false semantics: local DDS submissions accrue without being published. The "capture → restart offline → keep editing → re-capture and replay online later" workflow is the supporting case. Owner sign-off on this contract is appropriate.
  • Needs human judgment (markfields / ChumpChief) — Offline getAbsoluteUrl deliberately throws UsageError rather than fabricating a string. Well-reasoned (a captured resolved-form URL would mislead any consumer that passes it back into a real resolver), but a contract call worth confirming.
  • Needs human judgment (jatgarg) — Reshape of @legacy @beta ICreateAndLoadContainerProps into IContainerHostProps / IContainerDriverServices bases plus the @deprecated tag. Structurally identical so existing Loop call sites should still compile, but TS will emit deprecation diagnostics. PR Add legacy tags to the added apis for creating and loading container #23301 explicitly carved this surface out for Loop; a Loop-prod usage check and migration-window confirmation are human-scope.
  • Cannot be assessed by the pipeline — Whether the end-to-end offline blob round-trip test catches realistic consumer workloads beyond a single primitive-DDS + single attachment-blob scenario; runtime determinism across resolver implementations; whether Loader.resolveCore / Container.load evolution will preserve the "request.url is opaque-until-resolver in offline mode" invariant.
Review history (6 prior reviews)
  • 5efb38e 2026-05-21 · 5/10quiet-shrews-marry.md cross-package changeset gap for AllOrNone / validateAllOrNone
  • ce2ede7 2026-05-20 · 5/10resolvedUrl.id two-segment shape divergence still unaddressed at this commit
  • faa6a1f 2026-05-20 · 5/10resolvedUrl.id two-segment tenantId/docId shape divergence flagged inline on the offline path
  • 9213320 2026-05-20 · 8/10 — both prior correctness concerns resolved; AllOrNothing API-surface item flagged inline
  • 66f1d3d 2026-05-19 · 9/10AllOrNothing API-surface concern resolved (moved to core-interfaces, renamed AllOrNone); JSDoc shape-constraint Tier 3 flagged inline
  • 2a9813f 2026-05-19 · 5/10 — two correctness concerns flagged inline (offline blob-read footgun; unreachable UsageError jacket in tryParseCompatibleResolvedUrl)

throw new UsageError(
"Attempted to read an attachment blob from a frozen-loaded container without a live storage service. " +
"Fully-offline frozen loads must use pending state produced by `captureFullContainerState`, which inlines all referenced attachment blobs.",
);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep Review: This is an observable change to the @legacy @alpha surface that isn't called out in any changeset.

Pre-PR, frozenDocumentStorageServiceHandler threw new Error("Operations are not supported on the FrozenDocumentStorageService."). Post-PR the generic handler throws new UsageError(...) (different class), and readBlob is rebound to a new frozenReadBlobOfflineHandler that throws a dedicated UsageError whose message names captureFullContainerState explicitly.

The three changesets in this PR cover URL parsing (harden-try-parse-resolved-url.md), the ICreateAndLoadContainerProps deprecation (quiet-shrews-marry.md), and the AllOrNone / validateAllOrNone primitive (sweet-spiders-grow.md) — none mention this ErrorUsageError class change or the new readBlob-specific message.

Suggested fix: append a sentence to quiet-shrews-marry.md (or land a fourth changeset) noting that FrozenDocumentStorageService operations now throw UsageError instead of Error, and that readBlob on a no-inner-storage frozen container throws a dedicated UsageError whose message names captureFullContainerState.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant