Skip to content

feat: open Limrun uploaded apps - #2110

Open
thymikee wants to merge 11 commits into
mainfrom
feat/limrun-uploaded-apps
Open

feat: open Limrun uploaded apps#2110
thymikee wants to merge 11 commits into
mainfrom
feat/limrun-uploaded-apps

Conversation

@thymikee

@thymikee thymikee commented Aug 27, 2026

Copy link
Copy Markdown
Member

Summary

Let Limrun users discover and launch previously uploaded app assets through the existing app workflow without adding a provider-specific flag or public API.

  • connect limrun remains handshake-only and does not allocate an instance
  • before allocation, agent-device apps lists compatible uploaded assets for the selected platform
  • agent-device open <exact-uploaded-asset-name> allocates with Limrun initialAssets, resolves an exact installed identity, and launches it
  • ambiguous, partial-only, or unrelated foreground identities fail closed
  • after allocation, apps keeps its existing installed-app behavior; unmatched open targets preserve the existing path
  • Android accepts APK assets; iOS accepts IPA, ZIP, TAR, TAR.GZ, and TGZ assets
  • sessionless catalog requests retain daemon cancellation through Limrun I/O

The implementation keeps provider asset discovery, preinstallation, and identity mapping in the Limrun owner while reusing the normal CLI, daemon, lifecycle, and JavaScript command paths. Production commands ask semantic connection-policy capabilities instead of inspecting provider identities or importing provider implementations. Oxlint enforces both boundaries.

Size rationale

Exact-head Size CI measures +6.4 kB npm unpacked (+6.1 kB raw JS, +3.1 kB gzip, +2.6 kB tarball). The packed growth is split across:

  • about 3.4 kB raw in two lazy provider-owned feature chunks: asset filtering/exact lookup (app-catalog.js, 1.4 kB) and allocation plus fail-closed installed-identity resolution (session-allocation.js, 2.0 kB)
  • about 3.0 kB net across the provider catalog contract, sessionless apps daemon route, capability-driven deferred selection, provider-owned app-reference resolution, trusted catalog admission, CLI help, declarations, and the provider-independence enforcement follow-up

The largest per-file movements are chunk relocation rather than additive feature size: device-session.js appears at +10.8 kB while src.js falls 10.8 kB. The +6.4 kB aggregate is the authoritative net result after those moves. The final admission hardening deepens the existing composed catalog with side-effect-free capability admission and authoritative listing; it adds no parallel owner registry or eager provider dependency. The latest capability-owner cleanup removes 41 production lines and 221 emitted bytes by replacing seven shallow predicates with the existing capability projection.

A counterfactual size audit tested broader coalescing: one Limrun app chunk would save only another 403 raw bytes over the chosen layout while making pre-allocation apps load a 17.4 kB allocation/platform chunk. Keeping catalog lookup separate avoids that runtime regression. The remaining Limrun modules are loaded lazily, preserving the provider runtime's 29-module eager closure. A smaller device-inventory seam was rejected because installed-app inventory requires allocation and cannot represent uploaded assets; resolving in the CLI would duplicate provider authentication and asset semantics outside their owner. A new Limrun-only command or flag was also avoided so local and remote app discovery keep the same workflow.

Validation

Live Limrun verification at behavior head 265259cc62:

  • both connect calls verified access while reporting leaseAllocated: false; pre-allocation apps listed uploaded filenames without creating an instance
  • Android opened com.callstack.agentdevicelab.apk as com.callstack.agentdevicelab in 6.15s wall time, returned a healthy android-helper 0.20.11-dev snapshot, clicked the Catalog ref, and verified the Catalog screen
  • iOS opened easagentdevice.app.zip as dev.expo.easagentdevice in 4.64s wall time, returned the Development Build snapshot, clicked INFO, and verified the information screen
  • Limrun's three-minute inactivity expiry won the Android close race and returned typed LEASE_NOT_FOUND; both exact instance IDs were then queried directly and returned 404, and both local connection profiles were disconnected

Current rebased head 2e6bf6bec8:

  • rebased onto current origin/main; GitHub reports the PR mergeable with no conflicts
  • Oxlint rejects provider-package imports and raw .leaseProvider reads in both production command roots, plus connection-result .provider reads in CLI commands
  • planted aliases and renamed destructuring for both provider identity fields failed at their source reads before the identity could be hidden
  • sessionless catalog admission and listing use the same catalog composed from active runtimes that declare appCatalog; planted bogus, proxy, and browserstack cases failed before the fix and now reject without catalog, runtime-fact, or device-bind access
  • a planted empty-catalog fallthrough failed by reaching local device selection; supported empty catalogs now return apps: [] authoritatively without device access
  • focused admission/router/provider/help suites pass 55/55; format, lint, typecheck, layering, DI seams, fallow, build, package, integration-node, and macOS coverage checks pass locally
  • the broad related shard passed all 3,956 assertions, then reported the known unrelated Apple-runner late-import teardown; session-replay-repair-transaction.test.ts passes 17/17 alone with one worker
  • exact-head Size, Lint & Format, Compatibility & Provenance, and Resolve native fingerprint are green; remaining GitHub CI is running

Security policy decision pending from review: the daemon-wide Limrun credential makes uploaded assets account-scoped today. Both apps and exact-name selection during open must share any future tenant entitlement; gating listing alone would not close the boundary. The 0.24.5 SDK asset endpoint exposes only limit and nameFilter, with no cursor or offset, so catalogs beyond the current 1,000-item request cannot be completely paginated client-side.

This touches 49 files. Scope spans the existing apps command, Limrun provider runtime, shared provider contracts, daemon routing and admission, tests, CLI help, and the existing lint configuration; it does not add a command family, public API, or flag.

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 2.51 MB 2.51 MB +6.1 kB
JS gzip 837.2 kB 840.3 kB +3.1 kB
npm tarball 964.1 kB 966.8 kB +2.6 kB
npm unpacked 3.34 MB 3.35 MB +6.4 kB

npm unpacked components

Component Base Current Diff
JS / dist source 2.66 MB 2.67 MB +6.4 kB
Apple runner source/project 581.1 kB 581.1 kB 0 B
macOS helper source 54.8 kB 54.8 kB 0 B
Android helper artifacts 0 B 0 B 0 B
Other package files 45.6 kB 45.6 kB 0 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 30.4 ms 30.4 ms +0.1 ms
CLI --help 83.3 ms 82.0 ms -1.2 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/session2.js +436 B +125 B
dist/src/cli-help.js +230 B +79 B
dist/src/registry.js +212 B +73 B
dist/src/sdk-batch-runner.js +264 B +70 B
dist/src/perf-runtime-plan.js +194 B +56 B

Top changed packed files

Packed file Base Current Diff
dist/src/device-session.js 0 B 10.8 kB +10.8 kB
dist/src/src.js 19.2 kB 8.5 kB -10.8 kB
dist/src/session-allocation.js 0 B 2.0 kB +2.0 kB
dist/src/app-catalog.js 0 B 1.4 kB +1.4 kB
dist/src/session2.js 216.4 kB 216.8 kB +436 B
dist/src/provider-device-runtime.js 2.8 kB 3.1 kB +350 B
dist/src/connection-runtime.js 19.4 kB 19.8 kB +334 B
dist/src/application-lifecycle-interaction.js 4.6 kB 4.9 kB +317 B
dist/src/sdk-batch-runner.js 81.2 kB 81.5 kB +264 B
dist/src/limrun.d.ts 10.2 kB 10.4 kB +258 B

@thymikee

Copy link
Copy Markdown
Member Author

Not ready.

[P1] Fail closed when mapping an uploaded asset to the installed app. resolveInstalledAppIdForAsset accepts a unique partial containment match, then Android falls back to whichever user-installed app is foreground. Either can bind the exact uploaded asset name to and launch the wrong installed app. Obtain an authoritative identifier from Limrun, or derive it from a strong provider-owned/before-after signal and fail otherwise. Add planted-red collision and unrelated-foreground cases.

[P2] Preserve request cancellation through sessionless catalog lookup. ProviderAppCatalog accepts an AbortSignal and Limrun uses it, but resolveProviderAppCatalogResponse invokes it without the daemon request signal, so a cancelled/timed-out apps request can leave provider I/O running. Thread the request signal and test abort.

Exact-head Coverage has two owner-action failures: cli-config.test.ts grew 1282→1383 past its ratchet (extract the Limrun cases), and provider-limrun eager closure grew 29→32. Android smoke is red on the recurring native-alert timeout and needs rerun/base evidence. Remove the new narration docblocks. Size evidence crosses the +3 kB unpacked scrutiny trigger (+3.8 kB), so itemize justified packed growth and why a smaller seam was rejected. The live Limrun Android/iOS evidence and cleanup are otherwise strong.

@thymikee
thymikee force-pushed the feat/limrun-uploaded-apps branch from 82cb7cb to dbfa651 Compare August 28, 2026 06:37
@thymikee

Copy link
Copy Markdown
Member Author

Addressed in dbfa651 after rebasing onto current main:

  • removed partial/single-app/foreground fallback matching; uploaded assets now resolve only through an unambiguous exact normalized ID, terminal ID, or app name
  • added planted-red collision and unrelated-foreground regressions
  • threaded the daemon request signal into sessionless provider catalog lookup and added an in-flight abort test
  • split Limrun CLI cases into cli-config-limrun.test.ts, restoring cli-config.test.ts to its 1,282-line pin
  • lazily loaded the three Limrun allocation/catalog modules, restoring the eager closure to 29
  • removed the added narration docblocks
  • itemized package growth and the rejected smaller seams in the PR body

Local static gates and exact ratchets pass. The affected suite produced changing unrelated load failures across two runs; all 3,753 tests passed on the first run before teardown noise, and the second run's two timed-out interaction-contract files passed 16/16 immediately in isolation. Fresh exact-head CI is running.

@thymikee
thymikee force-pushed the feat/limrun-uploaded-apps branch from dbfa651 to 24bdcd0 Compare August 28, 2026 06:45
@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed 265259cc. I found no code defect in the strict exact-ID mapping, Android fallback removal, cancellation propagation, or lazy provider loading; the planted-red coverage is meaningful.

It is not merge-ready yet:

  1. Live Limrun Android and iOS apps → open <uploaded filename> → snapshot/click evidence is stamped only at 82cb7cb1, before the identity/fallback changes. Repeat it at this exact head and record cleanup.
  2. Update the size rationale from +3.8 kB to the current Size CI result, +6.3 kB npm unpacked, with the current itemization.
  3. iOS Smoke was still running at review time.

Main compatibility is clean.

@thymikee

Copy link
Copy Markdown
Member Author

Addressed at exact head 265259cc62.

Live Limrun evidence:

  • Android connect verified access with leaseAllocated: false; apps listed com.callstack.agentdevicelab.apk; open resolved it to com.callstack.agentdevicelab in 6.15s wall time. snapshot -i --json reported the expected bundle, healthy android-helper backend, and helper version 0.20.11-dev. Clicking the Catalog ref succeeded and the next snapshot showed Catalog, 12 results.
  • iOS connect verified access with leaseAllocated: false; apps listed easagentdevice.app.zip; open resolved it to dev.expo.easagentdevice in 4.64s wall time. The snapshot showed Development Build; clicking INFO succeeded and the next snapshot showed the development-build instructions.
  • Cleanup: Limrun's three-minute inactivity expiry won the Android close race and returned typed LEASE_NOT_FOUND. Direct get calls for both exact instance IDs returned 404, confirming termination. Both local connection profiles were disconnected.

Size rationale is updated in the PR body for exact-head Size CI: +6.3 kB npm unpacked (+6.0 kB raw JS, +3.3 kB gzip, +2.4 kB tarball), entirely under JS/dist. About 3.4 kB raw is the three lazy provider-owned feature chunks (app-catalog, session-allocation, app-preinstall); the remaining ~2.9 kB net covers the neutral catalog/daemon/capability/lifecycle/help/declaration seams. The apparent ios.js +9.4 kB is chunk relocation paired with src.js -10.7 kB, not additive growth. The body also retains why device inventory, CLI-owned resolution, and a provider-specific command/flag were rejected as smaller-looking but incorrect seams.

Exact-head CI is now fully green, including iOS Smoke.

@thymikee

Copy link
Copy Markdown
Member Author

Follow-up size audit at 32309bb5a3 found one safe simplification: the shallow 37-line app-preinstall.ts module now lives with session allocation, removing one dynamic import/chunk while preserving the 29-module eager closure. Exact-head Size CI moved from +6.3 kB to +6.1 kB npm unpacked (+5.8 kB raw JS, +2.8 kB gzip, +2.4 kB tarball).\n\nI also measured the more aggressive counterfactual. Coalescing catalog, identity resolution, and allocation saves only another 403 raw bytes, but makes pre-allocation apps load a 17.4 kB allocation/platform chunk. I rejected that trade: it optimizes package accounting by less than half a kilobyte while making the non-allocating catalog path materially heavier. File moves alone do not reduce the metric because Size CI sums all emitted JS; only eliminated wrappers/duplication or changed tree-shaking count.\n\nThe PR body now contains the exact-head itemization and this rejection rationale. Locally, focused catalog/runtime/closure tests passed 387/387 and pnpm check:affected --run passed all 3,756 tests plus format, lint, typecheck, layering, and build.

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed exact head 32309bb: clean and ready for human review. The delta from the live-tested behavior head is a behavior-preserving relocation of the two app-preinstall functions into session-allocation; exact-head checks are fully green. Discovery remains allocation-free, exact uploaded-name selection precedes allocation, identity resolution fails closed, and cancellation is threaded. Size rationale and planted-red coverage are sufficient. Residual: live Limrun evidence is stamped at 265259c, but the current byte-equivalent relocation plus exact-head package/platform CI does not warrant a device rerun.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Aug 28, 2026
@thymikee

Copy link
Copy Markdown
Member Author

Provider-independence follow-up at 599619fe59: the uploaded-app command path no longer branches on limrun in connection-presentation.ts. Lease preparation, suggested workflow, missing-app labeling, allocation deferral, and open-target forwarding all ask the same semantic capability, connectionProviderSupportsDeferredAppSelection. Provider identity remains confined to src/cli/connection/provider-policy.ts, where provider names are registered and mapped to behavior.\n\nI considered persisting capabilities in connection state or adding them to the provider contract, but rejected both: this decision is deterministic CLI connection policy, and either option would add state/schema surface without enabling behavior we cannot already express. A future provider can join by declaring the capability in the policy module; command code remains unchanged.\n\nValidation: focused presentation/policy tests passed 22/22; pnpm check:affected --run passed all 3,756 tests plus format, lint, typecheck, layering, fallow, and build. Exact-head Size CI remains +6.1 kB npm unpacked.

@thymikee

Copy link
Copy Markdown
Member Author

Added a structural guard for provider-independent commands in 024404db31.

Why the existing layering gate instead of an Oxlint plugin:

  • Oxlint has no built-in no-restricted-syntax equivalent for this AST shape.
  • Local JS plugins are still marked alpha by Oxlint.
  • The repository already runs an oxc-parser-based ownership gate on every PR.

R71 command-provider-policy now rejects string-literal comparisons and switches on .leaseProvider / .provider under src/commands/** and src/cli/commands/**, while leaving provider registration, adapters, policy, help text, and tests alone. The four existing command-side identity branches were replaced by small semantic predicates in src/cli/connection/provider-policy.ts.

Failure-direction evidence: a planted state.leaseProvider === "planted-provider" in connection-presentation.ts failed with R71 at the exact line and directed the fix to provider-policy.ts; removing it restored the gate. pnpm check:affected --run then passed all runnable checks, including 3,769 related tests. The first broad run had a known late-import teardown error from session-replay-repair-transaction.test.ts; that file passed 17/17 in isolation and the complete retry passed.

@thymikee thymikee removed the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Aug 28, 2026
@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed current exact head 024404d (the helper-triggered 599619f was superseded). This head is not ready: Lint & Format has a real owner-action failure at packages/provider-limrun/src/app-catalog.ts:98 (use replaceAll), and the new command-provider structural guard is bypassable by destructuring/aliasing verification.provider before comparing it. Its planted test covers only direct member expressions, so the claimed no-provider-branch guarantee is false. Strengthen the owning declaration/rule and plant alias/destructuring red. The prior iOS form-input failure is unrelated to this delta and likely runtime-shaped, but current-head iOS remains authoritative. ready-for-human should be removed until lint and guard validity are fixed.

@thymikee

Copy link
Copy Markdown
Member Author

Follow-up 27f67c3ae1 adds the cheaper general boundary discussed above: Oxlint now rejects @agent-device/provider-* imports from production files under src/commands/** and src/cli/commands/**. The wildcard covers future providers automatically; existing test overrides remain unchanged.

The planted-red proof used the existing @agent-device/provider-webdriver import in connection-runtime.ts: lint rejected it with the policy-seam remediation. That classification now goes through connectionProviderUsesCloudWebDriverLease in provider-policy.ts. R71 remains complementary and narrow: it catches raw provider-ID comparisons that need no provider-package import.

Validation: pnpm check:affected --run passed all runnable checks, including 3,769 related tests and package verification.

@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed exact head 27f67c3: still blocked. The lint finding is not fixed—app-catalog.ts still uses replace(/.../g); the new commit edits obsolete .oxlintrc.json, which main deleted in favor of oxlint.config.ts. The provider guard is also unchanged and remains bypassable through alias/destructuring; the new provider-package import restriction does not catch literal comparisons without imports, and no planted alias regression was added. GitHub reports CONFLICTING, so only CodeQL ran. Rebase current main, fix config at its owning file, use replaceAll, strengthen/test the provider identity boundary, then run full exact-head CI.

@thymikee

Copy link
Copy Markdown
Member Author

Cleanup follow-up: removed the custom R71 AST scan and its dedicated test/wiring (102 lines). It only recognized selected comparison/switch syntax, so expanding it would become a partial data-flow linter with ongoing false-positive/false-negative cost. The retained boundary is the built-in Oxlint no-restricted-imports rule for @agent-device/provider-* under production command roots. A planted import '@agent-device/provider-limrun' fails with the intended command/provider-capability message; after removal, pnpm check:affected --run passes all runnable checks. Commit: 20621dec0a.

@thymikee
thymikee force-pushed the feat/limrun-uploaded-apps branch from 20621de to 69e7ee8 Compare August 28, 2026 11:32
@thymikee

Copy link
Copy Markdown
Member Author

Addressed the still-relevant findings at rebased head 69e7ee83a2:\n\n- rebased onto current origin/main; GitHub now reports MERGEABLE with no conflicts\n- moved the production-command @agent-device/provider-* restriction from deleted .oxlintrc.json to the current owner, oxlint.config.ts\n- changed the app identity normalization to replaceAll\n- repeated the planted provider import after the config migration; Oxlint rejected @agent-device/provider-limrun at the import with the capability-policy remediation\n- pnpm check:affected --run passes all runnable checks on the rebased head\n\nThe request to strengthen alias/destructuring handling in R71 no longer applies: R71 and its claimed provider-literal guarantee were removed rather than expanded into a partial data-flow linter. The retained, deliberately narrower guarantee is that production commands cannot depend directly on provider implementation packages; current provider-dependent decisions go through semantic helpers in provider-policy.ts. Fresh exact-head CI, including Size, is running.

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed exact head 69e7ee83a2.

P1 — provider-independent commands are still not enforced. The latest commit deletes R71 and its regression test instead of closing the alias/destructuring bypass. The replacement no-restricted-imports rule in oxlint.config.ts only rejects imports from @agent-device/provider-*; it cannot detect provider-identity branching that needs no provider import, for example const { provider } = verification; if (provider === 'limrun') …. That shape passes the stated guard, so the PR body’s claim that the boundary is enforced for current and future providers is not true.

Please enforce this at an owning interface—either a complete structural rule with planted alias/destructuring red proof, or a type/API design that prevents command code from obtaining provider identity. Do not narrow the guarantee to import-only without explicit maintainer approval.

The production route, fail-closed identity resolution, live Limrun evidence, size rationale, mergeability, and exact-head CI otherwise look good.

@thymikee

Copy link
Copy Markdown
Member Author

Addressed the remaining provider-identity boundary at exact head e9725b6e21.

  • production command roots now reject reads of .leaseProvider; CLI command roots also reject reads of connection-result .provider
  • the guard is built-in Oxlint no-restricted-properties, so it fires at the property read before a copied value, alias, or renamed destructuring can hide the identity
  • connection commands now receive semantic capability projections from provider-policy.ts; provider metadata needed for transport/output is passed as whole owned fragments instead of inspected in commands
  • the existing @agent-device/provider-* import restriction remains as the complementary package boundary

Planted-red proof: a temporary command copied stateAlias.leaseProvider, destructured leaseProvider under a new name, and destructured verification.provider under a new name. Oxlint reported all three at their source reads with the policy-seam remediation. Removing the plant restores lint.

Validation: focused connection/provider/state tests passed 77/77; format, lint, typecheck, layering, fallow, build, package, and integration-node passed in the full affected run. The broad related shard twice hit the same unrelated gesture-admission-parity.test.ts timeout at exactly 5.00s, followed by known late-import teardown noise; the two named owner files pass 21/21 together with one worker. Exact-head GitHub CI is running, so I am not treating this as merge-ready until it is green.

No docs or skills changed: this is enforcement and internal ownership only.

@thymikee

Copy link
Copy Markdown
Member Author

Exact head e9725b6e21 is now fully green. The first iOS smoke attempt hit the previously observed unrelated TEXT_INPUT_COMMIT_NOT_OBSERVED form-fill canary; the failed-lane rerun passed the complete iOS simulator suite. GitHub's exact-head Size report remains +6.1 kB npm unpacked (+5.8 kB raw JS, +3.0 kB gzip, +2.5 kB tarball). The PR body now records the final guard, planted alias/destructuring evidence, 43-file scope, current size figures, and CI result. No merge performed.

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed exact head e9725b6e21.

P1 — sessionless apps still bypasses tenant lease admission for arbitrary provider strings. isDeferredProviderAppCatalogRequest grants the exemption whenever leaseProvider is any string plus iOS/Android and no lease. Admission returns before tenant lease enforcement. For an unknown/proxy/WebDriver provider, the composed catalog declines, then handleAppsInventory falls through to ordinary device resolution and app listing. A tenant-isolated caller can therefore supply e.g. leaseProvider: 'bogus' and enumerate a local device’s apps without a lease.

Derive this exemption from the trusted semantic deferred-catalog capability (or otherwise restrict it to the catalog-owning provider) before admission. Plant negative tests proving unknown, proxy, and WebDriver provider values remain lease-rejected and never bind a device.

The new provider-identity lint does close direct, alias, computed, and destructuring reads; exact-head CI and the remaining Limrun route look good.

@thymikee

Copy link
Copy Markdown
Member Author

Addressed the tenant admission bypass at exact head fe1ce5b9d4. The sessionless apps exemption now receives the provider IDs derived from active runtimes that actually implement appCatalog; an arbitrary string-shaped leaseProvider no longer qualifies. The runtime composition remains provider-generic: a future provider joins by implementing the existing catalog capability, with no provider name in admission.\n\nPlanted-red evidence: focused admission tests for bogus, proxy, and browserstack failed 3/3 before the fix because no exception was raised. They now pass. Router-level regressions additionally prove all three are lease-rejected before the provider catalog, runtime fact inspection, or device bind is called. The positive runtime-declared limrun case still lists uploaded assets with zero device access.\n\nValidation: focused admission/registry/composition tests pass 36/36; format, lint, typecheck, layering, fallow, build, package, and integration-node pass. Both broad affected runs completed all 3,957 tests, then hit the known Apple-runner late-import teardown in unrelated replay-repair owners; those two files pass 21/21 together with one worker. Fresh exact-head GitHub CI is running. No docs or skills changed.

@thymikee

Copy link
Copy Markdown
Member Author

Addressed the architecture concern at exact head 9146fb754b.

I re-reviewed the first fix against the provider-runtime and daemon-admission seams and replaced it rather than keeping the raw ID plumbing:

  • removed providerAppCatalogIds from daemon/runtime composition entirely
  • deepened the existing composed provider app-catalog interface: supports(provider) is the side-effect-free admission fact and list(query, signal) is the authoritative execution path
  • provider runtimes still opt in through the existing optional appCatalog handler, so another provider gains the behavior by declaring the capability; no command or admission code names Limrun
  • the command descriptor now classifies only the semantic exemption (unconditional or provider-app-catalog) and no longer receives root-composition context
  • admission and execution use the same composed catalog object, so the fact trusted before lease bypass cannot drift from a separately threaded ID list
  • a supported catalog returns an authoritative array, including []; it cannot decline after admission and fall through to local device discovery
  • removed the remaining Limrun-specific wording from production command help

Evidence:

  • planted the empty-catalog fallthrough: the new router test failed 1/1 by reaching local device selection (DEVICE_NOT_FOUND), then passed after restoring authoritative empty-catalog handling
  • unknown, proxy, and BrowserStack requests remain rejected before catalog listing, runtime-fact inspection, or device binding
  • focused final suites pass 55/55
  • format, lint, typecheck, layering, DI seams, fallow, build, package, integration-node, and macOS coverage pass
  • broad related shard: 3,957/3,958 passed; the unrelated gesture-admission test hit its existing five-second contention timeout and passes 4/4 alone with one worker

Exact-head GitHub CI is running.

@thymikee

Copy link
Copy Markdown
Member Author

Final architecture audit at exact head 9146fb754b4d736e855e4a60c8a8a8063d9d49d8:

  • no production command branches on provider identity or imports a provider implementation
  • no parallel catalog-ID registry remains; admission and execution share the same composed ProviderAppCatalog
  • the descriptor classifies request semantics only, while the provider runtime owns supports and authoritative list behavior
  • an empty supported catalog is terminal and cannot fall through to local device discovery; planted-red proof failed by reaching device selection before the fix and passes now
  • unknown, proxy, and WebDriver provider values remain lease-rejected before catalog, runtime-fact, or device access

Exact-head CI is fully green across Size, static/architecture gates, coverage, integration, CodeQL, and Android/iOS/Linux/macOS smoke tests. GitHub reports the PR MERGEABLE / CLEAN. The PR body now records the final design, size rationale, and validation evidence.

@thymikee

Copy link
Copy Markdown
Member Author

Independent review found one security contract to resolve before approval: sessionless apps is tenant-admitted but lists assets through the daemon-wide Limrun API key, while ProviderAppCatalogQuery contains no tenant. Any daemon-authenticated tenant can therefore enumerate Limrun-account asset filenames. If tenant isolation includes Limrun asset confidentiality, add a tenant-entitlement boundary or keep this route lease-gated; if the daemon bearer is deliberately account-scoped, document that security boundary and compromise explicitly. The current tenant-a test makes this policy, not an accidental guard gap.

Otherwise the implementation is cohesive: ProviderAppCatalog is the single runtime-owned fact used for admission and execution, empty catalogs terminate before device binding, and open passes only a provider-resolved asset ID into initialAssets with strict installed-identity verification and cleanup. I found no materially smaller safe implementation; exact-head CI is green. Minor follow-up: the 1,000-asset limit silently truncates larger accounts.

@thymikee

Copy link
Copy Markdown
Member Author

Size follow-up at 2e6bf6bec8 found one additional safe simplification:

  • collapsed seven shallow provider predicates and the one-entry deferred-selection Set into the existing capability projection
  • replaced helper-level tests with one test through the capability interface
  • net change from the prior head: 41 fewer production lines, 17 fewer test lines, -221 B raw/unpacked, -50 B gzip, and -63 B in the local tarball measurement

I did not take the larger-looking reductions: merging the catalog with allocation still saves only about 403 raw bytes while making pre-allocation apps load the 17.4 kB allocation/platform path. The catalog/admission seams are now earning their size by keeping discovery allocation-free and fail-closed.

The fresh tenant comment is valid and requires an explicit security-policy decision before it can be called addressed. The Limrun runtime has one daemon-wide API key. Therefore both sessionless apps and open <asset-name> operate on that account-wide catalog; gating only listing would still allow exact-name probing/use during allocation. A real tenant-isolation fix must bind both operations to one provider-owned tenant entitlement. The alternatives change product behavior: declare the daemon credential/bearer account-scoped and document cross-tenant asset visibility, introduce a tenant-to-provider-account entitlement, or disable uploaded-asset selection for auth-hook multi-tenant requests. I have not guessed that policy.

The 1,000-item observation is also accurate, but Limrun SDK 0.24.5 exposes /v1/assets as a plain array with only limit and nameFilter; there is no cursor or offset to retrieve another page. Raising the cap only moves silent truncation, and failing at exactly 1,000 regresses an exactly-full catalog. Exact-name open remains available because it uses nameFilter. This needs either a warning/result-contract choice or provider pagination support rather than a pretend local pagination fix.

Validation: focused capability/connection tests pass 23/23. The full affected run passed format, lint, typecheck, layering, DI seams, fallow, build, package, integration-node, macOS coverage, and all 3,956 related assertions; it then reported the known unrelated Apple-runner late-import teardown in session-replay-repair-transaction.test.ts, which passes 17/17 alone with one worker. Fresh exact-head CI is running.

@thymikee

Copy link
Copy Markdown
Member Author

Re-review at 2e6bf6bec8: the capability-projection cleanup is a safe reduction; it removes shallow helpers without weakening the runtime-owned catalog seam, and exact-head CI is green.

The tenant finding remains intentionally unresolved, so this is still not merge-ready: both sessionless apps and open <asset-name> use the daemon-wide Limrun account with no tenant entitlement. Please make the product/security choice explicit—account-scoped bearer with documented cross-tenant visibility, one provider-owned tenant entitlement applied to both operations, or no uploaded-app selection for multi-tenant auth-hook requests.

The 1,000-item catalog limit also remains a minor follow-up. With no provider pagination, do not simulate it locally; choose an incomplete-result/warning contract or obtain upstream pagination support.

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