402 lines
22 KiB
Markdown
402 lines
22 KiB
Markdown
# Implementation Plan: Spec 405 - JSON-to-JSONB Data-layer Hardening
|
|
|
|
**Branch**: `405-json-to-jsonb-data-layer-hardening` | **Date**: 2026-06-23 | **Spec**: `specs/405-json-to-jsonb-data-layer-hardening/spec.md`
|
|
**Input**: Feature specification from `specs/405-json-to-jsonb-data-layer-hardening/spec.md`
|
|
|
|
## Summary
|
|
|
|
Prepare and later implement a database-hardening slice that inventories every live PostgreSQL `json` and `jsonb` column, classifies all `json` columns, converts only appropriate queryable trust-layer payload columns to `jsonb`, adds only query-backed indexes, proves semantic payload preservation, and records local plus staging-like validation. The implementation must not add product behavior, UI surfaces, broad abstractions, normalized replacement tables, or speculative indexes.
|
|
|
|
## Technical Context
|
|
|
|
**Language/Version**: PHP 8.4.15, Laravel 12.52.0, Filament 5.2.1, Livewire 4.1.4.
|
|
**Primary Dependencies**: Laravel migrations/schema builder, PostgreSQL, Pest 4, Filament/Livewire test helpers where focused browser or rendered-surface proof is needed.
|
|
**Storage**: PostgreSQL via Sail/Dokploy; target change is existing column type conversion from `json` to `jsonb` where classified `CONVERT`.
|
|
**Testing**: Pest 4, PostgreSQL lane, targeted feature tests, focused browser smoke.
|
|
**Validation Lanes**: pgsql, confidence/feature, focused browser, optional profiling/explain for new indexes.
|
|
**Target Platform**: TenantPilot Laravel monolith under `apps/platform`; Spec Kit artifacts under `specs/405-json-to-jsonb-data-layer-hardening/`.
|
|
**Project Type**: Laravel web application plus Spec Kit workflow.
|
|
**Performance Goals**: Avoid speculative indexes; each new index must have an existing query path and bounded write-overhead rationale.
|
|
**Constraints**: No new product concepts, UI surfaces, Graph calls, authorization model, lifecycle behavior, normalized replacement tables, provider semantics, broad abstractions, or completed-spec rewrites.
|
|
**Scale/Scope**: All live PostgreSQL `json` and `jsonb` columns; conversion limited to selected existing columns with proof.
|
|
|
|
## Repository Truth And Initial Signals
|
|
|
|
Laravel Boost confirmed PostgreSQL and current packages:
|
|
|
|
```text
|
|
PHP 8.4.15
|
|
Laravel 12.52.0
|
|
Filament 5.2.1
|
|
Livewire 4.1.4
|
|
Pest 4.3.1
|
|
PostgreSQL
|
|
```
|
|
|
|
Laravel 12 migrations support `$table->jsonb('column')`; implementation should use Laravel migrations where possible and raw PostgreSQL `ALTER TABLE ... ALTER COLUMN ... TYPE jsonb USING ...::jsonb` where column type alteration requires explicit SQL.
|
|
|
|
Live PostgreSQL schema inspection on 2026-06-23 found active `json` columns requiring classification in:
|
|
|
|
```text
|
|
alert_deliveries.payload
|
|
alert_rules.tenant_allowlist
|
|
audit_logs.metadata
|
|
backup_items.assignments
|
|
backup_items.metadata
|
|
backup_items.payload
|
|
backup_schedules.days_of_week
|
|
backup_schedules.policy_types
|
|
backup_sets.metadata
|
|
managed_environment_onboarding_sessions.state
|
|
managed_environment_permissions.details
|
|
managed_environments.metadata
|
|
managed_environments.rbac_canary_results
|
|
managed_environments.rbac_last_warnings
|
|
policies.metadata
|
|
policy_versions.assignments
|
|
policy_versions.metadata
|
|
policy_versions.scope_tags
|
|
policy_versions.secret_fingerprints
|
|
policy_versions.snapshot
|
|
restore_runs.group_mapping
|
|
restore_runs.metadata
|
|
restore_runs.preview
|
|
restore_runs.requested_items
|
|
restore_runs.results
|
|
tenant_settings.value
|
|
workspace_settings.value
|
|
```
|
|
|
|
Newer trust-layer paths already use `jsonb`, including `operation_runs.summary_counts`, `operation_runs.failure_summary`, `operation_runs.context`, baseline `scope_jsonb`/`summary_jsonb`/`meta_jsonb`, evidence summaries, findings evidence, review pack summaries/options, stored report payloads, provider connection metadata/scopes, and review publication resolution payloads.
|
|
|
|
## Technical Approach
|
|
|
|
1. Record branch, HEAD, dirty state, and `git diff --check`.
|
|
2. Query PostgreSQL `information_schema.columns` for all `json` and `jsonb` columns and collect row counts, null counts, defaults, indexes, and constraints.
|
|
3. Map each column to model casts, factories/fixtures, query usages, Filament/rendered usage, tests, and sensitive-data boundaries.
|
|
4. Classify each column as `CONVERT`, `KEEP_JSON`, `ALREADY_JSONB`, `DEPRECATED`, or `DECISION_REQUIRED`.
|
|
5. Write one or more focused migrations converting only `CONVERT` columns.
|
|
6. Add only query-backed JSONB indexes with explicit proof and rollback/drop strategy.
|
|
7. Update casts or query code only if required by tests after conversion.
|
|
8. Add PostgreSQL/feature tests proving type conversion, semantic preservation, model read/write behavior, scope/authorization boundaries, and representative domain regressions.
|
|
9. Run focused browser proof over existing payload-backed surfaces.
|
|
10. Produce `implementation-report.md` with the inventory matrix, validation results, remaining findings, staging status, and final gate result.
|
|
|
|
## Likely Affected Repository Surfaces
|
|
|
|
Preparation identifies likely inspection and implementation surfaces; implementation must verify exact paths before editing.
|
|
|
|
```text
|
|
apps/platform/database/migrations/
|
|
apps/platform/app/Models/
|
|
apps/platform/database/factories/
|
|
apps/platform/tests/
|
|
apps/platform/app/Support/
|
|
apps/platform/app/Services/
|
|
apps/platform/app/Jobs/
|
|
apps/platform/app/Filament/
|
|
apps/platform/resources/views/
|
|
specs/405-json-to-jsonb-data-layer-hardening/implementation-report.md
|
|
```
|
|
|
|
Likely model/cast inspection targets include:
|
|
|
|
```text
|
|
App\Models\AlertDelivery
|
|
App\Models\AlertRule
|
|
App\Models\AuditLog
|
|
App\Models\BackupItem
|
|
App\Models\BackupSchedule
|
|
App\Models\BackupSet
|
|
App\Models\ManagedEnvironment
|
|
App\Models\ManagedEnvironmentOnboardingSession
|
|
App\Models\ManagedEnvironmentPermission
|
|
App\Models\Policy
|
|
App\Models\PolicyVersion
|
|
App\Models\RestoreRun
|
|
App\Models\TenantSetting
|
|
App\Models\WorkspaceSetting
|
|
```
|
|
|
|
## UI / Surface Guardrail Plan
|
|
|
|
- **Guardrail scope**: backend data-layer hardening with focused rendered regression proof.
|
|
- **Affected routes/pages/actions/states/navigation/panel/provider surfaces**: none changed by implementation unless the spec is updated first.
|
|
- **No-impact class**: backend-only schema/storage conversion.
|
|
- **Native vs custom classification summary**: N/A.
|
|
- **Shared-family relevance**: evidence/report viewers, provider readiness, OperationRun proof, backup/restore proof, and audit metadata are regression consumers only.
|
|
- **State layers in scope**: persistence and existing rendered state regression only.
|
|
- **Audience modes in scope**: operator/MSP and customer/read-only only for regression proof; no disclosure behavior changes.
|
|
- **Decision/diagnostic/raw hierarchy plan**: unchanged; raw payloads remain technical/audit detail.
|
|
- **Raw/support gating plan**: unchanged.
|
|
- **One-primary-action / duplicate-truth control**: unchanged.
|
|
- **Handling modes by drift class or surface**: report-only unless conversion causes a confirmed regression, then minimal in-scope fix.
|
|
- **Repository-signal treatment**: review-mandatory for changed database/runtime paths.
|
|
- **Special surface test profiles**: backend storage conversion plus focused browser proof.
|
|
- **Required tests or manual smoke**: PostgreSQL migration tests, feature regressions, focused browser smoke.
|
|
- **Exception path and spread control**: any required UI edit is out of scope and must stop implementation for spec/plan update.
|
|
- **Active feature PR close-out entry**: Guardrail / database hardening.
|
|
- **UI/Productization coverage decision**: No UI surface impact.
|
|
- **Coverage artifacts to update**: none unless implementation unexpectedly changes rendered UI; then stop and update spec/plan first.
|
|
- **No-impact rationale**: storage type conversion and proof only.
|
|
- **Navigation / Filament provider-panel handling**: no panel/provider changes.
|
|
- **Screenshot or page-report need**: focused browser proof may produce screenshots/logs as evidence; no page report required without UI changes.
|
|
|
|
## Product Surface Contract Plan
|
|
|
|
- **Product Surface Contract reference**: `docs/product/standards/product-surface-contract.md` as regression lens only.
|
|
- **No-legacy posture**: canonical replacement; no compatibility shims or dual-write paths.
|
|
- **Page archetype and surface budget plan**: N/A for changed code; browser proof names existing inspected archetypes.
|
|
- **Technical Annex and deep-link demotion plan**: unchanged; conversion must not expose raw payloads, internal IDs, OperationRun links, or evidence deep links by default.
|
|
- **Canonical status vocabulary plan**: unchanged.
|
|
- **Product Surface exceptions**: none.
|
|
- **Browser verification plan**: focused existing-surface regression proof required.
|
|
- **Human Product Sanity plan**: final implementation report confirms unchanged trust semantics.
|
|
- **Visible complexity outcome target**: neutral.
|
|
- **Implementation report target**: `specs/405-json-to-jsonb-data-layer-hardening/implementation-report.md`.
|
|
|
|
## Filament / Livewire / Deployment Posture
|
|
|
|
- **Livewire v4 compliance**: Livewire 4.1.4 confirmed; no Livewire code change planned.
|
|
- **Panel provider registration location**: Laravel 12 panel providers remain in `apps/platform/bootstrap/providers.php`; no panel change.
|
|
- **Global search posture**: no resource global search posture changed. If implementation touches a resource unexpectedly, verify View/Edit/global search safety or stop for spec update.
|
|
- **Destructive/high-impact action posture**: no actions added or changed. Existing destructive/high-impact action proof from Specs 401-404 must not regress.
|
|
- **Asset strategy**: no assets, no `FilamentAsset` registration, no new `filament:assets` requirement beyond existing deployment baseline.
|
|
- **Testing plan**: database/migration tests, feature/domain regressions, focused browser proof for existing payload-backed surfaces.
|
|
- **Deployment impact**: migrations and staging/Dokploy validation. No env vars, queues, scheduler, storage, assets, routes, provider scopes, or panel providers planned.
|
|
|
|
## Shared Pattern & System Fit
|
|
|
|
- **Cross-cutting feature marker**: yes at data storage level.
|
|
- **Systems touched**: database schema, existing casts, existing query paths, tests, implementation report.
|
|
- **Shared abstractions reused**: Laravel migrations, existing models/casts, existing scoped query paths, existing tests/browser fixtures.
|
|
- **New abstraction introduced? why?**: none planned.
|
|
- **Why the existing abstraction was sufficient or insufficient**: existing models and services already own payload meaning; storage conversion does not require a new runtime layer.
|
|
- **Bounded deviation / spread control**: any new helper must be justified as a narrow test/support helper, not a runtime framework.
|
|
|
|
## OperationRun UX Impact
|
|
|
|
- **Touches OperationRun start/completion/link UX?**: no.
|
|
- **Central contract reused**: N/A.
|
|
- **Delegated UX behaviors**: N/A.
|
|
- **Surface-owned behavior kept local**: N/A.
|
|
- **Queued DB-notification policy**: N/A.
|
|
- **Terminal notification path**: N/A.
|
|
- **Exception path**: none.
|
|
|
|
## Provider Boundary & Portability Fit
|
|
|
|
- **Shared provider/platform boundary touched?**: storage inspection only.
|
|
- **Provider-owned seams**: provider raw/permission/readiness payload keys remain unchanged.
|
|
- **Platform-core seams**: storage type, workspace/managed-environment scope, audit/report/evidence ownership, migration safety.
|
|
- **Neutral platform terms / contracts preserved**: workspace, managed environment, provider, connection, operation, evidence, report, backup, restore.
|
|
- **Retained provider-specific semantics and why**: existing Microsoft/Intune payload keys remain provider-owned payload content.
|
|
- **Bounded extraction or follow-up path**: none for this spec.
|
|
|
|
## Domain / Model Implications
|
|
|
|
- No new model, table, persisted artifact, enum, status family, route, action, provider type, or source of truth is introduced.
|
|
- Existing model casts may remain unchanged because Laravel treats `json` and `jsonb` similarly at the application layer; implementation must update casts only if tests prove current behavior breaks.
|
|
- Existing raw payload keys and product meaning remain unchanged.
|
|
- Columns with unclear ownership or product semantics must be classified `DECISION_REQUIRED`, not converted by assumption.
|
|
|
|
## Data / Migration Implications
|
|
|
|
Migration pattern for direct conversions:
|
|
|
|
```sql
|
|
ALTER TABLE table_name
|
|
ALTER COLUMN column_name TYPE jsonb
|
|
USING column_name::jsonb;
|
|
```
|
|
|
|
Rollback pattern where feasible:
|
|
|
|
```sql
|
|
ALTER TABLE table_name
|
|
ALTER COLUMN column_name TYPE json
|
|
USING column_name::json;
|
|
```
|
|
|
|
Implementation must preserve:
|
|
|
|
- nullable state
|
|
- defaults
|
|
- constraints
|
|
- existing indexes unless intentionally replaced
|
|
- row counts
|
|
- application read/write behavior
|
|
|
|
Implementation must document rollback limitations:
|
|
|
|
- `jsonb` normalizes key order
|
|
- duplicate JSON object keys may be normalized
|
|
- semantic content must remain preserved, but textual representation may differ
|
|
|
|
## Index Strategy
|
|
|
|
Allowed only with existing query proof:
|
|
|
|
- GIN index for existing containment/query usage.
|
|
- Expression index for existing frequently queried JSONB key.
|
|
- Partial index for existing filtered JSONB key access.
|
|
|
|
Rejected:
|
|
|
|
- index every converted column
|
|
- index because `jsonb` supports it
|
|
- index for future lifecycle/export/dashboard guesses
|
|
- index without write-overhead note
|
|
|
|
Each index requires a row in the implementation report:
|
|
|
|
```text
|
|
Index | Table/Column | Query Path | Reason | Expected Benefit | Write Overhead Risk | Proof
|
|
```
|
|
|
|
## RBAC / Security / Audit Implications
|
|
|
|
- No RBAC behavior changes.
|
|
- Any changed query involving payload keys must retain existing workspace and managed-environment scoping.
|
|
- Non-member and wrong-scope access must remain deny-as-not-found where applicable.
|
|
- Customer-safe report/review/evidence paths must not expose raw payloads because of conversion or debug output.
|
|
- Reports, logs, screenshots, and test fixtures must not include secrets, tokens, raw credential payloads, sensitive raw provider payloads, or customer-sensitive raw payloads.
|
|
- Audit metadata conversion must preserve actor/context fields.
|
|
|
|
## OperationRun / Evidence / Result Truth Implications
|
|
|
|
The plan distinguishes:
|
|
|
|
- **Execution truth**: existing `OperationRun` status/outcome/summary/context; live columns are already `jsonb`, but regression proof still checks rendering.
|
|
- **Artifact truth**: `ReviewPack`, `StoredReport`, evidence snapshots/items, backup sets/items.
|
|
- **Backup/snapshot truth**: policy versions, backup payloads, baseline/evidence payloads.
|
|
- **Recovery/evidence truth**: restore previews/results, evidence currentness, report receipts.
|
|
- **Operator next action**: unchanged existing UI states and actions.
|
|
|
|
## Test Strategy
|
|
|
|
Required test groups:
|
|
|
|
1. PostgreSQL schema/type tests for every converted column.
|
|
2. Migration semantic preservation tests for representative non-sensitive payloads.
|
|
3. Model cast/read-write tests where converted columns are written by Eloquent models.
|
|
4. Query-path tests for any changed JSON key query and any new index.
|
|
5. Evidence/currentness regression tests for converted evidence/report/review payloads.
|
|
6. OperationRun/audit regression tests where summary/context/audit metadata is in scope.
|
|
7. Provider readiness/freshness/permission regression tests where provider/environment payloads are converted.
|
|
8. Backup/restore payload regression tests for backup items/sets/schedules and restore preview/results.
|
|
9. Review/report receipt regression tests for review pack/stored report/customer output.
|
|
10. Authorization/scope tests for changed payload queries.
|
|
11. Focused browser smoke for representative existing payload-backed pages.
|
|
|
|
Preferred validation commands:
|
|
|
|
```bash
|
|
cd apps/platform && ./vendor/bin/sail php vendor/bin/pest -c phpunit.pgsql.xml --filter=Spec405
|
|
cd apps/platform && ./vendor/bin/sail artisan test --filter=Evidence
|
|
cd apps/platform && ./vendor/bin/sail artisan test --filter=OperationRun
|
|
cd apps/platform && ./vendor/bin/sail artisan test --filter=Provider
|
|
cd apps/platform && ./vendor/bin/sail artisan test --filter=Backup
|
|
cd apps/platform && ./vendor/bin/sail artisan test --filter=Restore
|
|
cd apps/platform && ./vendor/bin/sail artisan test --filter=ReviewPack
|
|
cd apps/platform && ./vendor/bin/sail artisan test --filter=StoredReport
|
|
```
|
|
|
|
Use narrower commands where implementation creates focused Spec 405 tests.
|
|
|
|
## Rollout And Deployment Considerations
|
|
|
|
- Local: run through Laravel Sail against PostgreSQL.
|
|
- Staging: validate migration execution, app boot, queue/browser relevant proof, representative pages, and rollback/forward notes where safe.
|
|
- Production: do not claim production readiness unless staging-like validation passes or the final report explicitly records `PASS WITH CONDITIONS`.
|
|
- Migrations: assess table size/row count and lock risk before direct conversion.
|
|
- Env vars: none expected.
|
|
- Queues/scheduler/storage/assets: none expected, but affected flows may rely on existing workers/storage and must not regress.
|
|
- Dokploy: database migration and app boot validation required before production promotion.
|
|
|
|
## Risk Controls
|
|
|
|
- Stop before migration if inventory is incomplete.
|
|
- Stop before conversion if a high-risk column has unclear product ownership.
|
|
- Use semantic JSON comparisons, not raw string comparisons.
|
|
- Classify large-table conversion risk before direct `ALTER COLUMN`.
|
|
- Do not add speculative indexes.
|
|
- Do not print sensitive payload samples.
|
|
- Do not rewrite completed specs.
|
|
- If a conversion causes UI/rendered regression, fix the data-layer cause if bounded; otherwise stop for spec update.
|
|
|
|
## Constitution Check
|
|
|
|
- Inventory-first: PASS; inventory and source-of-truth classification precede conversion.
|
|
- Read/write separation: PASS; no Graph/write product behavior is added.
|
|
- Graph contract path: PASS; no Graph calls are added.
|
|
- Deterministic capabilities: N/A; no capability resolver changes.
|
|
- RBAC-UX: PASS; authorization must not change and changed payload queries require scope tests.
|
|
- Workspace isolation: PASS; scoped query proof required.
|
|
- Tenant/managed-environment isolation: PASS; managed-environment scope remains enforced.
|
|
- Run observability: N/A; no new OperationRun.
|
|
- OperationRun start UX: N/A.
|
|
- Ops-UX lifecycle/summary counts: no changes to lifecycle; current `operation_runs` JSONB posture is inspected.
|
|
- Data minimization: PASS; sensitive payloads are not dumped.
|
|
- Test governance: PASS; PostgreSQL and focused browser proof are explicit.
|
|
- Proportionality: PASS; storage conversion only, no new product truth.
|
|
- No premature abstraction: PASS; no new framework.
|
|
- Persisted truth: PASS; no new persisted entity/table.
|
|
- Behavioral state: PASS; no new state.
|
|
- UI semantics: PASS; no UI semantics added.
|
|
- Shared pattern first: PASS; existing models/services/tests are reused.
|
|
- Provider boundary: PASS; provider payload keys unchanged.
|
|
- V1 explicitness / few layers: PASS.
|
|
- Spec discipline / bloat check: PASS; one coherent data-layer hardening package.
|
|
- Product Surface Contract Gate: PASS as no rendered surface change plus focused regression proof.
|
|
|
|
## Test Governance Check
|
|
|
|
- **Test purpose / classification by changed surface**: PostgreSQL migration/type proof, feature/domain regression, focused browser smoke.
|
|
- **Affected validation lanes**: pgsql, confidence, browser.
|
|
- **Why this lane mix is the narrowest sufficient proof**: storage type is PostgreSQL-specific and payload-backed pages require rendered regression proof, but no full browser audit is needed.
|
|
- **Narrowest proving command(s)**: focused Spec 405 pgsql/feature/browser tests once created.
|
|
- **Fixture / helper / factory / seed / context cost risks**: minimal explicit fixtures only; no shared default broadening.
|
|
- **Expensive defaults or shared helper growth introduced?**: no planned.
|
|
- **Heavy-family additions, promotions, or visibility changes**: focused browser proof only.
|
|
- **Surface-class relief / special coverage rule**: no UI code changes; browser is regression proof.
|
|
- **Closing validation and reviewer handoff**: verify inventory coverage, migration proof, no sensitive payload output, no speculative indexes, and final gate result.
|
|
- **Budget / baseline / trend follow-up**: record if pgsql/browser runtime materially increases.
|
|
- **Review-stop questions**: Is every `json` column classified? Is every conversion justified? Does every new index have existing query proof? Did any UI or product behavior change? Was staging-like validation completed or properly conditioned?
|
|
- **Escalation path**: document-in-feature for bounded findings; follow-up-spec for unsafe conversion, online migration need, or unresolved product/schema decision.
|
|
- **Active feature PR close-out entry**: Guardrail / Database Hardening.
|
|
- **Why no dedicated follow-up spec is needed**: the slice is bounded unless implementation finds large-table online migration or unresolved product/schema ownership.
|
|
|
|
## Implementation Phases
|
|
|
|
### Phase 1 - Inventory and Classification
|
|
|
|
Build schema inventory from PostgreSQL, migrations, models, casts, factories, query paths, and tests. Produce the draft inventory matrix before any migration.
|
|
|
|
### Phase 2 - Conversion and Index Design
|
|
|
|
Write focused migration(s) only for `CONVERT` columns. Add no index unless tied to an existing query path. Keep rollback explicit.
|
|
|
|
### Phase 3 - Regression and Preservation Proof
|
|
|
|
Add PostgreSQL and feature tests proving column type, semantic preservation, read/write behavior, scope/authorization, and representative domain behavior.
|
|
|
|
### Phase 4 - Browser, Staging, and Report
|
|
|
|
Run focused browser proof, staging-like validation where available, final dirty-state checks, and complete the implementation report with gate result.
|
|
|
|
## Complexity Tracking
|
|
|
|
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
|
|-----------|------------|--------------------------------------|
|
|
| None planned | N/A | N/A |
|
|
|
|
## Proportionality Review
|
|
|
|
- **Current operator problem**: trust-layer payload storage is inconsistent and can undermine future evidence/report/lifecycle work.
|
|
- **Existing structure is insufficient because**: older `json` payload columns conflict with current PostgreSQL/jsonb posture and query/index expectations.
|
|
- **Narrowest correct implementation**: classify all columns, convert only selected existing columns, add only query-backed indexes, and prove behavior.
|
|
- **Ownership cost created**: migration review, PostgreSQL tests, focused browser proof, and implementation report.
|
|
- **Alternative intentionally rejected**: blanket conversion plus blanket indexes.
|
|
- **Release truth**: current-release data-layer readiness.
|