-
Notifications
You must be signed in to change notification settings - Fork 0
feat(factory): emit completed-session replay pointers #290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| # Completed-session replay pointers on Factory PRs | ||
|
|
||
| Every pull request Factory opens ends with the ruled, reference-only trajectory | ||
| marker: | ||
|
|
||
| ```html | ||
| <!-- trajectory: work_unit_id=AgentWorkforce/factory#260 work_unit_surface=github session_ref=<relay-session-uuid> --> | ||
| ``` | ||
|
|
||
| The three keys are the contract. `session_ref` is the existing opaque UUID | ||
| emitted by Relay; Factory does not mint a replay id or add a fourth key. Linear | ||
| work uses its issue key, GitHub work uses `owner/repo#number`, and work without a | ||
| provider ticket uses a Factory-synthesized work-unit id. | ||
|
|
||
| Factory deliberately does not write `relay session replay <session_ref>` or a | ||
| retention claim into the PR body. Replay availability changes after publication | ||
| as the workspace's pricing-tier retention window advances. An authenticated | ||
| resolver reads this marker, obtains the workspace's live `retained-since` or | ||
| never-prune boundary, and only then renders the copyable replay command. If the | ||
| conversation has aged out, the resolver must show incomplete coverage and must | ||
| not render it as replayable. | ||
|
|
||
| When Factory has no canonical non-nil session UUID, the marker says | ||
| `session_ref=missing`. The SDK parser does not return that marker as resolver | ||
| input. Parsing a UUID does not itself establish replay availability. Factory | ||
| also strips inherited trajectory markers from issue text before | ||
| appending its single canonical marker. | ||
|
|
||
| The PR carries only a reference. Conversation payload and access control remain | ||
| at resolution time, and replay of completed work stays distinct from attaching | ||
| to or relocating a running session. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| import { describe, expect, it } from 'vitest' | ||
|
|
||
| import { | ||
| canonicalTrajectorySessionRef, | ||
| renderTrajectoryPointer, | ||
| stripTrajectoryPointers, | ||
| trajectoryPointerFromBody, | ||
| trajectorySessionRefFromBody, | ||
| } from './trajectory' | ||
|
|
||
| describe('trajectory replay pointer', () => { | ||
| const sessionRef = '0198b179-c6c2-7e63-9177-4ef52f56c192' | ||
|
|
||
| it('renders and parses the ruled three-key pointer for a canonical resolver input', () => { | ||
| const rendered = renderTrajectoryPointer({ | ||
| workUnitId: 'AgentWorkforce/factory#260', | ||
| workUnitSurface: 'github', | ||
| sessionRef, | ||
| }) | ||
|
|
||
| expect(rendered).toBe( | ||
| `<!-- trajectory: work_unit_id=AgentWorkforce/factory#260 work_unit_surface=github session_ref=${sessionRef} -->`, | ||
| ) | ||
| expect(trajectoryPointerFromBody(rendered)).toEqual({ | ||
| workUnitId: 'AgentWorkforce/factory#260', | ||
| workUnitSurface: 'github', | ||
| sessionRef, | ||
| }) | ||
| expect(trajectorySessionRefFromBody(rendered)).toBe(sessionRef) | ||
| }) | ||
|
|
||
| it.each([ | ||
| undefined, | ||
| '', | ||
| 'unknown-session-v3b', | ||
| 'missing', | ||
| 'ar-260-impl-factory', | ||
| '00000000-0000-0000-0000-000000000000', | ||
| 'unsafe --> comment', | ||
| ])('does not render unavailable input %j as replayable', (unavailableRef) => { | ||
| const rendered = renderTrajectoryPointer({ | ||
| workUnitId: 'AR-260', | ||
| workUnitSurface: 'linear', | ||
| sessionRef: unavailableRef, | ||
| }) | ||
|
|
||
| expect(canonicalTrajectorySessionRef(unavailableRef)).toBeUndefined() | ||
| expect(rendered).toContain('session_ref=missing -->') | ||
| expect(trajectoryPointerFromBody(rendered)).toBeUndefined() | ||
| expect(rendered).not.toContain('relay session replay') | ||
| }) | ||
|
|
||
| it('never bakes a replay availability or retention claim into the PR body', () => { | ||
| const rendered = renderTrajectoryPointer({ | ||
| workUnitId: 'AR-260', | ||
| workUnitSurface: 'linear', | ||
| sessionRef, | ||
| }) | ||
|
|
||
| expect(rendered).not.toContain('relay session replay') | ||
| expect(rendered).not.toMatch(/retained|retention|expires|available/iu) | ||
| }) | ||
|
|
||
| it('refuses conflicting pointers and strips inherited markers', () => { | ||
| const first = renderTrajectoryPointer({ | ||
| workUnitId: 'AR-1', | ||
| workUnitSurface: 'linear', | ||
| sessionRef, | ||
| }) | ||
| const second = renderTrajectoryPointer({ | ||
| workUnitId: 'AR-1', | ||
| workUnitSurface: 'linear', | ||
| sessionRef: '0198b179-c6c2-7e63-9177-4ef52f56c197', | ||
| }) | ||
|
|
||
| expect(trajectoryPointerFromBody(`${first}\n${second}`)).toBeUndefined() | ||
| expect(stripTrajectoryPointers(`body\n\n${first}\n${second}`)).toBe('body') | ||
| }) | ||
|
|
||
| it('rejects an unsafe work-unit token before emitting an HTML comment', () => { | ||
| expect(() => renderTrajectoryPointer({ | ||
| workUnitId: 'AR-1 --> leaked', | ||
| workUnitSurface: 'linear', | ||
| sessionRef, | ||
| })).toThrow(/comment-safe token/u) | ||
| }) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| export type TrajectoryWorkUnitSurface = 'linear' | 'github' | 'factory' | ||
|
|
||
| export interface TrajectoryPointer { | ||
| workUnitId: string | ||
| workUnitSurface: TrajectoryWorkUnitSurface | ||
| sessionRef?: string | ||
| } | ||
|
|
||
| export const MISSING_TRAJECTORY_SESSION_REF = 'missing' | ||
|
|
||
| const TRAJECTORY_POINTER_PATTERN = | ||
| /<!-- trajectory: work_unit_id=([^\s>]+) work_unit_surface=(linear|github|factory) session_ref=([^\s>]+) -->/gu | ||
| const AI_HIST_SESSION_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu | ||
| const NIL_SESSION_UUID = '00000000-0000-0000-0000-000000000000' | ||
| const POINTER_TOKEN = /^[^\s>]+$/u | ||
|
|
||
| /** | ||
| * Accept only the opaque UUID emitted by Relay for an ai-hist session. Factory | ||
| * does not resolve it or infer current replay availability; the authenticated | ||
| * replay resolver owns that live, retention-aware decision. | ||
| */ | ||
| export function canonicalTrajectorySessionRef(value: string | undefined): string | undefined { | ||
| const normalized = value?.trim() | ||
| if (!normalized || !AI_HIST_SESSION_UUID.test(normalized) || normalized.toLowerCase() === NIL_SESSION_UUID) { | ||
| return undefined | ||
| } | ||
| return normalized | ||
| } | ||
|
|
||
| /** Render the ruled three-key HTML marker without claiming replay availability. */ | ||
| export function renderTrajectoryPointer(pointer: TrajectoryPointer): string { | ||
| if (!POINTER_TOKEN.test(pointer.workUnitId)) { | ||
| throw new Error(`Trajectory work unit id must be a comment-safe token: ${pointer.workUnitId}`) | ||
| } | ||
| const sessionRef = canonicalTrajectorySessionRef(pointer.sessionRef) ?? MISSING_TRAJECTORY_SESSION_REF | ||
| return `<!-- trajectory: work_unit_id=${pointer.workUnitId} work_unit_surface=${pointer.workUnitSurface} session_ref=${sessionRef} -->` | ||
| } | ||
|
|
||
| /** | ||
| * Returns one unambiguous resolver input pointer. Parsing proves identity | ||
| * shape, not live replay availability; clients must resolve workspace retention. | ||
| */ | ||
| export function trajectoryPointerFromBody(body: string): Required<TrajectoryPointer> | undefined { | ||
| const pointers = new Map<string, Required<TrajectoryPointer>>() | ||
| for (const match of body.matchAll(TRAJECTORY_POINTER_PATTERN)) { | ||
| const sessionRef = canonicalTrajectorySessionRef(match[3]) | ||
| const workUnitId = match[1] | ||
| const workUnitSurface = match[2] as TrajectoryWorkUnitSurface | undefined | ||
| if (!sessionRef || !workUnitId || !workUnitSurface) continue | ||
| const pointer = { workUnitId, workUnitSurface, sessionRef } | ||
| pointers.set(`${workUnitId}:${workUnitSurface}:${sessionRef}`, pointer) | ||
| } | ||
| return pointers.size === 1 ? [...pointers.values()][0] : undefined | ||
| } | ||
|
|
||
| /** Return only the session UUID from the one unambiguous resolver input pointer. */ | ||
| export function trajectorySessionRefFromBody(body: string): string | undefined { | ||
| return trajectoryPointerFromBody(body)?.sessionRef | ||
| } | ||
|
|
||
| /** Remove inherited pointers before Factory appends its single canonical one. */ | ||
| export function stripTrajectoryPointers(body: string): string { | ||
| return body.replace(TRAJECTORY_POINTER_PATTERN, '').replace(/\n{3,}/gu, '\n\n').trim() | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.