Skip to content

Add packageStorage(): package-bucket storage that survives static imports - #816

Merged
kody-bot merged 1 commit into
mainfrom
package-storage-runtime-helper
Jul 21, 2026
Merged

kody-bot merged 1 commit into
mainfrom
package-storage-runtime-helper

Conversation

@kody-bot

@kody-bot kody-bot commented Jul 21, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Saved-package authors write import { storage } from 'kody:runtime' meaning
"my package's bucket" — a lexical intent — but the binding is dynamic, per
execution context. When package code is statically imported
(import x from 'kody:@scope/pkg/export') it runs in the caller's context,
where the declaring package's storage is not bound: in ad hoc execute without
a storageId, storage is undefined; inside another package, it is the
host package's bucket. Either way, the imported code can never reach its own
data outside its own runtime.

PRs #812 and #814 made the resulting failure articulate (the
runtime_helper_unbound hint). This PR makes it work: package code gets a
way to reach the declaring package's own bucket from any execution context,
without weakening the isolation rules that keep packages out of each other's
data.

Approach

New kody:runtime export packageStorage() — argument-less, returns the
same storage interface as ambient storage (get/set/list/sql/
delete/clear/id), always bound to the declaring package's bucket
(package:<packageId> under the calling user), writable, with the same
entitlement enforcement as other storage writes
(assertStorageRunnerWriteWithinEntitlement). Ambient storage is untouched
everywhere.

Three cooperating layers:

  • Bundle-time identity stamping (module-graph.ts). The runtime module
    cannot know which package a module came from, so the bundler stamps it:
    modules originating from a saved package get their kody:runtime import
    rewritten to a per-package virtual runtime module,
    .__kody_virtual__/package-runtime/<hex(packageId)>.js, which re-exports the
    shared runtime and overrides packageStorage with a variant that closes
    over the package's immutable UUID
    . A closure survives esbuild inlining —
    the exact gap that broke Explain unbound optional kody:runtime helper access in execute errors #812's first iteration (fixed in Match inlined runtime helper declarations in unbound-helper detection #814) — so
    per-module identity holds after the graph collapses into one module. Stamps
    apply to statically imported package sources (via ensurePackageLoaded
    provenance) and to a package's own root modules when builders pass the new
    rootPackageId (publish, invocation, jobs, apps, services, repo checks),
    so stamps persist into published artifacts that later get composed into
    foreign bundles. Hydration (refreshKodyRuntimeModules) regenerates stamped
    modules from the id encoded in the path, exactly like the shared runtime
    module, and materializes their sibling shared-runtime import target.
    Unstamped modules fall back to the run's own packageContext; with no
    provenance at all, packageStorage() throws an actionable error naming the
    storageId and packages.invokeChecked remedies.

  • Host-side provenance grants (run-kody-registry.ts,
    storage-runner.ts). The stamp routes identity but is not the security
    boundary. New kody.package_storage_* tools take a packageId per call and
    honor it only when it is in the run's grant set, which
    collectPackageStorageGrantIds computes exclusively from host-controlled
    provenance: the run's own package context, the packageId entries recorded
    in the bundle's static dependency metadata (new field on
    BundleArtifactDependency, populated from the resolved saved-package row at
    bundle time), and the published artifacts the host itself installs for
    literal dynamic package imports during hydration. Sandbox-supplied strings
    never extend the set, so a malicious community fork running as the installing
    user cannot claim another installed package's bucket — a forged
    kody.package_storage_get({ packageId: victim }) is rejected with a
    structured message. Cross-user access stays structurally impossible
    (buildStorageRunnerName keys the Durable Object on the calling user's id).

  • Shared bucket identity (package-invocations/service.ts,
    storage-runner.ts). buildPackageInvocationStorageId now delegates to the
    new buildPackageStorageId, so a package's own runtime (ambient storage)
    and packageStorage() provably reach the same bucket.

Behavior matrix delivered:

Context packageStorage() ambient storage
Package's own runtime own bucket (same as storage) own bucket (unchanged)
Statically imported into execute, no storageId declaring package's bucket undefined (unchanged)
Statically imported into another package each module: its own declaring package's bucket host package's bucket (unchanged)
Inline execute code, no provenance clear, actionable error per storageId (unchanged)

Docs: docs/use/packages.md gains a "Package storage" section
(storage vs packageStorage() vs packages.invokeChecked),
docs/contributing/packages-and-manifests.md documents the stamping and
grant model, the execute tool's sandbox-surface text names the new helper,
package-author typings (repo/checks.ts) declare it, and the #812
runtime_helper_unbound nextStep for storage now mentions
packageStorage().

Known edge (intentional): grants cover directly recorded provenance. If
package A statically imports package B, and a caller statically imports A,
B's stamped modules in the caller's bundle are denied (with the structured
message pointing at packages.invokeChecked) because the caller's bundle
metadata records only A. Extending grants transitively would require
persisting transitive provenance in artifact metadata; deferred until a real
use case shows up, and the conservative default is the safer one.

Tests

Learning from #814's lesson (synthetic module maps masked the inlining gap),
the new package-storage.workers.test.ts runs real
buildKodyModuleBundle / buildKodyImportableModuleBundle output
end to
end through runBundledModuleWithRegistry:

  • static-import-into-execute: published package's packageStorage().sql(...)
    reads its own seeded bucket from an ad hoc execute call while ambient
    storage stays undefined (the acceptance scenario);
  • two packages statically imported side by side each write and read their own
    bucket;
  • a package statically imported into another package keeps its own bucket
    while the host package's packageStorage() and ambient storage agree;
  • inline execute code without provenance gets the actionable error;
  • a forged package_storage_get with a hand-written victim package id is
    rejected and leaks nothing.

Node-unit coverage (module-graph.node.test.ts) exercises the stamping
mechanics: module-path round trip for stamped ids (including artifact-prefix
nesting), root-module stamping via rootPackageId vs the unstamped fallback,
dependency-module stamping, hydration regeneration of stale stamped modules
plus their sibling shared runtime, and the resolution ladder (stamp → run
package context → provenance error → availability error).
executor.node.test.ts covers the extended storage nextStep, and existing
suites were updated for the hydrateKodyRuntimeModules return-shape change
and the new packageId dependency field.

Local gate: npm run typecheck, npm run lint (0 errors),
npm run format:check, and npm run test (317 files / 1001 tests) all pass
on main @ 29bf8084. The Playwright/MCP E2E halves of npm run validate
were not run locally (untouched surfaces); CI covers them.

System recap — extends existing primitives (medium risk)

Mode: recap · Base: main @ 29bf8084 · Head: d3039a99

Classification: extends — new kody:runtime export plus bundle-metadata
field riding existing provenance tracking; no new primitive added.

Primitives touched

Primitive Group Impact
package-runtime runtime extends — per-package virtual runtime module stamping, rootPackageId, hydration refresh
capabilities-execute runtime extends — package_storage_* tools gated by host-derived provenance grants
package-storage storage extends — same buckets, new provenance-gated access path; bucket naming/isolation unchanged
mcp-server surfaces extends — execute sandbox-surface text and runtime_helper_unbound nextStep mention it

System map

The bundler stamps each saved-package module with its immutable package id;
at execution, the runtime resolves the stamp to a package_storage_* call
that the host honors only when the id is in the bundle's recorded provenance.

Legend: green = composes · amber = extended by this PR · red = new primitive
· gray = context.

flowchart LR
	packageRuntime["package-runtime<br/>Bundler + module graph"]:::extended
	capabilitiesExecute["capabilities-execute<br/>Capabilities execute runtime"]:::extended
	packageStorage["package-storage<br/>Per-package storage buckets"]:::extended
	mcpServer["mcp-server<br/>MCP endpoint (/mcp)"]:::extended
	packageRuntime -->|"stamped package-runtime modules + dependency packageId metadata"| capabilitiesExecute
	capabilitiesExecute -->|"package_storage_* gated by provenance grants"| packageStorage
	mcpServer -->|"execute code / package invocations"| capabilitiesExecute
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Before / after

Package code doing packageStorage().sql(...), statically imported into an ad
hoc execute call with no storageId:

  • Before: no such helper; the equivalent ambient-storage code failed with
    the Explain unbound optional kody:runtime helper access in execute errors #812 runtime_helper_unbound hint and had no working alternative short
    of packages.invokeChecked.
  • After: the call reads/writes the declaring package's own bucket
    (package:<packageId> under the calling user); ambient storage stays
    undefined; hand-written ids for other packages are rejected with a
    structured denial.

Invariants

Per-user isolation untouched: buckets remain Durable Objects named by
buildStorageRunnerName(userId, storageId). Cross-package access within one
account is granted only from bundler/host-controlled provenance (own package
context, recorded static dependency packageIds, host-installed dynamic-import
artifacts) — never from sandbox-supplied strings. Ambient storage semantics
are byte-for-byte unchanged; writes through packageStorage() pass the same
storage-bytes entitlement checks as every other storage write.

Summary by CodeRabbit

  • New Features

    • Added package-scoped durable storage through packageStorage(), allowing saved packages to securely read and write their own data.
    • Preserved package identity across static imports and dynamic invocations.
    • Added guidance and type support for using package storage in documentation and runtime checks.
  • Bug Fixes

    • Improved runtime error messages with actionable package storage guidance.
    • Prevented unauthorized or forged package identifiers from accessing storage.

…bucket

Saved-package code authored against ambient storage breaks when statically
imported into a foreign execution context (ad hoc execute, another package)
because the binding is per-run while authors mean "my package's bucket".
PRs #812/#814 made the resulting error articulate; this makes it work.

- New kody:runtime export packageStorage(): returns the same storage
  interface as ambient storage, always bound to the declaring package's
  bucket (package:<packageId> under the calling user), writable, with the
  same entitlement enforcement as other storage writes.
- Bundle-time identity stamping: modules originating from a saved package
  import kody:runtime through a per-package virtual runtime module
  (.__kody_virtual__/package-runtime/<hex(packageId)>.js) whose
  packageStorage closes over the package's immutable id. The closure
  survives esbuild inlining (the #814 lesson). Hydration regenerates the
  stamped modules from the id encoded in the path.
- Security: the executor grants bucket access only from host-controlled
  provenance (the run's own package context, packageId entries recorded in
  bundle static dependency metadata, and host-installed dynamic-import
  artifacts). Hand-written source claiming another package's id is
  rejected; ambient storage behavior is unchanged everywhere.
- Workers tests run real buildKodyModuleBundle output end to end for the
  behavior matrix, plus node-unit coverage of stamping and provenance.
- Docs: docs/use/packages.md, docs/contributing/packages-and-manifests.md,
  execute tool sandbox surface, and the #812 storage nextStep now mention
  packageStorage().
@coderabbitai

coderabbitai Bot commented Jul 21, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c9759a98-ca66-430b-9f08-179f53c734b9

📥 Commits

Reviewing files that changed from the base of the PR and between 29bf808 and bd17a10.

📒 Files selected for processing (21)
  • docs/contributing/packages-and-manifests.md
  • docs/use/packages.md
  • packages/worker/src/jobs/service.ts
  • packages/worker/src/mcp/executor.node.test.ts
  • packages/worker/src/mcp/executor.ts
  • packages/worker/src/mcp/run-kody-registry.node.test.ts
  • packages/worker/src/mcp/run-kody-registry.ts
  • packages/worker/src/mcp/tools/execute.ts
  • packages/worker/src/package-invocations/service.ts
  • packages/worker/src/package-registry/service.ts
  • packages/worker/src/package-retrievers/service.ts
  • packages/worker/src/package-runtime/module-graph.node.test.ts
  • packages/worker/src/package-runtime/module-graph.ts
  • packages/worker/src/package-runtime/package-app.node.test.ts
  • packages/worker/src/package-runtime/package-app.ts
  • packages/worker/src/package-runtime/package-service.ts
  • packages/worker/src/package-runtime/package-storage.workers.test.ts
  • packages/worker/src/package-runtime/published-runtime-artifacts.ts
  • packages/worker/src/repo/checks.ts
  • packages/worker/src/repo/repo-session-do.ts
  • packages/worker/src/storage-runner.ts

📝 Walkthrough

Walkthrough

Package-scoped storage is added through bundle-time package provenance, per-package virtual runtime modules, guarded storage tools, and propagated dependency metadata. Bundling, invocation, hydration, runtime guidance, documentation, and end-to-end tests are updated accordingly.

Changes

Package-scoped runtime storage

Layer / File(s) Summary
Runtime stamping and hydration
packages/worker/src/package-runtime/module-graph.ts, packages/worker/src/package-runtime/published-runtime-artifacts.ts, packages/worker/src/package-runtime/module-graph.node.test.ts
Bundles stamp saved-package runtime imports, record package IDs in dependency metadata, and expose packageStorage() through hydrated virtual runtime modules.
Package storage capability wiring
packages/worker/src/storage-runner.ts, packages/worker/src/mcp/run-kody-registry.ts
Package storage tools validate granted package IDs, route operations to package buckets, and bind helper functions into bundled execution.
Bundle and invocation integration
packages/worker/src/jobs/service.ts, packages/worker/src/package-invocations/*, packages/worker/src/package-registry/*, packages/worker/src/package-runtime/*, packages/worker/src/repo/*
Bundle builders receive root package IDs and runtime execution receives artifact dependency metadata across package execution paths.
Package storage end-to-end validation
packages/worker/src/package-runtime/package-storage.workers.test.ts
Tests cover package bucket access, isolation, nested imports, missing provenance, and forged package ID rejection.
Runtime guidance and usage documentation
docs/contributing/packages-and-manifests.md, docs/use/packages.md, packages/worker/src/mcp/executor.ts, packages/worker/src/mcp/tools/execute.ts
Documentation and runtime error guidance describe static imports, packageStorage(), ambient storage, and packages.invokeChecked.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PackageBundle
  participant RuntimeRegistry
  participant PackageStorageTools
  participant PackageBucket
  PackageBundle->>RuntimeRegistry: execute stamped runtime module
  RuntimeRegistry->>PackageStorageTools: provide granted package IDs
  PackageBundle->>PackageStorageTools: call packageStorage operation
  PackageStorageTools->>PackageBucket: route authorized request
  PackageBucket-->>PackageBundle: return package-scoped data
Loading

Possibly related PRs

  • kentcdodds/kody#418: Modifies the virtual runtime module machinery used by this package provenance work.
  • kentcdodds/kody#680: Touches the storage-runner tooling extended here with package-scoped storage enforcement.
  • kentcdodds/kody#812: Updates the executor’s unbound runtime-helper guidance used by this change.

Suggested reviewers: kentcdodds

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding packageStorage() for package-bucket storage that works across static imports.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch package-storage-runtime-helper

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-816.kody-a99.workers.dev

Worker: kody-pr-816
D1: kody-pr-816-db
KV: kody-pr-816-oauth-kv

Mocks:

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant