refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling - #14667

Merged
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key
Sep 2, 2026
Merged

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling#14667
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key

Conversation

@os-musk

Copy link
Copy Markdown
Collaborator

Fixes#14422

The action.objectName -> action.object -> object-less 'global' ladder that decides which engine key a standalone action declaration is filed under was written out three times, held equal only by a sentence in each docblock promising lockstep. All three now resolve to one implementation.

What changed

  1. packages/objectql/src/plugin.ts — the private actionObjectKey is deleted and its callers now call standaloneActionOwnerKey directly (same package, no dependency question).
  2. packages/runtime/src/action-execution.tsstandaloneActionOwnerKey joins the ADR-0110 re-export block that already exists in that file for exactly this purpose, and standaloneActionObjectName becomes a delegating alias whose whole body is return standaloneActionOwnerKey(action);.
  3. packages/objectql/src/action-governance.ts — the docblock sentences naming the copies are gone, replaced by a statement of where the one implementation lives and a note not to re-inline it.

The convergence, proved rather than asserted

Rungs 1 and 2 of the ladder, counted across packages/objectql/src and packages/runtime/src:

rung 1 (objectName)rung 2 (object)
before (control), 13bf05d3f33
after11

The one surviving carrier is action-governance.ts:92-93. standaloneActionObjectName keeps no body of its own, and no actionObjectKey symbol survives in either package.

The divergence this removes, which the card did not name

Copy 3 did not merely spell the ladder again. Its terminal rung returned the bare string literal 'global' (plugin.ts:2242 on origin/main), while copies 1 and 2 both return the shared GLOBAL_ACTION_OBJECT_KEY constant. Re-measured here on 13bf05d3f: the constant is declared 'global' at action-governance.ts:61, and all three ladders were driven over a twelve-row truth table (both rungs, the empty-string and wrong-type fall-throughs, null and undefined actions) and agreed on 12 of 12 inputs. So nothing was broken and this stayed a convergence card, not a defect card.

What copy 3 was, was the one spelling that would have parted from the other two in silence the day that constant's value changed — equal in value, invisible to every test in the repo. The same bare literal in the plugin's isArtifactShippedAction reader (objectKey !== 'global') is converged to the constant with it, so the file no longer carries a spelling that can drift from the constant it is supposed to track.

Three dispatch assumptions that measured differently

  • Call sites: four, not one. The triage named plugin.ts:2260. actionObjectKey actually had four call sites — :2260, :2612, :2615 and :2653. The deletion is still right; all four now call the canonical helper.
  • The export had to be published first.standaloneActionOwnerKey was not on @objectstack/objectql's published entry, so the runtime could not import it from the package root at all. Adding it to packages/objectql/src/index.ts is a fourth file, mechanically forced by step 2, and it is what makes objectql the package whose surface grows.
  • The minor lands on objectql, not on runtime. Measured in the built artifacts with a positive control: standaloneActionOwnerKey is declared and exported in packages/objectql/dist/index.d.ts, while standaloneActionOwnerKey, standaloneActionObjectName and GLOBAL_ACTION_OBJECT_KEY each appear 0 times in packages/runtime/dist/index.d.ts against a control of 32 for HttpDispatcheraction-execution.ts is not re-exported from packages/runtime/src/index.ts and the package publishes only .. Hence @objectstack/objectql: minor, @objectstack/runtime: patch.

The _deps decision

Kept, as a delegating alias. The engine helper is standaloneActionOwnerKey(action); the runtime name is standaloneActionObjectName(_deps, action). Every caller was measured first: exactly two, both inside action-execution.ts itself (:1524, :1699), no test importer, no other package. Dropping _deps would move an exported signature to save two characters at two internal call sites, so the alias keeps its arity and its meaning and ownsRoute plus any out-of-repo importer compile and behave exactly as before.

Reverse verification

The docblocks that promised lockstep are replaced by welds that enforce it — action-owner-key-single-source.test.ts in each package, each scoped to its own package's source so neither becomes a cross-package test input. Both were ablated from the committed tree, each mutation confirmed on disk by grep counts before the run and each restore proved by hash equality with the HEAD blob plus an empty git diff HEAD:

  • Re-inline the ladder into the runtime alias (byte-identical behaviour): half A stayed green, 11 passed — and half B went red, 2 failed. That is the point of the structural half: a byte-identical second spelling passes every behavioural assertion, which is exactly how the copies stayed invisible for so long.
  • Re-add copy 3 to the plugin: red, 2 failed, naming the carrier list ['action-governance.ts', 'plugin.ts'] and the returned private method.
  • Restored-tree control: both welds green again, 3 of 3 and 13 of 13, and git status --porcelain empty.

Also in this diff

  • content/docs/permissions/system-context.mdx — the two lines added to the import and re-export blocks shifted every anchored line in action-execution.ts below them by exactly two, so check:check-system-context-census went red on pure line rot. Repaired by the gate's own --fix, which re-anchored :64 to :66, :136 to :138 and :399 to :401 and touched nothing else. Not a behaviour change; the cost of the diff, paid in the diff.
  • packages/runtime/src/http-dispatcher.actions-global-key.test.ts — one clause of a docblock that named ObjectQLPlugin.actionObjectKey as a live symbol.

Verification, all at e9043cc36

  • pnpm --filter @objectstack/objectql exec vitest run264 files, 4550 tests, all passed.
  • pnpm --filter @objectstack/runtime exec vitest run210 files, 3092 tests, all passed.
  • pnpm --filter @objectstack/objectql --filter @objectstack/runtime run typecheck — clean, including objectql's check:test-typecheck (44 files / 242 errors / 69 pinned signatures, ratchet held). The new objectql test file is in that program, confirmed with --listFiles. The new runtime test file is read by no tsc program (runtime's typecheck excludes tests, TEST_DEBT ledger), so its enforcing runner is vitest alone — stated rather than implied.
  • The gate family derived from this diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 62 families, all green. The docs file above pulled in 24 families beyond the first derivation, which is why it was re-derived after the tree was final. check-test-completeness and pm/check-half-states exit 3 by their own definition when run without CI's arguments and are recorded NOT MEASURED, not green. check:dual-build-cjs-loads and check:type-check-debt also exit 3 until the workspace is built; the full package closure was built and both then reported OK.
  • Always-runs: pnpm lint over the whole repo exit 0 (no narrowing claimed), pnpm check:nul-bytes OK over 7,998 files, pnpm check:error-status-conformance OK.

No package.json is touched, so Validate Package Dependencies is not implicated by this branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68


Generated by Claude Code

…er one spelling
The `action.objectName` -> `action.object` -> object-less `'global'` ladder was
written out three times — `standaloneActionOwnerKey` (objectql), the runtime's
`standaloneActionObjectName`, and the private `ObjectQLPlugin.actionObjectKey` —
held equal only by a sentence in each docblock. All three now resolve to the one
implementation: the plugin calls it directly (same package), and the runtime
re-exports it in the ADR-0110 block that already exists for that purpose,
keeping `standaloneActionObjectName` as a delegating alias so its exported
signature does not move.
The plugin copy terminated on a bare `'global'` literal where the other two
return `GLOBAL_ACTION_OBJECT_KEY`; equal today, silently different the day the
constant moves. Its `isArtifactShippedAction` reader is converged with it.
The lockstep docblocks are replaced by package-local welds
(`action-owner-key-single-source.test.ts` in each package), because a docblock
is not a check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
The two lines added to `action-execution.ts`'s import and re-export blocks
shifted every anchored line below them by exactly two, so
`check:check-system-context-census` went red with pure line rot: the two real
`isSystem` read sites moved to :138 and :401 with no row anchoring them, while
the page still pointed at :136, :399 and :64. Repaired by the gate's own
`--fix`, which rewrote the three citations and nothing else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/runtime, touching 5 documentable anchor(s). ⚠️2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/objects.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/plugins/packages.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx(via ObjectQLPlugin (symbol, a top-level class))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via ObjectQLPlugin (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 33 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3b7acfba2285187f15d771da7dfb225af6d7ab4d — the merge of head e9043cc36249eeebba10b50d416996dec98292b8 into base 2aa8456cf2d66ec3825d262686fe4218e57cfd27, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3b7acfba2285187f15d771da7dfb225af6d7ab4d && git checkout 3b7acfba2285187f15d771da7dfb225af6d7ab4d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2aa8456cf2d66ec3825d262686fe4218e57cfd27 e9043cc36249eeebba10b50d416996dec98292b8 && git checkout -B drift-repro 2aa8456cf2d66ec3825d262686fe4218e57cfd27 && git merge --no-ff e9043cc36249eeebba10b50d416996dec98292b8
node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 2aa8456cf2d66ec3825d262686fe4218e57cfd27 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-musk
os-musk marked this pull request as ready for review September 2, 2026 18:10
@os-musk
os-musk enabled auto-merge September 2, 2026 18:10
@os-musk
os-musk added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit dc7c226Sep 2, 2026
43 checks passed
@os-musk
os-musk deleted the claude/issue-14422-converge-action-owner-key branch September 2, 2026 19:58
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-musk@claude
, '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

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling - #14667

Merged
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key
Sep 2, 2026
Merged

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling#14667
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key

Conversation

@os-musk

Copy link
Copy Markdown
Collaborator

Fixes#14422

The action.objectName -> action.object -> object-less 'global' ladder that decides which engine key a standalone action declaration is filed under was written out three times, held equal only by a sentence in each docblock promising lockstep. All three now resolve to one implementation.

What changed

  1. packages/objectql/src/plugin.ts — the private actionObjectKey is deleted and its callers now call standaloneActionOwnerKey directly (same package, no dependency question).
  2. packages/runtime/src/action-execution.tsstandaloneActionOwnerKey joins the ADR-0110 re-export block that already exists in that file for exactly this purpose, and standaloneActionObjectName becomes a delegating alias whose whole body is return standaloneActionOwnerKey(action);.
  3. packages/objectql/src/action-governance.ts — the docblock sentences naming the copies are gone, replaced by a statement of where the one implementation lives and a note not to re-inline it.

The convergence, proved rather than asserted

Rungs 1 and 2 of the ladder, counted across packages/objectql/src and packages/runtime/src:

rung 1 (objectName)rung 2 (object)
before (control), 13bf05d3f33
after11

The one surviving carrier is action-governance.ts:92-93. standaloneActionObjectName keeps no body of its own, and no actionObjectKey symbol survives in either package.

The divergence this removes, which the card did not name

Copy 3 did not merely spell the ladder again. Its terminal rung returned the bare string literal 'global' (plugin.ts:2242 on origin/main), while copies 1 and 2 both return the shared GLOBAL_ACTION_OBJECT_KEY constant. Re-measured here on 13bf05d3f: the constant is declared 'global' at action-governance.ts:61, and all three ladders were driven over a twelve-row truth table (both rungs, the empty-string and wrong-type fall-throughs, null and undefined actions) and agreed on 12 of 12 inputs. So nothing was broken and this stayed a convergence card, not a defect card.

What copy 3 was, was the one spelling that would have parted from the other two in silence the day that constant's value changed — equal in value, invisible to every test in the repo. The same bare literal in the plugin's isArtifactShippedAction reader (objectKey !== 'global') is converged to the constant with it, so the file no longer carries a spelling that can drift from the constant it is supposed to track.

Three dispatch assumptions that measured differently

  • Call sites: four, not one. The triage named plugin.ts:2260. actionObjectKey actually had four call sites — :2260, :2612, :2615 and :2653. The deletion is still right; all four now call the canonical helper.
  • The export had to be published first.standaloneActionOwnerKey was not on @objectstack/objectql's published entry, so the runtime could not import it from the package root at all. Adding it to packages/objectql/src/index.ts is a fourth file, mechanically forced by step 2, and it is what makes objectql the package whose surface grows.
  • The minor lands on objectql, not on runtime. Measured in the built artifacts with a positive control: standaloneActionOwnerKey is declared and exported in packages/objectql/dist/index.d.ts, while standaloneActionOwnerKey, standaloneActionObjectName and GLOBAL_ACTION_OBJECT_KEY each appear 0 times in packages/runtime/dist/index.d.ts against a control of 32 for HttpDispatcheraction-execution.ts is not re-exported from packages/runtime/src/index.ts and the package publishes only .. Hence @objectstack/objectql: minor, @objectstack/runtime: patch.

The _deps decision

Kept, as a delegating alias. The engine helper is standaloneActionOwnerKey(action); the runtime name is standaloneActionObjectName(_deps, action). Every caller was measured first: exactly two, both inside action-execution.ts itself (:1524, :1699), no test importer, no other package. Dropping _deps would move an exported signature to save two characters at two internal call sites, so the alias keeps its arity and its meaning and ownsRoute plus any out-of-repo importer compile and behave exactly as before.

Reverse verification

The docblocks that promised lockstep are replaced by welds that enforce it — action-owner-key-single-source.test.ts in each package, each scoped to its own package's source so neither becomes a cross-package test input. Both were ablated from the committed tree, each mutation confirmed on disk by grep counts before the run and each restore proved by hash equality with the HEAD blob plus an empty git diff HEAD:

  • Re-inline the ladder into the runtime alias (byte-identical behaviour): half A stayed green, 11 passed — and half B went red, 2 failed. That is the point of the structural half: a byte-identical second spelling passes every behavioural assertion, which is exactly how the copies stayed invisible for so long.
  • Re-add copy 3 to the plugin: red, 2 failed, naming the carrier list ['action-governance.ts', 'plugin.ts'] and the returned private method.
  • Restored-tree control: both welds green again, 3 of 3 and 13 of 13, and git status --porcelain empty.

Also in this diff

  • content/docs/permissions/system-context.mdx — the two lines added to the import and re-export blocks shifted every anchored line in action-execution.ts below them by exactly two, so check:check-system-context-census went red on pure line rot. Repaired by the gate's own --fix, which re-anchored :64 to :66, :136 to :138 and :399 to :401 and touched nothing else. Not a behaviour change; the cost of the diff, paid in the diff.
  • packages/runtime/src/http-dispatcher.actions-global-key.test.ts — one clause of a docblock that named ObjectQLPlugin.actionObjectKey as a live symbol.

Verification, all at e9043cc36

  • pnpm --filter @objectstack/objectql exec vitest run264 files, 4550 tests, all passed.
  • pnpm --filter @objectstack/runtime exec vitest run210 files, 3092 tests, all passed.
  • pnpm --filter @objectstack/objectql --filter @objectstack/runtime run typecheck — clean, including objectql's check:test-typecheck (44 files / 242 errors / 69 pinned signatures, ratchet held). The new objectql test file is in that program, confirmed with --listFiles. The new runtime test file is read by no tsc program (runtime's typecheck excludes tests, TEST_DEBT ledger), so its enforcing runner is vitest alone — stated rather than implied.
  • The gate family derived from this diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 62 families, all green. The docs file above pulled in 24 families beyond the first derivation, which is why it was re-derived after the tree was final. check-test-completeness and pm/check-half-states exit 3 by their own definition when run without CI's arguments and are recorded NOT MEASURED, not green. check:dual-build-cjs-loads and check:type-check-debt also exit 3 until the workspace is built; the full package closure was built and both then reported OK.
  • Always-runs: pnpm lint over the whole repo exit 0 (no narrowing claimed), pnpm check:nul-bytes OK over 7,998 files, pnpm check:error-status-conformance OK.

No package.json is touched, so Validate Package Dependencies is not implicated by this branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68


Generated by Claude Code

…er one spelling
The `action.objectName` -> `action.object` -> object-less `'global'` ladder was
written out three times — `standaloneActionOwnerKey` (objectql), the runtime's
`standaloneActionObjectName`, and the private `ObjectQLPlugin.actionObjectKey` —
held equal only by a sentence in each docblock. All three now resolve to the one
implementation: the plugin calls it directly (same package), and the runtime
re-exports it in the ADR-0110 block that already exists for that purpose,
keeping `standaloneActionObjectName` as a delegating alias so its exported
signature does not move.
The plugin copy terminated on a bare `'global'` literal where the other two
return `GLOBAL_ACTION_OBJECT_KEY`; equal today, silently different the day the
constant moves. Its `isArtifactShippedAction` reader is converged with it.
The lockstep docblocks are replaced by package-local welds
(`action-owner-key-single-source.test.ts` in each package), because a docblock
is not a check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
The two lines added to `action-execution.ts`'s import and re-export blocks
shifted every anchored line below them by exactly two, so
`check:check-system-context-census` went red with pure line rot: the two real
`isSystem` read sites moved to :138 and :401 with no row anchoring them, while
the page still pointed at :136, :399 and :64. Repaired by the gate's own
`--fix`, which rewrote the three citations and nothing else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/runtime, touching 5 documentable anchor(s). ⚠️2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/objects.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/plugins/packages.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx(via ObjectQLPlugin (symbol, a top-level class))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via ObjectQLPlugin (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 33 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3b7acfba2285187f15d771da7dfb225af6d7ab4d — the merge of head e9043cc36249eeebba10b50d416996dec98292b8 into base 2aa8456cf2d66ec3825d262686fe4218e57cfd27, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3b7acfba2285187f15d771da7dfb225af6d7ab4d && git checkout 3b7acfba2285187f15d771da7dfb225af6d7ab4d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2aa8456cf2d66ec3825d262686fe4218e57cfd27 e9043cc36249eeebba10b50d416996dec98292b8 && git checkout -B drift-repro 2aa8456cf2d66ec3825d262686fe4218e57cfd27 && git merge --no-ff e9043cc36249eeebba10b50d416996dec98292b8
node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 2aa8456cf2d66ec3825d262686fe4218e57cfd27 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-musk
os-musk marked this pull request as ready for review September 2, 2026 18:10
@os-musk
os-musk enabled auto-merge September 2, 2026 18:10
@os-musk
os-musk added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit dc7c226Sep 2, 2026
43 checks passed
@os-musk
os-musk deleted the claude/issue-14422-converge-action-owner-key branch September 2, 2026 19:58
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-musk@claude
, '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

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling - #14667

Merged
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key
Sep 2, 2026
Merged

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling#14667
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key

Conversation

@os-musk

Copy link
Copy Markdown
Collaborator

Fixes#14422

The action.objectName -> action.object -> object-less 'global' ladder that decides which engine key a standalone action declaration is filed under was written out three times, held equal only by a sentence in each docblock promising lockstep. All three now resolve to one implementation.

What changed

  1. packages/objectql/src/plugin.ts — the private actionObjectKey is deleted and its callers now call standaloneActionOwnerKey directly (same package, no dependency question).
  2. packages/runtime/src/action-execution.tsstandaloneActionOwnerKey joins the ADR-0110 re-export block that already exists in that file for exactly this purpose, and standaloneActionObjectName becomes a delegating alias whose whole body is return standaloneActionOwnerKey(action);.
  3. packages/objectql/src/action-governance.ts — the docblock sentences naming the copies are gone, replaced by a statement of where the one implementation lives and a note not to re-inline it.

The convergence, proved rather than asserted

Rungs 1 and 2 of the ladder, counted across packages/objectql/src and packages/runtime/src:

rung 1 (objectName)rung 2 (object)
before (control), 13bf05d3f33
after11

The one surviving carrier is action-governance.ts:92-93. standaloneActionObjectName keeps no body of its own, and no actionObjectKey symbol survives in either package.

The divergence this removes, which the card did not name

Copy 3 did not merely spell the ladder again. Its terminal rung returned the bare string literal 'global' (plugin.ts:2242 on origin/main), while copies 1 and 2 both return the shared GLOBAL_ACTION_OBJECT_KEY constant. Re-measured here on 13bf05d3f: the constant is declared 'global' at action-governance.ts:61, and all three ladders were driven over a twelve-row truth table (both rungs, the empty-string and wrong-type fall-throughs, null and undefined actions) and agreed on 12 of 12 inputs. So nothing was broken and this stayed a convergence card, not a defect card.

What copy 3 was, was the one spelling that would have parted from the other two in silence the day that constant's value changed — equal in value, invisible to every test in the repo. The same bare literal in the plugin's isArtifactShippedAction reader (objectKey !== 'global') is converged to the constant with it, so the file no longer carries a spelling that can drift from the constant it is supposed to track.

Three dispatch assumptions that measured differently

  • Call sites: four, not one. The triage named plugin.ts:2260. actionObjectKey actually had four call sites — :2260, :2612, :2615 and :2653. The deletion is still right; all four now call the canonical helper.
  • The export had to be published first.standaloneActionOwnerKey was not on @objectstack/objectql's published entry, so the runtime could not import it from the package root at all. Adding it to packages/objectql/src/index.ts is a fourth file, mechanically forced by step 2, and it is what makes objectql the package whose surface grows.
  • The minor lands on objectql, not on runtime. Measured in the built artifacts with a positive control: standaloneActionOwnerKey is declared and exported in packages/objectql/dist/index.d.ts, while standaloneActionOwnerKey, standaloneActionObjectName and GLOBAL_ACTION_OBJECT_KEY each appear 0 times in packages/runtime/dist/index.d.ts against a control of 32 for HttpDispatcheraction-execution.ts is not re-exported from packages/runtime/src/index.ts and the package publishes only .. Hence @objectstack/objectql: minor, @objectstack/runtime: patch.

The _deps decision

Kept, as a delegating alias. The engine helper is standaloneActionOwnerKey(action); the runtime name is standaloneActionObjectName(_deps, action). Every caller was measured first: exactly two, both inside action-execution.ts itself (:1524, :1699), no test importer, no other package. Dropping _deps would move an exported signature to save two characters at two internal call sites, so the alias keeps its arity and its meaning and ownsRoute plus any out-of-repo importer compile and behave exactly as before.

Reverse verification

The docblocks that promised lockstep are replaced by welds that enforce it — action-owner-key-single-source.test.ts in each package, each scoped to its own package's source so neither becomes a cross-package test input. Both were ablated from the committed tree, each mutation confirmed on disk by grep counts before the run and each restore proved by hash equality with the HEAD blob plus an empty git diff HEAD:

  • Re-inline the ladder into the runtime alias (byte-identical behaviour): half A stayed green, 11 passed — and half B went red, 2 failed. That is the point of the structural half: a byte-identical second spelling passes every behavioural assertion, which is exactly how the copies stayed invisible for so long.
  • Re-add copy 3 to the plugin: red, 2 failed, naming the carrier list ['action-governance.ts', 'plugin.ts'] and the returned private method.
  • Restored-tree control: both welds green again, 3 of 3 and 13 of 13, and git status --porcelain empty.

Also in this diff

  • content/docs/permissions/system-context.mdx — the two lines added to the import and re-export blocks shifted every anchored line in action-execution.ts below them by exactly two, so check:check-system-context-census went red on pure line rot. Repaired by the gate's own --fix, which re-anchored :64 to :66, :136 to :138 and :399 to :401 and touched nothing else. Not a behaviour change; the cost of the diff, paid in the diff.
  • packages/runtime/src/http-dispatcher.actions-global-key.test.ts — one clause of a docblock that named ObjectQLPlugin.actionObjectKey as a live symbol.

Verification, all at e9043cc36

  • pnpm --filter @objectstack/objectql exec vitest run264 files, 4550 tests, all passed.
  • pnpm --filter @objectstack/runtime exec vitest run210 files, 3092 tests, all passed.
  • pnpm --filter @objectstack/objectql --filter @objectstack/runtime run typecheck — clean, including objectql's check:test-typecheck (44 files / 242 errors / 69 pinned signatures, ratchet held). The new objectql test file is in that program, confirmed with --listFiles. The new runtime test file is read by no tsc program (runtime's typecheck excludes tests, TEST_DEBT ledger), so its enforcing runner is vitest alone — stated rather than implied.
  • The gate family derived from this diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 62 families, all green. The docs file above pulled in 24 families beyond the first derivation, which is why it was re-derived after the tree was final. check-test-completeness and pm/check-half-states exit 3 by their own definition when run without CI's arguments and are recorded NOT MEASURED, not green. check:dual-build-cjs-loads and check:type-check-debt also exit 3 until the workspace is built; the full package closure was built and both then reported OK.
  • Always-runs: pnpm lint over the whole repo exit 0 (no narrowing claimed), pnpm check:nul-bytes OK over 7,998 files, pnpm check:error-status-conformance OK.

No package.json is touched, so Validate Package Dependencies is not implicated by this branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68


Generated by Claude Code

…er one spelling
The `action.objectName` -> `action.object` -> object-less `'global'` ladder was
written out three times — `standaloneActionOwnerKey` (objectql), the runtime's
`standaloneActionObjectName`, and the private `ObjectQLPlugin.actionObjectKey` —
held equal only by a sentence in each docblock. All three now resolve to the one
implementation: the plugin calls it directly (same package), and the runtime
re-exports it in the ADR-0110 block that already exists for that purpose,
keeping `standaloneActionObjectName` as a delegating alias so its exported
signature does not move.
The plugin copy terminated on a bare `'global'` literal where the other two
return `GLOBAL_ACTION_OBJECT_KEY`; equal today, silently different the day the
constant moves. Its `isArtifactShippedAction` reader is converged with it.
The lockstep docblocks are replaced by package-local welds
(`action-owner-key-single-source.test.ts` in each package), because a docblock
is not a check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
The two lines added to `action-execution.ts`'s import and re-export blocks
shifted every anchored line below them by exactly two, so
`check:check-system-context-census` went red with pure line rot: the two real
`isSystem` read sites moved to :138 and :401 with no row anchoring them, while
the page still pointed at :136, :399 and :64. Repaired by the gate's own
`--fix`, which rewrote the three citations and nothing else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/runtime, touching 5 documentable anchor(s). ⚠️2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/objects.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/plugins/packages.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx(via ObjectQLPlugin (symbol, a top-level class))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via ObjectQLPlugin (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 33 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3b7acfba2285187f15d771da7dfb225af6d7ab4d — the merge of head e9043cc36249eeebba10b50d416996dec98292b8 into base 2aa8456cf2d66ec3825d262686fe4218e57cfd27, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3b7acfba2285187f15d771da7dfb225af6d7ab4d && git checkout 3b7acfba2285187f15d771da7dfb225af6d7ab4d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2aa8456cf2d66ec3825d262686fe4218e57cfd27 e9043cc36249eeebba10b50d416996dec98292b8 && git checkout -B drift-repro 2aa8456cf2d66ec3825d262686fe4218e57cfd27 && git merge --no-ff e9043cc36249eeebba10b50d416996dec98292b8
node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 2aa8456cf2d66ec3825d262686fe4218e57cfd27 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-musk
os-musk marked this pull request as ready for review September 2, 2026 18:10
@os-musk
os-musk enabled auto-merge September 2, 2026 18:10
@os-musk
os-musk added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit dc7c226Sep 2, 2026
43 checks passed
@os-musk
os-musk deleted the claude/issue-14422-converge-action-owner-key branch September 2, 2026 19:58
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-musk@claude
, '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

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling - #14667

Merged
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key
Sep 2, 2026
Merged

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling#14667
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key

Conversation

@os-musk

Copy link
Copy Markdown
Collaborator

Fixes#14422

The action.objectName -> action.object -> object-less 'global' ladder that decides which engine key a standalone action declaration is filed under was written out three times, held equal only by a sentence in each docblock promising lockstep. All three now resolve to one implementation.

What changed

  1. packages/objectql/src/plugin.ts — the private actionObjectKey is deleted and its callers now call standaloneActionOwnerKey directly (same package, no dependency question).
  2. packages/runtime/src/action-execution.tsstandaloneActionOwnerKey joins the ADR-0110 re-export block that already exists in that file for exactly this purpose, and standaloneActionObjectName becomes a delegating alias whose whole body is return standaloneActionOwnerKey(action);.
  3. packages/objectql/src/action-governance.ts — the docblock sentences naming the copies are gone, replaced by a statement of where the one implementation lives and a note not to re-inline it.

The convergence, proved rather than asserted

Rungs 1 and 2 of the ladder, counted across packages/objectql/src and packages/runtime/src:

rung 1 (objectName)rung 2 (object)
before (control), 13bf05d3f33
after11

The one surviving carrier is action-governance.ts:92-93. standaloneActionObjectName keeps no body of its own, and no actionObjectKey symbol survives in either package.

The divergence this removes, which the card did not name

Copy 3 did not merely spell the ladder again. Its terminal rung returned the bare string literal 'global' (plugin.ts:2242 on origin/main), while copies 1 and 2 both return the shared GLOBAL_ACTION_OBJECT_KEY constant. Re-measured here on 13bf05d3f: the constant is declared 'global' at action-governance.ts:61, and all three ladders were driven over a twelve-row truth table (both rungs, the empty-string and wrong-type fall-throughs, null and undefined actions) and agreed on 12 of 12 inputs. So nothing was broken and this stayed a convergence card, not a defect card.

What copy 3 was, was the one spelling that would have parted from the other two in silence the day that constant's value changed — equal in value, invisible to every test in the repo. The same bare literal in the plugin's isArtifactShippedAction reader (objectKey !== 'global') is converged to the constant with it, so the file no longer carries a spelling that can drift from the constant it is supposed to track.

Three dispatch assumptions that measured differently

  • Call sites: four, not one. The triage named plugin.ts:2260. actionObjectKey actually had four call sites — :2260, :2612, :2615 and :2653. The deletion is still right; all four now call the canonical helper.
  • The export had to be published first.standaloneActionOwnerKey was not on @objectstack/objectql's published entry, so the runtime could not import it from the package root at all. Adding it to packages/objectql/src/index.ts is a fourth file, mechanically forced by step 2, and it is what makes objectql the package whose surface grows.
  • The minor lands on objectql, not on runtime. Measured in the built artifacts with a positive control: standaloneActionOwnerKey is declared and exported in packages/objectql/dist/index.d.ts, while standaloneActionOwnerKey, standaloneActionObjectName and GLOBAL_ACTION_OBJECT_KEY each appear 0 times in packages/runtime/dist/index.d.ts against a control of 32 for HttpDispatcheraction-execution.ts is not re-exported from packages/runtime/src/index.ts and the package publishes only .. Hence @objectstack/objectql: minor, @objectstack/runtime: patch.

The _deps decision

Kept, as a delegating alias. The engine helper is standaloneActionOwnerKey(action); the runtime name is standaloneActionObjectName(_deps, action). Every caller was measured first: exactly two, both inside action-execution.ts itself (:1524, :1699), no test importer, no other package. Dropping _deps would move an exported signature to save two characters at two internal call sites, so the alias keeps its arity and its meaning and ownsRoute plus any out-of-repo importer compile and behave exactly as before.

Reverse verification

The docblocks that promised lockstep are replaced by welds that enforce it — action-owner-key-single-source.test.ts in each package, each scoped to its own package's source so neither becomes a cross-package test input. Both were ablated from the committed tree, each mutation confirmed on disk by grep counts before the run and each restore proved by hash equality with the HEAD blob plus an empty git diff HEAD:

  • Re-inline the ladder into the runtime alias (byte-identical behaviour): half A stayed green, 11 passed — and half B went red, 2 failed. That is the point of the structural half: a byte-identical second spelling passes every behavioural assertion, which is exactly how the copies stayed invisible for so long.
  • Re-add copy 3 to the plugin: red, 2 failed, naming the carrier list ['action-governance.ts', 'plugin.ts'] and the returned private method.
  • Restored-tree control: both welds green again, 3 of 3 and 13 of 13, and git status --porcelain empty.

Also in this diff

  • content/docs/permissions/system-context.mdx — the two lines added to the import and re-export blocks shifted every anchored line in action-execution.ts below them by exactly two, so check:check-system-context-census went red on pure line rot. Repaired by the gate's own --fix, which re-anchored :64 to :66, :136 to :138 and :399 to :401 and touched nothing else. Not a behaviour change; the cost of the diff, paid in the diff.
  • packages/runtime/src/http-dispatcher.actions-global-key.test.ts — one clause of a docblock that named ObjectQLPlugin.actionObjectKey as a live symbol.

Verification, all at e9043cc36

  • pnpm --filter @objectstack/objectql exec vitest run264 files, 4550 tests, all passed.
  • pnpm --filter @objectstack/runtime exec vitest run210 files, 3092 tests, all passed.
  • pnpm --filter @objectstack/objectql --filter @objectstack/runtime run typecheck — clean, including objectql's check:test-typecheck (44 files / 242 errors / 69 pinned signatures, ratchet held). The new objectql test file is in that program, confirmed with --listFiles. The new runtime test file is read by no tsc program (runtime's typecheck excludes tests, TEST_DEBT ledger), so its enforcing runner is vitest alone — stated rather than implied.
  • The gate family derived from this diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 62 families, all green. The docs file above pulled in 24 families beyond the first derivation, which is why it was re-derived after the tree was final. check-test-completeness and pm/check-half-states exit 3 by their own definition when run without CI's arguments and are recorded NOT MEASURED, not green. check:dual-build-cjs-loads and check:type-check-debt also exit 3 until the workspace is built; the full package closure was built and both then reported OK.
  • Always-runs: pnpm lint over the whole repo exit 0 (no narrowing claimed), pnpm check:nul-bytes OK over 7,998 files, pnpm check:error-status-conformance OK.

No package.json is touched, so Validate Package Dependencies is not implicated by this branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68


Generated by Claude Code

…er one spelling
The `action.objectName` -> `action.object` -> object-less `'global'` ladder was
written out three times — `standaloneActionOwnerKey` (objectql), the runtime's
`standaloneActionObjectName`, and the private `ObjectQLPlugin.actionObjectKey` —
held equal only by a sentence in each docblock. All three now resolve to the one
implementation: the plugin calls it directly (same package), and the runtime
re-exports it in the ADR-0110 block that already exists for that purpose,
keeping `standaloneActionObjectName` as a delegating alias so its exported
signature does not move.
The plugin copy terminated on a bare `'global'` literal where the other two
return `GLOBAL_ACTION_OBJECT_KEY`; equal today, silently different the day the
constant moves. Its `isArtifactShippedAction` reader is converged with it.
The lockstep docblocks are replaced by package-local welds
(`action-owner-key-single-source.test.ts` in each package), because a docblock
is not a check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
The two lines added to `action-execution.ts`'s import and re-export blocks
shifted every anchored line below them by exactly two, so
`check:check-system-context-census` went red with pure line rot: the two real
`isSystem` read sites moved to :138 and :401 with no row anchoring them, while
the page still pointed at :136, :399 and :64. Repaired by the gate's own
`--fix`, which rewrote the three citations and nothing else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/runtime, touching 5 documentable anchor(s). ⚠️2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/objects.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/plugins/packages.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx(via ObjectQLPlugin (symbol, a top-level class))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via ObjectQLPlugin (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 33 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3b7acfba2285187f15d771da7dfb225af6d7ab4d — the merge of head e9043cc36249eeebba10b50d416996dec98292b8 into base 2aa8456cf2d66ec3825d262686fe4218e57cfd27, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3b7acfba2285187f15d771da7dfb225af6d7ab4d && git checkout 3b7acfba2285187f15d771da7dfb225af6d7ab4d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2aa8456cf2d66ec3825d262686fe4218e57cfd27 e9043cc36249eeebba10b50d416996dec98292b8 && git checkout -B drift-repro 2aa8456cf2d66ec3825d262686fe4218e57cfd27 && git merge --no-ff e9043cc36249eeebba10b50d416996dec98292b8
node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 2aa8456cf2d66ec3825d262686fe4218e57cfd27 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-musk
os-musk marked this pull request as ready for review September 2, 2026 18:10
@os-musk
os-musk enabled auto-merge September 2, 2026 18:10
@os-musk
os-musk added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit dc7c226Sep 2, 2026
43 checks passed
@os-musk
os-musk deleted the claude/issue-14422-converge-action-owner-key branch September 2, 2026 19:58
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-musk@claude
, '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

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling - #14667

Merged
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key
Sep 2, 2026
Merged

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling#14667
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key

Conversation

@os-musk

Copy link
Copy Markdown
Collaborator

Fixes#14422

The action.objectName -> action.object -> object-less 'global' ladder that decides which engine key a standalone action declaration is filed under was written out three times, held equal only by a sentence in each docblock promising lockstep. All three now resolve to one implementation.

What changed

  1. packages/objectql/src/plugin.ts — the private actionObjectKey is deleted and its callers now call standaloneActionOwnerKey directly (same package, no dependency question).
  2. packages/runtime/src/action-execution.tsstandaloneActionOwnerKey joins the ADR-0110 re-export block that already exists in that file for exactly this purpose, and standaloneActionObjectName becomes a delegating alias whose whole body is return standaloneActionOwnerKey(action);.
  3. packages/objectql/src/action-governance.ts — the docblock sentences naming the copies are gone, replaced by a statement of where the one implementation lives and a note not to re-inline it.

The convergence, proved rather than asserted

Rungs 1 and 2 of the ladder, counted across packages/objectql/src and packages/runtime/src:

rung 1 (objectName)rung 2 (object)
before (control), 13bf05d3f33
after11

The one surviving carrier is action-governance.ts:92-93. standaloneActionObjectName keeps no body of its own, and no actionObjectKey symbol survives in either package.

The divergence this removes, which the card did not name

Copy 3 did not merely spell the ladder again. Its terminal rung returned the bare string literal 'global' (plugin.ts:2242 on origin/main), while copies 1 and 2 both return the shared GLOBAL_ACTION_OBJECT_KEY constant. Re-measured here on 13bf05d3f: the constant is declared 'global' at action-governance.ts:61, and all three ladders were driven over a twelve-row truth table (both rungs, the empty-string and wrong-type fall-throughs, null and undefined actions) and agreed on 12 of 12 inputs. So nothing was broken and this stayed a convergence card, not a defect card.

What copy 3 was, was the one spelling that would have parted from the other two in silence the day that constant's value changed — equal in value, invisible to every test in the repo. The same bare literal in the plugin's isArtifactShippedAction reader (objectKey !== 'global') is converged to the constant with it, so the file no longer carries a spelling that can drift from the constant it is supposed to track.

Three dispatch assumptions that measured differently

  • Call sites: four, not one. The triage named plugin.ts:2260. actionObjectKey actually had four call sites — :2260, :2612, :2615 and :2653. The deletion is still right; all four now call the canonical helper.
  • The export had to be published first.standaloneActionOwnerKey was not on @objectstack/objectql's published entry, so the runtime could not import it from the package root at all. Adding it to packages/objectql/src/index.ts is a fourth file, mechanically forced by step 2, and it is what makes objectql the package whose surface grows.
  • The minor lands on objectql, not on runtime. Measured in the built artifacts with a positive control: standaloneActionOwnerKey is declared and exported in packages/objectql/dist/index.d.ts, while standaloneActionOwnerKey, standaloneActionObjectName and GLOBAL_ACTION_OBJECT_KEY each appear 0 times in packages/runtime/dist/index.d.ts against a control of 32 for HttpDispatcheraction-execution.ts is not re-exported from packages/runtime/src/index.ts and the package publishes only .. Hence @objectstack/objectql: minor, @objectstack/runtime: patch.

The _deps decision

Kept, as a delegating alias. The engine helper is standaloneActionOwnerKey(action); the runtime name is standaloneActionObjectName(_deps, action). Every caller was measured first: exactly two, both inside action-execution.ts itself (:1524, :1699), no test importer, no other package. Dropping _deps would move an exported signature to save two characters at two internal call sites, so the alias keeps its arity and its meaning and ownsRoute plus any out-of-repo importer compile and behave exactly as before.

Reverse verification

The docblocks that promised lockstep are replaced by welds that enforce it — action-owner-key-single-source.test.ts in each package, each scoped to its own package's source so neither becomes a cross-package test input. Both were ablated from the committed tree, each mutation confirmed on disk by grep counts before the run and each restore proved by hash equality with the HEAD blob plus an empty git diff HEAD:

  • Re-inline the ladder into the runtime alias (byte-identical behaviour): half A stayed green, 11 passed — and half B went red, 2 failed. That is the point of the structural half: a byte-identical second spelling passes every behavioural assertion, which is exactly how the copies stayed invisible for so long.
  • Re-add copy 3 to the plugin: red, 2 failed, naming the carrier list ['action-governance.ts', 'plugin.ts'] and the returned private method.
  • Restored-tree control: both welds green again, 3 of 3 and 13 of 13, and git status --porcelain empty.

Also in this diff

  • content/docs/permissions/system-context.mdx — the two lines added to the import and re-export blocks shifted every anchored line in action-execution.ts below them by exactly two, so check:check-system-context-census went red on pure line rot. Repaired by the gate's own --fix, which re-anchored :64 to :66, :136 to :138 and :399 to :401 and touched nothing else. Not a behaviour change; the cost of the diff, paid in the diff.
  • packages/runtime/src/http-dispatcher.actions-global-key.test.ts — one clause of a docblock that named ObjectQLPlugin.actionObjectKey as a live symbol.

Verification, all at e9043cc36

  • pnpm --filter @objectstack/objectql exec vitest run264 files, 4550 tests, all passed.
  • pnpm --filter @objectstack/runtime exec vitest run210 files, 3092 tests, all passed.
  • pnpm --filter @objectstack/objectql --filter @objectstack/runtime run typecheck — clean, including objectql's check:test-typecheck (44 files / 242 errors / 69 pinned signatures, ratchet held). The new objectql test file is in that program, confirmed with --listFiles. The new runtime test file is read by no tsc program (runtime's typecheck excludes tests, TEST_DEBT ledger), so its enforcing runner is vitest alone — stated rather than implied.
  • The gate family derived from this diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 62 families, all green. The docs file above pulled in 24 families beyond the first derivation, which is why it was re-derived after the tree was final. check-test-completeness and pm/check-half-states exit 3 by their own definition when run without CI's arguments and are recorded NOT MEASURED, not green. check:dual-build-cjs-loads and check:type-check-debt also exit 3 until the workspace is built; the full package closure was built and both then reported OK.
  • Always-runs: pnpm lint over the whole repo exit 0 (no narrowing claimed), pnpm check:nul-bytes OK over 7,998 files, pnpm check:error-status-conformance OK.

No package.json is touched, so Validate Package Dependencies is not implicated by this branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68


Generated by Claude Code

…er one spelling
The `action.objectName` -> `action.object` -> object-less `'global'` ladder was
written out three times — `standaloneActionOwnerKey` (objectql), the runtime's
`standaloneActionObjectName`, and the private `ObjectQLPlugin.actionObjectKey` —
held equal only by a sentence in each docblock. All three now resolve to the one
implementation: the plugin calls it directly (same package), and the runtime
re-exports it in the ADR-0110 block that already exists for that purpose,
keeping `standaloneActionObjectName` as a delegating alias so its exported
signature does not move.
The plugin copy terminated on a bare `'global'` literal where the other two
return `GLOBAL_ACTION_OBJECT_KEY`; equal today, silently different the day the
constant moves. Its `isArtifactShippedAction` reader is converged with it.
The lockstep docblocks are replaced by package-local welds
(`action-owner-key-single-source.test.ts` in each package), because a docblock
is not a check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
The two lines added to `action-execution.ts`'s import and re-export blocks
shifted every anchored line below them by exactly two, so
`check:check-system-context-census` went red with pure line rot: the two real
`isSystem` read sites moved to :138 and :401 with no row anchoring them, while
the page still pointed at :136, :399 and :64. Repaired by the gate's own
`--fix`, which rewrote the three citations and nothing else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/runtime, touching 5 documentable anchor(s). ⚠️2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/objects.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/plugins/packages.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx(via ObjectQLPlugin (symbol, a top-level class))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via ObjectQLPlugin (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 33 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3b7acfba2285187f15d771da7dfb225af6d7ab4d — the merge of head e9043cc36249eeebba10b50d416996dec98292b8 into base 2aa8456cf2d66ec3825d262686fe4218e57cfd27, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3b7acfba2285187f15d771da7dfb225af6d7ab4d && git checkout 3b7acfba2285187f15d771da7dfb225af6d7ab4d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2aa8456cf2d66ec3825d262686fe4218e57cfd27 e9043cc36249eeebba10b50d416996dec98292b8 && git checkout -B drift-repro 2aa8456cf2d66ec3825d262686fe4218e57cfd27 && git merge --no-ff e9043cc36249eeebba10b50d416996dec98292b8
node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 2aa8456cf2d66ec3825d262686fe4218e57cfd27 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-musk
os-musk marked this pull request as ready for review September 2, 2026 18:10
@os-musk
os-musk enabled auto-merge September 2, 2026 18:10
@os-musk
os-musk added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit dc7c226Sep 2, 2026
43 checks passed
@os-musk
os-musk deleted the claude/issue-14422-converge-action-owner-key branch September 2, 2026 19:58
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-musk@claude
, '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

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling - #14667

Merged
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key
Sep 2, 2026
Merged

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling#14667
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key

Conversation

@os-musk

Copy link
Copy Markdown
Collaborator

Fixes#14422

The action.objectName -> action.object -> object-less 'global' ladder that decides which engine key a standalone action declaration is filed under was written out three times, held equal only by a sentence in each docblock promising lockstep. All three now resolve to one implementation.

What changed

  1. packages/objectql/src/plugin.ts — the private actionObjectKey is deleted and its callers now call standaloneActionOwnerKey directly (same package, no dependency question).
  2. packages/runtime/src/action-execution.tsstandaloneActionOwnerKey joins the ADR-0110 re-export block that already exists in that file for exactly this purpose, and standaloneActionObjectName becomes a delegating alias whose whole body is return standaloneActionOwnerKey(action);.
  3. packages/objectql/src/action-governance.ts — the docblock sentences naming the copies are gone, replaced by a statement of where the one implementation lives and a note not to re-inline it.

The convergence, proved rather than asserted

Rungs 1 and 2 of the ladder, counted across packages/objectql/src and packages/runtime/src:

rung 1 (objectName)rung 2 (object)
before (control), 13bf05d3f33
after11

The one surviving carrier is action-governance.ts:92-93. standaloneActionObjectName keeps no body of its own, and no actionObjectKey symbol survives in either package.

The divergence this removes, which the card did not name

Copy 3 did not merely spell the ladder again. Its terminal rung returned the bare string literal 'global' (plugin.ts:2242 on origin/main), while copies 1 and 2 both return the shared GLOBAL_ACTION_OBJECT_KEY constant. Re-measured here on 13bf05d3f: the constant is declared 'global' at action-governance.ts:61, and all three ladders were driven over a twelve-row truth table (both rungs, the empty-string and wrong-type fall-throughs, null and undefined actions) and agreed on 12 of 12 inputs. So nothing was broken and this stayed a convergence card, not a defect card.

What copy 3 was, was the one spelling that would have parted from the other two in silence the day that constant's value changed — equal in value, invisible to every test in the repo. The same bare literal in the plugin's isArtifactShippedAction reader (objectKey !== 'global') is converged to the constant with it, so the file no longer carries a spelling that can drift from the constant it is supposed to track.

Three dispatch assumptions that measured differently

  • Call sites: four, not one. The triage named plugin.ts:2260. actionObjectKey actually had four call sites — :2260, :2612, :2615 and :2653. The deletion is still right; all four now call the canonical helper.
  • The export had to be published first.standaloneActionOwnerKey was not on @objectstack/objectql's published entry, so the runtime could not import it from the package root at all. Adding it to packages/objectql/src/index.ts is a fourth file, mechanically forced by step 2, and it is what makes objectql the package whose surface grows.
  • The minor lands on objectql, not on runtime. Measured in the built artifacts with a positive control: standaloneActionOwnerKey is declared and exported in packages/objectql/dist/index.d.ts, while standaloneActionOwnerKey, standaloneActionObjectName and GLOBAL_ACTION_OBJECT_KEY each appear 0 times in packages/runtime/dist/index.d.ts against a control of 32 for HttpDispatcheraction-execution.ts is not re-exported from packages/runtime/src/index.ts and the package publishes only .. Hence @objectstack/objectql: minor, @objectstack/runtime: patch.

The _deps decision

Kept, as a delegating alias. The engine helper is standaloneActionOwnerKey(action); the runtime name is standaloneActionObjectName(_deps, action). Every caller was measured first: exactly two, both inside action-execution.ts itself (:1524, :1699), no test importer, no other package. Dropping _deps would move an exported signature to save two characters at two internal call sites, so the alias keeps its arity and its meaning and ownsRoute plus any out-of-repo importer compile and behave exactly as before.

Reverse verification

The docblocks that promised lockstep are replaced by welds that enforce it — action-owner-key-single-source.test.ts in each package, each scoped to its own package's source so neither becomes a cross-package test input. Both were ablated from the committed tree, each mutation confirmed on disk by grep counts before the run and each restore proved by hash equality with the HEAD blob plus an empty git diff HEAD:

  • Re-inline the ladder into the runtime alias (byte-identical behaviour): half A stayed green, 11 passed — and half B went red, 2 failed. That is the point of the structural half: a byte-identical second spelling passes every behavioural assertion, which is exactly how the copies stayed invisible for so long.
  • Re-add copy 3 to the plugin: red, 2 failed, naming the carrier list ['action-governance.ts', 'plugin.ts'] and the returned private method.
  • Restored-tree control: both welds green again, 3 of 3 and 13 of 13, and git status --porcelain empty.

Also in this diff

  • content/docs/permissions/system-context.mdx — the two lines added to the import and re-export blocks shifted every anchored line in action-execution.ts below them by exactly two, so check:check-system-context-census went red on pure line rot. Repaired by the gate's own --fix, which re-anchored :64 to :66, :136 to :138 and :399 to :401 and touched nothing else. Not a behaviour change; the cost of the diff, paid in the diff.
  • packages/runtime/src/http-dispatcher.actions-global-key.test.ts — one clause of a docblock that named ObjectQLPlugin.actionObjectKey as a live symbol.

Verification, all at e9043cc36

  • pnpm --filter @objectstack/objectql exec vitest run264 files, 4550 tests, all passed.
  • pnpm --filter @objectstack/runtime exec vitest run210 files, 3092 tests, all passed.
  • pnpm --filter @objectstack/objectql --filter @objectstack/runtime run typecheck — clean, including objectql's check:test-typecheck (44 files / 242 errors / 69 pinned signatures, ratchet held). The new objectql test file is in that program, confirmed with --listFiles. The new runtime test file is read by no tsc program (runtime's typecheck excludes tests, TEST_DEBT ledger), so its enforcing runner is vitest alone — stated rather than implied.
  • The gate family derived from this diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 62 families, all green. The docs file above pulled in 24 families beyond the first derivation, which is why it was re-derived after the tree was final. check-test-completeness and pm/check-half-states exit 3 by their own definition when run without CI's arguments and are recorded NOT MEASURED, not green. check:dual-build-cjs-loads and check:type-check-debt also exit 3 until the workspace is built; the full package closure was built and both then reported OK.
  • Always-runs: pnpm lint over the whole repo exit 0 (no narrowing claimed), pnpm check:nul-bytes OK over 7,998 files, pnpm check:error-status-conformance OK.

No package.json is touched, so Validate Package Dependencies is not implicated by this branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68


Generated by Claude Code

…er one spelling
The `action.objectName` -> `action.object` -> object-less `'global'` ladder was
written out three times — `standaloneActionOwnerKey` (objectql), the runtime's
`standaloneActionObjectName`, and the private `ObjectQLPlugin.actionObjectKey` —
held equal only by a sentence in each docblock. All three now resolve to the one
implementation: the plugin calls it directly (same package), and the runtime
re-exports it in the ADR-0110 block that already exists for that purpose,
keeping `standaloneActionObjectName` as a delegating alias so its exported
signature does not move.
The plugin copy terminated on a bare `'global'` literal where the other two
return `GLOBAL_ACTION_OBJECT_KEY`; equal today, silently different the day the
constant moves. Its `isArtifactShippedAction` reader is converged with it.
The lockstep docblocks are replaced by package-local welds
(`action-owner-key-single-source.test.ts` in each package), because a docblock
is not a check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
The two lines added to `action-execution.ts`'s import and re-export blocks
shifted every anchored line below them by exactly two, so
`check:check-system-context-census` went red with pure line rot: the two real
`isSystem` read sites moved to :138 and :401 with no row anchoring them, while
the page still pointed at :136, :399 and :64. Repaired by the gate's own
`--fix`, which rewrote the three citations and nothing else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/runtime, touching 5 documentable anchor(s). ⚠️2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/objects.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/plugins/packages.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx(via ObjectQLPlugin (symbol, a top-level class))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via ObjectQLPlugin (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 33 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3b7acfba2285187f15d771da7dfb225af6d7ab4d — the merge of head e9043cc36249eeebba10b50d416996dec98292b8 into base 2aa8456cf2d66ec3825d262686fe4218e57cfd27, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3b7acfba2285187f15d771da7dfb225af6d7ab4d && git checkout 3b7acfba2285187f15d771da7dfb225af6d7ab4d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2aa8456cf2d66ec3825d262686fe4218e57cfd27 e9043cc36249eeebba10b50d416996dec98292b8 && git checkout -B drift-repro 2aa8456cf2d66ec3825d262686fe4218e57cfd27 && git merge --no-ff e9043cc36249eeebba10b50d416996dec98292b8
node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 2aa8456cf2d66ec3825d262686fe4218e57cfd27 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-musk
os-musk marked this pull request as ready for review September 2, 2026 18:10
@os-musk
os-musk enabled auto-merge September 2, 2026 18:10
@os-musk
os-musk added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit dc7c226Sep 2, 2026
43 checks passed
@os-musk
os-musk deleted the claude/issue-14422-converge-action-owner-key branch September 2, 2026 19:58
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-musk@claude
, '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

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling - #14667

Merged
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key
Sep 2, 2026
Merged

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling#14667
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key

Conversation

@os-musk

Copy link
Copy Markdown
Collaborator

Fixes#14422

The action.objectName -> action.object -> object-less 'global' ladder that decides which engine key a standalone action declaration is filed under was written out three times, held equal only by a sentence in each docblock promising lockstep. All three now resolve to one implementation.

What changed

  1. packages/objectql/src/plugin.ts — the private actionObjectKey is deleted and its callers now call standaloneActionOwnerKey directly (same package, no dependency question).
  2. packages/runtime/src/action-execution.tsstandaloneActionOwnerKey joins the ADR-0110 re-export block that already exists in that file for exactly this purpose, and standaloneActionObjectName becomes a delegating alias whose whole body is return standaloneActionOwnerKey(action);.
  3. packages/objectql/src/action-governance.ts — the docblock sentences naming the copies are gone, replaced by a statement of where the one implementation lives and a note not to re-inline it.

The convergence, proved rather than asserted

Rungs 1 and 2 of the ladder, counted across packages/objectql/src and packages/runtime/src:

rung 1 (objectName)rung 2 (object)
before (control), 13bf05d3f33
after11

The one surviving carrier is action-governance.ts:92-93. standaloneActionObjectName keeps no body of its own, and no actionObjectKey symbol survives in either package.

The divergence this removes, which the card did not name

Copy 3 did not merely spell the ladder again. Its terminal rung returned the bare string literal 'global' (plugin.ts:2242 on origin/main), while copies 1 and 2 both return the shared GLOBAL_ACTION_OBJECT_KEY constant. Re-measured here on 13bf05d3f: the constant is declared 'global' at action-governance.ts:61, and all three ladders were driven over a twelve-row truth table (both rungs, the empty-string and wrong-type fall-throughs, null and undefined actions) and agreed on 12 of 12 inputs. So nothing was broken and this stayed a convergence card, not a defect card.

What copy 3 was, was the one spelling that would have parted from the other two in silence the day that constant's value changed — equal in value, invisible to every test in the repo. The same bare literal in the plugin's isArtifactShippedAction reader (objectKey !== 'global') is converged to the constant with it, so the file no longer carries a spelling that can drift from the constant it is supposed to track.

Three dispatch assumptions that measured differently

  • Call sites: four, not one. The triage named plugin.ts:2260. actionObjectKey actually had four call sites — :2260, :2612, :2615 and :2653. The deletion is still right; all four now call the canonical helper.
  • The export had to be published first.standaloneActionOwnerKey was not on @objectstack/objectql's published entry, so the runtime could not import it from the package root at all. Adding it to packages/objectql/src/index.ts is a fourth file, mechanically forced by step 2, and it is what makes objectql the package whose surface grows.
  • The minor lands on objectql, not on runtime. Measured in the built artifacts with a positive control: standaloneActionOwnerKey is declared and exported in packages/objectql/dist/index.d.ts, while standaloneActionOwnerKey, standaloneActionObjectName and GLOBAL_ACTION_OBJECT_KEY each appear 0 times in packages/runtime/dist/index.d.ts against a control of 32 for HttpDispatcheraction-execution.ts is not re-exported from packages/runtime/src/index.ts and the package publishes only .. Hence @objectstack/objectql: minor, @objectstack/runtime: patch.

The _deps decision

Kept, as a delegating alias. The engine helper is standaloneActionOwnerKey(action); the runtime name is standaloneActionObjectName(_deps, action). Every caller was measured first: exactly two, both inside action-execution.ts itself (:1524, :1699), no test importer, no other package. Dropping _deps would move an exported signature to save two characters at two internal call sites, so the alias keeps its arity and its meaning and ownsRoute plus any out-of-repo importer compile and behave exactly as before.

Reverse verification

The docblocks that promised lockstep are replaced by welds that enforce it — action-owner-key-single-source.test.ts in each package, each scoped to its own package's source so neither becomes a cross-package test input. Both were ablated from the committed tree, each mutation confirmed on disk by grep counts before the run and each restore proved by hash equality with the HEAD blob plus an empty git diff HEAD:

  • Re-inline the ladder into the runtime alias (byte-identical behaviour): half A stayed green, 11 passed — and half B went red, 2 failed. That is the point of the structural half: a byte-identical second spelling passes every behavioural assertion, which is exactly how the copies stayed invisible for so long.
  • Re-add copy 3 to the plugin: red, 2 failed, naming the carrier list ['action-governance.ts', 'plugin.ts'] and the returned private method.
  • Restored-tree control: both welds green again, 3 of 3 and 13 of 13, and git status --porcelain empty.

Also in this diff

  • content/docs/permissions/system-context.mdx — the two lines added to the import and re-export blocks shifted every anchored line in action-execution.ts below them by exactly two, so check:check-system-context-census went red on pure line rot. Repaired by the gate's own --fix, which re-anchored :64 to :66, :136 to :138 and :399 to :401 and touched nothing else. Not a behaviour change; the cost of the diff, paid in the diff.
  • packages/runtime/src/http-dispatcher.actions-global-key.test.ts — one clause of a docblock that named ObjectQLPlugin.actionObjectKey as a live symbol.

Verification, all at e9043cc36

  • pnpm --filter @objectstack/objectql exec vitest run264 files, 4550 tests, all passed.
  • pnpm --filter @objectstack/runtime exec vitest run210 files, 3092 tests, all passed.
  • pnpm --filter @objectstack/objectql --filter @objectstack/runtime run typecheck — clean, including objectql's check:test-typecheck (44 files / 242 errors / 69 pinned signatures, ratchet held). The new objectql test file is in that program, confirmed with --listFiles. The new runtime test file is read by no tsc program (runtime's typecheck excludes tests, TEST_DEBT ledger), so its enforcing runner is vitest alone — stated rather than implied.
  • The gate family derived from this diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 62 families, all green. The docs file above pulled in 24 families beyond the first derivation, which is why it was re-derived after the tree was final. check-test-completeness and pm/check-half-states exit 3 by their own definition when run without CI's arguments and are recorded NOT MEASURED, not green. check:dual-build-cjs-loads and check:type-check-debt also exit 3 until the workspace is built; the full package closure was built and both then reported OK.
  • Always-runs: pnpm lint over the whole repo exit 0 (no narrowing claimed), pnpm check:nul-bytes OK over 7,998 files, pnpm check:error-status-conformance OK.

No package.json is touched, so Validate Package Dependencies is not implicated by this branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68


Generated by Claude Code

…er one spelling
The `action.objectName` -> `action.object` -> object-less `'global'` ladder was
written out three times — `standaloneActionOwnerKey` (objectql), the runtime's
`standaloneActionObjectName`, and the private `ObjectQLPlugin.actionObjectKey` —
held equal only by a sentence in each docblock. All three now resolve to the one
implementation: the plugin calls it directly (same package), and the runtime
re-exports it in the ADR-0110 block that already exists for that purpose,
keeping `standaloneActionObjectName` as a delegating alias so its exported
signature does not move.
The plugin copy terminated on a bare `'global'` literal where the other two
return `GLOBAL_ACTION_OBJECT_KEY`; equal today, silently different the day the
constant moves. Its `isArtifactShippedAction` reader is converged with it.
The lockstep docblocks are replaced by package-local welds
(`action-owner-key-single-source.test.ts` in each package), because a docblock
is not a check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
The two lines added to `action-execution.ts`'s import and re-export blocks
shifted every anchored line below them by exactly two, so
`check:check-system-context-census` went red with pure line rot: the two real
`isSystem` read sites moved to :138 and :401 with no row anchoring them, while
the page still pointed at :136, :399 and :64. Repaired by the gate's own
`--fix`, which rewrote the three citations and nothing else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/runtime, touching 5 documentable anchor(s). ⚠️2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/objects.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/plugins/packages.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx(via ObjectQLPlugin (symbol, a top-level class))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via ObjectQLPlugin (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 33 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3b7acfba2285187f15d771da7dfb225af6d7ab4d — the merge of head e9043cc36249eeebba10b50d416996dec98292b8 into base 2aa8456cf2d66ec3825d262686fe4218e57cfd27, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3b7acfba2285187f15d771da7dfb225af6d7ab4d && git checkout 3b7acfba2285187f15d771da7dfb225af6d7ab4d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2aa8456cf2d66ec3825d262686fe4218e57cfd27 e9043cc36249eeebba10b50d416996dec98292b8 && git checkout -B drift-repro 2aa8456cf2d66ec3825d262686fe4218e57cfd27 && git merge --no-ff e9043cc36249eeebba10b50d416996dec98292b8
node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 2aa8456cf2d66ec3825d262686fe4218e57cfd27 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-musk
os-musk marked this pull request as ready for review September 2, 2026 18:10
@os-musk
os-musk enabled auto-merge September 2, 2026 18:10
@os-musk
os-musk added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit dc7c226Sep 2, 2026
43 checks passed
@os-musk
os-musk deleted the claude/issue-14422-converge-action-owner-key branch September 2, 2026 19:58
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-musk@claude
, '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

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling - #14667

Merged
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key
Sep 2, 2026
Merged

refactor(objectql,runtime): give the standalone-action owner-key ladder one spelling#14667
os-musk merged 3 commits into
mainfrom
claude/issue-14422-converge-action-owner-key

Conversation

@os-musk

Copy link
Copy Markdown
Collaborator

Fixes#14422

The action.objectName -> action.object -> object-less 'global' ladder that decides which engine key a standalone action declaration is filed under was written out three times, held equal only by a sentence in each docblock promising lockstep. All three now resolve to one implementation.

What changed

  1. packages/objectql/src/plugin.ts — the private actionObjectKey is deleted and its callers now call standaloneActionOwnerKey directly (same package, no dependency question).
  2. packages/runtime/src/action-execution.tsstandaloneActionOwnerKey joins the ADR-0110 re-export block that already exists in that file for exactly this purpose, and standaloneActionObjectName becomes a delegating alias whose whole body is return standaloneActionOwnerKey(action);.
  3. packages/objectql/src/action-governance.ts — the docblock sentences naming the copies are gone, replaced by a statement of where the one implementation lives and a note not to re-inline it.

The convergence, proved rather than asserted

Rungs 1 and 2 of the ladder, counted across packages/objectql/src and packages/runtime/src:

rung 1 (objectName)rung 2 (object)
before (control), 13bf05d3f33
after11

The one surviving carrier is action-governance.ts:92-93. standaloneActionObjectName keeps no body of its own, and no actionObjectKey symbol survives in either package.

The divergence this removes, which the card did not name

Copy 3 did not merely spell the ladder again. Its terminal rung returned the bare string literal 'global' (plugin.ts:2242 on origin/main), while copies 1 and 2 both return the shared GLOBAL_ACTION_OBJECT_KEY constant. Re-measured here on 13bf05d3f: the constant is declared 'global' at action-governance.ts:61, and all three ladders were driven over a twelve-row truth table (both rungs, the empty-string and wrong-type fall-throughs, null and undefined actions) and agreed on 12 of 12 inputs. So nothing was broken and this stayed a convergence card, not a defect card.

What copy 3 was, was the one spelling that would have parted from the other two in silence the day that constant's value changed — equal in value, invisible to every test in the repo. The same bare literal in the plugin's isArtifactShippedAction reader (objectKey !== 'global') is converged to the constant with it, so the file no longer carries a spelling that can drift from the constant it is supposed to track.

Three dispatch assumptions that measured differently

  • Call sites: four, not one. The triage named plugin.ts:2260. actionObjectKey actually had four call sites — :2260, :2612, :2615 and :2653. The deletion is still right; all four now call the canonical helper.
  • The export had to be published first.standaloneActionOwnerKey was not on @objectstack/objectql's published entry, so the runtime could not import it from the package root at all. Adding it to packages/objectql/src/index.ts is a fourth file, mechanically forced by step 2, and it is what makes objectql the package whose surface grows.
  • The minor lands on objectql, not on runtime. Measured in the built artifacts with a positive control: standaloneActionOwnerKey is declared and exported in packages/objectql/dist/index.d.ts, while standaloneActionOwnerKey, standaloneActionObjectName and GLOBAL_ACTION_OBJECT_KEY each appear 0 times in packages/runtime/dist/index.d.ts against a control of 32 for HttpDispatcheraction-execution.ts is not re-exported from packages/runtime/src/index.ts and the package publishes only .. Hence @objectstack/objectql: minor, @objectstack/runtime: patch.

The _deps decision

Kept, as a delegating alias. The engine helper is standaloneActionOwnerKey(action); the runtime name is standaloneActionObjectName(_deps, action). Every caller was measured first: exactly two, both inside action-execution.ts itself (:1524, :1699), no test importer, no other package. Dropping _deps would move an exported signature to save two characters at two internal call sites, so the alias keeps its arity and its meaning and ownsRoute plus any out-of-repo importer compile and behave exactly as before.

Reverse verification

The docblocks that promised lockstep are replaced by welds that enforce it — action-owner-key-single-source.test.ts in each package, each scoped to its own package's source so neither becomes a cross-package test input. Both were ablated from the committed tree, each mutation confirmed on disk by grep counts before the run and each restore proved by hash equality with the HEAD blob plus an empty git diff HEAD:

  • Re-inline the ladder into the runtime alias (byte-identical behaviour): half A stayed green, 11 passed — and half B went red, 2 failed. That is the point of the structural half: a byte-identical second spelling passes every behavioural assertion, which is exactly how the copies stayed invisible for so long.
  • Re-add copy 3 to the plugin: red, 2 failed, naming the carrier list ['action-governance.ts', 'plugin.ts'] and the returned private method.
  • Restored-tree control: both welds green again, 3 of 3 and 13 of 13, and git status --porcelain empty.

Also in this diff

  • content/docs/permissions/system-context.mdx — the two lines added to the import and re-export blocks shifted every anchored line in action-execution.ts below them by exactly two, so check:check-system-context-census went red on pure line rot. Repaired by the gate's own --fix, which re-anchored :64 to :66, :136 to :138 and :399 to :401 and touched nothing else. Not a behaviour change; the cost of the diff, paid in the diff.
  • packages/runtime/src/http-dispatcher.actions-global-key.test.ts — one clause of a docblock that named ObjectQLPlugin.actionObjectKey as a live symbol.

Verification, all at e9043cc36

  • pnpm --filter @objectstack/objectql exec vitest run264 files, 4550 tests, all passed.
  • pnpm --filter @objectstack/runtime exec vitest run210 files, 3092 tests, all passed.
  • pnpm --filter @objectstack/objectql --filter @objectstack/runtime run typecheck — clean, including objectql's check:test-typecheck (44 files / 242 errors / 69 pinned signatures, ratchet held). The new objectql test file is in that program, confirmed with --listFiles. The new runtime test file is read by no tsc program (runtime's typecheck excludes tests, TEST_DEBT ledger), so its enforcing runner is vitest alone — stated rather than implied.
  • The gate family derived from this diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 62 families, all green. The docs file above pulled in 24 families beyond the first derivation, which is why it was re-derived after the tree was final. check-test-completeness and pm/check-half-states exit 3 by their own definition when run without CI's arguments and are recorded NOT MEASURED, not green. check:dual-build-cjs-loads and check:type-check-debt also exit 3 until the workspace is built; the full package closure was built and both then reported OK.
  • Always-runs: pnpm lint over the whole repo exit 0 (no narrowing claimed), pnpm check:nul-bytes OK over 7,998 files, pnpm check:error-status-conformance OK.

No package.json is touched, so Validate Package Dependencies is not implicated by this branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68


Generated by Claude Code

…er one spelling
The `action.objectName` -> `action.object` -> object-less `'global'` ladder was
written out three times — `standaloneActionOwnerKey` (objectql), the runtime's
`standaloneActionObjectName`, and the private `ObjectQLPlugin.actionObjectKey` —
held equal only by a sentence in each docblock. All three now resolve to the one
implementation: the plugin calls it directly (same package), and the runtime
re-exports it in the ADR-0110 block that already exists for that purpose,
keeping `standaloneActionObjectName` as a delegating alias so its exported
signature does not move.
The plugin copy terminated on a bare `'global'` literal where the other two
return `GLOBAL_ACTION_OBJECT_KEY`; equal today, silently different the day the
constant moves. Its `isArtifactShippedAction` reader is converged with it.
The lockstep docblocks are replaced by package-local welds
(`action-owner-key-single-source.test.ts` in each package), because a docblock
is not a check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
The two lines added to `action-execution.ts`'s import and re-export blocks
shifted every anchored line below them by exactly two, so
`check:check-system-context-census` went red with pure line rot: the two real
`isSystem` read sites moved to :138 and :401 with no row anchoring them, while
the page still pointed at :136, :399 and :64. Repaired by the gate's own
`--fix`, which rewrote the three citations and nothing else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/runtime, touching 5 documentable anchor(s). ⚠️2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/objects.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/plugins/packages.mdx(via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx(via ObjectQLPlugin (symbol, a top-level class))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via ObjectQLPlugin (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/objectql/src/action-governance.ts, packages/objectql/src/index.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 33 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3b7acfba2285187f15d771da7dfb225af6d7ab4d — the merge of head e9043cc36249eeebba10b50d416996dec98292b8 into base 2aa8456cf2d66ec3825d262686fe4218e57cfd27, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3b7acfba2285187f15d771da7dfb225af6d7ab4d && git checkout 3b7acfba2285187f15d771da7dfb225af6d7ab4d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2aa8456cf2d66ec3825d262686fe4218e57cfd27 e9043cc36249eeebba10b50d416996dec98292b8 && git checkout -B drift-repro 2aa8456cf2d66ec3825d262686fe4218e57cfd27 && git merge --no-ff e9043cc36249eeebba10b50d416996dec98292b8
node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 2aa8456cf2d66ec3825d262686fe4218e57cfd27 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-musk
os-musk marked this pull request as ready for review September 2, 2026 18:10
@os-musk
os-musk enabled auto-merge September 2, 2026 18:10
@os-musk
os-musk added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit dc7c226Sep 2, 2026
43 checks passed
@os-musk
os-musk deleted the claude/issue-14422-converge-action-owner-key branch September 2, 2026 19:58
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-musk@claude