Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion docs/concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Python coverage is experimental and degraded-honest. Graph-backed structure, unt

## Findings

Findings are named signals such as `typescript.type_errors`, `rust.source_hygiene`, `python.syntax`, `python.source-hygiene`, `python.types`, `python.import-graph`, `python.dead-code`, `python.relevant-tests`, and `coverage.unsupported_stacks`. Counts come from validation diagnostics, graph evidence when available, optional-tool degradation, and repository census data. Opcore does not blend them into a single rating.
Findings are named signals such as `typescript.type_errors`, `rust.source_hygiene`, `python.syntax`, `python.source-hygiene`, `python.types`, `python.import-graph`, `python.dead-code`, `python.relevant-tests`, `python.pytest`, and `coverage.unsupported_stacks`. Counts come from validation diagnostics, graph evidence when available, optional-tool degradation, and repository census data. Opcore does not blend them into a single rating.

## Artifacts

Expand Down
27 changes: 27 additions & 0 deletions packages/asp-provider/src/mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,33 @@ function validationEvidence(result: ValidationResult, selectedIds: readonly stri
});
continue;
}
if (run.capability === "pytest") {
evidence.push({
kind: "python_validation_capability_run",
message: `Python pytest capability evidence for ${run.projectRoot ?? run.checkId}.`,
data: {
capability: run.capability,
checkId: run.checkId,
activation: run.activation,
outcome: run.outcome,
message: run.message,
...(run.projectKey === undefined ? {} : { projectKey: run.projectKey }),
...(run.projectRoot === undefined ? {} : { projectRoot: run.projectRoot }),
...(run.configFile === undefined ? {} : { configFile: run.configFile }),
...(run.afterStateFingerprint === undefined ? {} : { afterStateFingerprint: run.afterStateFingerprint }),
...(run.targetCount === undefined ? {} : { targetCount: run.targetCount }),
...(run.candidatePaths === undefined ? {} : { candidatePaths: [...run.candidatePaths] }),
...(run.collectedNodeIds === undefined ? {} : { collectedNodeIds: [...run.collectedNodeIds] }),
...(run.selectionMode === undefined ? {} : { selectionMode: run.selectionMode }),
...(run.selectionDigest === undefined ? {} : { selectionDigest: run.selectionDigest }),
...(run.counts === undefined ? {} : { counts: { ...run.counts } }),
...(run.collection === undefined ? {} : { collection: { ...run.collection } }),
...(run.execution === undefined ? {} : { execution: { ...run.execution } }),
...(run.cleanup === undefined ? {} : { cleanup: { ...run.cleanup } })
}
});
continue;
}
evidence.push({
kind: "python_validation_capability_run",
message: `Python ${run.capability} capability evidence for ${run.checkId}.`,
Expand Down
4 changes: 4 additions & 0 deletions packages/contracts/schemas/opcore-contracts.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3371,6 +3371,10 @@
"type": "array",
"items": { "$ref": "#/$defs/PythonProjectContext" }
},
"pythonCapabilityRuns": {
"type": "array",
"items": { "$ref": "#/$defs/PythonValidationCapabilityRun" }
},
"manifest": {
"type": "object",
"additionalProperties": false,
Expand Down
269 changes: 262 additions & 7 deletions packages/contracts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export type PythonValidationAuthoritySource = (typeof pythonValidationAuthorityS
export const pythonValidationCapabilityTerminationKinds = ["exited", "timeout", "signal", "spawn_error"] as const;
export type PythonValidationCapabilityTerminationKind = (typeof pythonValidationCapabilityTerminationKinds)[number];

export const pythonValidationCapabilities = ["types", "ruff_lint", "ruff_format"] as const;
export const pythonValidationCapabilities = ["types", "ruff_lint", "ruff_format", "pytest"] as const;
export type PythonValidationCapability = (typeof pythonValidationCapabilities)[number];

export const pythonValidationCapabilityStates = [
Expand Down Expand Up @@ -1409,7 +1409,8 @@ export interface PythonRuffValidationCapabilityRun {

export type PythonValidationCapabilityRun =
| PythonTypesValidationCapabilityRun
| PythonRuffValidationCapabilityRun;
| PythonRuffValidationCapabilityRun
| PythonPytestValidationCapabilityRun;

export interface PythonValidationCapabilityInvocation {
argv: readonly string[];
Expand Down Expand Up @@ -1445,6 +1446,70 @@ export interface ValidationResultManifest {
skippedChecks?: readonly ValidationSkippedCheck[];
}

export const pythonCapabilityActivations = ["enabled", "disabled", "not_applicable"] as const;
export type PythonCapabilityActivation = (typeof pythonCapabilityActivations)[number];

export const pythonPytestSelectionModes = ["none", "direct_argv", "manifest"] as const;
export type PythonPytestSelectionMode = (typeof pythonPytestSelectionModes)[number];

export const pythonCapabilityProcessTerminations = ["exited", "timeout", "signal", "spawn_error", "overflow"] as const;
export type PythonCapabilityProcessTermination = (typeof pythonCapabilityProcessTerminations)[number];

export interface PythonCapabilityCounts {
candidateCount: number;
collectedCount: number;
executedCount: number;
passedCount: number;
failedCount: number;
skippedCount: number;
xfailedCount: number;
xpassedCount: number;
errorCount: number;
}

export interface PythonCapabilityCleanupEvidence {
attempted: boolean;
ok: boolean;
failureMessage?: string;
}

export interface PythonCapabilityInvocation {
stage: "collection" | "execution";
command: string;
argsDigest: string;
argCount: number;
selectionMode: PythonPytestSelectionMode;
selectionDigest?: string;
durationMs: number;
termination: PythonCapabilityProcessTermination;
exitCode?: number;
signal?: string;
outputBytes: number;
stdoutDigest?: string;
stderrDigest?: string;
}

export interface PythonPytestValidationCapabilityRun {
capability: "pytest";
checkId: string;
activation: PythonCapabilityActivation;
outcome: string;
message: string;
projectKey?: string;
projectRoot?: string;
configFile?: string;
targetCount?: number;
candidatePaths?: readonly string[];
collectedNodeIds?: readonly string[];
afterStateFingerprint?: string;
selectionMode?: PythonPytestSelectionMode;
selectionDigest?: string;
counts?: PythonCapabilityCounts;
collection?: PythonCapabilityInvocation;
execution?: PythonCapabilityInvocation;
cleanup?: PythonCapabilityCleanupEvidence;
}

export interface ValidationResult {
ok: boolean;
status: ValidationResultStatus;
Expand Down Expand Up @@ -6744,9 +6809,9 @@ export function validateValidationResultPayload(result: ValidationResult): Valid
export function validatePythonValidationCapabilityRun(
run: PythonValidationCapabilityRun
): PythonValidationCapabilityRun {
return run.capability === "types"
? validatePythonTypesValidationCapabilityRun(run)
: validatePythonRuffValidationCapabilityRun(run);
if (run.capability === "types") return validatePythonTypesValidationCapabilityRun(run);
if (run.capability === "pytest") return validatePythonPytestValidationCapabilityRun(run);
return validatePythonRuffValidationCapabilityRun(run);
}

function validatePythonTypesValidationCapabilityRun(
Expand Down Expand Up @@ -6891,6 +6956,130 @@ export function validatePythonValidationCapabilityRuns(
return runs;
}

function validatePythonPytestValidationCapabilityRun(
run: PythonPytestValidationCapabilityRun
): PythonPytestValidationCapabilityRun {
if (!run || typeof run !== "object") throw new Error("Python pytest capability run is required");
validateExactObjectKeys(run, [
"capability",
"checkId",
"activation",
"outcome",
"message",
"projectKey",
"projectRoot",
"configFile",
"targetCount",
"candidatePaths",
"collectedNodeIds",
"afterStateFingerprint",
"selectionMode",
"selectionDigest",
"counts",
"collection",
"execution",
"cleanup"
], "Python pytest capability run");
if (run.capability !== "pytest" || run.checkId !== "python.pytest") {
throw new Error("Python pytest capability run must describe python.pytest");
}
if (!includesString(pythonCapabilityActivations, run.activation)) {
throw new Error(`Unknown Python pytest capability activation: ${String(run.activation)}`);
}
validateNonEmptyString(run.outcome, "Python pytest capability run outcome");
validateNonEmptyString(run.message, "Python pytest capability run message");
if (run.projectKey !== undefined) validateSha256Identity(run.projectKey, "Python pytest capability run projectKey");
if (run.projectRoot !== undefined) validatePythonProjectRoot(run.projectRoot, "Python pytest capability run projectRoot");
if (run.configFile !== undefined) validateRepoRelativePath(run.configFile);
if (run.targetCount !== undefined) validateNonNegativeInteger(run.targetCount, "Python pytest capability run targetCount");
if (run.candidatePaths !== undefined) validateRepoPathArray(run.candidatePaths, "Python pytest capability run candidatePaths");
if (run.collectedNodeIds !== undefined) {
validateStringArray(run.collectedNodeIds, "Python pytest capability run collectedNodeIds", { allowEmpty: true });
}
if (run.afterStateFingerprint !== undefined) {
validateSha256Identity(run.afterStateFingerprint, "Python pytest capability run afterStateFingerprint");
}
if (run.selectionMode !== undefined && !includesString(pythonPytestSelectionModes, run.selectionMode)) {
throw new Error(`Unknown Python pytest capability selection mode: ${String(run.selectionMode)}`);
}
if (run.selectionDigest !== undefined) {
validateSha256Identity(run.selectionDigest, "Python pytest capability run selectionDigest");
}
if (run.counts !== undefined) validatePythonPytestCapabilityCounts(run.counts);
if (run.collection !== undefined) validatePythonPytestCapabilityInvocation(run.collection, "Python pytest capability collection");
if (run.execution !== undefined) validatePythonPytestCapabilityInvocation(run.execution, "Python pytest capability execution");
if (run.cleanup !== undefined) validatePythonPytestCapabilityCleanupEvidence(run.cleanup);
return run;
}

function validatePythonPytestCapabilityCounts(counts: PythonCapabilityCounts): void {
if (!counts || typeof counts !== "object") throw new Error("Python pytest capability counts are required");
validateExactObjectKeys(counts, [
"candidateCount",
"collectedCount",
"executedCount",
"passedCount",
"failedCount",
"skippedCount",
"xfailedCount",
"xpassedCount",
"errorCount"
], "Python pytest capability counts");
for (const key of Object.keys(counts) as (keyof PythonCapabilityCounts)[]) {
validateNonNegativeInteger(counts[key], `Python pytest capability counts ${key}`);
}
}

function validatePythonPytestCapabilityInvocation(invocation: PythonCapabilityInvocation, label: string): void {
if (!invocation || typeof invocation !== "object") throw new Error(`${label} is required`);
validateExactObjectKeys(invocation, [
"stage",
"command",
"argsDigest",
"argCount",
"selectionMode",
"selectionDigest",
"durationMs",
"termination",
"exitCode",
"signal",
"outputBytes",
"stdoutDigest",
"stderrDigest"
], label);
if (!includesString(["collection", "execution"] as const, invocation.stage)) {
throw new Error(`${label} stage must be collection or execution`);
}
validateNonEmptyString(invocation.command, `${label} command`);
validateSha256Identity(invocation.argsDigest, `${label} argsDigest`);
validateNonNegativeInteger(invocation.argCount, `${label} argCount`);
if (!includesString(pythonPytestSelectionModes, invocation.selectionMode)) {
throw new Error(`Unknown ${label} selectionMode: ${String(invocation.selectionMode)}`);
}
if (invocation.selectionDigest !== undefined) {
validateSha256Identity(invocation.selectionDigest, `${label} selectionDigest`);
}
validateNonNegativeNumber(invocation.durationMs, `${label} durationMs`);
if (!includesString(pythonCapabilityProcessTerminations, invocation.termination)) {
throw new Error(`Unknown ${label} termination: ${String(invocation.termination)}`);
}
if (invocation.exitCode !== undefined) validateNonNegativeInteger(invocation.exitCode, `${label} exitCode`);
if (invocation.signal !== undefined) validateNonEmptyString(invocation.signal, `${label} signal`);
validateNonNegativeInteger(invocation.outputBytes, `${label} outputBytes`);
if (invocation.stdoutDigest !== undefined) validateSha256Identity(invocation.stdoutDigest, `${label} stdoutDigest`);
if (invocation.stderrDigest !== undefined) validateSha256Identity(invocation.stderrDigest, `${label} stderrDigest`);
}

function validatePythonPytestCapabilityCleanupEvidence(cleanup: PythonCapabilityCleanupEvidence): void {
if (!cleanup || typeof cleanup !== "object") throw new Error("Python pytest capability cleanup evidence is required");
validateExactObjectKeys(cleanup, ["attempted", "ok", "failureMessage"], "Python pytest capability cleanup evidence");
if (typeof cleanup.attempted !== "boolean") throw new Error("Python pytest capability cleanup attempted must be boolean");
if (typeof cleanup.ok !== "boolean") throw new Error("Python pytest capability cleanup ok must be boolean");
if (cleanup.failureMessage !== undefined) {
validateNonEmptyString(cleanup.failureMessage, "Python pytest capability cleanup failureMessage");
}
}

function validatePythonValidationCapabilityTool(
tool: PythonValidationCapabilityToolProvenance,
run: PythonTypesValidationCapabilityRun
Expand Down Expand Up @@ -7099,6 +7288,72 @@ export function validatePythonProjectContexts(contexts: readonly PythonProjectCo
return contexts;
}

function validatePythonCapabilityCounts(counts: PythonCapabilityCounts): void {
if (!counts || typeof counts !== "object") throw new Error("Python capability counts are required");
validateExactObjectKeys(counts, [
"candidateCount",
"collectedCount",
"executedCount",
"passedCount",
"failedCount",
"skippedCount",
"xfailedCount",
"xpassedCount",
"errorCount"
], "Python capability counts");
for (const key of Object.keys(counts) as (keyof PythonCapabilityCounts)[]) {
validateNonNegativeInteger(counts[key], `Python capability counts ${key}`);
}
}

function validatePythonCapabilityCleanupEvidence(cleanup: PythonCapabilityCleanupEvidence): void {
if (!cleanup || typeof cleanup !== "object") throw new Error("Python capability cleanup evidence is required");
validateExactObjectKeys(cleanup, ["attempted", "ok", "failureMessage"], "Python capability cleanup evidence");
if (typeof cleanup.attempted !== "boolean") throw new Error("Python capability cleanup attempted must be boolean");
if (typeof cleanup.ok !== "boolean") throw new Error("Python capability cleanup ok must be boolean");
if (cleanup.failureMessage !== undefined) {
validateNonEmptyString(cleanup.failureMessage, "Python capability cleanup failureMessage");
}
}

function validatePythonCapabilityInvocation(invocation: PythonCapabilityInvocation, label: string): void {
if (!invocation || typeof invocation !== "object") throw new Error(`${label} is required`);
validateExactObjectKeys(invocation, [
"stage",
"command",
"argsDigest",
"argCount",
"selectionMode",
"selectionDigest",
"durationMs",
"termination",
"exitCode",
"signal",
"outputBytes",
"stdoutDigest",
"stderrDigest"
], label);
if (!includesString(["collection", "execution"] as const, invocation.stage)) {
throw new Error(`${label} stage must be collection or execution`);
}
validateNonEmptyString(invocation.command, `${label} command`);
validateSha256Identity(invocation.argsDigest, `${label} argsDigest`);
validateNonNegativeInteger(invocation.argCount, `${label} argCount`);
if (!includesString(pythonPytestSelectionModes, invocation.selectionMode)) {
throw new Error(`Unknown ${label} selectionMode: ${String(invocation.selectionMode)}`);
}
if (invocation.selectionDigest !== undefined) validateSha256Identity(invocation.selectionDigest, `${label} selectionDigest`);
validateNonNegativeInteger(invocation.durationMs, `${label} durationMs`);
if (!includesString(pythonCapabilityProcessTerminations, invocation.termination)) {
throw new Error(`Unknown ${label} termination: ${String(invocation.termination)}`);
}
if (invocation.exitCode !== undefined) validateNonNegativeInteger(invocation.exitCode, `${label} exitCode`);
if (invocation.signal !== undefined) validateNonEmptyString(invocation.signal, `${label} signal`);
validateNonNegativeInteger(invocation.outputBytes, `${label} outputBytes`);
if (invocation.stdoutDigest !== undefined) validateSha256Identity(invocation.stdoutDigest, `${label} stdoutDigest`);
if (invocation.stderrDigest !== undefined) validateSha256Identity(invocation.stderrDigest, `${label} stderrDigest`);
}

function validatePythonProjectTarget(target: PythonProjectTarget): void {
if (!target || typeof target !== "object") throw new Error("Python project targetRuntime is required");
validateExactObjectKeys(
Expand Down Expand Up @@ -7732,8 +7987,8 @@ function validatePythonRuffValidationCapabilityRun(
}
if (run.schemaVersion !== 1) throw new Error("Python validation capability run schemaVersion must be 1");
validateValidationCheckId(run.checkId, "Python validation capability run checkId");
if (!includesString(pythonValidationCapabilities, run.capability)) {
throw new Error(`Unknown Python validation capability: ${String(run.capability)}`);
if (!includesString(["ruff_lint", "ruff_format"] as const, run.capability)) {
throw new Error(`Unknown Python Ruff validation capability: ${String(run.capability)}`);
}
if (!includesString(pythonValidationCapabilityStates, run.state)) {
throw new Error(`Unknown Python validation capability state: ${String(run.state)}`);
Expand Down
3 changes: 2 additions & 1 deletion packages/fixtures/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,8 @@ export const conformanceFixtureMetadata = [
"python.types",
"python.import-graph",
"python.dead-code",
"python.relevant-tests"
"python.relevant-tests",
"python.pytest"
],
degradedTools: ["mypy", "pyright", "ruff", "pytest"]
}
Expand Down
2 changes: 2 additions & 0 deletions packages/fixtures/validation-python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
`mypy-authority/` is the real-tool proof repository for `python.types`. It combines strict mypy configuration, a configured plugin, a stub-only dependency, and a namespace package. Source and configuration resources retain a `.fixture` suffix on disk so Opcore does not discover them as live repository inputs; the proof script strips that suffix in its isolated workspace. `scripts/check-python-mypy-authority.mjs` runs pinned mypy over both clean and hypothetical/materialized after-states and verifies portable manifest identity plus cleanup.

`pyright-authority/` is the real-tool Pyright proof repository. It covers JSONC, recursive extends, include/exclude/ignore, extra paths, strict execution environments, namespace/src/stub layouts, hypothetical/materialized equivalence, portable receipts, packed Opcore execution, and cleanup with pinned Pyright 1.1.411.

`pytest-authority/` contains opt-in real-tool pytest authority fixtures for pass, failure, collection failure, timeout, manifest fallback, ambient-plugin isolation, and workspace isolation. Synthetic source, test, and packaging files retain a `.fixture` suffix so repository validation and provenance checks do not treat them as live product source; the authority proof materializes canonical filenames in an isolated workspace.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[project]
name = "pytest-authority-collection-fail"
version = "0.0.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def answer() -> int:
return 42
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
Loading
Loading