Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
54ab473
test(self-driving): e2e harness + snapshots
gewenyu99 Jun 30, 2026
584a018
Fix tui-host: mirror run-wizard's composed walk
gewenyu99 Jun 30, 2026
c618f20
Fix e2e flow trace for the composed-run model
gewenyu99 Jun 30, 2026
d3f1d54
e2e: drive the self-driving detect pick + refresh the profile path
gewenyu99 Jun 30, 2026
b2cfd99
e2e: monorepo-aware detect pick in tui-host
gewenyu99 Jun 30, 2026
7ef9565
e2e: keep wizard_ask live when an e2e driver answers; cancel self-dri…
gewenyu99 Jun 30, 2026
a57e9e4
e2e: answer self-driving's asks instead of cancelling
gewenyu99 Jul 2, 2026
1a2cfaf
test(source-maps): e2e harness + snapshots for upload-source-maps
ablaszkiewicz Jul 2, 2026
e66836d
Merge e2e snapshot harness (self-driving + source-maps) onto source-m…
gewenyu99 Aug 27, 2026
1adaa68
feat(e2e): let the source-maps host run the real build
gewenyu99 Aug 27, 2026
6da81ac
fix(e2e): load the app env file into the source-maps build env
gewenyu99 Aug 27, 2026
5671a93
fix(e2e): let the app env file override inherited POSTHOG_* build vars
gewenyu99 Aug 27, 2026
50c3e1b
Merge branch 'posthog/bind-source-maps-to-sol-medium' into posthog/so…
gewenyu99 Aug 27, 2026
b494975
feat(wizard-ci): let a PR comment pin the context-mill ref (#1153)
Gilbert09 Aug 27, 2026
0b87fc8
feat(e2e): let the e2e harness drive the warehouse flow (#1152)
Gilbert09 Aug 27, 2026
d1cce49
feat(telemetry): stamp wizard_ask outcome events with the ask subject…
Gilbert09 Aug 28, 2026
f0e024d
fix(warehouse-e2e): stop the secret rule refusing every credential qu…
Gilbert09 Aug 28, 2026
dc32339
feat(gateway): use server-minted scoped tokens for model calls (#1130)
brandonleung Aug 28, 2026
5444275
chore(main): release 2.68.0 (#1158)
releaser-wizard[bot] Aug 28, 2026
db0c556
feat(gateway): tag events with the gateway edition and fail hollow ru…
gewenyu99 Aug 28, 2026
a8c3ef1
fix(gateway): route v2 openai calls over the responses api (#1166)
brandonleung Aug 28, 2026
ea6ce9b
chore(main): release 2.69.0 (#1167)
releaser-wizard[bot] Aug 28, 2026
d1123ea
test(source-maps): let the e2e host drive native variants
ablaszkiewicz Aug 31, 2026
dc65104
feat(privacy): disclose the dependency scan and let users decline sha…
MattBro Aug 31, 2026
748daba
chore(main): release 2.70.0 (#1173)
releaser-wizard[bot] Aug 31, 2026
d64b381
Merge remote-tracking branch 'origin/main' into HEAD
gewenyu99 Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 90 additions & 8 deletions .github/workflows/wizard-ci-trigger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ jobs:
bodyParts.push('</details>');
}

bodyParts.push('');
bodyParts.push('**Test against a Context Mill branch:**');
bodyParts.push('- `/wizard-ci all context-mill:my-branch`');
bodyParts.push('');
bodyParts.push('Add `context-mill:<branch>` to any command above to pin the Context Mill branch. It defaults to `main`.');
bodyParts.push('');
bodyParts.push('Results will be posted here when complete.');

Expand All @@ -154,6 +159,7 @@ jobs:
if: |
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) &&
startsWith(github.event.comment.body, '/wizard-ci') &&
!startsWith(github.event.comment.body, '/wizard-ci-snapshots')
runs-on: ubuntu-latest
Expand Down Expand Up @@ -188,7 +194,36 @@ jobs:

const comment = context.payload.comment.body;
const match = comment.match(/\/wizard-ci\s+(\S+)/);
const app = match ? match[1] : 'all';
const firstArg = match ? match[1] : 'all';

// A reviewer can pin the companion repo with `context-mill:<ref>`.
// A lone pin is not an app name, so fall back to every app.
const app = firstArg.startsWith('context-mill:') ? 'all' : firstArg;

// The ref arrives in a PR comment, so treat it as untrusted. Accept
// git ref characters only. Reject traversal, a leading dash and a
// trailing slash.
const isPlausibleRef = (ref) =>
ref.length <= 200 &&
/^[A-Za-z0-9._\/-]+$/.test(ref) &&
!ref.includes('..') &&
!ref.startsWith('-') &&
!ref.startsWith('/') &&
!ref.endsWith('/');

// Read the pin from the command line only, not from later prose.
const companion = comment
.split(/\r?\n/)[0]
.match(/(?:^|\s)context-mill:(\S+)/);
const contextMillRef = companion ? companion[1] : null;

if (contextMillRef && !isPlausibleRef(contextMillRef)) {
core.setFailed(
'The context-mill ref is not a valid git ref. ' +
'Use `/wizard-ci <app> context-mill:<branch>`.'
);
return;
}

// React with rocket to show we're processing
await github.rest.reactions.createForIssueComment({
Expand All @@ -210,20 +245,26 @@ jobs:
source_pr_number: context.payload.issue.number,
source_pr_url: pr.data.html_url,
wizard_ref: pr.data.head.ref,
// Omit the key when unpinned. The workbench then uses `main`.
...(contextMillRef ? { context_mill_ref: contextMillRef } : {}),
notify_slack: 'false',
notify_pr: 'true'
}
});

console.log(`Triggered wizard-ci for app: ${app}`);
const pinNote = contextMillRef ? ` (context-mill ref ${contextMillRef})` : '';
console.log(`Triggered wizard-ci for app: ${app}${pinNote}`);

# ============================================================================
# HANDLE SNAPSHOTS: Parse /wizard-ci-snapshots and trigger the snapshots workflow
# ============================================================================
# Same write-access restriction as handle-command above. This job matters more:
# its positional `[ref]` argument is attacker-chosen, not the PR's own branch.
handle-snapshots-command:
if: |
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) &&
startsWith(github.event.comment.body, '/wizard-ci-snapshots')
runs-on: ubuntu-latest
timeout-minutes: 5
Expand Down Expand Up @@ -253,12 +294,50 @@ jobs:
return;
}

// /wizard-ci-snapshots <app> [wizard_ref] — ref defaults to this PR's branch.
const m = context.payload.comment.body.match(
/\/wizard-ci-snapshots\s+(\S+)(?:\s+(\S+))?/
);
const body = context.payload.comment.body;

// The ref arrives in a PR comment, so treat it as untrusted. Accept
// git ref characters only. Reject traversal, a leading dash and a
// trailing slash.
const isPlausibleRef = (ref) =>
ref.length <= 200 &&
/^[A-Za-z0-9._\/-]+$/.test(ref) &&
!ref.includes('..') &&
!ref.startsWith('-') &&
!ref.startsWith('/') &&
!ref.endsWith('/');

// Optional companion pin. Read it from the command line only.
const companionToken = /(?:^|\s)context-mill:(\S+)/;
const companion = body.split(/\r?\n/)[0].match(companionToken);
const contextMillRef = companion ? companion[1] : null;

if (contextMillRef && !isPlausibleRef(contextMillRef)) {
core.setFailed(
'The context-mill ref is not a valid git ref. Use ' +
'`/wizard-ci-snapshots <app> context-mill:<branch>`.'
);
return;
}

// Remove the pin so the positional ref cannot capture it.
const args = contextMillRef ? body.replace(companionToken, ' ') : body;

// /wizard-ci-snapshots <app> [wizard_ref] [context-mill:<ref>] —
// wizard_ref defaults to this PR's branch.
const m = args.match(/\/wizard-ci-snapshots\s+(\S+)(?:\s+(\S+))?/);
const app = m ? m[1] : 'basic-integration/javascript-node/express-todo';
const wizardRef = m && m[2] ? m[2] : pr.data.head.ref;

// The positional ref also comes from the comment. Check it too.
const wizardRefArg = m && m[2] ? m[2] : null;
if (wizardRefArg && !isPlausibleRef(wizardRefArg)) {
core.setFailed(
'The wizard ref is not a valid git ref. Use ' +
'`/wizard-ci-snapshots <app> <wizard-branch>`.'
);
return;
}
const wizardRef = wizardRefArg || pr.data.head.ref;

await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
Expand All @@ -282,10 +361,13 @@ jobs:
source_pr_number: context.payload.issue.number,
source_pr_url: pr.data.html_url,
wizard_ref: wizardRef,
// Omit the key when unpinned. The workbench then uses `main`.
...(contextMillRef ? { context_mill_ref: contextMillRef } : {}),
notify_slack: 'false',
notify_pr: 'true',
snapshots: true
}
});

console.log(`Triggered snapshots for app: ${app} (ref ${wizardRef})`);
const pinNote = contextMillRef ? `, context-mill ref ${contextMillRef}` : '';
console.log(`Triggered snapshots for app: ${app} (ref ${wizardRef}${pinNote})`);
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
# Changelog

## [2.70.0](https://github.com/PostHog/wizard/compare/v2.69.0...v2.70.0) (2026-08-31)


### Features

* **privacy:** disclose the dependency scan and let users decline sharing ([#1099](https://github.com/PostHog/wizard/issues/1099)) ([dc65104](https://github.com/PostHog/wizard/commit/dc65104f55a4b5012c91fff0788f0b21627dbab8))

## [2.69.0](https://github.com/PostHog/wizard/compare/v2.68.0...v2.69.0) (2026-08-28)


### Features

* **gateway:** tag events with the gateway edition and fail hollow runs ([#1165](https://github.com/PostHog/wizard/issues/1165)) ([db0c556](https://github.com/PostHog/wizard/commit/db0c556fedd1cc2bf012e28e369d5b22b9ebb482))


### Bug Fixes

* **gateway:** route v2 openai calls over the responses api ([#1166](https://github.com/PostHog/wizard/issues/1166)) ([a8c3ef1](https://github.com/PostHog/wizard/commit/a8c3ef1f21a764f0189b93b27591cc0cd1ef4d56))

## [2.68.0](https://github.com/PostHog/wizard/compare/v2.67.0...v2.68.0) (2026-08-28)


### Features

* **e2e:** let the e2e harness drive the warehouse flow ([#1152](https://github.com/PostHog/wizard/issues/1152)) ([0b87fc8](https://github.com/PostHog/wizard/commit/0b87fc8f97fa76ab9f9de39b9c9f848fc0e39b84))
* **gateway:** use server-minted scoped tokens for model calls ([#1130](https://github.com/PostHog/wizard/issues/1130)) ([dc32339](https://github.com/PostHog/wizard/commit/dc32339cdfaf7aa8064de88d504acf9092c58750))
* **telemetry:** stamp wizard_ask outcome events with the ask subject ([#1162](https://github.com/PostHog/wizard/issues/1162)) ([d1cce49](https://github.com/PostHog/wizard/commit/d1cce49244e9462861220c03d317858cf95bef63))
* **wizard-ci:** let a PR comment pin the context-mill ref ([#1153](https://github.com/PostHog/wizard/issues/1153)) ([b494975](https://github.com/PostHog/wizard/commit/b4949755db48c9f10484634446f69b8d05edf424))


### Bug Fixes

* **warehouse-e2e:** stop the secret rule refusing every credential question ([#1161](https://github.com/PostHog/wizard/issues/1161)) ([f0e024d](https://github.com/PostHog/wizard/commit/f0e024d301dcdb53587e06fbf51e01d1cd06e5b2))

## [2.67.0](https://github.com/PostHog/wizard/compare/v2.66.0...v2.67.0) (2026-08-27)


Expand Down
45 changes: 44 additions & 1 deletion e2e-harness/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ e2e-harness/
wizard-ci-driver.ts WizardCiDriver — read_state / perform_action over the store
action-registry.ts screen → the actions legal on it (+ NO_ACTION_SCREENS)
e2e-profile.ts WizardE2eProfile + decideE2eAction — the scripted walk policy
profiles.ts per-program profiles + profileFor(programId)
profiles.ts per-program profiles + profileFor(programId) + resolveE2eProfile(env)
e2e-result.ts the E2E_RESULT_JSON payload: E2eRunRecorder + buildE2eResult
tui-capture.ts run a command in a PTY (node-pty) + read its real screen (@xterm/headless)
scripts/
tui-host.no-jest.ts the real-TUI host: startTUI + WizardCiDriver, MODE=fixed | serve
Expand Down Expand Up @@ -78,6 +79,48 @@ the program's entry (typed by `WizardE2eProfile`); the host asks
`decideE2eAction(state, profile)` what to commit on each screen. The (screen →
decision) trace is snapshot-tested offline in `__tests__/` (`jest -u` to update).

## Driving the agent-in-the-loop layer

Two decision points ask a person to act, and the harness stands in for them.

`wizard_ask` overlay. A `ci` session normally has no ask bridge at all. The host
sets `session.e2eAsk` from `E2E_ASK=true`, which keeps the bridge wired (see
`shouldDisableAsk`). The profile then answers every question in the batch:
`askAnswers` routes a question to a value, else the first option, else the `'e2e'`
sentinel. Route credentials with `${ENV_VAR}` values, never literals.

Mark a credential rule `"secret": true`. The skill names its own questions, so a
rule matches text the agent controls — without the flag, a question called
`password` takes the value, and by omitting `sensitive: true` gets it back
unvaulted in plaintext. A secret rule answers only free text flagged sensitive
and refuses every other question shape; refusals land in the payload's
`refusedIds` / `refusedAsks`, so a run that withholds a credential says so
instead of looking like an ordinary sentinel.

Task-notice overlay. `profile.notice` decides `keep` or `decline`. `E2E_NOTICE`
overrides it per run.

Both env inputs are folded into the profile by `resolveE2eProfile`, at load.
`decideE2eAction` must stay pure — it reads no env and no store.

## The result payload

A `MODE=fixed` run writes `E2E_RESULT_JSON`: the run phase and screens walked,
plus every ask batch, every task notice, the task list, the detected warehouse
sources, the program's report file, and the abort reason. `e2e-result.ts` builds
it.

**Only question prompt text and question ids go in the payload. No answer value
ever does.** The decision function reports ids and a keep/decline verdict, so the
recorder never holds an answer. Keep it that way — the workbench scans the file
for its own injected credentials.

The report file is the one payload field whose *content* comes from the app
directory, which is a checkout the run does not control. `readReportFile` reads
only a regular file inside `appDir` — never a symlink, and never through a
symlinked parent — so a committed `posthog-warehouse-report.md` pointing at a
host file cannot copy it into the payload.

## Visual-regression snapshots (the workbench flow)

[wizard-workbench](https://github.com/PostHog/wizard-workbench) runs the CI route
Expand Down
Loading
Loading