Vrida — Documentation Index

Session entry point. Read this after CLAUDE.md and before anything else. It maps the entire doc system, states what is authoritative vs. frozen, and tracks project-level status. For per-module detail (schema counts, pipeline stage, build order) see docs/modules/MODULE_INDEX.md.


A. Purpose

This file is the documentation source-of-truth index. It exists so that any session — human or AI — can orient in under two minutes: where each kind of truth lives, what the current project status is at a system level, and what to read next. It does not duplicate per-module data; that belongs in MODULE_INDEX.

Current Inventory status correction (2026-07-16 task run). Stock Transfer adds exactly 3 tables / 63 columns / 28 CHECKs / 20 FKs / 11 triggers. Inventory's current live shape is 29 base tables / 425 base-table columns / 1 view. The arithmetic is 26/362 before Transfer plus the ruled 3/63 addition. Transfer remains schema-only: no service, API, UI, worker, runtime role execution, or credential gateway is built. Current module records are in docs/modules/MODULE_INDEX.md; Inventory Core evidence is PROJECT_DECISIONS #75 and Stock Transfer evidence is PROJECT_DECISIONS #76.


B. Folder map

Folder Holds Status
docs/ai/ AI Capability Plane — governance controls (A-series), capabilities (B-series), governing rules (C-series), module-walk (D-series). The canonical contract for how AI behaves across every module. Also holds AI_CAPABILITY_GAPS.md, a point-in-time competitive gap analysis, not part of the canonical contract itself. authoritative (Plane); gaps doc is a dated snapshot
docs/architecture/ System-wide technical decisions and conventions: tenancy model, RLS, offline strategy, AI capacity/cost, schema naming rules. authoritative
docs/database/ Authoritative schema definitions — the tables, columns, types, constraints, and indexes of record. authoritative
docs/decisions/ Architecture Decision Records (ADRs). One file per non-obvious decision: what, why, what was rejected, any guard clause. authoritative
docs/modules/ Per-module documentation. One subfolder per module (created when module enters design). Also holds MODULE_INDEX.md — the module-level source-of-truth for schemas, table counts, build order, and pipeline stage. authoritative
docs/process/ The repeatable module pipeline, the two gates (Design-Lock and Ship), and the runbooks every module flows through. authoritative
docs/business-requirements/ Market research, the gaps registry, and explicit product scope. Part A input — not design output. input only
docs/old/ Full Sprig-era doc set: schema, decisions, design rationale, module specs, architecture, contracts. Retained as reference. Do not edit; do not cite as current truth. frozen reference

C. Source-of-truth pointers

What you need Where it lives
Product overview (what Vrida is, who it's for, capabilities, how it's built) docs/WHAT_IS_VRIDA.md
Cross-cutting decisions (tech stack, architecture, tenancy, billing split, consumer-layer, retention) docs/decisions/PROJECT_DECISIONS.md
Module catalog, schemas, table counts, build order, pipeline stage docs/modules/MODULE_INDEX.md
Per-module design docs (schema, knowledge, AI mapping, service contract) docs/modules/<module>/
Per-module specs (purpose, ownership, capabilities, service contract, state machines) docs/modules/module_spec/<module>.md
Decisions ("why") docs/decisions/
Schema definitions (authoritative) docs/database/
AI capability contract + governance controls docs/ai/AI_CAPABILITY_PLANE.md
AI capability plain-language reading docs/ai/AI_CAPABILITY_PLANE_READING.md
AI capability gap analysis vs. the market (2026-07-06) docs/ai/AI_CAPABILITY_GAPS.md
Market gaps and scope (Part A input) docs/business-requirements/MARKET_RESEARCH_GAPS.md
Frozen reference schema (per-module) docs/old/schema/schema_modules/
Frozen reference decisions docs/old/PROJECT_DECISIONS.md
Frozen design rationale docs/old/design_rationale/
Module pipeline + gates docs/process/ + docs/process/runbooks/
Service-layer module runbook (13-step procedure) docs/process/runbooks/SERVICE_LAYER_RUNBOOK.md

D. Project-level status

Phase: Platform (module #1) schema-locked and spec authored, reopened a 4th time 2026-07-07 for tenant-identity absorption from Admin's planned tenant_business_profile (PROJECT_DECISIONS #34 set the governing split — platform owns tenant identity, admin owns tenant technical/operational config; #35 records this reopen's actual build): tenant_profile 34→39 cols (+5: business_email, legal_address, mailing_address, business_classification_code, ein_ref) and trading_name (text) retyped to dbas (jsonb array, NOT NULL DEFAULT '[]', bare string-array shape, e.g. ["Acme Garden Co","Rose Garden Nursery"]) — confirmed zero-risk live (12 rows, 0 non-null trading_name at retype time); tax_id and logo_url deprecated in place (column comments only, no DDL change), superseded by ein_ref and (once Admin v2 exists) admin.tenant_branding.logo_ref; platform module-wide now 23 tables (unchanged) / 408 cols (up from 403); code cutover (PlatformService, DTOs, packages/types, admin tenant page) and 10 new tests all done, full apps/api suite 539/539 passing — see PROJECT_DECISIONS #34–35. Identity (module #2) schema design complete (all batches A–D locked, 35 tables / 396 cols) + IdentityService complete (all 5 phases, 86 methods, 2026-06-29); Shared (module #3) schema locked and fully seeded 2026-07-05 (10 tables / 108 cols — global reference data, no service layer yet). Multi-Location (module #4) schema locked 2026-07-05 (1 table / 37 cols — site, global address/currency/units/climate via natural-key FKs into shared, no service layer yet). A cross-cutting autonomy-first backfill (2026-07-06) added agent-as-actor attribution, automation_source, and human-in-the-loop review columns across all 4 locked modules (platform +6 cols, identity +2, shared +7, multi_loc +8) — see PROJECT_DECISIONS #19. Identity also gained the A5 agent-authority passport (2026-07-06): +agent_duty_grant (+25 cols) — per-agent, per-permission draft/execute/needs-approval authority and spend/quantity ceilings — see PROJECT_DECISIONS #22; schema-only, no service methods yet. CRM (module #5), Inventory (module #12), AI (module #6, cross-cutting), and Pricing (module #13) all locked 2026-07-06 — the first four of the 13 nursery product/vertical modules (crm 13 tables/189 cols; inventory 24 tables/337 cols, first real shared.plant consumer; ai 7 tables/118 cols, the agent-runtime schema layer; pricing 4 tables/75 cols, the sell path's first module, resolving 6 outside-critique gaps + 3 binding cross-module hard contracts) — see PROJECT_DECISIONS #23–26; all schema-only, no service layers yet. POS (module #14) locked 2026-07-07 — the sell path's offline-first transaction spine (9 tables/138 cols, down from v1's 19/280 via 10 deliberately deferred tables), honoring Pricing's Hard Contract 1 verbatim on sale_line — see PROJECT_DECISIONS #27; reopened same day for 2 fail-closed CHECK gates (gift_card/store_credit tenders). crm/inventory/pricing were also reopened 2026-07-07 for a retroactive v1→v2 column-level delta-accounting pass plus 3 erosion fixes (a live tax-certificate verification bug, 2 restored capabilities) — see PROJECT_DECISIONS #28. Orders (module #15) locked 2026-07-07 — completes the sell path (crm → pricing → inventory → orders → pos), 7 tables/173 cols, the first module designed AND built under the new Design-Phase Integrity rules (zero v1 tables consolidated or dropped) — see PROJECT_DECISIONS #29; schema-only, no OrderService yet. Purchasing (module #16) locked 2026-07-07 — the BUY path, completing the supply loop opposite orders/pos (16 tables/398 cols, +57 off v1's 341, zero tables consolidated or dropped); the receiving seam writes an inventory.stock_movement (the inbound counterpart to a sale's outbound decrement) + updates weighted-average cost, and the module closes orders' draft_po_id forward-ref (a real FK now, both directions) — see PROJECT_DECISIONS #30; schema-only, no PurchasingService yet. Tax (module #17) locked 2026-07-07 — the first module in this entire build with zero v1 precedent (v1 fully outsourced tax to Stripe Tax, no local schema at all); 2 tables/37 cols restoring the per-jurisdiction tax decomposition pos's own audit found lost, automation_source defaulting 'system' as the first justified deviation from the universal 'human' default codebase-wide — see PROJECT_DECISIONS #31; schema-only, no TaxService yet. Billing (module #18) locked 2026-07-07 same day — the SETTLE half of the financial layer (9 tables/162 cols, up from v1's locked 8/113, all 8 v1 tables preserved + 1 NEW ar_adjustment); ar_charge.tax_calculation_id is the tax seam; a live NULL-distinctness bug in ar_charge's idempotency dedup was caught and fixed during the build's own test-writing pass (two-partial-unique split, mirroring orders.order_header's precedent) — see PROJECT_DECISIONS #32; schema-only, no BillingService yet. Payments (module #19) locked 2026-07-07 same day — the money-MOVEMENT layer, executing what billing records (9 tables/159 cols, up from v1's locked 8/112, all 8 v1 tables preserved + 1 NEW terminal_reader); payment_intent.source_ref polymorphic seam widened to 3 sources (pos/orders/billing); chk_payment_intent_source_pair is a genuine fix caught by independent adversarial verification during the design-phase pass (not v1-preserved). First module built under the new evidenced independent-verification gate (SCHEMA_DESIGN_RUNBOOK §2.3.7/2.7/6.1a, added the same day after this module's own design proposal was initially self-graded "Zero FAILs" and found wrong by adversarial review) — a separate agent independently re-audited the built schema and found zero FAILs, findings pasted in PROJECT_DECISIONS #33; schema-only, no PaymentsService yet. The financial + money-movement layer (tax + billing + payments) is now complete. Both admin.vrida.app and tenant.vrida.app have page shells (mostly 🟡 sample-data mockups, 2026-06-30). Platform's admin API is partially built (3 controllers, 9 endpoints); Identity's HTTP controller layer — zero endpoints today — is the next gap. 5 of the 13 nursery product/vertical modules (crm, inventory, pricing, pos, orders, all schema only) have entered the pipeline; the other 8 have not; purchasing, tax, billing, and payments are cross-cutting/supply-and-financial-layer modules outside that 13-module nursery-vertical count. Admin locked 2026-07-07 same day, immediately after the platform reopen above — Admin's first real v2 pass (v1 was 11 tables/145 cols, locked 2026-06-10, never rebuilt until now): 10 tables/128 cols, tenant_business_profile (17 cols) dropped entirely (16 cols moved to platform.tenant_profile in the reopen above, 1 — attributes — dropped outright with no successor), a faithful port of v1's other 10 tables plus the module-wide actor-attribution retarget (5 cols across 3 tables, identity.identity_useridentity.actor) — see PROJECT_DECISIONS #34/#36; schema-only, no AdminService yet. Admin, like purchasing/tax/billing/payments, is a cross-cutting module outside the 13-module nursery-vertical count. A senior-architect 8-lens schema review plus a 15-module validation sweep (both 2026-07-08) fed a 4-phase remediation plan; Remediation Phase 1 (enforcement foundation) is now complete — real RLS wiring (a genuine non-superuser authenticated Postgres role now exists, GRANT-closed across all 15 schemas, replacing the prior superuser-only connection that bypassed RLS entirely), the C8 financial-autonomy boundary, the agent-authority-passport self-issue guard, append-only enforcement on 9 ledger tables, and a 19-constraint fail-open review-flag sweep across 8 modules — plus 3 real bugs (a PO CHECK's NULL-bypass, 2 RLS coverage gaps) found and fixed by the phase's own live-reproduction and independent-verification steps. Inventory's column count is now 337 (+1, stock_count.reconciled_at) — the only column-count change in the phase. See PROJECT_DECISIONS #37. Remediation Phase 2 (non-additive decisions) is now also complete (2026-07-08, PROJECT_DECISIONS #38): a custom platform.uuid_generate_v7() function (mandatory — this Postgres is 17.6, native uuidv7() ships PG 18, no pg_uuidv7 extension available) is now the PK-generation strategy on all 25 tables independently confirmed genuinely append-only (widened from the plan's 4 named "hot ledgers" at near-zero marginal cost) — keeping future time-range partitioning possible on these ledgers before any accumulate production data, something impossible once a UUIDv4 PK has landed live rows. payments.payment_intent also gained a processor anchor column (NOT NULL DEFAULT 'stripe', CHECK-constrained to ('stripe') for now), the minimal-now step of a vendor de-primitivization whose full scope (19 other stripe_* columns across 6 schemas, 2 table renames, this CHECK's own widening, tax.provider's CHECK widening) is deferred to OPEN_ITEMS. Payments is now 9 tables/159 cols (+1) — the only column-count change this phase. Both items were independently verified with zero BLOCKERs. Remediation Phase 3 (missing capabilities) is now also complete (2026-07-08, PROJECT_DECISIONS #39) — 6 items across 6 modules: crm.customer gained credit_limit_cents/credit_terms (Item 8, closing a live crm↔billing orphan — billing.ar_account's own comment already said it read these from crm, but crm never actually had them); tax.tax_calculation gained calculation_type/reversed_calculation_id (Item 9, a refund's tax reversal is now representable with sign-aware CHECKs so SUM(original+reversal) nets to zero for remittance reporting — a same-day post-verification addendum also added a real DB trigger enforcing the per-jurisdiction sign, upgrading a disclosed-but-unenforced gap to enforced); pos.sale restored sale_number and pos.sale_refund_line/sale_refund gained a no-receipt-refund identifier + tax-capture columns (Item 10 — pos.sale_refund.sale_id itself stays NOT NULL; the anonymous-walk-in-return question is explicitly flagged for the architect, not decided or bundled); the fail-closed tender gate was widened to also block 'reward' and inventory.stock_movement.movement_type gained 'produced' (Item 11); identity.agent_identity gained a kill-switch (status/suspended_at/suspended_by_actor_id/suspension_reason, Item 12 — one link in a documented precedence chain: tenant status > platform.ai_credit_account status > agent status > agent_duty_grant > skill assignment > role assignment > feature flags); and Admin gained an 11th table, setting_definition (Item 13, the config-key catalog for tenant_setting), alongside a canonical money-unit-suffix convention now documented in SCHEMA_CONVENTIONS.md §8.1. Column deltas: identity 396→400, crm 189→191, tax 37→39, pos 138→143, admin 128→140 (+1 table); inventory unchanged (CHECK-widen only). Independently verified by 2 adversarial lenses, both CLEAN — every NOTE-level finding addressed same-day, not deferred. Remediation Phase 4 (futureproofing, the final phase) is now also complete (2026-07-08, PROJECT_DECISIONS #40) — 7 items (14–20) across 11 modules plus closing the Phase 3-deferred anonymous-walk-in-return decision (DECIDED = ALLOW: pos.sale_refund.sale_id relaxed nullable, chk_sale_refund_identification requires a linked sale or a documented reason). Platform gained 3 tables (accounting_period — this codebase's first EXCLUDE USING gist, a flag-not-reject fiscal-period trigger; legal_entity — 1:N from tenant, entity_id added to 10 header tables codebase-wide; outbox — a mutable transactional-outbox event table). Shared gained 2 tables (exchange_rate; payment_terms_catalog, 10 seeded rows) plus a cross-table currency-agreement trigger on billing.ar_payment_application. 4 enum→catalog additive-interim additions (Item 17a–d: POS tender types, payment terms, tax jurisdiction levels — with a real CHECK-widen adding 'country', closing the VAT/GST gap — and integration providers). crm.customer.pii_vault_ref + ai.agent_memory's GDPR/erasure seam (Item 18); admin.custom_field_definition governing the 7 confirmed ungoverned attributes JSONB columns codebase-wide (Item 19); inventory.stock.last_movement_id + this codebase's FIRST CREATE VIEW (stock_reconciliation_shell, deliberately a minimal aggregation-free shell, Item 20). Column/table deltas: platform 23→26 tables/408→440 cols, admin 11→13/140→161, pos 9→10/143→160, tax 2→3/39→47, shared 10→12/108→136 (of which +12 corrects a pre-existing, Phase-4-unrelated stale baseline), billing/purchasing/orders/crm/ai/inventory unchanged table counts with +2/+4/+1/+2/+3/+1 cols respectively. Independently verified by 2 adversarial lenses, both CLEAN with zero BLOCKERs — the one substantive finding (no dedicated regression tests existed yet for these items, a deviation from every prior phase) was closed same-day with 105 new tests across all 11 touched modules before the phase was declared complete. This completes the entire 4-phase remediation plan. A new cross-cutting module, Approvals (approvals schema), was then schema-locked 2026-07-09 — a shared, tenant-side approval-workflow engine split OUT of Admin: 8 tables/97 cols, moving admin.approval_workflow/approval_routing_rule/approval_request wholesale (10→12, 11→11, 18→20 cols respectively — approval_request.requested_by_actor_id renamed initiator_actor_id and made NOT NULL, closing a null-approver bypass bug found during design verification) plus 5 net-new tables: approval_policy (self-approval/SoD config, min_distinct_approvers DB-enforced via trigger), approval_step (per-request step instances replacing the dropped step_history JSON, 3 triggers guarding self-approval-on-any-step/parallel-step quorum-spoofing/the C8 agent-money boundary), approval_delivery (approver notifications — a disclosed, temporary overlap with the not-yet-built Notifications module's own planned "delivery attempts" scope), approval_token (hash-only one-click approve/reject tokens, one atomic single-use redemption UPDATE closing a TOCTOU race), and approval_event (append-only audit log, reusing platform.reject_append_only_mutation()). This REVERSES PROJECT_DECISIONS #34 Section 5's Option B decision that the engine "stays Admin-internal." 6 critical guards were live-reproduced against the local Supabase Postgres DB with real before/after proof: approved-by-nobody, self-approval on every path (header, a non-final parallel step, and the null-approver path), parallel-step quorum-spoofing, the C8 agent-money boundary (plus a genuine 3rd bug found live during this build — a Postgres column-level REVOKE cannot subtract from a broader table-level GRANT since ACLs are additive across granularities; fixed by revoking the table-level grant entirely and re-granting column-by-column), one-click token replay/expiry, and append-only audit enforcement (rejects UPDATE/DELETE even as superuser). See PROJECT_DECISIONS #44. Admin was reopened a 3rd time the same day to hand off the 3 moved tables: 13→10 tables/161→122 cols (-3 tables/-39 cols); its remaining 10 tables (tenant_branding, compliance_document, tenant_setting, setting_definition, hardware_device, integration_config, integration_provider_catalog, webhook_config, api_key, custom_field_definition) are unchanged, and its "Tenant-Side Approval Engine" concern-group is dropped entirely (3 groups remain: Presentational / Technical-Operational Config / Tenant Extensibility). approvals, like admin/purchasing/tax/billing/payments, is a cross-cutting module outside the 13-module nursery-vertical count. Schema-only so far — no ApprovalsService yet. Identity was reopened a 3rd time the same day (2026-07-09, PROJECT_DECISIONS #45) — an isolated Vrida-operator identity (identity.operator + identity.operator_role_assignment, +2 tables/+22 cols), replacing the identity_user.is_platform_user boolean overlay; actor.actor_type CHECK widened to add 'operator'; belt-and-suspenders security backstop (REVOKE + RLS-with-zero-policies) and a NULL-safe self-issue guard both live-reproduced; 7 same-pass code retargets plus 3 more found during the build; admin login proven end-to-end against the real local Supabase stack. Identity is now 37 tables / 422 cols (up from 35/400). A new, coordinated "Header/Line Remediation" effort then began (2026-07-10, design doc vrida-header-line-remediation-design-2026-07-10.md) — a systemic sweep for missing header/line reconciliation, unenforced immutability claims, and bare (non-composite) cross-tenant FKs across the codebase; pos was reopened first (PROJECT_DECISIONS #46) for fix #8, closing a real gap where sale_refund_line's documented write-once/immutability had zero DB enforcement (now closed via REVOKE + a trigger reusing platform.reject_append_only_mutation() verbatim, matching sale_line's own precedent), plus 2 bundled additions (a sale_line UNIQUE(id, tenant_id) prerequisite for a still-deferred Orders-side fix, and a composite-FK upgrade on sale_refund_line.sale_line_id) — pos stays 10 tables / 160 cols, unchanged (see PROJECT_DECISIONS #46). purchasing was reopened next (2026-07-10, same effort, PROJECT_DECISIONS #47) for 3 bundled fixes: chk_purchase_order_line_quantity_rollup on purchase_order_line (fix #10, must-fix — mandatory pre-migration audit found zero violating rows live); line_number partial-unique indexes on 4 tables (purchase_receipt_line/vendor_invoice_line/vendor_return_line/purchase_order_template_line, fix #12 — mandatory dedup audit found zero duplicates on all 4); and a new table, vendor_credit_line (fix #4 — a write-once per-line decomposition of vendor_credit, 3 composite FKs each requiring a new prerequisite UNIQUE(id, tenant_id) on its parent, plus a header-is-truth reconciliation trigger capping the line-sum at the parent's credit_amount_cents). purchasing is now 17 tables / 410 cols (up from 402). Platform reopened last (2026-07-10, same effort, PROJECT_DECISIONS #48) — the 3rd and final reopen (POS → Purchasing → Platform), closing out this Header/Line Remediation effort: new table subscription_invoice_line (10 cols, per-line decomposition of a subscription_invoice's total, write-once) reconciled LINES-ARE-TRUTH via a new trigger, trg_subscription_invoice_line_sync_totals (the opposite pattern from Purchasing's own header-is-truth vendor_credit_line), requiring a new prerequisite UNIQUE(id, tenant_id) on subscription_invoice for the line table's composite FK. Design-phase independent verification caught and corrected the single most serious finding of the whole 3-module effort — a migration-sequencing bug where installing the sync trigger before backfill+verify would have made the reconciliation check tautological (the trigger would silently overwrite the very totals the verification step needed to compare against) — the corrected 8-step order snapshots pre-backfill totals first and installs the trigger only after backfill and verification. Pre-migration inspection also disclosed that all 5 pre-existing live invoices held an empty line_items blob despite non-zero subtotal_cents — a genuine pre-existing data-quality gap, logged via RAISE NOTICE not silently fixed; line_items is now deprecated in place. platform is now 27 tables / 450 cols (up from 440). This closes the entire 3-reopen Header/Line Remediation effort (POS #46, Purchasing #47, Platform #48). A second batch of the same effort then began the same day, with inventory reopened first for fixes #5 and #9 (PROJECT_DECISIONS #49): new table stock_adjustment_batch (10 cols, a header grouping multiple stock_adjustment_request rows for joint review) with stock_adjustment_request gaining a nullable batch_id composite FK into it (fix #5); stock_count_line gaining reconciled_at/reconciled_by_actor_id plus a NULL-safe CHECK and a bespoke conditional-immutability trigger (trg_stock_count_line_lock_after_reconciled, deliberately not the shared blanket append-only function, since a count line must stay editable pre-reconciliation) (fix #9). inventory is now 25 tables / 351 cols (up from 24/338). 2 gaps disclosed, not silently fixed, and logged to OPEN_ITEMS: stock_movement_line still lacks UNIQUE(id, tenant_id) (the prerequisite for Purchasing's still-deferred fix #7), and stock_adjustment_batch.updated_at has no maintaining trigger. orders was reopened next in this same second batch (2026-07-10, PROJECT_DECISIONS #50) for fix #6 — order_line.sale_line_id, a new nullable, composite FK → pos.sale_line(id, tenant_id) extending the header-level pos-fulfillment seam down to the line level, link-don't-convert and mirroring order_header.fulfilled_sale_id's own established asymmetry (no reciprocal column on POS's side either); the prerequisite UNIQUE(id, tenant_id) on pos.sale_line had already been laid in the first batch's own POS reopen (fix #8, PROJECT_DECISIONS #46), anticipating exactly this. orders is now 7 tables / 175 columns (up from 174). Independent verification confirmed the core fix fully correct and disclosed 2 findings, neither invalidating it: a test-suite cleanup bug (the new test section's afterAll originally attempted a hard DELETE against append-only pos.sale_line, since fixed via a tryDelete() helper mirroring pos-schema.spec.ts's own precedent) and a new, previously undisclosed finding — 5 bare (non-composite) FKs still sit inside orders' own internals (order_line.order_id/order_payment.order_id/order_fulfillment.order_idorder_header, order_fulfillment_line.order_fulfillment_idorder_fulfillment, order_template_line.order_template_idorder_template), none of whose parent tables yet carry UNIQUE(id, tenant_id) — logged to OPEN_ITEMS as a future Orders reopen candidate, not fixed in this pass. billing was reopened next in this same second batch (2026-07-10, PROJECT_DECISIONS #51) for fix #2 — a new table, ar_charge_line (12 cols, write-once per-line decomposition of ar_charge.charge_amount_cents), reconciled HEADER-IS-TRUTH via trg_ar_charge_line_validate_against_charge (mirroring Purchasing's own header-is-truth vendor_credit_line precedent), with 2 new composite FKs (→ billing.ar_charge, → tax.tax_calculation) each needing a new prerequisite UNIQUE(id, tenant_id) — the tax.tax_calculation one a cross-module prerequisite this fix needed and added (no column/table impact on tax itself). Unlike Platform's own subscription_invoice_line fix, no JSONB blob existed to convert — rows were RECONSTRUCTED by joining back through ar_charge.source_ref to pos.sale/pos.sale_line; a mandatory pre-migration dry run found zero live pos-sourced ar_charge rows share a tenant with any seeded pos.sale row (disconnected seed datasets, not a reconciliation ambiguity), and the full reconstructive backfill query was still written and actually run, confirmed INSERT 0 0. billing is now 10 tables / 176 columns (up from 164). Independent verification found the fix fully correct and disclosed 2 findings, neither invalidating it: ar_charge.tax_calculation_id was ALSO found to be a bare FK (closed in a LATER, separate migration/docs pass, not credited to this fix) and a minor, informational rounding-methodology note (unreachable in today's data — every live pos.sale row has exactly 1 line). identity was reopened a 4th time next in this same second batch (2026-07-10, PROJECT_DECISIONS #52) for fix #3 — a new table, invitation_site_assignment (6 cols, pre-acceptance staging of intended site access, mirroring user_site_assignment's own shape; site_id deliberately carries no FK, matching user_site_assignment.site_id's own pre-existing gap), plus user_site_assignment.created_from_invitation_site_assignment_id (a nullable composite FK back to the new table). identity.invitation gained a prerequisite UNIQUE(id, tenant_id); its site_assignments JSONB is now deprecated in place. identity is now 38 tables / 429 columns (up from 422). A mandatory pre-migration orphan-check audit found ONE live orphaned row in user_site_assignment.site_id (absent from multi_loc.site) — the optional bundle wiring all 3 site_id-shaped columns to real multi_loc.site FKs was deliberately NOT taken because of it; flagged for a human decision, not resolved — see OPEN_ITEMS. Independent verification found zero findings of concern and zero scope creep. This second batch's fifth and final item landed the same day (2026-07-10, PROJECT_DECISIONS #53): 3 standalone bare-cross-tenant-FK fixes in one migration, each closing a gap disclosed during an earlier fix's own independent verification rather than its own build — platform.payment.invoice_id (bare since fix #1, PROJECT_DECISIONS #48), purchasing.vendor_invoice_match.vendor_invoice_line_id (bare since the first batch's own Purchasing reopen, fix #4, PROJECT_DECISIONS #47), and billing.ar_charge.tax_calculation_id (bare since fix #2, PROJECT_DECISIONS #51) all upgraded to composite (col, tenant_id) FKs, each reusing a UNIQUE(id, tenant_id) prerequisite that already existed from its respective earlier fix. vendor_invoice_match.purchase_receipt_line_id deliberately stays bare (purchase_receipt_line still lacks the prerequisite UNIQUE(id, tenant_id), out of this fix's own named scope) — disclosed, logged to OPEN_ITEMS. The standout independent-verification finding: vendor_invoice_match had zero live rows at build time, so the cross-tenant-rejection claim couldn't be reproduced by the build itself — the independent verifier built a full 2-tenant fixture chain from scratch and proved it for real. All 3 fixes are constraint-shape-only — platform/purchasing/billing table/column counts are unchanged (27/450, 17/410, 10/176 respectively). This closes the entire 2-batch Header/Line Remediation effort (batch 1: POS/Purchasing/Platform, PROJECT_DECISIONS #46-48; batch 2: Inventory/Orders/Billing/Identity/this fix, PROJECT_DECISIONS #49-53) — every fix in both batches' own scoped lists is now done; remaining named gaps (Purchasing's fix #7/#11, the multi_loc site_id FK-wiring bundle, Orders' 5 internal bare FKs, purchase_receipt_line's own missing UNIQUE(id, tenant_id)) are disclosed follow-on candidates for a future reopen, not outstanding work from either batch. The multi_loc site_id FK-wiring bundle was then itself closed the same day (2026-07-10, PROJECT_DECISIONS #54) — a direct follow-up to entry #52's own flagged human decision: the orphaned user_site_assignment row was investigated (isolated dev-seed fixture junk, confirmed via a DB-wide tenant-isolation scan) and deleted, then user_site_assignment.site_id, tenant_user.default_site_id, and invitation_site_assignment.site_id were wired to real composite FKs → multi_loc.site(id, tenant_id) (multi_loc's 1st reopen since its 2026-06-29 lock, identity's 5th). Pure constraint-shape change — multi_loc stays 1 table/37 cols, identity stays 38 tables/429 cols. platform.tenant.primary_site_id and identity.user_permission_override.scope_id remain genuinely deferred, plus a previously-untracked 4th sibling surfaced during this pass's own docs update, identity.access_request.requested_scope_id — none of these 3 were touched. Independent verification: CLEAN, zero findings. A new cross-cutting module, Receiving (receiving schema), was then schema-locked 2026-07-10 (PROJECT_DECISIONS #55) — extracted OUT of Purchasing, the same move-tables-into-a-new-schema pattern Approvals used against Admin: 2 tables/62 cols. goods_receipt (33 cols, moved+renamed from purchasing.purchase_receipt's 30, +3 new: voided_at/voided_by_actor_id/void_reason, closing a real pre-existing gap where 'void' was already a valid status value with zero attribution) and goods_receipt_line (29 cols, moved+renamed from purchasing.purchase_receipt_line's 27, +2 new: stock_movement_line_id + the self-referencing reversal_of_goods_receipt_line_id; inspection_status widened to add 'quarantine'). This closes the 2 fixes Purchasing's own prior reopens had explicitly deferred to this extraction: fix #11 (over-receipt tolerance) — a new trigger reading admin.tenant_setting/setting_definition with site-scoped > tenant-wide > catalog-default precedence, flagging (default) or blocking the line per tenant config, deliberately scoped to BEFORE INSERT OR UPDATE OF accepted_qty only (corrected post-independent-verification from an initial OF over_short_qty scoping, once over_short_qty became a derived rather than caller-supplied value) rather than the design doc's own unscoped literal wording — a disclosed narrowing so a later, unrelated edit to an already-reviewed line can't silently re-flip a human review decision; and fix #7 (movement-line linkage) — goods_receipt_line.stock_movement_line_id, a composite FK → inventory.stock_movement_line, superseding the older header-grain stock_movement_id (renamed from inventory_movement_id, deprecated in place). Design-phase independent verification caught 2 BLOCKERs before the build started: an uncapped PO-line write-back (fixed via absorbed_qty = LEAST(accepted_qty, ordered_qty − received_qty − invoiced_qty − cancelled_qty), so an over-tolerance receipt still satisfies purchasing.chk_purchase_order_line_quantity_rollup, with over_short_qty = accepted_qty − absorbed_qty capturing the excess) and a second retarget the first draft missed (vendor_return_line.purchase_receipt_line_id, not just vendor_invoice_match's own). Reversal is a compensating goods_receipt_line (via reversal_of_goods_receipt_line_id) plus a compensating inventory.stock_movement_line carrying a NEGATIVE quantity_delta under the same stock_movement.correlation_id — the original append-only movement is never edited or deleted, and the compensating line's own received_qty/accepted_qty stay a positive magnitude, since the sign lives entirely in the linked movement, not in goods_receipt_line itself (a build-time clarification, not stated explicitly in the design doc). 13 composite (col, tenant_id) → parent(id, tenant_id) FKs are new or upgraded from bare in this build, each individually live-reproduced for cross-tenant rejection. 3 more real bugs were found and fixed during the build's own live-reproduction pass, distinct from the 2 pre-build BLOCKERs: a missing UNIQUE(id, tenant_id) on goods_receipt itself (needed for goods_receipt_line's own composite FK into it); an admin.custom_field_definition.entity_type CHECK-vocabulary migration-ordering bug — widening the CHECK from 'purchasing.purchase_receipt' to 'receiving.goods_receipt' and backfilling 11 live test-fixture rows required DROP CHECK → backfill → re-ADD, since either "widen-then-backfill" or "backfill-then-widen" alone fails; and a defensive-cast gap in the tolerance trigger (a corrupted, non-numeric catalog value would have crashed it instead of failing safe — fixed via BEGIN/EXCEPTION WHEN invalid_text_representation falling back to 0% tolerance). 32 live-reproduction guard assertions across 6 sections, all pass, run inside one transaction and rolled back — zero residue. purchasing is now 15 tables / 353 cols (down from 17/410) — vendor/vendor_address/purchase_order/purchase_order_line each gained UNIQUE(id, tenant_id), the prerequisite for receiving's own composite FKs pointing back into this module, and vendor_invoice_match.purchase_receipt_line_id/vendor_return_line.purchase_receipt_line_id are both renamed goods_receipt_line_id and upgraded from bare to composite FKs → receiving.goods_receipt_line; every remaining table's own column count is unchanged — the entire −57 delta is just the 2 moved tables' own combined column count. inventory is unaffected at the row level — still 25 tables / 351 colsitem_variant/lot/stock_movement/stock_movement_line each gained UNIQUE(id, tenant_id) only, the prerequisite for receiving's own new composite FKs pointing the other direction. Entitlement-bundled to Purchasing's own toggle (no independent toggle — no per-module entitlement mechanism exists anywhere in this codebase yet). receiving, like admin/purchasing/tax/billing/payments/approvals, is a cross-cutting module outside the 13-module nursery-vertical count. Schema-only — no ReceivingService yet. This closes the last 2 named gaps (Purchasing's own fix #7/#11) the entire 2-batch Header/Line Remediation effort had disclosed as deferred. Consumer, Rewards, and Offers (modules #23–#25) are now also schema-locked (2026-07-11, PROJECT_DECISIONS #56) — the Consumer Layer's first real v2 build (Model B), each superseding a stale v1-carryover placeholder row (consumer 4/47, rewards 6/89, offers 4/68 — 14 tables/204 cols combined) rather than adding 3 new schemas: consumer (10 tables/104 cols, non-tenant-scoped except 2 named exceptions — consumer_merchant_link/event stay tenant-scoped and merchant-accessible), rewards (6 tables/102 cols, per-business loyalty), and offers (6 tables/115 cols, this build's own AI-authored-offer surface) — a +8 tables/+117 cols delta, for 22 tables / 321 cols total across the 3. This is the first cross-tenant-boundary module set in this codebase: a dedicated consumer_authenticated Postgres role, structurally separate from the merchant authenticated role, gates 8 of consumer's 10 tables (the other 2 stay merchant-accessible, as above). The consumer-boundary auth mechanism was investigated and corrected mid-build — a consumerDB() connection helper mirroring the existing tenantDB() pattern (explicit SET LOCAL ROLE per transaction), not a Supabase Custom Access Token Auth Hook, since apps/api never routes through PostgREST's auto-role-switching layer. 5 critical guards were live-reproduced: the concurrency race (a naive two-trigger point-redemption shape let a 100-point balance overshoot to -60 under 2 concurrent redemptions; the fixed atomic single-trigger shape correctly serialized 3-of-5 concurrent redemptions), the AI margin bypass (chk_offer_ai_requires_guardrail structurally forbids a non-human-defined offer with zero discount guardrails; the margin-floor check fails CLOSED against a zero/NULL cost), refund clawback, a negative-balance CHECK backstop, and the consumer_authenticated boundary proven at the ACL level in both directions. pos.sale gained the prerequisite UNIQUE(id, tenant_id) this build needed for its own composite FKs. Schema-only so far — no ConsumerService/RewardsService/OffersService yet. Files (module #26) is now also schema-locked (2026-07-11, PROJECT_DECISIONS #58/#59) — the generic file-metadata registry, another stale v1-carryover placeholder (3 tables/43 cols, locked as a design 2026-06-11, never migrated) becoming its first real v2 build: 6 tables/89 cols. Cloudflare R2 is the sole storage of record; AWS S3 is transient staging ONLY for AWS Textract's async extraction path (PROJECT_DECISIONS #58 records the full storage-architecture decision + cost model). Every v1 table/column survives (2 disclosed renames, 1 widened CHECK, 1 FK retarget) plus 3 genuinely new tables: attachment (the polymorphic many-to-many join) and the "vector spine," built now and populated later — document_index + document_chunk (embedding vector(1024), left NULL until semantic search activates; FTS works immediately via a generated search_vector column). files.file carries UNIQUE(id, tenant_id) from this build's own day one — a first for this codebase, built in rather than added via a later reopen. Foundation-layer discipline: file.consumer_id/file_access_grant.grantee_customer_id are deliberately loose, unenforced (no FK into the consumer/business layers) — independently confirmed live via pg_constraint. The dual-principal boundary closes at the GRANT layer: consumer_authenticated gets zero grant on files at all; the sole consumer read path is consumer.get_files_for_consumer(), a consumer-schema-owned, parameter-scoped SECURITY DEFINER function mirroring consumer.get_cross_tenant_activity()'s own shape. A 3-lens independent adversarial verification caught a real layer-boundary violation in the pre-verification design draft (wrong FK direction, wrong function placement, a non-executable join) and fixed it before the build; a further mandatory evidenced lock-gate verification ran after the build itself. Schema-only so far — no FilesService yet. Platform was then reopened a 6th time (2026-07-12) for Phase 1 of a new 6-phase "agents-v2/v3" build authorization (design of record: vrida-agents-v2-design-amendment-2026-07-11.md as amended by vrida-agents-v3-correction-pass-2026-07-12.md, sequenced with a mandatory independent lock-gate verification between every phase before the next begins) — this phase adds 7 new tables, 0 existing tables altered: module_catalog (A1, the global module registry replacing the closed CHECK-enum module-tag pattern this codebase's polymorphic module columns have used until now, lifecycle_status forward-only via a new DB trigger), tier_module_entitlement (which modules a tier includes by default, replacing the dead tier_definition.entitled_modules JSONB), tenant_module_activation (the REAL per-tenant module switch, replacing is_toggleable — found to have zero callers anywhere in the codebase), module_dependency (module prerequisite graph with real recursive-CTE cycle detection plus a reverse-dependency-on-deactivation guard), ai_capacity_policy/tenant_ai_capacity_usage (A8/E4 — the 6-level AI-capacity precedence chain's atomic enforcement half, platform.try_increment_ai_capacity_spend(), embedding the resolved ceiling as a correlated subquery inside one UPDATE...RETURNING), and tenant_regional_policy (E7 — one resolvable regional-placement policy per tenant for later phases' routing/storage/retrieval/archival decisions). platform is now 34 tables / 514 cols (up from 27/450). An independent lock-gate verification found and fixed 4 issues before Phase 2 was authorized to proceed: a live GRANT-additivity bug that left the platform-wide AI cost ceiling freely mutable by any tenant-scoped authenticated session (closed via explicit REVOKE), a refund/correction bypass in the spend-increment function (negative deltas now always succeed unconditionally), a vestigial priority tie-break column (now honored), and a module_catalog seed misclassification (multi_loc corrected to layer='business'). Schema-only — no service-layer changes this phase (every phase of this build stays schema-only until Phase 5 lands the agents module itself). See PROJECT_DECISIONS #62. Phase 1 lock-gate verdict: CLEAN — Phase 2 (ai reopen) is authorized to proceed. (Note: this entry documents the Platform reopen only; the intervening 2026-07-11 rewards/offers proportional-reversal reopen (PROJECT_DECISIONS #60) and the returns module build (module #27, PROJECT_DECISIONS #61) — both already reflected in docs/modules/MODULE_INDEX.md and docs/modules/MODULE_BUILD_STATUS.md — are a pre-existing gap in this file's own narrative, not introduced by this update.) ai was then reopened for the first time (2026-07-13) for Phase 2 of the same 6-phase agents-v2/v3 build authorization — 14 new tables (C1 registry: provider_registry/model_family/model_version; C1 deployment: model_deployment + _limit/_region/_policy/_override satellites + model_deployment_status_observation; C1 prompts: prompt_definition/_version/prompt_model_compatibility; routing_policy; C4: agent_memory_source) — PROJECT_DECISIONS #63's own summary sentence says "13 new tables," a pre-existing off-by-one against its own 14-item enumeration, disclosed rather than corrected here (see docs/database/schema_docs/ai.md's own disclosure note). The 2 pre-existing v1 tables agent_execution/agent_memory are, for the first time in this codebase, PARTITIONED BY RANGE(created_at), monthly — closing 2 gaps neither the v2 nor v3 design addressed (a native cross-partition unique index cannot express "at-most-one-resolver," "idempotency dedup," or "at-most-one-active-memory-per-key"; all 3 are now enforced by new advisory-lock BEFORE INSERT triggers instead of partial-unique indexes). ai is now 49 tables / 227 cols (up from 7/121) — the 227 counts each distinct table definition once (partition-child tables inherit their parent's columns); the 49 physical-table count is live-verified via information_schema.tables and includes the 28 partition-child tables (13 monthly + 1 DEFAULT catch-all, × 2 partitioned tables). A mandatory pre-migration audit found 948 live agent_execution rows (backfilled with zero unresolved shadow-column rows) and one genuine pre-existing orphaned permission_id (nulled, disclosed, not silently dropped) — full writeup, including a disclosed post-migration test-cleanup mistake, in PROJECT_DECISIONS #63. Schema-only — no service-layer changes this phase either (every phase of this build stays schema-only until Phase 5 lands the agents module itself). semantics was then schema-locked for the first time (2026-07-14) as Phase 3 of the same 6-phase agents-v2/v3 build authorization — a brand-new foundation-layer schema with zero v1 antecedent, the shared business ontology the not-yet-built agents/signals modules will read from rather than each inventing their own metric/goal/constraint vocabulary: 14 tables / 114 cols (approved_function_registry, metric_definition/_version/tenant_metric_binding/metric_dependency, entity_definition/entity_alias, dimension_definition, goal_definition/constraint_definition/tenant_goal_binding/tenant_constraint_binding, attribution_model_definition/_version), featuring the I2 approved-function-registry supply-chain-hole-closing re-verification pattern (verify_function_still_matches_approval(), re-resolving and re-hashing a registered function fresh on every call rather than trusting a one-time approval) and EXCLUDE USING gist hard-reject overlap prevention on the 3 tenant-scoped binding tables (a genuine, disclosed departure from platform.accounting_period's own flag-not-reject pairing, since no offline-sync reason applies here). A Section 4 self-audit found and fixed 2 real gaps before lock (3 lifecycle_status columns missing their CHECK entirely; tenant_goal_binding/tenant_constraint_binding missing created_at/updated_at + a maintaining trigger their sibling tenant_metric_binding already had — this is why the schema landed at 114 cols, not the original 110), and the regression suite alone caught a missing GRANT USAGE ON SCHEMA omission the read-only audit pass missed. tenant_goal_binding.module_id/.site_id and tenant_constraint_binding.module_id/.site_id are deliberately left as bare uuid with no FK — a disclosed judgment call (not a deferred-FK debt of the ordinary kind), logged to OPEN_ITEMS. Schema-only — no SemanticsService yet. Full apps/api suite green at 1150/1150. See PROJECT_DECISIONS #64. signals was then schema-locked (2026-07-15) as Phase 4 of the same 6-phase agents-v2/v3 build authorization — this build's own explicitly-designated HIGHEST-RISK phase, a brand-new foundation-layer schema with zero v1 antecedent (11 tables / 111 cols: feature_definition/feature_version, experiment/experiment_version, feature_value/forecast/anomaly_score, experiment_assignment/experiment_exposure_event, outcome_observation/outcome_authority), the feature/forecast/outcome store the not-yet-built agents module (Phase 5) will read from and write to, opposite semantics's own operational profile — observational, high-volume, bitemporal, append-only, and partitioned (feature_value/forecast/anomaly_score weekly by recorded_at, outcome_observation monthly by created_at; outcome_authority and the 7 other tables stay unpartitioned) rather than definitional/low-volume/human-curated. This phase combines 3 mechanisms no prior phase combined in one schema — partitioning, a split-authority pattern, and SECURITY DEFINER tenant-isolation functions — and live-reproduced 4 named critical guards: GUARD 1 (BLOCKER 1) closes a tenant-spoof vector in the 3 as-of functions by removing the p_tenant_id parameter entirely and deriving tenant scope from a new platform.current_tenant_id() session-GUC helper instead, since a spoofable parameter cannot be fixed by validating it; GUARD 2 (BLOCKER 2) resolves "exactly one authoritative observation per scope" — an invariant a native partitioned unique index structurally cannot express — via a genuinely separate, unpartitioned outcome_authority table whose own PK is the real enforcement, maintained by a native race-free INSERT ... ON CONFLICT upsert; GUARD 3 (BLOCKER 3) closes both assigned_at forgery (an unconditional trigger overwrites any caller-supplied value with clock_timestamp()) and a live-reproduced retroactive-exposure-contamination race (a genuinely-earlier exposure event arriving after the causal basis is already locked is flagged, never silently absorbed into history, closed via matching row-locks in 2 cooperating trigger functions); GUARD 4 (B3 mandatory test 4a) proves the bitemporal read is DB-enforced, not a service-layer convention — a fact the ERP learned after a decision's own knowledge-cutoff is structurally invisible to that decision, regardless of how far in the business past it describes. A genuine gap in the design doc's own literal SQL (2 sibling constraints omitting the required partition key, the identical error class BLOCKER 2 itself documents) was found and fixed during this phase's own migration-apply pass, alongside 2 privilege-mechanics gaps (role-membership/schema-CREATE requirements for the function-ownership transfer; a USAGE grant the SECURITY DEFINER function body itself needed on schema platform) neither the v2 nor v3 design anticipated. 2 forward-refs are disclosed: outcome_observation/outcome_authority.agent_action_id (bare uuid, agents.agent_action doesn't exist until Phase 5) and the 3 as-of functions' own REVOKE EXECUTE FROM PUBLIC with no corresponding GRANT EXECUTE yet (agent_reader doesn't exist until Phase 6). Full apps/api suite green at 1172/1172. Schema-only — no SignalsService yet. See PROJECT_DECISIONS #65. agents was then schema-locked (2026-07-16) as Phase 5 of the same 6-phase agents-v2/v3 build authorization (Phase 1 reopened platform for the module registry + AI-capacity layer, PROJECT_DECISIONS #62; Phase 2 reopened ai for the model-registry/deployment/prompt/routing layer, PROJECT_DECISIONS #63; Phase 3 built semantics, PROJECT_DECISIONS #64; Phase 4 built signals, PROJECT_DECISIONS #65) — module #28, the module this entire authorization exists to build, 47 tables / 475 cols. Merges the full v1 agent-orchestration baseline (21 v1 tables — agent_task, agent_thread, agent_event_log, agent_schedule, agent_trigger, agent_eval_suite, agent_eval_run, agent_eval_case, agent_performance_profile, agent_shadow_run, agent_shadow_decision, agent_autonomy_profile, agent_action, rollback_recipe, rollback_execution, tool_catalog, agent_tool_grant, agent_catalog_entry, agent_catalog_entry_required_tool, tenant_agent_deployment, agent_incident) with v2's own A1–A8 amendments and v3's own BLOCKER1–8/I1–I8 corrections (v3 wins on conflict), across 12 Drizzle files. tool_catalog (v1) is disclosed as SUPERSEDED by the new tool_definition/tool_version version-row pattern (zero live v1 rows). agent_decision, decision_context_manifest, and decision_context_snapshot are PARTITIONED monthly. 8 distinct trigger-backed critical guards, all live-reproduced (the design named 7; a 6th, T1, was added and proven during the Section 4 audit itself — disclosed honestly as an 8th rather than force-fit): E3 (atomic task claim + fencing, lease renewal never bumps the fencing token), BLOCKER7 (kill-switch history/state split + resume-safe propagation, plus a Section-4-audit-found RLS gap — see below), BLOCKER4+I1 (the saga gate enforced against real writes, not a static toolset-shape proxy), the runaway-ceiling guard (BLOCK not flag, closing v1's own Gap 1), A5 corrected (the kill-switch check fires on UPDATE OF status — task CLAIM — not just INSERT), A3+BLOCKER5+A8R4 (skill activation: certification + duty-grant authority + a spend-ceiling policy, folded into validate_skill_activation() — where 2 genuine PL/pgSQL bugs were found and fixed during live guard-reproduction, not the read-only audit: a record-typed variable's IS NOT NULL check is unreliable used directly as an IF condition, silently swallowing the entire A8 Rule 4 spend-ceiling check, fixed via scalar variables; and SELECT MIN(...) ... FOR UPDATE is syntactically rejected by Postgres, fixed by splitting into a lock-only PERFORM ... FOR UPDATE followed by a separate SELECT MIN(...)), and the A2b retirement-version-check trigger (reject_retired_decision_context_versions). A Section 4 self-audit found and fixed 1 more genuine security gap beyond live guard-testing: agents.kill_switch_event carried tenant_id + a GRANT but zero RLS policies, letting any tenant session read every other tenant's kill-switch history and forge a kill/suspend/resume event against another tenant's agents — fixed with the same 2-policy mixed-scope RLS pattern already established by agent_eval_suite/rollback_recipe, live-reproduced and covered by a new regression test (I5); the same audit fixed item J (15 JSONB columns lacked documented example shapes) and disclosed, not fixed, item K (12 tables lack a tenant_id-leading index — logged to OPEN_ITEMS, low severity). 3 pre-existing forward-ref OPEN_ITEMS rows CLOSE with this build: ai.routing_policy.workload_class_id → agents.workload_class(id) (plain FK) and signals.outcome_observation/outcome_authority.agent_action_id → agents.agent_action(id, tenant_id) (composite FKs) — 0 orphans found for all 3; Phase 4's own signals-schema.spec.ts needed a retrofitted real fixture chain (identity.actor → agent_identity → decision_context_snapshot → ai.agent_execution → agents.agent_decision → agents.agent_action) to keep passing. ai.agent_execution gained 8 columns this same migration (agent_task_id, sequence_index, tool_version_id, approval_request_id, retry_count, blocked_by_policy, confidence_threshold, escalation_reason), ai.routing_policy.workload_class_id is now wired, and approvals.approval_request's source-module CHECK was widened to add 'agents' (11 values total) — all disclosed companion touches inside this one migration. platform also gained 1 table this same migration (polymorphic_target_registry, I6, 4 cols) — platform is now 35 tables / 518 cols (up from 34/514). A transitional Drizzle barrel collision (identity.agentSkillAssignment v1, still used by production IdentityService, and agents.agentSkillAssignment I5's new destination table) is resolved via explicit named re-exports in packages/db/src/schema/index.ts until Phase 6 drops the old table. Migration packages/db/migrations/20260716000000_agents_module_new_schema.sql (~1650 lines, incl. a same-day SECTION 10 appended after the Section 4 self-audit's own 2 fixes). Tests: apps/api/src/platform/__tests__/agents-schema.spec.ts, new file, 24/24 passing; signals-schema.spec.ts (Phase 4's own file) retrofitted with the real fixture chain above. Full apps/api suite green at 1198/1198 (serially — a known pre-existing connection-pool-exhaustion flake in rls-cross-tenant.spec.ts intermittently fails only under parallel workers, unrelated to this build). Schema-only — no AgentsService yet (every phase of this build stays schema-only until Phase 6). See PROJECT_DECISIONS #66. identity was then reopened a 6th time (2026-07-17) as Phase 6 of the same 6-phase agents-v2/v3 build authorization — the final phase, closing the entire build. identity.agent_skill (9 cols) and identity.agent_skill_assignment (10 cols) — the v1 skill-catalog tables — were DROPPED, fully superseded by agents.skill_definition/skill_version/agent_skill_assignment (built Phase 5); a pre-migration audit found agent_skill_assignment had 0 live rows and agent_skill had 13, all test debris (test.skill.%/ti3.skill.% patterns), cleaned up rather than preserved. IdentityService's skill methods (listSkillCatalog, assignSkillToAgent, removeSkillFromAgent, listAgentSkillAssignments, and the skill-check half of authorizeAgentAction) were rewritten in apps/api/src/identity/identity.service.ts to join through agents.skill_version/skill_definition instead, now at skill-VERSION granularity (skillVersionId replacing skillId throughout, listSkillCatalog's return shape flattened to {id, code, name, lifecycleStatus}); zero controllers/DTOs referenced any of these 4 methods (grep-confirmed), so no live HTTP consumer was affected. This resolves the transitional Drizzle-barrel collision Phase 5 disclosed (identity.agentSkillAssignment vs agents.agentSkillAssignment) — the old export is gone. identity is now 36 tables / 410 cols (down from 38/429). The same phase also added a new agent_reader Postgres role (mirrors consumer_authenticated's shape) scoped to files.document_chunk/document_index (GRANT SELECT + 2 new dedicated RLS policies) and EXECUTE on signals' 3 *_as_of() functions — closing the disclosed forward-ref Phase 4 (PROJECT_DECISIONS #65) left open; files and signals are structurally unchanged (grant/policy-only, no table/column impact). A real gap was found and fixed: the pre-existing authenticated-scoped RLS policies on document_chunk/document_index do not automatically extend to a new role (Postgres role-scoped policies apply only to the exact role named, confirmed live via pg_policies) — GRANT SELECT alone would have left every agent_reader query silently returning zero rows, closed with 2 new SELECT-only policies. A new agentReaderDB() connection helper mirrors consumerDB() exactly, and a new ESLint rule (scoped to src/agents/**/*.ts) bans adminDb/tenantDB imports and raw set_config(...) calls from future agent-execution code — verified firing via a temporary probe file (no src/agents/ directory exists yet, since no AgentsService is built). Honest disclosure: agent_reader/agentReaderDB() have zero real call sites today — logged as a new OPEN_ITEMS row, not treated as fully closed. 4 guards live-reproduced (tenant-scoped read, cross-tenant isolation, write rejection, EXECUTE on the 3 signals functions) via a new regression describe block in rls-cross-tenant.spec.ts (4 tests). Full apps/api suite green at 1205/1205 (up from 1201). See PROJECT_DECISIONS #67. This closes the entire 6-phase agents-v2/v3 build authorization (Phase 1 platform, Phase 2 ai, Phase 3 semantics, Phase 4 signals, Phase 5 agents, Phase 6 identity + agent_reader — PROJECT_DECISIONS #62–#67). A separately-authorized Phase 1: Security & Integrity Remediation pass then ran (2026-07-18) — a live-DB structural sweep across all 25 business schemas (not a new module, no service-layer work), governed throughout by "can a caller with ordinary privileges do the wrong thing anyway?" with every claim live-reproduced via before/after demonstration. 10 items: platform.polymorphic_target_registry lockdown (root-caused to a 2026-07-08 ALTER DEFAULT PRIVILEGES statement silently over-granting future platform tables); a 25-schema GRANT/REVOKE structural sweep off a live 325-row privilege inventory (2 GRANT-additivity bugs on already-append-only tables — approvals.approval_event/returns.return_resolution_line — 8 reference catalogs narrowed SELECT-only, 4 cross-tenant-leak tables — identity.actor/identity_user/identity_session, platform.tenant — fully REVOKEd from authenticated, confirmed zero behavioral impact since IdentityService/PlatformService both read exclusively via getAdminDb()); append-only enforcement completed on 16 more tables across platform/crm/inventory/pricing/agents (found via a "no updated_at" structural sweep of 34 candidates; 3 — billing.ar_charge_line, purchasing.vendor_credit_line, platform.subscription_invoice_line — correctly reclassified "validated-mutable" instead after column-shape inspection found a genuine reconciliation-trigger UPDATE path, now codified as SCHEMA_CONVENTIONS.md §20); consumer.event consent enforcement via a new BEFORE INSERT trigger reading the pre-existing consumer_merchant_link.consumer_opt_in seam (anonymous events unaffected); a new platform.processor_catalog reference table replacing closed-vocabulary CHECKs on payments.payment_intent.processor/tax.tax_calculation.providerplatform is now 36 tables / 524 cols (up from 35/518, the only table/column-count change this phase); a cross-module bare-FK audit off a live 115-row sweep, fixing 2 judged-safe cases (identity.agent_identity gained UNIQUE(id, tenant_id), closing the exact agents.agent_skill_assignment.agent_identity_id gap PROJECT_DECISIONS #67's own independent verification had deferred; 4 columns on purchasing.vendor_return_line) while disclosing the broader 113-row remainder and a 99.2%-orphaned identity_access_event.session_id ruling, both logged to OPEN_ITEMS rather than force-fixed; a new drift CI gate (packages/db/scripts/drift-check.ts + .github/workflows/db-drift-check.yml, the workflow itself disclosed as unexercised — this environment cannot run GitHub Actions) diffing live DB metadata against Drizzle/docs, live-reproduced by deliberately reintroducing a real GRANT-additivity bug on consumer.event and confirming the gate caught it; an ai barrel-export fix (14 missing table re-exports, which would have made the new drift gate itself blind to that schema) and a tenant_module_activation RLS Drizzle-drift fix; and 4 missing set_updated_at triggers (admin.integration_provider_catalog, pos.tender_type_catalog, tax.jurisdiction_level_catalog, returns.return_source_line_tracker). Item 10 (doc/source drift cleanup)'s original sub-item list was lost to an earlier context-compaction event in the same session and was not reconstructed from guesswork — disclosed, logged to OPEN_ITEMS as its own reopened row, with this phase's own docs fan-out serving as the closest good-faith substitute for the drift Items 1-9 themselves introduced. shared schema's own write-permission lockdown is deliberately deferred to this same effort's own Phase 2, not overlooked — its 12 tables mostly lack tenant_id altogether, needing a dedicated write-model design pass rather than this phase's tenant-scoped GRANT/REVOKE patterns. Full apps/api suite unchanged at 1205/1205 (2 pre-existing schema-wide count assertions updated 35→36/518→524 to reflect the real processor_catalog addition). See PROJECT_DECISIONS #68. A same-day pre-Phase-2 confirmation pass (2026-07-18) then closed the 2 loose ends #68 disclosed, plus triaged the 115-bare-FK finding into a real count and a scheduling recommendation, before the nursery-vertical extraction (Phase 2) begins. The drift CI gate was found to have never once been able to fire or block: this repo lands 100% of changes via direct commits to main (zero PRs, confirmed via gh pr list and git log --merges), main carries zero branch protection, and the workflow's vanilla Postgres service fails on migration #1 of 90 (missing Supabase-provisioned roles/extensions this repo's own schema depends on) — 3 stacked, independent gaps, each needing a human decision, logged to OPEN_ITEMS; the underlying drift-check.ts script itself was independently re-confirmed genuinely fail-closed (333 real failures against an empty DB, never a false PASS). identity.identity_access_event.session_id's 99%+ orphan rate was fully root-caused to a single test fixture (identity-session.spec.ts's own teardown hard-deleting identity_session rows, violating that table's documented "permanent audit history" design) and fixed (teardown corrected; identity.md's DR-21 record — which had incorrectly claimed an enforced FK + partial index since the column's original 2026-06-28 build — corrected to the real, unenforced state). The 115-bare-FK estimate was regenerated from scratch and reconciled to 230, categorized into 5 buckets (12 correctly-bare into mixed global/tenant parents — composite would be a regression; 13 + 27 cheap-but-dormant agent_identity_id/site_id fixes; 61 cheap missing-composite-FK fixes; 117 expensive ones needing a new UNIQUE(id,tenant_id) on 39 distinct parent tables first). 2 of the 117 were concrete live-code gaps — PlatformService.recordInvoice()/.createSubscription() both inserted a caller-supplied, cross-tenant-capable id with zero ownership check — fixed immediately (app-layer tenant guard + 4 new regression tests, no schema change needed) rather than deferred. The remaining ~228-row finding is recommended as its own future dedicated multi-batch remediation phase (comparable in scope to the 2-batch Header/Line Remediation effort), explicitly NOT folded into Phase 2's nursery-extraction migration. All 3 investigations were independently spot-checked by a second round of agents before being written up — every load-bearing claim held. Full apps/api suite: 1209/1209 (up from 1205 — 4 new tests, zero regressions). See PROJECT_DECISIONS #69. Phase 2 (Nursery Vertical Extraction + shared Lockdown) is now complete (2026-07-18) — 2 new schemas, nursery_ref and nursery, relocate nursery-specific concepts out of the vertical-neutral core; shared, inventory, and multi_loc are the 3 existing rows this phase touches. See PROJECT_DECISIONS #70; per-module detail in docs/modules/MODULE_INDEX.md. Phase 3 (Gift Card + Store Credit) is now also complete (2026-07-18, same day) — the first phase in this sequence to add real business capability: billing reopened for 6 new stored-value tables (gift_card, gift_card_transaction, gift_card_reversal_tracker, store_credit_account, store_credit_transaction, store_credit_reversal_tracker, +1 view), 10→16 tables / 176→260 cols; pos.sale_payment's fail-closed tender gate narrowed to payment_method != 'reward' with real composite FKs replacing the old forward-refs; returns.return_resolution gained a real store_credit_transaction_id pointer (152→153 cols). See PROJECT_DECISIONS #71; per-module detail in docs/modules/MODULE_INDEX.md.

Metric Value
Modules locked (Design-Lock passed) 27 (platform, identity, shared, multi_loc, crm, inventory, ai, pricing, pos, orders, purchasing, tax, billing, payments, admin, approvals, receiving, consumer, rewards, offers, files, returns, semantics, signals, agents, nursery_ref, nursery)
Modules shipped 0
Schemas defined 27
Tables defined 309 (platform 23 + identity 36 + shared 10 + multi_loc 1 + crm 13 + inventory 25 + ai 7 + pricing 4 + pos 9 + orders 7 + purchasing 15 + tax 2 + billing 10 + payments 9 + admin 10 + approvals 8 + receiving 2 + consumer 10 + rewards 7 + offers 7 + files 6 + returns 9 + semantics 14 + signals 11) — reflects the 2026-07-10 Header/Line Remediation inventory reopen's own +1 (24→25, PROJECT_DECISIONS #49), the same batch's billing reopen's own +1 (9→10, PROJECT_DECISIONS #51), and the same batch's identity reopen's own +1 (37→38, fix #3, PROJECT_DECISIONS #52), on top of the 2026-07-09 operator identity build's +2 to identity (35→37, PROJECT_DECISIONS #45) and the same-day net +5 from approvals (approvals +8, admin −3 off admin's true pre-reopen count of 13/161); this line's own admin figure had separately stalled at 11 since Remediation Phase 3 and was never updated for Phase 4's admin 11→13 change, corrected in a prior pass since admin's row was already being touched. The 2026-07-10 Receiving extraction (PROJECT_DECISIONS #55) added receiving as a genuinely new 17th named module (0→2, the 2 tables moved out of purchasing) and corrected purchasing's own entry directly from its previously-stale 16 to its current true 15 — this line never separately showed the intermediate 17 (reached post-Header/Line-Remediation-batch-1, PROJECT_DECISIONS #47), so the visible 16→15 drop was a stale-value correction compounded with a genuine 2-table extraction, not a 1-table net change. The 2026-07-11 Consumer Layer build (PROJECT_DECISIONS #56) adds consumer/rewards/offers as 3 genuinely new named modules to this line (+22 tables: 10+6+6) — this line had never previously named them individually, so the visible 17→20 module-count jump is 3 new entries to this list, not a delta to any existing named module's own count; the underlying consumer/rewards/offers schemas already existed in docs/modules/MODULE_INDEX.md's own broader schema tally as stale, never-reconciled v1-carryover placeholders (14 tables/204 cols combined), so MODULE_INDEX's own running total moves by the smaller +8 tables/+117 cols delta, not the full +22/+321 — see docs/modules/MODULE_INDEX.md for that reconciliation. platform/shared/pos/tax above still lag Phase 4's/Header-Line-Remediation's own 23→27/10→12/9→10/2→3 changes (pre-existing, unrelated to this update; see docs/modules/MODULE_INDEX.md for current per-module truth). The same-day 2026-07-11 rewards/offers proportional-clawback reopen (PROJECT_DECISIONS #60) adds a new reversal-tracker table to each — rewards 6→7 (+1), offers 6→7 (+1) — a +2 tables delta reopening both already-locked modules; net 221→223. The same-day 2026-07-11 returns module build (PROJECT_DECISIONS #61) adds returns as a genuinely new 22nd named module to this line (+9 tables, module #27, schema-locked at 9 tables/152 cols) — the 5 companion reopens bundled into the same migration (pos, orders, inventory, approvals, files) are constraint/CHECK-shape only with zero table/column impact, so none of their own per-module figures above move; net 223→232. The 2026-07-14 semantics build (PROJECT_DECISIONS #64, Phase 3 of the 6-phase agents-v2/v3 build authorization) adds semantics as a genuinely new 23rd named module to this line — a TRUE new schema like tax/returns before it, not a delta to an existing row (+14 tables, module-catalog module_code='semantics', schema-locked at 14 tables/114 cols); net 232→246. This line's own ai figure (7) still lags Phase 2's own 7→49 reopen (PROJECT_DECISIONS #63, table-count-only — the 227-column figure is unaffected since partition-child tables inherit their parent's columns) — pre-existing, unrelated to this update; see docs/modules/MODULE_INDEX.md for current per-module truth. The 2026-07-15 signals build (PROJECT_DECISIONS #65, Phase 4 of the same 6-phase agents-v2/v3 build authorization, this build's own explicitly-designated HIGHEST-RISK phase) adds signals as a genuinely new 24th named module to this line — a TRUE new schema like tax/returns/semantics before it, not a delta to an existing row (+11 tables, module-catalog module_code='signals', schema-locked at 11 tables/111 cols, of which 4 are partitioned — feature_value/forecast/anomaly_score weekly, outcome_observation monthly); net 246→257. The 2026-07-16 agents build (PROJECT_DECISIONS #66, Phase 5 of the same 6-phase agents-v2/v3 build authorization — module #28, the module this authorization exists to build) adds agents as a genuinely new 25th named module to this line — a TRUE new schema like tax/returns/semantics/signals before it, not a delta to an existing row (+47 tables, module-catalog module_code='agents', schema-locked at 47 tables/475 cols); net 257→304. The same migration also grows platform's own existing row by a further +1 table (polymorphic_target_registry, I6) — a delta to platform's already-lagging component in the parenthetical breakdown above (still stated as 23 there, pre-existing and unrelated to this update — live platform is now 35 tables/518 cols, up from 34/514), disclosed here rather than silently reconciled into that stale figure; net 304→305. The 2026-07-17 identity reopen (Phase 6, PROJECT_DECISIONS #67, the final phase of the same authorization, CLOSING IT) drops 2 tables from identity's own row (38→36 — agent_skill/agent_skill_assignment DROPPED, both v1 test-debris-only tables, superseded by agents.skill_definition/skill_version/agent_skill_assignment built Phase 5); net 305→303. The 2026-07-18 Phase 2 Nursery Vertical Extraction (PROJECT_DECISIONS #70) adds nursery_ref (+4 tables) and nursery (+2 tables) as 2 genuinely new named modules to this line — the same migration's changes to shared/inventory/multi_loc are column-count-only at the schema level (shared's own table loss is exactly offset by nursery_ref's gain, since the same 4 tables simply changed schema via ALTER TABLE ... SET SCHEMA), so no other figure in this parenthetical moves; net 303→309

Per-module detail (build order, schema counts, gate status): docs/modules/MODULE_INDEX.md.


E. Reading order for a new session

  1. CLAUDE.md — project orientation: how the project works, the pipeline, core rules
  2. docs/WHAT_IS_VRIDA.md — product overview: what Vrida is, who it's for, capabilities, how it's built
  3. DOCS_INDEX.md — this file: folder map, source-of-truth pointers, project status
  4. docs/modules/MODULE_INDEX.md — module catalog, schemas, build order, pipeline stage
  5. Relevant module doc: docs/modules/<module>/ for the module being worked on
  6. docs/ai/AI_CAPABILITY_PLANE.md — when working on any AI feature or Part B of design

F. Raw markdown access

All source .md files are mirrored as plain text at https://docs.vrida.app/raw/.

For AI tools: fetch the index first to get every available URL: https://docs.vrida.app/raw/index.txt — plain text, one absolute URL per line, auto-generated at build time.

Once you have that list, paste any URL from it to fetch that file directly.

Last modified: Jul 16, 2026, 9:05 AM PT
On this page
Esc