Skip to content

fix(objectql,runtime): the ADR-0110 D5 inventory is engine-owned — AppPlugin never ran it under os dev - #4011

Merged
os-zhuang merged 1 commit into
mainfrom
claude/rest-actions-type-dispatch-y5vqfj
Jul 30, 2026
Merged

fix(objectql,runtime): the ADR-0110 D5 inventory is engine-owned — AppPlugin never ran it under os dev#4011
os-zhuang merged 1 commit into
mainfrom
claude/rest-actions-type-dispatch-y5vqfj

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Found by dogfooding the inventory itself with a positive control — inject an undeclared handler into app-todo, boot, expect it named. It never was.

The defect

D5's inventory hung off a kernel:ready hook in AppPlugin, and AppPlugin is registered conditionally: serve.ts skips it when the host already wraps itself, and the os dev fast path loads apps without it entirely. A programmatic kernel (which does register AppPlugin) printed the inventory correctly; the platform's own dev loop never did. That matters beyond aesthetics: #3987 removed the migration valve on the argument that the boot inventory names every offender — a claim that was true nowhere a developer actually looks.

Silence was unfalsifiable by design — the inventory logs only when orphans exist — which is why this needed a positive control rather than a "boot it and grep" check.

The fix

Ownership follows the data. The map being audited is ObjectQL's own actions registry, so the audit moves to the engine plugin, which is unconditionally present wherever actions can execute:

  • The addressing vocabulary (GLOBAL_ACTION_OBJECT_KEY, actionHandlerObjectKeys, isObjectLessActionKey, resolveActionHandlerKeys) and reconcileActionRegistrations move into @objectstack/objectql. The dependency direction (runtime → objectql, never the reverse) permits no other home for code the engine plugin must call. Runtime re-exports them unchanged — dispatch, the MCP bridge and every existing importer keep reading ONE implementation, the property that stops the inventory from ever disagreeing with the router. (reconcileActionRegistrations keeps its old deps-taking signature in runtime as a thin wrapper.)
  • ObjectQLPlugin runs the audit inside its existing kernel:ready handler — after resyncAuthoredActions, so the registry it reads is final for the boot — and re-runs it on metadata:reloaded with fingerprint suppression: a Studio edit that orphans or binds a handler updates the report live; a reload that changed nothing action-related logs nothing. The AppPlugin version was boot-only (stale after the first Studio edit) and ran once per app instance over the same global registry (N apps → N identical reports).

Verification

  • Positive control, programmatic kernel: injected ghostProbe is named ([action-governance] … handlers: ['todo_task:ghostProbe']); reverting restores silence — so silence is now evidence, not absence of it.
  • 6 new tests in action-governance.test.ts (orphan naming, unbound-declaration naming, clean silence, standalone-item folding, fingerprint suppression across reloads, throw-proofing via a poisoned declarations source).
  • Runtime's 9 reconciliation tests pass unchanged through the wrapper. objectql / runtime / metadata suites green by exit code — except 3 pre-existing failures in plugin.step2.test.ts / protocol-discovery.test.ts (analytics-slot expectations) that reproduce on clean main, filed separately.

Known remaining gap, filed separately

os dev and os serve swallow all plugin boot-phase logs — a debug-level boot produces thousands of data-hook lines but zero bootstrap/kernel:ready lines and zero plugin WARNs, on both entrypoints. So this inventory (like every other boot diagnostic, including the flow/hook failures ADR-0032 promises to surface at the default warn level) is still invisible on the CLI surfaces until that sink is fixed. That is a pre-existing console-sink defect, not a hook-placement one — this PR fixes the half that was ours, and the issue tracks the half that isn't.

Also corrects app-todo's register-handlers.ts comment, which still described defer/reminder as "modal-type actions" with modal-page targets — the shape #3974 removed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DTTKgYriDB6RVrkYjxxnG1


Generated by Claude Code

…pPlugin never ran it under os dev

A positive-control dogfood (inject an undeclared handler, expect it named at
boot) proved the inventory's kernel:ready hook never fired on the platform's
own dev loop: AppPlugin is registered CONDITIONALLY — serve.ts skips it when
the host wraps itself, and the dev fast path loads apps without it — so the
checklist that justifies D3's no-opt-out refusal was never printed where an
upgrade most needs it.

The addressing vocabulary (GLOBAL_ACTION_OBJECT_KEY, actionHandlerObjectKeys,
isObjectLessActionKey, resolveActionHandlerKeys) and the reconciliation move
into @objectstack/objectql: the engine owns the map they describe, and the
dependency direction (runtime -> objectql, never the reverse) permits no
other home for code its plugin must call. Runtime re-exports them unchanged
(reconcileActionRegistrations keeps its deps-taking signature as a thin
wrapper), so dispatch, the MCP bridge and every existing importer keep
reading ONE implementation — the property that stops the inventory from ever
disagreeing with the router.

ObjectQLPlugin runs the audit in its existing kernel:ready handler, AFTER
resyncAuthoredActions so the registry is final, and re-runs it on
metadata:reloaded with fingerprint suppression — a Studio edit that orphans
or binds a handler updates the report live, and a reload that changed
nothing logs nothing. It also stops multiplying: the AppPlugin version ran
once per app instance over the same global registry.

Verified with a programmatic kernel: the injected orphan is named, a clean
registry is silent. os dev / os serve still swallow ALL plugin boot logs
(pre-existing console-sink defect, filed separately); on those surfaces the
inventory surfaces once that sink is fixed.

Also corrects app-todo's register-handlers comment, which still described
defer/reminder as 'modal-type actions' with modal-page targets — the shape
#3974 removed.

objectql 6 new tests; runtime action-reconciliation 9 via the wrapper;
objectql/runtime/metadata suites green by exit code (except 3 pre-existing
analytics failures reproduced on clean main, filed separately).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTTKgYriDB6RVrkYjxxnG1
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 30, 2026 2:41am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/runtime.

26 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via packages/runtime)
  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql, @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/runtime)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/objectql)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql, @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime)
  • content/docs/plugins/index.mdx (via @objectstack/objectql)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql, @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql, @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql)
  • content/docs/releases/implementation-status.mdx (via @objectstack/objectql, @objectstack/runtime)
  • content/docs/releases/v9.mdx (via @objectstack/objectql)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants