Skip to content

feat: open Limrun uploaded apps - #2110

Merged
thymikee merged 14 commits into
mainfrom
feat/limrun-uploaded-apps
Aug 31, 2026
Merged

feat: open Limrun uploaded apps#2110
thymikee merged 14 commits into
mainfrom
feat/limrun-uploaded-apps

Conversation

@thymikee

@thymikee thymikee commented Aug 27, 2026

Copy link
Copy Markdown
Member

Summary

Let provider-backed connections discover and launch pre-uploaded app assets through the existing app workflow, with Limrun as the first implementation.

  • 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> passes that target as generic lease context, allocates with Limrun initialAssets, resolves a strict installed identity, and launches it
  • after allocation, apps retains its installed-app behavior
  • ambiguous, partial-only, unrelated-foreground, or missing identity matches fail closed
  • Android accepts APK assets; iOS accepts IPA, ZIP, TAR, TAR.GZ, and TGZ assets

The command layer contains no Limrun-specific branch or provider import. Commands ask a centralized connection-policy capability; provider runtimes opt into the composed ProviderAppCatalog; Limrun owns asset filtering, allocation, installed-identity mapping, and filename aliases for open, relaunch, and close. Oxlint keeps provider-package imports out of both command roots without brittle property-name bans or pass-through wrappers.

The public HTTP surface fails closed until a tenant-to-provider-account entitlement exists. The same request-origin fact reaches generic catalog and lease contexts, and the Limrun-owned guard rejects both listing and exact-name allocation before provider I/O. The optional initial app is transported additively over the existing protocol-2 lease RPC; released clients omit it and released daemons ignore it.

Size rationale

Exact-head Size CI measures +7.5 kB npm unpacked (+7.1 kB raw JS, +3.4 kB gzip, +2.8 kB tarball), entirely in emitted JS/declarations. Startup medians did not regress (--version -2.1 ms, --help -3.9 ms).

  • 3.6 kB raw is the two lazy provider-owned feature chunks: app-catalog.js (1.6 kB) and session-allocation.js (2.0 kB)
  • the remaining net growth covers the generic catalog contract/composition and sessionless admission, capability-driven CLI flow, help/descriptor updates, strict provider-owned lifecycle aliases, secure HTTP lease transport, and declarations
  • the apparent device-session.js +10.8 kB is chunk relocation paired with src.js -10.7 kB, not additive growth
  • the real public-endpoint regression is test-only and does not enter the package

A measured counterfactual that merged catalog discovery into allocation saved only 403 raw bytes while making allocation-free apps load a 17.4 kB allocation/platform chunk. Moving files alone cannot reduce the aggregate. Further meaningful reduction would remove an ownership/security seam or weaken regression proof, so +7.5 kB is the smallest safe shape found.

Limrun SDK 0.24.5 exposes only limit and nameFilter, without cursor or offset. The implementation requests up to 1,000 assets and does not invent client-side pagination; larger catalogs need provider pagination or an intentionally designed incomplete-result contract.

Validation

Current rebased head 77d853c0a6:

  • pnpm check:affected --run passes all runnable checks, including format, lint, typecheck, 170 layering tests, DI seams, Fallow, build/package, integration-node, wire compatibility, and 3,867 related tests
  • focused uploaded-app/provider/HTTP suites pass 112/112
  • the real public HTTP server regression sends public-network apps and lease-allocation requests through production routing and asserts UNAUTHORIZED, zero Limrun asset-list calls, and zero instance-create calls
  • planted-red coverage caught CLI scope overrides/secrets from broad profile spreading and exact assets excluded by the old two-result lookup
  • the 1,873-line legacy daemon-client test remains at its pin; the new lease-payload assertion lives with daemon-client-rpc
  • protocol remains version 2: 170 declarations checked, 8 compatible changes acknowledged, 0 removed, 1 additive declaration

Live Limrun verification at behavior head 265259cc62:

  • both connect calls verified access with 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, returned a healthy snapshot, clicked Catalog, and verified the Catalog screen
  • iOS opened easagentdevice.app.zip as dev.expo.easagentdevice in 4.64s, returned the Development Build snapshot, clicked INFO, and verified the information screen
  • both exact instance IDs were confirmed terminated and both local connection profiles were disconnected

No docs or skills changed: versioned CLI help and command metadata own the user-facing behavior.

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 2.53 MB 2.53 MB +7.1 kB
JS gzip 847.2 kB 850.7 kB +3.4 kB
npm tarball 973.7 kB 976.5 kB +2.8 kB
npm unpacked 3.36 MB 3.37 MB +7.5 kB

npm unpacked components

Component Base Current Diff
JS / dist source 2.68 MB 2.69 MB +7.5 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.6 ms 30.6 ms -0.0 ms
CLI --help 82.9 ms 84.1 ms +1.2 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/session2.js +550 B +153 B
dist/src/perf-runtime-plan.js +194 B +92 B
dist/src/cli-help.js +230 B +79 B
dist/src/registry.js +212 B +72 B
dist/src/sdk-batch-runner.js +264 B +66 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.7 kB
dist/src/session-allocation.js 0 B 2.0 kB +2.0 kB
dist/src/app-catalog.js 0 B 1.6 kB +1.6 kB
dist/src/app-log-runtime2.js 17.9 kB 18.6 kB +685 B
dist/src/session2.js 216.4 kB 216.9 kB +550 B
dist/src/provider-device-runtime.js 2.8 kB 3.1 kB +350 B
dist/src/limrun.d.ts 10.2 kB 10.5 kB +343 B
dist/src/connection-runtime.js 19.4 kB 19.7 kB +312 B
dist/src/internal/daemon.js 111.2 kB 111.4 kB +268 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.

@thymikee

Copy link
Copy Markdown
Member Author

Sentinel follow-up: exact head 2e6bf6bec8e56284a3bcea69d140abdd79779cdc is unchanged and its CI remains green, but GitHub now reports the branch DIRTY against current main after the platform stack merged. Rebase/resolve the conflict before further readiness review. The previously reported tenant-entitlement product/security decision remains unresolved as well, so this PR is still not merge-ready.

@thymikee
thymikee force-pushed the feat/limrun-uploaded-apps branch from 2e6bf6b to ae09e64 Compare August 31, 2026 08:55
@thymikee

Copy link
Copy Markdown
Member Author

Addressed the rebase and tenant-security blockers at exact head ae09e645fe.\n\n- Rebased onto current origin/main; the plain-close admission exception now uses main's unified descriptor-based exemption together with the provider-app-catalog exemption, so there is one admission declaration path.\n- Chose the fail-closed product policy for the daemon public HTTP surface: Limrun uploaded-app discovery and open <uploaded-name> are unavailable there until a tenant-to-provider-account entitlement exists. Local/account-scoped Limrun use remains available.\n- This is provider-owned, not command-owned: the existing daemon request-origin fact is forwarded through the generic app-catalog and lease-lifecycle contexts; Limrun shares one UNAUTHORIZED check across listing and exact asset resolution.\n- Regressions prove a public request cannot list assets or create an instance, and prove the daemon forwards the access context to both catalog and allocation.\n\nValidation: focused tests 23/23; pnpm check:affected --run passed format, lint, typecheck and the elevated process-visibility rerun cleared the sandbox-only ps blocker. Fresh exact-head CI is running.\n\nThe 1,000-item observation remains a documented SDK limitation: 0.24.5 has no cursor/offset, so a fabricated local pagination fix would be incorrect. I have not changed the public apps result contract merely to attach an ambiguous warning at exactly 1,000; that follow-up needs either provider pagination or an intentionally designed incomplete-result contract.

@thymikee

Copy link
Copy Markdown
Member Author

Follow-up at exact head fde759691c: fresh CI's Compatibility & Provenance failure was a real Fallow complexity finding in resolveProviderAppCatalogResponse. I split pure request classification from the provider I/O path; the CI-equivalent audit now reports no issues across all 51 PR-changed files.\n\nValidation after the extraction: focused admission/catalog/Limrun suite 28/28; pnpm check:affected --run passed through format, lint, typecheck, and all 167 layering checks with elevated ps visibility. Fresh exact-head CI is running.

@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed exact head fde759691c5c483efd958df5900395d140336e02. The prior tenant-isolation concern is fixed in production: public HTTP trust flows through catalog and lease contexts, and the single Limrun-owned guard rejects both listing and exact-name allocation before provider I/O. Remaining blocker: the new tests manually construct internal contexts; none drives public HTTP apps and open <uploaded-name> / lease allocation through the real server-proxy-handler route. Add endpoint regressions with mocked Limrun I/O asserting UNAUTHORIZED and zero asset-list/instance-create calls for both paths. Also refresh stale PR evidence: body still names the old head/pending policy, while exact-head CI is green and Size is +6.9 kB unpacked. No ready-for-human until endpoint proof is present.

@thymikee
thymikee force-pushed the feat/limrun-uploaded-apps branch from fde7596 to 14b0817 Compare August 31, 2026 10:32
@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed exact head 14b08172baac3df86a072366e3c4f4958772a451. The prior endpoint-proof blocker is fixed. Existing proxy-to-real-daemon coverage proves the public-only marker reaches request.internal.publicNetworkOnly; the new real HTTP server/request-handler/Limrun regression proves both uploaded-app listing and allocation return UNAUTHORIZED with zero asset-list/instance-create calls. Production ownership remains fail-closed and provider-owned; the latest simplification removes shallow policy plumbing, and no materially smaller safe design is evident. No code findings. Refresh the stale PR body: it still names 2e6bf6, says security policy is pending, and reports +6.4 kB while current Size is +7.5 kB. Completed exact-head checks are green; Android/Linux/iOS Smoke and Coverage are still in progress.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Aug 31, 2026
@thymikee
thymikee force-pushed the feat/limrun-uploaded-apps branch from 14b0817 to 77d853c Compare August 31, 2026 10:48
@thymikee

Copy link
Copy Markdown
Member Author

Addressed the latest review and completed the holistic cleanup at exact head 77d853c0a6.

  • added a real public HTTP endpoint regression using the production server, request handler, and Limrun runtime; public apps and lease allocation both return UNAUTHORIZED with zero asset-list and zero instance-create calls
  • restored explicit connection-state field copies, preserving CLI tenant/run/session overrides and keeping profile secrets out of persisted state
  • removed the brittle property-name lint and all carrier/identity wrappers it forced; retained the cheap @agent-device/provider-* import restriction for command roots
  • commands now ask one centralized semantic capability and contain no Limrun-specific branch/import
  • moved uploaded-filename aliases into the Limrun lifecycle owner and covered resolve/open/relaunch/close
  • pass the open target as generic initialApp lease context instead of overwriting the public providerApp flag; the optional value is carried additively by the existing protocol-2 lease RPC
  • exact asset lookup uses the catalog limit instead of the unsafe two-result window; ambiguous/missing installed identities still fail closed

The first exact-head Coverage run exposed a valid one-line test-size ratchet. I moved the new lease-payload assertion out of the pinned 1,873-line legacy test and into the existing daemon-client-rpc test owner instead of raising the pin.

Validation: pnpm check:affected --run passes every runnable local gate, including 3,867 related tests, typecheck, 170 layering tests, Fallow, package verification, and protocol compatibility. Exact-head Size CI is +7.5 kB unpacked (+7.1 kB raw, +3.4 kB gzip, +2.8 kB tarball) with no startup regression; the PR body now contains the current itemization and rejected 403-byte/heavier-load counterfactual.

At the requested stop point, exact-head GitHub checks were green for Size, lint, typecheck/package, compatibility, integration, repo guards, CodeQL, native fingerprint, macOS, and Linux. Coverage and the remaining Android/iOS smoke jobs were still running; I did not wait for them.

@thymikee
thymikee merged commit 48eb1b0 into main Aug 31, 2026
18 checks passed
@thymikee
thymikee deleted the feat/limrun-uploaded-apps branch August 31, 2026 11:01
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-31 11:01 UTC

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

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant