Skip to content

feat(peer): request destination secrets and create signed webhooks without a destination agent #385

Open

Description

@lukemaj

What: PR #583 preserves privately saved destination-owned webhook secrets through timeout, transient validation and cleanup recovery.
Why: Losing cancellation and destructive/parked recovery could discard or retain protected copies incorrectly on idle B.
So what: Parent freezes this exact component for both fresh whole reviews, completes CI/client/cross-machine acceptance and controls integration composition and all merges.

Parent

#379

Outcome

An agent active only on A obtains human entry for a B-owned secret and creates one signed webhook on idle B without exposing the value or raw secretRef. This contributes to every machine and Drafter in one workspace and Promachos directing work across environments.

Elon record

Requirements and who asked: User-requested native cross-machine tool/lifecycle parity, specified and independently approved in #379.
Deleted: Parallel orchestration systems, privilege bypasses, schema-only completion claims and unrelated delivery work.
Bottleneck: This slice's application-service ownership and its stated predecessor contracts.
Checked myself: Exact frozen de50 owners and callers, 126 final-head focused tests, five scoped compilers, private client ingress, protected-store/SQL interruption recovery, exact retirement ACK, signed idle-B delivery and fresh final-head CI Lint.

Acceptance criteria

  • Negotiate dedicated remote-secret routing; request binds A actor/run, B project, stable key, operation family and current authority via the link-scoped concurrent control channel.
  • Source-only client sees metadata and submits through authenticated private relay; B stores the value and returns only a non-bearer environment-qualified request handle. Old raw-ref handler is never generically forwarded.
  • Consumer variants resolve/reserve in B under actor/project/operation checks; protected pending/saved/reserved(command)/consumed state preserves one-use semantics and crash recovery.
  • Without a durable consumer receipt, recovery revalidates source liveness/authority before a new commit; a durable receipt is not another use. Duplicate/rival commands cannot consume twice.
  • Source exit, revocation, expired/stale UI and interrupted save/commit retire or reconcile exact owned material without secret values/references in parent projections, logs, transcripts or results.

Non-goals

Public release/deployment, account/link capability changes without explicit consent, physical-device use, unrelated source cleanup, package version bumps, live user data testing, and declaring the whole parent complete from this component.

Blocked by

#369, #384

Required proof

Integrated A-only active agent -> human submit through A -> signed webhook persisted on B with no B agent. Inspect sanitized receipts and protected-store lifecycle across save/reserve/commit crashes, duplicate consumption, stale run, wrong project and grant generation; no raw secret material in retained evidence. Include focused behavioral checks, sanitized identity-bound evidence and independent exact-candidate review. CI owns repository-wide checks.

Current component handoff: PR #583, R1 repair

Binding Value
Head 28338d0f9f355657cf0fa2d66a909e0b986cac90
Tree 27c9b2ef83c554b305787083b2e5cdc7a1ef1907
Frozen source base de50f47a47a87310649e058da0467badc1d48938
Base tree a32295120382ba5d8a8c2f3e052476ad2b07cae9
Checkpoint checkpoint/583-before-r1-cbd72 = cbd72f9cdb8066e0c4c6e73b41a031b681c5cde6
Full diff SHA-256, de50..HEAD ba590c658fd898df3e215a3de2b5d149a05ed6d9571724cc809d6737626ae7c6
Incremental diff SHA-256, cbd72..HEAD 969a2d0d686cc911c0e0c620df9e353852dcfbd22c6f26266aa2d46310ddff53
Final proof index SHA-256 827262e63e8233cccbe230c56c3c6ce23b0e77aaf9a03f5cd4ca4e2566039c88

Owners, callers and state

RemoteSecretRelay owns A negotiation/create/private authenticated answer and the durable exact request/run/generation retirement obligation before the card. RemoteSecretRequests owns B immutable pending -> saving -> saved -> reserved -> consumed state, definitive retirement, protected publication, cleanup and receipt-only recovery. PeerAuthority prepare/accept/checkInteractiveRecord/request-actor/control-mailbox remain authoritative; no synthetic B thread, ambient grant or generic raw-ref/path tunnel exists. The dedicated version/family/consumer route negotiates before prompting and B refuses generic peer request_secret.

Callers preserve the complete canonical nonsecret intent, stable key, original run/project/family/generation, verified human client and consumer identity. Schedule, automation acceptance, consumed CAS and sanitized receipt remain one OUTERMOST SQL transaction; protected bytes are read without deletion there and redundant copy removal follows commit. Confirmed authenticated receipt recovery precedes fresh source authorization and cannot execute absent work. Terminal row/attempt/file ownership remains durable when cleanup fails; the in-memory retry decision merely schedules another scan and startup reconstructs it from persisted ownership. Schema/state/authority failures are not converted to cleanup success. Reserved retirement still reconciles its consumer receipt first.

The five repair files are OrchestratorMcpService requestSecret cancellation only, RemoteSecretRequests cleanup/eligible state owner, PeerAuthority TWO cleanup lists, the existing remote-secret native workflow and necessary existing fixture leaves. All 36 whole-component paths remain in candidate-inventory.txt. Whole-component R1 exact retirement, R2 safe identity/actor, R3 immutable save, R4 atomic receipt-first acceptance and R5 privacy/bounded parity remain the approved architecture; this round corrects lifecycle means within it.

Full review frontier and dispositions

Finding Disposition and executed evidence
Sol F1, timeout/save winner Repaired only in requestSecret cancellation: normal timeout rereads the committed card before retirement; a winning save returns its handle and consumes once. Aborted/ended calls still retire even after a winning save because no caller remains. TestClock controls the final pending poll and timeout; native A private WebSocket answer commits before timeout. Ordinary timeout and aborted-call controls pass.
Sol F2 + Opus F6, terminal cleanup/availability The existing loop retries a failed cleanup batch before parking; failed remove also wakes this owner. Only filesystem cleanup leaves are deferred and logged per row, retaining terminal row/attempt/file ownership. Healthy rows continue. Startup schema, SQL transition, decoding and authority errors are outside that boundary. The ORIGINAL running B owner retries after a transient remove fault clears, with no reconstruction, manual drain, new request or unrelated wake. Separate startup reconstruction builds the protected service despite one bad deletion, cleans the other row, denies consumed material through the fresh owner and keeps native B HTTP available. A startup SQL-transition fault still fails construction.
Opus F2, transient secret-origin frames Added secret-origin to BOTH the ask finalizer and startup sweep. Actual success, TestClock timeout, cancellation, abandoned-frame startup and 33 consecutive cancelled asks pass; no unanswered-frame budget is leaked. Startup uses a copy of an actual in-flight frame to model process loss, not an OS kill.
Opus F3, destructive transient validation eligible retires/deletes only capability_denied or local expiry; typed unavailability propagates without changing saved/reserved state or protected bytes. Actual held control replies produce the real B ask timeout and orchestration_error; complete row/consumer/attempt and file SHA remain unchanged, then the same original attempt and consumer identity succeed once. Existing definitive run/grant denial races still retire. No admission result is mocked and SQL corruption is not the transient discriminator.
Opus F1 + Sol proof gap, active hook failure The cbd72 case is strengthened, not inherited as approved. Actual hook SQL failure logs a sanitized warning, delivers the real parent question, leaves the complete obligation unchanged with pending=0/no ACK, denies stale consumption and preserves the protected-file hash. Normal source recovery is reconstructed/started WHILE the SQL fault still exists; its existing retry timer is observed. After fault removal the same automatic recovery loop verifies the exact B ACK without a manual drain. This is direct actual reactor handling plus startup/recovery proof, not proof of live client registration or an installed harness.
Opus F4, global network semaphore Deferred per explicit parent disposition. No lock/concurrency redesign or measurement claim.
Opus F5, history wakes/scans Deferred per explicit parent disposition. No history pruning, new table or performance harness; only necessary failed-cleanup wake/retry behavior changes.
Optional card ID/guidance/span/placement/status observations No edits per parent. The question reactor's original pending/question logic and assertions remain unchanged.
Both old reviews' incomplete full-file coverage No approval inferred. Parent owns fresh Sol MEDIUM and Opus 5.5 MEDIUM whole-candidate reviews of all 36 files, current CI, actual clients/cross-machine acceptance, controlled composition and all merges.

Executed proof and current CI

174/174 focused tests pass, ten full files, no skipped tests, including both unchanged catch-up files 34/34 and all 33 native secret cases. Five direct cache-free scoped compilers and all scoped lint/format/exports/fork/registry/whitespace checks pass. All twelve observations bind exact committed head/tree/base, argv/cwd/start/end/exit, clean before/after state, complete tracked inputs and installed runtime hashes. Only generated Vitest results.json changes; raw final logs contain no fixture values and captured commands have exited.

Final immutable-production red is 10 failed/23 passed; repair green is 33/33 with identical argv/test/fixture bytes and pinned Node. The idle red never reaches deletion completion and hits the unchanged test safety timeout; green awaits the actual removal milestone after driving the production timer. The compiler supervisor's exit 143 is retained separately; three unfinished scopes then passed independently without source changes.

Snapshot 20/10/2026, 16:09:36 Europe/Warsaw, exact head 28338d0: successful Changes, Fork Stack Model, Native fingerprint diff, Label PR size, Collect PR targets, Lint, Label PR 583, Build and Typecheck, Test Server 1, Release Smoke. Pending: Test, Test Web, Test Server 2, Test Server 3, Test Server 4, Test Server 5, Test Server 6, Test Server 7, Test Server 8. Existing preview/Rust/mobile-native jobs are skipped in this workflow. No complete CI or integration gate is claimed. Current CI evidence: Fork Stack Model, Lint, Build and Typecheck, Test.

Limits and next owner

Native proof uses real isolated SQLite, HTTP/OAuth, authenticated private A WebSocket ingress, PeerAuthority and ServerSecretStore. Fixture catalog/provider entries are not an actual installed harness or second machine. Abort is an injected call interruption at the actual service; frame startup is a captured in-flight row restored as an abandoned row; storage/SQL interruption uses deterministic faults and service reconstruction, not an OS process kill. Startup availability proves construction of the protected layer used by the server and B native HTTP availability, not a full production server launch. Transient classification is observed at the actual B validator; the dedicated control transport keeps its existing external error mapping.

B scheduled_tasks.webhook_secret is separately authorized protected storage. Cards, results, receipts, transcripts and named forbidden DB/WAL owners exclude values/raw refs; no false whole-B-database absence claim is made. One-element remote fields map to one webhook value; broader fields remain a #379/#386 catalog obligation. Local fields/files/ref and unknown-ref semantics are unchanged and affected positives pass. No real human secret/account/grant/live userdata, live provider, browser/device/devserver, persistent automation, deployment, global tooling/shared runtime write or version bump occurred.

The source branch stays on exact de50 while integration independently advanced to 02343e5 through parent-owned #582. No fetch/merge/rebase/retarget or source absorption occurred. #386 completion/result/group regions and #531 pending/question ownership are untouched. The component is one open PR, not acceptance or merge clearance. #369/#531, #384/#555 and parent #379/#386 native edges remain open for final delivery.

Current canonical private handoff and both response/review packets: /home/luke/.local/state/chromeria-379-proof/385-implementation/r1-repair/report.md, sol-review-packet.md and opus55-review-packet.md. Exact bindings/raw logs: r1-repair/verification-index.json. All earlier c9/cbd indexes/manifests/logs remain unchanged; 72 retained log/manifest hashes and three initial identities match. The root historical index is intentionally preserved; the current index is under r1-repair. Workspace/branch/checkpoint remain for parent review and eventual parent-owned retirement.

Elon record for this repair

Requirements and who asked: User-approved #379/#385 R1-R5 and parent-accepted Sol F1/F2 plus Opus F1/F2/F3/F6 require private A capture, B one-use ownership, safe cancellation, durable retirement and recoverable acceptance.
Deleted: Optional unmeasured semaphore/history redesign, new tables/frameworks/locks, generic raw refs/paths, synthetic B agents, saved replacement and unrelated topology/version/deployment work.
Bottleneck: Faithful native lifecycle proof where timeouts, unavailable validation and cleanup meet durable destination state.
Checked myself: Both complete reports/addendum, real owner/caller/state paths, immutable-production red versus identical-test green, all 174 final-head cases, five compilers, full scoped static checks, preserved old proof hashes and actual current CI/Git state.

Activity

  1. lukemaj commented on Oct 20, 2026

    @lukemaj
    ContributorAuthor

    What The #385 design report proposes a dedicated negotiated secret channel and a destination-owned one-use consumer.
    Why Current source generically forwards request_secret and deletes local secret material before the schedule transaction commits.
    So what The parent will obtain a bounded design challenge before starting the component implementation; no acceptance or new human-owned choice is declared resolved.

    This is source-read evidence, not an executed leak or crash reproduction. The full designer report follows unchanged. Current integration is now de50f47 on main f3a399d; the report's frozen c8c tree equals the preserved pre-alignment checkpoint. Current-main changes were inspected and leave secret/peer/schedule runtime owners unchanged.

    Parent constraints for the design challenge:

    No source/workspace/GitHub status or native dependency-edge mutation occurred in the design task. #369/#384 internal artifacts are available, but their outcome Issues remain open until final #379 delivery.

    #385 design report: dedicated negotiated remote-secret route

    Read-only design by Claude Opus 5.5 (claude-opus-5-5), MEDIUM, Claude Code harness in T3 Code.
    Date 2026-10-20 (Warsaw). No source, test, Git, GitHub, install, grant, data or secret action was taken.

    0. Identity and inputs

    Item Value How checked
    Frozen source workspace /home/luke/.t3/worktrees/chromeria/fix-379-keyboard-pointer-isolation read-only git rev-parse, git grep, sed/cat
    HEAD c8c3862ba806b59e511a98344fddbc624cf19fd7 git rev-parse HEAD
    Tree 72482028e2ba56de77cc17dd55eb6caaf609c6f8 git rev-parse HEAD^{tree} and cf54166e^{tree}: byte-equal
    Current main f3a399d40c252730b02b7022b627763f7add91b0 (merge-base with integration 68b3177c85) git merge-base, git diff --stat in the main checkout (read-only)
    Upstream main pingdotgg/t3code d36b7da2485bc889b320a285deea181f637d760a (2026-10-20 10:29 UTC) gh api repos/pingdotgg/t3code/commits/main
    Report dir /home/luke/.local/state/chromeria-379-proof/385-cf541-design created by exclusive mkdir (succeeded, no fallback)

    GitHub records read in full (read-only gh): #385 body (no comments exist); #379 body; #379 comments 6219474098 (approved graph), 6289390454 (current parent), 6289219018; #369 canonical repair 6288724706 (PR #531, merged 2026-10-20 10:34 UTC as 28ba9e6); #384 canonical handoff 6282166254 and approval 6282445086 (PR #555, merged 2026-10-20 03:08 UTC as 232f820). Native blocked_by for #385 still lists #369 (open) and #384 (open); left unchanged.

    Procedures loaded in full: operations SKILL, software-design (index + compare-designs, state-and-boundaries, change-existing-systems, typescript), elon-method (index, algorithm, first-principles, current-constraint), research (index, mechanics). Great Library README read in full. Project direction triad and preferences came from the session hook (status ready, canonical AGENTS.md unchanged).

    1. Verified current mechanics (source facts at c8c3862)

    Proof class for this section: source-read, not executed.

    1. Local secret owner. apps/server/src/secrets/SecretRequests.ts (answer, saved, consume) keys a request by (threadId, turnItemId), requires a B-local thread run (agent_stopped otherwise), writes per-field 0600 files plus, for one field, a value under secret-request-<hmac> in ServerSecretStore. consume reads and deletes in one step (one-use), with a global consumeLock. There is no reserved state.
    2. Local tool. OrchestratorMcpService.requestSecret (OrchestratorMcpService.ts:2562-2694) requires latestActiveRun(parent) owned by the calling provider session, records a secret_request turn item (secret_request.record internal command), polls its own projection, closes the card cancelled on timeout/abort/run end, and returns files + optional secretRef. The handler is McpToolAccess.actsAsCaller (toolkits/orchestrator/handlers.ts:119), so it goes through McpRouting.invoke.
    3. Today it is generically forwarded. McpRouting.invokeRouted (McpRouting.ts:248-330) forwards any tool with a remote environmentId through PeerForwarding.call, special-casing only web_*, delegate_task, t3_thread_wait, html_render. request_secret has no special case. With contextThreadId naming an active B thread, McpRouting.withContext binds that thread (providerSessionId: peer:<record>, providerInstanceId from the shell), so B's local requestSecret can create a B card and return B file paths and a raw secretRef to A. PeerToolRouting.test.ts:606-611 asserts exactly this generic route (operation: "secret-request", context: true). This is the "raw secretRef tunneling" feat(peer): request destination secrets and create signed webhooks without a destination agent #385 must remove. (Source-read; the leak path itself was not executed.)
    4. Consumer. ScheduledTaskService.ts:1393-1417 consumes signature.secretRef inside the schedule SQL transaction via secretRequests.consume, which deletes the value from the file store before the transaction commits. mutationReceipts.accept (scheduledTasks/mutationReceipts.ts:265-338) commits a secret-started row outside the transaction and, on any failure afterwards, returns outcome_unknown ("may have been consumed"). So a crash between consume and commit loses the sole value and leaves a permanent unknown. feat(peer): request destination secrets and create signed webhooks without a destination agent #385 requires the opposite: keep the value until the consumer outcome is durable and let the same command recover.
    5. Authority and liveness owner exists. PeerAuthority.prepare (PeerAuthority.ts:1521-1617) saves a source record with localThreadId, originRunId and an envelope whose requestOwner.namespace is the source thread namespace (thread:<A>:<threadId>). B's MCP auth middleware (McpHttpServer.ts:314-340) runs accept, which asks A over the link-scoped control mailbox (ask → validate frame, PeerAuthority.ts:682-735, drained by A's dialer stream). A's validate denies when thread.activeRunId !== record.originRunId for non-read records (PeerAuthority.ts:1233-1241), when the link generation changed/expired, or when the session is revoked. checkInteractiveRecord(id) re-runs this for a stored ingress record in:<session>:<recordId> (PeerAuthority.ts:3460), re-asking A. request-actor returns {type:"client"} for records prepared with PendingClientSession after requireClient checks an active session with orchestration:operate (PeerAuthority.ts:1937-1968, 2858-2868).
    6. Dedicated typed peer routes have a precedent. /mcp/peer-control/{tasks,pending-requests,requests,attachment}: GET advertises {version:1} or 409; POST decodes a typed body; the middleware supplies the authenticated, envelope-verified scope (McpHttpServer.ts:1580-1760). PeerMcpClient.lifecycleRequest/requireRequestProtocol show the client side, including ensureChannel before a control-dependent operation (PeerMcpClient.ts:827-900, 982-1020).
    7. Human ingress on A. secrets.answerRequest RPC → SecretRequests.answer(input) (ws.ts:2163), guarded by AuthOrchestrationOperateScope in RpcAuthorization.ts:128; the handler has currentSessionId in scope (ws.ts:1215) but does not pass it today.
    8. Digests are unsalted. requestDigest = sha256 of canonical input (McpRequestReceipts.ts:37); envelopes, mcp_request_receipts.input_digest and peer_authority_records keep such digests durably. A secret value must therefore never be part of any digested argument set (an unsalted hash of a low-entropy password is an offline-guessable copy).
    9. Remote schedule creation authority. Linked schedule_task on B passes assertNotLinked → checkTarget(scope,{type:"environment"}) plus full-access/default modes (linkOrigin.ts:103-132), then captureAutomation (feat(peer): run destination schedules webhooks and Spectrum while the creator is offline #384). This is the authority any B project secret request must also meet, so a human is never prompted for a secret that can never be consumed.

    2. Requirement (Elon) and decision

    Requirements and who asked: User-approved #379 v5 spec and #385 acceptance: A-only active agent, idle B, human entry only through A's client, B-owned value, non-bearer destination-qualified handle, reserve/one-use/crash recovery, retirement on exit/revoke/stale, no raw ref/value in projections/logs/results, no generic forwarding, old-peer refusal before prompting.
    Deleted: synthetic B thread/run; generic forwarding of request_secret (and its current routing-test contract); browser-vault/profile borrowing; a value copy on A; multi-field remote files (no remote consumer can read B paths from A); a source-run watcher on B (existing TTL sweep plus live revalidation at every use cover exit); a distributed transaction.
    Bottleneck: one destination owner for the protected state machine, wired to the already-existing link-scoped validation and the existing schedule receipt; not transport or UI.
    Checked myself: sections 1 and 7 of this report, by reading the cited source lines and live GitHub/upstream records.

    Compared shapes

    Candidate Verdict Deciding reason
    A. Generic forward with B context thread (current) Rejected Returns raw secretRef/B paths to A; needs a live B run; explicitly forbidden.
    B. Keep value on A, send it with schedule_task Rejected Value travels in forwarded tool args, lands in unsalted durable digests on both sides, B does not "store the value", no reservation owner.
    C. Reuse local SecretRequests with a synthetic B thread Rejected Spec forbids synthetic destination runs; local consume deletes before commit, so crash recovery is still wrong.
    D. Hidden internal MCP tool for relay Rejected It is a generic tool/RPC name reachable through forwarding; precedent is dedicated /mcp/peer-control/* routes.
    E. New destination owner RemoteSecretRequests + dedicated /mcp/peer-control/secrets route + one consumer variant Chosen Smallest shape that meets every criterion; reuses prepare/accept/checkInteractiveRecord/request-actor, ServerSecretStore, the existing card/RPC and mutationReceipts. Strongest reason to lose: adds one table and one route; accepted because no existing owner has a reserved state.

    Load-bearing assumptions (each has a falsifying test in section 6): A's validate still accepts a stored thread record after its tool call returned while the run stays active; B's ask can complete while A's request_secret HTTP call is still open (dialer channel concurrent); SQLite holds the consumer row, receipt and secret-row CAS in one transaction.

    3. Representative caller code (before internals)

    Common path, agent on A:

    const answer = await request_secret({
      environmentId: B, projectId: "proj-b",          // projectId required when environmentId is linked
      label: "GitHub webhook secret",
      reason: "Signs deliveries for the release webhook on B.",
      clientRequestId: "gh-hook-secret-1",
    });
    // { status: "saved", secretRequest: { environmentId: B, requestId: "rsr_<32 hex>" } }
    // no files, no secretRef
    
    const receipt = await schedule_task({
      environmentId: B, projectId: "proj-b", prompt: "...",
      schedule: { type: "webhook", signature: {
        header: "x-hub-signature-256", encoding: "hex", prefix: "sha256=",
        secretRequest: answer.secretRequest,           // new variant, exclusive with secret/secretRef
      } },
      clientRequestId: "gh-hook-task-1",
    });
    // { type: "confirmed-operation", applied: true, scheduledTaskId, ... }

    Failure paths: B without the route → invalid_request "The destination does not support remote secret requests v1; no card was shown." Narrow link (not environment-resources/full-access) → capability_denied before the card. Run ends before the human answers → {status:"cancelled"}, card retired. Run ended, revoked or relinked before schedule_task → capability_denied "The run that requested this secret is no longer authorized; ask again", B retires the material. Wrong project, another A thread, update_scheduled_task on a different family → capability_denied, no state change.

    Awkward paths: lost schedule_task reply → same clientRequestId returns the same confirmed receipt, no second webhook or use. Crash after reservation → same key revalidates source liveness/authority then commits once. A second key with the same handle → capability_denied "This secret is reserved by another operation." Old A generically forwarding request_secret to new B → refused by B before a card.

    Human path on A's client: the card renders in A's own thread (label, reason, destination environment label and project); submit uses the unchanged secrets.answerRequest RPC; A's server relays the value privately to B. Old clients still work (they ignore the new optional destination field, and answer through the same RPC).

    4. Concrete design

    4.1 Contracts (packages/contracts)

    • New fork file remoteSecret.ts:
      • RemoteSecretRequestId (branded, rsr_ + 32 hex, B-generated random).
      • RemoteSecretHandle = { environmentId: EnvironmentId, requestId: RemoteSecretRequestId } (non-bearer).
      • RemoteSecretFamily = Literal("schedule.webhook-signature") (the only family now).
      • RemoteSecretControl request union for the route body: create {projectId, label, reason, placeholder?, key, family}, answer {requestId, answer: {type:"save"} | {type:"decline"}, secret?}, cancel {requestId}. The secret value is a separate body field excluded from every digest.
      • Route support { version: 1, families: ["schedule.webhook-signature"] }.
    • orchestratorMcp.ts: OrchestratorMcpRequestSecretInput gains projectId?: ProjectId (required by the service when the destination is linked; forbidden locally unless it equals the thread project); OrchestratorMcpRequestSecretResult gains { status: "saved", secretRequest: RemoteSecretHandle } as its own union member (no files, no secretRef).
    • scheduledTask.ts: webhook signature.secretRequest?: RemoteSecretHandle, mutually exclusive with secret and secretRef (schema filter).
    • orchestrationV2.ts: secret_request turn item and secret_request.record gain optional destination: { environmentId, environmentLabel, projectId, requestId } (ids/labels only). Excess-property-tolerant old clients ignore it.

    4.2 Destination owner (B): apps/server/src/secrets/RemoteSecretRequests.ts (new fork module)

    SQLite table remote_secret_requests (created like scheduled_mutation_receipts):
    request_id PK, source_environment_id, source_generation, source_namespace, source_record_id, key_digest, project_id, family, label, status, consumer_request_id, created_at, saved_at, expires_at, UNIQUE(source_environment_id, source_generation, source_namespace, key_digest), and CHECK((status IN ('reserved','consumed')) = (consumer_request_id IS NOT NULL)). Value only in ServerSecretStore under secret-remote-<request_id> (distinct prefix: a rolled-back old binary cannot map a handle into its secret-ref: consumer).

    State machine (TypeScript discriminated union mirrors it; only reserved/consumed carry consumerRequestId):

    pending --answer save--> saved            (value set first, then row; crash between = orphan value, swept)
    pending --answer decline--> declined
    saved   --answer save (retry, unreserved)--> saved (value replaced)
    pending|saved --cancel from source--> cancelled
    saved   --reserve(cmd)--> reserved(cmd)   (own committed write, after live revalidation)
    reserved(cmd) --reserve(cmd) retry--> reserved(cmd) (revalidate again)
    reserved(cmd) --consumer txn CAS--> consumed(cmd)   (same SQLite txn as schedule row + confirmed receipt)
    pending|saved|reserved --TTL--> expired
    pending|saved|reserved --source liveness/revocation denial at answer or reserve--> retired
    terminal (consumed/declined/cancelled/expired/retired): value deleted after commit; hourly sweep retries
    

    Methods (each checks here === handle.environmentId):

    • create(scope, input): requires scope.authority from the envelope (accept already validated A's record and run over the control channel), checkPayload(authority, "remote_secret.create", input), assertNotLinked-equivalent environment authority (same predicate as linked schedule creation), project exists and not deleted, single value only. Idempotent insert on the unique key; returns the existing request_id for the same key. Stores source_record_id = scope.authority.id (in:<session>:<recordId>), source_namespace = requestOwner.namespace, source_generation = authority.peer.generation.
    • answer(scope, input, secret?): envelope must come from the same source environment and generation; request-actor must return {type:"client"} (human-only route); checkInteractiveRecord(source_record_id) (originating run still active, grant current) else retire; status pending or unreserved saved; store.set then UPDATE … SET status='saved'. Decline deletes any value.
    • cancel(scope, input): same source environment/generation/namespace; pending|saved → cancelled, value removed. Idempotent.
    • reserve({ scope, handle, projectId, family, consumerRequestId }): actor requestNamespace(scope) === source_namespace, generation equal, project equal, family equal; status saved or reserved by the same id; not expired; checkInteractiveRecord(source_record_id) (live, no cache) else retire + delete; CAS to reserved.
    • take({ handle, consumerRequestId }) inside the consumer transaction: read value (no delete) and UPDATE … SET status='consumed' WHERE request_id=? AND status='reserved' AND consumer_request_id=? AND expires_at>?; zero rows aborts the transaction.
    • finalize(handle): post-commit value delete; failure logged and left to the sweep.
    • Sweep: extend the existing hourly SecretRequests sweep (or a sibling in this module) to expire rows past TTL (24 h, SECRET_REF_TTL_MS) and delete secret-remote-* values whose row is terminal or missing.

    4.3 Route

    McpHttpServer layerPeerControl: GET /mcp/peer-control/secrets → {version:1,families:[…]} (409 without the owner); POST /mcp/peer-control/secrets → decode RemoteSecretControl, call one owner method, map errors; never log the body; span attributes ids only.

    4.4 Source side (A)

    • PeerMcpClient: requireRemoteSecrets(environmentId, generation) (GET) and remoteSecret({ environmentId, generation, authority, body }) (POST with AUTHORITY_HEADER), calling ensureChannel first so B's validate is answered while the POST waits.
    • McpRouting.invokeRouted: for request_secret with a remote environmentId, call the local handler with the destination (like delegate_task), never PeerForwarding.call. PeerForwarding.forward additionally refuses toolName === "request_secret" (defence for any other path).
    • OrchestratorMcpService.requestSecret, remote branch: existing active-run check on A; reject fields; negotiate; authority.prepare(scope, B, generation, "remote_secret.create", {projectId,label,reason,placeholder,key,family}, limits, undefined, false, true); POST create; record the A card pending with destination; unchanged wait loop on A's own projection; closeCard also POSTs best-effort cancel; on saved return {status:"saved", secretRequest}. Destination B's same method refuses scope.client?.linked === true (old A generically forwarding).
    • SecretRequests.answer(input, { clientSessionId }) (ws passes currentSessionId): when the card has destination, keep the existing pending/run checks and the global answer lock, prepare a client-actor envelope under PendingClientSession with args {requestId, answerType} (no value), POST answer with the value as a separate field, then record saved|declined on A. The post-record "card closed meanwhile" branch POSTs cancel. No value is written on A.
    • PeerForwarding.forward: when schedule_task/update_scheduled_task carries signature.secretRequest, require requireRemoteSecrets and handle.environmentId === destination before forwarding (old B refused before effects).

    4.5 Consumer (B)

    • OrchestratorMcpService.scheduleTask/updateScheduledTask: CurrentMutation gets secret: { type:"local-ref" } | { type:"remote", handle } instead of the boolean consumesSecret (named variant; the boolean currently cannot say which recovery applies). Remote requires a keyed request (durable identity); without clientRequestId → invalid_request.
    • mutationReceipts.accept, remote variant: RemoteSecretRequests.reserve first; then the existing secret-started insert; an existing secret-started row for the same request with the reservation held proceeds (after mutation.recheck) instead of returning outcome_unknown; confirmed replays; transaction failure after reservation returns a specific retryable failure (value still reserved), not outcome_unknown. Local secretRef keeps today's semantics.
    • ScheduledTaskService webhook block: signature.secretRequest → remoteSecrets.take(...) inside the transaction; after commit (Effect.tap beside notifyAutomationChanges) finalize.

    4.6 Guidance, docs, registry

    Update request_secret and schedule_task descriptions (toolkits/orchestrator/tools.ts), provider-core/.../orchestrationInstructions.ts:18, the docs/fork-features.md entry and scripts/fork-upstream-edits.txt for touched upstream files; one sentence in the user secrets/webhook section if it describes linked environments. Web MessagesTimeline secret card and mobile SecretRequestCard show the destination via one packages/client-runtime/src/secretRequest.ts helper.

    5. Acceptance-to-owner map

    #385 criterion Owner (file / method) Proof
    Negotiated dedicated route; binds A actor/run, B project, stable key, family, current authority via link-scoped channel PeerMcpClient.requireRemoteSecrets/remoteSecret; McpRouting.invokeRouted; OrchestratorMcpService.requestSecret remote branch; PeerAuthority.prepare/accept (validate frame); RemoteSecretRequests.create native two-server workflow: create with no B thread; old-B 409 → no A card; old-A generic forward → B refusal; narrow link refusal; direct call completes validation while its request is open
    Source-only client sees metadata, submits via authenticated private relay; B stores value; non-bearer handle; old raw-ref handler never generically forwarded SecretRequests.answer remote branch; ws secretsAnswerRequest; RemoteSecretRequests.answer; request-actor client check; PeerForwarding.forward refusal; replaced PeerToolRouting row answer through A RPC only; agent-origin answer envelope refused; A projection/result contain only ids; sentinel-absence scan
    Consumer resolve/reserve under actor/project/operation checks; pending/saved/reserved(command)/consumed; one-use; crash recovery RemoteSecretRequests.reserve/take/finalize; mutationReceipts.accept remote variant; ScheduledTaskService webhook block wrong project, other thread, wrong family, rival key, duplicate concurrent same key; fault injection at save/status, reserve/commit, commit/finalize
    No durable receipt → revalidate source liveness/authority before new commit; durable receipt is not another use; no double consume reserve calls checkInteractiveRecord; accept replays confirmed crash after reserve then retry with run ended (denied, retired) and with run active (one commit); retry after confirmed (same receipt, value already gone, no second webhook)
    Source exit, revocation, expiry/stale UI, interrupted save/commit retire or reconcile exact material; no values/refs in projections, logs, transcripts, results closeCard cancel; answer/reserve retirement on denial; sweep; route without body logging run exit before answer; revoke/relink generation after save; TTL; stale card answer; scan both SQLite files, logs and tool results for the sentinel
    #379 integrated case (A-only agent → human on A → signed webhook on idle B) all of the above one integrated native test with real PeerLinks OAuth pairing (existing PeerAuthority.workflow.fixture.ts pattern), real SQLite, then a signed delivery to B's webhook path starts one run (reuse #384 schedule delivery seam)

    6. Proof plan and proof classes

    Distinct classes, never merged: (1) source-read (this report); (2) focused native behavior (vitest, two in-process servers, real migrations/SQLite/PeerAuthority/PeerMcpClient, deterministic external timing only, no sleeps); (3) package compilers + scoped lint/fmt; (4) CI (owns repo-wide checks); (5) independent exact-candidate review; (6) real client/cross-machine acceptance (parent/#386, separately authorized). Fixtures use a sanitized sentinel such as RSR-SENTINEL-<random per test>; no real credential, live userdata or existing grant.

    Suggested test homes: new apps/server/src/secrets/RemoteSecretRequests.test.ts (state machine over SQLite and a real ServerSecretStore temp dir), new apps/server/src/peer/PeerAuthority.remoteSecrets.workflow.test.ts (two-server cases above), updated PeerToolRouting.test.ts row (generic forward refused), existing SecretRequests.test.ts and OrchestratorMcpToolkit.integration.test.ts local cases unchanged and rerun, mutationReceipts local-ref outcome_unknown cases unchanged. The new workflow file needs its own CI shard weight (the #384 handoff shows these files run 40-200 s locally).

    7. Finite implementation sequence (one writer, one PR to feat/379-cross-machine-parity)

    1. Contracts (4.1) plus the B-side refusal of linked request_secret and the PeerForwarding refusal; replace the routing-test row. Observable: old generic path closed.
    2. RemoteSecretRequests owner + sweep + unit state-machine tests.
    3. Route + PeerMcpClient negotiation/POST + requestSecret remote branch + answer relay + ws session id. Observable: card on A, value on B, handle result.
    4. Consumer variant in mutationReceipts/ScheduledTaskService/OrchestratorMcpService; negotiation in PeerForwarding for handles.
    5. Integrated workflow test, fault injection, sentinel scans.
    6. Guidance/docs/registry and the two client card labels.

    Required review after implementation: fresh whole reviews by GPT-6.1 Sol MEDIUM (codex) and Claude Opus 5.5 MEDIUM (claudeAgent) on the exact candidate head/base, each given this report, the #379/#385 records and all prior findings; neither approval transfers to a changed head.

    8. Main delta impact (f3a399d vs integration)

    68b3177..f3a399d (#565, #571, #574) changes Prism roles/effort: prism.ts, PrismService.ts, preview/tools.ts, PrismSettings.tsx, forkToolInstructions.ts, provider-core orchestrationInstructions.ts, the delegate_task description in toolkits/orchestrator/tools.ts, docs and registry. No change to secret, peer, schedule, routing or receipt owners. Textual overlap risk only: toolkits/orchestrator/tools.ts, orchestrationInstructions.ts, docs/fork-features.md, scripts/fork-upstream-edits.txt (all edited by step 6). Recommend the parent aligns main before step 6, or step 6 rebases those four files.

    9. Upstream-first record

    Queries (2026-10-20): gh search prs|issues --repo pingdotgg/t3code for "secret request", "request_secret", "secretRef", "remote secret", "secret peer"; gh api …/commits?path=apps/server/src/secrets/SecretRequests.ts&sha=main; …?path=apps/server/src/peer&sha=main.
    Results: upstream origin of the local feature is merged #15907 (2026-10-05); later upstream touches are refactors (#16232, #16266, #16360, #19559, #21996 tracing on 2026-10-20). No upstream remote/peer secret request, reservation or webhook-handle work exists; upstream main has no apps/server/src/peer path. Provenance change: upstream #16684 ("agents use linked environments through any T3 tool"), listed as open in #379, is now CLOSED unmerged (2026-10-19 12:37 UTC); #17129 (agent sets up a link) is open and unrelated to secrets. No port selected. Rebase note: upstream #21996 edits SecretRequests.ts tracing; keep the fork hook there to one small branch.

    10. Unverified questions (with what resolves each)

    1. A's validate of the stored request_secret record after that tool call returned but while its run is still active: source says it passes (checks run, not call). Resolve with the integrated test step 5.
    2. ensureChannel is sufficient for a dedicated POST with no earlier tool call on a cold link: resolve with a cold-start case.
    3. No HTTP/OTel/request logging of /mcp/peer-control/* bodies anywhere in the stack: resolve with the sentinel log scan.
    4. Whether today's local keyed request_secret result (with secretRef) is persisted in mcp_request_receipts.result_json: pre-existing local behavior, outside feat(peer): request destination secrets and create signed webhooks without a destination agent #385; resolve by reading McpRequestReceipts record paths or a local test. If true, file separately.
    5. Old-B tool-parameter decoding of an unknown signature.secretRequest (strip vs reject): moot behind negotiation; confirm in the old-peer case.
    6. ServerSecretStore.set atomic replace for a re-save after a lost relay reply (fix(server): a racing secret creator never reads a half-written secret #261 covers create racing): confirm in the save/status fault case.

    No unresolved user-owned semantic decision found. Reversible means chosen here: remote requests are single-value; the handle is valid only while the requesting run is active (spec text); the human-answer path keeps A's global answer lock across the bounded relay call.

    11. Negative results and the checks behind them

    • "No existing remote-secret route or reserved state": git grep -i secret across apps/server/src/peer, McpRouting.ts, McpToolAccess.ts, McpInvocationContext.ts (only fixture/link-token hits); git grep secretRef|request_secret|SecretRequests across server, contracts, web, mobile, client-runtime; full read of SecretRequests.ts, secretRequest.ts, mutationReceipts.ts.
    • "No upstream analogue": section 9 queries and commit-path listings.
    • "Main delta does not touch feat(peer): request destination secrets and create signed webhooks without a destination agent #385 owners": git diff --stat and --name-only filtered for secret/peer/schedule/routing/ws/rpc.
    • Tool docs/help/config checked: request_secret and schedule_task tool descriptions (toolkits/orchestrator/tools.ts:126,175-179), agent guidance (orchestrationInstructions.ts:18), docs/fork-features.md:2989, RPC guard table (clientRpcPermissions.ts has no secrets entry; server guard is RpcAuthorization.ts:128). No environment variable or config flag governs secret requests: grep -i secret apps/server/src/config.ts shows only secretsDir (stateDir/secrets, the ServerSecretStore location) and an unrelated desktop bootstrap secret; grep secretRequest|secret_request|requestSecret over serverSettings.ts and contracts settings found nothing.
    • Not attempted by instruction: running tests, servers, browsers, devices, providers, GitHub writes.
  2. lukemaj commented on Oct 20, 2026

    @lukemaj
    ContributorAuthor

    What The parent accepts the dedicated #385 architecture with the five technical amendments in this independent design review.
    Why Retirement, stable intent, save fencing and receipt-first recovery need explicit ownership to satisfy the already approved one-use contract.
    So what One exclusive component writer will implement the amended contract and prove its native cases before fresh dual whole-candidate review.

    Design review: GPT-6.1 Sol, MEDIUM, Codex; verdict REQUEST_CHANGES on the original source-only proposal. Parent disposition: R1-R5 accepted as mandatory implementation constraints, not waived. User decisions remain the approved379 spec and385 acceptance; no additional human-owned semantic choice was found.

    Selected simplification: delete the proposal's unnecessary saved-to-saved value replacement. One request accepts one immutable save attempt; a lost response reconciles that attempt rather than replacing saved/reserved bytes. A different value requires a new request. The B owner must persist attempt/fence identity and reconcile partial filesystem publication with SQL; stale cleanup touches only its own material. No parent directive chooses extra tables or cleanup frameworks beyond what this requires.

    All other R1-R5 constraints below are load-bearing: durable exact retirement obligations and ACK/recovery hooks, canonical non-secret input/origin-run binding, human actor derived from authenticated client session, accepted-receipt-only recovery before live source checks, same-command no-receipt live revalidation, consumed CAS/task/automation receipt in the same outer transaction, cleanup after its commit, old-peer/generic-handler fail-before-prompt, and correctly scoped sentinel oracles. Single-value webhook capture is385 scope, with multi-field request_secret parity retained as a386 obligation. A one-field fields input must have explicit equivalent mapping.

    Dependency readiness is verified by internal reviewed artifacts: #369 PR531 publishedff8eb336caf233086e250ddb0a2ef259724474d4 was reviewed/CI-green and merged28ba9e6b83, retained in currentde50 as0cd4de4db9. #384 PR555 reviewed694f80d0783d18847c50337d233bd8b537c8d16a and internal merge44812e9368, retained as750224f15e. Relevant runtime changes remain patch-equivalent through current main alignment. Their outcome Issues remain open; this does not transfer final acceptance or close native parent dependencies.

    The independent full report follows unchanged. All its proposed native falsifiers become implementation proof obligations; none was executed by the reviewer.

    #385 bounded design challenge

    Verdict: REQUEST_CHANGES. Keep the proposed dedicated negotiated route, B-owned request state and explicit webhook consumer variant. Amend the five correctness contracts below before implementation; these are bounded technical corrections, not a competing architecture or a request for another proposal panel.

    Reviewer: GPT-6.1 Sol, MEDIUM, Codex harness. Date: 2026-10-20, Europe/Warsaw. This is independent source/design review only. No #385 implementation exists in the inspected tree. No implementation acceptance, runtime proof, dependency clearance, merge or rollout is claimed.

    Candidate and input ledger

    Review method and actual reads

    Read the canonical /home/luke/dev/toolboxmd/agentsmd/skills/operations/SKILL.md, verification, code-review index, review-method, impact-analysis, software-design index, state-and-boundaries, change-existing-systems, compare-designs, test-design, Elon index/algorithm/current-constraint/first-principles; read the canonical README setup/harness boundary and /home/luke/dev/promachos/library/README.md. Root source GLOSSARY.md read; no GLOSSARY-MAP.md exists. No new project concept is proposed.

    Source reads at de50, full where stated and otherwise the relevant complete methods/blocks:

    • Full secrets/SecretRequests.ts, auth/ServerSecretStore.ts, scheduledTasks/mutationReceipts.ts, peer/PeerAuthorityRecord.ts, mcp/requestIdentity.ts, contracts secretRequest.ts.
    • peer/PeerAuthority.ts: record persistence/readInteractive (466-490), grant/enroll and concurrent ask/exchange (including transient frame cleanup, 600-740), validate source/client/run and resource checks (1100-1280), prepare (1521-1617), accept/checkPayload (1797-1849), actorForRecord/requireClient (1937-1968), captureAutomation (2386 onward), request-actor/validate frame handling (2858 onward), recheckInvocation (3280 onward), checkInteractiveRecord (3460), historicalScheduleReceipt (3554-3615), setGrant (3640 onward).
    • peer/PeerMcpClient.ts: ensureChannel streaming/polling/concurrent frame processing (345 onward), disable batch/ACK/recovery (508 onward), normal call/automation negotiation (640 onward), lifecycleRequest and requireRequestProtocol (827 onward, 982 onward).
    • peer/PeerLinks.ts: generation replacement, retireAutomationLink, unlink, retained original recovery route; full PeerLinkEvents.ts; persistence AuthSessions.ts replacement/revoke invalidation hooks. peer/peerEffectAuthority.ts was searched and its authority-record ownership blocks inspected; it is not a secret request lifetime owner.
    • mcp/McpHttpServer.ts: authentication/accept before handler (280-345), dedicated peer routes (1578-1768). McpRouting.ts: withContext, invokeRouted and invoke (90-415). PeerForwarding.ts: forward, scoped identity, prepare/call and negotiation paths. PeerToolRouting.test.ts existing request_secret forwarding row (606-611).
    • mcp/OrchestratorMcpService.ts: scheduleRequest/replay/recheck, scheduleTask, requestSecret (2230-2420, 2562-2694), update mutation context locations. scheduledTasks/ScheduledTaskService.ts: protected webhook row/write/read fields, upsert preflight/capture, nested transaction, secret consume and outer acceptance completion (1240-1500). scheduledTaskChecks/ScheduledTaskChecks.ts: upsert and definition extension wrappers (647 onward, 780-900); engine removeTask acceptance wrapper (1033 onward).
    • mcp/McpSessionRegistry.ts: credential liveness/pruning/currentScope/issue/resolve/revocation (full central implementation); contracts orchestratorMcp.ts request_secret input/result (808-864); orchestrator handlers request_secret; ws.ts secretsAnswerRequest and auth/RpcAuthorization.ts scope table.
    • PeerAuthority.workflow.test.ts exact grant-expiry/transient-validation-history case (920-985) read as source, not run.

    Some first file guesses did not exist (for example secrets/SecretStore.ts, orchestration/OrchestratorMcpService.ts); rg --files, actual imports and owner source resolved these to the files above. No missing path was treated as evidence that a capability does not exist.

    Main alignment checked directly: git diff --name-only c8c3862... de50f47... contains Prism, guidance, glossary, map and supporting tests. A separate git diff c8c3862... de50f47... -- covering peer/secrets/auth/scheduledTasks runtime directories, OrchestratorMcpService, McpRouting, McpHttpServer, McpRequestReceipts, ws and relevant contracts returned no changes. Thus the proposal's relevant runtime premises still apply at de50; guidance overlap must use de50, not blindly copy c8c text.

    Elon record

    Requirements and who asked: User-approved #379 and #385 require A-only agent, human private entry on A, B-owned one-use secret, signed idle-B webhook, exact authority/receipt/lifetime binding and no forbidden exposure.
    Deleted: No alternative design panel, synthetic B agent, generic forwarding, distributed transaction, account grant, new cleanup framework or TTL-as-lifecycle-proof. No extra implementation or full acceptance in this review.
    Bottleneck: The proposed state owner's crash/retry and retirement contracts, specifically the point where live authority becomes durable destination acceptance.
    Checked myself: Real prepare/accept/validate, human actor, store replacement, schedule transaction/receipt and unlink/revoke ACK owners listed above; no execution inference is presented as a measured result.

    Required corrections

    R1. What retires saved material after request_secret returns?

    Proposal sections 2 and 4.2/4.4 delete the source-run watcher, rely on live checks at answer/reserve and hourly TTL, and cancel only pending/saved rows. After a successful answer the source wait returns and no longer watches the run. If that run then exits, or A unlinks before a consumer call, there may be no later operation to discover denial. A best-effort cancel authenticated as the old live actor can itself become inadmissible after run exit or unlink. This does not implement the promised exact retirement/reconciliation on those events.

    Evidence: OrchestratorMcpService.ts:2633-2694 ends the local wait after saved; PeerAuthority.ts:1233-1241 enforces run liveness on demand, not by pushing cleanup. PeerLinks.ts:199-213,491-507 and AuthSessions.ts:294-315,372-410 currently create automation invalidation obligations, not remote-secret ones. PeerMcpClient.ts:508-550 verifies an exact disable ACK, and retained recovery material is conditional on automation obligations. Merely using those hooks does not automatically retire new request rows.

    Finite amendment: register exact source run/request/generation ownership durably before showing the card; connect run-end/card-close, startup reconciliation, link replacement/unlink, grant revocation and local expiry hooks to terminalization of the exact B request. Reuse the existing wake/obligation/ACK/recovery pattern through a narrow request-retirement operation, retaining original disable-only recovery material until the matching ACK. A retirement action must be authorized by its durable ownership, not by borrowing a now-dead interactive run; it cannot create/answer/consume anything. Locally known B revocation retires immediately; unreachable cleanup remains explicitly unconfirmed and replayable. Reconcile reserved state against its consumer receipt before deleting it. TTL remains a backstop. Do not promise distributed atomic revocation beyond the approved validation-to-effect interval.

    Cheapest falsifier: real two-server native workflow saves and returns the handle, then ends the exact A run without another secret call; observe exact B row/material retirement. Repeat with unlink/relink while B is unavailable, restart A, then reconnect and await exact retirement ACK. An unrelated request/new generation must survive. No timer sleep or 24-hour clock jump substitutes for this observation.

    R2. What binds a stable create key to its original non-secret input and run?

    Proposal table uniqueness is (source_environment_id, source_generation, source_namespace, key_digest) and create returns an existing row on collision, but it specifies neither persisted input-conflict comparison nor explicit run ownership comparison. The namespace is a thread namespace, not a run namespace. Reusing the key in that thread with a different project/family/label or a later run must not return the old request as if the new intent had been accepted. Consumer reservation also needs the complete stable operation identity, not a caller-chosen consumer id alone.

    Evidence: requestIdentity.ts makes namespace stable across provider credentials; PeerAuthority.prepare:1559-1614 binds tool/args in its digest and captures originRunId in its protected source record. checkPayload:1841-1849 checks one action's payload, not equality between a newly authenticated create and an existing request row. A request handle plus matching thread namespace alone does not prove this is the original run. mutationReceipts.read:172-190 already rejects generation/inputDigest conflicts for schedule commands.

    Finite amendment: store/compare a canonical non-secret create input digest with the stable key and server-verified source run binding. Define same-key/same-input replay versus same-key/changed-input or run conflict before returning a handle. Bind reserve/take to the full verified consumer request identity, project and family. Keep origin run and grant generation immutable; do not overwrite the authority pointer on a create replay. A later run cannot revive unused material. Specify checkPayload for every dedicated action over the exact operation, request id, answer kind and revision/attempt metadata, while excluding the value itself from both envelope digest AND prepare's record-id hash. Secret values must never enter generic identify, requestDigest or logged error input.

    Human relay amendment: derive the client actor only from the authenticated WebSocket session under PendingClientSession, with orchestration:operate checked; B independently checks request-actor is client at the bound source and checks the original request's current eligibility. Human identity is separate from the agent authority that created the request. The metadata assertion cannot authorize arbitrary agent consumption or be publicly claimable through MCP. Existing requireClient/actorForRecord provide this building block, not blanket authority for a caller-supplied actor field.

    Cheapest falsifiers: same create key with changed project/family/input and same thread's later run; second thread's handle use; provider credential rotation in the same run; forged client actor through MCP; raw secret varied while inspecting only safe digest inputs. Retain positive native human-answer proof so denial tests cannot pass through a dead route.

    R3. How are filesystem save, re-save, reserve and cancellation fenced at B?

    Proposal store.set then UPDATE saved, plus saved-to-saved replacement, has no destination serialization/fencing contract. A's global answer lock does not cover B reserve/cancel/sweep or another source process. A re-save can replace the file after a consumer reserved/read it; cancellation can delete a newer write; an interrupted set can leave material attached to a pending row. The proposed sweep only deletes terminal/missing-row values, so its claim that every save/status orphan is swept is incomplete when the pending row exists.

    Evidence: full ServerSecretStore.ts set uses unique temp + rename + chmod, which avoids partial visible file replacement but provides no SQL CAS or multi-call lock. SecretRequests.ts explicitly serializes local answers and separately serializes consume; it does not solve the new remote owner's cross-state race. Proposal's remote owner adds no equivalent complete fence. set is not a transaction with SQLite.

    Finite amendment: give B one fenced save/transition owner. Record a durable save attempt/revision, prohibit reserve while a save can still replace bytes, and publish saved only for the exact protected write that won. Either serialize all relevant transitions with restart reconciliation, or use immutable attempt-named protected blobs and a SQL CAS pointer; choose the smaller implementation, but state its interruption behavior. A stale completion/cleanup may touch only its own attempt. Lost answer response must reconcile the original saved attempt, not silently replace a reserved or terminal secret. Pending-with-blob and temp-file crash states require explicit startup reconciliation. Retired/reserved provenance must remain sufficient to reconcile a receipt; do not erase it merely to satisfy the proposed consumer_id CHECK.

    Cheapest falsifiers: pause after filesystem publication before saved-row update, restart and reconcile; overlap re-save with reserve, cancel and expiry; lose answer reply and retry after reservation. Assert the webhook receives exactly the accepted value and stale cleanup cannot delete a newer accepted attempt. These require owner-level SQLite + real temporary SecretStore, not a full UI.

    R4. Can a committed receipt be returned without reauthorizing a new use?

    Proposal 4.5 says reserve first, then secret-started, with confirmed replay later. A consumed handle fails reserve and a dead origin run fails its source check before the existing receipt is reached. Moving a lookup inside mutationReceipts alone is insufficient: existing A prepare and B HTTP accept validate before the handler, and historicalScheduleReceipt itself validates original authority. Therefore the stated lost-response replay after origin run exit is not established by existing plumbing.

    Evidence: PeerForwarding.ts:996 calls prepare; PeerAuthority.prepare:1617 validates before sending; McpHttpServer.ts:332 calls accept before dispatch; PeerAuthority.accept:1808 asks source validate; historicalScheduleReceipt at 3554-3615 reads acceptance first but subsequently validates original authority and caller eligibility. mutationReceipts.accept:281-289 currently rechecks before reading confirmed. ScheduledTaskService.upsert:1245 has an earlier confirmed shortcut, but it is still behind those outer routes.

    Finite amendment: make the exact durable confirmed row the first recovery decision before any new source-run authority query/reservation/capture. Use an explicitly receipt-only authenticated recovery branch across the real transport, bound to original owner, generation, operation id, digest and project/resource evidence. It may return only the existing sanitized receipt and cannot execute a missing operation. Do not weaken generic live acceptance. If no durable receipt exists, recovery must complete current source-run/authority validation outside SQL immediately before a new commit and use local fences inside SQL. No receipt means no offline positive cache. Reconcile receipt uncertainty before deciding a reservation is reusable or discardable. Distinguish run expiry from grant replacement: a new generation cannot replay protected work automatically.

    Atomicity constraint: destination schedule row, automation acceptance, request consumed CAS and confirmed consumer receipt must share the outer acceptance transaction. Read the protected value without deletion inside that fenced acceptance; perform cleanup only after that OUTERMOST transaction succeeds. ScheduledTaskService.ts:1320-1497 has an inner SQL transaction wrapped by MutationReceipts.accept, whose transaction also writes the receipt. The proposed tap beside notifyAutomationChanges is a suitable location only if it remains outside accept and no caller wraps it in a new outer transaction. Never finalize beside an inner withTransaction or inside perform; preserve a recoverable copy if any outer step rolls back. Local-ref secret-started/outcome_unknown remains unchanged.

    Cheapest falsifiers: commit schedule/consumed/receipt, suppress response, end original run and deny any new source validation, then replay same identity and get the same receipt without another use; same test with no receipt must deny/retire. Race same key and rival keys; induce rollback after take but before receipt commit, then retry; stop after commit before cleanup and recover only the redundant request copy. Confirm exactly one task/incarnation and correct protected webhook credential. Run through actual two-server MCP entry, not just direct accept calls.

    R5. Bound the parity and leakage claims accurately

    Single-value schedule.webhook-signature is a defensible #385 consumer bound. It does not delete current request_secret.fields support (1-5 fields, contracts orchestratorMcp.ts:821-835 and SecretRequests.answerValues). Proposal's unconditional remote fields rejection must be recorded as an explicit remaining #379/#386 catalog obligation, not complete request_secret parity. Even a one-element fields array deserves an explicit mapping/validation decision, rather than accidental rejection of an equivalent single value. Do not redesign multi-field consumers here; the parent owns clearing the native catalog/coverage edges before final acceptance.

    The proposed scan of both entire SQLite databases for no sentinel is false as an oracle. ScheduledTaskService.ts:213,583-669,1417 intentionally persists scheduled_tasks.webhook_secret on B. Require sentinel absence in named forbidden projections, events, cards, tool results, request/consumer receipts, transcript data and logs/traces; separately assert it exists only in authorized protected storage while needed, then redundant request copies disappear after commit. Inspect WAL/other retained test outputs where relevant without publishing secret payloads. A sanitized fixture sentinel is not a reason to assert that no secret may exist anywhere in B's database.

    Compatibility constraints retained: negotiate exact route version/family and consumer support before ANY card; reject the old generic handler on B itself before record/file/ref side effects, not merely at A's special route. Replace PeerToolRouting.test.ts:606-611, which currently asserts the bad generic route, with positive dedicated path plus direct/old-A generic rejection. Preserve destination-local multi-field/files/local-ref behavior and local-ref outcome_unknown recovery. No fallback to a synthetic B agent or raw ref tunnel.

    Cleared concerns and finite amendment boundary

    • Source authority records are durably inserted into peer_authority_records (save, ON CONFLICT DO NOTHING); no per-call deletion/expiration appears in this owner. validate checks originating activeRunId, not whether the requesting tool call is still open. Transient validate mailbox frames are deleted, which is a different lifetime. McpSessionRegistry credentials have a 24-hour liveness window and explicit revoke operations; do not confuse that credential lifetime with durable request authority. A create record can therefore survive a closed tool call while its exact run remains active, subject to current link/grant validation. Still prove this through the new route because no feat(peer): request destination secrets and create signed webhooks without a destination agent #385 route exists yet.
    • ensureChannel forks intake independently, processes frames concurrently and has bounded long-poll fallback. It is a plausible existing owner for cold-link validation, not a demonstrated feat(peer): request destination secrets and create signed webhooks without a destination agent #385 result. The cheapest decisive native case is a first secret control POST on a fresh link, with no delegation or earlier tool request and no B-to-A dialing, while the POST is still open.
    • Dedicated route + explicit non-bearer handle is a small warranted extension: the current local service requires a B run and destructive consume cannot satisfy recoverable remote consumption. Keep business sequencing in the B owner/service; transport only authenticates/decodes/maps.
    • No actual human-owned semantic choice is unresolved. These are reversible technical means to already approved semantics. Parent can accept the amendments, clear only proven feat(peer): surface and answer remote descendant requests from the parent #369/feat(peer): run destination schedules webhooks and Spectrum while the creator is offline #384 edges, then commission one exclusive implementation writer. A runtime/test failure later may change the evidence, not retroactively waive these invariants.

    Proof classes and delivery limits

    Executed here: source/document/Git-object and GitHub-record reads; scratch-only report/input writes. All native cases above are proposed falsifiers, NOT executed tests or reproduced bugs. No package install, test, probe, server, browser, device, live userdata, account/grant change, source/index/ref mutation, GitHub write or nested agent was used. This report is complete as a bounded design challenge, not INCOMPLETE merely because runtime proof is intentionally forbidden.

    Parent retains design adjudication, native dependency edges, implementation workspace, fresh whole-candidate Sol MEDIUM and Opus 5.5 MEDIUM reviews, CI/current client and cross-machine proof, and final approved #379 main merge only. No rollout authority is exercised or inferred here.

  3. lukemaj commented on Oct 20, 2026

    @lukemaj
    ContributorAuthor

    What One exclusive385 component writer is implementing the amended native remote-secret contract.
    Why The independent design challenge found five bounded lifecycle/identity/recovery corrections to the original proposal.
    So what The writer will return a clean component PR into integration for fresh Sol MEDIUM and Opus5.5 MEDIUM whole reviews.

    Canonical design decision and unchanged independent report: #385 (comment) . All R1-R5 accepted constraints are in the implementation packet. New task379-385-native-secret-channel-implementation-de50-medium-v1 is a visible T3 delegated worker (Prism selected GPT-6.1 Sol), requested MEDIUM, exclusive branchfeat/385-remote-secret-channel provisioned fromde50f47a47a87310649e058da0467badc1d48938. The parent retains all integration/main topology, review, CI, current-client acceptance and merge-order ownership.

    The worker may edit only its new385 workspace and task-owned proof, use isolated sanitized native fixtures and task-local dependencies, and create/update385's component PR. No nested review, actual human secret capture, existing grants/live userdata, Browser/Device/product dev server, installed restart, deployment or merge. Native public service tests must prove cold-link concurrency, one-use save/reserve/outer-commit recovery, exact retirement ACKs and confirmed-receipt-only replay. A failed boundary is evidence to repair its owner, not a scope waiver. Local multi-field/ref behavior remains unchanged; broader remote fields parity remains386 acceptance.

    #386 Run-group design continues separately read-only against frozen driver. No concurrent source writer shares385 files or its branch.

    Requirements and who asked User-approved379/385 and accepted independent design amendments.
    Deleted Synthetic B agents, raw-ref forwarding, unnecessary re-save, new orchestration frameworks and old-head review transfer.
    Bottleneck Implement and prove dedicated destination secret lifetime and its durable consumer.
    Checked myself Full proposal/challenge, actual consuming/routing/receipt owners, predecessor review-ready records and current exact integration base.

  4. lukemaj commented on Oct 20, 2026

    @lukemaj
    ContributorAuthor

    What: The exclusive #385 writer verified its task binding and frozen base and selected the amended dedicated secret owners before code.
    Why: The generic destination request route returns local refs and the local ref owner deletes before outer scheduler acceptance, so neither can own recoverable remote consumption.
    So what: This child implements one component into feat/379-cross-machine-parity; parent retains topology, dual whole reviews, CI, acceptance and all merges.

    Requirements and who asked: User-approved #379 and #385, original proposal6289669134 with mandatory parent amendment6289829273 R1-R5.
    Deleted: Generic request_secret forwarding, synthetic B threads, saved-value replacement, TTL-only retirement, raw ref/path transport, and destructive remote consumption before receipt commit.
    Bottleneck: Destination save/consume fencing and exact durable source retirement across the real authority/control transport.
    Checked myself: Clean exact feat/385-remote-secret-channel workspace /home/luke/.t3/worktrees/chromeria/feat-385-remote-secret-channel; HEAD de50f47, tree a32295120382ba5d8a8c2f3e052476ad2b07cae9; T3 actual current thread worktree/branch match, origin toolboxmd/chromeria. Read actual local secret, prepare/accept, request-actor, scheduler outer acceptance, recovery route and disable-obligation owners. No code or proof result yet.

    Native ownership selected: dedicated typed /mcp/peer-control/secrets, destination RemoteSecretRequests service with immutable protected attempt names plus SQL CAS; existing SecretRequests card/RPC routes authenticated client entry through source relay. Existing PeerAuthority supplies verified actor/run/grant evidence and payload validation without values in digest inputs. Exact source run/request/generation obligations integrate existing link/session disable invalidation, wake and retained original recovery transport, with retirement-only ACK. Scheduler confirmed receipt is checked on an authenticated receipt-only transport before new live authority; new consumption reserves full verified operation identity, reads without deletion and consumes under the outer task/automation/receipt SQL transaction, then cleans redundant material after commit.

    R1-R5 are mandatory. A one-element fields request maps its sole value; broader remote fields remain explicitly unsupported under parent #386 catalog obligation. Local multi-field/files/ref semantics stay intact. Forbidden-owner sentinel oracles exclude authorized B webhook_secret storage. Independent native workflows and owner fault injection remain unexecuted, not waived.

    Upstream refresh: main d36b7da2485bc889b320a285deea181f637d760a. gh search issues --repo pingdotgg/t3code 'remote secret' returned []; open secret PR discovery returned atomic-store10579, plugin16050/16056, peer21824/21672 and unrelated entries, no selected dedicated remote-secret implementation. Existing proposal provenance remains source-only; no upstream patch selected or port claimed.

    Private continuation: /home/luke/.local/state/chromeria-379-proof/385-implementation. Dependencies absent initially; task-only offline frozen ignore-scripts setup running with existing Node24.13.1 and pnpm11.10.0, no shared/global install. No process services outside isolated fixtures are authorized.

  5. lukemaj commented on Oct 20, 2026

    @lukemaj
    ContributorAuthor

    What: Repair the four accepted lifecycle mechanisms in PR #583 under the existing #385 owner.
    Why: Independent c9e source reviews found timeout/save loss, parked terminal cleanup/startup failure, leaked transient frames and destructive transient validation handling.
    So what: Implementation and exact new-head proof precede a new parent freeze; parent owns both whole reviews, integration composition, acceptance and merge.

    Checkpoint checkpoint/583-before-r1-cbd72 points to cbd72f9, tree 2a1b36d00fdc34f8204eed7a6ac9c4bf147f40c1. Own base stays de50f47 even though integration advanced independently. New proof is exclusively r1-repair; all earlier indexes/manifests/logs remain immutable.

    Finite owners: requestSecret cancellation hunk, RemoteSecretRequests cleanup/eligibility owner, PeerAuthority two transient-frame lists, own new native workflow and necessary existing fixture leaves. Original production Git objects retained for failing-before reproduction; no other pending/question/completion scope, version, topology, framework, new table or lock.

    Requirements and who asked: Parent accepted Sol F1/F2 and Opus F2/F3/F6, plus explicit reassessment of the active-obligation proof.
    Deleted: Optional unmeasured global-lock/history-wake redesign and unrelated card/guidance/span edits.
    Bottleneck: Faithful deterministic timeout/idle-retry/transient-failure discriminators at existing public owners.
    Checked myself: Both complete reports/addendum, clean exact cbd72 head/tree/base/remotes, current owner/caller states and explicit production immutable objects.

  6. lukemaj commented on Oct 20, 2026

    @lukemaj
    ContributorAuthor

    What: On main 1b2c69b, a destination secret request and signed webhook work end to end, but a missing link grant produces a misleading refusal.
    Why: RemoteSecretRequests.control requires the destination link grant "Access existing environment resources" (resourceScope: environment-resources). The POST /mcp/peer-control/secrets route in McpHttpServer.ts maps every failure to 403, so the source reports "The destination could not confirm remote secret v1; no fallback prompt was created." That reads like a version mismatch, not a missing permission.
    So what: Small fix for this Issue's owner: return a distinct denial that names the required grant. Non-blocking.

    Evidence (#379 acceptance, isolated A and C on bestia): after granting A's link that permission on C, request_secret showed its private card in the A-only web client, and the value was saved on C. In the same turn, schedule_task used the signature.secretRequest handle and created one signed webhook; a retry with the same key returned the same id, and the listing showed webhookSignature: set. With A stopped, unsigned and wrong-signature POSTs got 401, and the correctly signed POST got 202. The run on C wrote the body, framed as untrusted webhook data. Using the handle in a later turn fails with "The secret request belongs to another run or retired generation", which looks intended.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions