Skip to content

feat(userspace): first-party reference .taosapp + boot-seeding (#89 P4a)#973

Merged
jaylfc merged 2 commits into
devfrom
feat/userspace-seed
Jun 17, 2026
Merged

feat(userspace): first-party reference .taosapp + boot-seeding (#89 P4a)#973
jaylfc merged 2 commits into
devfrom
feat/userspace-seed

Conversation

@jaylfc

@jaylfc jaylfc commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Phase P4a (backend) of the app system (#89): a minimal first-party reference .taosapp plus boot-seeding, proving the package + trusted-install loop end to end. Additive and backend-only.

What lands

  • tinyagentos/userspace/seed/welcome/ -- a self-contained reference app (taos-welcome): loads the SDK, applies injected theme tokens as CSS variables and subscribes for theme changes, does a kv set/get round-trip + a notify, and renders a themed page. The reference example for studio authors.
  • tinyagentos/userspace/seed.py -- seed_bundled_apps(store, apps_root, seed_dir): for each seed subdir with a manifest.yaml, builds the .taosapp in memory, validates + extracts via extract_package, and installs with trust="first-party" (the trusted path). Idempotent: skips when already installed at the same version, re-seeds on a version bump, per-app try/except so one bad entry only warns.
  • app.py lifespan: seeds the bundled apps after the userspace store init, wrapped so a seeding failure logs a warning and never crashes startup.

Scope boundary

Backend only. Surfacing userspace apps in the desktop launcher (the high-blast-radius UI merge) is a separate, design-sensitive step handled by the lead. This PR proves the package format + the first-party seed/runtime path; the welcome app installs first-party at boot and is served with the first-party CSP.

Tests

8 new (tests/userspace/test_seed.py): first-party seed, idempotency, version-bump re-seed, missing-dir tolerance, first-party CSP on the served bundle, and the real bundled welcome app. Full userspace suite 68 pass; create_app OK.

Part of #89 (P4).

Summary by CodeRabbit

  • New Features

    • Bundled first-party apps are now automatically seeded and installed at startup with idempotent re-seeding behavior.
    • New Welcome app included with key-value storage and notification functionality for testing and onboarding.
  • Tests

    • Added comprehensive test suite for app boot-seeding mechanism, including HTTP asset serving, content security policy validation, and idempotency verification.

Adds tinyagentos/userspace/seed/welcome/ (manifest.yaml + index.html),
a minimal first-party reference app that exercises the SDK theme API
(get + subscribe), a kv round-trip, and a notify call.

Adds tinyagentos/userspace/seed.py exposing seed_bundled_apps, which
builds each seed subdirectory into an in-memory .taosapp zip, validates
it through extract_package, and calls store.install with trust="first-party".
Idempotent: skips apps already installed at the same version; re-seeds on
a version bump. A seeding error is caught and logged -- it does not crash
startup.

Wires seed_bundled_apps into the app.py lifespan immediately after the
userspace store and data store are initialised. The call is wrapped in a
try/except so a seeding failure only emits a warning.

Adds tests/userspace/test_seed.py covering: install as first-party,
idempotency, version-bump re-seed, missing seed_dir is silent, bundle
route returns first-party CSP, and the real bundled taos-welcome app
for all of the above.
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jaylfc, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 1 minute and 56 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 36c598d2-c4a7-4dff-8293-9033153b10be

📥 Commits

Reviewing files that changed from the base of the PR and between 087cb9e and 82f4d8c.

📒 Files selected for processing (2)
  • tests/userspace/test_seed.py
  • tinyagentos/userspace/seed.py
📝 Walkthrough

Walkthrough

Adds tinyagentos/userspace/seed.py implementing seed_bundled_apps, which scans bundled app subdirectories, builds in-memory ZIPs, and installs them with trust="first-party" at startup. The taos-welcome app is added as the first bundled seed. FastAPI lifespan calls seeding on startup. A new test module covers seeding correctness, idempotency, version-bump behavior, missing-directory handling, and bundle-route CSP headers.

Changes

Userspace boot-seeding

Layer / File(s) Summary
seed_bundled_apps implementation
tinyagentos/userspace/seed.py
Adds _build_zip_from_dir (in-memory ZIP builder) and async seed_bundled_apps that scans manifest.yaml files, checks installed version for idempotency, builds ZIP bytes, and calls store.install with trust="first-party"; per-app errors are caught and logged.
taos-welcome bundled app
tinyagentos/userspace/seed/welcome/manifest.yaml, tinyagentos/userspace/seed/welcome/index.html
Adds the first bundled seed app: a manifest declaring id taos-welcome (v1.0.0) with app.kv and app.notify permissions, and an HTML page performing KV round-trip and notification checks with theme token integration.
FastAPI lifespan wiring
tinyagentos/app.py
Calls seed_bundled_apps(userspace_apps, data_dir / "apps") in lifespan startup with a broad try/except that logs warnings on failure without aborting initialization.
Seeding and bundle-route tests
tests/userspace/test_seed.py
Tests first-party installation fields, idempotency (no duplicate rows, unchanged installed_at/trust), version-bump re-seed, missing seed_dir silent handling, and CSP header invariants (sandbox, no allow-same-origin, default-src 'none') for both synthetic and real taos-welcome apps.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • jaylfc/taOS#972: This PR's seed_bundled_apps first-party trust installation and bundle-route CSP assertions build directly on the trust column, first-party vs. community CSP logic, and authorization changes introduced in that PR.

Poem

🐰 Hop, hop, the seeds are sown at dawn,
A welcome app springs up upon the lawn!
First-party trust with sandbox walls so tight,
No allow-same-origin in sight!
Idempotent seeds—plant once, no fuss,
The rabbit cheers: this boot's for us! 🌱

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% 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 'feat(userspace): first-party reference .taosapp + boot-seeding (#89 P4a)' is directly related to the main changes: adding a reference .taosapp app and implementing the boot-seeding mechanism. It accurately summarizes the primary additions.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/userspace-seed

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 and usage tips.

Comment on lines +78 to +79
except (PackageError, Exception):
logger.warning("failed to seed bundled app in %s", app_dir, exc_info=True)

@gitar-bot gitar-bot Bot Jun 17, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Quality: Redundant exception tuple catches everything via Exception

except (PackageError, Exception): is redundant — PackageError subclasses Exception, so the tuple is equivalent to except Exception:. Listing PackageError first is misleading (it suggests differentiated handling that doesn't exist) and linters (e.g. flake8/pylint) flag the unreachable-by-subset clause. The broad catch-all is intentional here (per-app isolation so one bad seed only warns), so simplify to a plain except Exception: to make the intent explicit.

Drop the redundant PackageError from the tuple.:

except Exception:
    logger.warning("failed to seed bundled app in %s", app_dir, exc_info=True)

Was this helpful? React with 👍 / 👎

Comment on lines +65 to +66
zip_bytes = _build_zip_from_dir(app_dir)
extract_package(zip_bytes, apps_root)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Edge Case: Version-bump re-seed leaves stale files from old version

On a version bump, extract_package is called again for the same app and uses app_dir.mkdir(parents=True, exist_ok=True) then writes the new zip's files on top of the existing directory. Files that existed in the previous bundled version but were removed in the new version are never deleted, so stale assets accumulate in the app directory across version bumps. For the current single-file welcome app this is harmless, but as reference apps grow this can serve dead/obsolete files. Consider clearing the destination directory before extracting a new version (or extracting to a fresh temp dir and swapping) so the on-disk bundle exactly matches the new package.

Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

Note

Your trial team has used its Gitar budget, so automatic reviews are paused. Upgrade now to unlock full capacity. Comment "Gitar review" to trigger a review manually.
Learn more about usage limits

Code Review 👍 Approved with suggestions 0 resolved / 2 findings

Implements bundled app seeding and the reference .taosapp package structure to validate first-party installation flows. Simplify the redundant catch-all exception tuple and ensure stale files are cleaned up during version-bump re-seeding.

💡 Quality: Redundant exception tuple catches everything via Exception

📄 tinyagentos/userspace/seed.py:78-79

except (PackageError, Exception): is redundant — PackageError subclasses Exception, so the tuple is equivalent to except Exception:. Listing PackageError first is misleading (it suggests differentiated handling that doesn't exist) and linters (e.g. flake8/pylint) flag the unreachable-by-subset clause. The broad catch-all is intentional here (per-app isolation so one bad seed only warns), so simplify to a plain except Exception: to make the intent explicit.

Drop the redundant PackageError from the tuple.
except Exception:
    logger.warning("failed to seed bundled app in %s", app_dir, exc_info=True)
💡 Edge Case: Version-bump re-seed leaves stale files from old version

📄 tinyagentos/userspace/seed.py:65-66

On a version bump, extract_package is called again for the same app and uses app_dir.mkdir(parents=True, exist_ok=True) then writes the new zip's files on top of the existing directory. Files that existed in the previous bundled version but were removed in the new version are never deleted, so stale assets accumulate in the app directory across version bumps. For the current single-file welcome app this is harmless, but as reference apps grow this can serve dead/obsolete files. Consider clearing the destination directory before extracting a new version (or extracting to a fresh temp dir and swapping) so the on-disk bundle exactly matches the new package.

🤖 Prompt for agents
Code Review: Implements bundled app seeding and the reference .taosapp package structure to validate first-party installation flows. Simplify the redundant catch-all exception tuple and ensure stale files are cleaned up during version-bump re-seeding.

1. 💡 Quality: Redundant exception tuple catches everything via Exception
   Files: tinyagentos/userspace/seed.py:78-79

   `except (PackageError, Exception):` is redundant — `PackageError` subclasses `Exception`, so the tuple is equivalent to `except Exception:`. Listing `PackageError` first is misleading (it suggests differentiated handling that doesn't exist) and linters (e.g. flake8/pylint) flag the unreachable-by-subset clause. The broad catch-all is intentional here (per-app isolation so one bad seed only warns), so simplify to a plain `except Exception:` to make the intent explicit.

   Fix (Drop the redundant PackageError from the tuple.):
   except Exception:
       logger.warning("failed to seed bundled app in %s", app_dir, exc_info=True)

2. 💡 Edge Case: Version-bump re-seed leaves stale files from old version
   Files: tinyagentos/userspace/seed.py:65-66

   On a version bump, `extract_package` is called again for the same app and uses `app_dir.mkdir(parents=True, exist_ok=True)` then writes the new zip's files on top of the existing directory. Files that existed in the previous bundled version but were removed in the new version are never deleted, so stale assets accumulate in the app directory across version bumps. For the current single-file welcome app this is harmless, but as reference apps grow this can serve dead/obsolete files. Consider clearing the destination directory before extracting a new version (or extracting to a fresh temp dir and swapping) so the on-disk bundle exactly matches the new package.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/userspace/test_seed.py (1)

69-94: ⚡ Quick win

Add a regression test for same-version trust upgrade.

Current coverage checks same-version idempotency, but not the case where an app already exists at that version with non-first-party trust. Add a case to ensure seeding upgrades trust instead of skipping.

Suggested test addition
+@pytest.mark.asyncio
+async def test_seed_upgrades_same_version_to_first_party(tmp_path):
+    seed_dir = tmp_path / "seed"
+    apps_root = tmp_path / "apps"
+    _write_app(seed_dir, "my-app", version="1.0.0")
+    store = await _make_store(tmp_path)
+
+    await store.install(
+        app_id="my-app",
+        name="Test App",
+        version="1.0.0",
+        app_type="web",
+        entry="index.html",
+        icon="",
+        permissions_requested=["app.kv"],
+        trust="community",
+    )
+
+    await seed_bundled_apps(store, apps_root, seed_dir)
+    row = await store.get("my-app")
+    assert row is not None
+    assert row["trust"] == "first-party"
+    await store.close()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/userspace/test_seed.py` around lines 69 - 94, Add a new async test
function to verify that seeding an app at the same version upgrades its trust
level instead of skipping the update. Create a test that first seeds an app with
a non-first-party trust value (such as "third-party"), then calls
seed_bundled_apps again with the same version, and verifies that the trust is
upgraded to "first-party" while the installed_at timestamp remains unchanged.
This test should follow the same pattern as test_seed_idempotent_same_version
but specifically validate the trust upgrade behavior by checking the trust value
before and after the second seed operation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tinyagentos/userspace/seed.py`:
- Around line 61-63: The idempotency check on line 61 only validates that the
version matches but does not verify the trust level of the existing app. Modify
the condition that checks "if existing is not None and existing.get("version")
== version" to also require that the existing entry has first-party trust by
adding an additional check for the trust attribute/field. This ensures that if
an app exists at the same version but with non-first-party trust, the seeding
will not be skipped and the trust will be corrected to the trusted path.

---

Nitpick comments:
In `@tests/userspace/test_seed.py`:
- Around line 69-94: Add a new async test function to verify that seeding an app
at the same version upgrades its trust level instead of skipping the update.
Create a test that first seeds an app with a non-first-party trust value (such
as "third-party"), then calls seed_bundled_apps again with the same version, and
verifies that the trust is upgraded to "first-party" while the installed_at
timestamp remains unchanged. This test should follow the same pattern as
test_seed_idempotent_same_version but specifically validate the trust upgrade
behavior by checking the trust value before and after the second seed operation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0899cc43-eeab-4827-8291-4a198479cbd4

📥 Commits

Reviewing files that changed from the base of the PR and between 02941a6 and 087cb9e.

📒 Files selected for processing (5)
  • tests/userspace/test_seed.py
  • tinyagentos/app.py
  • tinyagentos/userspace/seed.py
  • tinyagentos/userspace/seed/welcome/index.html
  • tinyagentos/userspace/seed/welcome/manifest.yaml

Comment thread tinyagentos/userspace/seed.py Outdated
…stale files (CodeRabbit/gitar #973)

- the skip-if-same-version check now also requires trust=first-party, so a
  community row claiming a seeded id is re-seeded to first-party rather than
  skipped (CodeRabbit Major)
- a re-seed rmtree's the extracted app dir before extracting, so a smaller new
  version cannot inherit stale files from the old one (gitar edge case)
- tests for both
@jaylfc jaylfc merged commit 9e7a028 into dev Jun 17, 2026
7 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in TinyAgentOS Roadmap Jun 17, 2026
@jaylfc jaylfc deleted the feat/userspace-seed branch June 17, 2026 01:43
# Re-seed (new app, version bump, or a non-first-party row claiming
# this id): remove any previously extracted files first so a smaller
# new version cannot inherit stale files from the old one, then extract.
shutil.rmtree(apps_root / app_id, ignore_errors=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Validate app_id before deleting the target bundle directory

app_id comes from the seed manifest and is used directly in shutil.rmtree(apps_root / app_id). A manifest declaring an absolute or traversal id can delete outside apps_root before extract_package has a chance to reject the unsafe path. This also deletes the existing bundle before extraction succeeds, so a bad package can leave the app broken.

Validate/resolve the target path before deletion, and prefer extracting to a temporary directory and swapping only after success.

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

jaylfc added a commit that referenced this pull request Jun 17, 2026
…st+package+seeding all on dev, holding for Jay
@kilo-code-bot

kilo-code-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 0
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
tinyagentos/userspace/seed.py 71 Validate app_id before deleting the target bundle directory; shutil.rmtree(apps_root / app_id) can delete outside apps_root for absolute or traversal manifest ids, and it removes the old bundle before extraction succeeds.
Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

File Line Issue
n/a n/a None
Files Reviewed (5 files)
  • tests/userspace/test_seed.py - 0 issues
  • tinyagentos/app.py - 0 issues
  • tinyagentos/userspace/seed.py - 1 issue
  • tinyagentos/userspace/seed/welcome/index.html - 0 issues
  • tinyagentos/userspace/seed/welcome/manifest.yaml - 0 issues

Fix these issues in Kilo Cloud


Reviewed by nex-n2-pro:free · 1,592,032 tokens

jaylfc added a commit that referenced this pull request Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

1 participant