Repository navigation
feat(wizard): add run registry foundation #85921
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
Changes from all commits
47ad793
e27e4fd
943259c
4dd3a4d
777a261
ac2dc43
a494aca
455ff98
14d48f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| # Wizard registry | ||
|
|
||
| Status: design document for the Wizard run stack. | ||
| This layer ships only the data models and the registry parsing described under "Remote configuration". | ||
| The "API" routes and the "Cloud execution" workers describe planned behavior and do not exist yet; they land in later stack layers. | ||
|
|
||
| The Wizard Registry lists the programs a person can run in the Wizard. | ||
| Programs are personalized by the signed-in user's distinct ID and the organization that owns the selected project. | ||
|
|
||
| ## API | ||
|
|
||
| The registry endpoint returns a paginated list: | ||
|
|
||
| ```text | ||
| GET /api/projects/{project_id}/wizard/registry/?limit=100&offset=0 | ||
| ``` | ||
|
|
||
| Each program contains an ID, display metadata, a pinned Wizard version, Wizard command arguments, tags, prerequisite program IDs, and supported run environments. | ||
|
|
||
| Creating a run requires the selected `program_id`: | ||
|
|
||
| ```json | ||
| { | ||
| "program_id": "posthog-integration", | ||
| "environment": "local", | ||
| "workspace": { | ||
| "type": "local_folder", | ||
| "project_name": "example-project" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The backend resolves the ID from the personalized registry, checks that the program supports the requested environment, and stores the complete program definition on the run. | ||
| Clients cannot supply or change program metadata. | ||
| Run responses return the stored `program` snapshot. | ||
|
|
||
| ## Remote configuration | ||
|
|
||
| The registry is stored in the `wizard-program-registry` feature flag payload: | ||
|
|
||
| ```json | ||
| { | ||
| "version": 1, | ||
| "programs": [ | ||
| { | ||
| "id": "web-analytics-audit", | ||
| "name": "Web analytics audit", | ||
| "description": "Audit a project's web analytics setup", | ||
| "wizard_version": "2.60.0", | ||
| "command": ["audit", "web-analytics"], | ||
| "tags": ["audit", "web-analytics"], | ||
| "required_programs": ["posthog-integration"], | ||
| "supported_environments": ["local", "cloud"] | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| The entire payload is invalid when its version or shape is unsupported, program IDs are duplicated, an environment is unknown, or any program field is invalid. | ||
| Each program must include a `wizard_version` pinned to an exact release, such as `2.60.0`. | ||
| The registry payload rejects `latest`, so a program that omits `wizard_version` or sets it to `latest` makes the whole payload invalid. | ||
| A valid empty program list remains empty. | ||
|
|
||
| When the payload cannot be fetched or is invalid, the registry contains one `posthog-integration` program with local and cloud support. | ||
| Its command is empty, which keeps the Wizard package's default command. | ||
|
|
||
| ## Cloud execution | ||
|
|
||
| Cloud workers execute the program snapshot stored when the run was created. | ||
| They do not evaluate the registry again. | ||
| They run the pinned `wizard_version` from that snapshot, never a mutable tag such as `latest`. | ||
|
|
||
| Program command tokens are positional command names. | ||
| They appear after `@posthog/wizard@{program.wizard_version}` and before worker-owned flags. | ||
| Option flags and shell syntax are rejected, and every accepted token is shell-quoted before execution. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| from datetime import timedelta | ||
|
|
||
| DEFAULT_WIZARD_VERSION = "2.67.0" | ||
| LATEST_WIZARD_VERSION = "latest" | ||
|
fercgomes marked this conversation as resolved.
Resolved
|
||
| WIZARD_REGISTRY_VERSION = 1 | ||
| WIZARD_RUN_DEADLINE = timedelta(minutes=85) | ||
| WIZARD_ERROR_CODE_MAX_LENGTH = 50 | ||
| WIZARD_ERROR_CODE_PATTERN = r"PHW_[A-Z][A-Z0-9_]*" | ||
| WIZARD_GIT_DIFF_CONTENT_TYPE = "text/x-diff; charset=utf-8" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You know, you hate AI comments, but some heredocs here would have been quite useful!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah my bad |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,9 +7,19 @@ | |
|
|
||
| from dataclasses import dataclass | ||
| from datetime import datetime, timedelta | ||
| from typing import Any | ||
| from typing import Any, Literal | ||
| from uuid import UUID | ||
|
|
||
| from .enums import RunPhase, TaskStatus | ||
| from posthog.dataclasses import frozen | ||
|
|
||
| from .enums import ( | ||
| WizardRunArtifactType, | ||
| WizardRunEnvironment, | ||
| WizardRunStage, | ||
| WizardRunStatus, | ||
| WizardSessionRunPhase, | ||
| WizardSessionTaskStatus, | ||
| ) | ||
|
|
||
| STALE_AFTER = timedelta(minutes=10) | ||
|
|
||
|
|
@@ -18,7 +28,7 @@ | |
| class WizardTaskDTO: | ||
| id: str | ||
| title: str | ||
| status: TaskStatus | ||
| status: WizardSessionTaskStatus | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
|
|
@@ -35,7 +45,7 @@ class WizardSessionDTO: | |
| workflow_id: str | ||
| skill_id: str | ||
| started_at: datetime | ||
| run_phase: RunPhase | ||
| run_phase: WizardSessionRunPhase | ||
| tasks: tuple[WizardTaskDTO, ...] | ||
| event_plan: dict[str, Any] | None | ||
| error: dict[str, Any] | None | ||
|
|
@@ -47,10 +57,6 @@ class WizardSessionDTO: | |
| is_stale: bool | ||
|
|
||
|
|
||
| class WizardSessionOwnershipError(Exception): | ||
| """Raised when an upsert would overwrite a session owned by a different user.""" | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class UpsertWizardSessionRequest: | ||
| """What the wizard CLI POSTs. team_id is derived from the URL, not the body.""" | ||
|
|
@@ -59,7 +65,7 @@ class UpsertWizardSessionRequest: | |
| workflow_id: str | ||
| skill_id: str | ||
| started_at: datetime | ||
| run_phase: RunPhase | ||
| run_phase: WizardSessionRunPhase | ||
| tasks: tuple[WizardTaskDTO, ...] | ||
| event_plan: dict[str, Any] | None = None | ||
| error: dict[str, Any] | None = None | ||
|
|
@@ -74,11 +80,138 @@ class UpsertWizardSessionInput: | |
| workflow_id: str | ||
| skill_id: str | ||
| started_at: datetime | ||
| run_phase: RunPhase | ||
| run_phase: WizardSessionRunPhase | ||
| tasks: tuple[WizardTaskDTO, ...] | ||
| event_plan: dict[str, Any] | None | ||
| error: dict[str, Any] | None | ||
| pending_input: dict[str, Any] | None | ||
| handoff_text: str | None = None | ||
| # Set on create only, never overwritten on later pushes for the same run. | ||
| created_by_id: int | None = None | ||
|
|
||
|
|
||
| class WizardSessionOwnershipError(Exception): | ||
| pass | ||
|
|
||
|
|
||
| @frozen | ||
| class LocalFolderWorkspace: | ||
| project_name: str | ||
| type: Literal["local_folder"] = "local_folder" | ||
|
|
||
|
|
||
| @frozen | ||
| class GitRepositoryWorkspace: | ||
| repository: str | ||
| type: Literal["git_repository"] = "git_repository" | ||
|
|
||
|
|
||
| type WizardWorkspace = LocalFolderWorkspace | GitRepositoryWorkspace | ||
|
Comment on lines
+97
to
+109
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't know what's more common in Python: having a base class you inherit from or this. Also, Github doesn't seem to handle the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
i don't think it matters honestly
can you elaborate? |
||
|
|
||
|
|
||
| @frozen | ||
| class WizardProgram: | ||
| id: str | ||
| name: str | ||
| description: str | ||
| wizard_version: str | ||
|
posthog[bot] marked this conversation as resolved.
Resolved
|
||
| command: tuple[str, ...] | ||
| tags: tuple[str, ...] | ||
| required_programs: tuple[str, ...] | ||
| supported_environments: tuple[WizardRunEnvironment, ...] | ||
|
|
||
|
|
||
| @frozen | ||
| class WizardRegistry: | ||
| programs: tuple[WizardProgram, ...] | ||
| version: Literal[1] = 1 | ||
|
|
||
|
|
||
| @frozen | ||
| class CreateWizardRunInput: | ||
| team_id: int | ||
| created_by_id: int | ||
| environment: WizardRunEnvironment | ||
| workspace: WizardWorkspace | ||
| program_id: str | ||
| wizard_version: str | None = None | ||
| idempotency_key: str | None = None | ||
|
|
||
|
|
||
| @frozen | ||
| class WizardRunCreationResult: | ||
| run: "WizardRunDTO" | ||
| created: bool | ||
|
|
||
|
|
||
| @frozen | ||
| class WizardRunDTO: | ||
| id: UUID | ||
| team_id: int | ||
| created_by_id: int | None | ||
| environment: WizardRunEnvironment | ||
| workspace: WizardWorkspace | ||
| program: WizardProgram | ||
| status: WizardRunStatus | ||
| error_code: str | None | ||
| error_message: str | None | ||
| stage: WizardRunStage | None | ||
| created_at: datetime | ||
| updated_at: datetime | None | ||
| started_at: datetime | None | ||
| finished_at: datetime | None | ||
| deadline_at: datetime | None | ||
|
|
||
|
|
||
| @frozen | ||
| class ListWizardRunsInput: | ||
| team_id: int | ||
| offset: int | ||
| limit: int | ||
|
|
||
|
|
||
| @frozen | ||
| class WizardRunPage: | ||
| results: tuple[WizardRunDTO, ...] | ||
| count: int | ||
|
|
||
|
|
||
| @frozen | ||
| class CreatePullRequestArtifactInput: | ||
| team_id: int | ||
| run_id: UUID | ||
| url: str | ||
| number: int | ||
| repository: str | ||
| head_branch: str | ||
| base_branch: str | ||
|
|
||
|
|
||
| @frozen | ||
| class WizardRunGitDiffArtifactDTO: | ||
| id: UUID | ||
| team_id: int | ||
| run_id: UUID | ||
| artifact_type: Literal[WizardRunArtifactType.GIT_DIFF] | ||
| size_bytes: int | ||
| content_hash: str | ||
| additions: int | None | ||
| removals: int | None | ||
| created_at: datetime | ||
|
|
||
|
|
||
| @frozen | ||
| class WizardRunPullRequestArtifactDTO: | ||
| id: UUID | ||
| team_id: int | ||
| run_id: UUID | ||
| artifact_type: Literal[WizardRunArtifactType.PULL_REQUEST] | ||
| url: str | ||
| number: int | ||
| repository: str | ||
| head_branch: str | ||
| base_branch: str | ||
| created_at: datetime | ||
|
|
||
|
|
||
| type WizardRunArtifactDTO = WizardRunGitDiffArtifactDTO | WizardRunPullRequestArtifactDTO | ||
Uh oh!
There was an error while loading. Please reload this page.