Skip to content

feat(packs): script host context — pack store and change-request reads (T2) - #443

Merged
johnnyelwailer merged 4 commits into
mainfrom
feature/seam-t2-script-host-context-store-change-requests-8ad5e6dc
Oct 8, 2026
Merged

johnnyelwailer merged 4 commits into
mainfrom
feature/seam-t2-script-host-context-store-change-requests-8ad5e6dc

Conversation

@johnnyelwailer

Copy link
Copy Markdown
Owner

Recipe scripts could only reach files, fetch and tools. Pack logic needs its own durable store (T1) and a provider-neutral, read-only view of the change requests it works on, without holding provider tokens or calling gh/glab itself. This is seam S4, the T2 subset.

What scripts get

ScriptHandlerCtx gains two optional members. Each is built per run and is present only when the run is entitled to it:

  • ctx.store: ScriptPackStore: the recipe's own pack store (T3TeamPackDocumentStore.forPack), offering get, list, insertOrGet, put (CAS) and increment. It is present only when the recipe directory is a registered pack recipe root and that pack registered persistence (which requires store:v1). No method takes a pack id, so pack A cannot see pack B.
  • ctx.changeRequests: ChangeRequestReader: detail(ref) and diff(ref, { cursor?, pageSize?, commit? }), backed by PullRequestService, so every provider it reads works here too. It is present only when the recipe's own declared tool groups include integration.read (read from the recipe, never the request).
    • Scope: ref = { repository, number, host? }, never a URL. Only repositories PullRequestService resolves for the run's project are readable: the project's own remote plus its linked repositories, via the new PullRequestService.projectRepositories(projectId). Anything else fails with the typed ChangeRequestScopeError before any provider call. This is stricter than a host-qualified PullRequestRef, which may route to any repository on that host.
    • Diff pages are bounded. Each page holds whole files only, at most pageSize of them (default 20, max 100) and at most 256,000 characters. The cursor is opaque: the provider's own slice cursor plus a file offset, so re-reading a slice is a PullRequestService cache read. The page is returned as data: { kind: "change-request-diff-page", repository, number, patch, fileCount, truncated, next? }.
    • A bad number or cursor fails with ChangeRequestInputError.

Wiring

  • SDK: WorkflowRunOptions.scriptHost is spread into the script context in t3team-sdk.workflowRunner.ts.
  • Server: the context is built by T3TeamScriptHost.forRun({ projectId, recipePath, toolGroups }) (t3team-scriptHostContext.ts) in two places:
    • the recipe launch route;
    • boot rehydration, which rebuilds it from the run row's recipe_path and persisted host-tool grant, so a restored run keeps exactly the access it launched with and gains none.
  • The layer is provided at the serve level in server.ts. The route requires the service, so typecheck proves it is provided.
  • PullRequestService.ts and server.ts were already in allowedModifiedFiles.

Deviations from the S4 sketch

  • diff(ref, { cursor, pageSize, commit }) instead of diff(ref, { base, head }, cursor). PullRequestService diffs the whole change request or a single commit, not an arbitrary base..head range.
  • Detail adds headSha and headRepository. baseSha, isCrossRepository and authorAssociation would need contract and provider-adapter changes, so they are not added.
  • fileAt, blobShas and project.linkedRepositories are left to T12, as planned.

Verification (local; this fork's CI skips test jobs)

  • vp test run (run serially):
    • t3team-scriptHostContext.test.ts: 5/5. A real run via launchWorkflowRecipe with a script that reads detail, pages the diff across two provider slices and writes the pack store, all against a fake PullRequestService. Also covers: refusal of an unlinked repository or the right selector on the wrong host, with zero provider calls; store isolation between two packs; members absent without store:v1, without a pack, or without integration.read.
    • t3team-scriptHostDiffPage.test.ts: 4/4.
    • Regressions: rehydrate 8/8, rehydrate scripts 2/2, host tool grant 4/4, host draft tools 5/5, PR context 5/5, SDK engine replay 7/7.
  • tsc --noEmit clean for apps/server and packages/t3team-sdk. Targeted vp lint and vp fmt clean. node t3team-additive-guard.mjs passes.
  • No cross-provider review yet: Codex hit its spend cap, the Nexplore review was cancelled, and the Cursor/Copilot CLIs did not finish (restart). The orchestrator's pre-merge review follows.

🤖 Generated with Claude Code

Phil J and others added 2 commits October 7, 2026 21:34
…s (T2)

Recipe scripts get two host members on ScriptHandlerCtx:
- ctx.store: the recipe's own pack document store (T1), present iff the recipe is a pack
  recipe whose pack registered persistence (store:v1); bound to that pack id, so one pack
  can never see another's documents.
- ctx.changeRequests.detail/diff: read-only change requests backed by PullRequestService,
  present iff the recipe declares integration.read. Only repositories the service resolves
  for the run's project (own remote + linked repositories, new
  PullRequestService.projectRepositories) are readable; anything else is refused with the
  typed ChangeRequestScopeError before any provider call. Diff pages are whole files,
  bounded by page size and characters, with an opaque cursor.

Built per run at launch and rebuilt on boot rehydration from the run row, so a restored run
keeps exactly the access it launched with.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e script host

Effect diagnostics on main now reject global Error values in a failure channel and unknown
error channels; a bad number or diff cursor fails with ChangeRequestInputError, and the pack
store adapter carries T3TeamPackDocumentStoreError.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL labels Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

⚠️ The latest CI run did not produce a thread transfer result for 0e6df0c.

This comment will update automatically after the next completed run.

Phil J and others added 2 commits October 7, 2026 21:46
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@johnnyelwailer
johnnyelwailer marked this pull request as ready for review October 8, 2026 05:56
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-08T06:01:07.917707Z 0e6df0c Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@johnnyelwailer
johnnyelwailer merged commit afa9a57 into main Oct 8, 2026
26 of 27 checks passed
@johnnyelwailer
johnnyelwailer deleted the feature/seam-t2-script-host-context-store-change-requests-8ad5e6dc branch October 8, 2026 05:57

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0e6df0c238

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +112 to +117
const next =
nextOffset < files.length
? encodeDiffCursor({ slice: position.slice, offset: nextOffset })
: slice.nextCursor === null
? undefined
: encodeDiffCursor({ slice: slice.nextCursor, offset: 0 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject repeated provider diff cursors

When a provider returns the same non-null nextCursor for consecutive slices, this code reissues an identical encoded next, so a script following while (page.next) repeatedly fetches the same page and can make unbounded provider calls. This failure mode is already handled by the seen-cursor and page-limit guards in loadFullDiff; apply an equivalent guard here, or at minimum reject slice.nextCursor === position.slice, rather than returning the cursor again.

AGENTS.md reference: AGENTS.md:L41-L47

Useful? React with 👍 / 👎.

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

Labels

size:XL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant