Skip to content
LACE
  • v0.1 Current
  • Python
  • TypeScript Soon

lace-app-sdk

Testing & proof lanes

Every app ships its own proof harness. lace-app test runs it locally — the same lanes CI runs before sealing a release.

Why proof lanes exist

A builder-generated app reaches publish through the same gate as a hand-written one. The harness (src/lace_app_sdk/testing.py, wired by lace.builder.publisher) is the enforcement point: if a lane fails, the publisher refuses to seal. Running it locally closes the loop before you push.

The lanes

LaneWhat it checksTypical failure
typecheckpyright / mypy across app/ (if configured)Missing import or typed schema drift
buildvite build for ui/ + python -m compileall app/Broken UI bundle or syntax error
route_smokeMounts AppRouteProviders and curls /health/ready + app routesUnmounted route or 500 on GET
toolInvokes each @tool handler with a representative payload via AppToolRegistryDescriptor/handler mismatch or missing tool_modules import
permissionMatrix of RBAC scopes × resource ACLs over routes and toolsPublic route leaking tenant data
app_dataCollections registered, unique constraints hold, full-text indexes exist, GET /notes survives restartListing from an in-memory dict (the harness restarts the sidecar and checks persistence)
isolationBuilder isolation + import-boundary scan (lace.builder.import_boundary)App imports lace.apps.* directly

Running it

terminalbash
lace-app test
# → typecheck (pyright/mypy if configured)
# → build (vite + python -m compileall)
# → route smoke (mount providers, GET /health/ready, GET /apps/<app_id>/api/* 2xx)
# → tool invocation (AppToolRegistry handler, real args, 200 schema)
# → permission lane (RBAC scope × resource ACL matrix)
# → AppData contract (collections exist, constraints hold, search indexes present)

lace-app test --dir ./field_intake --json > proof.json
  • Requires lace-app-sdk[runtime] — base install prints a clear hint and exits non-zero if you forget.
  • Exit code 0 = all lanes passed. Exit code 1 + JSON report = at least one lane failed (use --json to pipe into CI).
  • Run from the app root (where lace_app_manifest.json lives) or pass --dir.

Writing fixtures

Put tests under tests/. The harness discovers tests/conftest.py for shared fixtures (a seeded app_state, a test tenant, a fake blob store). Example patterns live in src/lace_app_sdk/examples/notes_app/ — see tests/test_routes.py for a route smoke that actually hits AppDataService.

CI

lace-app push uploads the repo, CI runs the harness in a throwaway environment, and only a green report seals the image. lace-app deploy (direct path, no CI) still runs the same lanes locally before sealing.

Load-bearing pitfalls the lanes catch

  • In-memory list for GETGET /notes returning [] after a sidecar restart → automatic harness FAIL. Fix: use AppDataService.list_records (see data collections).
  • Missing tool_modules — tool descriptor exists but sidecar registry is empty at invoke time → 404. Fix: add tool_modules: ["app.tools"] to the manifest (tools).
  • Direct lace.* import — breaks the facade invariant and will fail the isolation scan. Fix: import from lace_app_sdk.*.

Next: publishing & releases or CLI reference.