Skip to content

feat(objectql): materialize seed rows on EVERY publish path (seedApplied)#1686

Merged
os-zhuang merged 1 commit into
mainfrom
feat/seed-apply-on-publish
Jun 10, 2026
Merged

feat(objectql): materialize seed rows on EVERY publish path (seedApplied)#1686
os-zhuang merged 1 commit into
mainfrom
feat/seed-apply-on-publish

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Problem

Publishing a seed draft only promoted its metadata — whether the rows actually landed depended on which route you published through:

  • /packages/:id/publish-drafts applied seeds at the dispatcher level, swallowing failures silently (the chat says "Published!", the app's tables are empty — exactly what we hit live on staging with an AI-built recruiting app).
  • /meta/:type/:name/publish (per-ref — what the home Publish banner uses since objectui#1621) never applied seeds at all.

Fix

Move SeedLoaderService into @objectstack/objectql (runtime keeps a re-export shim — rest can't depend on runtime, and objectql owns both the engine and the publish primitive), then apply seeds where every path converges — the protocol itself:

  • publishMetaItem: publishing a single seed materializes its rows from the just-promoted body (no read-back → no org-scope resolution pitfalls) and reports under seedApplied. Best-effort: a seed problem never fails the publish, but it's now loud in the response instead of invisible.
  • publishPackageDrafts: structure first, seeds last (their objects' tables must exist), all seed bodies batch-applied in one loader pass so cross-seed references resolve regardless of draft order. Per-item self-apply suppressed (_skipSeedApply) so nothing double-inserts.
  • dispatcher publish-drafts route: route-level apply only as a fallback when the protocol didn't self-apply (custom-protocol back-compat) — never both.

The per-ref REST route passes the protocol result through unchanged, so the banner path gets seedApplied with zero rest-server changes.

Test

  • objectql 546 + runtime 381 green.
  • New coverage: seeds publish last; one batch pass with all bodies; _skipSeedApply; non-seed publishes untouched; per-ref self-apply; loud failure for unreadable bodies; real-loader smoke test (rows actually inserted via the engine).

🤖 Generated with Claude Code

…ied)

Publishing a `seed` draft only promoted its metadata — whether rows actually
landed depended on WHICH route you published through. The package route
(/packages/:id/publish-drafts) applied seeds at the dispatcher level and
swallowed failures silently; the per-ref publish (/meta/:type/:name/publish —
what the home banner uses) never applied them at all. Result: "Published!"
with an app full of empty tables.

Move SeedLoaderService into @objectstack/objectql (runtime keeps a re-export
shim) and apply seeds where every path converges — the protocol itself:

- publishMetaItem: publishing a single `seed` materializes its rows from the
  just-promoted body (no read-back, no org-scope resolution pitfalls) and
  reports under `seedApplied`. Best-effort: a seed problem NEVER fails the
  publish, but it is now LOUD in the response instead of invisible.
- publishPackageDrafts: structure publishes first, seeds LAST (their objects'
  tables must exist), with every seed body batch-applied in ONE loader pass so
  cross-seed references resolve regardless of draft order. Per-item self-apply
  is suppressed (_skipSeedApply) so nothing double-inserts.
- http-dispatcher publish-drafts route: falls back to its route-level apply
  ONLY when the protocol didn't self-apply (custom protocol back-compat).

objectql 546 + runtime 381 tests green; new coverage for ordering, batch
single-pass, _skipSeedApply, non-seed publishes, and a real-loader smoke test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 10, 2026 1:52pm

Request Review

@os-zhuang
os-zhuang merged commit 11f9767 into main Jun 10, 2026
10 of 11 checks passed
@os-zhuang
os-zhuang deleted the feat/seed-apply-on-publish branch June 10, 2026 13:46
xuyushun441-sys added a commit that referenced this pull request Jun 13, 2026
* chore: bump objectui to 893e5302174c

feat(ADR-0046): package documentation portal + nav entry (#1686)

objectui@893e5302174c7cbf75a7bed9e8e6dcb935273363

* feat(objectql): ADR-0048 Phase 1+2 — namespace install gate + prefer-local resolution

Phase 1 — install-time namespace gate. `SchemaRegistry.installPackage` refuses
a package whose `manifest.namespace` is already owned by a DIFFERENT installed
package (new `NamespaceConflictError`), making explicit and early the constraint
the object/table layer already enforces implicitly (a duplicate
`CREATE TABLE <ns>_<obj>` fails at the DB). Same-package reinstall and shareable
platform namespaces (base/system/sys) are exempt; OS_METADATA_COLLISION=warn
downgrades to a warning.

Phase 2 — prefer-local (container-scoped) resolution. `getItem(type, name, ns?)`
resolves a bare name to the item owned by `ns`'s package before any
cross-package fallback, preserving ADR-0005 overlay precedence and remaining
backward compatible (param optional). `getApp` resolves prefer-local against its
own name (app.name ≡ namespace in v1). The per-item collision guard now narrows
to the cases prefer-local CANNOT disambiguate (shared/missing namespace), so two
DIFFERENT-namespace packages legitimately coexist on the same bare name — the
marketplace coexistence the revised ADR-0048 targets. Every existing collision
test still passes (namespace-less fixtures remain a hard error).

Tests: registry-namespace-install-gate.test.ts (7), registry-prefer-local-
resolution.test.ts (7). Full objectql suite green (600).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ADR-0046): add optional `description` to package docs

A doc can now carry a one-line `description` (frontmatter `description:`) —
title / summary / body, the natural minimal model.

- spec: DocSchema gains optional `description`.
- cli: collect-docs reads `description:` from frontmatter (generalized the
  scalar extractor; title + description share it). Regression test added.
- It travels in GET /meta/doc list (content is omitted there), so the docs
  portal shows summaries without fetching each body — verified live: showcase
  /meta/doc returns description, hasContent:false.
- Example docs (app-showcase, app-todo) carry descriptions.
- ADR-0046: schema + a P3 design note for doc TAGS (deferred) — tags are
  i18n-resolved keys, core protocol vocabulary + namespace-prefixed package
  tags; not a field to bolt on early.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
xuyushun441-sys added a commit that referenced this pull request Jun 13, 2026
…local resolution (#1810)

* chore: bump objectui to 893e5302174c

feat(ADR-0046): package documentation portal + nav entry (#1686)

objectui@893e5302174c7cbf75a7bed9e8e6dcb935273363

* feat(objectql): ADR-0048 Phase 1+2 — namespace install gate + prefer-local resolution

Phase 1 — install-time namespace gate. `SchemaRegistry.installPackage` refuses
a package whose `manifest.namespace` is already owned by a DIFFERENT installed
package (new `NamespaceConflictError`), making explicit and early the constraint
the object/table layer already enforces implicitly (a duplicate
`CREATE TABLE <ns>_<obj>` fails at the DB). Same-package reinstall and shareable
platform namespaces (base/system/sys) are exempt; OS_METADATA_COLLISION=warn
downgrades to a warning.

Phase 2 — prefer-local (container-scoped) resolution. `getItem(type, name, ns?)`
resolves a bare name to the item owned by `ns`'s package before any
cross-package fallback, preserving ADR-0005 overlay precedence and remaining
backward compatible (param optional). `getApp` resolves prefer-local against its
own name (app.name ≡ namespace in v1). The per-item collision guard now narrows
to the cases prefer-local CANNOT disambiguate (shared/missing namespace), so two
DIFFERENT-namespace packages legitimately coexist on the same bare name — the
marketplace coexistence the revised ADR-0048 targets. Every existing collision
test still passes (namespace-less fixtures remain a hard error).

Tests: registry-namespace-install-gate.test.ts (7), registry-prefer-local-
resolution.test.ts (7). Full objectql suite green (600).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(objectql): pivot ADR-0048 to option A — package-id-scoped resolution

Per the sharpened ADR-0048 decision: the URL/route key is the package id (A),
not the namespace. Migration cost is symmetric between A and B (both change one
thing), so A wins on the dimensions that matter for an open marketplace —
coordination-free global uniqueness, a single identity coordinate, no
land-grab, and a self-describing URL.

Backend:
- getItem(type, name, currentPackageId?) prefers `${currentPackageId}:${name}`
  directly (was namespace→owner indirection). ADR-0005 overlay precedence and
  backward compatibility unchanged.
- getApp(name, currentPackageId?) resolves prefer-local by package id.
- Retire the per-item CROSS-package throw: package ids are globally unique, so
  package-scoped resolution always disambiguates two distinct packages — what
  the old guard flagged is now the supported coexistence case. Remove the now-
  dead MetadataCollisionError, findOtherPackageOwner, isPreferLocalDisambiguable,
  isRealPackage, SYS_METADATA_OWNER. collisionPolicy now governs only the Phase
  1 namespace gate.

Tests: rewrite *-cross-package-collision.test.ts from "throws" to "coexists +
package-scoped resolves"; prefer-local test keys on package id. Full objectql
suite green (598).

ADR: §3.3 package-scoped (package-id) resolution; §3.4 cross-package throw
retired; §3.6 transparent package-id URL + optional per-tenant namespace alias;
phasing marks Phase 1 + Phase 2 backend/frontend done, package-id routing as the
remaining frontend step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: re-trigger checks for the option-A pivot commit

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant