Repository navigation
Add metamodel interfaces for ObjectQL/ObjectUI contract - #1
Merged
Merged
Conversation
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Add Metamodel interfaces for backend and frontend
Add metamodel interfaces for ObjectQL/ObjectUI contract
Jan 18, 2026
huangyiirene
approved these changes
Jan 18, 2026
huangyiirene
marked this pull request as ready for review
January 18, 2026 09:09
hotlong
added a commit
that referenced
this pull request
May 20, 2026
Closes gap #1 from the production-readiness review: the client-side ObjectForm / inline-grid masker (shipped earlier) was only a UX layer — a hand-crafted POST or direct ObjectQL call could still target any field. This commit closes the loop by enforcing field-level write permissions in the SecurityPlugin middleware. Behavior: on every insert/update, after the existing CRUD check and before the tenant/owner auto-injection, the middleware now scans the caller's payload against the merged field permissions for the target object. If the payload references any field the caller is not permitted to edit, the engine throws PermissionDeniedError (HTTP 403) with the offending field names exposed via details.forbiddenFields. Design choices: - **Fail-closed via throw, not silent strip.** Silent strip hides the boundary from honest clients (partial-save confusion: 'why didn't my change save?') AND gives probing clients no signal that the field exists. Throwing makes the boundary observable in both directions — legitimate UIs get an actionable error; probing clients learn nothing they could not already infer. - **Allow-list semantics.** Only fields explicitly enumerated in a permission set's 'fields' map are constrained. Fields without a rule pass through untouched. - **Bulk inserts checked row-by-row.** Arrays are scanned in full; a single offender in any row rejects the entire batch atomically. - **Runs BEFORE auto-injection.** The tenant/owner auto-fill (org_id, owner_id) is system-supplied from ExecutionContext, not from the caller's payload, so it is not subject to the user's edit permissions even when the user has no rule for those fields. - **System operations bypass entirely.** ExecutionContext.isSystem short-circuits the whole security middleware including this check. API additions: - FieldMasker.detectForbiddenWrites(data, fieldPermissions): string[] — exported helper for adapters that want to perform the check out-of-band (e.g., strip-then-warn instead of fail-closed). Documentation: - content/docs/guides/security.mdx — new 'Server-side enforcement (fail-closed)' subsection under Field-Level Security with the 403 response shape, the why-throw-vs-strip rationale, allow-list semantics, and the bulk/system bypass rules. - .changeset/security-fls-write-enforcement.md — minor bump. Tests: 7 unit tests for FieldMasker.detectForbiddenWrites + 8 integration tests via the existing security middleware harness covering insert/update/bulk/system-bypass/no-rule passthrough. 53 plugin-security tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
xuyushun441-sys
pushed a commit
that referenced
this pull request
May 22, 2026
Vendor-neutral observability primitives, extracted as a standalone
package so deployment-target code (cloud, self-hosted, ...) can depend
on the contracts without pulling in the whole runtime.
Owns:
- Contracts: MetricsRegistry, ErrorReporter, MetricSample, CapturedError
(Logger is re-exported from @objectstack/spec/contracts).
- Semantic conventions (SEMCONV): canonical Prometheus-style metric
names emitted by the framework, plus the back-compat RUNTIME_METRICS
alias.
- Metric exporters: Noop, InMemory (with totalCounter/histogramValues/
lastGauge helpers), Console, and OtlpHttp (buffered JSON exporter,
flush()-on-demand so it works on Workers as well as Node).
- Error reporters: Noop, InMemory, Console (structured JSON to stderr).
- Loggers: Noop, Console, Json (production-ready structured logging
that satisfies the existing @objectstack/spec Logger contract).
Backwards compatibility:
- @objectstack/runtime now depends on @objectstack/observability and
its src/observability/{metrics,error-reporter}.ts files are thin
re-export shims, so existing internal imports (and the public
runtime/index.ts surface) are unchanged.
Tests: 34 new tests covering all exporters; @objectstack/runtime test
suite still passes (the 2 pre-existing app-plugin.test.ts failures
around i18n service warnings are not affected by this change — they
were already failing on main).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
xuyushun441-sys
pushed a commit
that referenced
this pull request
May 22, 2026
Introduces an opt-in path in ObjectStackProtocolImplementation.saveMetaItem that writes overlay metadata through SysMetadataRepository.put instead of the raw engine, so writes append to the change-log and emit HMR seq events. Behavioural changes (all behind options.useRepositoryWritePath / OBJECTSTACK_USE_REPOSITORY_WRITE_PATH=1): - saveMetaItem request gained optional parentVersion (If-Match) and actor fields. ConflictError -> 409 metadata_conflict. - Plural type aliases (views, dashboards, ...) normalized to singular before the repo's overlay-allowlist gate (rubber-duck #5). - Object-registry mutation moved AFTER successful put() so a conflict does not leave the in-memory registry stale (rubber-duck #3 invariant test added). Repo/test-fake fixes uncovered by rubber-duck review: - SysMetadataRepository.put/delete now update/delete by row id because the engine's strict .update requires id or multi:true (rubber-duck #1). - sys_metadata.checksum column widened from 64 -> 71 chars to hold the sha256: prefix produced by hashSpec() (rubber-duck #2). - Three test fake engines extended to support both overlay-tuple and id-based where lookups. 333/333 objectql tests pass. Deferred to PR-10d.4: REST plumbing for parentVersion/actor (rubber-duck #6), race-window retry for omitted parentVersion (rubber-duck #4), default flag flip + legacy path removal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
xuyushun441-sys
pushed a commit
that referenced
this pull request
May 23, 2026
Walking through Studio as a low-code developer surfaced a fundamental gap: it is a beautiful metadata BROWSER but offers no authoring affordances. The #1 reflex of every Airtable / Power Apps user — add a field — has no entry point in our UI. This change adds two authoring touchpoints to the Object Hub > Fields panel that respect Prime Directive #6 (no temporary workarounds) and stay true to metadata-as-code: 1. + Add field button A primary CTA in the toolbar opens a guided dialog (AddFieldDialog) with a type picker (18 supported field types, each with icon + one-line semantics), a derived snake_case machine-name preview, and a live snippet preview. Two actions: • Copy snippet — pastes a defineField-style literal into the clipboard, ready to drop into the fields: { … } block. • Open .object.ts in VS Code — vscode:// deep-link via the existing vscode-objectstack extension. Filesystem writes from the browser are intentionally avoided. When the runtime overlay write-path matures (ADR-0005), the dialog can swap the snippet flow for a real persist call without changing its contract. 2. Click any field row to open a detail drawer Rows are now cursor-pointer and trigger a side Sheet (FieldDetailDrawer) showing the full normalised field spec — all properties, options enumerated, references, formula, validation — plus the same VS Code deep-link and a per-field Copy snippet that emits just this field's literal. The drawer is read-only; users who want to edit follow the VS Code link. The previous behaviour (clicking a row did nothing) was the single biggest dead-end during the persona walkthrough. The drawer is the minimum viable acknowledgement that a field is an interactive object, not a static row of text. Plumbing changes - ObjectSchemaInspector preserves every property of the field spec (spread over the cherry-picked subset) so the drawer has access to schema properties beyond the table columns. - Added a ChevronRight column on the right edge of every row, group-hover translate-x for the same drill-in affordance used on MetadataListPage compact rows. - CopyButton stops propagation so the row click does not fire when copying the field name. Build / tests pnpm --filter @objectstack/studio build — clean. pnpm --filter @objectstack/studio test — 69/69 tests pass; same 2 pre-existing @object-ui/core/dist/evaluator/ExpressionEvaluator module resolution failures in playground-plugins / plugin-system suites, unrelated to this work. Files - apps/studio/src/components/FieldDetailDrawer.tsx (new, ~160 lines) - apps/studio/src/components/AddFieldDialog.tsx (new, ~280 lines) - apps/studio/src/components/ObjectSchemaInspector.tsx · Imports FieldDetailDrawer, AddFieldDialog, Plus, ChevronRight · State for selectedField + addOpen · Preserves full field spec via spread in fieldEntries · Toolbar: + Add field primary CTA · TableRow: cursor-pointer, onClick → setSelectedField · New chevron column on right; colSpan bumped to 7 · Drawer + dialog mounted at end of component · CopyButton stops click propagation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced May 25, 2026
Closed
Closed
xuyushun441-sys
added a commit
that referenced
this pull request
May 31, 2026
…nector-rest (ADR-0018) (#1416) Promote `connector_action` to a built-in baseline node — the generic-dispatch counterpart to `http_request`: where http_request calls any raw URL, connector_action invokes any registered connector's declared action. - engine: connector registry (registerConnector / unregisterConnector / resolveConnectorAction / getRegisteredConnectors) + ConnectorActionHandler / ConnectorActionContext / RegisteredConnector types. registerConnector validates via ConnectorSchema and asserts every declared action has a handler. - builtin/connector-nodes.ts: connector_action executor (source:'builtin', category:'io', all three paradigms), wired into installBuiltinNodes() — the core plugin now seeds 11 baseline node types. Missing connector fails the step (not flow registration) with a clear error. - packages/connectors/connector-rest (@objectstack/connector-rest): the reference concrete connector. createRestConnector + ConnectorRestPlugin, `request` action, static auth (none/api-key/basic/bearer), no OAuth2 refresh (enterprise tier). - New packages/connectors/ workspace category (alongside plugins/services/adapters). - ADR-0018 §Addendum: records the decision, resolves Open-question #1, supersedes M2's "connector_action dropped from baseline". Tests: service-automation 87/87, connector-rest 10/10 (incl. end-to-end kernel boot: both plugins -> connector_action flow -> REST handler). Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
This was referenced Oct 20, 2026
os-tesla
pushed a commit
that referenced
this pull request
Oct 25, 2026
…namic import (objectui#13450) (#13473) Fixes #13450 Clause-②: no (narrowing) Metadata-protocol: narrowed Retires the `module` widget source. `WidgetRegistry.load()` resolved `{ type: 'module', url }` with `new Function('url', 'return import(url)')`. That is the runtime dynamic import AGENTS.md commandment `#6` forbids ("No `eval()` / runtime dynamic imports to load components"). The claim is the PM's comment `6386913269` on the card, for branch `claude/issue-13450-retire-widget-module-source`, session `https://claude.ai/code/session_01432GBvPzfZ6CsLanp9xzkZ`. ## What changes - **`@object-ui/types`**: the `module` member and its `WidgetSourceModule` interface are removed. `RuntimeWidgetSource` is now `WidgetSourceInline | WidgetSourceRegistry`, and the barrel no longer exports `WidgetSourceModule`. The union's docblock records the retirement and the migration. The manifest's JSDoc `@example` now uses `{ type: 'inline', component }` with a locally defined `CustomChart` component (the build-time import is named in a comment, so the block compiles in isolation under `check-doc-example-types`) instead of a URL. - **`@object-ui/core`**: the `case 'module'` arm and its Function-constructor sink are removed from `WidgetRegistry.resolveComponent()`. The `default:` arm is now exhaustive (`const unhandled: never = source`), so a union member added without an arm fails to compile. - An untyped manifest that still reaches `load()` with `type: 'module'` is refused by name before anything is fetched. Untyped means plain JS, a cast, or an older `@object-ui/types`. The error names the widget, the retired `"module"` source and both replacements. - Any other unknown type is refused with the supported types listed. - In both cases `load()` emits `widget:error` and `loadAll()` reports the error. The class docblock and its example no longer mention URL loading. - **Changeset** `.changeset/13450-retire-widget-module-source.md`: `minor` for core and types. objectui never declares `major` (`check-changeset-no-major`); the break is stated in the body as **BREAKING**. The changeset gives the FROM → TO migration and the one-line fix, and carries the `Clause-②:` / `Metadata-protocol:` lines. - `page-nav-misc-spec-parity.test.ts`: the reason string in one `it.each` row said "module/inline/registry loader union" and now says "inline/registry". It is a test title, not an assertion. ## Migration (FROM → TO) - FROM `source: { type: 'module', url: '/widgets/chart.js', exportName: 'Chart' }` - TO `import { Chart } from './widgets/chart'` and `source: { type: 'inline', component: Chart }` (imported at build time, so the bundler sees it) - OR `ComponentRegistry.register('chart', Chart)` once, and `source: { type: 'registry', registryKey: 'chart' }` ## Premise check: the card's "four emitting sites" are not widget sources The card and the triage route name four `type: 'module'` producers. Each one was read in context at `a3c0da9`. All four are the generated **`package.json`'s ESM marker** (`"type": "module"`), not a widget manifest source. Deleting them would make the scaffolded packages CommonJS and break their `import` syntax. **None was changed:** | Site (by symbol) | What it builds | Verdict | |---|---|---| | `buildAppPackageJson` in `packages/cli/src/utils/app-generator.ts` | temp app `package.json` (`name`, `private`, `type`, deps) | package.json ESM marker; kept | | `buildRoutedAppPackageJson` in the same file | routed temp app `package.json` | package.json ESM marker; kept | | `buildInitPackageJson` in `packages/cli/src/commands/init.ts` | `objectui init` project `package.json` (`scripts.dev: 'vite'`) | package.json ESM marker; kept | | `buildPackageJson` in `packages/create-plugin/src/templates.ts` | generated plugin `package.json` (`main`, `module`, `exports`) | package.json ESM marker; kept | So `@object-ui/cli` and `@object-ui/create-plugin` carry no change and no changeset, which narrows the route. No widget-manifest producer of a `module` source exists in this tree. A `git grep` for `WidgetSourceModule` before this change hit only its declaration and the barrel. `apps/console/src` and `packages/app-shell/src` construct no `module` source (triage's reading). `objectstack` and `hotcrm` reference neither `WidgetRegistry` nor `RuntimeWidgetSource` in code (the only `objectstack` hit is a doc paragraph that does not name the `module` source). Out-of-tree consumers are NOT MEASURED. No user-facing doc names the `module` widget source. `content/docs`, the package READMEs and `docs/` were checked with `git grep`; the control `ComponentRegistry` hits in `content/docs`. The two JSDoc examples were the only documentation of it, and both are rewritten. ## Tests (head `e15036c`) - `be01ada` (patch round): `check-doc-example-types` exit 1 → 0 (34 → 35 compile; `WidgetRegistry #1` back on its TS2304 ledger row). JSDoc text only. New `describe('the retired \`module\` source (objectui#13450)')` in `packages/core/src/registry/__tests__/WidgetRegistry.test.ts`: 1. A manifest with `source: { type: 'module', url }` is refused. The rejection is an `Error` that names `"test-widget"`, `"module"`, `type: 'inline'` and `type: 'registry'`; these are the named subjects, not the wording. The global `Function` constructor, proxied for construct and apply, records zero calls. Nothing is marked loaded or synced into the component registry. The literal carries `@ts-expect-error`, so the type-level refusal is pinned too: core's `type-check` runs `tsconfig.test.json`, which includes `src/**/*.test.ts`. 2. `loadAll()` reports the refusal for that widget, a sibling still loads, and `widget:error` fires. 3. An unknown type (`remote`) is refused with `"inline"` and `"registry"` named and no "retired" wording. - `pnpm exec vitest run packages/core/ --maxWorkers=2`: `Test Files 211 passed (211)`, `Tests 4089 passed | 27 skipped (4116)` - `pnpm exec vitest run packages/types/ --maxWorkers=2`: `Test Files 388 passed (388)`, `Tests 10568 passed (10568)` - `pnpm --filter @object-ui/types --filter @object-ui/core run type-check`: both `Done`, exit 0. This ran after both packages were rebuilt, so core's test program read the new `dist/*.d.ts`. **Ablation, runtime** (one-shot, through `ablation-replace.mjs`, restored to the HEAD blob): a `case 'module'` arm with the Function-constructor import was re-planted ahead of `default:`. On-disk marker count was 1, sink count 1. Result: `Tests 2 failed | 24 passed (26)`. Test 1 received `"A dynamic import callback was not specified."` instead of the refusal; test 2 failed the same way. Test 3 stayed green, which is the expected direction. After the restore, `git diff HEAD` was empty and the blob equalled HEAD. A first attempt used an anchor that the replacement re-contained; the tool refused it and nothing ran. **Reverse verification, types** (cross-package): a `{ type: 'module'; url: string }` member was re-added to `RuntimeWidgetSource` and `@object-ui/types` rebuilt; the dist marker count was 1. Core's `tsc -p tsconfig.test.json` then reported `TS2578: Unused '@ts-expect-error' directive` on the new test and `TS2322 ... is not assignable to type 'never'` on the exhaustive arm. After the restore and a rebuild, the dist marker count was 0 and the same `tsc` was green. ## Gates (local, at `e15036c`) Exit codes were captured to a file before reading: - `lint` (core + types): exit 0. 0 errors; the warnings are all pre-existing `no-explicit-any`, and none is on a changed line. - `check:new-line-citations` 0 · `check:control-bytes` 0 · `check-changeset-presence` 0 · `changeset:check` (fixed + no-major) 0 · `check:changeset-claims` 0 · `check:pending-changeset-literals` 0 · `check:spec-symbols` 0 · `check:unreferenced-sources` 0 · `check:doc-example-readers` 0 · `check:shell-escape-residue` 0 · `check:test-path-roots` 0 - `check:readme-exports`: exit 1, NOT MEASURED for the repo as a whole. The only unjudged population is `@object-ui/app-shell`, which was unbuilt; every other package was judged with `0 fabricated, 0 wrong-path`. No README names `WidgetSourceModule`. - `check:published-dist`: exit 1, NOT MEASURED. Its internal whole-workspace `turbo run build` hit a heap OOM in `@object-ui/app-shell`, so it judged nothing. That is environmental and not this diff. CI owns this gate. `origin/main` was merged before opening; it had not moved from the branch base `a3c0da9`, so the merge was a no-op. objectui#13442 is in flight in the same batch on disjoint files (`packages/core/src/actions/**` and the `DataSource` type). The only shared surface is the `@object-ui/types` barrel, where this PR removes a single line in the widget block. ## Acceptance notes - Remote widgets as a product capability (a signed manifest and an allow-listed origin, decided in spec) are the maintainer's call and are not part of this card, per the triage ruling. - `register()` still accepts a manifest with a retired or unknown source, and the refusal happens at `load()`. That matches the ruling's "refuse an old manifest loudly in `default:`" and how the registry already treats a missing `registryKey`. --- _Generated by [Claude Code](https://claude.ai/code/session_01432GBvPzfZ6CsLanp9xzkZ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Oct 26, 2026
zhuangjianguo
pushed a commit
that referenced
this pull request
Nov 2, 2026
…e shared userActions defaults The maintainer ruled that an interface page reads the same UserActionsConfigSchema defaults as a list view. The amendment banner qualifies TL;DR #1 and #5 for the toolbar toggles only; the closed rows of section 3.4 and the export lock stand. Regenerates the ADR index, notes the ADR in the page code, and adds a test that the page draws what a list view parses the same block to. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUidUZdX3neiGCfRHE
This was referenced Nov 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Defines the type system shared between backend (ObjectQL) parser and frontend (ObjectUI) renderer.
Core Interfaces
FieldType- 16 data types covering text, numeric, date/time, lookup, selection, and special fields (file, image, json)ObjectField- Field metadata with validation rules, constraints, lookup configuration, and display propertiesObjectEntity- Complete entity definition including fields array, keys, UI hints, audit/soft-delete flags, and search configurationObjectView- View configuration supporting 10 presentation types (list, form, detail, card, kanban, calendar, chart, map, timeline, custom) with columns, filters, sorting, and layoutsUsage Example
Implementation Notes
metadatafields without core schema changesisFieldType()for runtime validationOriginal prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.