Lewati ke konten utama
Versi: v0.0.81

v0.0.74 — Data Policies + Webapp builder + theme cascade

Released: 2026-05-17. Forty-one commits.

A big batch. Two tracks dominate: Data Policies (Batch H) lands as a complete end-to-end feature across every read surface, and the Webapp asset type (renamed from analytics_app) gets a full Track A2 builder UI plus a per-tenant theme cascade.

Data Policies (Batch H)

A new governance subsystem: row-filter and column-mask policies authored centrally, enforced everywhere data flows out. Six slices land in this release.

Slice 1 — Registry

New honeyframe.data_policies table (row_filter / column_mask, scoped to org / project / dataset, JSONB predicate_model + subject_model, draft / active / disabled status). routers/data_policies.py mounted at /api/governance/data-policies under org.admin. New DataPoliciesPage at /data-policies groups policies by status with collapsible JSON preview.

Slice 1.5 — Create drawer + status flip

PATCH /api/governance/data-policies/{id}/status flips draft ↔ active ↔ disabled. Active flip records approved_by + approved_at as an audit anchor. New-policy drawer with name / description / type / scope / priority + JSON predicate/subject editors and per-card Activate / Disable / Re-draft buttons.

Slice 2 — Enforcement compiler + user-attribute resolver

services/user_attributes.py resolves attribute name → values (V1 wires site_idshoneyframe.user_sites). services/data_policy_compiler.py exposes compile_data_policy_filters() returning CompiledPolicies(predicates, warnings, bypassed_by_admin, considered_count). Handles four V1 operators (in_user_attribute, equals_user_attribute, equals_literal, in_group_mapping) + empty_behavior + admin bypass + subject targeting + unsafe-column rejection.

Slice 2b/2b-ext — Explore enforcement

dataset_explore compiles every active row_filter policy for the caller's org / project / dataset scope and AND-s the resulting predicates into both the COUNT and the data SELECT. The Postgres path and the DuckDB explore paths (UPLOADED_FILE + LAKEHOUSE) both enforce — viewers see consistent behavior regardless of backing engine. Compiler emits col = ANY(:bind) instead of col IN :bind so plain text() executes without bindparam(expanding=True).

Responses gain a policies metadata block alongside masking, so the frontend can render a "policies applied" chip.

Slice 2c — Simulator

POST /api/governance/data-policies/simulate compiles every active policy against an admin-supplied impersonated subject (user_id / role / groups) and returns predicates + warnings + bypassed_by_admin — pure compile path, no tenant SQL. DataPoliciesPage gets a per-card Simulate button.

Slice 2d — Data API row_scope

api_keys.row_scope JSONB is now read and AND-ed into every Data API SELECT. Service-account semantics — independent of any user-policy compilation, no inheritance from the issuing user. compile_api_key_row_scope(row_scope) supports equals_literal / in_literal / {and:[...]}; anything malformed denies all so a misconfigured key fails closed.

Slice 3 — Dashboard card enforcement

dashboards.execute_card wraps card SQL as a subquery and AND-s active row_filter predicates. Compiles at org+project scope only (dashboards run freeform SQL with no single owning dataset). Admin bypass leaves the SQL untouched. Predicate columns must appear in the inner SELECT's projection — silent drop would let admins write masking-via-projection bugs.

Slice 3b — Dashboard /ai-chat

/ai-chat ANSWER branch now goes through _apply_dashboard_policies so chat-driven SQL obeys row_filter policies the same way card execute does. services/chat_service.execute_chat_sql accepts an optional params dict.

Slice 4 — Agents + Cobuild

AgentContext gains user_role / user_groups / is_superadmin. _tool_run_sql_preview compiles row_filter policies and wraps the LLM-provided SQL as SELECT * FROM (...) AS _agent_pol WHERE <pred>. Cobuild planner forwards identity to all three call sites (/run, /resume, /run-parallel); the parallel helper passes user identity as kwargs so gathered child tasks compile inside their own AsyncSessions.

Slice 5 — Preview-as-user simulator

DataPoliciesPage gets a global Preview as user header button. SimulateDrawer accepts policy: DataPolicy | null; null flips on the scope inputs so an admin can compare scopes without hopping between policies.

Slice 6 — Audit trail + author column

data_policies.created_by_id + a data_policy_audit table land. _audit() writes one event row per created / status_flip / edited action, running in its own transaction so a missing audit table on un-migrated installs can't poison the user's PATCH. History button on every policy card opens an AuditDrawer with before/after diffs.

Structured editors + edit flow

The New-policy drawer's raw JSON textareas are replaced with operator-aware structured inputs for the 4 row_filter operators + the 3 column_mask types. Subject model gets role checkboxes + groups CSV + include_admins toggle. Advanced mode keeps the JSON textareas accessible. Compiled JSON preview is always visible under a collapsible.

PATCH /api/governance/data-policies/{id} edits name / description / priority / predicate_model / subject_model on draft | disabled policies. Refuses active policies (400 "disable first") so the compiler never reads a half-edited predicate. New PoliciesTab on dataset detail lists scoped policies (own + inherited org/project) with Edit / Activate / Disable + a Pin policy button that opens the drawer prescoped.

Webapp asset type (renamed from analytics_app)

Week 1 foundation

New webapp (originally analytics_app) project-scoped asset type composes existing Dashboard Builder cards into a custom left-rail + branded chrome shell. Backend: migration extends published_assets.asset_type CHECK constraint, routers/analytics_apps.py exposes GET /api/analytics-apps and GET /api/analytics-apps/:key. Frontend: types.ts (AnalyticsAppConfig, CardRefSpec, PresetSpec, AppTheme), api.ts, CardRef.tsx (single-card hydrator), AppShell.tsx, FilterBar.tsx, AnalyticsApp.tsx route handler at /apps/:appKey/:pageKey?.

Week 2 Track 1 — 5 Wave 1 card-type additions

Opt-in via card_config so existing dashboards are unchanged:

  • G1 — KPI hero / compact variants with accent_color, target_label_text, delta_label, pct_of_target_label. Hero cards with top accent bar + percent-of-target pill + delta indicator + target line.
  • G3reference_lines for dashed target overlays on line / area / bar charts.
  • G15column_thresholds per-column tiered RAG coloring + suffix. Legacy table_rules still works.
  • G21badge_columns renders cell value as a colored pill with hash-derived background. Branch codes get distinct, stable colors.
  • G25alert_variant on text card (info | warning | critical) + cta_label + cta_href. Text card becomes a colored banner with leading icon + CTA.

CardRef gains card_config_overrides on CardRefSpec, merged on top of the source dashboard card's config so a webapp can decorate cards without mutating the underlying dashboard. api.ts adds a module-level Promise cache for /api/dashboards/:did so N CardRefs from one source dashboard issue one network request.

Asset-type rename

analytics_appwebapp across DB, backend router, frontend module, and seed scripts. User-facing route stays at /apps/:appKey. /api/analytics-apps becomes /api/webapps. Migration reversible via the matching rollback SQL.

Per-tenant theme cascade (Track B)

AppTheme gains sidebarBackground / sidebarText / sidebarActiveBg / accentText; AppShell applies them via CSS vars so per-app palette cascades end-to-end. Hamburger toggle collapses sidebar to 64px icon-only (persisted in localStorage per appKey). NavItem.icon strings resolve through a new inline-SVG registry.

Header chrome: notification bell + red dot, theme-toggle stub, JWT-initial user avatar; gated by chrome.show_notifications / show_user_avatar. Optional clock pill (chrome.show_clock) ticks every 30s in chrome.timezone (default Asia/Jakarta). Optional bottom-right help ? FAB (chrome.show_help_fab).

FilterBar v2: card-style panel with date-range native inputs + preset chips (MTD / QTD / YTD / L30D / Last Month) + searchable multi-select branch dropdown with checkboxes + active-filter chip row + Reset button. A tenant's branch list seeds directly so the dropdown renders real entries.

FilterBar values wired into card parameters

Threads Period + branch selections through to dashboards.execute_card so cards whose SQL references :start, :end, :branch_id actually filter. A regex extracts :placeholder names from the card's SQL and forwards only the keys the SQL references. Caller params merge over policy params (the regex filter prevents collision in practice). New WebappFilterContext provider; writes debounced 250ms.

Webapp builder (Track A2)

The saas EditWebappPage is ported to paas as a sibling builder (WebappBuilderPage), following the per-asset-type builder convention. Slices:

  • Slice 1 — read-only edit page at /apps/_edit/:key proving the data path + preview render.
  • Slice 2 — drag/resize grid via react-grid-layout, hover-revealed drag handle + delete, Add card modal listing project dashboards, Save → PUT /api/publish/assets/{id} with current_version bump.
  • Slice 3card_config_overrides drawer with 8 form fields for the most-used override keys + raw JSON escape hatch + live preview.
  • Slice 4 — page CRUD (add via modal, inline rename, delete with confirm, reorder with ↑/↓ buttons). Mutations route through a new updateConfig() helper that touches config.pages and config.nav atomically.

The renderer also moves to packages/blocks/webapp/ (paas-first); both PaaS and SaaS now import from @hubstudio/blocks.

Multi-page nav + seed

Sidebar was a one-item rail. A seed can now emit many NavItem entries grouped under labelled sections. Each new nav entry has a matching AppPageSpec stub (empty card_refs + "Coming soon" footnote) so routes resolve and pages don't 404.

Scenarios — Schedules page sunset

Email-report scheduling is fully covered by Scenarios (cron trigger + send_email_report step). Existing tenants already run legacy_report_schedule_* scenarios doing exactly this, replacing the old /schedules surface. Nav entry under Automation removed; both /schedules routes redirect to the matching /scenarios path so existing bookmarks don't 404.

catalog_discovery.project_id is now Optional[int]; None scopes the search to every project the caller's org owns (powers F15 cross-project AI Search). Cobuild callers still pass an explicit project_id.

Infra fixes

  • nginx-test — SPA shell now sends no-cache, no-store, must-revalidate; hashed /assets/* get 1y immutable. Fixes the case where browsers held stale index.html referencing old Vite chunk hashes after a deploy.
  • nginx-test — explicit location /app/ → saas dist so the post-login redirect (/app/profiles) resolves instead of falling through to paas. Versioned as nginx-test.conf at the repo root.
  • nginxapp.example.com root re-pointed to the current frontend dist path. A stale path (removed in the 2026-04-27 cutover) was 500-ing every request via try_files rewrite cycle.
  • Webapp builder Back buttonnavigate(-1) jumped to wherever the user was before opening the editor. Now goes to /projects/<slug>/publish (the natural parent — where webapps are listed).