Skip to content

fix(runtime): install ctx.log.debug in the sandbox — the fourth level the CLI and docs already promise - #7908

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-7661-ctx-log-debug
Aug 12, 2026
Merged

fix(runtime): install ctx.log.debug in the sandbox — the fourth level the CLI and docs already promise#7908
hotlong merged 1 commit into
mainfrom
claude/issue-7661-ctx-log-debug

Conversation

@hotlong

@hotlong hotlong commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Fixes #7661

ctx.log.debug was declared on three surfaces and implemented on none. An author who followed the documentation wrote a body whose declared capability was satisfied and whose call then threw TypeError: not a function inside the VM — under onError: 'abort', an aborted write rather than a missing log line.

Direction is the enforce arm of ADR-0049, per the ruling on the card: all four surfaces now agree on four methods.

The premise, measured before any code was written

The ruling hangs on two halves, both verified on origin/main @ d91fad5:

Half Verdict Evidence
Logger.debug(message, meta) exists on the contract ✅ holds packages/spec/src/contracts/logger.ts:15 — declared, two-arg, alongside info/warn/error/fatal
the logger the log capability is served from carries a callable debug ✅ holds the surface is built from opts.logger (body-runner.ts buildBodyLogSurface, #7448), which all four app-plugin.ts construction sites populate with the kernel's ctx.logger = createLogger()ObjectLogger, whose debug (packages/core/src/logger.ts:401) writes at level debug through the real sink. Not absent, not a throwing stub, not a no-op. body-runner.ts:164 already calls opts.logger?.debug?.(…) on that same object — the [BodyRunner] hook fired line QA run #7439 observed at --log-level debug.

And the gap itself is still live on current main — this is not an already-fixed card:

  • packages/cli/src/utils/extract-hook-body.ts:56/ctx\.log\.(?:info|warn|error|debug)\b/log, debug still in the alternation
  • content/docs/automation/hook-bodies.mdx:317 — still teaches ctx.log.info / warn / error / debug
  • quickjs-runner.ts:639 — still ['info', 'warn', 'error']

What changed

  • quickjs-runner.ts — the ctx.log install loop covers four levels.
  • script-runner.tsScriptContext['log'] declares debug.
  • body-runner.ts — the host-side surface forwards debug to the factory's logger, and warns for it in the no-logger branch. This file is load-bearing for the fix, not incidental: installCtx forwards through the optional call ctx.log?.[level]?.(…), so a VM-side debug with no host-side member is not a throw but a silent drop — the [finding] Hook body ctx.log output is unobservable — body-runner wires log: engineCtx?.logger with no fallback, so the declared ['log'] capability can silently produce nothing #7448 defect verbatim. (Noted for the file-surface declaration: the card named quickjs-runner.ts + script-runner.ts; body-runner.ts is the third file in the same runtime/src/sandbox/** directory the claim reserved, and the consequence cannot be delivered without it. Nothing outside that directory is touched.)

Two pre-existing test doubles built a three-member ctx.log; they now build four. packages/cli/src/utils/extract-hook-body.ts and the docs table are deliberately untouched — under the enforce arm both were already correct.

Reverse-verification — measured in both directions

The pin must fail for a missing debug and for a no-op debug. Both arms were run.

1. Install reverted (i.e. the pin against unmodified main) — red, with the card's own error:

FAIL  src/sandbox/body-log-capability.test.ts > [#7661] … > delivers the card's own reproduction
  Caused by: SandboxError: hook 'h' threw: TypeError: not a function
   ❯ QuickJSScriptRunner.execute src/sandbox/quickjs-runner.ts:400:17
FAIL  … > gates debug behind the log capability like the other three levels
  AssertionError: expected [Function] to throw error matching /capability 'log' not granted/
  + Received: "hook 'ungranted_hook' threw: TypeError: not a function"

 Tests  4 failed | 5 passed (9)

2. VM install kept, host-side forward removed (the no-op arm) — still red, and nothing throws:

FAIL  … > delivers the card's own reproduction — ctx.log.debug('hi') — as a debug record
  AssertionError: expected +0 to be 1
FAIL  … > carries structured data across the VM boundary as a value
  AssertionError: expected undefined to be 'debug'

 Tests  2 failed | 7 passed (9)

That is the point the card insisted on: a debug installed as a no-op passes a throw-only test, so every assertion is on what the host logger received, not on the absence of an exception.

3. After the fix — green:

 Test Files  1 passed (1)
      Tests  9 passed (9)

Verification record

Command Result
npx vitest run src/sandbox/body-log-capability.test.ts 9 passed (4 new)
npx vitest run src/sandbox/ 12 files, 138 passed
npx vitest run (whole @objectstack/runtime) 137 files, 2090 passed
pnpm --filter @objectstack/runtime... build success (DTS included)
pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' 70/70 successful
pnpm check:changeset-gate-self-tests ✓ 118 + 153 + 117 assertions
node scripts/check-empty-changeset.mjs ✓ 1 declaring changeset added
node scripts/check-changeset-no-major.mjs ✓ no major bump
node scripts/check-nul-bytes.mjs ✓ 7231 files scanned
pnpm check:type-check-debt ✓ OK — 33 entries re-measured, none above its recorded number

Gate families derived with node scripts/pm/dispatch-gates.mjs <changed paths> (→ check:changeset-gate-self-tests), plus the convention-scoped gates and the affected package's suites.

On the type-check ratchet. The first run reported @objectstack/runtime TEST_DEBT 227 → 229 (+2). The two new errors were real and mine: making debug required on ScriptContext['log'] broke the two three-member test doubles (capability-denial-is-a-fault.test.ts:95, quickjs-runner.test.ts:114, both TS2741: Property 'debug' is missing). They were fixed, not ledgered — the entry is back to exactly its frozen 227 and the ceiling was not raised.


Generated by Claude Code

`ctx.log.debug` was declared on three surfaces and implemented on none: the
CLI capability extractor matched it and granted `log`, the docs table taught
it, and the QuickJS install loop covered only `info`/`warn`/`error`. A body
that followed the documentation therefore threw `TypeError: not a function`
inside the VM — and under `onError: 'abort'` that aborted the write.

Enforced rather than retired (ADR-0049). The `crypto.hash` precedent this
shape echoes (#4391) was removed because implementing it widened the sandbox's
security surface; emitting a debug-level diagnostic carries no such argument,
and `Logger.debug(message, meta)` already existed on the contract.

- `quickjs-runner.ts`: the install loop now covers four levels.
- `script-runner.ts`: `ScriptContext['log']` declares `debug`.
- `body-runner.ts`: the host-side log surface forwards `debug` to the
  factory's logger, and warns for it in the no-logger branch. Without this
  the new VM method would be a SILENT DROP rather than a throw, since
  `installCtx` forwards through the optional call `ctx.log?.[level]?.(…)`.

Two existing test doubles built a three-member `ctx.log`; they now build four.

Reverse-verified in both directions: with the install reverted the new pin
fails with the card's own `TypeError: not a function`, and with only the
host-side forward removed it fails on the delivery assertions while nothing
throws — so the pin cannot be satisfied by a no-op `debug`.

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

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 12, 2026 3:47am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/runtime.

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

  • content/docs/api/client-sdk.mdx (via packages/runtime)
  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/runtime)
  • content/docs/kernel/cluster.mdx (via @objectstack/runtime)
  • content/docs/permissions/authentication.mdx (via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime)
  • content/docs/permissions/system-context.mdx (via packages/runtime)
  • content/docs/plugins/packages.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime)

2 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/runtime)
  • content/docs/releases/v17.mdx (via @objectstack/runtime)

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.

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

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] ctx.log.debug is inferred, granted and documented, but the sandbox never installs it — a body calling it throws TypeError: not a function

2 participants