Merged PR 2405: Representation Entry Policy discovery and wrapper

Related work items: #23754
This commit is contained in:
Robert Bond
2026-06-19 08:19:56 +00:00
parent 60a2ff4594
commit be68b987a1
4 changed files with 1054 additions and 116 deletions
+3 -5
View File
@@ -52,10 +52,8 @@ import {
buildSubmitEnrichedValues,
runFinalisationSequence
} from "./utils/finalisationBoundary";
import {
buildRepresentationContext,
buildRepsArrFromContext
} from "./utils/buildRepsArrRules";
import { buildRepresentationContext } from "./utils/buildRepsArrRules";
import { getAvailableRepresentationTypes } from "../../../lib/domain/representation-type-policy/getAvailableRepresentationTypes";
let MakeRepresentation = (props) => {
let { t } = useTranslation();
@@ -334,7 +332,7 @@ let MakeRepresentation = (props) => {
isNRW
});
return buildRepsArrFromContext(ctx);
return getAvailableRepresentationTypes(ctx);
};
const repCapacityType = () => {
@@ -1,3 +1,7 @@
const {
getAvailableRepresentationTypes
} = require("../../../../lib/domain/representation-type-policy/getAvailableRepresentationTypes");
export const APPEAL_TYPES = {
SIPS: 846040002,
DNS: 846040011,
@@ -110,115 +114,6 @@ export const buildRepresentationContext = ({
};
};
export const addQuestionnaire = (options, ctx) => {
const excludedTypes = [
APPEAL_TYPES.EXCLUDE_QUESTIONNAIRE_1,
APPEAL_TYPES.DNS,
APPEAL_TYPES.EXCLUDE_QUESTIONNAIRE_2,
APPEAL_TYPES.EXCLUDE_QUESTIONNAIRE_3
];
const canAdd =
ctx.isLPA &&
!excludedTypes.includes(ctx.appealType) &&
isWithinWindow(ctx.now, ctx.startDate, ctx.finalCommentsDueDate);
if (canAdd) {
options.add(REPRESENTATION_OPTIONS.QUESTIONNAIRE);
}
};
export const addStatements = (options, ctx) => {
const isLpaAdvertPart3NoStatement =
ctx.isLPA &&
ctx.appealType === APPEAL_TYPES.ADVERT &&
ctx.specialistProcess === SPECIALIST_PROCESS.WRITTEN_REPS;
const canAddDnsLpaDocs =
ctx.isLPA &&
ctx.isDNS &&
ctx.appealType !== APPEAL_TYPES.EXCLUDE_QUESTIONNAIRE_1 &&
!ctx.isCaseOwner &&
isWithinWindow(ctx.now, ctx.startDate, ctx.finalCommentsDueDate);
if (canAddDnsLpaDocs) {
options.add(REPRESENTATION_OPTIONS.LOCAL_IMPACT_REPORT);
options.add(REPRESENTATION_OPTIONS.STATEMENT);
}
const canAddStatementForNonDns =
!ctx.isDNS &&
ctx.appealType !== APPEAL_TYPES.HOUSEHOLDER &&
!isLpaAdvertPart3NoStatement &&
!ctx.isCaseOwner &&
isWithinWindow(ctx.now, ctx.startDate, ctx.statementDueDate);
if (canAddStatementForNonDns) {
options.add(REPRESENTATION_OPTIONS.STATEMENT);
}
const canAddStatementForDnsNonLpa =
ctx.isDNS &&
!ctx.isLPA &&
!ctx.isCaseOwner &&
isWithinWindow(ctx.now, ctx.startDate, ctx.statementDueDate);
if (canAddStatementForDnsNonLpa) {
options.add(REPRESENTATION_OPTIONS.STATEMENT);
}
};
export const addFinalComments = (options, ctx) => {
const canSubmitFinalCommentsDefault =
ctx.isSelectedAppellant ||
ctx.isSelectedAgent ||
ctx.isSelectedInterestedParty ||
ctx.isLPA;
const canSubmitFinalComments = ctx.isDNS
? ctx.isLPA
: canSubmitFinalCommentsDefault;
const isSpecialistNonHearing =
ctx.specialistProcess !== SPECIALIST_PROCESS.HEARING;
const finalCommentsWindowStart = ctx.isDNS
? ctx.startDate
: ctx.statementDueDate;
const canAdd =
canSubmitFinalComments &&
isWithinWindow(
ctx.now,
finalCommentsWindowStart,
ctx.finalCommentsDueDate
) &&
(ctx.appealType !== APPEAL_TYPES.HOUSEHOLDER || isSpecialistNonHearing);
if (canAdd) {
options.add(REPRESENTATION_OPTIONS.FINAL_COMMENTS);
}
};
export const addConsultation = (options, ctx) => {
if (!ctx.isSIPS) return;
options.add(REPRESENTATION_OPTIONS.CONSULTATION_RESPONSE);
if (!ctx.isSelectedAppellant) {
options.add(REPRESENTATION_OPTIONS.LOCAL_IMPACT_REPORT);
}
if (ctx.isNRW) {
options.add(REPRESENTATION_OPTIONS.MARINE_IMPACT_REPORT);
}
};
export const buildRepsArrFromContext = (ctx) => {
const options = new Set();
addQuestionnaire(options, ctx);
addStatements(options, ctx);
addFinalComments(options, ctx);
addConsultation(options, ctx);
return [...options];
return getAvailableRepresentationTypes(ctx);
};
@@ -0,0 +1,898 @@
## Findings
## Architectural Discovery Programme
Status: Complete
Outcome:
The PEDW architectural discovery programme is considered complete for practical purposes.
No strong undiscovered business-policy candidates remain.
Future architectural work should focus on:
- documentation consolidation
- adoption planning
- characterization coverage
- extraction planning
- domain-layer adoption
rather than broad architectural discovery.
---
## Final Architectural Classification
### Business Decision Models
- Appeal Type Policy
- Representation Entry Policy
- Representation Type Availability
### Presentation Models
- Dashboard Policy
- Case Lifecycle / Journey Interpretation
- Welsh Lookup Translation
### CRM Compatibility Models
- Query Profiles
- CRM Field Compatibility
- Specialist Process Compatibility
### Workflow / Integration Models
- Submission Behaviour
- Notification Behaviour
- New Appeal Start & Resume Behaviour
- Appeal Type Availability Filtering
- Ownership Enforcement
### Mixed Areas
- Participant & Ownership
- Submission Eligibility & Journey Access
## Architectural Classification Inventory
| Area | Classification | Status |
| --------------------------------------- | --------------------------- | --------------------------- |
| Appeal Type Policy | Business Decision Model | Characterized |
| Representation Entry Policy | Business Decision Model | Characterized |
| Representation Type Availability | Business Decision Model | Substantially Characterized |
| Dashboard Policy | Presentation Model | Characterized |
| Case Lifecycle / Journey Interpretation | Presentation Model | Characterized |
| Welsh Lookup Translation | Presentation Model | Characterized |
| Query Profiles | CRM Compatibility Model | Characterized |
| Participant & Ownership | Mixed | Characterized |
| Submission Eligibility & Journey Access | Mixed | Characterized |
| Submission Behaviour | Integration / Workflow | Characterized |
| Notification Behaviour | Integration / Workflow | Characterized |
| New Appeal Start & Resume Behaviour | Workflow / Technical Access | Characterized |
---
## Domain Layer Status
### Current Status
Status: Established
The PEDW domain layer is now considered an established architectural pattern, but it remains intentionally narrow.
Current proven policy modules:
- Appeal Type Policy
- Representation Entry Policy
- Representation Type Availability
Not everything under `lib/domain/**` represents a business decision model.
The current architectural categories now in use are:
- Business Decision Models
- Presentation Models
- CRM Compatibility Models
- Workflow / Integration Models
### Policy Boundary Maturity
#### Appeal Type Policy
- Status: Established
- Adoption level: narrow, stable, selectively consumed
- Ownership clarity: high within current scope
- Characterization confidence: high
#### Representation Entry Policy
- Status: Emerging
- Adoption level: partially adopted shared seam
- Ownership clarity: moderate because meaningful consumer-specific logic remains outside the boundary
- Characterization confidence: high for extracted helpers, medium-high for broader adoption boundary
#### Representation Type Availability
- Status: Emerging → Established
- Adoption level: conceptually strong, runtime adoption incomplete
- Ownership clarity: high at policy-rule level, moderate at runtime-adoption level
- Characterization confidence: high
Representation Type Availability is the strongest proven business-policy seam.
Representation Entry Policy is partially adopted.
Appeal Type Policy is intentionally narrow and stable.
### Current Domain-Layer Interpretation
The domain layer should now be read as a deliberately small set of behaviour-preserving extraction seams rather than as a large hidden domain architecture.
This means each module still needs to be interpreted according to what it actually owns:
- some modules are genuine business-policy seams
- some modules are presentation interpretation seams
- some modules are compatibility-preserving helpers
---
## Adoption Planning Readiness
Status: Ready
The project now possesses:
- identified policy boundaries
- characterized policy behaviour
- identified consumers
- identified adoption risks
Remaining work is primarily:
- adoption planning
- characterization coverage
- extraction sequencing
- documentation refinement
---
## Programme Phase
Discovery
Characterization
Documentation
Adoption Planning
Extraction
Adoption
The project should now be considered to be entering:
**Adoption Planning**
---
## Subsequent Investigation Updates
### Participant & Ownership Investigation (Follow-up)
#### Status
Characterized
#### Classification
Mixed
#### Updated Conclusion
Follow-up investigation determined that PEDW does not currently contain a single explicit Participant & Ownership Domain Model.
The area consists of three overlapping concerns:
1. CRM Compatibility Model
2. Workflow / Orchestration Behaviour
3. Limited Participant-Driven Business Logic
Participant concepts are stable and recurring:
- Appellant
- Agent
- Interested Party
- LPA
- NRW
- Land Owner
However ownership and relationship behaviour is distributed across:
- CRM involvement mappings
- CRM relationship entities
- account registration defaults
- appeal completion side effects
- representation-capacity routing
- watch relationships
- dashboard segmentation
#### Key Finding
The strongest reusable rule seam remains:
Participant Type
Representation Capacity
Representation Type Policy
Participant concepts primarily act as inputs into existing policy models rather than forming a standalone policy model themselves.
#### Architectural Assessment
Participant & Ownership should not currently be considered a proven Domain Policy candidate.
Best current classification:
- CRM Compatibility Model (strong)
- Workflow / Orchestration Behaviour (strong)
- Business Decision Logic (limited)
#### Recommendation
No further broad Participant & Ownership discovery track is recommended unless new evidence emerges.
Confidence: Medium-High
### Domain Layer Status Verification
#### Status
Verified
#### Updated Conclusion
The PEDW domain layer is real but deliberately narrow.
It currently contains a mixture of:
- genuine business decision seams
- presentation/worklist segmentation seams
- compatibility-preserving helper seams
Follow-up verification established that not everything under `lib/domain/**` should be treated as a business-policy boundary.
Current verified status by area:
- `lib/domain/representation-type-policy/**` -> clearest current business decision module
- `lib/domain/representation-policy/**` -> partial business decision boundary with residual UI-side duplication
- `lib/domain/appeal-type-policy/**` -> narrow family-classification helper, not a broad appeal-type policy layer
- `lib/domain/dashboard-policy/**` -> presentation/worklist segmentation only
- `lib/domain/case-lifecycle/**` -> read-only journey interpretation seam, not a lifecycle business policy
#### Key Finding
The domain layer now appears best understood as an intentionally small set of behaviour-preserving extraction seams rather than a large or fully adopted domain architecture.
#### Architectural Assessment
The domain layer exists and is useful, but its boundaries are mixed and must continue to be interpreted module-by-module.
Confidence: High
### Representation Type Availability Adoption Assessment
#### Status
Characterized
#### Updated Conclusion
`lib/domain/representation-type-policy/getAvailableRepresentationTypes.js` is confirmed as the clearest current business decision module in the domain layer.
However, it is not yet the adopted runtime source of truth.
The active runtime path still uses the component-side rule engine:
- `components/case/representation/utils/buildRepsArrRules.js`
- consumed by `components/case/representation/index.js`
The two rule engines appear behaviourally equivalent for the inspected rule set:
- Questionnaire
- Statement
- Final comments
- Consultation Response
- Local Impact Report
- Marine Impact Report
- Householder behaviour
- DNS/SIPS behaviour
- NRW behaviour
- LPA behaviour
- specialist-process behaviour
#### Key Finding
The remaining uncertainty is no longer the rule bodies themselves.
The remaining uncertainty is the **context-preparation boundary**:
- the domain engine expects prepared policy context
- the component engine still owns the active context adapter and runtime consumer path
#### Architectural Assessment
Representation Type Availability remains the strongest proven business decision model, but adoption is incomplete.
Confidence: High
### Representation Type Availability Status
#### Status
Substantially Characterized
#### Classification
Business Decision Model
#### Characterized Areas
- Policy Logic
- Runtime Adoption Status
- Context Contract
- Input Provenance
#### Conclusion
The domain policy implementation and component implementation are behaviourally equivalent for the inspected rule set.
Remaining uncertainty now relates primarily to consistency of upstream input preparation rather than to policy logic itself.
Confidence: High
### Submission Eligibility & Journey Access Investigation
#### Status
Characterized
#### Classification
Mixed
#### Updated Conclusion
No coherent PEDW-wide Submission Eligibility model was identified.
Business-rule behaviour in this area is largely confined to:
- Representation Entry Policy
- Representation Type Availability
Most remaining eligibility behaviour is workflow or technical access gating.
#### Architectural Assessment
Submission Eligibility & Journey Access should currently be treated as a mixed area rather than as a proven standalone business-policy model.
Confidence: High
### New Appeal Start & Resume Behaviour
#### Status
Characterized
#### Classification
Workflow / Technical Access Model
#### Updated Conclusion
No standalone New Appeal Eligibility business-policy model was identified.
The area is primarily composed of:
- authentication guards
- route validation
- account lookup requirements
- blob-backed draft retrieval
- workflow continuation
- Redux hydration
A small amount of local product logic exists, but it does not form a reusable policy model.
#### Architectural Assessment
New Appeal Start & Resume Behaviour should be treated primarily as workflow and technical access behaviour rather than as a domain-policy candidate.
Confidence: High
### Discovery Programme Status
#### Discovery Conclusions
The majority of investigated PEDW behaviour now falls into:
- Presentation
- CRM Compatibility
- Workflow
- Integration
The primary proven business-decision seams identified so far are:
- Appeal Type Policy
- Representation Entry Policy
- Representation Type Availability
#### Architectural Observation
The architecture increasingly resembles:
Compatibility Layer
Adapter Layer
Business Policy
Presentation Layer
rather than large hidden domain models embedded throughout the application.
---
### Part 1 Discovery Inventory
Major portal behaviour areas not already covered by the completed investigations:
| Area | Primary folders/files | Responsibility summary |
| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Submission orchestration New Appeal | `components/newappeal/buildchecksection.js`, `lib/newappeal/journeyEffects.js`, `pages/api/endpoint/createcase_api.js`, `pages/api/file/createappealcompletemessage_api.js`, `pages/api/file/generateappealpdf.js`, `actions/azurestorage.js` | Orchestrates appeal save, upload, PDF generation, CRM case creation, completion queue/message creation, and confirmation side effects. |
| Submission orchestration Representations | `components/case/representation/index.js`, `components/case/representation/utils/finalisationBoundary.js`, `lib/representation/pageLoaders.js`, `pages/api/file/createrepcompletemessage_api.js`, `pages/api/file/createrepinvolvement_api.js`, `actions/azurestorage.js` | Orchestrates draft save, resume, representation payload enrichment, PDF generation, file upload, involvement creation, completion queue/message creation, and confirmation transitions. |
| Draft/save-resume storage workflow | `lib/myportal/loadMyPortalAppealPage.js`, `lib/myportal/hydrateMyPortalAppealStore.js`, `lib/representation/pageLoaders.js`, `pages/api/file/getawaitingsubmissionfromblob.js`, `pages/api/file/deleteawaitingsubmissionfromblob.js`, blob helpers in `actions/azurestorage.js` | Persists and restores in-progress appeal/representation state from blob storage and exposes “awaiting submission” worklists. |
| Participant / involvement / ownership handling | `pages/api/auth/[...nextauth].js`, `pages/api/file/createcaseinvolvement_api.js`, `pages/api/file/createrepinvolvement_api.js`, `lib/representation/pageLoaders.js`, `components/case/representation/index.js`, `pages/myportal/case/[ticketnumber].js` | Determines user role/involvement defaults, CRM contact/case links, representation capacity choices, and ownership-related flow branching. |
| Representation entry eligibility and path selection | `lib/domain/representation-policy/*`, `components/case/summary/utils/representationEntry.js`, `components/search/repsonresults.js`, `lib/domain/representation-type-policy/getAvailableRepresentationTypes.js` | Determines if representation entry is available, whether blocked messaging is shown, and which downstream representation types are offered. |
| Dashboard/worklist workflow segmentation | `lib/domain/dashboard-policy/*`, `pages/api/endpoint/getawaitingsubmission_api.js`, `pages/api/endpoint/getwatchedcases_api.js`, `pages/myportal/index.js`, `components/myportal/viewall.js`, `components/myportal/topthree.js` | Splits records into awaiting submission, watched, submitted representations, and related user-facing worklists. |
| Case lifecycle/status interpretation seam outside the already-completed case-progress conclusion | `lib/domain/case-lifecycle/*`, `components/case/status.js`, `components/case/summary/utils/caseStagesByAppealType.js` | Read-only interpretation seam for stage-journey selection and progress display, but not a proven lifecycle business model. |
| Notification/template selection behaviour | `pages/api/email/notify.js`, `actions/services/notifyService.js`, `lib/newappeal/journeyEffects.js`, `components/case/representation/index.js`, `pages/api/auth/[...nextauth].js` | Sends GOV.UK Notify emails for sign-in, partial save, and completion-style events; template choice is driven locally by flow/origin/locale. |
| CRM query-shape compatibility and data-shaping | `actions/selectQueryTypes.js`, `docs/query-profile-compatibility.md`, `pages/api/endpoint/get*` routes | Preserves CRM field/profile compatibility for downstream UI and workflow consumers. |
| Azure queue / completion message handoff | `actions/azurestorage.js`, `pages/api/file/createappealcompletemessage_api.js`, `pages/api/file/createrepcompletemessage_api.js` | Packages finalised appeal/representation data into queue/message handoff for downstream processing. |
---
### Part 2 Classification Assessment
| Area | Classification | Why |
| --------------------------------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Submission orchestration New Appeal | **Integration Model** | The code is dominated by sequencing side effects: create CRM case, write blob state, generate PDF, upload docs, send completion message, update account role. No single reusable decision module was found. |
| Submission orchestration Representations | **Mixed / Unclear** | Mostly orchestration/integration, but includes embedded participant/path decisions and representation-type branching inside UI/orchestration components. |
| Draft/save-resume storage workflow | **Integration Model** | Main concern is blob persistence, restoration, and worklist exposure. Rules appear storage/workflow-oriented, not domain-policy-like. |
| Participant / involvement / ownership handling | **Mixed / Unclear** | There are clear role concepts (Appellant, Agent, Interested Party, LPA) and involvement bindings, but logic is scattered between auth, loaders, UI, and CRM write endpoints rather than centralized as one coherent model. |
| Representation entry eligibility and path selection | **Business Decision Model** | This area already shows reusable rule logic based on appeal type, specialist process, date window, and participant/LPA status. It behaves like a true policy seam. |
| Dashboard/worklist workflow segmentation | **Presentation Model** with workflow markers | `dashboard-policy` owns only watched/submitted bucket classification. It is portal-facing segmentation of records for display, not a broader business policy. |
| Case lifecycle/status interpretation seam | **Presentation Model** | The seam explicitly says it does not own lifecycle semantics; it selects stage catalogues and progress display from CRM inputs. |
| Notification/template selection behaviour | **Integration Model** | Notification behaviour originates from journey orchestration points and auth, with local template IDs and locale checks. No coherent notification policy module exists. |
| CRM query-shape compatibility and data-shaping | **CRM Compatibility Model** | Field selection and naming are preserved to match CRM/Dataverse contracts and entity differences. |
| Azure queue / completion message handoff | **Integration Model** | Concern is message creation and downstream handoff, not business rule ownership. |
---
### Part 3 Submission Behaviour
#### Conclusion
Submission behaviour appears primarily to be **workflow orchestration plus integration concerns**, with some embedded participant/path decisions. It does **not** currently appear to be one coherent reusable business decision model.
#### Evidence
**New Appeal**
- `lib/newappeal/journeyEffects.js` is a thin effect wrapper over:
- `uploadFiles`
- `sendEmail`
- `generateAppealPDF`
- `sendCaseCompleteMessage`
- This is orchestration glue, not a rule model.
- `pages/api/endpoint/createcase_api.js` creates CRM incidents with hard-coded operational fields:
- `caseorigincode: 3`
- `servicestage: 1`
- contact binding
- appeal type binding
- LPA binding
- That suggests submission starts with an operational CRM creation contract rather than a domain decision engine.
- `pages/api/file/createappealcompletemessage_api.js`:
- loads progress blob
- mutates blob to `appealComplete`
- writes case blob
- conditionally updates contact involvement to Appellant (`846040001`) unless current involvement is Agent (`846040000`)
- calls `createCaseCompleteMessage(...)`
- This is a finalisation/handoff chain, not a reusable rule boundary.
**Representations**
- `components/case/representation/index.js` owns:
- draft update body creation
- save-vs-submit behaviour
- partial-save email trigger
- routing/redirect behaviour
- representation capacity branching
- submit enrichment
- `components/case/representation/utils/finalisationBoundary.js` contains helper extraction, but `runFinalisationSequence(...)` is still orchestration:
- set finalising UI state
- generate PDF
- update links
- upload files
- mark submit flags
- `pages/api/file/createrepcompletemessage_api.js` only validates query/hash then calls `createRepCompleteMessage(...)`.
- `pages/api/file/createrepinvolvement_api.js` checks whether a contact/case/type involvement already exists and otherwise creates one in CRM. This is a compatibility/integration write model.
**Draft handling / save-resume**
- `lib/representation/pageLoaders.js` distinguishes:
- new representation
- existing representation (`state` + `created` query)
- Existing representation resume is blob-driven via `getRepsFromBlob(...)` and `getRepsFilesBlobs(...)`.
- `getawaitingsubmissionfromblob` and related endpoints expose explicit portal draft storage concepts.
- This strongly suggests portal-owned workflow state separate from CRM lifecycle.
**Queue message creation / CRM / DMS handoff**
- `actions/azurestorage.js` includes queue client use and functions such as:
- `createCaseCompleteMessage`
- `createRepCompleteMessage`
- Completion APIs call those directly after assembling required blob/case data.
- The handoff concern is technical/integration-oriented.
#### Determination
- **Business decisions:** only partially present, mainly around who/what may submit and which representation path/type applies.
- **Workflow orchestration:** strongly present.
- **Integration concerns:** strongly present.
- **Overall:** **Mixed, but primarily workflow orchestration + integration**.
---
### Part 4 Notification Behaviour
#### Conclusion
Notification decisions originate locally in auth and journey orchestration points. Notification behaviour does **not** currently appear to be driven by a coherent reusable notification policy.
#### Evidence
- `pages/api/auth/[...nextauth].js`
- selects sign-in template by locale:
- `templateId`
- `templateIdcy`
- locale resolution comes from CRM preferred language first, then request/callback URL fallback.
- this is a local auth notification decision.
- `lib/newappeal/journeyEffects.js`
- exposes `sendPartialSaveEmailEffect(...)`
- exposes `sendCompletionEmailEffect(...)`
- but these are thin wrappers only.
- `components/case/representation/index.js`
- partial save email is triggered inside `updateRepresentation(...)`
- template ID is embedded inline: `021b0a7b-df00-41f1-b94e-c269bee98c75`
- reference is embedded inline: `PEDW-PARTIAL-REP`
- `pages/api/email/notify.js` is a generic transport endpoint to GOV.UK Notify.
- Searches found no authoritative notification-policy module governing:
- event → template mapping
- audience selection
- locale strategy across all journeys
- communication suppression/eligibility rules
#### Logic App interactions
- The inspected code and searches did not reveal a clear portal-owned Logic App policy layer.
- Notification/integration references are much clearer for GOV.UK Notify and Azure queue/storage than for Logic App-specific orchestration.
- If Logic Apps are involved downstream, the visible portal code suggests that handoff is likely integration-level rather than decision-model-level.
#### Determination
- Notification behaviour is primarily **integration/orchestration**.
- A coherent **notification policy** does **not** currently appear explicit in the inspected code.
---
### Part 5 Participant & Ownership Behaviour
#### Conclusion
A coherent participant relationship model is **partially visible**, but it is fragmented and not centralized. The repository shows stable role concepts, yet ownership/relationship behaviour is spread across CRM involvement writes, auth defaults, page loaders, and UI branching.
#### Evidence
- `pages/api/auth/[...nextauth].js` and `context/domain-flows.md` indicate newly registered/authenticated users default to **Interested Party**.
- `pages/api/file/createcaseinvolvement_api.js`
- links contact to incident via case involvement association.
- `pages/api/file/createrepinvolvement_api.js`
- maps string involvement to CRM codes:
- `appellant: 846040001`
- `interestedparty: 846040061`
- `agent: 846040000`
- `LPA: 846040012`
- checks for existing identical involvement before creating a new one.
- `pages/api/file/createappealcompletemessage_api.js`
- after appeal completion, updates contact involvement to Appellant unless current involvement is Agent.
- this is strong evidence that appeal submission can change participant identity/ownership state in CRM.
- `lib/representation/pageLoaders.js`
- determines `isLPA` from account details formatted value.
- `components/case/representation/index.js`
- branches representation capacity and form controls across Appellant / Agent / Interested Party / LPA / Land Owner.
- `isAppellant` is inferred by comparing account email with case contact email in one flow.
- `pages/myportal/case/[ticketnumber].js` and worklist logic separate “My cases”, “Watched cases”, and “Submitted representations”, indicating different user-to-case relationships.
#### Assessment
There is evidence of a meaningful participant model:
- contact role / type of involvement
- case ownership vs watched relationship
- representation capacity
- LPA vs non-LPA role
- Appellant role assignment on appeal completion
But it is not one explicit model yet. It is split across:
- CRM compatibility codes
- flow branching
- identity defaults
- involvement write endpoints
#### Determination
- **Classification:** **Mixed / Unclear**
- There is a likely discoverable participant relationship model, but it is not yet explicit.
---
### Part 6 Eligibility Behaviour
#### Conclusion
The portal does contain reusable business-rule sets for representation eligibility and type selection. Outside that area, broader “may submit” behaviour is less centralized and often tied to workflow/auth/integration state.
#### Evidence
**Strong evidence of reusable eligibility business rules**
- `components/case/summary/utils/representationEntry.js`
- uses domain policy helpers:
- `resolveRepresentationWindow`
- `canShowRepresentationButtonForAppealType`
- `canStartHouseholderRepresentation`
- `canStartCpoRepresentation`
- additionally applies appeal-type-specific branching for:
- Householder
- ROW/hearing behaviour
- Advert specialist-process behaviour
- CPO date-window gating
- also owns blocked-message suppression for non-LPA Householder.
- `lib/domain/representation-type-policy/getAvailableRepresentationTypes.js`
- applies rules using:
- appeal type family
- isLPA
- specialist process
- isCaseOwner
- selected participant flags
- start / statement due / final comments due dates
- NRW flag
- determines whether options such as Questionnaire, Statement, Final comments, Consultation Response, Local Impact Report, Marine Impact Report are available.
This is the clearest remaining business-rule area in the inspected code.
**Weaker evidence for broader “may submit”**
- `lib/representation/pageLoaders.js` enforces auth/session presence and case query prerequisites.
- `components/case/representation/index.js` branches form route/capacity based on role and current state.
- New appeal eligibility appears more tied to available appeal types and route/store setup than to a centralized submit-eligibility policy.
#### Determination
- Representation participation and type availability: **Business Decision Model**.
- Broader submit eligibility across all journeys: **Mixed**, not yet one reusable general policy.
---
### Part 7 Lifecycle Behaviour
#### Conclusion
The portal does **not** contain one explicit shared lifecycle model for Draft → Submitted → Accepted → Relayed → CRM-created → Failed → Re-submitted. Instead it contains overlapping workflow and status concepts across storage, CRM status, watched/submitted flags, and presentation seams.
#### Evidence
- `lib/domain/case-lifecycle/README.md` explicitly says the lifecycle boundary:
- is a narrow read-only seam
- does not own lifecycle semantics
- does not own progress/status calculation ownership broadly
- `pages/api/endpoint/createcase_api.js` sets `servicestage: 1` when creating a case, which looks like operational CRM state rather than a portal lifecycle abstraction.
- `getawaitingsubmission_api.js` and blob endpoints define a portal-owned “awaiting submission” concept.
- `lib/domain/dashboard-policy/README.md` classifies watched records by whether `pinswg_representationsubmitted == null`.
- `context/domain-flows.md` describes portal-visible states:
- Awaiting submission
- Pending
- Submission processing
- Submitted representations
- `components/case/representation/index.js` uses local UI flags:
- `representationSubmit`
- `representationSubmitConfirmation`
- `finaliseAppealProcess`
- `components/case/representation/utils/finalisationBoundary.js` toggles finalisation process state but does not define shared lifecycle terms.
#### Lifecycle concept assessment
| Concept | Explicit? | Where seen |
| ------------ | ------------------------------- | ------------------------------------------------------------------------------------ |
| Draft | Yes, portal-owned | blob-backed representations/appeals, awaiting-submission worklists |
| Submitted | Yes, but fragmented | representation submit flags, queue completion, watched submitted marker |
| Accepted | Not explicit as shared model | may exist in CRM status, but not as portal-owned reusable lifecycle concept |
| Relayed | Not explicit business lifecycle | relay middleware has request lifecycle telemetry, but that is transport lifecycle |
| CRM-created | Implicit only | `createcase_api.js` creates CRM case; not expressed as stable portal lifecycle state |
| Failed | Local error paths only | API error contracts, no shared lifecycle failure model |
| Re-submitted | Not found as shared concept | no coherent reusable resubmission lifecycle model surfaced |
#### Determination
- Lifecycle behaviour is **Mixed / Unclear**.
- There are explicit workflow markers, but no single shared lifecycle model.
---
### Part 8 Top 10 Remaining Discovery Candidates
| Rank | Candidate name | Classification likelihood | Confidence | Architectural value | Maintenance value | Nature of follow-up |
| ---- | ------------------------------------------------- | -------------------------------- | ---------- | ------------------- | ----------------- | ---------------------------------- |
| 1 | Appeal Type Availability Filtering Verification | Business-rule filtering check | High | High | High | Verification rather than discovery |
| 2 | Server-Side Ownership Enforcement Verification | Workflow / access enforcement | High | High | High | Verification rather than discovery |
| 3 | Representation Type Context Contract Verification | Business-policy adoption check | High | Medium-High | High | Verification rather than discovery |
| 4 | Representation Entry Divergence Verification | Consumer divergence check | High | Medium-High | High | Verification rather than discovery |
| 5 | Runtime adoption boundary documentation | Documentation / boundary clarity | High | Medium | High | Documentation consolidation |
| 6 | Characterization tests for proven policies | Safety-net verification | High | Medium | High | Verification support |
| 7 | Submission state boundary verification | Workflow/state interpretation | Medium | Medium | Medium-High | Focused verification |
| 8 | Portal workflow vs CRM lifecycle verification | Workflow / compatibility check | Medium | Medium | Medium-High | Focused verification |
| 9 | Notification event/template verification | Integration behaviour check | Medium | Medium | Medium | Focused verification |
| 10 | Query-shape dependency map verification | CRM compatibility check | High | Medium | High | Focused verification |
- Participant & Ownership Behaviour -> Characterized; not current next candidate; not a proven standalone domain-policy candidate.
- Submission Eligibility & Journey Access -> Characterized; not current highest-value next candidate; mixed rather than a coherent policy model.
- New Appeal Start & Resume Behaviour -> Characterized; not current highest-value next candidate; workflow/technical access rather than business policy.
#### Remaining Verification Candidates
1. **Appeal Type Availability Filtering Verification**
- investigate `getappealtypesfornewappeal_api`
- verify whether hidden business restrictions exist before the start UI receives available appeal types
2. **Server-Side Ownership Enforcement Verification**
- inspect create-case APIs
- inspect blob retrieval APIs
- inspect awaiting-submission APIs
- verify how ownership enforcement is implemented server-side
---
## Evidence
### Business decision evidence
- `lib/domain/representation-type-policy/getAvailableRepresentationTypes.js`
- explicit branching on appeal family, specialist process, LPA status, case owner status, date windows, selected participant, NRW.
- `components/case/summary/utils/representationEntry.js`
- explicit representation CTA eligibility and blocked-message rules.
### Presentation model evidence
- `lib/domain/dashboard-policy/README.md`
- boundary only classifies watched vs submitted representation records for dashboard consumers.
- `lib/domain/case-lifecycle/README.md`
- explicitly read-only and non-semantic.
### CRM compatibility evidence
- `pages/api/endpoint/createcase_api.js`
- direct CRM field writes and bindings.
- `pages/api/file/createrepinvolvement_api.js`
- CRM involvement entity and hard-coded type IDs.
- `actions/selectQueryTypes.js`
- differing field selections by appeal type/entity shape.
### Integration evidence
- `lib/newappeal/journeyEffects.js`
- thin wrappers around upload/notify/pdf/completion-message services.
- `components/case/representation/utils/finalisationBoundary.js`
- finalisation side-effect chain.
- `pages/api/file/createappealcompletemessage_api.js`
- blob mutation + case blob + account update + completion message.
- `pages/api/file/createrepcompletemessage_api.js`
- queue/completion handoff endpoint.
- `actions/azurestorage.js`
- storage and queue integrations.
- `pages/api/auth/[...nextauth].js`
- auth email template choice and GOV.UK Notify send.
---
## Remaining Unknowns
1. Whether participant role/involvement rules are intentional business policy or mainly CRM/write compatibility plus UI branching.
2. Whether any Logic App-based communication or downstream orchestration exists outside the inspected portal-visible code paths.
3. Whether new appeal flow contains a deeper hidden rule set for who may submit on whose behalf, beyond the visible orchestration and role reassignment.
4. Whether a distinct “submission state model” is documented elsewhere, or whether current draft/awaiting-submission/submitted/finalising states are purely incidental workflow markers.
5. Whether CRM operational statuses intentionally align with portal draft/finalisation concepts anywhere outside the case-progress presentation seam.
---
## Recommendation
Broad architectural discovery should now be considered complete.
Future work should focus on adoption-planning activity rather than on identifying additional hidden domain candidates.
Primary focus:
1. Representation Type Availability
2. Representation Entry Policy
3. Appeal Type Policy
Reasoning:
- the main business-policy seams are now identified and characterized
- the domain layer exists and is intentionally narrow
- Representation Type Availability is the strongest proven policy boundary
- Representation Entry Policy is a partially adopted policy seam with known consumer boundaries
- Appeal Type Policy is already stable as a narrow supporting boundary
- the highest-value next work is planning safe adoption of proven policy boundaries rather than continuing discovery
---
## Whether Another Discovery Slice Is Justified
**No broad discovery slice is currently justified.**
If any further work is undertaken before adoption planning, it should be limited to narrow verification or characterization-coverage confirmation.
Those activities would support planning confidence, but they would not reopen the discovery programme.
---
## Recommended Next Workstream
Domain Layer Adoption Strategy
Objective:
Plan safe adoption of proven policy boundaries.
This is an adoption-planning activity, not a discovery activity.
Primary focus:
1. Representation Type Availability
2. Representation Entry Policy
3. Appeal Type Policy
---
### Recommended Next Verification Slice
**Appeal Type Availability Filtering Verification**
Reason:
- The new-appeal start flow depends on `getAppealsTypesForNewAppeal()`.
- The client-side characterization found no strong business-policy gate in the start flow itself.
- The highest-value remaining uncertainty is whether the upstream API is already applying hidden business filtering before the UI renders options.
### Confidence Level For Recommendation
**High**
Participant & Ownership, Submission Eligibility & Journey Access, and New Appeal Start & Resume Behaviour have now all been characterized and deprioritized as broad next streams.
The latest follow-up work establishes that the architecture map has moved beyond broad discovery and toward verification of remaining filtering and ownership-enforcement assumptions.
@@ -47,6 +47,10 @@ module.exports = {
"getAvailableRepresentationTypes.js"
);
let domainSource = fs.readFileSync(domainFilePath, "utf8");
domainSource = domainSource.replace(
/import\s+\{\s*getAppealTypeFamily\s*\}\s+from\s+"\.\.\/appeal-type-policy";/,
'const { getAppealTypeFamily } = require("../appeal-type-policy");'
);
domainSource = domainSource.replace(
/export const\s+getAvailableRepresentationTypes\s*=/g,
"const getAvailableRepresentationTypes ="
@@ -57,7 +61,150 @@ module.exports = {
const domainContext = {
module: { exports: {} },
exports: {},
require,
require: (nestedModulePath) => {
if (nestedModulePath === "../appeal-type-policy") {
const appealTypePolicyPath = path.join(
rootDir,
"lib",
"domain",
"appeal-type-policy",
"index.js"
);
let appealTypePolicySource = fs.readFileSync(
appealTypePolicyPath,
"utf8"
);
appealTypePolicySource =
appealTypePolicySource.replace(
/export\s+\{\s*getAppealTypeFamily\s*\}\s+from\s+"\.\/getAppealTypeFamily";/,
'const { getAppealTypeFamily } = require("./getAppealTypeFamily");\nmodule.exports = { getAppealTypeFamily };'
);
const appealTypePolicyContext = {
module: { exports: {} },
exports: {},
require: (appealModulePath) => {
if (
appealModulePath ===
"./getAppealTypeFamily"
) {
const getAppealTypeFamilyPath =
path.join(
rootDir,
"lib",
"domain",
"appeal-type-policy",
"getAppealTypeFamily.js"
);
let getAppealTypeFamilySource =
fs.readFileSync(
getAppealTypeFamilyPath,
"utf8"
);
getAppealTypeFamilySource =
getAppealTypeFamilySource.replace(
/import\s+\{\s*mapAppealType\s*\}\s+from\s+"\.\.\/case-lifecycle\/mapAppealType";/,
'const { mapAppealType } = require("../case-lifecycle/mapAppealType");'
);
getAppealTypeFamilySource =
getAppealTypeFamilySource.replace(
/export function\s+getAppealTypeFamily\s*\(/,
"function getAppealTypeFamily("
);
getAppealTypeFamilySource +=
"\nmodule.exports = { getAppealTypeFamily };\n";
const getAppealTypeFamilyContext = {
module: { exports: {} },
exports: {},
require: (caseLifecyclePath) => {
if (
caseLifecyclePath ===
"../case-lifecycle/mapAppealType"
) {
const mapAppealTypePath =
path.join(
rootDir,
"lib",
"domain",
"case-lifecycle",
"mapAppealType.js"
);
let mapAppealTypeSource =
fs.readFileSync(
mapAppealTypePath,
"utf8"
);
mapAppealTypeSource =
mapAppealTypeSource.replace(
/export const\s+/g,
"const "
);
mapAppealTypeSource =
mapAppealTypeSource.replace(
/export function\s+mapAppealType\s*\(/,
"function mapAppealType("
);
mapAppealTypeSource +=
"\nmodule.exports = { caseTypeAliases, caseTypeKeyByAppealTypeId, normaliseCaseType, mapAppealType };\n";
const mapAppealTypeContext =
{
module: {
exports: {}
},
exports: {}
};
vm.runInNewContext(
mapAppealTypeSource,
mapAppealTypeContext,
{
filename:
mapAppealTypePath
}
);
return mapAppealTypeContext
.module.exports;
}
return require(
caseLifecyclePath
);
}
};
vm.runInNewContext(
getAppealTypeFamilySource,
getAppealTypeFamilyContext,
{
filename:
getAppealTypeFamilyPath
}
);
return getAppealTypeFamilyContext.module
.exports;
}
return require(appealModulePath);
}
};
vm.runInNewContext(
appealTypePolicySource,
appealTypePolicyContext,
{
filename: appealTypePolicyPath
}
);
return appealTypePolicyContext.module.exports;
}
return require(nestedModulePath);
},
Date
};