Skip to content

Add OpenAPI imports and live OAuth integrations - #1383

Closed
michielbdejong wants to merge 9 commits into
developfrom
feat/oad-import
Closed

Add OpenAPI imports and live OAuth integrations#1383
michielbdejong wants to merge 9 commits into
developfrom
feat/oad-import

Conversation

@michielbdejong

@michielbdejong michielbdejong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Adds an Integrations section to /app/sync and the atomic-server import-oad CLI command. Clicking GitHub or Google starts OAuth and immediately imports data into the running server after consent, with progress, errors and a link to the imported drive on Sync. API discovery, pagination, ontology derivation and Atomic Data conversion remain in reflector-rs / syncables.

Behavior

  • Discovers integration folders under REFLECTOR_ROOT/spec (currently github and google-calendar, displayed as GitHub and Google). Reads each document and its overlays; provider endpoints, scopes, constants and client-credential environment variable names are declarative.
  • Current upstream auth overlays are bearer-only, so server/integrations/ supplies supplemental OAuth declarations. A folder's own OAuth flow and metadata take precedence. The generic server host handles browser-bound state, PKCE, the callback, access-token expiry/401 refresh and import status.
  • Uses the existing live Db; no second database process or server shutdown is needed for UI imports. Tokens stay in server memory for the import and are never returned to the browser. Pending authorization expires after ten minutes; imports time out after thirty minutes. Reconnecting starts OAuth again; persistent credentials and scheduled sync are not included.
  • Requires signed Atomic agent requests to list/start integrations. Imported record namespaces and drive ownership are scoped to that agent, including account-relative Google calendars such as primary. Callbacks are bound to the initiating browser and single-use. Declined authorization reports a failed job.
  • Preserves percent-encoded resource paths in HTTP GET handling. Decoding %2F before verifying signatures changed both the signed URL and the identity of imported resources, preventing the returned drive link from opening.
  • Retains the CLI workflow for imports with the server stopped, upgrading Reflector to ec3553bf705f2003cab743e4312bb2fee833f11c and adapting to its multi-platform configuration. Both workflows rebuild search indexes after sync, including collected sync errors/partial results.

Setup

Set REFLECTOR_ROOT, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, GOOGLE_CLIENT_ID, and GOOGLE_CLIENT_SECRET when starting AtomicServer. Register http://localhost:9883/integrations/callback for both OAuth applications (use the configured server origin for other deployments). Optional GITHUB_API_CONSTANTS and GOOGLE_CALENDAR_API_CONSTANTS select the source repository/calendar. Buttons indicate missing OAuth setup.

The usage guide covers registration, configuration, token lifetime, partial imports and the CLI. Git dependencies are pinned; the workspace patches Reflector's atomic_lib to this checkout so Storelike/database types agree. Published dependencies are still required before publishing this server version to crates.io.

Validation

Passed:

  • Rust build/check and cargo clippy -p atomic-server --no-default-features --features light --test it (existing warnings outside this change).
  • CLI importer test: local API/overlay, persistence, repeat updates, owner grants, origin mismatch, API error status.
  • Live integration test: real running AtomicServer plus local OAuth/API provider, signed endpoints, PKCE, missing browser cookie, replay rejection, expired access-token refresh, immediate import, drive reads while running, another agent's access denial and cancelled consent.
  • All three existing server_cli tests.
  • Frontend production build (reusing existing WASM), new component lint, changed UI formatting, Rust formatting and diff checks.
  • Manual browser check on an isolated server: Integrations shows GitHub and Google with OAuth-setup-needed buttons.

pnpm typecheck was run and remains blocked by 14 errors in unchanged files: missing Node globals/modules in existing tests, plus two instanceof errors in src/views/Document/upgradeDocument.ts. No errors are reported in the new component. Actual GitHub/Google consent cannot be tested until the operator registers OAuth apps and supplies credentials; the complete protocol/import flow is covered with the local provider.

Related Issues

Closes #1381

Checklist

  • Add changelog entry linking to issue, describe API changes
  • Add or update tests if needed
  • Update docs if needed

Follow-up fixes

  • Build downstream workspaces with the same atomic_lib source as Reflector; verified from a separate consumer workspace.
  • Use Git CLI fetches for pinned CI dependencies.
  • Run live imports outside HTTP workers and bound each provider request to 60 seconds; report failed background tasks instead of leaving them importing.
  • Save completed imports into the private-drive list used by My drives.
  • Verify sorted drive-child queries and stalled-provider errors in regression tests.

Validation: real-server OAuth integration test, provider-deadline unit test, indexed-prefix unit test, separate-workspace cargo check and production UI build pass. The UI typecheck still reports pre-existing errors in unrelated test and document-migration files.

The accompanying Reflector scan optimization is prepared locally (51 tests and strict Clippy pass) and awaits its repository-required human review before the dependency pin can be updated.

@michielbdejong michielbdejong changed the title Add OpenAPI overlay importer backed by Reflector Add OpenAPI imports and live OAuth integrations Sep 7, 2026
@michielbdejong

Copy link
Copy Markdown
Contributor Author
Screenshot 2026-09-07 at 17 28 55

@joepio

joepio commented Sep 8, 2026

Copy link
Copy Markdown
Member

This looks complementary to the work on feat/plugin-model: Reflector/syncables gives us a declarative way to cover many APIs, while that branch adds the integration lifecycle: discovery, connections, reviewed imports, ongoing sync, recovery, and integration actions/events used by automations and the assistant.

I think we should converge on one integration platform with multiple ways to author connectors. A possible path:

  1. Keep the OpenAPI + overlay engine. Reuse its API discovery, pagination and ontology derivation instead of rewriting these in each JavaScript connector. Keep the CLI entry point too.
  2. Unify the product and connection lifecycle. Expose these connectors in the same integration store, with shared authorization, connection status and scheduling. Consolidate the OAuth implementations behind a common host interface rather than keeping separate setup flows on Sync and Integrations. Managed and self-hosted deployments should use the same implementation with different provider-app configuration.
  3. Add an adapter at the storage boundary. For the integrated flow, translate Reflector/syncables records into the common import intents instead of writing directly through AtomicStorage. This should preserve source identity through the existing parent + localId machinery, allow a selected nested destination, and use the same preview, conflict handling and recovery. We should verify how its read/list/delete operations map to that contract; this is more than replacing put.
  4. Separate API schemas from shared domain mappings. Deriving an ontology is useful, but a provider's schema is not automatically our shared task/calendar schema. Allow explicit mappings into reusable Atomic properties and templates, preserving provider-specific fields where needed.
  5. Make execution guarantees explicit. The current Reflector path is trusted Rust in the server; the plugin branch uses sandboxed JavaScript. We should decide which capabilities and limits the declarative engine needs rather than imply that both have the same isolation. Custom JS can remain an escape hatch for transformations and provider-specific behavior. Bidirectional sync still needs explicit conflict, deletion and uncertain-write semantics beyond OpenAPI.

Google Calendar seems like a good first convergence test: use this PR's discovery/pagination, feed it through the common importer, and expose it in the existing integration store. Start with a reviewed one-way import. Test nested placement, repeat imports without duplicates, local edits, partial failures and reconnects before adding scheduled or bidirectional sync.

This does not need to make the whole convergence a prerequisite for landing the engine work. We could separate the reusable engine/CLI and general fixes from the overlapping UI/lifecycle pieces, then land the adapter as a focused follow-up. The important thing is to agree on the common boundary now so we do not end up maintaining two independent integration systems.

claude and others added 2 commits September 8, 2026 07:48
…grations

Reflector's OAD documents and overlays no longer live in its own repo
(moved to localthought/openapi-directory and localthought/overlays,
fetched by Reflector's scripts/fetch-oad.sh into spec/ — see
localthought/reflector-rs#26), so the catalog can no longer infer which
integrations exist by scanning spec/ for folders. discover() now reads
the operator-set OAD_INTEGRATIONS env var (a comma-separated list of
spec/<id> ids) instead; load() itself is unchanged.

Bumps the pinned reflector-rs revision to a299b200 (main, after merging
#25 and #26) and updates the OAD import docs and CLI walkthrough to
match: REFLECTOR_ROOT now needs spec/ fetched via Reflector's own script,
and OAD_INTEGRATIONS set to opt integrations into the Sync page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012bmJGaUn2iAazsUGqaL98N
@michielbdejong

Copy link
Copy Markdown
Contributor Author

A lot has happened since yesterday so I'm restarting this work in the feat/api-plugins branch and will PR it against feat/plugin-model so I can build on that. My new plan:

  • create a fake API integration that just exposes a "pets" data source.
  • see how it looks in the UI and how an LLM can for instance use this data source to store it in a table in a doc.
  • separately build the integration-proxy, with the OAD & overlays ecosystem around it. This is closed-source for now, but I think we should open-source a light-weight version of it and only keep our throttling middleware proprietary. TODO: discuss with @joepio.
  • create the machinery so that any API that is listed on the proxy is available to become a plugin data source

michielbdejong pushed a commit that referenced this pull request Sep 8, 2026
First commit toward rebuilding the API-plugins direction from PR #1383 on
feat/plugin-model. Pets is a static, provider-free demo integration (five
pets, no OAuth, no secrets) that walks every touch point a real API plugin
needs: a code-first ontology, an import mapping into the shared sandbox, a
reproducible plugin.js bundle, an installable connection with a table and
view, integration discovery in the UI, and a server-side sandbox test.

See planning/api-plugins.md for the direction, including the next step:
discovering providers through a localthought proxy instead of hand-writing
integrations/<name>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkL48idFqkuYw8n7KdH6Dj
@michielbdejong

Copy link
Copy Markdown
Contributor Author

Continued in #1387

michielbdejong added a commit that referenced this pull request Sep 11, 2026
* feat(integrations): add trivial demo Pets plugin

First commit toward rebuilding the API-plugins direction from PR #1383 on
feat/plugin-model. Pets is a static, provider-free demo integration (five
pets, no OAuth, no secrets) that walks every touch point a real API plugin
needs: a code-first ontology, an import mapping into the shared sandbox, a
reproducible plugin.js bundle, an installable connection with a table and
view, integration discovery in the UI, and a server-side sandbox test.

See planning/api-plugins.md for the direction, including the next step:
discovering providers through a localthought proxy instead of hand-writing
integrations/<name>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkL48idFqkuYw8n7KdH6Dj

* fix: discover local Ollama and restore feedback hover (#1389)

* fix: subscribe to live collaborator profile updates (#1390)

* Allow recovery-code secret reveal and additional passkey enrollment

* Security audit: fix sync, authentication and rights gaps, bump advisories (#1384)

* docs(planning): security and code-quality audit, September 2026

Consolidated findings for server, lib, browser, desktop, CLI and CI:
unauthenticated /iroh-sync, SYNC_PUSH without per-entry authorization,
agent impersonation via /agents/{key} URLs on arbitrary hosts, committed
API keys, forwarded-host trust, plus dependency advisories and a list of
sloppy code with file:line references.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tcSpZhjA3SgzcEfRdUr67

* fix(security): close the sync, authentication and rights gaps from the September audit

Findings from planning/security-audit-2026-09.md fixed on this branch:

- Remove the committed editor config carrying live API keys, plus the
  tracked pnpm-store and vitest cache files; ignore them.
- /iroh-sync requires a signed agent with write on the drive (or the
  policy's leave to bring a new drive here) and only answers POST. Dial-
  side owner trust is confined to the drive that was dialed for.
- SYNC_PUSH checks every entry against the admitted drive: an existing
  resource must carry it as its stored drive stamp, a new one must
  resolve to it via its parent or its own stamp.
- Legacy https://host/agents/{key} subjects bind the key in the path to
  the signing key in both header auth and commit validation; agent
  subjects on other hosts are refused instead of fetched.
- check_append no longer falls back to the new resource's own write
  array, and a parentless did:ad:agent genesis is accepted only from
  that key or the node's own agent. Regression tests for both.
- Request origin is taken from Host/X-Forwarded-* only for the
  configured domain, its tenants or loopback; the WebSocket handler uses
  the same origin. Unit tests.
- /forget-peer requires write on a drive the peer was paired for (known
  peers now record those) or the node's own agent.
- Desktop and Android bind the embedded server to 127.0.0.1 by default.
- The data-browser accepts a node-reported portalUrl only as https (or
  http on localhost), pins the portal a device token was issued by, and
  routes every portal navigation through safePortalUrl. URI values and
  downloadUrl reject javascript:/data:/vbscript: schemes.
- BLOB_RESPONSE bytes must hash to the requested key; downloadUrl is
  escaped in the SPA meta tags; the plugin-ui query string is attribute-
  escaped; multipart uploads and search limits are bounded; LanceDB
  filter literals are quoted; origin() no longer panics; image rendition
  parameters are quantized before caching; config.toml is written 0600;
  plugin RPC ignores foreign message sources; the session cookie is
  Secure on https.
- Sloppy code: ValueComp comma-case, unawaited resource.save() calls, the
  CLI agent race, a commented-out tool block, the duplicate EventManager,
  SIGNER set twice, bitwise & on bools, the empty authorization test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tcSpZhjA3SgzcEfRdUr67

* fix(server): treat *.localhost names as loopback in the request-origin check

CI serves the app at atomic.localhost for a server whose domain is
`atomic`, so the browser signs its WebSocket AUTH and requests for that
origin. The host allowlist added for the forwarded-host fix rejected it,
fell back to the configured origin, and every signed request failed
("requestedSubject ... does not name this server"). Names under
.localhost resolve to loopback by definition (RFC 6761), so accept them
like localhost itself.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tcSpZhjA3SgzcEfRdUr67

* test(browser): mock setManagedDeviceToken in the managed session test

logoutManagedSession now also drops the device token, so the api mock
from develop's new session test needs that export too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tcSpZhjA3SgzcEfRdUr67

* fix(security): bind Iroh AUTH to the responder, close the remaining rights gaps, bump advisories

Second round of fixes from planning/security-audit-2026-09.md:

- Iroh AUTH: the dialer signs `drive#<responder node id>` and the accept
  side verifies its own node id as the connection's challenge, so a proof
  captured by one responder cannot open another. A proof without a node
  id is honoured only from a peer this node paired with.
- check_append: a parentless non-agent DID is allowed only when it has no
  parent at all, or when its drive stamp names a drive the agent may
  append to; a non-DID top-level Drive needs the node's own agent.
- Path-only auth signatures are no longer accepted; the signer's Agent
  resource is created only after the commit is accepted; genesis certs
  with a parent or drive must match the document; did: resources are
  indexed only into their own drive's watched queries; Agent and
  SharedConfig redact secrets in Debug.
- Plugin zips and bookmark bodies are fetched through the SSRF guard
  with size caps; EPHEMERAL Loro payloads are relayed only from a
  subscriber; the ACME flow returns errors instead of panicking and a
  daily task renews the certificate on disk; /plugin-list and plugin UI
  files are read as the calling agent (the data-browser signs the list
  request) and the plugin parameter is validated; default_service logs
  at debug.
- The Flutter bridge deletes the database only on a corruption error;
  the atomic-saas checkout no longer persists its PAT; release.yml has
  per-job permissions; notarization secrets go through env; the desktop
  devtools feature is opt-in.
- Dependencies: wasmtime 47.0.4, h2 0.4.19, quinn-proto, rustls-webpki
  0.103.13; @tiptap/* 3.30, @modelcontextprotocol/sdk 1.30 and the
  transitive build tools, after which pnpm audit reports nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tcSpZhjA3SgzcEfRdUr67

* docs: changelog entry for the security audit fixes

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tcSpZhjA3SgzcEfRdUr67

* test(lib): upload integration tests create the agent's private drive

The fixture fell back to the server's root URL as the upload parent when
the config carried no initialDrive. A fresh server has no resource at
that URL since the key-derived drive became the default, and the
tightened check_append now refuses a child whose parent is not on the
server, so the roundtrip test failed with "Parent ... not found".
Materialize the private drive through Store.ensurePrivateDrive, the
same path the data-browser takes at sign-in, and upload into that.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tcSpZhjA3SgzcEfRdUr67

* Fix desktop workspace discovery and restore feedback

* style(browser): fix the lint errors develop gained in #1389 and #1390

`pnpm lint` failed on develop's own head after those merges: missing
blank lines in websockets.ts, LocalOllamaDiscovery.tsx and the two new
e2e specs, and three shadowed names in username-live.spec.ts. The CI
pipeline stops at the first failing package, which hid the rest.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tcSpZhjA3SgzcEfRdUr67

* test(data-browser): give the whole-app compiler test a 60 second budget

It transforms every TSX file in the app: about two seconds on a
workstation and on the self-hosted runner, nine on a two-core hosted
runner, where vitest's default five-second budget failed it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tcSpZhjA3SgzcEfRdUr67

* revert(lib): drop the drive-scoping of DID resources in watched queries

For a `did:` query, `QueryFilter.drive` is `drive_prefix_from_subject`
of the queried subject, which is that subject itself rather than the
drive root. Comparing it with a resource's `drive` stamp excluded every
legitimate row, and the query_aggregates tests failed on the first CI
run that reached them. Back to develop's index; C17 moves to the
report's not-fixed list with what a correct fix needs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tcSpZhjA3SgzcEfRdUr67

* test(lib): drop the drive-scoping test that went with the reverted C17 change

It asserted the watched-query index behaviour the previous commit
withdrew, so it failed on the first CI run after the revert.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tcSpZhjA3SgzcEfRdUr67

* fix(lib): do not steal our own OPFS lock during a slow cold start

Ported from #1386. When the leader election window (2 s) elapses while
this tab already holds the lock and its worker is still importing WASM
and opening OPFS, the election treated the silence as a ghost leader,
logged the "stealing OPFS lock" warning and re-requested the lock with
steal. On a loaded CI runner every fresh page hit that path, and the
browser-diagnostics gate from #1382 fails a spec on any warning, so the
light e2e suite was red on develop and on this branch. Skip the steal
when our own worker already exists; the settle wait below still covers
the genuine ghost case. Adds #1386's unit test for the cold start.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tcSpZhjA3SgzcEfRdUr67

* fix(data-browser): skip the SaaS logout call on a FOSS server

Ported from #1386. Signing out posted /api/logout to whatever
getManagedApiBase resolved to, and on a self-hosted node that is the
node's own origin, which answers 405. The browser logs that as an
error, and the diagnostics gate from #1382 fails the sign-out smoke
spec on it (the only failure left on this branch's hosted run).
logoutManagedSession now returns early when no control plane is known:
no linked portal, no build-time API base, no remembered portal and no
compiled-in portal URL. The local device token is still dropped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tcSpZhjA3SgzcEfRdUr67

* chore(browser): dedupe prosemirror-model and prosemirror-view

The @tiptap bumps in this PR pulled prosemirror-model 1.25.11 in next
to the 1.25.7 that @tiptap/pm still resolved, and the two copies made
`pnpm typecheck` in data-browser fail on incompatible Node types in the
RTE chunks. `pnpm dedupe prosemirror-model prosemirror-view` collapses
them (and, as dedupe does, other duplicates to versions the lockfile
already carried: vite 8.2.2, codemirror state and lint, picomatch).
Typecheck, lint, the data-browser unit tests and the vite build pass;
a frozen offline install is consistent.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tcSpZhjA3SgzcEfRdUr67

* Fix formatting and lint checks for desktop restore PR

* Validate sync entries before persistence and bind discovery authentication

---------

Co-authored-by: Claude <noreply@anthropic.com>

* Fix per-drive hosting status, metadata and collaboration UX (#1386)

* Cancel automatic Vault work when the account session ends

* Improve collaboration profiles and managed subscription UX

* Fix nodeless profile loading and cold database startup warnings

* Settle lint and formatting after develop integration

* Allow cold CI time for whole-app compiler regression check

* Scope Cloud Server hosting and sync status to the selected drive

* Preserve drive display metadata and scope managed billing requests

* Prepare 0.41.0-beta.6 release (#1393)

* Prepare 0.41.0-beta.6 versions and release gates

* Record full beta.6 E2E results and release blockers

* Fix release E2E regressions in sync and plugin loading

* Finalize beta.6 release notes after passing E2E

* test(integrations): cover Pets setup flow

* fix(server): rustfmt pets_tests.rs

CI's fmt check failed on the multi-line assert! in pets_tests.rs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkL48idFqkuYw8n7KdH6Dj

* ci: run Pets browser journey on its branch

* fix(ci): mount integration sources for browser build

* fix(ci): alias /browser for integrations' tsconfig extends in jsBuild()

Mounting /integrations (previous commit) fixed the UNRESOLVED_IMPORT
errors, but every integrations/*/tsconfig.json extends the repo-root-
relative ../../browser/tsconfig.build.json, which doesn't resolve
inside the container: browser mounts at /app, not /browser. jsTest()
and integrationCertificationReport() already hit this and fix it with
`ln -s /app /browser`; apply the same fix to jsBuild(), which backs the
production vite build, e2e and netlify-preview pipelines.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkL48idFqkuYw8n7KdH6Dj

* fix(i18n): settle translation catalogs for the Pets integration card

The e2e run showed the Pets card rendering with a blank heading and
description text: wuchale renders an untranslated string as empty
rather than falling back to source text, and these new literal strings
(IntegrationDiscovery.tsx's bundledIntegrations() entry) were never
extracted into the catalogs. Settled by running the app, per AGENTS.md's
translation-catalog guidance (matches the dev server's fixed point,
unlike the standalone `wuchale --clean` extractor).

ConnectPets.tsx's own strings (only reachable once the setup dialog is
opened against a real drive) still need the same treatment in a
follow-up commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkL48idFqkuYw8n7KdH6Dj

* fix(i18n): settle translation catalogs for ConnectPets.tsx

Same issue as the previous commit, for the strings only reachable once
the Pets setup dialog is actually opened (Install/Reinstall demo pets,
the description paragraph, Open Pets). Settled by running the app
against a real dev-drive session and opening the dialog, so this
matches the dev server's fixed point exactly (verified: purely
additive, 20 lines per locale, no reordering).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkL48idFqkuYw8n7KdH6Dj

* fix(pets): restore production labels and display pet names

* fix(e2e): give the Pets install-and-apply assertion room to actually finish

Root-caused the CI failure at "Apply 5 changes": not a hang, not a
missing translation, and not an artifact of a locally-underbuilt
server (ruled that out by rebuilding atomic-server with the real
wasm32-wasip2 plugin runtime and rerunning). Installing genuinely
takes ~24s — pluginClassesFor's six sequential lookups, two
ensureSchema calls, three ensureInstallationResource calls, then the
sandboxed plugin run itself — measured locally on a fresh, otherwise
idle server via the real `playwright test`, not a synthetic script.
The dialog's default 10s expect timeout is tuned for interaction
latency, not this one-time setup cost. Same targeted-timeout pattern
already used elsewhere in this file (vault-backup-restore.spec.ts,
onboarding.spec.ts, etc.).

Verified: `playwright test -g "Pets installs its demo table"` passes
end to end (~47s) against a real dev-drive session with the actual
embedded plugin runtime.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkL48idFqkuYw8n7KdH6Dj

* fix(ci): install wasm32-wasip2 before building the e2e server binary

The Pets E2E job consistently failed at "Apply 5 changes" never
enabling, even with a generous timeout. The server log in the job
output shows why: `/plugin-run` 500s with "this server was built
without the plugin runtime... Rebuild with the wasm32-wasip2 target
installed."

rustBuild()'s e2e path enables the `wasm-plugins` cargo feature, whose
build.rs compiles atomic-plugin-runtime for wasm32-wasip2 as a nested
cargo build. Without that target's std lib installed, the nested build
fails and build.rs treats it as an optional degradation — it embeds an
empty runtime instead of failing the outer build, so the gap is silent
until something actually calls /plugin-run. rustTest() already adds
this target for the identical reason; rustBuild() (which builds the
binary the e2e Playwright suite actually talks to) never did.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkL48idFqkuYw8n7KdH6Dj

* fix(e2e): type plugin test fixtures to unblock CI

* fix(ci): mount testdata/ and lib/defaults/tasks.json for lib tests

jsBuild()/jsTest() only mounted testdata/pairing-request.json as a
single file and never mounted lib/defaults/tasks.json outside the
Vite-only /app/lib-defaults alias. Newly added
browser/lib/src/plugin-manifest.test.ts,
plugin-plan.fixtures.test.ts, and task-schema.test.ts read these
repo-root fixtures directly via readFileSync/readdirSync (not
Vite-resolved), so `vitest run` failed with ENOENT for all three in
CI while passing locally against a full checkout.

plugin-plan.fixtures.test.ts also readdirSync()s the whole
testdata/plugin-plans/ directory, which a single-file mount can't
provide, so testdata/ is now mounted whole (it isn't an OS path, so
unlike /lib there's no collision risk). lib/defaults/tasks.json stays
a single-file mount, matching the existing genesis_test_vectors.json
precedent, to avoid touching /lib itself.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkL48idFqkuYw8n7KdH6Dj

* fix(ci): bump rust-target cache volume to unstick the plugin runtime

The wasm32-wasip2 fix (20368ff) didn't take: the Pets E2E run on
that exact commit still failed with the identical "this server was
built without the plugin runtime" 500. build.rs's build_plugin_runtime()
only declares `rerun-if-changed` on plugin-runtime/{src,wit} and the
ATOMICSERVER_SKIP_PLUGIN_RUNTIME env var — nothing tells cargo that
"the wasm32-wasip2 target just became installed" should invalidate its
build-script fingerprint. Every prior CI run had already cached an
empty embedded runtime (from before that target existed) in the
rust-target-v3 volume, so cargo kept trusting that stale fingerprint
and never re-ran the nested wasm32-wasip2 build to notice the target
was now there.

This is the same class of staleness bug TOUCH_WORKSPACE_SOURCES
already documents fixing twice before (the -v2 and -v3 renames on this
same volume, for the same "cached artifact looks newer than fresh
sources" reason) — same fix, one more rename to force a clean
re-evaluation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkL48idFqkuYw8n7KdH6Dj

* fix(ci): isolate the WASI plugin build from musl compiler settings

* fix(data-browser): give the full-app oxc-transform sweep real headroom

src/oxc-react-compiler.test.ts's "transforms every app TSX/JSX file
without a fatal error" test compiles every TSX/JSX file under src/ one
at a time in a single it() block, with vitest's default 5000ms budget.
That budget was already tight and now consistently times out in CI
(reproduced twice in a row on the same commit, ~38s total suite
duration but this one test alone exceeding 5s) — this is deterministic
under CI load, not a flake, and the file count only grows over time.
Give it its own 20s timeout; the assertions are unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkL48idFqkuYw8n7KdH6Dj

* fix(data-browser): fix oxfmt formatting from the previous timeout fix

My previous commit's manual reformatting for the it() timeout argument
didn't match oxfmt's expected style, which failed the format-check
lint step. Move the explanatory comment onto a named constant instead
of a trailing comment after the test body, which oxfmt formats cleanly
and reads better besides. Verified with oxfmt --check, oxlint, and a
local vitest run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkL48idFqkuYw8n7KdH6Dj

* Revert "fix(data-browser): fix oxfmt formatting from the previous timeout fix"

This reverts commit 740d603.

* Revert "fix(data-browser): give the full-app oxc-transform sweep real headroom"

This reverts commit d59d4d2.

* Add LocalThought catalog plugins with typed paginated imports

* Record successful live GitHub import verification

* Add bounded Calendar event imports through Syncables

* Record successful scoped Calendar live import

* Fix LocalThought lint and retain upstream preview timing checks

* Fix shared Cargo cache locking across CI containers

* Remove temporary Pets E2E workflow

* Allow the full-app compiler test to finish on hosted CI

* Run LocalThought integrations entirely in the browser

* Run focused Pets E2E on the browser integration branch

* Add browser Devonian issue tracker sync demo

* Use browser tenant connections and test two-way issue sync

* Show one-way Google Calendar imports in a calendar view

* Verify browser-only Calendar sync through shared mock proxy

* API plugins: LocalThought connections and typed Syncables imports (#1394)

* Add LocalThought catalog plugins with typed paginated imports

* Record successful live GitHub import verification

* Add bounded Calendar event imports through Syncables

* Record successful scoped Calendar live import

* Fix LocalThought lint and retain upstream preview timing checks

* Fix shared Cargo cache locking across CI containers

* Allow the full-app compiler test to finish on hosted CI

* Queue main CI runs instead of replacing pending validation

* Run LocalThought integrations entirely in the browser (#1401)

* Run LocalThought integrations entirely in the browser

* Run focused Pets E2E on the browser integration branch

* Add browser Devonian issue tracker sync demo (#1399)

* Add browser Devonian issue tracker sync demo

* Use browser tenant connections and test two-way issue sync

* Record live proxy OAuth verification and repository access blocker

* Run one-way Google Calendar sync entirely in the browser (#1398)

* Show one-way Google Calendar imports in a calendar view

* Verify browser-only Calendar sync through shared mock proxy

---------

Co-authored-by: Michiel de Jong <michielbdejong@ontola.io>

---------

Co-authored-by: Michiel de Jong <michielbdejong@ontola.io>

---------

Co-authored-by: Michiel de Jong <michielbdejong@ontola.io>

---------

Co-authored-by: Michiel de Jong <michielbdejong@ontola.io>

* Allow reconnecting Devonian trackers and record live write blocker

* Preserve and render exclusive date-only all-day event ranges

* Recognize namespaced all-day properties in installed Google calendars

* Document all-day validation and remaining Google Calendar import gaps

* Document successful live browser issue sync verification

* Add reviewed two-way Google Calendar event edits (#1408)

Co-authored-by: Michiel de Jong <michielbdejong@ontola.io>

* Support recurring Google Calendar meetings and exceptions (#1409)

* Preserve and render exclusive date-only all-day event ranges

* Import and render recurring calendar series with exceptions and timezone support

---------

Co-authored-by: Michiel de Jong <michielbdejong@ontola.io>

* Keep local integration resource reads off the server (#1407)

* Fix local integration reads reaching the server

* Fix integration formatting errors blocking CI

---------

Co-authored-by: Michiel de Jong <michielbdejong@ontola.io>

* Connect LocalThought platforms without pasting tenant secrets (#1417)

* Connect LocalThought integrations without pasting a tenant secret

* Satisfy demo callback statement-spacing lint

* Apply Rust formatting required by CI

* Format calendar recurrence library for workspace CI

* Clarify automated coverage and live verification records

* Lint the plugin feature set used by CI tests and deployment

* Fix Node MCP loading and complete plugin CI feature coverage

* Declare certification compiler and report failed checks

* Align compiler dependency with workspace override

---------

Co-authored-by: Michiel de Jong <michielbdejong@ontola.io>

* Externalize platform integrations and extend metadata-driven imports (#1419)

* Load Clockify and Notion integrations from Devonian

* Load OAuth token endpoint from trusted metadata

* Remove server tests for extracted integration bundles

* Restore integration regression tests during migration

* Follow OpenAPI links across sync collections

* Describe only root sync parameters

* Validate full Moneybird traversal fixture

* Consume platform lenses from Devonian

* Make integration OAuth provider-driven

* Keep import budgets in consumer and preserve mixed record fields

* Pin generated OAuth provider metadata

* Apply catalog import selections explicitly

* Handle declared absent objects and share link response context

* Document generic platform traversal regression coverage

* Extract GitHub and Calendar platform code

* Verify explicit selection removes inherited query defaults

* Validate per-item link selection and query removals

* Update Calendar import coverage ownership

* Load platform UI from Devonian registry

* Restore neutral external action test fixture

* Run external platform tests against pinned package

* Neutralize embedded platform host fixtures

* Document pinned external platform regression coverage

* Run external platform regressions in JS CI

* Render platform UI from external metadata

---------

Co-authored-by: Michiel de Jong <michielbdejong@ontola.io>

* Preserve bundled plugins and add configurable integration proxy settings

* Fix integration test root, local reads, and reconnect isolation

* Fix regression test statement spacing

* Make integration certification independent of CI mount paths

* Split general CI hardening out of the integration feature

* Restore required shared test fixture mounts in CI

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Joep Meindertsma <joep@ontola.io>
Co-authored-by: Michiel de Jong <michielbdejong@ontola.io>
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.

OAD powered Importer workflow

3 participants