# SuperRepo local preview: 12 gaps hit while building a function-backed write path against an imported ontology

**URL:** <https://community.palantir.com/t/superrepo-local-preview-12-gaps-hit-while-building-a-function-backed-write-path-against-an-imported-ontology/7271>\
**Category:** Product Feedback\
**Tags:** osdk, superrepo\
**Created:** [September 24, 2026, 11:37am UTC](https://community.palantir.com/t/superrepo-local-preview-12-gaps-hit-while-building-a-function-backed-write-path-against-an-imported-ontology/7271 "2026-09-24T11:37:43Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![cf582ce1ccb5a71d27c8](https://yyz1.discourse-cdn.com/palantir/user_avatar/community.palantir.com/cf582ce1ccb5a71d27c8/32/6099_2.png) [@cf582ce1ccb5a71d27c8](https://community.palantir.com/u/cf582ce1ccb5a71d27c8)\
**Post date:** [September 24, 2026, 11:37am UTC](https://community.palantir.com/t/superrepo-local-preview-12-gaps-hit-while-building-a-function-backed-write-path-against-an-imported-ontology/7271/1 "2026-09-24T11:37:43Z")

</div>

We are building an ingest app on SuperRepo (beta): a user tags the entities a source document mentions (customers, devices, sites — types imported read-only from an Ontology Manager ontology of ~95 types) and one function-backed action commits creates, updates and links across about ten imported types plus three code-born ones (document, provenance row, receipt). Everything below was found on the **\*\*local preview\*\*** ; several questions ask what holds when deployed. All but one gap have a workaround (G11 is an accepted degradation); none blocked us. The underscore link-id failure we posted earlier is fixed in `@osdk/maker` 0.67.0 — thank you. Every name below is a stand-in.

**## Environment**

- foundry-cli 0.253.0 (local ontology server `lohi-lib` 0.635.0, platform-api-proxy) · macOS 27.0 arm64 · Node 25.9.0 (`.nvmrc` pins 24) · pnpm 11.12.0

- `@osdk/maker` 0.67.0 · `@osdk/maker-experimental` 0.61.0 · `@osdk/maker-import` 0.37.0 · `@osdk/generator-converters.preview` 0.45.0 · `@osdk/generator-converters.ontologyir` 2.67.0 · `@osdk/generator` 2.67.0 · `@osdk/seed-helpers` 0.27.0

- `@osdk/client` / `@osdk/api` 2.57.0 (workspace override; maker 0.67.0 declares `@osdk/api ~2.67.0` but builds against it) · `@osdk/functions` 1.20.0 · `@foundry/functions-typescript-runtime` 0.299.0 · `@foundry/functions-typescript-osdk-discovery` 1.35.0

- `@osdk/vite-plugin-superrepo` 0.10.0 (dev smart client)

- Imports via `foundry import ontology`; new types code-born under `com.example.app.*`.

**## Summary**

| # | Component (version) | Symptom | Local workaround |

|—|—|—|—|

| G1 | `@osdk/maker-experimental` 0.61.0 `api/defineOntologyV2.js:37` | Link with an imported end: imported side gets a temp RID → `generate-sdk: Unable to find entity` | `void importedType.apiName` at module top level |

| G2 | preview converter + local ontology server 0.635.0 | Once built, a link with an imported end is not served (M2M and 1:N); for M2M, no reverse side in the OSDK and writes get `400` | Code-born join object + property filters |

| G3 | maker-experimental `cli/generateBackingDataset.js:311` | `includeEmptyBackingDatasource` on such a link: `Could not find object types for link` | Omit the flag (leaves an unbound dataset input) |

| G4 | maker-experimental `cli/main.js:126` | Block-data function discovery sees no imported types → `unsupportedType` → function-backed action cannot register | Code-born-only declared return type + cast |

| G5 | functions runtime 0.299.0 `PropertyTypeConverter` ↔ local `metadata.json` | Array and geopoint edits fail on every type | Omit locally behind a positive preview check; report the omission |

| G9 | functions runtime ↔ local ontology server | Timestamp edits rejected (`Ontologies:InvalidPropertyValue`) | Same guard, by property name |

| G6 | local ontology server | Non-atomic apply; duplicate PK → `500 Default:Internal` | Validation-first commit; receipt is the last edit |

| G10 | local ontology server | A failed persist can answer `200` with zero edits | App confirms success by reading the receipt back |

| G7 | `@osdk/vite-plugin-superrepo` 0.10.0 `smartClient.transformResponseFromLocal` | Query `UserFacingError` text lost; struct results still wire-wrapped | Errors as data; JSON-string returns |

| G8 | local ontology server | Same-type M2M links stored side-swapped; intermediary links are plain tables | Avoid directional self-links; write relationship rows |

| G11 | functions runtime / `@osdk/functions` 1.20.0 | No calling-user identity reaches a function locally (live route not tested) | None (no user attribution on the function’s writes) |

| G12 | platform-api-proxy + local ontology server | Media sets cannot be written locally (`501` / `404` / `400`); attachments work | Attachment property |

**## 1. Links with an imported end (G1–G3)**

```ts

import { customer } from “#generated-imports”;

defineLink({ apiName: “documentCustomer”,

many: { object: IngestDocument, metadata: { apiName: “customers” } },

toMany: { object: customer, metadata: { apiName: “documents” } },

editsEnabled: true, includeEmptyBackingDatasource: true });

```

**### G1 — imported link ends get temporary RIDs**

**\*\*Observed:\*\*** `generate-sdk … ERROR Unable to find entity`; the IR names the imported side `ri.ontology-metadata.temp.object-type.`. `OntologyRidGeneratorImpl` snapshots `getImportedTypes()` at construction (`defineOntologyV2.js:37`); import stubs are lazy proxies registered on first property access, which `convertLink` triggers too late.

**\*\*Expected:\*\*** the stub resolves to its `ridHint`.

**\*\*Workaround:\*\*** `void customer.apiName;` at module top level.

**\*\*Question:\*\*** is eager stub registration or a lazy RID lookup an acceptable fix, and is the top-level `void` safe in deployed block data (it seems only to change registration order)?

**### G2 — the built link is not served locally**

**\*\*Repro:\*\*** G1 workaround applied; start the preview.

**\*\*Observed:\*\***

```

GET objectTypes/com.example.app.ingestDocument/outgoingLinkTypes → 200 {“data”:[]}

GET objectTypes/customer/outgoingLinkTypes/documents → 404 Ontologies:LinkTypeNotFound

POST actions/…/apply (batch.link(doc, “customers”, customer))

→ 400 {“errorName”:“Default:InvalidArgument”,“parameters”:{“linkTypeRid”:“”,“objectType”:“com.example.app.ingestDocument”}}

```

A 1:N with an imported `one` side builds but is likewise not served (`404 LinkTypeNotFound` on the imported side; write not attempted). Imported↔imported and code-born↔code-born links work.

**\*\*Expected:\*\*** both sides served, `batch.link` accepted.

**\*\*Workaround:\*\*** a code-born join object `documentLink { id, documentId, targetType, targetPk }`, linked 1:N to `ingestDocument`, queried by `where({ targetType, targetPk })` plus a pivot.

**\*\*Question:\*\*** are links with an imported end meant to work locally? When deployed?

**### G3 — `includeEmptyBackingDatasource` on that link fails the build**

**\*\*Observed:\*\*** `Error: Could not find object types for link “documentCustomer”` — `generateBackingDataset.js:311` searches only code-born `ontologyIr.ontology.objectTypes`.

**\*\*Expected:\*\*** the flag binds the link’s datasource as for code-born object types.

**\*\*Workaround:\*\*** omit it; the datasource becomes an unbound block input (`inputs[“dataset-datasource-link.documentCustomer”]`) an install must map.

**\*\*Question:\*\*** should the flag be legal here, and what does the unbound input do at install?

**## 2. Function discovery ignores imports (G4)**

**\*\*Repro:\*\*** `commitIngest` declares its honest edit union (`Edits.Object | Edits.Link\<customer, “device_owner_2”\> | Edits.Object`) and a `defineFunctionBackedAction` names it.

**\*\*Observed:\*\*** `{“code”:“unsupportedType”,“message”:“\“Edits.Object\” is not a supported type.”,“severity”:“error”}`, then `ERROR Rebuild failed: Invariant failed: Function “commitIngest” not found in functions IR`; the whole ontology build fails and the previous one keeps serving. `cli/main.js:126` calls `getPreviewFullMetadataFromBlockData(blockDataJson)` without the import JSON; the SDK-stage pass, which gets imports, accepts the same function.

**\*\*Expected:\*\*** both passes see the same entities.

**\*\*Workaround:\*\*** declare code-born edits only and cast:

```ts

type Declared = Edits.Object; // batch is still createEditBatch(client)

return batch.getEdits() as unknown as Declared[];

```

The spec then lists `editedObjects: {“com.example.app.ingestReceipt”: {}}` only, yet the local server applies the `customer` edits — provenance is not enforced locally.

**\*\*Question:\*\*** does deployed Actions reject edits outside the declared provenance? If so this cast is a deploy-time trap and the import JSON at `main.js:126` is the real fix.

**## 3. Property types the local runtime cannot write (G5, G9)**

**### G5 — arrays and geopoints, on any type**

**\*\*Repro:\*\*** `batch.create(customer, { …, aliases: [“a”, “b”] })`; `batch.update(site, { location: { type: “Point”, coordinates: [10.0, 20.0] } })`.

**\*\*Observed:\*\***

```

aliases → 400 Ontologies:ApplyActionFailed {“kind”:“runtimeError”,“message”:“Cannot read properties of undefined (reading ‘itemType’)”} PropertyTypeConverter.js:176

location → 400 Ontologies:ApplyActionFailed {“kind”:“runtimeError”,“message”:“Unexpected property type for ontology edits”} PropertyTypeConverter.js:250

```

The runtime’s `PropertyTypeConverter` expects OMS shapes (`array.itemType`, `geohash`); the build’s `.dev-server/var/conf/metadata.json` carries API-v2 shapes (`array.subType`, `geopoint`), consumed unconverted. Empty arrays pass (the per-item map callback never runs), so the fault is item-type conversion. Same under preview converter 0.31.0, so not a regression. Arrays and geopoints seed and read back fine, and arrays filter with `$contains`; only function writes fail.

**\*\*Expected:\*\*** function edits accept what the seed accepts.

**\*\*Workaround:\*\*** none without patching; our wrapper omits array/geopoint values **\*\*only\*\*** after positively detecting the preview (`GET /api/v2/ontologies` lists exactly one ontology, `ri.ontology.main.ontology.0`) and reports the omission. Any doubt ⇒ write everything.

**\*\*Questions:\*\*** does the deployed runtime get OMS-shaped metadata, making this local-only? Is a shim planned? Is there a supported way to detect the preview from inside a function?

**### G9 — timestamps**

**\*\*Repro:\*\*** a function edit setting a code-born `timestamp` property from an ISO-8601 string, e.g. `batch.create(ingestReceipt, { …, committedAt: new Date().toISOString() })`. For comparison, the seed validator (`@osdk/seed-helpers` 0.27.0) requires `timestamp` values as RFC 3339.

**\*\*Observed:\*\*** the runtime emits epoch millis; the local server accepts only ISO-8601 and rejects the edit as `Ontologies:InvalidPropertyValue` (value shown as `U64(…)`).

**\*\*Expected:\*\*** one agreed wire shape.

**\*\*Workaround:\*\*** the same guard, by property name.

**\*\*Question:\*\*** which shape does the deployed runtime/OMS pair use for timestamp edits — epoch millis or ISO-8601 — and will the local server accept what the runtime emits? Is there a supported way to write a timestamp from a local function today?

**## 4. Apply semantics on the local server (G6, G10)**

**### G6 — non-atomic apply; duplicate PK is a 500**

**\*\*Repro:\*\*** one batch creates the `ingestReceipt`, then a `customer` with an existing PK.

**\*\*Observed:\*\***

```

POST actions/com.example.app.commit-ingest-action/apply

→ 500 {“errorName”:“Default:Internal”,“parameters”:{“objectType”:“customer”,“primaryKey”:“00000000-0000-4000-8000-000000000001”}}

server log: cause “Cannot add object that already exists in the store”

GET objects/com.example.app.ingestReceipt/ → 200 ← the first edit persisted

```

Edits apply in array order and stop mid-batch without rollback. The template’s notes say a plain create action returns `Ontologies:ObjectAlreadyExists` here; we did not re-verify that, but a function-backed action does not.

**\*\*Expected:\*\*** all-or-nothing, as documented for deployed actions, and a typed duplicate-PK error.

**\*\*Workaround:\*\*** all reads and validation (idempotency, natural-key collisions via `$in`) before the first edit; the receipt is the **\*\*last\*\*** edit, so a partial apply leaves none and a retry re-plans.

**\*\*Question:\*\*** are deployed function-backed actions editing imported types transactional? Could the local server roll back and return `ObjectAlreadyExists`?

**### G10 — HTTP 200 with zero edits after a failed persist**

- Observed during development: an apply whose edits did not persist answered `200` with an empty edit list, so locally a 200 is not proof of a write. We have not isolated a minimal repro to share.

**\*\*Expected:\*\*** a non-2xx status when persistence fails.

**\*\*Workaround:\*\*** the app treats a commit as successful only when the receipt reads back.

**\*\*Question:\*\*** is this known? A pointer to a server path that swallows a persist failure, or a condition yielding a 200 with no edits, would help us isolate a repro.

**## 5. Error plumbing in the dev smart client (G7)**

**\*\*Repro:\*\*** `throw new UserFacingError(“…”)` in a query called via `useOsdkFunction`.

**\*\*Observed:\*\*** the runtime returns `{“executionResult”:{“type”:“failed”,“failed”:{“type”:“userFacingError”,“userFacingError”:{“message”:“…”}}}}`, but `transformResponseFromLocal` reads only `failed.runtimeError.message`, so the app gets `Error(“Function execution failed with no error message”)`. REST, plain `@osdk/client` and actions over the Vite `/api` path all deliver `error.parameters.message` (`409 Ontologies:QueryEncounteredUserFacingError`, `400 Ontologies:ApplyActionFailed`). (The transform also unwraps only the top level, so struct returns should stay wire-wrapped — plugin source, not browser-confirmed.)

**\*\*Expected:\*\*** parity with the `/api` path.

**\*\*Workaround:\*\*** the preview query returns `{ok:false, errors:[…]}` as data; every function returns a JSON string.

**\*\*Question:\*\*** is a fix planned? Is `error.parameters.message` the stable location when deployed too (`error.message` is only `Failed to fetch 400 Bad Request`)?

**## 6. Link storage quirks (G8)**

**\*\*Repro:\*\*** `batch.link(a, “related_customer_2”, b)` on an imported same-type M2M.

**\*\*Observed:\*\*** reads back as `b.related_customer_2 → a`, `a.related_customer_1 → b`, `a.related_customer_2 → []`; cross-type links are unaffected. Separately, a live link backed by a relationship object (`DeviceSiteRelationship`) is served locally as an independent table: creating the row does not populate `device.sites`, while a direct `batch.link(device, “sites”, site)` succeeds.

**\*\*Expected:\*\*** direction preserved; intermediary links derived from their rows.

**\*\*Workaround:\*\*** avoid directional self-links until deployed; write relationship rows and read two-hop.

**\*\*Question:\*\*** is the side-swap local-only? Is a direct link on an intermediary link type rejected when deployed?

**## 7. No current-user identity in functions, locally (G11)**

**\*\*Observed:\*\*** `@osdk/functions` 1.20.0 exposes no identity API — only the `UserId`/`GroupId`/`Principal` _\*types\*_; `FunctionContext` is `{ foundryToken, traceId, spanId, parentSpanId, sampled }` and the local bearer (`mock` / `mock-token-for-local-dev` / `fake-local-dev-token`) carries no identity. The one route, Admin `GET /api/v2/admin/users/getCurrent`, sits in the template’s `platformApiProxy.passthrough`, so forwards to the live enrollment; we did not call it.

**\*\*Expected:\*\*** a fixed mock principal locally.

**\*\*Workaround:\*\*** no user attribution on the function’s writes; the platform’s edit history is the record of who committed.

**\*\*Question:\*\*** is there, or will there be, a supported way to read the calling user inside a function, locally and deployed? Should the template’s passthrough be documented as live traffic?

**## 8. Media sets (G12)**

**\*\*Repro:\*\*** `{ type: “mediaReference”, includeEmptyBackingMediaSet: true, editOnly: true }` on a code-born type; a create action with `{ fileName, data: Blob }`.

**\*\*Observed:\*\*** the client sends `PUT /api/v2/mediasets/media/upload?preview=true`; the proxy answers `501 PlatformApiProxyPathNotAllowlisted` (`x-foundry-platform-api-proxy: blocked`) with a `config_hint` to add the path to `passthrough` — which would ship the bytes to the live enrollment. Direct to the local server: `404` (no `/v2/mediasets/*`); `POST …/objectTypes//media//upload` → `400 Default:InvalidArgument`, `uploadMediaContent in Ontologies namespace not supported. Use uploadMedia in MediaSets namespace instead.` A fabricated `ri.mio.main.*` reference is accepted (`200`); `fetchMetadata` then gives `404 MediaSets:MediaItemNotFound`. Also: `mediaReference`/`attachment` on an `includeEmptyBackingDatasource` type need `editOnly: true`, and a `MediaReference` function parameter is silently dropped (use `Media` from `@osdk/api`). **\*\*Attachments work end to end\*\*** (plain, modify and function-backed actions; byte-exact to 30 MiB from Node and a browser `File`; readable in a function), though bytes do not survive a dev-loop restart and seeds cannot carry them.

**\*\*Expected:\*\*** a local media store, or a loud error.

**\*\*Workaround:\*\*** an attachment property behind a thin upload seam; media sets deferred to post-deploy.

**\*\*Questions:\*\*** is a local MediaSets store planned? Could the `config_hint` warn that the path is live traffic? Should the dropped parameter be an error?

**—**

**## What would help adopters most**

1. Local runtime parity for array, geopoint and timestamp edits (G5/G9), or a documented shim.

2. Import JSON passed to block-data function discovery (G4).

3. Links with an imported end served locally, with a deploy story for their datasource (G1–G3).

4. Smart client surfacing `userFacingError` text and unwrapping structs (G7).

5. Atomic local apply, `ObjectAlreadyExists` on duplicates, never a 200 on failure (G6/G10).

6. A mock identity for functions (G11) and a local media store (G12).

7. A documented “local preview limitations” page — half of this list would have been a lookup; we would gladly review a draft.

Happy to share minimal repros (synthetic data) for all but G10, for which we have not isolated one.

---

<div class="post-metadata">

**Author:** ![andras](https://avatars.discourse-cdn.com/v4/letter/a/838e76/32.png) [@andras](https://community.palantir.com/u/andras)\
**Post date:** [September 29, 2026, 1:54pm UTC](https://community.palantir.com/t/superrepo-local-preview-12-gaps-hit-while-building-a-function-backed-write-path-against-an-imported-ontology/7271/2 "2026-09-29T13:54:43Z")

</div>

Hey,

Thank you for the detailed report and the repros. Good news, I’ve checked the latest changes, and several fixes have already shipped based on other reports.

The imported-link RID issue (G1) is fixed for CLI imports in `@osdk/maker-experimental` 0.62.0. We’re still tracking the missing imported links (G2), which remain reproducible on latest main, and the backing dataset generation failure (G3). An unbound datasource requiring a mapping during installation is expected behaviour.

Function discovery for imported types (G4) is also fixed in `@osdk/maker-experimental` 0.62.0, so the narrowed edit-union workaround should no longer be necessary.

Array and geopoint edits (G5) are fixed in `@osdk/generator-converters.preview` 0.50.0, and timestamp edits (G9) are fixed in foundry-cli 0.293.0.

We’re tracking atomic local apply and typed duplicate-key errors (G6), and investigating the empty-success response (G10). A minimal repro for G10 would help.

The smart-client issues (G7) are confirmed. We’re tracking both preserving user-facing error messages and correctly decoding struct and collection results.

Intermediary link metadata and SDK generation have now been fixed and will be released shortly in `@osdk/generator-converters.ontologyir` and `@osdk/generator-converters.preview`. For G8, we’re still tracking self-link direction and updating local links when intermediary-object rows change.

For user identity (G11), functions can already call the platform SDK’s current-user endpoint. Locally, this returns the user signed into the CLI.

Local MediaSets uploads (G12) are not supported yet.

We’re also tracking a local-preview limitations page and clearer documentation that passthrough routes send traffic to the live enrolment.

Thanks,  
Andras
