Skip to content

feat(senpi-codemode): bind kernel tool invoker authority - #4390

Open
code-yeongyu wants to merge 8 commits into
mainfrom
feat/kernel-tool-invoker-authority
Open

code-yeongyu wants to merge 8 commits into
mainfrom
feat/kernel-tool-invoker-authority

Conversation

@code-yeongyu

@code-yeongyu code-yeongyu commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Summary

Part of #4309.

Stacked on #4387 at 42e0e2707c118b438da2414f5dc22496e619976e; the base is feat/eval-kernel-permission-core, not main.

  • The coding-agent dispatcher binds the calling SessionManager's identity in host async context. A kernel-tool capability captured in a parent and executed by a child therefore uses the child's identity. The invocation wrapper overwrites caller-supplied invoker options; tool arguments remain ordinary data.
  • The shared host pump forwards invocation options into PR 2's authority hook before posting a worker frame. Absent or owner identity gets an owner token. A cross-session invocation is refused, with an activating deny rule attached, while either session enforces. With neither enforcing, JS mints an invoker-session token with its execution cwd and cancellation signal.
  • Python applies the same cross-session refusal before posting to its interpreter. Invoker identity is never added to the cell-facing protocol.
  • Live kernel owners publish permission snapshots. The typed host-only permissions.resolveSession seam lets the permission integration resolve sessions that have never opened a kernel. As in PR 2, connecting the real permission extension remains integration work; an unregistered session without that resolver retains the core's inactive behavior.

Evidence (initial implementation)

  • RED at the parent implementation: four cross-session tests resolved instead of refusing, with four compatibility controls passing. An additional RED run with a parent-captured capability proved that binding identity only when the capability is retrieved is insufficient.
  • GREEN: all 13 new contracts pass, including worker and inline JS, live Python in both enforcement directions, zero body-counter effects on refusal, owner identity forged in both arguments and invocation options, and token session/cwd/epoch/cancellation attribution.
  • bun run check passed.
  • The prescribed gated push passed both gates: changelog coverage and the codemode package test command with all three new regression files. The latter ran the complete package suite: 296 files passed, 5 skipped; 2,342 tests passed, 20 skipped. Both gates and the push returned exit code 0.
  • Real CLI mock-model QA passed: two model turns, auto-preset eval refusal reached the model, the cell created nothing, final assistant response returned, and real auth remained unchanged.
  • The live kernel-scope QA driver passed: denied nested write never reached the bridge, allowed read completed, and the parent cell survived.
  • The existing dispatcher-seam suite has one child-bootstrap failure, tool_child_bootstrap_failed: exited_before_ready; the same test fails at the untouched parent commit 42e0e2707c. Its other six tests pass. No test was weakened.

The CLI harness needed the inherited SENPI_RPC_INTERNAL_KEY_STDIN bootstrap flag removed from its subprocess environment. This is a QA-launcher isolation detail, not a product change.

QA artifacts are retained locally under local-ignore/qa-evidence/20261027-pr2b-invoker/:

e8c345dcb9b749223a84a11f24e582f7cf9ef66080ff6e5459fb26e8b2785f82  summary.json
b3658184b02eaa0fc74eea212bc60345dffe6c0a6ca1a3d806b453c6f59cf7ea  mock-loop-openai-completions-requests.json
f9af631dc6e75c94d3a8717f719b5abebbb73a6d43bc2455a17e3c6d0fc61851  mock-loop-openai-completions-stderr.txt
6c5d4facf3f110ab6664a871ba9354905509843cbe730cc3cdeb8af008f8b688  mock-loop-openai-completions-stdout.txt

Known limitation: reads performed by the runtime's native module resolver (bare packages, tsconfig aliases, package.json #imports, symlinked packages) are not covered by this work; tracked in #4380.

Kept draft.

CI fold

Pushed 6bbda331f92ca96045ea6a2ef735be8cb678399a without restacking. The parent remains 42e0e2707c118b438da2414f5dc22496e619976e. The CI repair is b4f1d54737; the subsequent review-LOW fold is 6bbda331f9.

Finding Fix or classification Evidence
child-dispatch.test.ts reads an unavailable ctx.sessionManager; both observer-failure.test.ts runtimes throw at getSessionId The dispatcher checks whether a host session manager is present before reading it. Without one, it executes outside the ambient invoker scope, preserving owner authority instead of inheriting another dispatch's identity. A 28-row context matrix covers absent context, observer context, unreadable child context, undefined manager, own manager, inherited manager, and lazy manager, across both runtimes with and without an ambient invoker. RED: 16 matrix failures plus the three existing failures. GREEN: all 34 dispatch tests pass on Node and Bun.
This PR's unapproved codemode imports/contracts Added only two invocation-authority.ts import memberships and 16 invocation-contract approvals under senpi#4390, including three review-fold metadata cases. No baseline cell, historical outcome, or prior approval was removed or changed. reviewBaselineChanges returns zero failures; the comparison has zero unapproved additions owned by this PR. Cold base/head import measurements differ by exactly invocation-authority.ts in each scope.
Runtime inventory: node: eval bootstrap was not delivered Inherited; not changed here. The exact test reproduces at untouched parent 42e0e2707c; the original child/observer tests pass there, 6/6.
Gate import census: Permission authority not initialized at gate-import-observer.ts:12 Inherited observer bootstrap failure; not copied from the newer base into this PR. Direct canonical import census reproduces the same exception at 42e0e2707c. The separate membership measurement uses the repaired upstream observer against both unchanged source targets; it is not represented as a passing canonical gate.
21 unapproved permission-core contracts Inherited from #4387; no approvals added here. All 21 exact contract identities were measured and passed at 42e0e2707c. The nine inherited permission-core import memberships are also present at that base.
CI's blocked Bun.spawnSync interrupt assertion Not reproduced; not claimed fixed or proven inherited. The unchanged six-test file passes at the parent; the head's complete codemode suite also passes, including this file.

Validation:

  • Final gated codemode run: 296 files passed, 5 skipped; 2,345 tests passed, 20 skipped.
  • bun run check: passed, as did the normal pre-commit formatting/lint/typecheck hook.
  • Final permission run with --maxWorkers=4: all 51 files and 1,186 tests passed. The earlier uncapped run had 1,184 passes and two RPC deadline failures; both files passed all seven focused tests at base and head before the final clean full run.
  • Final dispatch matrix on Node and Bun: 40/40 each. Adjacent runner, loader, metadata, and preview-safety coverage: 139/139. Invocation contracts: 16/16.
  • Real CLI mock tool loop and isolated RPC QA: 4/4 checks each, with real auth unchanged.
  • Final gated push: changelog gate, required complete src/core/tool-dispatch test gate, full codemode package test gate, and exact additive-approval review each returned 0; push returned 0.
  • Canonical full gate at final head 6bbda331f92ca96045ea6a2ef735be8cb678399a completed FAIL (240 prompt cells, 5 runtimes). Remaining failures are the inherited import-observer failure/unmeasured imports, 21 inherited permission-core contract additions, and three local Julia JSON-byte ordering mismatches. The three Julia witnesses have identical decoded JSON values to the baseline; the comparison remains unchanged. The gate recorded 2,262 legacy contract outcomes and no unapproved additions owned by feat(senpi-codemode): bind kernel tool invoker authority #4390. The initial base/head attempts hit the local ten-minute watcher deadline; their incomplete output is not counted as a gate result.

Gate-review method: read the repository's gate implementation and baseline-review instructions; use the final focused JSON report's 16 passing invocation contracts plus cold import measurements of this head and the pinned base. Only the import observer comes from the already-repaired upstream harness. The committed baseline receives two measured membership additions only; every non-import baseline section and every existing allowlist node is unchanged.

Local evidence is retained under local-ignore/qa-evidence/20261027-pr4390-ci-fold/. Decisive SHA-256 receipts:

a9befcca296b515fc5e1f52f41498671b9008a51d8a0ad897a04380bf64a37da  dispatch-red.log
eb8645e479aae7f5b6282f88e9ab5137f226001e1814d5e9e9d2d542400bb1a3  dispatch-green.log
743a0147b28da848afa3954ce2568fdbeb58a6d85f9ef88ad11b7bbadb674f12  dispatch-bun.log
2841d5a9db73f472b86be04ab950e52bc7ff7fe246192e1668fb5b67e0cbddf6  codemode-full.log
572633bee0fe6675193ea80334bf21e16e5678f29f8d7df2809e4f3f76f69e3f  check-fixed.log
3ccfe8778192232b72d5f41bffc2267aee97be887f17a97653dcb0960300c321  base-coding.log
9a771719d29dd54aad8e7a3db56d09f45672589a4e1e931bffc3e4032648574a  base-imports.log

CLEAR review LOWs

The receipt for original head 353b6038b735b823f523e04195c6fa6cc3a0895a reported CLEAR / APPROVE-WITH-NITS, with no CRITICAL/HIGH/MEDIUM findings and two LOWs. Both are folded; neither is deferred.

LOW Fix RED / GREEN evidence
A child event/hook can inherit its parent's invoker binding, so its own or captured kernel capability is stamped as the parent. The existing owned handler-registration wrapper binds the emitting host context. This covers every registered event path without replacing handler identity or breaking preview-safe/non-mutating metadata. Six RED rows: session event, queued lifecycle, tool-call hook, tool-result hook, context transform, preview-safe prompt hook. Each now forwards the child's identity for both its own capability and a parent-captured capability, across an await, and restores the outer parent binding.
Refusal names rules[0] as though that rule matched the invoked operation. Refusal now carries reason: "cross_session_authority" and enforcingSessionId; the message/documentation identify rule as an example activating deny rule, not a match against the unexecuted body. Refusal behavior and the existing rule identity remain compatible. Three RED rows for owner enforcement, invoker enforcement, and both. All pass with the structured barrier reason and enforcing session; the existing JS/Python no-effect and inactive controls remain green.

The CLEAR receipt applies to the original head only. No new review verdict is claimed for 6bbda331f9.

Keep no-session contexts on the owner-authority path and clear ambient invokers. Cover all supported context shapes across both dispatch runtimes, and approve only PR 4390 invocation gate additions.
Fold both LOW findings from the PR 4390 review. Registered handlers use their emitting session even inside a parent task. Cross-session errors identify the authority barrier and enforcing session rather than claiming an operation-specific rule match.
…port discovery

Fold the two LOW findings from the #4387 delta review at 49d718a:
- js-kernel-permission-interrupt.test.ts holds a host decision, interrupts,
  and asserts the active token word is dead before any result frame and the
  held write never lands. Replacing the interrupt-time #release with a plain
  registry delete fails it.
- receivedPorts pushes collection entries one at a time, so a frame holding a
  port inside an array or Set past the engine's argument limit is delivered
  with the port working instead of degrading to messageerror. The transfer
  scenario adds a 3,000,000-element array row.
@code-yeongyu
code-yeongyu force-pushed the feat/kernel-tool-invoker-authority branch from 6bbda33 to fb6ae1d Compare October 27, 2026 10:16
code-yeongyu added a commit that referenced this pull request Oct 27, 2026
…ation

A live token with no deny rules kept its invoker binding when it was released,
and the binding only went away at worker retirement. One finished cross-session
call therefore let that invoker revoke and retire the owner's whole kernel long
after the work was done, costing the owner its kernel state.

The binding is now held per retained token and given back as soon as that token
is dead, which closing the kernel makes true. A settled cross-session
continuation that is still alive keeps its binding and stays revocable by its
invoker, which the #4390 contract still pins. Folds the LOW from the #4390
delta review at fb6ae1d. Part of #4309.
code-yeongyu added a commit that referenced this pull request Oct 27, 2026
The fragment and the comment claimed the binding stopped outliving a finished
cross-session call. It does not. A token released alive stays alive under
#4387's rule, so it keeps its binding, and its invoker can still revoke and
retire the owner's kernel until the kernel closes; closing is what ends those
tokens and returns their bindings. Both halves are pinned by the existing
contract, which asserts the retirement still lands right after release and no
longer lands once the kernel has closed.

Tying the binding to the continuation itself is not available: node:async_hooks
createHook emits no init or destroy callbacks under Bun 1.4.4, so nothing
reports when the continuation finishes, and releasing on a guessed signal would
drop revocability that #4390 requires. Part of #4309.
Session replacement (8c749e5 on main) emits session_extensions_removed on
the old runner after it is invalidated. The #4390 handler wrapper read
context.sessionManager first, whose guarded getter throws
StaleExtensionContextError, so the removed extension's handler never ran.

The wrapper now resolves the handler's session without letting a stale
context abort the dispatch: a stale context binds no invoker and runs the
handler under kernelToolInvokerStorage.exit, so it never borrows the
emitting scope's identity, and the context's own guarded getters (kernelTools
included) still refuse it. Other errors still propagate.
Base automatically changed from feat/eval-kernel-permission-core to main October 27, 2026 21:44

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant