243 lines
15 KiB
Markdown
243 lines
15 KiB
Markdown
|
|
# Codex Plan vs Claude Plan - Comparison Analysis
|
||
|
|
|
||
|
|
**Date**: 2026-03-23
|
||
|
|
**Purpose**: Identify issues or strengths in the Codex plan that our Claude plan may have missed or should consider adopting.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. Agreement / Shared Ground
|
||
|
|
|
||
|
|
Both plans converge on the same high-level design, which is reassuring:
|
||
|
|
|
||
|
|
- Additive hybrid approach: keep legacy fixed-column field extractions, add new JSONB-backed custom metadata
|
||
|
|
- Schema-as-data stored as JSON Schema documents in JSONB
|
||
|
|
- Immutable schema versions (new row per revision, new ID)
|
||
|
|
- Nullable schema reference on the `documents` table
|
||
|
|
- Schema binding locks once metadata is written
|
||
|
|
- Application-layer JSON Schema validation (not DB-side `json_matches_schema()`)
|
||
|
|
- `DISTINCT ON` view for current metadata version
|
||
|
|
- No GIN index initially
|
||
|
|
- JSON Schema draft 2020-12
|
||
|
|
- Client-scoped schemas with cross-client isolation
|
||
|
|
- Version history for metadata records
|
||
|
|
|
||
|
|
These are the foundational decisions and both plans agree on all of them. No action needed.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. Issues Codex Raises That We Should Evaluate
|
||
|
|
|
||
|
|
### 2.1 DB Triggers for Critical Invariants (STRONG - Consider Adopting)
|
||
|
|
|
||
|
|
**What Codex says**: Two database triggers should enforce invariants:
|
||
|
|
1. Prevent `documents.metadataSchemaId` changes after any extraction exists (legacy or custom)
|
||
|
|
2. Validate `documents.clientId = documentMetadataSchemas.clientId` on insert/update
|
||
|
|
|
||
|
|
**Our plan**: Relies entirely on application-layer enforcement.
|
||
|
|
|
||
|
|
**Assessment**: This is the strongest point in the Codex plan that we missed. Application-layer enforcement is correct but fragile -- a future developer could bypass the service layer (e.g., direct SQLC query, migration script, admin fix-up query) and silently violate the invariant. DB triggers make the invariant durable regardless of code path.
|
||
|
|
|
||
|
|
**Risk of NOT adopting**: A data integrity bug that is invisible until documents have mismatched schemas. Hard to detect, hard to fix retroactively.
|
||
|
|
|
||
|
|
**Recommendation**: **Adopt.** Add both triggers. The cost is two small trigger functions in the migration. The benefit is defense-in-depth for the two most critical invariants (schema lock and client isolation). This aligns with our existing pattern of using DB constraints as safety nets alongside app-layer checks.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2.2 Three-State Schema Status Enum (MODERATE - Consider Adopting)
|
||
|
|
|
||
|
|
**What Codex says**: Uses a `metadataSchemaStatus` enum with three values: `active`, `superseded`, `retired`.
|
||
|
|
|
||
|
|
**Our plan**: Uses a boolean `is_active` (true/false).
|
||
|
|
|
||
|
|
**Assessment**: The three-state model captures a meaningful distinction:
|
||
|
|
- `superseded`: automatically set when a newer version is created -- the schema is "replaced" but not manually disabled
|
||
|
|
- `retired`: explicitly disabled by an admin -- different intent, possibly different business rules (e.g., retired schemas might warrant a warning in the UI)
|
||
|
|
|
||
|
|
With a boolean, both cases collapse to `is_active = false` and the system cannot distinguish "replaced by v2" from "admin decided this schema is bad."
|
||
|
|
|
||
|
|
**Risk of NOT adopting**: Minor. We can always add the distinction later. But it's cleaner to model it upfront since it costs nothing extra at the DB level and avoids a later migration.
|
||
|
|
|
||
|
|
**Recommendation**: **Consider adopting.** A three-state enum is a small change that gives us more precise semantics. If we keep the boolean, we should at minimum document that `is_active = false` covers both cases and note it as a known simplification.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2.3 `schemaHash` for Deduplication Detection (WEAK - Note But Skip)
|
||
|
|
|
||
|
|
**What Codex says**: Includes a `schemaHash text NOT NULL` column on the schema table.
|
||
|
|
|
||
|
|
**Our plan**: No hash column.
|
||
|
|
|
||
|
|
**Assessment**: Useful for detecting accidental duplicate submissions (same schema content submitted as a "new version"). Could also speed up equality checks. However, it adds complexity (what hash algorithm? is it normalized JSON before hashing?) and the use case is marginal for v1. An admin accidentally creating a duplicate version is a minor annoyance, not a data integrity issue.
|
||
|
|
|
||
|
|
**Recommendation**: **Skip for now.** Note it as a potential future addition. If duplicate detection becomes a real problem, we can add it without schema changes (just a new column + backfill).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2.4 `supersedesSchemaId` / Explicit Version Chain (WEAK - Skip)
|
||
|
|
|
||
|
|
**What Codex says**: Each schema row has a `supersedesSchemaId uuid REFERENCES documentMetadataSchemas(id)` creating an explicit linked-list chain of versions.
|
||
|
|
|
||
|
|
**Our plan**: Relies on `(client_id, name, version)` ordering to determine lineage. The `PUT` response includes `previousVersionId` but it's computed, not stored.
|
||
|
|
|
||
|
|
**Assessment**: Both approaches work. The explicit FK chain makes traversal trivial (`SELECT * FROM ... WHERE supersedesSchemaId = ?`) but `(name, version)` ordering is equally functional and simpler. An explicit chain also creates complexity around edge cases: what if a superseded schema is itself deleted? What if someone creates a revision of a superseded (not latest) version?
|
||
|
|
|
||
|
|
**Recommendation**: **Skip.** Our `(name, version)` ordering is simpler and sufficient. The lineage is already navigable via `ORDER BY version`.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2.5 `documentClass` Column (WEAK - Skip)
|
||
|
|
|
||
|
|
**What Codex says**: Includes a `documentClass text` column on the schema table for categorization.
|
||
|
|
|
||
|
|
**Our plan**: No document class column. The schema `name` and `description` serve the categorization role.
|
||
|
|
|
||
|
|
**Assessment**: This is a thin metadata field. It could be useful for filtering schemas by document type (e.g., "show me all schemas for aircraft engineering documents"). But `name` already serves this purpose, and adding a separate classification dimension without clear UI or API requirements is speculative.
|
||
|
|
|
||
|
|
**Recommendation**: **Skip.** Can be added later if a classification use case emerges. The schema `name` and `description` fields handle this for now.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2.6 Metadata Table Design: Two Tables vs One (MODERATE - Evaluate)
|
||
|
|
|
||
|
|
**What Codex says**: Uses two tables mirroring the existing legacy pattern:
|
||
|
|
- `documentMetadataExtractions` (the actual JSONB data)
|
||
|
|
- `documentMetadataExtractionVersions` (version tracking)
|
||
|
|
|
||
|
|
**Our plan**: Uses a single `document_custom_metadata` table with a `version` column combining both concerns.
|
||
|
|
|
||
|
|
**Assessment**: This is a tradeoff:
|
||
|
|
|
||
|
|
| Aspect | Two-table (Codex) | One-table (Ours) |
|
||
|
|
|--------|-------------------|------------------|
|
||
|
|
| Consistency with existing code | Matches legacy `documentFieldExtractions` + `documentFieldExtractionVersions` pattern | New pattern, different from legacy |
|
||
|
|
| Simplicity | More tables, more joins | Fewer tables, simpler queries |
|
||
|
|
| Row-level locking for version increment | Version rows can be locked independently of data rows | Must lock the last data row for version increment |
|
||
|
|
| Query complexity | Need JOIN for full data | Direct SELECT |
|
||
|
|
|
||
|
|
The existing codebase uses the two-table pattern for field extractions. Matching it means developers familiar with the legacy code can reason about the new code by analogy. Breaking from it means two different versioning patterns in the same codebase.
|
||
|
|
|
||
|
|
**Risk of NOT adopting**: Cognitive overhead for developers who expect the two-table pattern. Potential row-locking issues if we need to increment versions under concurrency (though our traffic patterns likely don't warrant this concern).
|
||
|
|
|
||
|
|
**Recommendation**: **Evaluate.** The consistency argument is real. However, our single-table approach is simpler and YAGNI applies -- we don't need the version table separation unless we have a concrete reason. If Q prefers consistency with the legacy pattern, we should switch. Otherwise, keep our simpler design and document why we deviated.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2.7 Mutual Exclusivity: Legacy OR Custom (MODERATE - Discuss with Q)
|
||
|
|
|
||
|
|
**What Codex says**: Strict mutual exclusivity:
|
||
|
|
- If `metadataSchemaId IS NULL`, only legacy `/field-extractions` endpoints work
|
||
|
|
- If `metadataSchemaId IS NOT NULL`, only custom `/field-extractions/metadata` endpoints work
|
||
|
|
- Assigning a schema to a document with legacy extractions returns `409 Conflict`
|
||
|
|
|
||
|
|
**Our plan**: A document can have BOTH static field extractions AND custom metadata. They are "independent and complementary."
|
||
|
|
|
||
|
|
**Assessment**: This is a fundamental design philosophy difference:
|
||
|
|
|
||
|
|
- **Codex (exclusive)**: Cleaner model, prevents confusion about which system is "authoritative" for a document. Simpler to reason about. But constrains flexibility -- what if a document needs both legacy fields AND custom fields during a transition period?
|
||
|
|
|
||
|
|
- **Ours (both allowed)**: More flexible. Allows gradual migration. But introduces ambiguity: if a document has both, which is the "real" metadata? Could lead to inconsistencies if different systems write to different stores for the same document.
|
||
|
|
|
||
|
|
**Risk of our approach**: A document could end up with conflicting data in legacy fields and custom metadata, with no clear source of truth. This is a data governance concern, not a technical one.
|
||
|
|
|
||
|
|
**Recommendation**: **Discuss with Q.** This is a business decision. If the expectation is that documents will gradually migrate from legacy to custom, allowing both may be needed during transition. If the expectation is a clean cut-over, mutual exclusivity is cleaner. Our plan should explicitly document whichever choice is made and why.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2.8 Route Structure: `/field-extractions/...` vs Split (MINOR)
|
||
|
|
|
||
|
|
**What Codex says**: All new endpoints under `/field-extractions/...` to stay aligned with existing Permit resource naming.
|
||
|
|
|
||
|
|
**Our plan**: Admin schemas under `/admin/custom-schemas`, metadata under `/custom-metadata`.
|
||
|
|
|
||
|
|
**Assessment**: Our split is intentional and defensible -- it separates admin-scoped operations from user-scoped operations, which maps cleanly to role-based access. The Codex approach keeps everything grouped under one resource family, which simplifies Permit configuration but mixes admin and user operations under the same prefix.
|
||
|
|
|
||
|
|
**Recommendation**: **Keep our approach.** The admin/user split is cleaner for RBAC. We already account for the Permit.io changes needed.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2.9 Schema Size Limit: 256KB vs 64KB (MINOR)
|
||
|
|
|
||
|
|
**What Codex says**: 256KB limit.
|
||
|
|
|
||
|
|
**Our plan**: 64KB limit with a tunable constant.
|
||
|
|
|
||
|
|
**Assessment**: Both are reasonable. 64KB is generous for a JSON Schema document (most schemas will be 1-5KB). 256KB is very generous. Since we made it a tunable constant (`MaxSchemaDefinitionBytes`), the exact value is easy to change.
|
||
|
|
|
||
|
|
**Recommendation**: **Keep 64KB.** It's more than enough, and a lower limit is a better default. If a client hits it, we bump the constant.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2.10 Schema Validation: `additionalProperties` Required (MINOR - Consider)
|
||
|
|
|
||
|
|
**What Codex says**: The server should reject schema creation unless `additionalProperties` is explicitly present at root level.
|
||
|
|
|
||
|
|
**Our plan**: `additionalProperties: false` is "recommended but not required."
|
||
|
|
|
||
|
|
**Assessment**: Requiring it prevents a common mistake -- submitting a schema without `additionalProperties`, which defaults to `true` in JSON Schema, meaning the schema accepts any extra fields. This is almost never what the client admin intends. Requiring explicit declaration forces a conscious choice.
|
||
|
|
|
||
|
|
**Recommendation**: **Consider adopting.** Requiring `additionalProperties` to be explicitly set (not necessarily `false`, just present) is a small validation rule that prevents a common gotcha. Low cost, moderate value.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2.11 Request Body Should Not Carry `schemaId` (ALREADY HANDLED)
|
||
|
|
|
||
|
|
**What Codex says**: The metadata write request body should not include `schemaId` -- the server derives it from the document's assigned schema.
|
||
|
|
|
||
|
|
**Our plan**: Same approach. The `POST /custom-metadata` request contains `documentId` and `metadata` but not `schemaId`. The response includes `schemaId` (server-derived).
|
||
|
|
|
||
|
|
**Assessment**: Both plans agree. No action needed.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2.12 Usage Stats in Schema Responses (MINOR - Already Handled Differently)
|
||
|
|
|
||
|
|
**What Codex says**: Schema detail response includes a `usage` block: `assignedDocumentCount`, `metadataRecordCount`, `canDelete`.
|
||
|
|
|
||
|
|
**Our plan**: Schema list/detail responses include `documentCount`.
|
||
|
|
|
||
|
|
**Assessment**: The Codex response is slightly richer (`canDelete` is convenient for UIs, `metadataRecordCount` distinguishes "assigned but no data yet" from "actively in use"). Our `documentCount` covers the main use case.
|
||
|
|
|
||
|
|
**Recommendation**: **Consider adding `canDelete`.** It's a simple boolean derived from the count and saves the client a mental step. `metadataRecordCount` is nice-to-have but not essential for v1.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. Things Our Plan Has That Codex Missed
|
||
|
|
|
||
|
|
### 3.1 Bulk Folder Assignment
|
||
|
|
|
||
|
|
Our plan includes `POST /admin/folders/{folderId}/assign-schema` with recursive subfolder traversal, skip reporting, and transactional safety. Codex has no bulk operations at all. This is a significant operational feature for large clients.
|
||
|
|
|
||
|
|
### 3.2 `client_admin` Role
|
||
|
|
|
||
|
|
Our plan defines a new `client_admin` role with clear permission boundaries. Codex mentions the need for Permit.io updates but doesn't define a new role or permission model.
|
||
|
|
|
||
|
|
### 3.3 Detailed Service Layer Design
|
||
|
|
|
||
|
|
Our plan provides Go code outlines for the service, validator, and controller layers. Codex stays at the SQL/API level and doesn't describe the Go implementation structure.
|
||
|
|
|
||
|
|
### 3.4 Sample Schemas / Documentation
|
||
|
|
|
||
|
|
Our plan includes comprehensive sample schemas covering all supported JSON Schema types. Codex doesn't address documentation.
|
||
|
|
|
||
|
|
### 3.5 More Granular Implementation Phases
|
||
|
|
|
||
|
|
Our plan has 11 phases with explicit dependency tracking. Codex has 4 broad phases.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. Summary: Recommended Actions
|
||
|
|
|
||
|
|
| # | Codex Issue | Strength | Action |
|
||
|
|
|---|------------|----------|--------|
|
||
|
|
| 2.1 | DB triggers for invariants | **STRONG** | **Adopt** -- add triggers for schema lock + client match |
|
||
|
|
| 2.2 | Three-state schema status | MODERATE | **Consider** -- enum is cleaner than boolean |
|
||
|
|
| 2.3 | `schemaHash` | Weak | Skip |
|
||
|
|
| 2.4 | `supersedesSchemaId` chain | Weak | Skip |
|
||
|
|
| 2.5 | `documentClass` column | Weak | Skip |
|
||
|
|
| 2.6 | Two-table metadata pattern | MODERATE | **Discuss with Q** -- consistency vs simplicity |
|
||
|
|
| 2.7 | Mutual exclusivity (legacy OR custom) | MODERATE | **Discuss with Q** -- business decision |
|
||
|
|
| 2.10 | Require `additionalProperties` present | Minor | **Consider** -- prevents common mistake |
|
||
|
|
| 2.12 | `canDelete` in responses | Minor | **Consider** -- small UX improvement |
|
||
|
|
|
||
|
|
**Bottom line**: The Codex plan is a competent alternative design that arrives at the same destination via slightly different choices. The one genuinely strong point we should adopt is **DB triggers for critical invariants** (2.1). The three moderate items (2.2, 2.6, 2.7) deserve a decision from Q. Everything else is either already covered in our plan or is a minor preference difference that doesn't materially affect the design.
|