Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/action-governance-registry-rung.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
---
'@objectstack/objectql': patch
---

Startup `[action-governance]` resolves declarations through the same rungs the router does

The boot inventory built its declaration set from object-embedded `actions[]` plus the
metadata service's `action` rows. `resolveRouteActionDeclaration` resolves through a third
source between those two — the engine registry's standalone `action` items,
`registry.getItem('action', name)`, accepted when the item owns the route. On the in-process
boot (`new AppPlugin(...)` then `kernel.bootstrap()`), where the metadata plane holds no
`action` rows at all, every object-less `defineAction` was therefore reported as a
"registered handler with NO declaration — REFUSED at dispatch (ADR-0110 D3) and there is no
opt-out" in the same boot in which the router resolved it at that rung and dispatched it.
Both remedies the message offered were wrong for that shape: the action was already declared
with `defineAction`, and dropping the registration would have broken a working endpoint under
a green `pnpm validate`.

The registry rung is now injected into the audit by `ObjectQLPlugin` — the one caller holding
the engine, because objectql cannot import the router — and judged by the same ownership test
the router applies. The warning also stops asserting a dispatch outcome it never checked: it
names the three sources it read, says it did not dispatch, and points an author whose action
IS declared at the real bug instead of at deleting the registration. The other finding in the
block, `declared script actions with NO handler`, is unchanged in wording and in population.
178 changes: 178 additions & 0 deletions packages/objectql/src/action-governance.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,18 @@
* fingerprint-suppressed across `metadata:reloaded` re-runs, and that a
* failing declaration source degrades to a debug line instead of throwing —
* a diagnostic must never be the reason a kernel fails to boot.
*
* The second describe block pins the router's registry rung. The measured
* defect: on the in-process boot (`new AppPlugin(...)` then
* `kernel.bootstrap()`), `meta.loadMany('action')` answers `[]` while
* `registry.getItem('action', name)` answers the declaration, so every
* object-LESS `defineAction` was named as a "registered handler with NO
* declaration ... REFUSED at dispatch" in the same boot in which the router
* resolved it at rung 2 and dispatched it. Pinned here: the two boots (the
* registry holds it, the plane does not), both call forms (object-bound and
* object-less), a positive control that must stay reported, the ownership
* test that keeps the rung from clearing a foreign declaration, and the
* second warning holding its exact wording while the first changes.
*/

import { describe, it, expect, vi } from 'vitest';
Expand DownExpand Up@@ -120,3 +132,169 @@ describe('runActionGovernanceInventory (ADR-0110 D5)', () => {
);
});
});

describe('runActionGovernanceInventory — the router registry rung (#14123)', () => {
/** `registry.getItem('action', name)`, as the plugin injects it. */
const registryOf = (items: Record<string, any>) => (name: string) => items[name];

const applyAction = { name: 'duly_catalog_apply', type: 'script', locations: [] };

it('clears an object-LESS declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [], // no object embeds it
loadStandaloneActions: async () => [], // in-process boot: the plane is empty
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('clears an object-BOUND declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'todo_task', type: 'script' },
}),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('POSITIVE CONTROL — a handler no source declares is still named', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [
{ objectName: 'global', actionName: 'duly_catalog_apply' },
{ objectName: 'global', actionName: 'ghostProbe' },
{ objectName: 'todo_task', actionName: 'ghostBound' },
],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ count: 2, handlers: ['global:ghostProbe', 'todo_task:ghostBound'] }),
);
});

it('applies the router ownership test — a foreign object-bound item does not cover the route', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'crm_lead', type: 'script' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['todo_task:archive_task'] }),
);
});

it('stops asserting a dispatch outcome it did not check, and names the sources it did read', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'ghostProbe' }],
objects: [],
logger,
});

const [message] = logger.warn.mock.calls[0];
expect(message).not.toMatch(/REFUSED at dispatch/);
expect(message).not.toMatch(/there is no opt-out/);
expect(message).not.toMatch(/drop the registration/);
expect(message).toMatch(/it did not dispatch/);
expect(message).toMatch(/object-embedded `actions\[\]`/);
expect(message).toMatch(/the engine registry standalone `action` items/);
expect(message).toMatch(/the metadata service `action` rows/);
});

it('leaves the OTHER warning byte-identical — a registry item is not folded into the declaration set', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [],
objects: todoObjects,
lookupRegistryAction: registryOf({
// A registry-only script declaration with no handler anywhere. It must
// not join `unboundDeclarations`: the router never enumerates the
// registry, so neither does this audit.
orphan_action: { name: 'orphan_action', type: 'script', target: 'orphanHandler' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
'[action-governance] declared script actions with NO handler — a button wired to '
+ 'nothing (ADR-0078); add a `body`, or register a handler under the declared `target`',
{ count: 1, actions: ['todo_task:complete_task'] },
);
});

it('keeps the handler when the registry lookup throws — and never throws itself', async () => {
const logger = makeLogger();
await expect(runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: () => { throw new Error('registry unreadable'); },
logger,
})).resolves.toBeDefined();

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});

it('fingerprints the FILTERED set, so a rung-cleared boot reports and remembers nothing', async () => {
const logger = makeLogger();
const fp = await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(fp).toBe('');
expect(logger.warn).not.toHaveBeenCalled();

// The declaration disappears on a later reload: the finding is new, so it reports.
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({}),
logger,
lastFingerprint: fp,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
});

it('is unchanged when no rung is injected — two sources, and the finding stands', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});
});
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/action-governance-registry-rung.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
---
'@objectstack/objectql': patch
---

Startup `[action-governance]` resolves declarations through the same rungs the router does

The boot inventory built its declaration set from object-embedded `actions[]` plus the
metadata service's `action` rows. `resolveRouteActionDeclaration` resolves through a third
source between those two — the engine registry's standalone `action` items,
`registry.getItem('action', name)`, accepted when the item owns the route. On the in-process
boot (`new AppPlugin(...)` then `kernel.bootstrap()`), where the metadata plane holds no
`action` rows at all, every object-less `defineAction` was therefore reported as a
"registered handler with NO declaration — REFUSED at dispatch (ADR-0110 D3) and there is no
opt-out" in the same boot in which the router resolved it at that rung and dispatched it.
Both remedies the message offered were wrong for that shape: the action was already declared
with `defineAction`, and dropping the registration would have broken a working endpoint under
a green `pnpm validate`.

The registry rung is now injected into the audit by `ObjectQLPlugin` — the one caller holding
the engine, because objectql cannot import the router — and judged by the same ownership test
the router applies. The warning also stops asserting a dispatch outcome it never checked: it
names the three sources it read, says it did not dispatch, and points an author whose action
IS declared at the real bug instead of at deleting the registration. The other finding in the
block, `declared script actions with NO handler`, is unchanged in wording and in population.
178 changes: 178 additions & 0 deletions packages/objectql/src/action-governance.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,18 @@
* fingerprint-suppressed across `metadata:reloaded` re-runs, and that a
* failing declaration source degrades to a debug line instead of throwing —
* a diagnostic must never be the reason a kernel fails to boot.
*
* The second describe block pins the router's registry rung. The measured
* defect: on the in-process boot (`new AppPlugin(...)` then
* `kernel.bootstrap()`), `meta.loadMany('action')` answers `[]` while
* `registry.getItem('action', name)` answers the declaration, so every
* object-LESS `defineAction` was named as a "registered handler with NO
* declaration ... REFUSED at dispatch" in the same boot in which the router
* resolved it at rung 2 and dispatched it. Pinned here: the two boots (the
* registry holds it, the plane does not), both call forms (object-bound and
* object-less), a positive control that must stay reported, the ownership
* test that keeps the rung from clearing a foreign declaration, and the
* second warning holding its exact wording while the first changes.
*/

import { describe, it, expect, vi } from 'vitest';
Expand DownExpand Up@@ -120,3 +132,169 @@ describe('runActionGovernanceInventory (ADR-0110 D5)', () => {
);
});
});

describe('runActionGovernanceInventory — the router registry rung (#14123)', () => {
/** `registry.getItem('action', name)`, as the plugin injects it. */
const registryOf = (items: Record<string, any>) => (name: string) => items[name];

const applyAction = { name: 'duly_catalog_apply', type: 'script', locations: [] };

it('clears an object-LESS declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [], // no object embeds it
loadStandaloneActions: async () => [], // in-process boot: the plane is empty
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('clears an object-BOUND declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'todo_task', type: 'script' },
}),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('POSITIVE CONTROL — a handler no source declares is still named', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [
{ objectName: 'global', actionName: 'duly_catalog_apply' },
{ objectName: 'global', actionName: 'ghostProbe' },
{ objectName: 'todo_task', actionName: 'ghostBound' },
],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ count: 2, handlers: ['global:ghostProbe', 'todo_task:ghostBound'] }),
);
});

it('applies the router ownership test — a foreign object-bound item does not cover the route', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'crm_lead', type: 'script' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['todo_task:archive_task'] }),
);
});

it('stops asserting a dispatch outcome it did not check, and names the sources it did read', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'ghostProbe' }],
objects: [],
logger,
});

const [message] = logger.warn.mock.calls[0];
expect(message).not.toMatch(/REFUSED at dispatch/);
expect(message).not.toMatch(/there is no opt-out/);
expect(message).not.toMatch(/drop the registration/);
expect(message).toMatch(/it did not dispatch/);
expect(message).toMatch(/object-embedded `actions\[\]`/);
expect(message).toMatch(/the engine registry standalone `action` items/);
expect(message).toMatch(/the metadata service `action` rows/);
});

it('leaves the OTHER warning byte-identical — a registry item is not folded into the declaration set', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [],
objects: todoObjects,
lookupRegistryAction: registryOf({
// A registry-only script declaration with no handler anywhere. It must
// not join `unboundDeclarations`: the router never enumerates the
// registry, so neither does this audit.
orphan_action: { name: 'orphan_action', type: 'script', target: 'orphanHandler' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
'[action-governance] declared script actions with NO handler — a button wired to '
+ 'nothing (ADR-0078); add a `body`, or register a handler under the declared `target`',
{ count: 1, actions: ['todo_task:complete_task'] },
);
});

it('keeps the handler when the registry lookup throws — and never throws itself', async () => {
const logger = makeLogger();
await expect(runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: () => { throw new Error('registry unreadable'); },
logger,
})).resolves.toBeDefined();

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});

it('fingerprints the FILTERED set, so a rung-cleared boot reports and remembers nothing', async () => {
const logger = makeLogger();
const fp = await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(fp).toBe('');
expect(logger.warn).not.toHaveBeenCalled();

// The declaration disappears on a later reload: the finding is new, so it reports.
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({}),
logger,
lastFingerprint: fp,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
});

it('is unchanged when no rung is injected — two sources, and the finding stands', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});
});
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/action-governance-registry-rung.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
---
'@objectstack/objectql': patch
---

Startup `[action-governance]` resolves declarations through the same rungs the router does

The boot inventory built its declaration set from object-embedded `actions[]` plus the
metadata service's `action` rows. `resolveRouteActionDeclaration` resolves through a third
source between those two — the engine registry's standalone `action` items,
`registry.getItem('action', name)`, accepted when the item owns the route. On the in-process
boot (`new AppPlugin(...)` then `kernel.bootstrap()`), where the metadata plane holds no
`action` rows at all, every object-less `defineAction` was therefore reported as a
"registered handler with NO declaration — REFUSED at dispatch (ADR-0110 D3) and there is no
opt-out" in the same boot in which the router resolved it at that rung and dispatched it.
Both remedies the message offered were wrong for that shape: the action was already declared
with `defineAction`, and dropping the registration would have broken a working endpoint under
a green `pnpm validate`.

The registry rung is now injected into the audit by `ObjectQLPlugin` — the one caller holding
the engine, because objectql cannot import the router — and judged by the same ownership test
the router applies. The warning also stops asserting a dispatch outcome it never checked: it
names the three sources it read, says it did not dispatch, and points an author whose action
IS declared at the real bug instead of at deleting the registration. The other finding in the
block, `declared script actions with NO handler`, is unchanged in wording and in population.
178 changes: 178 additions & 0 deletions packages/objectql/src/action-governance.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,18 @@
* fingerprint-suppressed across `metadata:reloaded` re-runs, and that a
* failing declaration source degrades to a debug line instead of throwing —
* a diagnostic must never be the reason a kernel fails to boot.
*
* The second describe block pins the router's registry rung. The measured
* defect: on the in-process boot (`new AppPlugin(...)` then
* `kernel.bootstrap()`), `meta.loadMany('action')` answers `[]` while
* `registry.getItem('action', name)` answers the declaration, so every
* object-LESS `defineAction` was named as a "registered handler with NO
* declaration ... REFUSED at dispatch" in the same boot in which the router
* resolved it at rung 2 and dispatched it. Pinned here: the two boots (the
* registry holds it, the plane does not), both call forms (object-bound and
* object-less), a positive control that must stay reported, the ownership
* test that keeps the rung from clearing a foreign declaration, and the
* second warning holding its exact wording while the first changes.
*/

import { describe, it, expect, vi } from 'vitest';
Expand DownExpand Up@@ -120,3 +132,169 @@ describe('runActionGovernanceInventory (ADR-0110 D5)', () => {
);
});
});

describe('runActionGovernanceInventory — the router registry rung (#14123)', () => {
/** `registry.getItem('action', name)`, as the plugin injects it. */
const registryOf = (items: Record<string, any>) => (name: string) => items[name];

const applyAction = { name: 'duly_catalog_apply', type: 'script', locations: [] };

it('clears an object-LESS declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [], // no object embeds it
loadStandaloneActions: async () => [], // in-process boot: the plane is empty
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('clears an object-BOUND declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'todo_task', type: 'script' },
}),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('POSITIVE CONTROL — a handler no source declares is still named', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [
{ objectName: 'global', actionName: 'duly_catalog_apply' },
{ objectName: 'global', actionName: 'ghostProbe' },
{ objectName: 'todo_task', actionName: 'ghostBound' },
],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ count: 2, handlers: ['global:ghostProbe', 'todo_task:ghostBound'] }),
);
});

it('applies the router ownership test — a foreign object-bound item does not cover the route', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'crm_lead', type: 'script' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['todo_task:archive_task'] }),
);
});

it('stops asserting a dispatch outcome it did not check, and names the sources it did read', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'ghostProbe' }],
objects: [],
logger,
});

const [message] = logger.warn.mock.calls[0];
expect(message).not.toMatch(/REFUSED at dispatch/);
expect(message).not.toMatch(/there is no opt-out/);
expect(message).not.toMatch(/drop the registration/);
expect(message).toMatch(/it did not dispatch/);
expect(message).toMatch(/object-embedded `actions\[\]`/);
expect(message).toMatch(/the engine registry standalone `action` items/);
expect(message).toMatch(/the metadata service `action` rows/);
});

it('leaves the OTHER warning byte-identical — a registry item is not folded into the declaration set', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [],
objects: todoObjects,
lookupRegistryAction: registryOf({
// A registry-only script declaration with no handler anywhere. It must
// not join `unboundDeclarations`: the router never enumerates the
// registry, so neither does this audit.
orphan_action: { name: 'orphan_action', type: 'script', target: 'orphanHandler' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
'[action-governance] declared script actions with NO handler — a button wired to '
+ 'nothing (ADR-0078); add a `body`, or register a handler under the declared `target`',
{ count: 1, actions: ['todo_task:complete_task'] },
);
});

it('keeps the handler when the registry lookup throws — and never throws itself', async () => {
const logger = makeLogger();
await expect(runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: () => { throw new Error('registry unreadable'); },
logger,
})).resolves.toBeDefined();

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});

it('fingerprints the FILTERED set, so a rung-cleared boot reports and remembers nothing', async () => {
const logger = makeLogger();
const fp = await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(fp).toBe('');
expect(logger.warn).not.toHaveBeenCalled();

// The declaration disappears on a later reload: the finding is new, so it reports.
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({}),
logger,
lastFingerprint: fp,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
});

it('is unchanged when no rung is injected — two sources, and the finding stands', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});
});
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/action-governance-registry-rung.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
---
'@objectstack/objectql': patch
---

Startup `[action-governance]` resolves declarations through the same rungs the router does

The boot inventory built its declaration set from object-embedded `actions[]` plus the
metadata service's `action` rows. `resolveRouteActionDeclaration` resolves through a third
source between those two — the engine registry's standalone `action` items,
`registry.getItem('action', name)`, accepted when the item owns the route. On the in-process
boot (`new AppPlugin(...)` then `kernel.bootstrap()`), where the metadata plane holds no
`action` rows at all, every object-less `defineAction` was therefore reported as a
"registered handler with NO declaration — REFUSED at dispatch (ADR-0110 D3) and there is no
opt-out" in the same boot in which the router resolved it at that rung and dispatched it.
Both remedies the message offered were wrong for that shape: the action was already declared
with `defineAction`, and dropping the registration would have broken a working endpoint under
a green `pnpm validate`.

The registry rung is now injected into the audit by `ObjectQLPlugin` — the one caller holding
the engine, because objectql cannot import the router — and judged by the same ownership test
the router applies. The warning also stops asserting a dispatch outcome it never checked: it
names the three sources it read, says it did not dispatch, and points an author whose action
IS declared at the real bug instead of at deleting the registration. The other finding in the
block, `declared script actions with NO handler`, is unchanged in wording and in population.
178 changes: 178 additions & 0 deletions packages/objectql/src/action-governance.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,18 @@
* fingerprint-suppressed across `metadata:reloaded` re-runs, and that a
* failing declaration source degrades to a debug line instead of throwing —
* a diagnostic must never be the reason a kernel fails to boot.
*
* The second describe block pins the router's registry rung. The measured
* defect: on the in-process boot (`new AppPlugin(...)` then
* `kernel.bootstrap()`), `meta.loadMany('action')` answers `[]` while
* `registry.getItem('action', name)` answers the declaration, so every
* object-LESS `defineAction` was named as a "registered handler with NO
* declaration ... REFUSED at dispatch" in the same boot in which the router
* resolved it at rung 2 and dispatched it. Pinned here: the two boots (the
* registry holds it, the plane does not), both call forms (object-bound and
* object-less), a positive control that must stay reported, the ownership
* test that keeps the rung from clearing a foreign declaration, and the
* second warning holding its exact wording while the first changes.
*/

import { describe, it, expect, vi } from 'vitest';
Expand DownExpand Up@@ -120,3 +132,169 @@ describe('runActionGovernanceInventory (ADR-0110 D5)', () => {
);
});
});

describe('runActionGovernanceInventory — the router registry rung (#14123)', () => {
/** `registry.getItem('action', name)`, as the plugin injects it. */
const registryOf = (items: Record<string, any>) => (name: string) => items[name];

const applyAction = { name: 'duly_catalog_apply', type: 'script', locations: [] };

it('clears an object-LESS declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [], // no object embeds it
loadStandaloneActions: async () => [], // in-process boot: the plane is empty
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('clears an object-BOUND declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'todo_task', type: 'script' },
}),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('POSITIVE CONTROL — a handler no source declares is still named', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [
{ objectName: 'global', actionName: 'duly_catalog_apply' },
{ objectName: 'global', actionName: 'ghostProbe' },
{ objectName: 'todo_task', actionName: 'ghostBound' },
],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ count: 2, handlers: ['global:ghostProbe', 'todo_task:ghostBound'] }),
);
});

it('applies the router ownership test — a foreign object-bound item does not cover the route', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'crm_lead', type: 'script' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['todo_task:archive_task'] }),
);
});

it('stops asserting a dispatch outcome it did not check, and names the sources it did read', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'ghostProbe' }],
objects: [],
logger,
});

const [message] = logger.warn.mock.calls[0];
expect(message).not.toMatch(/REFUSED at dispatch/);
expect(message).not.toMatch(/there is no opt-out/);
expect(message).not.toMatch(/drop the registration/);
expect(message).toMatch(/it did not dispatch/);
expect(message).toMatch(/object-embedded `actions\[\]`/);
expect(message).toMatch(/the engine registry standalone `action` items/);
expect(message).toMatch(/the metadata service `action` rows/);
});

it('leaves the OTHER warning byte-identical — a registry item is not folded into the declaration set', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [],
objects: todoObjects,
lookupRegistryAction: registryOf({
// A registry-only script declaration with no handler anywhere. It must
// not join `unboundDeclarations`: the router never enumerates the
// registry, so neither does this audit.
orphan_action: { name: 'orphan_action', type: 'script', target: 'orphanHandler' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
'[action-governance] declared script actions with NO handler — a button wired to '
+ 'nothing (ADR-0078); add a `body`, or register a handler under the declared `target`',
{ count: 1, actions: ['todo_task:complete_task'] },
);
});

it('keeps the handler when the registry lookup throws — and never throws itself', async () => {
const logger = makeLogger();
await expect(runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: () => { throw new Error('registry unreadable'); },
logger,
})).resolves.toBeDefined();

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});

it('fingerprints the FILTERED set, so a rung-cleared boot reports and remembers nothing', async () => {
const logger = makeLogger();
const fp = await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(fp).toBe('');
expect(logger.warn).not.toHaveBeenCalled();

// The declaration disappears on a later reload: the finding is new, so it reports.
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({}),
logger,
lastFingerprint: fp,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
});

it('is unchanged when no rung is injected — two sources, and the finding stands', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});
});
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/action-governance-registry-rung.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
---
'@objectstack/objectql': patch
---

Startup `[action-governance]` resolves declarations through the same rungs the router does

The boot inventory built its declaration set from object-embedded `actions[]` plus the
metadata service's `action` rows. `resolveRouteActionDeclaration` resolves through a third
source between those two — the engine registry's standalone `action` items,
`registry.getItem('action', name)`, accepted when the item owns the route. On the in-process
boot (`new AppPlugin(...)` then `kernel.bootstrap()`), where the metadata plane holds no
`action` rows at all, every object-less `defineAction` was therefore reported as a
"registered handler with NO declaration — REFUSED at dispatch (ADR-0110 D3) and there is no
opt-out" in the same boot in which the router resolved it at that rung and dispatched it.
Both remedies the message offered were wrong for that shape: the action was already declared
with `defineAction`, and dropping the registration would have broken a working endpoint under
a green `pnpm validate`.

The registry rung is now injected into the audit by `ObjectQLPlugin` — the one caller holding
the engine, because objectql cannot import the router — and judged by the same ownership test
the router applies. The warning also stops asserting a dispatch outcome it never checked: it
names the three sources it read, says it did not dispatch, and points an author whose action
IS declared at the real bug instead of at deleting the registration. The other finding in the
block, `declared script actions with NO handler`, is unchanged in wording and in population.
178 changes: 178 additions & 0 deletions packages/objectql/src/action-governance.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,18 @@
* fingerprint-suppressed across `metadata:reloaded` re-runs, and that a
* failing declaration source degrades to a debug line instead of throwing —
* a diagnostic must never be the reason a kernel fails to boot.
*
* The second describe block pins the router's registry rung. The measured
* defect: on the in-process boot (`new AppPlugin(...)` then
* `kernel.bootstrap()`), `meta.loadMany('action')` answers `[]` while
* `registry.getItem('action', name)` answers the declaration, so every
* object-LESS `defineAction` was named as a "registered handler with NO
* declaration ... REFUSED at dispatch" in the same boot in which the router
* resolved it at rung 2 and dispatched it. Pinned here: the two boots (the
* registry holds it, the plane does not), both call forms (object-bound and
* object-less), a positive control that must stay reported, the ownership
* test that keeps the rung from clearing a foreign declaration, and the
* second warning holding its exact wording while the first changes.
*/

import { describe, it, expect, vi } from 'vitest';
Expand DownExpand Up@@ -120,3 +132,169 @@ describe('runActionGovernanceInventory (ADR-0110 D5)', () => {
);
});
});

describe('runActionGovernanceInventory — the router registry rung (#14123)', () => {
/** `registry.getItem('action', name)`, as the plugin injects it. */
const registryOf = (items: Record<string, any>) => (name: string) => items[name];

const applyAction = { name: 'duly_catalog_apply', type: 'script', locations: [] };

it('clears an object-LESS declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [], // no object embeds it
loadStandaloneActions: async () => [], // in-process boot: the plane is empty
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('clears an object-BOUND declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'todo_task', type: 'script' },
}),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('POSITIVE CONTROL — a handler no source declares is still named', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [
{ objectName: 'global', actionName: 'duly_catalog_apply' },
{ objectName: 'global', actionName: 'ghostProbe' },
{ objectName: 'todo_task', actionName: 'ghostBound' },
],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ count: 2, handlers: ['global:ghostProbe', 'todo_task:ghostBound'] }),
);
});

it('applies the router ownership test — a foreign object-bound item does not cover the route', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'crm_lead', type: 'script' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['todo_task:archive_task'] }),
);
});

it('stops asserting a dispatch outcome it did not check, and names the sources it did read', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'ghostProbe' }],
objects: [],
logger,
});

const [message] = logger.warn.mock.calls[0];
expect(message).not.toMatch(/REFUSED at dispatch/);
expect(message).not.toMatch(/there is no opt-out/);
expect(message).not.toMatch(/drop the registration/);
expect(message).toMatch(/it did not dispatch/);
expect(message).toMatch(/object-embedded `actions\[\]`/);
expect(message).toMatch(/the engine registry standalone `action` items/);
expect(message).toMatch(/the metadata service `action` rows/);
});

it('leaves the OTHER warning byte-identical — a registry item is not folded into the declaration set', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [],
objects: todoObjects,
lookupRegistryAction: registryOf({
// A registry-only script declaration with no handler anywhere. It must
// not join `unboundDeclarations`: the router never enumerates the
// registry, so neither does this audit.
orphan_action: { name: 'orphan_action', type: 'script', target: 'orphanHandler' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
'[action-governance] declared script actions with NO handler — a button wired to '
+ 'nothing (ADR-0078); add a `body`, or register a handler under the declared `target`',
{ count: 1, actions: ['todo_task:complete_task'] },
);
});

it('keeps the handler when the registry lookup throws — and never throws itself', async () => {
const logger = makeLogger();
await expect(runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: () => { throw new Error('registry unreadable'); },
logger,
})).resolves.toBeDefined();

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});

it('fingerprints the FILTERED set, so a rung-cleared boot reports and remembers nothing', async () => {
const logger = makeLogger();
const fp = await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(fp).toBe('');
expect(logger.warn).not.toHaveBeenCalled();

// The declaration disappears on a later reload: the finding is new, so it reports.
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({}),
logger,
lastFingerprint: fp,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
});

it('is unchanged when no rung is injected — two sources, and the finding stands', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});
});
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/action-governance-registry-rung.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
---
'@objectstack/objectql': patch
---

Startup `[action-governance]` resolves declarations through the same rungs the router does

The boot inventory built its declaration set from object-embedded `actions[]` plus the
metadata service's `action` rows. `resolveRouteActionDeclaration` resolves through a third
source between those two — the engine registry's standalone `action` items,
`registry.getItem('action', name)`, accepted when the item owns the route. On the in-process
boot (`new AppPlugin(...)` then `kernel.bootstrap()`), where the metadata plane holds no
`action` rows at all, every object-less `defineAction` was therefore reported as a
"registered handler with NO declaration — REFUSED at dispatch (ADR-0110 D3) and there is no
opt-out" in the same boot in which the router resolved it at that rung and dispatched it.
Both remedies the message offered were wrong for that shape: the action was already declared
with `defineAction`, and dropping the registration would have broken a working endpoint under
a green `pnpm validate`.

The registry rung is now injected into the audit by `ObjectQLPlugin` — the one caller holding
the engine, because objectql cannot import the router — and judged by the same ownership test
the router applies. The warning also stops asserting a dispatch outcome it never checked: it
names the three sources it read, says it did not dispatch, and points an author whose action
IS declared at the real bug instead of at deleting the registration. The other finding in the
block, `declared script actions with NO handler`, is unchanged in wording and in population.
178 changes: 178 additions & 0 deletions packages/objectql/src/action-governance.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,18 @@
* fingerprint-suppressed across `metadata:reloaded` re-runs, and that a
* failing declaration source degrades to a debug line instead of throwing —
* a diagnostic must never be the reason a kernel fails to boot.
*
* The second describe block pins the router's registry rung. The measured
* defect: on the in-process boot (`new AppPlugin(...)` then
* `kernel.bootstrap()`), `meta.loadMany('action')` answers `[]` while
* `registry.getItem('action', name)` answers the declaration, so every
* object-LESS `defineAction` was named as a "registered handler with NO
* declaration ... REFUSED at dispatch" in the same boot in which the router
* resolved it at rung 2 and dispatched it. Pinned here: the two boots (the
* registry holds it, the plane does not), both call forms (object-bound and
* object-less), a positive control that must stay reported, the ownership
* test that keeps the rung from clearing a foreign declaration, and the
* second warning holding its exact wording while the first changes.
*/

import { describe, it, expect, vi } from 'vitest';
Expand DownExpand Up@@ -120,3 +132,169 @@ describe('runActionGovernanceInventory (ADR-0110 D5)', () => {
);
});
});

describe('runActionGovernanceInventory — the router registry rung (#14123)', () => {
/** `registry.getItem('action', name)`, as the plugin injects it. */
const registryOf = (items: Record<string, any>) => (name: string) => items[name];

const applyAction = { name: 'duly_catalog_apply', type: 'script', locations: [] };

it('clears an object-LESS declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [], // no object embeds it
loadStandaloneActions: async () => [], // in-process boot: the plane is empty
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('clears an object-BOUND declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'todo_task', type: 'script' },
}),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('POSITIVE CONTROL — a handler no source declares is still named', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [
{ objectName: 'global', actionName: 'duly_catalog_apply' },
{ objectName: 'global', actionName: 'ghostProbe' },
{ objectName: 'todo_task', actionName: 'ghostBound' },
],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ count: 2, handlers: ['global:ghostProbe', 'todo_task:ghostBound'] }),
);
});

it('applies the router ownership test — a foreign object-bound item does not cover the route', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'crm_lead', type: 'script' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['todo_task:archive_task'] }),
);
});

it('stops asserting a dispatch outcome it did not check, and names the sources it did read', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'ghostProbe' }],
objects: [],
logger,
});

const [message] = logger.warn.mock.calls[0];
expect(message).not.toMatch(/REFUSED at dispatch/);
expect(message).not.toMatch(/there is no opt-out/);
expect(message).not.toMatch(/drop the registration/);
expect(message).toMatch(/it did not dispatch/);
expect(message).toMatch(/object-embedded `actions\[\]`/);
expect(message).toMatch(/the engine registry standalone `action` items/);
expect(message).toMatch(/the metadata service `action` rows/);
});

it('leaves the OTHER warning byte-identical — a registry item is not folded into the declaration set', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [],
objects: todoObjects,
lookupRegistryAction: registryOf({
// A registry-only script declaration with no handler anywhere. It must
// not join `unboundDeclarations`: the router never enumerates the
// registry, so neither does this audit.
orphan_action: { name: 'orphan_action', type: 'script', target: 'orphanHandler' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
'[action-governance] declared script actions with NO handler — a button wired to '
+ 'nothing (ADR-0078); add a `body`, or register a handler under the declared `target`',
{ count: 1, actions: ['todo_task:complete_task'] },
);
});

it('keeps the handler when the registry lookup throws — and never throws itself', async () => {
const logger = makeLogger();
await expect(runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: () => { throw new Error('registry unreadable'); },
logger,
})).resolves.toBeDefined();

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});

it('fingerprints the FILTERED set, so a rung-cleared boot reports and remembers nothing', async () => {
const logger = makeLogger();
const fp = await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(fp).toBe('');
expect(logger.warn).not.toHaveBeenCalled();

// The declaration disappears on a later reload: the finding is new, so it reports.
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({}),
logger,
lastFingerprint: fp,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
});

it('is unchanged when no rung is injected — two sources, and the finding stands', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});
});
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/action-governance-registry-rung.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
---
'@objectstack/objectql': patch
---

Startup `[action-governance]` resolves declarations through the same rungs the router does

The boot inventory built its declaration set from object-embedded `actions[]` plus the
metadata service's `action` rows. `resolveRouteActionDeclaration` resolves through a third
source between those two — the engine registry's standalone `action` items,
`registry.getItem('action', name)`, accepted when the item owns the route. On the in-process
boot (`new AppPlugin(...)` then `kernel.bootstrap()`), where the metadata plane holds no
`action` rows at all, every object-less `defineAction` was therefore reported as a
"registered handler with NO declaration — REFUSED at dispatch (ADR-0110 D3) and there is no
opt-out" in the same boot in which the router resolved it at that rung and dispatched it.
Both remedies the message offered were wrong for that shape: the action was already declared
with `defineAction`, and dropping the registration would have broken a working endpoint under
a green `pnpm validate`.

The registry rung is now injected into the audit by `ObjectQLPlugin` — the one caller holding
the engine, because objectql cannot import the router — and judged by the same ownership test
the router applies. The warning also stops asserting a dispatch outcome it never checked: it
names the three sources it read, says it did not dispatch, and points an author whose action
IS declared at the real bug instead of at deleting the registration. The other finding in the
block, `declared script actions with NO handler`, is unchanged in wording and in population.
178 changes: 178 additions & 0 deletions packages/objectql/src/action-governance.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,18 @@
* fingerprint-suppressed across `metadata:reloaded` re-runs, and that a
* failing declaration source degrades to a debug line instead of throwing —
* a diagnostic must never be the reason a kernel fails to boot.
*
* The second describe block pins the router's registry rung. The measured
* defect: on the in-process boot (`new AppPlugin(...)` then
* `kernel.bootstrap()`), `meta.loadMany('action')` answers `[]` while
* `registry.getItem('action', name)` answers the declaration, so every
* object-LESS `defineAction` was named as a "registered handler with NO
* declaration ... REFUSED at dispatch" in the same boot in which the router
* resolved it at rung 2 and dispatched it. Pinned here: the two boots (the
* registry holds it, the plane does not), both call forms (object-bound and
* object-less), a positive control that must stay reported, the ownership
* test that keeps the rung from clearing a foreign declaration, and the
* second warning holding its exact wording while the first changes.
*/

import { describe, it, expect, vi } from 'vitest';
Expand DownExpand Up@@ -120,3 +132,169 @@ describe('runActionGovernanceInventory (ADR-0110 D5)', () => {
);
});
});

describe('runActionGovernanceInventory — the router registry rung (#14123)', () => {
/** `registry.getItem('action', name)`, as the plugin injects it. */
const registryOf = (items: Record<string, any>) => (name: string) => items[name];

const applyAction = { name: 'duly_catalog_apply', type: 'script', locations: [] };

it('clears an object-LESS declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [], // no object embeds it
loadStandaloneActions: async () => [], // in-process boot: the plane is empty
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('clears an object-BOUND declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'todo_task', type: 'script' },
}),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('POSITIVE CONTROL — a handler no source declares is still named', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [
{ objectName: 'global', actionName: 'duly_catalog_apply' },
{ objectName: 'global', actionName: 'ghostProbe' },
{ objectName: 'todo_task', actionName: 'ghostBound' },
],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ count: 2, handlers: ['global:ghostProbe', 'todo_task:ghostBound'] }),
);
});

it('applies the router ownership test — a foreign object-bound item does not cover the route', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'crm_lead', type: 'script' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['todo_task:archive_task'] }),
);
});

it('stops asserting a dispatch outcome it did not check, and names the sources it did read', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'ghostProbe' }],
objects: [],
logger,
});

const [message] = logger.warn.mock.calls[0];
expect(message).not.toMatch(/REFUSED at dispatch/);
expect(message).not.toMatch(/there is no opt-out/);
expect(message).not.toMatch(/drop the registration/);
expect(message).toMatch(/it did not dispatch/);
expect(message).toMatch(/object-embedded `actions\[\]`/);
expect(message).toMatch(/the engine registry standalone `action` items/);
expect(message).toMatch(/the metadata service `action` rows/);
});

it('leaves the OTHER warning byte-identical — a registry item is not folded into the declaration set', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [],
objects: todoObjects,
lookupRegistryAction: registryOf({
// A registry-only script declaration with no handler anywhere. It must
// not join `unboundDeclarations`: the router never enumerates the
// registry, so neither does this audit.
orphan_action: { name: 'orphan_action', type: 'script', target: 'orphanHandler' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
'[action-governance] declared script actions with NO handler — a button wired to '
+ 'nothing (ADR-0078); add a `body`, or register a handler under the declared `target`',
{ count: 1, actions: ['todo_task:complete_task'] },
);
});

it('keeps the handler when the registry lookup throws — and never throws itself', async () => {
const logger = makeLogger();
await expect(runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: () => { throw new Error('registry unreadable'); },
logger,
})).resolves.toBeDefined();

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});

it('fingerprints the FILTERED set, so a rung-cleared boot reports and remembers nothing', async () => {
const logger = makeLogger();
const fp = await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(fp).toBe('');
expect(logger.warn).not.toHaveBeenCalled();

// The declaration disappears on a later reload: the finding is new, so it reports.
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({}),
logger,
lastFingerprint: fp,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
});

it('is unchanged when no rung is injected — two sources, and the finding stands', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});
});
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/action-governance-registry-rung.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
---
'@objectstack/objectql': patch
---

Startup `[action-governance]` resolves declarations through the same rungs the router does

The boot inventory built its declaration set from object-embedded `actions[]` plus the
metadata service's `action` rows. `resolveRouteActionDeclaration` resolves through a third
source between those two — the engine registry's standalone `action` items,
`registry.getItem('action', name)`, accepted when the item owns the route. On the in-process
boot (`new AppPlugin(...)` then `kernel.bootstrap()`), where the metadata plane holds no
`action` rows at all, every object-less `defineAction` was therefore reported as a
"registered handler with NO declaration — REFUSED at dispatch (ADR-0110 D3) and there is no
opt-out" in the same boot in which the router resolved it at that rung and dispatched it.
Both remedies the message offered were wrong for that shape: the action was already declared
with `defineAction`, and dropping the registration would have broken a working endpoint under
a green `pnpm validate`.

The registry rung is now injected into the audit by `ObjectQLPlugin` — the one caller holding
the engine, because objectql cannot import the router — and judged by the same ownership test
the router applies. The warning also stops asserting a dispatch outcome it never checked: it
names the three sources it read, says it did not dispatch, and points an author whose action
IS declared at the real bug instead of at deleting the registration. The other finding in the
block, `declared script actions with NO handler`, is unchanged in wording and in population.
178 changes: 178 additions & 0 deletions packages/objectql/src/action-governance.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,18 @@
* fingerprint-suppressed across `metadata:reloaded` re-runs, and that a
* failing declaration source degrades to a debug line instead of throwing —
* a diagnostic must never be the reason a kernel fails to boot.
*
* The second describe block pins the router's registry rung. The measured
* defect: on the in-process boot (`new AppPlugin(...)` then
* `kernel.bootstrap()`), `meta.loadMany('action')` answers `[]` while
* `registry.getItem('action', name)` answers the declaration, so every
* object-LESS `defineAction` was named as a "registered handler with NO
* declaration ... REFUSED at dispatch" in the same boot in which the router
* resolved it at rung 2 and dispatched it. Pinned here: the two boots (the
* registry holds it, the plane does not), both call forms (object-bound and
* object-less), a positive control that must stay reported, the ownership
* test that keeps the rung from clearing a foreign declaration, and the
* second warning holding its exact wording while the first changes.
*/

import { describe, it, expect, vi } from 'vitest';
Expand DownExpand Up@@ -120,3 +132,169 @@ describe('runActionGovernanceInventory (ADR-0110 D5)', () => {
);
});
});

describe('runActionGovernanceInventory — the router registry rung (#14123)', () => {
/** `registry.getItem('action', name)`, as the plugin injects it. */
const registryOf = (items: Record<string, any>) => (name: string) => items[name];

const applyAction = { name: 'duly_catalog_apply', type: 'script', locations: [] };

it('clears an object-LESS declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [], // no object embeds it
loadStandaloneActions: async () => [], // in-process boot: the plane is empty
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('clears an object-BOUND declaration the registry holds and the plane does not', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'todo_task', type: 'script' },
}),
logger,
});

expect(logger.warn).not.toHaveBeenCalled();
});

it('POSITIVE CONTROL — a handler no source declares is still named', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [
{ objectName: 'global', actionName: 'duly_catalog_apply' },
{ objectName: 'global', actionName: 'ghostProbe' },
{ objectName: 'todo_task', actionName: 'ghostBound' },
],
objects: [{ name: 'todo_task', actions: [] }],
loadStandaloneActions: async () => [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ count: 2, handlers: ['global:ghostProbe', 'todo_task:ghostBound'] }),
);
});

it('applies the router ownership test — a foreign object-bound item does not cover the route', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'todo_task', actionName: 'archive_task' }],
objects: [{ name: 'todo_task', actions: [] }],
lookupRegistryAction: registryOf({
archive_task: { name: 'archive_task', objectName: 'crm_lead', type: 'script' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['todo_task:archive_task'] }),
);
});

it('stops asserting a dispatch outcome it did not check, and names the sources it did read', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'ghostProbe' }],
objects: [],
logger,
});

const [message] = logger.warn.mock.calls[0];
expect(message).not.toMatch(/REFUSED at dispatch/);
expect(message).not.toMatch(/there is no opt-out/);
expect(message).not.toMatch(/drop the registration/);
expect(message).toMatch(/it did not dispatch/);
expect(message).toMatch(/object-embedded `actions\[\]`/);
expect(message).toMatch(/the engine registry standalone `action` items/);
expect(message).toMatch(/the metadata service `action` rows/);
});

it('leaves the OTHER warning byte-identical — a registry item is not folded into the declaration set', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [],
objects: todoObjects,
lookupRegistryAction: registryOf({
// A registry-only script declaration with no handler anywhere. It must
// not join `unboundDeclarations`: the router never enumerates the
// registry, so neither does this audit.
orphan_action: { name: 'orphan_action', type: 'script', target: 'orphanHandler' },
}),
logger,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
'[action-governance] declared script actions with NO handler — a button wired to '
+ 'nothing (ADR-0078); add a `body`, or register a handler under the declared `target`',
{ count: 1, actions: ['todo_task:complete_task'] },
);
});

it('keeps the handler when the registry lookup throws — and never throws itself', async () => {
const logger = makeLogger();
await expect(runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: () => { throw new Error('registry unreadable'); },
logger,
})).resolves.toBeDefined();

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});

it('fingerprints the FILTERED set, so a rung-cleared boot reports and remembers nothing', async () => {
const logger = makeLogger();
const fp = await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({ duly_catalog_apply: applyAction }),
logger,
});

expect(fp).toBe('');
expect(logger.warn).not.toHaveBeenCalled();

// The declaration disappears on a later reload: the finding is new, so it reports.
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
lookupRegistryAction: registryOf({}),
logger,
lastFingerprint: fp,
});

expect(logger.warn).toHaveBeenCalledTimes(1);
});

it('is unchanged when no rung is injected — two sources, and the finding stands', async () => {
const logger = makeLogger();
await runActionGovernanceInventory({
registered: [{ objectName: 'global', actionName: 'duly_catalog_apply' }],
objects: [],
logger,
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(/registered handlers with NO declaration/),
expect.objectContaining({ handlers: ['global:duly_catalog_apply'] }),
);
});
});
Loading
Loading