Skip to content

feat(canvas): add secure application build pipeline#73727

Draft
k11kirky wants to merge 10 commits into
masterfrom
posthog-code/canvas-application-build-pipeline
Draft

feat(canvas): add secure application build pipeline#73727
k11kirky wants to merge 10 commits into
masterfrom
posthog-code/canvas-application-build-pipeline

Conversation

@k11kirky

@k11kirky k11kirky commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Problem

Canvas generation needs a secure and reproducible backend for arbitrary client-side browser applications, available from every task while preserving last-known-good canvases.

Changes

  • Add immutable source versions, diff-aware guarded edits, and authoritative cloud builds backed by private object storage.
  • Add deterministic React, Quill, HTML, Three.js, D3, ECharts, worker, WebAssembly, and binary-asset builds with signed delivery from a dedicated user-content origin.
  • Expose universal MCP tools and skills with automatic task-run attribution, conflict-safe publishing, retention, and runtime capability enforcement.

The client contracts, local validation, artifact host, and generation flow are implemented in PostHog/code#3823.

How did you test this code?

  • Full Python typecheck and focused builder/security tests
  • OpenAPI and MCP regeneration, MCP tests and typecheck, tool-name validation, skill lint, Tach, Ruff, and preflight checks
  • Clean isolated builder dependency audit and deterministic HTML, React/Quill, Three.js, package, worker, asset, and WebAssembly fixtures
  • PostgreSQL lifecycle, concurrency, provenance, retention, and tenant-scoping tests are included for CI

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

Added internal deployment and retention documentation for artifact origin, signing-key rotation, object storage, and future serverless scope.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

The implementation follows the approved four-phase canvas application architecture. Source and artifacts stay out of PostgreSQL, authoritative builds run without registry access or lifecycle scripts, artifacts require a dedicated user-content origin with short-lived signatures, and direct canvas egress remains denied until a user-facing capability approval flow exists.


Created with PostHog Code

Add normalized canvas source and build persistence, deterministic cloud validation and builds, signed user-content artifact delivery, universal agent tooling, and run-level attribution.

Generated-By: PostHog Code
Task-Id: f2d133f0-a9e6-4a16-8ecd-fc0a20bab2c3
@k11kirky k11kirky self-assigned this Jul 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Hey @k11kirky! 👋

It looks like your git author email on this PR isn't your @posthog.com address (k11kirky@gmail.com). Since you're on the PostHog team, it's worth pointing your local git author email at your @posthog.com address. Why it matters:

  • Consistent work identity in git history — internal tooling that attributes commits to team members keys off your @posthog.com address.
  • Keeps team contributions easy to tell apart from external community ones when scanning history.

You can fix it for this repo with:

git config user.email "you@posthog.com"

Or set it globally with git config --global user.email "you@posthog.com". No need to redo this PR — just a nudge for next time. 🙂

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

Bundle size — no change

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 64.44 MiB · no change

No file changed by more than 1000 B.

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.24 MiB · 22 files no change ███░░░░░░░ 27.6% of 4.51 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.08 MiB · 3,014 files no change ████████░░ 83.2% of 9.71 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
789 B src/scenes/ChunkLoadErrorBoundary.tsx
762 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
281.5 KiB ../node_modules/.pnpm/posthog-js@1.407.2/node_modules/posthog-js/dist/rrweb.js
267.7 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
236.0 KiB src/taxonomy/core-filter-definitions-by-group.json
226.1 KiB ../node_modules/.pnpm/posthog-js@1.407.2/node_modules/posthog-js/dist/module.js
154.3 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
106.2 KiB src/lib/api.ts
94.0 KiB ../packages/quill/packages/quill/dist/index.js
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

Toolbar bundle — eager 2.18 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.18 MiB · 17 files no change ████░░░░░░ 38.1% of 5.72 MiB
Deferred (lazy) 2.07 MiB · 33 files no change n/a — loads on demand
Loader dist/toolbar.js 1.1 KiB no change █░░░░░░░░░ 5.8% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
716.5 KiB dist/toolbar/toolbar-app-HMV4VZ5O.css
545.2 KiB dist/toolbar/chunk-chunk-3RADJBLD.js
484.3 KiB dist/toolbar/chunk-chunk-ZXJK34VQ.js
133.6 KiB dist/toolbar/chunk-chunk-6SQZIKIH.js
131.8 KiB dist/toolbar/chunk-chunk-T5KY5WYR.js
71.0 KiB dist/toolbar/toolbar-app-G6ARZY2E.js
69.0 KiB dist/toolbar/chunk-chunk-27JL52RE.js
35.6 KiB dist/toolbar/chunk-chunk-P4AKBHPC.js
20.9 KiB dist/toolbar/chunk-chunk-B7POBA4G.js
12.2 KiB dist/toolbar/chunk-chunk-PIK3PADE.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

Dist folder size — 🔺 +44.6 KiB (+0.0%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1355.92 MiB · 🔺 +44.6 KiB (+0.0%)

ℹ️ MCP UI apps size — 32 app(s), 17065.5 KB JS

Built size of each MCP UI app (main.js + styles.css).

App JS CSS
debug 599.5 KB 187.7 KB
action 457.8 KB 187.7 KB
action-list 564.3 KB 187.7 KB
cohort 456.8 KB 187.7 KB
cohort-list 563.3 KB 187.7 KB
email-template 456.6 KB 187.7 KB
error-details 472.4 KB 187.7 KB
error-issue 457.5 KB 187.7 KB
error-issue-list 564.2 KB 187.7 KB
experiment 561.5 KB 187.7 KB
experiment-list 565.1 KB 187.7 KB
experiment-results 563.2 KB 187.7 KB
feature-flag 567.1 KB 187.7 KB
feature-flag-list 570.9 KB 187.7 KB
feature-flag-testing 461.0 KB 187.7 KB
insight-actors 562.1 KB 187.7 KB
invite-email-preview 456.0 KB 187.7 KB
llm-costs 559.5 KB 187.7 KB
session-recording 458.6 KB 187.7 KB
session-summary 463.9 KB 187.7 KB
survey 458.4 KB 187.7 KB
survey-global-stats 562.2 KB 187.7 KB
survey-list 565.0 KB 187.7 KB
survey-stats 562.2 KB 187.7 KB
trace-span 457.2 KB 187.7 KB
trace-span-list 564.2 KB 187.7 KB
workflow 457.1 KB 187.7 KB
workflow-list 563.7 KB 187.7 KB
loops-review 461.2 KB 187.7 KB
query-results 745.5 KB 187.7 KB
render-ui 826.2 KB 187.7 KB
visual-review-snapshots 461.6 KB 187.7 KB
⚠️ MCP snapshots — 5 updated (0 modified, 5 added, 0 deleted)

Snapshots: MCP unit test snapshots updated

Changes: 5 snapshots (0 modified, 5 added, 0 deleted)

What this means:

  • Snapshots have been automatically updated to match current output

Next steps:

  • Review the changes to ensure they're intentional
  • If unexpected, investigate what caused the output to change

Review snapshot changes →

⚠️ Django migration SQL — 1 new migration to review

We've detected new migrations on this PR. Review the SQL output for each migration:

posthog/migrations/1265_canvas_application_builds.py

BEGIN;
--
-- Create model CanvasSourceVersion
--
CREATE TABLE "posthog_canvassourceversion" ("id" uuid NOT NULL PRIMARY KEY, "task_id" uuid NOT NULL, "task_run_id" uuid NOT NULL, "source_hash" varchar(64) NOT NULL, "source_object_key" varchar(500) NOT NULL, "source_size" integer NOT NULL CHECK ("source_size" >= 0), "prompt" text NULL, "created_by_id" bigint NULL, "created_at" timestamp with time zone NOT NULL, "canvas_id" uuid NOT NULL, "parent_version_id" uuid NULL, "team_id" integer NOT NULL);
--
-- Create model CanvasBuild
--
CREATE TABLE "posthog_canvasbuild" ("id" uuid NOT NULL PRIMARY KEY, "build_status" varchar(16) NOT NULL, "artifact_object_prefix" varchar(500) NULL, "integrity" varchar(100) NULL, "diagnostics" jsonb NOT NULL, "manifest" jsonb NULL, "pinned" boolean NOT NULL, "created_at" timestamp with time zone NOT NULL, "started_at" timestamp with time zone NULL, "completed_at" timestamp with time zone NULL, "canvas_id" uuid NOT NULL, "team_id" integer NOT NULL, "source_version_id" uuid NOT NULL);
--
-- Create model CanvasApplication
--
CREATE TABLE "posthog_canvasapplication" ("id" uuid NOT NULL PRIMARY KEY, "created_at" timestamp with time zone NOT NULL, "updated_at" timestamp with time zone NOT NULL, "canvas_id" uuid NOT NULL UNIQUE, "team_id" integer NOT NULL, "active_build_id" uuid NULL, "previous_build_id" uuid NULL, "current_source_version_id" uuid NULL);
--
-- Create index canvas_source_history_idx on field(s) team, canvas, created_at of model canvassourceversion
--
CREATE INDEX "canvas_source_history_idx" ON "posthog_canvassourceversion" ("team_id", "canvas_id", "created_at");
--
-- Create index canvas_source_hash_idx on field(s) team, source_hash of model canvassourceversion
--
CREATE INDEX "canvas_source_hash_idx" ON "posthog_canvassourceversion" ("team_id", "source_hash");
--
-- Create constraint canvas_source_unique_run on model canvassourceversion
--
ALTER TABLE "posthog_canvassourceversion" ADD CONSTRAINT "canvas_source_unique_run" UNIQUE ("canvas_id", "task_run_id");
--
-- Create index canvas_build_history_idx on field(s) team, canvas, created_at of model canvasbuild
--
CREATE INDEX "canvas_build_history_idx" ON "posthog_canvasbuild" ("team_id", "canvas_id", "created_at");
--
-- Create index canvas_build_retention_idx on field(s) build_status, completed_at of model canvasbuild
--
CREATE INDEX "canvas_build_retention_idx" ON "posthog_canvasbuild" ("build_status", "completed_at");
ALTER TABLE "posthog_canvassourceversion" ADD CONSTRAINT "posthog_canvassource_parent_version_id_6334ec9f_fk_posthog_c" FOREIGN KEY ("parent_version_id") REFERENCES "posthog_canvassourceversion" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "posthog_canvassourceversion_canvas_id_19a2a00f" ON "posthog_canvassourceversion" ("canvas_id");
CREATE INDEX "posthog_canvassourceversion_parent_version_id_6334ec9f" ON "posthog_canvassourceversion" ("parent_version_id");
CREATE INDEX "posthog_canvassourceversion_team_id_1945a64c" ON "posthog_canvassourceversion" ("team_id");
ALTER TABLE "posthog_canvasbuild" ADD CONSTRAINT "posthog_canvasbuild_source_version_id_bd4a4399_fk_posthog_c" FOREIGN KEY ("source_version_id") REFERENCES "posthog_canvassourceversion" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "posthog_canvasbuild_canvas_id_1de5b7b5" ON "posthog_canvasbuild" ("canvas_id");
CREATE INDEX "posthog_canvasbuild_team_id_72863759" ON "posthog_canvasbuild" ("team_id");
CREATE INDEX "posthog_canvasbuild_source_version_id_bd4a4399" ON "posthog_canvasbuild" ("source_version_id");
CREATE INDEX "posthog_canvasapplication_team_id_4ce7957e" ON "posthog_canvasapplication" ("team_id");
CREATE INDEX "posthog_canvasapplication_active_build_id_c95390f0" ON "posthog_canvasapplication" ("active_build_id");
CREATE INDEX "posthog_canvasapplication_previous_build_id_f7237016" ON "posthog_canvasapplication" ("previous_build_id");
CREATE INDEX "posthog_canvasapplication_current_source_version_id_bf914651" ON "posthog_canvasapplication" ("current_source_version_id");
COMMIT;

Last updated: 2026-07-26 10:28 UTC (0f590b2)

Django migration risk — migration analysis complete

We've analyzed your migrations for potential risks.

Summary: 1 Safe | 0 Needs Review | 0 Blocked

✅ Safe

Brief or no lock, backwards compatible

posthog.1265_canvas_application_builds
  └─ #1 ✅ CreateModel
     Creating new table is safe
     model: CanvasSourceVersion
  └─ #2 ✅ CreateModel
     Creating new table is safe
     model: CanvasBuild
  └─ #3 ✅ CreateModel
     Creating new table is safe
     model: CanvasApplication
  │
  └──> ℹ️  INFO:
       ℹ️  Skipped operations on newly created tables (empty tables
       don't cause lock contention).

Last updated: 2026-07-26 10:28 UTC (0f590b2)

ℹ️ Docs preview — preview build triggered

Docs from this PR will be published at posthog.com.

Project Preview Updated (UTC)
posthog.com Open preview Jul 26, 2026, 10:25 AM

The preview should be ready in about 10 minutes. Open the preview at /handbook/engineering/.

Hobby preview — passed

Hobby deployment smoke test passed successfully.


Run 30198223910

tests-posthog Bot and others added 9 commits July 26, 2026 08:56
Include the standalone builder in the Docker context, cover canvas models in IDOR rules, and enforce builder lint conventions.

Generated-By: PostHog Code
Task-Id: f2d133f0-a9e6-4a16-8ecd-fc0a20bab2c3
…line

Generated-By: PostHog Code
Task-Id: f2d133f0-a9e6-4a16-8ecd-fc0a20bab2c3
Generated-By: PostHog Code
Task-Id: f2d133f0-a9e6-4a16-8ecd-fc0a20bab2c3
Generated-By: PostHog Code
Task-Id: f2d133f0-a9e6-4a16-8ecd-fc0a20bab2c3
Generated-By: PostHog Code
Task-Id: f2d133f0-a9e6-4a16-8ecd-fc0a20bab2c3
Generated-By: PostHog Code
Task-Id: f2d133f0-a9e6-4a16-8ecd-fc0a20bab2c3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant