diff --git a/context/api-grouping-adoption-roadmap.md b/context/api-grouping-adoption-roadmap.md index 3cd3243c..d08afaec 100644 --- a/context/api-grouping-adoption-roadmap.md +++ b/context/api-grouping-adoption-roadmap.md @@ -259,6 +259,65 @@ It means: - excluded adjacent search journeys remain unchanged - no route migration, deletion, or contract change has occurred +#### API Grouping Façade Rollout Checkpoint + +- The first successful façade slices now establish a proven additive rollout pattern for: + - subscriptions / watched cases + - documents / published document metadata + - public search results +- Proven runtime shape: + +```text +UI journey +→ existing service layer +→ grouped façade route +→ legacy canonical handler +→ unchanged integration behaviour +``` + +- Proven implementation pattern: + - audit the active journey path first + - identify the existing owning/common service layer already used by that path + - add grouped façade routes only for the proven in-scope active calls + - implement each grouped route as a tiny wrapper that delegates directly to the existing legacy handler + - keep the legacy handler as the canonical implementation + - adopt the grouped route in the existing service layer only where the active journey path is proven and low-risk + - leave adjacent, inactive, support, or orchestration-heavy routes out of scope unless separately justified + - prove the slice with lightweight characterization tests covering delegation, service adoption, retained legacy handlers, and unchanged scope boundaries +- Important interpretation: + - this checkpoint proves a safe rollout pattern for additive façade grouping + - it does not approve route removal + - it does not approve handler replacement + - it does not approve broad migration + - it does not approve grouping of more complex API families without another bounded audit + +#### Vertical façade slice completion criteria + +- A vertical façade slice should be treated as complete only when: + - active journey paths are audited + - grouped façade routes exist for the in-scope active routes only + - the service layer adopts the grouped routes where safe and proven + - legacy handlers remain present and canonical behind the façade + - inactive or support routes are explicitly left out of scope where they are not part of the active adopted path + - characterization tests prove façade delegation and service-layer adoption + - characterization tests do not imply that legacy handlers can be removed + - documentation records the exact scope, completion meaning, exclusions, and non-goals +- A slice is not complete merely because grouped files exist. +- Completion requires both bounded adoption evidence and explicit documentation of what remains outside scope. + +#### Do not group yet criteria + +- Grouping should be delayed when any of the following apply: + - no clear service layer exists + - active ownership is unclear + - the route is orchestration-heavy + - the route crosses storage + queue + CRM boundaries + - the route is auth/session critical + - the route is not proven active in a bounded current journey + - grouping would require frontend refactor rather than additive service-level adoption + - grouping would require contract change +- In these cases, documentation may still classify the area conceptually, but additive façade rollout should wait for a safer bounded slice. + --- ### Stage 3 — Documentation Maturity diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index cb7f208f..c57d7649 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -20,6 +20,73 @@ Follow-ups: ### CL-2026-06-25-API-GROUPING-FACADE-SEARCH-VERTICAL-SLICE: active public search results grouping and service adoption +### CL-2026-06-25-API-GROUPING-FACADE-ROLLOUT-CHECKPOINT: proven rollout pattern and guardrails checkpoint + +date: 2026-06-25 +author: Cline +scope: `context/api-grouping-adoption-roadmap.md`, `tests/phase22/api-grouping-facade-rollout.test.cjs`, `memory-bank/change-log.md` +type: milestone +rationale: Record a short checkpoint after the first successful additive API grouping façade slices so the proven rollout pattern, completion criteria, and delay/guardrail criteria are documented before considering more complex API groups. +impact: Documentation and characterization only; no runtime behaviour, handler implementation, service URL, route location, payload, contract, auth/session behaviour, or integration behaviour change. +status: completed + +Summary: + +- Confirmed the required checkpoint context was read before implementation: + - `context/api-grouping-plan.md` + - `context/api-grouping-adoption-roadmap.md` + - `context/api-route-map.md` + - `context/portal-api-platform-assessment.md` + - `context/architecture.md` + - `memory-bank/change-log.md` + - existing façade tests under `tests/phase22` +- Confirmed the proven slice pattern now established by the completed façade areas: + - `pages/api/subscriptions/*` + - `pages/api/documents/*` + - `pages/api/search/*` +- Recorded the rollout checkpoint in the adoption roadmap with the proven shape: + - UI journey + - existing service layer + - grouped façade route + - legacy canonical handler + - unchanged integration behaviour +- Documented completion criteria for a vertical façade slice, including: + - bounded active-journey audit + - grouped façade coverage for in-scope active routes only + - safe service-layer adoption + - retained canonical legacy handlers + - explicit out-of-scope treatment for inactive/support routes + - characterization proof and scope documentation +- Documented “do not group yet” criteria for cases such as: + - unclear ownership + - missing clear service layer + - orchestration-heavy routes + - storage + queue + CRM crossing routes + - auth/session-critical routes + - unproven activity + - required frontend refactor + - required contract change +- Added a lightweight meta-test to characterize the checkpoint structure without introducing a larger framework. + +Validation: + +- Focused validation only for this checkpoint: + - `npx eslint tests/phase22/api-grouping-facade-rollout.test.cjs` + - `node tests/phase22/api-grouping-facade-rollout.test.cjs` + - `node tests/phase22/api-grouping-facade-subscriptions.test.cjs` + - `node tests/phase22/api-grouping-facade-documents.test.cjs` + - `node tests/phase22/api-grouping-facade-search.test.cjs` +- No heavy repo-wide tooling run. + +Follow-ups: + +- Use this checkpoint as the baseline before considering any further façade candidates. +- Prefer the next candidate only where the same additive pattern can be proven without route movement, contract change, or orchestration-risk expansion. + +--- + +### CL-2026-06-25-API-GROUPING-FACADE-SEARCH-VERTICAL-SLICE: active public search results grouping and service adoption + date: 2026-06-25 author: Cline scope: `pages/api/search/basic-paged.js`, `pages/api/search/advanced.js`, `pages/api/search/advanced-paged.js`, `pages/api/search/basic-details-paged.js`, `actions/services/searchDirectService.js`, `tests/phase22/api-grouping-facade-search.test.cjs`, `context/api-grouping-adoption-roadmap.md` diff --git a/tests/phase22/api-grouping-facade-rollout.test.cjs b/tests/phase22/api-grouping-facade-rollout.test.cjs new file mode 100644 index 00000000..af78a78f --- /dev/null +++ b/tests/phase22/api-grouping-facade-rollout.test.cjs @@ -0,0 +1,103 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const rootDir = path.resolve(__dirname, "..", ".."); + +const tests = []; +const test = (name, fn) => tests.push({ name, fn }); + +const exists = (relativePath) => + fs.existsSync(path.join(rootDir, relativePath)); + +const read = (relativePath) => + fs.readFileSync(path.join(rootDir, relativePath), "utf8"); + +test("known grouped facade folders exist for proven slices", async () => { + [ + "pages/api/subscriptions", + "pages/api/documents", + "pages/api/search" + ].forEach((folderPath) => { + assert.strictEqual(exists(folderPath), true, folderPath); + }); +}); + +test("known façade characterization test files exist", async () => { + [ + "tests/phase22/api-grouping-facade-subscriptions.test.cjs", + "tests/phase22/api-grouping-facade-documents.test.cjs", + "tests/phase22/api-grouping-facade-search.test.cjs" + ].forEach((filePath) => { + assert.strictEqual(exists(filePath), true, filePath); + }); +}); + +test("existing slice tests cover service adoption or retained scope checks", async () => { + const subscriptionsSource = read( + "tests/phase22/api-grouping-facade-subscriptions.test.cjs" + ); + const documentsSource = read( + "tests/phase22/api-grouping-facade-documents.test.cjs" + ); + const searchSource = read( + "tests/phase22/api-grouping-facade-search.test.cjs" + ); + + assert.match( + subscriptionsSource, + /portalDirectService watched-case methods now target subscriptions facade routes/ + ); + assert.match( + documentsSource, + /searchDirectService document journey methods now target grouped documents facade routes/ + ); + assert.match( + searchSource, + /searchDirectService active public search methods now target grouped search routes/ + ); + assert.match( + searchSource, + /excluded routes remain outside grouped public search adoption scope/ + ); +}); + +test("checkpoint tests do not imply legacy handlers can be removed", async () => { + const sliceSources = [ + read("tests/phase22/api-grouping-facade-subscriptions.test.cjs"), + read("tests/phase22/api-grouping-facade-documents.test.cjs"), + read("tests/phase22/api-grouping-facade-search.test.cjs") + ].join("\n"); + + assert.doesNotMatch(sliceSources, /legacy handlers can be removed/i); + assert.doesNotMatch(sliceSources, /delete legacy/i); + assert.match(sliceSources, /legacy .* remain/i); + + const roadmapSource = read("context/api-grouping-adoption-roadmap.md"); + assert.match(roadmapSource, /does not approve route removal/i); + assert.match( + roadmapSource, + /legacy handlers remain present and canonical/i + ); +}); + +async function run() { + let failures = 0; + + for (const { name, fn } of tests) { + try { + await fn(); + console.log(`✓ ${name}`); + } catch (error) { + failures += 1; + console.error(`✗ ${name}`); + console.error(error); + } + } + + if (failures > 0) { + process.exit(1); + } +} + +run();