Skip to content

Tracking: Studio-authored metadata that's declared but never wired at runtime (the 'silently does nothing' family) #2605

Closed

Description

@os-zhuang

The pattern (tracking / umbrella)

A recurring, high-severity class surfaced by the online-authoring push and the publish-rebind consumer audit: a business user authors metadata in the Studio, publishes it, and it's stored + shown as live — but the runtime never actually WIRES it, so it silently does nothing (often not even after a restart). Code-reading and mocked unit tests pass; only a real-runtime probe reveals it (the #2560 lesson). This is the same silent-failure theme as the just-shipped #4 (inline validation) and #6 (enable/disable) work — "you authorized it, it quietly doesn't take effect."

This issue consolidates the remaining members of that family into one place, now that the first fix has landed and established the pattern.

✅ Fixed — the template

🔴 Open — same "declared-but-not-wired" family

  1. Authored actions' handler may never run — UNVERIFIED, untracked (the reason for this issue).
    executeAction reads a boot-populated this.actions Map filled only from the app manifest via registerAction; objectql does not load action metadata at boot. So a Studio-authored action (a script/body action) is likely dead at runtime — the exact parallel of the hooks gap, and not covered by the hooks fix (fix(objectql,runtime,metadata-protocol): execute authored (Studio) hook bodies — default bodyRunner + live rebind (#2588) #2596 touched the hook-bind path only). Next step: runtime-probe it (author a script action into a writable base, publish, invoke via run_action / a flow action node, check before/after/restart). If broken, fix by reusing the fix(objectql,runtime,metadata-protocol): execute authored (Studio) hook bodies — default bodyRunner + live rebind (#2588) #2596 bodyRunner wiring for the action path.

  2. Authored translations never reach the i18n runtime — Authored (Studio) translations never reach the i18n runtime — only static bundles are loaded #2591. loadTranslations is called only with static bundles (app bundle.translations, plugin translations/); nothing reads translation metadata items into service-i18n (getMetaItems({type:'translation'}) → 0). translation is allowRuntimeCreate:true but unwired → a Studio-authored translation is a dead-end on publish AND after restart.

  3. Sharing rules created at runtime don't bind until restart — Sharing rules created at runtime don't bind their hooks until restart (bindRuleHooks is boot-only) #2592. Enforcement reads sys_sharing_rule rows live, but the hook binding (bindRuleHooks, sharing-plugin.ts:201) is boot-only. Different trigger from the rest: authoring a rule is a data insert, so metadata:reloaded never fires → needs a sys_sharing_rule data-change rebind.

✅ Proven-fine — do NOT chase (audit, runtime-verified)

validation rules, object definition edits, action schema, report_schedule, and permission-sets / RLS / FLS all go live on publish without restart (read live per-request, not boot-cached). Confirmed by adding a field to an already-cached object and publishing → the new field was accepted without a restart.

Related but distinct (cross-reference — separate work, already tracked)

Reusable runtime-verify harness

The decisive method for any "authored X takes effect at runtime" claim: com.example.ops (an unregistered writable-base package) + a probe object/hook/action + POST /packages/:id/publish-drafts + a before / after / restart check. Code-reading and mocked tests pass while the real runtime is broken — always probe the running server.

Suggested sequencing

  1. Verify + (if broken) fix authored actions' handler — most likely the biggest remaining hole, and reuses the fix(objectql,runtime,metadata-protocol): execute authored (Studio) hook bodies — default bodyRunner + live rebind (#2588) #2596 bodyRunner wiring.
  2. i18n translations (Authored (Studio) translations never reach the i18n runtime — only static bundles are loaded #2591).
  3. Sharing-rule data-change rebind (Sharing rules created at runtime don't bind their hooks until restart (bindRuleHooks is boot-only) #2592).

Each is a real feature-fix with its own design surface (e.g. "should authored actions/hooks run sandboxed JS?" — a security gate), not a generic metadata:reloaded subscription.

Activity

  1. os-zhuang commented on Jul 5, 2026

    @os-zhuang
    ContributorAuthor

    All three open items in this family shipped in #2608 (4f5b791), each runtime-verified against a live showcase server with the before/after/restart harness described above:

    1. Authored actions' handler never ran — CONFIRMED broken, now fixed. The probe reproduced the gap exactly as hypothesized (POST /actions/... → Action not found for a published action row). Fixed by reusing the fix(objectql,runtime,metadata-protocol): execute authored (Studio) hook bodies — default bodyRunner + live rebind (#2588) #2596 template: default QuickJS action runner installed by AppPlugin (engine.setDefaultActionRunner, opt-out OS_DISABLE_AUTHORED_ACTIONS=1) + resyncAuthoredActions in ObjectQLPlugin (kernel:ready / metadata:reloaded / action+object mutations; artifact-wins filter; covers standalone action rows AND actions embedded in authored object rows). Verified live: body executes on publish and after restart.
    2. i18n translations (Authored (Studio) translations never reach the i18n runtime — only static bundles are loaded #2591) — closed by the PR. Note for the audit record: the first cut wired only service-i18n and the runtime probe caught it doing nothing, because dev/standalone stacks run the kernel's in-memory i18n fallback — the fix now covers both adapters via a shared sync in core (wireAuthoredTranslationSync), with clear-then-reload delete semantics verified live.
    3. Sharing-rule data-change rebind (Sharing rules created at runtime don't bind their hooks until restart (bindRuleHooks is boot-only) #2592) — closed by the PR. Verified live: first runtime rule for an object with zero boot-time rules produced a sys_record_share row on the next matching insert, no restart.

    Residual (not chased here): standalone authored action items execute via the REST route but aren't discoverable via MCP run_action/list_actions, since those resolve declarations from object.actions — same declaration-surfacing gap as the Studio-rail listing work (task_8dd7c8a0). Object-embedded authored actions are covered on both surfaces.


    Generated by Claude Code

  2. self-assigned this
    on Jul 16, 2026
  3. os-zhuang commented on Jul 16, 2026

    @os-zhuang
    ContributorAuthor

    Re-evaluation against latest main (93fd58e, 2026-07-16)

    Re-audited every item in this umbrella against current code. Verdict: all three family members remain fixed on main, all sub-issues and cross-references are closed, and the one residual gap is now tracked separately (#3010) — this tracking issue can be closed.

    🟢 The three fixes are all present on main, each with a dedicated regression test

    1. Authored actions — resyncAuthoredActions is live in the ObjectQL plugin with all three triggers (kernel:ready plugin.ts:259, metadata:reloaded :377, action/object mutations :389), serialized resyncs, full-replace semantics (removeActionsByPackage('metadata-service') then re-register), artifact-wins filtering, and coverage of both standalone action rows and object-embedded ones (resyncAuthoredActionsNow, plugin.ts:1338). The default QuickJS runner is installed by AppPlugin (app-plugin.ts:263) with the OS_DISABLE_AUTHORED_ACTIONS=1 opt-out, and a loud warning fires if authored actions exist with no runner installed. Test: packages/objectql/src/plugin-authored-actions.test.ts.
    2. Authored translations (Authored (Studio) translations never reach the i18n runtime — only static bundles are loaded #2591, closed) — wireAuthoredTranslationSync lives in core (packages/core/src/fallbacks/authored-translation-sync.ts) and is wired by both hosts (AppPlugin for the kernel in-memory fallback, I18nServicePlugin for the file adapter, i18n-service-plugin.ts:126), with an ownership marker so double-wiring no-ops. Clear-then-reload delete semantics; triggers on kernel:ready, metadata:reloaded, and the onMetadataMutation stream. Test: packages/services/service-i18n/src/authored-translations.test.ts.
    3. Sharing-rule data-change rebind (Sharing rules created at runtime don't bind their hooks until restart (bindRuleHooks is boot-only) #2592, closed) — sharing-plugin.ts:171 binds afterInsert/afterUpdate/afterDelete triggers on sys_sharing_rule that re-run the full unbind+bindRuleHooks cycle, serialized and fail-safe (previous bindings kept on rebind failure). Test: packages/plugins/plugin-sharing/src/rule-rebind.test.ts.

    The hooks template fix (#2588/#2596) is also still intact: setDefaultBodyRunner is called at app-plugin.ts:225.

    🟢 Cross-references all resolved since the last update

    🟢 The pattern got institutionalized: ADR-0096

    The family's failure mode ("a schema collection the AI can author but the runtime ignores is the worst failure mode a metadata platform can ship") is now the explicit motivation of ADR-0096 declarative connector instances (#2977, runtime re-materialization landed in #3001 = the current main HEAD): declarative connectors: entries were exactly this class (registered as metadata, never reaching the engine registry). The fix goes further than the members here — provider-bound entries that can't materialize are a hard boot error, plus a metadata:reloaded reconcile with signature-based re-materialization. That's the "fail loudly instead of silently doing nothing" upgrade this umbrella was arguing for. Related: #2992 tracks the latent (stub) GraphQL/realtime surfaces in the same declared-vs-wired spirit.

    🔴 The one residual → now tracked as #3010

    The wrap-up comment's residual is still real on 93fd58e and was untracked until now: standalone authored action rows execute (REST route, engine registry) but are invisible to MCP list_actions/run_action, because both bridge resolution paths read declarations only from object.actions (http-dispatcher.ts:813, :1174). Split out as #3010 so it doesn't hold this umbrella open — it's a declaration-surfacing gap (same family as the rail-listing work), not an execution gap.

    Recommendation

    With all three members shipped + regression-tested, all cross-refs closed, and the residual split into #3010, nothing remains that this issue tracks. Recommend closing as completed.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions