Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8813de5
test(onboard): align sandbox fixture contracts
prekshivyas Aug 27, 2026
e094695
Merge origin/main into codex/fix-post-10339-merge-test-contracts
prekshivyas Aug 27, 2026
1d09d73
test: tighten fixture contract coverage
prekshivyas Aug 27, 2026
fe82942
test: isolate shared fixture contract data
prekshivyas Aug 27, 2026
2268707
merge: resolve conflicts with main
github-actions[bot] Aug 27, 2026
1a089c3
merge: resolve conflicts with main
github-actions[bot] Aug 27, 2026
2b1e7b0
merge: resolve conflicts with main
github-actions[bot] Aug 27, 2026
a77b19a
test(e2e): close fast coverage gaps
prekshivyas Aug 27, 2026
70b7256
Merge remote-tracking branch 'origin/codex/fix-post-10339-merge-test-…
prekshivyas Aug 27, 2026
7bb095d
test(e2e): map latest messaging coverage
prekshivyas Aug 27, 2026
0038615
test(onboard): restore fixture identity contract
prekshivyas Aug 27, 2026
bbe7261
test(onboard): satisfy merged size guard
prekshivyas Aug 27, 2026
d2b1334
Merge branch 'main' into codex/fix-post-10339-merge-test-contracts
prekshivyas Aug 27, 2026
ad69d70
Merge branch 'main' into codex/fix-post-10339-merge-test-contracts
prekshivyas Aug 28, 2026
ba5790d
test(e2e): fix proof and fixture contracts
prekshivyas Aug 28, 2026
185d4db
test(onboarding): centralize sandbox fixture identity
prekshivyas Aug 28, 2026
dd6f7fb
test(e2e): enforce parity for live helpers
prekshivyas Aug 28, 2026
37bde88
test(e2e): secure Slack credential scan transport
prekshivyas Aug 28, 2026
91b223d
test(e2e): align provider proof claims
prekshivyas Aug 28, 2026
4873647
Merge branch 'main' into codex/fix-post-10339-merge-test-contracts
prekshivyas Aug 28, 2026
3127999
test(e2e): keep raw Slack tokens host-side
prekshivyas Aug 28, 2026
4c28a1b
merge: refresh from main
prekshivyas Aug 28, 2026
ee5334e
Merge branch 'main' into codex/fix-post-10339-merge-test-contracts
prekshivyas Aug 28, 2026
cf40569
merge: refresh from main
prekshivyas Aug 28, 2026
1c6aa63
fix(e2e): preserve retained Hermes Slack resources
prekshivyas Aug 28, 2026
9305eab
merge: refresh from main
prekshivyas Aug 28, 2026
6e1c0e6
fix(ci): accept SDK archive not required
prekshivyas Aug 28, 2026
9296c72
fix(e2e): enforce reviewer safety boundaries
prekshivyas Aug 28, 2026
8c70c34
Merge branch 'main' into codex/fix-post-10339-merge-test-contracts
cv Aug 28, 2026
fa83d12
merge: refresh from main
prekshivyas Aug 28, 2026
d043671
merge: resolve conflicts with main
github-actions[bot] Aug 28, 2026
9124d24
merge: reconcile upstream branch
prekshivyas Aug 28, 2026
6bcc994
test(e2e): remove unused Google Chat entrypoint
prekshivyas Aug 28, 2026
41e6b9b
test(e2e): consolidate channel lifecycle cleanup
prekshivyas Aug 28, 2026
bdd3a0f
test(e2e): fail closed on cleanup evidence
prekshivyas Aug 28, 2026
9efe608
chore: merge latest main
prekshivyas Aug 28, 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
85 changes: 78 additions & 7 deletions scripts/checks/e2e-mock-parity.mts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import { fileURLToPath } from "node:url";

import ts from "typescript";

import { moduleTagDeclarations } from "../../tools/e2e/module-tags.mts";

const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
export const DEFAULT_PARITY_MANIFEST = "test/e2e/mock-parity.json";

export type MockParityEntry = {
live: string;
liveSources?: string[];
fast?: string[];
liveOnlyReason?: string;
};
Expand All @@ -23,6 +26,7 @@ export type MockParityManifest = {
};

const LIVE_TEST = /^test\/e2e\/live\/.+\.test\.ts$/u;
const LIVE_HELPER = /^test\/e2e\/live\/(?!.*\.test\.ts$).+\.ts$/u;
const FAST_TESTS = [
/^src\/.+\.test\.ts$/u,
/^nemoclaw\/src\/.+\.test\.ts$/u,
Expand Down Expand Up @@ -50,7 +54,10 @@ function sourceTokens(source: string): string {
for (const child of children) visit(child);
};
visit(sourceFile);
return JSON.stringify(tokens);
return JSON.stringify({
moduleTags: moduleTagDeclarations(source).map(({ tag }) => tag),
tokens,
});
}

export function isMockParityRelevantSourceChange(
Expand Down Expand Up @@ -91,6 +98,7 @@ export function validateMockParity(options: {
}

const entries = new Map<string, MockParityEntry>();
const sourceOwners = new Map<string, MockParityEntry[]>();
for (const entry of manifest.entries) {
if (!entry || typeof entry !== "object" || typeof entry.live !== "string") {
errors.push("mock parity entries must be objects with a live path");
Expand All @@ -106,6 +114,14 @@ export function validateMockParity(options: {
}
entries.set(entry.live, entry);

if (
entry.liveSources !== undefined &&
(!Array.isArray(entry.liveSources) ||
entry.liveSources.some((file) => typeof file !== "string"))
) {
errors.push(`${entry.live}: liveSources must be an array of live E2E helper paths`);
continue;
}
if (
entry.fast !== undefined &&
(!Array.isArray(entry.fast) || entry.fast.some((file) => typeof file !== "string"))
Expand All @@ -126,6 +142,18 @@ export function validateMockParity(options: {
}

if (!fileExists(entry.live)) errors.push(`${entry.live}: live test does not exist`);
for (const sourceFile of new Set(entry.liveSources ?? [])) {
if (!isSafeRepoPath(sourceFile) || !LIVE_HELPER.test(sourceFile)) {
errors.push(`${entry.live}: ${sourceFile} is not a test/e2e/live/**/*.ts helper file`);
continue;
}
if (!fileExists(sourceFile)) {
errors.push(`${entry.live}: live E2E helper does not exist: ${sourceFile}`);
}
const owners = sourceOwners.get(sourceFile) ?? [];
owners.push(entry);
sourceOwners.set(sourceFile, owners);
}
for (const fastFile of new Set(fast)) {
if (!isFastPrTest(fastFile)) {
errors.push(`${entry.live}: ${fastFile} is not collected by a fast PR test project`);
Expand All @@ -135,10 +163,41 @@ export function validateMockParity(options: {
}
}

for (const liveFile of [...new Set(changedFiles)].filter((file) => LIVE_TEST.test(file))) {
if (!entries.has(liveFile)) {
const changedFileSet = new Set(changedFiles);
const requireChangedFastTest = (entry: MockParityEntry, changedSource: string): void => {
const mappedFastTests = Array.isArray(entry.fast)
? entry.fast.filter((fastFile): fastFile is string => typeof fastFile === "string")
: [];
if (
mappedFastTests.length > 0 &&
!mappedFastTests.some((fastFile) => changedFileSet.has(fastFile))
) {
errors.push(
changedSource === entry.live
? `${entry.live}: change at least one mapped fast PR test with the live E2E`
: `${changedSource}: change at least one fast PR test mapped from ${entry.live}`,
);
}
};

for (const liveFile of [...changedFileSet].filter((file) => LIVE_TEST.test(file))) {
const entry = entries.get(liveFile);
if (!entry) {
errors.push(`${liveFile}: changed live E2E needs an entry in ${DEFAULT_PARITY_MANIFEST}`);
continue;
}
requireChangedFastTest(entry, liveFile);
}

for (const helperFile of [...changedFileSet].filter((file) => LIVE_HELPER.test(file))) {
const owners = sourceOwners.get(helperFile) ?? [];
if (owners.length === 0) {
errors.push(
`${helperFile}: changed live E2E helper needs an owning entry in ${DEFAULT_PARITY_MANIFEST}`,
);
continue;
}
for (const owner of owners) requireChangedFastTest(owner, helperFile);
}

return errors.sort();
Expand All @@ -161,6 +220,18 @@ function sourceAtRef(ref: string, file: string): string | null {
}
}

/** Remove metadata-only live and fast test changes before parity validation. */
export function filterMockParityRelevantChangedFiles(
files: readonly string[],
sourceAtBase: (file: string) => string | null,
sourceAtHead: (file: string) => string | null,
): string[] {
return files.filter((file) => {
if (!LIVE_TEST.test(file) && !LIVE_HELPER.test(file) && !isFastPrTest(file)) return true;
return isMockParityRelevantSourceChange(sourceAtBase(file), sourceAtHead(file));
});
}

function changedFiles(base: string, head: string): string[] {
const files = execFileSync(
"git",
Expand All @@ -172,10 +243,10 @@ function changedFiles(base: string, head: string): string[] {
)
.split(/\r?\n/u)
.filter(Boolean);
return files.filter(
(file) =>
!LIVE_TEST.test(file) ||
isMockParityRelevantSourceChange(sourceAtRef(base, file), sourceAtRef(head, file)),
return filterMockParityRelevantChangedFiles(
files,
(file) => sourceAtRef(base, file),
(file) => sourceAtRef(head, file),
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/actions/sandbox/policy-channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ function withSandboxMutationLockUnlessPreview<T>(
* Internal composition dependencies for channel mutation.
*
* The Google Chat capability is intentionally absent from the public CLI
* composition. The live E2E entrypoint supplies it directly so environment
* composition. The channels stop/start live E2E helper supplies it directly so environment
* variables and predictable sandbox names cannot enable non-interactive
* audience enrollment in ordinary production execution.
*/
Expand Down
Loading
Loading