Law Office Reference Public

Research & Evidence

The evidence trail behind this guide: which live Law Office module files were read, what the database harnesses proved about visibility, workflow and conflicts, and the design precedents reused.

Guide version: r1 Module version: 1.0.0 Updated: 2026-08-20 Estimated time: 6 min 5 views 0% helpful
Evidence

Research & Evidence

This guide was written against the live module source, not from intent. This page records what was verified, and how.

Primary sources read

Claim areaVerified against
Module identity, permissions, menusmodule.json, config/module_config.php, config/permissions.php, config/menu.php + default_menu.php
Workflow rulesconfig/statuses.php (the single state-map source) + libraries/Law_office_workflow.php (acceptance guard, close guard, verified immutability, supersede chain)
Conflict search behaviorlibraries/Law_office_conflicts.php (four passes, ≤20 terms, involvement freezing) + helpers/law_office_helper.php (normalization, accent folding, SOUNDEX, token matching)
Visibility & confidentialitylaw_office_matter_visibility_sql() and its use in the matters/docket/deadlines/tasks/events models; intake scoping in Law_intakes_model; law_access_logs writers
Docket & sweepcontrollers/Law_office_docket.php, jobs/DeadlineReminderSweepJob.php (ladder, dedupe marker, writes-before-notify)
Documents pipelinelibraries/Law_office_documents.php (CityBPLO hybrid: move_temp_file_smart() + central general_files row + module linkage)
APIsconfig/api_routes.php / euapi_routes.php / api_scope.php, both controllers, and the regenerated OpenAPI specs

What the database harnesses proved

Each build phase closed with a throwaway seeder harness run through the full CI stack against the real database (then deleted). Highlights of what passed:

  • Visibility (14 checks) — an outsider's list excludes a restricted matter; team member, lead and bypass-holder see it; direct access denied to the outsider; a permitted restricted view writes the access log.
  • Conflicts (17 checks) — a withdrawn intake's party still hits a later search; “Gerardo Yap” finds “Gerardo M. Yap” (token pass — added when the initial run missed it); dispositions cannot be edited or deleted; acceptance blocked until a clearing disposition exists; latest disposition wins.
  • Sweep (12 checks, real cron:run twice) — each ladder rung fires exactly once; second run is a no-op; a restricted matter's hearings and deadlines are invisible to outsiders in the calendar and register while its own team's reminders still fire.
  • Work product (24 checks) — task lifecycle with the completer stamp; the restricted-matter rule holds for cross-matter task reads; document store/download/remove round-trip (S3 mode); the exactly-one-owner rule refuses both-owner and no-owner rows; the source-document dropdown contract.
  • Demo seeder — load (37 rows) → idempotent re-run (0) → --remove (37 soft-deleted) → re-run restores the same rows.

Standing test suite

php erpat module:test LawOffice — 7 classes / 45 tests pinning: the manifest and its version↔CHANGELOG agreement, the frozen 25-key permission catalog, live-menu↔default-menu lockstep, nav descriptions, route→controller resolution, the module gate in every controller, the widgets pair, the API sidecars and self-only EU identity, the visibility fragment's four arms and int-cast safety, all three workflow state maps, and the conflict normalization primitives (including accent folding — José/Jose must share one corpus key, a bug found and fixed by this very test).

The schema-drift guard (added after two shipped bugs)

Two defects reached users that no linter or behavioral test could see, because both were code referencing a database column the migrations never create:

VersionDefectHow it presented
0.1.7The Matter Portfolio report grouped by law_practice_areas.title; that table's label column is name.The report crashed outright.
0.1.8The Integration API read court; matters store court_name + court_branch. The demo seeder had the same typo.Silent. The API returned an empty venue for every litigation matter, and the seeder's schema-drift filter quietly discarded the key.

The second was found by executing every report, dashboard, widget and API query against a live database — something the unit suite cannot do. That runtime harness is preserved (documentation-only) under specs/harnesses/ with instructions for re-running it after any query change; its last run was 58 checks, 0 failures.

The class is now closed statically by LawOfficeSchemaContractTest, which reads the module's own migrations for the authoritative column list and asserts that every SQL column reference, every demo-seeder write key and every API projection property actually exists. It was mutation-tested: each of the three shipped defects was deliberately re-introduced and confirmed caught — the first attempt missed the seeder case (the scanner only read inline arrays, while the seeder builds rows in a variable), which is precisely why a new guard is not trusted until it has been made to fail.

The pre-sign-off security audit

Before release sign-off the module was audited across six dimensions (column drift, language keys, controller→view contracts, undefined global functions, visibility leakage, permission-gate coverage), with every candidate finding independently re-examined by a reviewer instructed to refute it. Result: 8 confirmed, 3 refuted.

The important find was a class no amount of query-execution testing could reach, because the queries were all correct — the flaw was in who was authorized. Five write endpoints (matter tasks, matter parties, docket events, deadlines, intake parties) checked permission against the parent id sent by the browser while saving to the row id sent by the browser. Since the data layer updates by primary key alone, a user with access to a single matter could have overwritten — and moved — records belonging to matters or confidential intakes they could not see, including restricted ones. Updates now resolve the parent from the stored record; matter-scoped rows are additionally pinned so they cannot be re-parented, while docket rows (where moving between matters is a legitimate action) require access to both ends.

Two disclosure defects were fixed alongside: the Parties registry's "Matters" count was not visibility-scoped (a party appearing only in a restricted matter showed a count while their matter tab showed none — the mismatch revealed the hidden matter), and the confidential-intake list reported the firm-wide intake total to users who could only see their own.

Three further candidates were correctly refuted on evidence rather than accepted, which matters as much: a NOT-NULL concern that cannot occur because strict mode is disabled platform-wide and the display path is guarded, and two count-disclosure claims for tables whose totals nothing in the interface ever renders.

Every defect found across versions 0.1.7–0.1.9 — eleven in total — was then deliberately re-introduced one at a time to confirm the test suite catches it. The first version of the authorization guard failed that exercise (four of five bugs slipped past, because its code parser desynchronized on an apostrophe inside a comment); it was rebuilt on PHP's own tokenizer and now self-tests its parser. A guard that has never been made to fail is not a guard.

Design precedents reused (not reinvented)

PrecedentReused for
modules/Barangay KP deadline sweep + notification bridgeThe reminder job shape and the module-owned bell/email delivery (zero core edits).
modules/CityBPLO document pipelineUpload/download/delete with the central general_files registry.
modules/Community / OrganizationHub API conventionsRoute fragments, scope sidecar, module-gate 404, projection shaping.
Core DataTableTraitServer-side listings and model-pushed API pagination.

Full plan & status trail

The complete implementation plan (77 tasks, phases A–G) and the per-phase status log — including every deviation and its rationale — live in modules/LawOffice/specs/law-office-implementation-plan.md and law-office-status.md.

Was this guide helpful?

Report a content problem