Skip to content

Consolidate Workshop Intern against current backend contract - #116

Merged
JoshuaPurtell merged 30 commits into
claude/workshop-v011-rc1-20260911from
codex/workshop-intern-contract-consolidated-20260914
Sep 14, 2026
Merged

JoshuaPurtell merged 30 commits into
claude/workshop-v011-rc1-20260911from
codex/workshop-intern-contract-consolidated-20260914

Conversation

@JoshuaPurtell

Copy link
Copy Markdown
Contributor

Summary

  • advances the internal Workshop product line through the latest packaged diagnostics, mailbox, optimizer, visual, and Intern work
  • pins the Workshop research client to backend main c852f2e79accf8bc477516b38c77b5d080757131 (242 OpenAPI operations)
  • removes client methods for nine routes no longer present in the canonical backend
  • ports the reviewed Stop notification fence from public main without importing the structurally reduced public-export tree
  • adds regression assertions that Stop excludes late output/completion and preserves interrupted usage

Validation

  • python3 scripts/check-research-contract.py --backend ../backend-index-intern-consolidated (242 operations; exact bytes)
  • cargo test --locked --manifest-path crates/synth-api-client/Cargo.toml (16 passed)
  • completed_envelope_with_a_failed_turn_is_normalized_to_failed (1 passed)
  • interrupt_terminates_non_cooperative_tool_tree_and_allows_a_new_turn (1 passed)
  • desktop crate compiled successfully; pre-existing warnings remain

Branch topology

The default public main branch is a deliberately reduced public export. A raw merge into the internal desktop line causes structural delete/rename conflicts and is not the release integration path. This PR targets the existing internal v0.11 RC lineage.

JoshuaPurtell and others added 30 commits September 12, 2026 12:42
…d build

The packaged instance log store reported `binary_missing` because only
scripts/build-tier.sh fetched the diagnostics index executable. `npm run
build`, desktop-instance CUA bundles and `tauri dev` instances bundled or
resolved an unstaged services/victoria-logs directory that held only its
.gitignore. The development-checkout fallback could also mask a missing
bundled binary on the machine that built the bundle.

- tauri.package.json beforeBuildCommand runs package:stage-diagnostics, so
  every packaged `tauri build` stages the binary before bundling.
- desktop-instance.sh stages it for CUA bundles (cua-live-build clears
  beforeBuildCommand) and, non-fatally, for dev launches.
- fetch-victorialogs.sh verifies pinned archive SHA-256s (darwin arm64 and
  amd64 v1.52.0), refuses a mismatch, supports --if-missing and records the
  staged version.
- A packaged bundle (.../Contents/MacOS/<exe>) resolves only its own
  Resources, never the compile-time checkout.

Tests: packaged and development resolution layouts, non-executable refusal
and a packaging-config assertion. The packaged .app itself was not built
here (resource limits); the fetch script was run end to end.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DvS6DjNGSe3a3BrHRxhK5K
Qualify the gated scoped-cloud schema, then register it as desktop
migration 69. The DDL is idempotent: a lane that collided on version 69
heals every table through heal_missing_tables. CloudStore::open verifies
the column shape and refuses a same-named table with another shape.
Registration adopts no existing rows. The host runtime stays
QualificationRequired until a store is explicitly installed.

Mailbox persistence, coded against the manderqueue
docs/WORKSHOP_GRANT_CONTRACT.md (sha256 8fc1669a...):
- An explicitly selected existing Local session is bound to one thread as
  the server-derived enrollment principal. Legacy/remote-linked sessions,
  another account's session, rebinding and silent policy changes refuse.
- Server incarnation and grant generation are persisted. A higher
  generation fences queued writes and open deliveries from the older one;
  revocation fences everything.
- Granted-history pages commit atomically with the cursor. An authorized
  skip is recorded in cloud_mq_history_gaps; real gaps, foreign orgs and a
  changed floor refuse.
- Delivery ladder: delivered -> observed -> acting -> answered, declined or
  expired, plus fenced. Native acceptance fences on session, incarnation and
  grant generation. Handler concurrency, rate, causal depth and deadline
  are counted from durable rows.
- Outbox: original key/correlation/causation/parent are stored immutably.
  Explicit sign-out and account switch advance a per-account write fence;
  identity expiry does not. Uncertain sends settle only through matching
  own history, never by resending.
- Grant contract DTO validation, a GrantAuthority trait with a thin HTTP
  adapter, the granted /history reader and SSE wake reader, and the
  participant policy/tool gate.

Tests: migration clean/existing-v68/failed-then-retried/collided-lane,
restart and account isolation, shape refusal, history gaps, native fences,
write fences and history reconciliation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DvS6DjNGSe3a3BrHRxhK5K
…ivery

Compose the registered store with fresh identity verification and grant
credentials:
- connect/resume: enroll the device/session (fresh incarnation), bind the
  selected session and create or re-read exactly its own grant. Uncertain
  or duplicate creates resolve by a GET, never a retried mutation.
- One bounded pass: a validated <=300 s credential (the endpoint must equal
  the enrolled one), granted-history catch-up, outbox flush with the
  original identities, then history reconciliation of uncertain sends. An
  unmatched uncertain send stays explicitly unknown and is never resent.
  Contract §7 denials map to revoked/expired/fenced.
- Delivery only at an idle turn boundary. Heartbeats, notices, answers and
  status requests are handled with no model call. Work requests reach a
  RestrictedExecutor only under the Respond preset, after handler
  admission and the session's existing paid-compute authorization. The
  executor receives only a ToolGate that refuses out-of-policy
  files/artifacts/tools, writes, spend, deploy, invites, access expansion
  and spawned work. No production executor is registered, so requests
  otherwise wait for an operator answer. Replies are correlated
  answer/decline/expiry notices with hop counts.
- A supervisor with SSE wake-then-fetch, backoff and sleep detection. It
  stops on explicit sign-out, cancellation or terminal authority loss.
  Explicit sign-out now also fences the account's queued MQ writes and
  drops cached credentials.
- Eval-driver routes /v1/cloud/mailbox/{activate,connect,resume,pass,
  publish,answer,status,disconnect,signout,sleep} (instance builds only).

Tests: in-process backend grant and MQ HTTP fixture journeys covering
binding refusals, no-model handling, restricted-tool refusal, operator and
correlated replies, expiry, handler bounds, uncertain sends, crash
recovery with original IDs, account-switch/sign-out/revocation fencing,
SSE wake and sign-out cancellation of an in-flight request.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DvS6DjNGSe3a3BrHRxhK5K
- Re-vendor the MQ snapshot to claude/workshop-v011-grants-mq 02db5d4. It is
  the whole-tree `git archive` with per-file SHA-256s, the same method as
  before; archive sha256 1861aea8..., 73 files; check-mq-vendor.py passes.
  mq-core/mq-sdk dependencies are unchanged, so Cargo.lock is untouched.
- Granted-history catch-up now uses MqClient::read_history and the SDK's
  HistoryPage/HistorySkip types. It keeps the no-redirect, 30 s deadline and
  bounded-body protections, plus a page/thread bound check. The SDK still
  has no SSE reader, so the hardened wake reader stays.
- Contract v2 (committed sha256 9a442993...): enrollment `revoked_at`,
  GrantAuthority revoke_enrollment/get_enrollment, and `enrollment_revoked`
  mapped to revoked. sign_out_device_with revokes every enrollment of the
  account (an uncertain revoke is resolved by a GET, never retried), fences
  queued writes and open deliveries, then signs out locally. The eval-driver
  `signout` route uses it.
- Loopback identity origin: http is accepted only in the backend's exact
  APP_ENVIRONMENT=local form (127.0.0.1/localhost/[::1], explicit port, no
  path/query/userinfo), and only when it equals the configured origin. The
  identity document has no environment field, and the backend emits this
  form only in local.

Tests: device sign-out journey (enrollment and grants revoked, supervisor
stopped, queued write fenced, no re-enroll) and local-origin rule cases
mirroring the backend's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DvS6DjNGSe3a3BrHRxhK5K
Accepted Respond-preset work requests can now run through a production
RestrictedExecutor that never uses start_turn_inner or the session's
normal Codex authority. Each request gets its own ephemeral directory and
app-server process. Nothing survives the request.

Enforcement, verified against the local Codex 0.145 (app-server protocol
v2 schema, --strict-config, `features list`, a model-free confined boot):
- A macOS seatbelt (sandbox-exec) around the whole app-server tree. File
  data and listings under /Users and /Volumes are unreadable except the
  Codex install and the ephemeral dir. Writes go only to the ephemeral dir.
  Outbound network reaches only the loopback provider port. Codex compiles
  in a view_image tool with no config switch, so only OS confinement can
  bound its reads.
- A zero-tool Codex: shell, unified exec, apps, plugins, browser and
  computer use, image generation, hooks, sub-agents and related features
  are disabled. Web search is disabled and no MCP servers are configured.
  approval_policy is "never" with the read-only sandbox, and turn/start
  pins sandboxPolicy readOnly with networkAccess false.
- Every approval, tool or elicitation request from the app-server is
  declined and recorded as a refused gate decision.
- Only gate-authorized files are copied into the ephemeral workspace,
  SHA-256 hashed and given to the model as quoted untrusted data. The
  reply carries the input and answer hashes.
- Requests it cannot enforce are declined before running, and before any
  handler budget is consumed (RestrictedExecutor::can_enforce):
  artifacts, tools other than read_allowed_file, non-loopback providers
  and non-macOS hosts. Deadline, concurrency and rate bounds come from
  the handler limits. A loopback provider bills nothing.

Production wiring: host::confined_executor exists only with a native Codex
binary, macOS seatbelt, the local Laguna provider on loopback and a passing
confined boot check. The eval-driver `pass` route attaches it.

Tests (model-free): a scripted app-server inside the real seatbelt proves
that out-of-allowlist reads, listings and writes are denied, that only the
provider port connects (other loopback ports and 1.1.1.1 are denied), that
its command-approval request is declined, that the host environment is not
inherited and that the allowed file arrives hashed. The real codex binary
boots confined with the generated config and reports the restricted
effective configuration. Config/profile content, the refusal of
unenforceable policies and a host journey declining an unenforceable
request without running it are also covered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DvS6DjNGSe3a3BrHRxhK5K
Five renderer commands over the native mailbox:
- cloud_mailbox_connections
- cloud_mailbox_status (connection/grant state, inbox with requests that
  wait for an operator, outbox with unresolved unknown outcomes, gaps)
- cloud_mailbox_answer
- cloud_mailbox_decline
- cloud_mailbox_sign_out (device sign-out via enrollment revoke, then the
  local sign-out; the local sign-out happens even when the backend cannot
  be reached)

Each command checks the host scope first and refuses while it is
QualificationRequired, before reading any config, credential or network.
The views are specta-typed with JS-safe integers and never carry a
credential or grant token. protocol.ts is regenerated and the lockstep
count moves from 332 to 337.

CloudMailboxPanel, mounted in BackendSettings, renders nothing while the
host is gated. Otherwise it shows the connection and grant state,
answer/decline for requests awaiting an operator, the outbox with an
explicit "outcome unknown - not resent" state, and device sign-out.

Tests: one Rust test per command against the in-process fixture journeys,
plus a gated-host test that all five refuse. The specta lockstep test
passes. Frontend typecheck (tsc --noEmit) reports 0 errors; the worktree
borrows the same-base rc1 install through gitignored symlinks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DvS6DjNGSe3a3BrHRxhK5K
… ready

Item 4 of the follow-up asks for a packaged `.app` check. It was not
feasible within the disk/resource limits, for the reasons in the report.
This tests the resolution fix directly instead. With the pinned
VictoriaLogs binary staged by fetch-victorialogs.sh (the new packaging
hook), an unpackaged build resolves the checkout binary. The real
sidecar then starts on loopback and reports `ready` with a URL, not
`binary_missing`, and stops cleanly. The test skips when nothing is
staged.

A shared lock now serializes the tests that set the process-wide
SYNTH_VICTORIALOGS_BIN override, so they cannot race.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DvS6DjNGSe3a3BrHRxhK5K
@JoshuaPurtell
JoshuaPurtell merged commit d08a6c4 into claude/workshop-v011-rc1-20260911 Sep 14, 2026
1 check passed
@JoshuaPurtell
JoshuaPurtell deleted the codex/workshop-intern-contract-consolidated-20260914 branch September 14, 2026 07:06
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