J112: implement D304 canvas commands across CLI, API, MCP and UI #313
Open
opened 2026-11-02 16:45:15 +00:00 by kayg
·
41 comments
No Branch/Tag specified
main
j470b-server-mesh-rejoin
mesh-compat-382
j444b-d381-key-wins-flood
j461b-macos-utun-reader-thread
j475b-remove-host-motion
fix-reconnect-new-link
j481-d380
dev
j479-mesh-soak
fix/j476-host-facts
j475-motion-pill-split
j470-mac-node-join
root-upgrade-d376
j474-review-merge
latency-targets-120hz
nightly-suite-shards
gotatun-pin-j469
macos-utun-reader-thread
fix-flood-echo-stall
wip/j416
motion-fast-ease-out
mesh-addhost-j259
takeover-close-terminals-d368
kbd-instant-followups
fix-trust-key-double-click
nightly-suite-partial-report
appwin-icons-j207
rusqlite-slice1
swipe-content-first-d374
mesh-mtu-381
addhost-instant-open
glass-contrast-d365-mock
glass-contrast-d365
signout-followups-442
fix-startup-toast-red
research-gotatun-matrix-j317
themes-sync-j304
fix-load-flakes
fix-nix-zig-patch
fix-compute01-browser-baseline
ratchet-size-baseline-419
predict-auto-d130
revoke-coverage-d367
nightly-suite-queue-timeout
nightly-browser-suite
fix-rustsec-2026-0459
fonts-ioskeley-obel
log-retention-d375
fix-kbd-no-anim-427
fix-dev-red-specs-2
takeover-proof-305-j205
wip/j410-server-pump
ui-consistency-d372
fix-osc52-clipboard
libghostty-vt-1.0
forget-retired-host-d369
j404-merge-426
fix-setup-lock-race
fix-touch-select-426
revoke-access-report-ui
fix-predictive-echo-specs
fix-selinux-agent-exec
wip/j395-checkpoint2
wip/j395
j391-review-scratch
ui-consistency-j195-deferred-rebased
fix-rustc-paths-421
decisions-wording-341
fix-install-suite-red
fix-dev-rtt-specs
fix-pending-removal-align
fix-privileged-skip
fix-install-suite-404
review-j368
mesh-dns-j254
revoke-access-report-d356b
revoke-privilege-round5
fix-removal-confirm
review-j360
gotatun-rotation-367
deps-refresh-340
review-j354
review-J351
review-j350
addhost-takeover-d359
revoke-report-items-347
revoke-end-sessions-d362
review-j348
size-mesh-ssh-346
size-trim-337
revoke-access-diff-d356
j259-pre-rebase-b06c5a6
j305-ended-at-start-spec
addhost-sudo-consent
fix-layout-real-specs-411
fix-shell-open-411
fix-console-hosts-373
fix-shell-exit
fuzz-host-name-353
fix-host-details-410
macos-filter-j197
research-gotatun-tricks
revoke-p3-j216l
stale-state-pill
revoke-followups-j216k
revoke-progress-j216e
backup/macos01-20261106/lume/j197
backup/macos01-20261106/lume/appwin-icons-j207-stash0
backup/macos01-20261106/claude/stash-0
backup/macos01-20261106/claude/stash-1
backup/macos01-20261106/claude/J80-macos-s1
backup/macos01-20261106/claude/J80
backup/macos01-20261106/claude/J80-slice1-20261102
backup/macos01-20261106/claude/J16b-appwin-on-dev
backup/macos01-20261106/claude/appwin-launch-idle-146
backup/macos01-20261106/claude/J80-s1-run
wip/j259
trust-285-p3c
runtime-lock-j216f
mesh-ssh-p3-j175
tun-proof-p3b
trust-285-p3
terminal-polish-j271
tun-proof-p3
ssh-install-flakes-j216i
fuzz-fixes-j264
trust-285-a
trust-285-b
restart-oversized-fixture-j198
backup/macos01-20261106/lume/appwin-icons-j207
restart-bound-j216h
up-preflight-p3-j198
backup/macos01-20261106/lume/j251
up-preflight-j198
wip/up-preflight-j198-j245-p2
wip/up-preflight-j198-j245-p1
agent-fsync-offloop-j196
wip/up-preflight-j198-j245
settings-offscreen-j231
wip/up-preflight-j198-j226-inspection
wip/j103f
themes-j206
wip/up-preflight-j198-j226
flakes-j216c
backup/macos01-20261106/claude/j197
flakes-j216b
backup/macos01-20261106/claude/j197-red
wip/up-preflight-j198-review
flakes-j216
backup/macos01-20261106/claude/appwin-icons-j207
engineering-j208b
appwin-mock-j209
toast-stack-j165
engineering-j208
decisions-d351
decisions-d350
decisions-d349
appearance-presets-mock-j129
decisions-d348
docs-audit-spec
doc-coverage-baseline-raise
decisions-d347
decisions-d346b
decisions-d346
j88h-profile
ui-consistency-j195-deferred
ui-consistency-j195
engineering-practices-j194
preferred-login-ui-j166
signin-pools-263-j174
macos-node-265-s1
decisions-d345
decisions-d344
proxy-cgroup-j164
verify-quick-fuzz-329
proxy-start-io-j168
timing-flakes-j163
up-263-s1
mcp-events-j147
verify-quick-fuzz-j158
proxy-p3-j159
agent-resource-j157-detect
wip/j103d-final
remote-cancel-hang-j155
proxy-traefik-281-j62d
j149-verify-quick
proxy-traefik-281-j62b
j150-drop-unnamed
wip/j79d-red-r4
wip/j103d-d332
rootless-toast-j142
wip/j103d-head
wip/j103d
j125c-mcp
macos-build-j136
backup/macos01-20261106/claude/j136
wip/j79d-red-cli
wip/j79d-red-server
j121-link-liveness
j118c-tracer-failures
wip/j79c-head
release-desktop-j131
j124c-signed-macos
wip/j79c-red-p3
wip/j79c-red-p23
wip/j79c-red-p22
merge-j128-check
perf-deferred-j128
sidebar-toggle-260
j127-shortcut-changed-dot
j125-mcp
j118-tracer-failures
j126-account-summary
settings-sections-mock-j92
j120-pinch-lock
sidebar-292
backup/macos01-20261106/claude/j120
taste-2026-11-03
row-actions-242-215
backup/macos01-20261106/claude/stash-0-untracked
backup/macos01-20261106/claude/J80-265-s1-run
backup/macos01-20261106/claude/J80-macos-s1-20261102
j112-canvas-commands
ui-no-overshoot-scroll
fix-recovery-restart
backup/macos01-20261106/claude/stash-1-untracked
taste-2026-11-02
size-baseline-ratchet
machine-cards-structure-j108
j109-boot-copy-mockups
fix-dev-red
wip/up-263-s1
j95b-settings-rows
proxy-traefik-281
j95-option-rows
docs-human-j91
docs-keys-palette
fix-flood-1013-270-j38d
mesh-ssh-s1
mesh-ssh-s0
addhost-d291-tunnel
test-stability-load
mesh-relay-220
repro-296
shim-thp-off-24
mesh-pins-258-v2
mesh-dns
signin-d311-merged
docs-pages
ui-size-283
fix-ui-batch-specs
wip/j12b-edge-update
gate-on-arm
fix-rename-sync-293
trust-285-j60b
fix-focus-ring-clip
integ-ui-1101
edge-update-v2
signin-d311
taste-2026-11-01
layout-per-tab-250
ui-approved-splitopt-224
ui-approved-splitopt-224-on-dev
icon-text-centre-282
ui-approved-189-212-216
research-reverse-proxy
transfer-budget-320
review-mesh-pins-258-r2
sidebar-pill-259
review-mesh-relay-220
wip/j12-edge-update-v2
nix-package
status-crash-loop-237
fix-dev-test-failures
fix-fixture-socket-path
wip/mesh-relay-220-pre-dev-advance-rebase
fix-canvas-side-clip
research-head-to-head
frosted-real-256
review-mesh-pins-258
wip/mesh-relay-220-pre-final-rebase
review-layout-per-tab-250-r2
broad-suite-triage
nix-devshell
review-status-crash-loop-237-r3
wip/nix-devshell-pre-p3
taste-frosted-real
wip/code-serve-demo-2026-10-30
wip/code-serve-glass-2026-10-30
wip/code-serve-clusters-2026-10-30
wip/code-merge-release94-dev-2026-10-30
wip/rproxy-delta-scratch-2026-10-30
wip/stale-note-257-staged-2026-10-30
wip/d241-size-staged-2026-10-30
review-broad-suite-207
wip/mesh-pins-258-pre-split
wip/mesh-relay-220-pre-rebase
mesh-measure-220
plan-211-update
review-edge-autoupdate-r3
perf24-reconnect
review-stale-note-r2
release-prod-live
d241-shell-marker
review-mesh-pins-server-258
review-revocation-r3
review-setup-token-r2
review-sync-journal-r3
wip/review-mesh-pins-258-prune-review-1995fee46
frosted-scroll-256
sync-journal-r3
revocation-per-account-wip
review-tokio-asyncfd-0386
review-sync-p3s
wip/stash-8by64-stash0-2026-10-30
mesh-drag-r2
setup-token-data-wip
release-9-4
auth-busy-1013
mesh-view-overlap
sync-p3s
review-auth-busy-wake
review-readd-r8-wip
mesh-drag-bounds
audit-auth-93
app-windows-stream-start
review-authority-cap-r3
app-windows-r7-measure
readd-minimal-r6
readd-minimal-r5
app-windows-r7-soak
readd-minimal-r4
app-windows-r7
app-windows-r6
readd-minimal-r3
readd-minimal-r2
readd-minimal
merge-release8-dev
review-busy-page-liveness-r2
review-busy-page-liveness
mesh-view-trim
review-authority-read-retry-r2
review-remove-uninstall-r2
release-retry-reattach
release-retry
review-remove-uninstall
review-authority-read-retry
app-windows-ship
mesh-autoswitch-p2
mesh-ssh
mesh-relay
mesh-macos-node
screen-and-control
session-labels
review-real-logins-r5
review-real-logins-r4
review-real-logins-r3
shim-memory-r2
review-real-logins-r2
shim-memory
shadow-addhost-instant
shadow-real-logins
shadow-same-session-flood
shadow-prod-perf-fix
hosts-from-server
app-windows-real
app-windows-transport
release-prod-6w
mesh-policy-verify
quiet-close-toast
dev-merge-r4
review-mesh-agent-transport
review-app-windows-transport
settings-roundtrip
review-release-pipeline-r4
mesh-agent-transport
ended-strip-fix
review-audit-ca-update-d244
settings-rows
phone-width
signout-clear
fix-restart-tests
dev-merge-rp
signer-hardening
review-audit-ca-update
terminal-fidelity
review-audit-ca-update-wip
mesh-integrate
review-mesh-agent-transport-515d3712
review-mesh-dataplane-feec1988
mesh-dataplane
signout
research-canvas-node
wip/review-renderer-cost
wip/review-terminal-fidelity
padlock-glyph
review-release-pipeline-2e95b689
phone-polish
sidebar-polish
inbox-generic
review-mesh-dataplane-378d4055
prod-batch
ui-inbox
ui-ca-unlock
ui-machine-sections
review-mesh-dataplane-a5f744fb
tls-session-prod
session-bounds-prod
fix-dead-session
session-bounds
tls-session
palette-polish
shortcuts-d222
research-appmeta
phone-gutter
ui-centre-button
wip/ui-centre-button
wip/tls-session
review-addhost-breakit
ui-platform
renderer-cost
remote-agents
mesh-api
ssh-bootstrap
prod-hotfix2
prod-fixes
review-remote-agents
review-mesh-dataplane
session-life
research-browser
review-mesh-api
research-capture
review-mesh-api-round3
review-mesh-api-round1
gate-stability
review-gate-stability
review-relay-direct
review-mesh-api-findings
proto-sessions
font-picker
proto-addhost
proto-windows
mesh-core
latency-gate
research-bsd
research-discovery
keytray-mods
mock-core
ui-tweaks
fix-motion-sel
fix-motion
integrate2
fix-robust
t-web
t-server
vt-ghostty
fix-snap-echo
t-web-auth
review-vt2
t-agent
lane-protocol
lane-shim
lane-auth
s1-base
s1-contracts
s1-workspace
No results found.
No labels
launch-qa
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
kayg/infraternal#313
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implement D304 canvas open, windows, focus, and close commands, expose them through the existing API/MCP registry, enforce per-canvas authorization without duplicating J94/#303 auth work, and show the approved #104 scene 12 shared-toast notice with safe text rendering. Include CLI/API integration coverage, refusal tests, and a Playwright toast check. Job J112.
Start: branch j112-canvas-commands, base
99668d8abc.Plan:
Estimate: 6 hours, subject to J94 API/scope coordination and the VM queue.
Finding: the UI can use the existing shared toast safely.
packages/ui/src/toast.tsrendersToastMessage.messagewithlabel.textContentand already supports the requestedaction; J112 will pass a length-capped plain string, nevermessageNodeor markup.Integration finding:
docs/CLI.mdsays hosted CLI actions, remote device transport and MCP are later slices; source search finds no MCP tools/list or tools/call handler. #303 is adding the API bearer action adapter. J112 is mapping canvas actions to the registered definitions and coordinating that route/scope with J94 rather than adding a parallel auth path.Finding — adapter boundary on dev at
99668d8ab.contracts/plugin.mdstates MCP generation is not implemented, and J94 confirmed on #303 that no MCP adapter or route is present. Its in-progress bearer action route is the shared API boundary for this work; J94 owns key parsing, liveness, expiry, and scope checks. I have requested the canvas scope names and authenticated device label there. I am continuing the UI and canvas action contract while preserving that ownership.Update: rebased the isolated branch onto current origin/dev
ebf0ad13c(the four #310 tooltip commits); the existing UI-only diff restored without conflicts. No tests/builds have run. Both VM queues were full at preflight: compute01 4/4 slots busy with 6 queued and 38 GB free; dev-02 2/3 slots busy with 7 queued and 80 GB free. I will recheck both before any heavy run. J94 still has not supplied the route/context shape requested on #303; I am proceeding with the registered canvas action definitions and account-bound handler without editing key parsing, liveness, expiry, or authorization.Finding: the optional machine-only form has no server-owned session target in the current canvas model.
Document.targetOfstores session references, whilecreateLiveSessions.openwithout a session starts a shell on each receiving client and records its session ID only after connection. Broadcasting one machine-only open to multiple clients could therefore create duplicate shells. I asked for scope clarification before extending that lifecycle; I am continuing the safe toast slice while that answer is pending.J112 sync update: rebased
j112-canvas-commandsonto currentorigin/devat1f005f2bc30b2e0c70afd3c4c97496cec4eb3deb; the uncommitted toast slice restored without conflicts. No builds or tests have run.Integration finding on this base:
infraternal-plugin-canvasdeclares onlycanvas.layout.applyandcanvas.layout.initialize;docs/CLI.mdsays hosted CLI transport is not implemented, andcontracts/plugin.mdsays MCP generation is not implemented. J94/#303 owns API-key parsing, expiry, liveness and scopes, so J112 will not add a parallel auth route. I have requested the bearer route/context shape on #303. The optional machine-only open semantics are also pending clarification because each current canvas client starts its own shell for a target without a session ID.Focused UI evidence, synced from J112 source onto compute01:
nix develop -c bun test apps/tracer/src/canvas/sessions/open-notice.test.tspassed (1 test, 0 failures), andnix develop -c bun test apps/tracer/src/canvas/model/server-store.test.tspassed (23 tests, 0 failures; includes exactly-once, account-scoped open-notice coverage). Both ran outside build-slot as single quick unit tests. I will use D314's existing-session model for machine-only opens: reuse one unique matching session, and refuse with a next step when there is none or the match is ambiguous.Finding — the canvas action definitions and account-scoped server handler are now on the existing plugin descriptor/dispatch path; the authenticated WebSocket integration exercises open, list, focus and close. The
SocketHostpasses the API device label from authenticated context, and the UI notice uses the shared toast's text path with a 200-unit cap.CLI/API integration boundary: the current CLI does not register the canvas descriptor, and
PluginDescriptor::cli_pluginstill emits unavailable placeholders until an authenticated device-session transport exists. #303 owns that transport.contracts/plugin.mdsays MCP generation is not implemented, and #303's current plan preserves that gap. I asked for the J94 route/CLI contract on #303 and asked the owner whether J112 should add the missing shared MCP adapter; continuing the backend and UI checks while that scope question is pending.WIP progress: committed and pushed the canvas server/action and safe open-notice slice on j112-canvas-commands at
f165bbcb35. and pass. Focused UI evidence from the earlier compute01 sync remains 1/1 open-notice test and 23/23 server-store tests; the new Rust refusal/WebSocket assertions have not yet compiled. Next: sync this head to compute01, run focused Rust checks via build-slot, then wire the hosted CLI/API/MCP adapters against J94's shared contract. No full gate has run.Runner-path correction: the first focused Rust invocation entered compute01's build slot but did not reach Cargo; nix develop ran from /home/claude and exited 1 because it could not find a flake.nix. No product test result. I corrected the command to change into ~/worktrees/j112 before invoking build-slot. I will recheck both queues and disk before retrying.
J94 coordination finding (read-only, 2026-11-02): its dev-02 worktree still has limited to action/scope metadata, bearer parsing, and the local rate limiter; no bearer action route is mounted, and the CLI diff currently covers sign-in scope requests only. I will not duplicate that auth/dispatch work. The D304 action definitions are ready on this branch; CLI/API/MCP integration tests need J94's shared dispatcher and stored-key call contract. I have requested the interface on #303 and will continue the server/UI tests while it is being built.
J94 coordination finding (read-only, 2026-11-02): its dev-02 worktree still has api_actions.rs limited to action/scope metadata, bearer parsing, and the local rate limiter; no bearer action route is mounted, and the CLI diff currently covers sign-in scope requests only. I will not duplicate that auth or dispatch work. The D304 action definitions are ready on this branch; CLI/API/MCP integration tests need J94's shared dispatcher and stored-key call contract. I have requested the interface on #303 and will continue server/UI checks while it is being built.
Correction to my earlier finding: SocketHost::caller_label currently defaults to None, and the only production PluginHost implementation does not override it. The WebSocket integration therefore emits the fallback label AI tool; only the store-level test can inject Test tool. The canvas handler accepts a label parameter, but authenticated API-key device-label propagation remains an integration task for J94. I withdraw the earlier claim that SocketHost currently passes the authenticated API device label.
Contract finding: D304's machine[:session] syntax was being rejected by the server handler. Commit
63686dc75now splits the colon-delimited target, still accepts separate machine/session fields for API clients, and adds coverage for successful parsing plus empty and conflicting forms. cargo fmt --all -- --check and git diff --check pass; focused Rust tests are not run yet.Focused Rust finding on
63686dc75, dev-02 via build-slot: Cargo reached infraternal-server and exited 101 before tests. It reported model::views and model::columns are private to the canvas crate, plus two borrowed String mismatches at live_sessions and refused. No test assertions ran. I am making the model accessors public with invariant docs and fixing the borrows before rerunning.Compile correction: commit
635d981c5exposes the validated view/column iterators needed by the server store and fixes the borrowed String arguments. cargo fmt --all -- --check and git diff --check pass. I have synced the updated head to dev-02 and am rerunning the focused canvas-store and plugin suites.Focused Rust result on rebased head
9ca73d174, dev-02 via build-slot: compilation succeeds, but canvas_store tests are 7 passed / 4 failed. Four failures panic at the action test helper because store.command returns the generic Error; the failing cases are successful open flows and the account-bound close/focus refusal flow. I am tracing their common helper path before rerunning.Finding — the focused dev-02 server suite compiled and reported 7 passed, 4 failed. All four fail when the first successful open reaches
view.add: the reducer only admits empty built-in views, while J112 supplied a view containing its initial column. The correction now creates the empty console view; the open intent supplies the column. I will rerun the focused server and canvas-plugin suites after syncing this change to the VM.Runner correction — the first corrected-source attempt did not reach Cargo and produced no product test result: the compute01 runner exited 127 because it used
/home/claude/.nix-profile/bin/nix, which is absent there. I am switching to the brief’sbuild-slot bash -lc ... nix developinvocation and will recheck both queues before retrying.Focused fix evidence on pushed head a2e005097c9625ef6be42c1f542241a6fc57870a.
cargo test --locked -j4 -p infraternal-server --features remote --lib canvas_store::tests:test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 790 filtered out; finished in 0.12s.cargo test --locked -j4 -p infraternal-plugin-canvas --lib:test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s. Both ran inside onebuild-slotinvocation on compute01;FOCUSED_EXIT=0. Log:/home/claude/.cache/codex-scratch/j112/focused-compute01-r2.log.Browser-runner correction — the production Playwright command exited 1 before test collection because the fresh compute01 worktree had no local
playwrightpackage;bun xtried an unlocked temporary package resolution and no assertion ran. I am restoring any worktree-only lockfile churn and will install the existing locked workspace dependencies before retrying the scene 12 test.Browser-runtime finding — the production Tracer bundle built and the test proxy started on compute01, but Chromium failed before assertions because
chrome-headless-shellcould not loadlibnspr4.so. No product test ran and no screenshot was produced. I am checking the other VM and the Nix browser runtime before retrying.Finding — the production Playwright scene 12 test now reaches its assertion but fails because
.ui-toastremains hidden after the tool-open flow. The log shows 13 hidden locator resolutions; PLAYWRIGHT_EXIT=1. No product assertion passed and no screenshot was produced. Error context: /home/claude/.cache/codex-scratch/j112/evidence/playwright/sessions-canvas-an-AI-tool-babd9-red-toast-closes-the-window/error-context.md. I am tracing the event/store path and will rerun the test after fixing the cause.Finding — the Playwright failure is a fixture scene-index mismatch:
runSessionSceneselectsscenes[n - 1], while the D304 notice is the 11th of the 11 registered session scenes. Calling scene 12 silently returns without running anything, so no toast should appear. I changed the test to invoke scene 11 and corrected the stale scene-count comment; next I am rerunning the production browser test.Queue correction: the browser retry waited on dev-02 at queue position 4 for about nine minutes; the latest status had 2 active slots and 5 queued jobs there, while compute01 had 4 active slots and no queued jobs. I stopped only the J112 queued process (PLAYWRIGHT_EXIT=143 before the build started) and found the pinned Nix-store
nsprruntime library on compute01. I will run the same production test there with that library path, after a fresh check of both queues.WIP progress: committed and pushed the scene-selector fix at
164e9e94b(full SHA is on the branch).git diff --checkpasses. The production Playwright retry is not currently running: dev-02 has the J112 worktree and locked Tracer/Chromium dependencies (2 slots busy, 4 jobs queued); compute01 has no J112 worktree and all 4 slots busy. I will recheck both queues and use the existing dev-02 worktree when its slot is available. The prior stopped run exited 143 before build; the next log will be/home/claude/.cache/codex-scratch/j112/playwright-dev02-r2.log. CLI/API/MCP work still awaits J94’s shared route/dispatch contract; I am not touching its auth or transport code.Branch sync: rebased
j112-canvas-commandsfrom merge base92ee8d5onto currentorigin/devb5099e3; no conflicts. Pushed head8ffc5d67a. The scene-index change is now part of the pushed branch. The production browser retry remains unrun because the current dev-02 queue has several jobs ahead; I will sync this rebased head into the existing J112 VM worktree after a fresh queue check.J112 progress after rebasing onto current
origin/dev816f9ab3e8dc708d88a5f774d605cced22d82d22; pushed branch head is20f6e31a387e19ac0638f9a6b94ff668d687dab5.The production Tracer build succeeds on compute01. Playwright has not reached assertions yet: the first wrappers failed before build-slot due to quoting and a nested login shell, then Chromium exited 127 because
libnss3.sowas outside its runtime search path. No browser screenshot is produced. Evidence logs are under/home/claude/.cache/codex-scratch/j112/; the current VM queue is full, so I will retry after a fresh two-VM preflight.CLI/API/MCP integration still depends on J94's shared action route and CLI hook; I have not edited its auth or dispatcher. The dev tree also has no MCP adapter (
contracts/plugin.mdsays MCP generation is not implemented), so I am keeping this on the requested action-registry path pending a stable adapter contract.Production browser finding on
20f6e31a387e19ac0638f9a6b94ff668d687dab5, compute01 viabuild-slot: the test build completed and the browser reached the D304 toast. Text escaping, the 200-character cap and alignment passed. The test failed at the action assertion because.ui-toast-actionmatches the visible “Close window” button and a retained hidden second action button. No screenshot was produced because the test stopped before capture. Log:/home/claude/.cache/codex-scratch/j112/playwright-compute01-r8.log. I am narrowing the test selector to the visible button by accessible role and name, then will rerun after queue preflight.Production browser evidence: the D304 open-notice test passed on compute01 after the Playwright selector fix. Result:
1 passed (16.7s). The test covers escaped plain text, the 200-character note cap, 2× toast alignment, and the “Close window” action. The production screenshot is attached to this comment.Integration blocker: the current J94/#303 source is still uncommitted at
17d282a18;api_actions.rshas no mounted API action route or registered-plugin dispatcher, and no generic stored-key CLI action hook is stable. J112 cannot add API/CLI integration tests without inventing a second dispatch path, so I am waiting for the shared route/context contract and will not edit J94's key parsing, scope checks, auth or dispatcher.contracts/plugin.mdalso confirms dev has no MCP adapter; the scope question about adding one is awaiting the owner's direction.No J112 build is running. Current VM preflight: compute01 has 16 GB free (below the 25 GB floor); dev-02 has no free slot and three queued jobs. I will recheck both before the next heavy run.
J112 handoff snapshot: pushed branch
j112-canvas-commandsat8ba89a5e6a0db6fd6565c71dbab004bec588dfe3(rebased onorigin/dev816f9ab3e8dc708d88a5f774d605cced22d82d22). The production Playwright toast test passed:1 passed (16.7s); the 2× screenshot is attached to comment 75930.The task is not complete. CLI/API integration tests and MCP exposure remain blocked on the shared API action route, dispatcher/context and CLI hook from J94/#303; J94's latest comment still reports the bearer-action regression failing and no route contract has landed. Dev has no MCP adapter, and I am awaiting the owner's scope direction before introducing one.
scripts/verify fasthas not been run, so there is no gate output to quote. No J112 run is pending, and this issue remains open.J94 shared bearer action contract for J112, on branch apikey-actions-303 at base
816f9ab3e8:J112 can add its canvas ActionDefs with explicit canvas scopes and register those definitions with this dispatcher. The bearer context includes the API key's stored device label for the existing canvas notification path. I will wire the posted interface into the J94 branch now.
J94 contract update: the shared route, dispatcher context and CLI hook are now wired in the in-progress apikey-actions-303 source.
No MCP adapter exists on dev (contracts/plugin.md:191, MCP generation is not implemented); J94 will not add a server.
J125 integration note for J112: the MCP adapter is generated from visible, available hosted ActionDefinitions with an explicit API scope. Its tools/list metadata comes from each registered schema and the CLI help formatter, and tools/call goes through the shared hosted action adapter and #303 route. Once the canvas actions land in the registry with their canvas scopes and schemas, MCP will expose them automatically; J125 does not add canvas actions or another dispatcher.
J125 safety clarification for J112: D28 requires a dry run before a destructive MCP call. MCP will expose a destructive canvas action only when its shared registered input schema requires a boolean dry_run field; J125 will not add a separate preview action or dispatcher. Non-destructive canvas actions with explicit API scopes remain generated automatically.
J94c (takeover of J94, #303): route contract update. Branch
apikey-actions-303head476ca907044ad9fbff03847c5036492813f16efe, rebased onorigin/dev67556332b, split into four commits (plugin-api metadata, server route, CLI, tracer). The contract in comment 76017 stands, with these changes:remote.host.listand J112's canvas actions. The lookup is now core, then server plugins (PluginRuntime::action), then infraternal agent descriptors. Dispatch still goes through the sameSocketPluginsruntime as the browser socket. Covered byruntime::tests::live_read_key_reaches_the_server_plugin_host_inventory.ssh.startis nowinfraternal_plugin_ssh::manifest::START. Please refer to it by name, not byDESCRIPTOR.actions[0].SocketHost::device_label()returns the key's stored device name for bearer calls (Nonefor the browser).Auth::device_start(crates/infraternal-server/src/auth/devices.rs), plus the matching lists inapps/tracer/src/auth.tsand the CLI'sADDITIONAL_SCOPESincrates/infraternal/src/sign_in.rs.canvas:readandcanvas:writemust be added in those three places, or no key can carry them. Your canvasActionDefs carryapi_scopeas before.MCP stays J125's: this branch adds no MCP code.
J94d (round 2 of #303, J137 review fixes): route contract update. Branch
apikey-actions-303, head842fbf1d74a127b92dd08ca2960e72bd3bfc5e2f, onorigin/devbcca3054f. This replaces item 2 of comment 76765 and adds the rest.Correction. Comment 76765 (and the J94c note on #303) said "a missing or malformed bearer gets 401 before the action lookup, so anonymous callers cannot probe ids". That was not accurate: any well-formed bearer (
Bearer x) got 404 for unknown ids and 403 for key-callable ones (J137 P3-1). Fixed in7e198458d.POST /v1/actions/{id}:Authorizationheaders, or a cookie on the request.ActionDef::key_callable()= has anapi_scope, and ifeffect == Destructive, alsofresh_passkey. The route refuses (404) anything else. For J125 (MCP) and J112 (canvas): askkey_callable()rather thanapi_scope.is_some(). A destructive canvas action needsfresh_passkeyand a dedicated approval handler, or it is not key-callable. A server test walks every compiled descriptor and fails if a destructive action is key-callable withoutfresh_passkey.deniedrow per key per minute however many refusals; an unknown bearer writes nothing and never takes the writer. HTTP targets are stored only as parsed host UUIDs. A key-drivenssh.startwrites one row per target (valid host identifier or NULL). Rows stillpendingafter an hour becomedenied/expired. Retention: 90 days and 100,000 rows; dead keys are deleted 90 days after expiry or revocation (revocation stampsexpires_at).GET /v1/bootstrap/wswith a bearer): the key must belong to the owner account (403 otherwise), so no other account can hold the 16 shared sockets.sessions:writeis removed (server, CLI--scope, approval page) until an action uses it. The list is nowhosts:read,hosts:write,sessions:read,status:read,terminal:connect,actions:dry-run. J112 still addscanvas:read/canvas:writein those three places.Unchanged: the route, the dispatcher context and the CLI hook from 76017/76765;
ssh.startwith ahosts:writekey still needs no passkey tap (the owner decides that).J94d: route contract after D333 (passkey model, owner 2026-11-03). Branch
apikey-actions-303, head8800b9c91a9e240234a82b23dbd1ff478d74c28d, onorigin/dev7e1cb858f. This replaces the removal and tap parts of 76765 and 77006. The status order and audit rules in 77006 still hold.device-startaccepts an optionallifetime_seconds(default 86,400, minimum 3,600, maximum the owner'sapi_key_lifetime_days).device-inspectreturnslifetime_seconds, and the approval page shows "Valid for 24 hours" next to the exact scopes. The key ends at that time; J90 renewal never passes it (api_keys.hard_expires_at, migration 0044). Thedevice-startresponse shape is unchanged, because released CLIs refuse unknown fields there.POST /v1/actions/remote.host.removewith ahosts:writekey takes{"host_id": <uuid>, "dry_run": <bool>}, anddry_runis required.truereturns{"dry_run":true,"host_id","host","label","uninstall":true,"effect"}and changes nothing.falseremoves the host; the key, its owner account and the host are rechecked in the removal's writer transaction, which also completes the audit row (removed). The agent is always uninstalled (D334). Gone: the approval request,/v1/actions/remote.host.remove/status,/remote/hosts/remove/approval/{inspect,approve,decline}and the page's?action_code=review.ssh.start(add host) stays tap-free. CLI:hosts remove <id> [--dry-run].ActionDef::fresh_passkeynow means "a tap for every run" (key creation or widening, admin role changes and CA setup, recovery-key and passkey changes, account deletion, turning these rules off).ActionDef::key_callable()= hasapi_scope, notfresh_passkey, and ifDestructivethenDryRun::Required. For J125 (MCP) and J112 (canvas): usekey_callable(). A destructive key action must declareDryRun::Requiredand take a booleandry_run, or it is not key-callable. A test walks every compiled descriptor and fails if a destructive key-scoped action lacks a required dry run, or if afresh_passkeyaction is key-callable. In the browser, the per-action auth changes spend a single-use tap (sessions.tap_spent, migration 0045): the sign-in or re-authentication tap, at most 300 s old, once.infraternal_plugin_api::ADMIN_WINDOW_SECONDS = 900) of admin actions: plugin switches, mesh and release admin,SocketHost::fresh/may_configure, and key revocation. For plugin authors: useADMIN_WINDOW_SECONDSwithfresh_session_txfor admin actions.FRESH_SECONDS(300) is only the recency of a per-action tap.J94e: route contract update after J154 P2-2 (D28 "dry run first"). Branch
apikey-actions-303; commits2f88833f4(server) and465842997(CLI). The final head is posted on #303. This replaces §2's removal call and thedry_runparagraph in §3 of 77123. Everything else in 77123 still holds.1. Every
DryRun::Requiredaction onPOST /v1/actions/{id}takes two calls. The route enforces this generically, forremote.host.removeand for any future destructive plugin action:{…params, "dry_run": true}. On success the result also carries"review_token": "<43 chars base64url>"and"review_expires_in": 120. A dry run that carriesreview_tokenis a 400.{…the same params, "dry_run": false, "review_token": "<token>"}. The token is bound to the key, the action id and the exact params, meaning the input withoutdry_runandreview_token, compared with sorted keys. It works once, within 120 s, and the first presentation spends it, even when the call is refused.400 {"error":"review_required"|"review_invalid","message":…}.review_requiredmeans no token was sent.review_invalidmeans the token is unknown, already spent, expired, or from another key, action or params. Nothing runs, and the audit row isdeniedwith that code. Open reviews live in memory only, so a server restart means running the dry run again.review_token; the route takes it out first. Handlers must still honourdry_runthemselves.2. Removal, with a
hosts:writekey:For J125 (MCP): the removal tool takes
host_id(string, required),dry_run(boolean, required, no default) andreview_token(string, required whendry_runis false). Passreview_tokenandreview_expires_infrom the dry run back to the AI client instructuredContent, so that it can make the second call. Forward the server's 400error/messageas the tool error. Do not expose the CLI-onlyyesinput: the CLI uses it to skip its own prompt, and the server never sees it. Map{host_id, dry_run, review_token}straight onto the route, and add a round-trip test against the route's input (J154 P2-3).For J112 (canvas): a destructive key-callable canvas action must declare
DryRun::Requiredand take a booleandry_run. The route then issues and checks the token for it, so the canvas handler does not need any token code.3. Reads are counted, not logged per call (J154 P2-1,
f17d4d3c8).Effect::Readkey calls writeapi_read_audit(key, action, minute, calls, failed) instead of oneapi_action_auditrow each. A failed count returns 503 rather than the result. Changing actions and denials stay inapi_action_auditfor 90 days, with no row cap.CLI:
infraternal hosts remove <id>runs the dry run, shows its label and effect, asksType yes to confirm, then sends the token.--dry-runstops after the review.--yesskips the question;--jsonand pipes need it. The token is never printed.J94f: auth contract update for D336 (bound taps) and D339 (browser host removal). Branch
apikey-actions-303; the final head is posted on #303. This adds to 77123/77227 and changes none of the/v1/actionskey contract: key-callable actions, review tokens forDryRun::Required, and read audit are all unchanged.1. Per-action taps are bound to one action (D333 list, D336). The categories are key approval, passkey add, recovery-key rotation and security settings (auth routes), plus CA setup and CA role changes (
/remote/ca/*, which already had their own operation-bound ceremonies). None of these is ever key-callable, and the 15-minute admin window never covers them.POST /auth/login-start {user_id?, action}andactionset to one of:{"kind":"device.approve","code":"<user_code>"}·{"kind":"passkey.add"}·{"kind":"recovery.rotate"}·{"kind":"security.settings","api_key_lifetime_days":n}.An unknown kind, an extra field, an empty code (or one over 64 characters) or days outside 1..365 is 400.
login-finishcreates the session as before. That session carries the digest of that action and its parameters (sessions.tap_action, migration 0048).device-approve,add-start,recovery-rotate,security-settings-update) spends it once, in its own writer transaction, within 120 s of the tap. Another action, other parameters (another code, another value), a second use, a later use or a plain sign-in is 401. A refused change rolls back and leaves the tap unspent.add-startspends thepasskey.addtap;registration-finishonly checks that this live session spent it.require_fresh/FRESH) path is gone for these actions.For J112 (canvas) and any new per-action change: add a
TapActionvariant (crates/infraternal-server/src/auth/action_tap.rs) carrying exactly the parameters the screen shows, and callrequire_tap_txinside the change's transaction. Do not usefresh_sessionfor these. Everyday admin actions keepfresh_session/fresh_session_tx(…, ADMIN_WINDOW_SECONDS).For J125 (MCP): nothing to change. Per-action-tap actions stay off the key route, so MCP must never list them.
2. Browser host removal (D339: "yes it should"). It falls under the 15-minute admin window, like removal through a key; it has no passkey ceremony of its own.
POST /remote/hosts/remove/start {host_id, uninstall?}is the dry run. It requires the admin window, changes nothing, and returns{review, review_expires_in:120, action:{id,label,host_id,host,uninstall}}.POST /remote/hosts/remove/finish {host_id, review}removes the host. The review is single use, bound to the session, owner, host and uninstall choice, and lasts 120 s. The removal transaction rechecks session, owner, host and the admin window by both clocks. A foreign host id is refused without spending the review.remote_host_removalsis unchanged.remove-host --finishnow reads{session, review}on stdin, no longer{session, ceremony, proof}.