fix: wire Studio-authored actions, translations, and runtime sharing rules (#2605, #2591, #2592)#2608
Merged
Merged
Conversation
…uthored actions, translations, and runtime sharing rules (#2605, #2591, #2592) Three more members of the "declared but never wired" family (#2605), each following the #2596 authored-hooks template and runtime-verified against a live showcase server (author → invoke/read → delete → restart). Authored actions (#2605 item 1): engine.executeAction's map was only ever populated from the app bundle at boot, so a published `action` row was stored and listed but never executable. AppPlugin now installs a QuickJS default action runner (engine.setDefaultActionRunner, opt-out via OS_DISABLE_AUTHORED_ACTIONS=1) and ObjectQLPlugin re-registers authored actions (standalone rows AND object-embedded actions[]) under packageId 'metadata-service' at kernel:ready, on metadata:reloaded, and on action/object protocol mutations. Package-artifact actions are excluded so AppPlugin's handlers are never clobbered. Authored translations (#2591): only static bundles ever reached the i18n runtime. Both adapters (FileI18nAdapter and the kernel's in-memory fallback) now carry a separate authored layer replaced wholesale on each sync (clear-then-reload), overlaying static bundles. The shared sync lives in @objectstack/core (wireAuthoredTranslationSync) and is wired by AppPlugin and I18nServicePlugin with single-owner semantics. Sharing rules (#2592): bindRuleHooks was boot-only and rule authoring is a data insert, so the first runtime rule for an object never evaluated until restart. The sharing plugin now binds afterInsert/afterUpdate/afterDelete triggers on sys_sharing_rule that unbind + re-bind the rule-hook package from a fresh listRules(), serialized and fail-safe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ag7HEv9FABsCQnVNNcPweH
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 5 package(s): 40 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
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.
Closes the three open members of the "declared but never wired" family tracked in #2605, each following the #2596 authored-hooks template. Fixes #2591, fixes #2592.
1. Authored actions now execute (#2605 item 1 — verified broken, then fixed)
engine.executeActionreads a map populated only from the app bundle at boot; nothing ever registered handlers foractionmetadata. Runtime probe on the live showcase confirmed the gap:POST /api/v1/actions/showcase_task/probe_authored_action→Action not foundeven though the row was stored + listed.engine.setDefaultActionRunner) — the action-path twin of the fix(objectql,runtime,metadata-protocol): execute authored (Studio) hook bodies — default bodyRunner + live rebind (#2588) #2596 hook body runner. Opt out withOS_DISABLE_AUTHORED_ACTIONS=1.resyncAuthoredActions: reads activeactionrows and actions embedded in authoredobjectrows'actions[], unions them withloadMany('action')(fresher DB row wins), filters package-artifact actions (AppPlugin owns those —registerActionreplaces by key, so re-registering would clobber), and registers the rest underpackageId: 'metadata-service'. Runs atkernel:ready, onmetadata:reloaded, and onaction/objectprotocol mutations; serialized; full-replace so edits re-register and deletes tear down; a failed read never tears down live registrations.2. Authored translations reach the i18n runtime (#2591)
Only static bundles (app
bundle.translations, plugintranslations/) were ever loaded — a publishedtranslationitem was a dead-end on publish AND after restart.FileI18nAdapter(service-i18n) and the kernel's in-memory fallbackcreateMemoryI18n(core) — the fallback is what dev/standalone stacks actually run, which the runtime probe surfaced (the first cut only patched service-i18n and did nothing on the showcase).wireAuthoredTranslationSync/readAuthoredTranslationLayer) and is wired by both AppPlugin and I18nServicePlugin with single-owner semantics (first wirer per i18n service instance wins). Locale resolution:_meta.locale→ top-levellocale→ BCP-47-shaped item name; the name pattern is deliberately narrow so snake_case item names don't classify as locales.3. Sharing rules created at runtime bind without a restart (#2592)
bindRuleHookswas boot-only, and rule authoring is a data insert —metadata:reloadednever fires — so the first runtime rule for an object with no boot-time rule silently never evaluated.afterInsert/afterUpdate/afterDeletetriggers onsys_sharing_rule(own package idplugin-sharing:rule-rebind, so the rebind's ownunbindAllRuleHookscan never tear its triggers down) that re-run the boot bind from a freshlistRules(). Serialized so overlapping writes can't leave a stale snapshot bound; awaited so the rule is enforceable when the authoring call returns; fail-safe so a rebind failure never fails the rule write and keeps previous bindings.Runtime verification (live showcase server,
objectstack dev --seed-admin)The #2560 lesson — mocked tests pass while the real runtime is broken — so every claim was probed end-to-end:
PUT /meta/action/...(script body) →POST /actions/showcase_task/...Action not found{ok:true, doubled:42}PUT /meta/translation/zh-CN→GET /i18n/translations/zh-CNprobe_hello = 你好,探针PUT+DELETE /meta/translation/frsys_sharing_ruleforshowcase_contact(0 rules at boot) → insert matching recordsys_record_sharerow written,reason: rule:probe_share_contactsTests
objectql: 13 new tests (plugin-authored-actions.test.ts) mirroring the fix(objectql,runtime,metadata-protocol): execute authored (Studio) hook bodies — default bodyRunner + live rebind (#2588) #2596 suite; artifact filter, embedded-object shape, union semantics, teardown, failed-read no-op, mutation routing. Full suite 775 green (one pinned-invariant test updated to allow the new narrowaction/objectboot reads — object hydration is still asserted NOT to happen).service-i18n: 13 new tests (adapter layer semantics + plugin sync wiring); one stale assertion updated (registerRoutes:falsenow asserts "no routes registered" instead of "no hooks at all"). 48 green.plugin-sharing: 6 new tests (rule-rebind.test.ts) incl. the exact Sharing rules created at runtime don't bind their hooks until restart (bindRuleHooks is boot-only) #2592 repro and rebind serialization.core: 302 green;runtime: 454 green.Residual (called out, not in scope)
run_action/list_actionsresolve declarations from object schema (obj.actions), so a standalone authored action executes via the REST route but isn't discoverable via MCP until standaloneactionitems are merged into object reads — same declaration-surfacing gap as the Studio-rail listing task (task_8dd7c8a0). Object-embedded authored actions are fully covered on both surfaces.🤖 Generated with Claude Code
https://claude.ai/code/session_01Ag7HEv9FABsCQnVNNcPweH
Generated by Claude Code