Skip to content

Registered Office — full connected module ​

Status: Delivery spec for the complete ROF product. Not code. Does not replace frozen D1-5-ROF.
Date: 14 October 2026
Sidebar name: Registered Office
Frozen module code: ROF
Harness workflow: ROF.HANDLE_MAIL (already stubbed in @breezycorp/agent-runtime)
RBAC product (today): CORRESPONDENCE — same product, older name. New screens say Registered Office.

Sole system: the monorepo. aima-mvp is reference only. No further feature work lands in AIMA.

This document is the connected build: AIMA’s working mail pipeline + Helpdesk tickets + the frozen ROF loop (draft → human release → send to client → store → deadline flag). It is one module with two outbound loops, not two products.


1. What the full module is ​

The firm is the registered office for client companies. Physical and agency mail arrives here. Staff must:

  1. Take the scan in.
  2. Split a multi-client PDF into letters.
  3. Know which company and which account manager.
  4. Tell the mailroom the scan was processed (and give ticket numbers).
  5. Open a Helpdesk ticket so Secretarial / Accounting can do the real work.
  6. Draft a client email (“we received this at your registered office”).
  7. Let the account manager release that email (nothing goes to the client without that click).
  8. Store the scan and the notification on the company.
  9. If the letter implies a statutory date, flag a deadline. Do not file at ACRA or reply to IRAS — that is Secretarial or Tax.

ROF routes and notifies. It does not execute the letter.


2. Three pieces we already have — connect them ​

PieceWhat it is todayRole in the connected module
AIMA / @breezycorp/correspondenceWorking OCR, split, classify, extract, match scoringThe read engine. Do not rewrite.
HelpdeskChatwoot inbox → ticket; Mailboxes settings; reply jobThe front door (which inbox) and the staff work queue (tickets).
ROF.HANDLE_MAILStub workflow: extract → resolve → draft → rules → tier-3 release → apply (send, store, flag deadline)The client-notify spine. Replace mock extract with AIMA output.

Do not add a fourth Gmail poller. Do not keep AIMA as a second app.


3. Binding decisions ​

#Decision
D1No code in aima-mvp.
D2No correspondence.gmail-poll. Chatwoot is the mailbox.
D3The fork is a mailbox setting, not a subject keyword.
D4No Helpdesk ticket on arrival for that inbox.
D5One Helpdesk ticket per extracted letter, after match (or manual match).
D6Reply on the scanner Chatwoot thread when the run has ticketed what it can.
D7Match Entity (UEN / name), then HelpdeskCustomer. No live Odoo JSON-RPC.
D8Reuse AIMA logic/ + ocr/. Rewrite only persistence and wiring.
D9Sidebar label is Registered Office (/dashboard/rof). Helpdesk stays Helpdesk. They link both ways.
D10Client email is a tier-3 ROF.RELEASE_NOTIFICATION. Never auto-send to the client.
D11Deadlines are flags only. ROF does not file or compute the full statutory calendar.
D12One scan email = one Run. One logical letter = one Item. One item = one ticket + one release (when ticketable).
D13Storage keys are server-owned. Product segment on disk: registered-office.

4. End-to-end (full module) ​

A later email on the same scanner conversation, while the run is still open, belongs to that run. After the run is DONE, a new scanner email on that thread starts a new run linked to the previous one.


5. Inbox setting ​

Where: Helpdesk → Settings → Mailboxes.

Switch (default off):

Treat as registered-office mail
This inbox does not open a ticket on arrival. Mail goes to Registered Office. Tickets and the client draft happen after processing.

Field name: registeredOfficeIntake (boolean, default false).
UI copy may say “Registered-office mail”. Do not name the column unprocessed.

Rules — mailbox ​

IDRuleSeverity
R-MB-01New mailboxes default off. Fail-safe: ordinary mail still becomes tickets.Block
R-MB-02More than one mailbox may be on (mailroom + backup).—
R-MB-03Turning the flag on does not rewrite historical tickets.—
R-MB-04Turning the flag off does not delete open runs. New mail uses the ticket path; open runs finish.—
R-MB-05Whitelist and junk filter are not applied on an RO inbox. The sender is the scanner.Block
R-MB-06Out-of-office auto-reply is off on an RO inbox. The only automated mail on that thread is the done-reply.Block
R-MB-07Done-reply and client notify from a ticket need a Chatwoot bot token. Without it, process and tickets still run; send is failed + retryable.Warn
R-MB-08Subject keywords are not the fork. ROSMAIL / ROSDOC / Physical Mail Scan are parsed inside the run for MRID/DRID only.—

6. Why the ticket is late ​

Helpdesk assumesRegistered-office mail
Sender is the clientSender is the scanner
One email, one companyOne PDF, many addressees
Match from From/To/CcMatch from OCR UEN / legal name

A ticket on arrival is an Unassigned row with no company and a 12-page PDF. The Run is the process. The ticket is an output.


7. Objects ​

All new/changed tables: tenant_id, RLS, fail-closed. Do not revive parked CorrespondenceDocument / GmailIntakeQueue.

Prefer names RofRun / RofItem (module code ROF). If the first migration already says Correspondence*, keep the table names and use ROF in the UI — do not dual-write.

7.1 RofRun ​

One inbound scanner email.

FieldMeaning
mailbox, tenantThe flagged inbox
chatwoot conversation + message idsDedupe and scanner reply
subject, from, RFC Message-IDDisplay + MRID parse
MRIDFrom subject or minted ROSMAIL + date + sequence
source FileOriginal PDF, SHA-256
previousRunIdIf this is a follow-up scan on a DONE thread
status§8
countsitems, review, matched, ticketed, released
scannerReplyMessageIdThe done-reply, when sent

Idempotency: (tenantId, chatwootMessageId) unique.

Duplicate scan: same SHA-256 on the same mailbox within a short window → do not start a second run; attach a note on the existing one.

7.2 RofItem ​

One logical letter after split.

FieldMeaning
run, index, page rangeSplit identity
DRIDMinted per item
label, confidence, method, rationaleClassification
name, UEN, document type, document date, sender name/addressExtract
impliedDeadlineType, impliedDueDateFrom extract / rules
split FileSliced PDF
matchStatuspending / matched / ambiguous / no_match / error / manual
entityId, helpdeskCustomerIdPlatform + ticket company
matchScore, matchMethoduen / name_exact / name_fuzzy / manual
ticketIdOne item, one ticket
agentRunIdThe ROF.HANDLE_MAIL run for this item
draft subject/bodyClient notification
releaseStatusnone / drafted / awaiting_release / released / skipped
deferredAt, closedAt, ticketedAt, releasedAtAIMA-style parks

A run with no usable attachment still exists (REVIEW, zero items). Mail is never silent-dropped.

7.3 Reused rows ​

RowUse
FileOriginal + each split + stored notification artefact
EntityClient master
HelpdeskCustomer / HelpdeskTicketStaff work
AgentRun / ProposedAction / ReviewTaskROF.HANDLE_MAIL + tier-3 release
Document (ROF_INBOUND, ROF_NOTIFICATION)Already written by rof-apply
StatutoryDeadlineFlag only, via upsertStatutoryDeadline
AuditEvent / recordEventLifecycle

7.4 Hidden scanner thread ​

The done-reply must not create a staff-visible Unassigned ticket.

PreferenceHow
FirstSend on Chatwoot conversation + mailbox bot token without a ticket
FallbackHidden system ticket per run, excluded from every Helpdesk queue

8. Status machines ​

8.1 Run ​

IDRule
R-RN-01INTAKE → PROCESSING only after the source File exists.
R-RN-02PROCESSING → REVIEW if any non-deferred, non-closed item needs review (§12).
R-RN-03PROCESSING → READY only if every remaining item is MATCHED or MANUAL_MATCH, or the operator chose Continue with only deferred/closed leftovers.
R-RN-04Ticket create before scanner reply so the email can cite HD-….
R-RN-05Scanner-reply failure does not roll back tickets. Stay REPLYING, retry.
R-RN-06DONE is terminal for this scan. Un-deferring later creates a follow-up ticket, not a second done-reply.
R-RN-07Client release can still be in flight while the run is ITEMS_OPEN. The run is not DONE until every ticketable item is released or skipped.
R-RN-08FAILED is retryable. Do not ticket from FAILED.

ITEMS_OPEN means: mailroom loop finished; client loop may still be on Release.

8.2 Item ​

IDRule
R-IT-01Ticket only from MATCHED or MANUAL_MATCH. Never from AMBIGUOUS / NO_MATCH / ERROR.
R-IT-02DEFERRED does not block READY if the operator Continues. No ticket until un-deferred and matched.
R-IT-03CLOSED = junk / blank verso / duplicate. No ticket, no client email.
R-IT-04One item → at most one ticket (ticketId unique).
R-IT-05One item → at most one ROF.HANDLE_MAIL AgentRun.
R-IT-06SKIPPED notify still keeps the ticket. The letter was received; staff may still act.

8.3 Helpdesk ticket ​

Normal helpdesk machine (new_mail → … → closed). ROF does not own it after create.


9. Mapping onto ROF.HANDLE_MAIL ​

The stub workflow is already:

ingestMail → extract → resolveEntity → draftNotification → evalRules → release (proposes ROF.RELEASE_NOTIFICATION)

Connect it like this. Do not invent a second harness.

Stub stepConnected implementation
1 ingestMailhelpdesk.ingest + stored File. Channel is Chatwoot, not EMAIL_M365.
2 extractAIMA OCR + classify + extract. Not MockMailExtractionAdapter in production.
3 resolveEntityAIMA match scoring against Entity (§11).
4 draftNotificationExisting draftRofNotification (Claude) + templates per letter type.
5 evalRulesExisting SG_ROF_PACK (§13).
6 releaseExisting tier-3 proposed action + ReviewTask.
applyExisting rof-apply: send, ROF_INBOUND + ROF_NOTIFICATION Documents, upsertStatutoryDeadline. Send goes through Helpdesk/Chatwoot to the client contact, not the scanner.

Granularity: the stub assumed one AgentRun per scan. The connected module starts one AgentRun per RofItem once that item is ticketed (entity is known). The RofRun is the mailroom container; the AgentRun is the client-notify loop.


10. Read pipeline (AIMA — do not rewrite) ​

StepKeep fromChange
Byteshelpdesk.attachmentChatwoot URL, not Gmail download
Store + hash@breezycorp/storage_inbox/… then file under entity registered-office/documents/…
MRID/DRIDmail-id.ts, mail-subject.tsKeep
OCRVision async GCS + pdf-parseKeep; do not force sync Vision adapter
Splitbarcode + Vision + Claude pagesKeep
Classifydocument-classify.tsKeep labels
Extract rowsocr-clients-pipeline.tsInject Claude as today
Review predicatesocr-clients-review.tsRename odoo_match_* → match_*
Scoreodoo-match-helpers.tsCandidates = Entity rows
Odoo JSON-RPC—Dead on the live path
Gmail dispatch—Scanner = helpdesk.reply. Client = rof-apply after Release

Un-quarantine packages/correspondence logic/ + ocr/. Point RofAdapters.extraction at that pipeline.


11. Classification rules ​

Labels (unchanged from AIMA):

IRAS · ACRA · MOM · BANK_FINANCIAL · LEGAL · UTILITY_PROPERTY · GENERAL · UNKNOWN

IDRule
R-CL-01Rules run first. Each label has weighted phrase patterns (IRAS full name, GST+tax, ACRA full name, MOM, bank/statement, legal/ECT, utility/property, etc.).
R-CL-02Winner = highest weighted score. UNKNOWN if nothing fires or scores tie with no clear winner.
R-CL-03If rules confidence is below the LLM threshold (default 90) and classification LLM is on, call Claude. Method becomes rules_then_llm or llm.
R-CL-04LLM cannot invent a new label. Output must be one of the eight.
R-CL-05Operator may override the label. Override is audited. Team on the ticket uses the final label.
R-CL-06Do not rename labels to SOP vocabulary in this build. Mapping tables can come later.

Letter type → implied deadline ​

Used by extract + SG.ROF.DEADLINE_FLAGGED.

Signal on the letterimpliedDeadlineType
Annual return / AR reminder / ACRA ARANNUAL_RETURN
AGM / annual general meetingAGM
ECI / estimated chargeableECI
Form C / C-S / income tax returnTAX_RETURN
GST return / F5GST_RETURN
Pass expiry / renewal (MOM)PASS_RENEWAL
None of the abovenone
IDRule
R-CL-07Deadline type is a flag hint, not a computed due date. If OCR has a date, store impliedDueDate; else apply may use “flag as due today” only as a last resort (current stub). Prefer an extracted date.
R-CL-08ROF never creates a FilingTransaction.

12. Match rules ​

Order per item:

  1. normalizeUen → Entity.registrationNumber (tenant, not deleted). One hit → matched, method uen.
  2. Else score legalName, formerNames, trading-name metadata (normalizeOrgName, bestNameScore).
  3. Then HelpdeskCustomer for the ticket.

Score bands (AIMA / SOP) ​

BandScoreOutcome
High≥ 92matched if a clear winner
Mid85–91ambiguous, except a single mid candidate → matched (live AIMA behaviour)
Low< 85no_match
Tietop two within 1 pointambiguous
IDRule
R-MT-01Never match from the scanner From-line.
R-MT-02No address match in this build.
R-MT-03No live Odoo res.partner search.
R-MT-04Soft-deleted entities are invisible.
R-MT-05Two high-scoring entities → ambiguous, never pick silently.
R-MT-06On match, resolve HelpdeskCustomer (entity link, else UEN/name/externalRef). If none, create one from the Entity. A matched item must not have a null customer.
R-MT-07Account manager = Entity.assignedExecutiveId, else group relationship manager. Same person on the ticket’s accountManagerId and on the release ReviewTask.
R-MT-08Manual match requires a picked Entity. Audited as method manual.
R-MT-09Contact for client notify: HelpdeskContact preferred RO-mail recipient, else any client contact email, else company email. Missing contact blocks Release (R-RL-03), not ticket create.

13. Review rules (match quality) ​

An item needs review when it is not deferred / closed / ticketed, and any of:

IDCondition
R-RV-01Extract or classification confidence < 70 (AIMA) / < 0.7 on the ROF pack warn.
R-RV-02matchStatus is missing, error, no_match, or ambiguous.
R-RV-03UEN empty/Null and status is not matched / manual.
R-RV-04Split PDF error.
R-RV-05OCR page mismatch or forced single-document fallback (ocr_page_mismatch, multi_invoice_suspected).
R-RV-06Run has zero items.

A run stays in REVIEW while any non-deferred, non-closed item needs review.

Operator actions ​

ActionRule
Manual matchClears R-RV-02/03 for that item.
Correct labelAudited. Does not clear a match miss by itself.
Re-run OCRRun → PROCESSING. Previous items replaced, not merged blindly.
DeferPark. Continue may still READY the run.
Close without ticketJunk/duplicate.
ContinueAllowed only if leftovers are deferred or closed.

14. Frozen ROF pack (release gate) ​

Already in packages/rules SG_ROF_PACK v0.1.0. These run on evalRules before Release, not on ticket create.

IDPack ruleSeverityMeaning
R-PK-01SG.ROF.ADDRESSEE_RESOLVEDBlockNo Entity → cannot Release.
R-PK-02SG.ROF.EXTRACTION_CONFIDENCEWarnLow confidence → closer AM attention on the Release screen (highlight), not a silent send.
R-PK-03SG.ROF.DEADLINE_FLAGGEDBlockIf impliedDeadlineType is set, a deadline flag must be prepared (apply upserts it). Cannot Release “and forget” a statutory hint.

ROF does not execute Secretarial rules (SG.SEC.*). Those belong to the ticket’s later work.


15. Helpdesk ticket rules ​

Created when the run hits TICKETING, one per ticketable item.

Ticket fieldValue
Subject[<LABEL>] <type or name> — <legalName>
DescriptionUEN, pages, date, match method, link to /dashboard/rof/runs/:id
CustomerHelpdeskCustomer from R-MT-06
Account managerR-MT-07
Team§15.1
Statusnew_mail
PriorityUnset
MailboxThe RO mailbox (so a later client send can go out)
Chatwoot conversationNot the scanner thread
AttachmentSplit PDF only
SourceREGISTERED_OFFICE + rofItemId so Helpdesk can link back

15.1 Team from final label ​

LabelTeam
IRAS, BANK_FINANCIALaccounting
ACRA, LEGAL, MOMsecretarial
UTILITY_PROPERTY, GENERAL, UNKNOWNmiscellaneous
IDRule
R-TK-01Missing/deactivated team → mailbox defaultTeamId → Unassigned. Never drop the ticket.
R-TK-02Scanner email is never a ticket.
R-TK-03Zero-item runs never ticket.
R-TK-04Helpdesk list may filter source=REGISTERED_OFFICE.
R-TK-05Ticket detail shows “From Registered Office” + deep link to the item.
R-TK-06Helpdesk SLA starts when the ticket is created, not when the scan arrived.
R-TK-07Closing the ticket does not Release the client email, and Release does not close the ticket. Two jobs.

16. Scanner done-reply rules ​

Audience: mailroom. Not the client.

IDRule
R-SR-01One email per run, after tickets exist.
R-SR-02Body: MRID; each ticketed item (label, client, UEN, pages, HD-…); each deferred/closed/unmatched line so the scan was seen.
R-SR-03No statutory advice. No “dear director”.
R-SR-04Idempotent on scannerReplyMessageId.
R-SR-05Failure → retry. Do not recreate tickets.
R-SR-06Not visible as an Unassigned ticket (R-§7.4).

17. Client notification and Release (full ROF) ​

This is FR-ROF-004 / 005 / 006. The human gate is Release, not “create ticket”.

17.1 Draft ​

Started per item after ticket create (ROF.HANDLE_MAIL steps 4–6).

Templates (defaults; tenant can replace later):

LetterSubject patternBody intent
IRAS / tax / GST / ECIMail received for {legalName} — IRASWe received {type} from IRAS at your registered office on {date}. Your accounting team has it (ticket {HD}).
ACRA / AR / AGMMail received for {legalName} — ACRASame, secretarial team.
MOMMail received for {legalName} — MOMSame.
Bank / utility / generalMail received forNeutral receipt.
Implied deadlineSame + one sentenceThis letter appears to relate to {deadlineType}. Your team will confirm dates.

Claude (draftRofNotification, rof.draft.v1) may write 2–4 plain sentences. It cannot change the Entity, invent a deadline type, or mark the letter Released.

IDRule
R-DR-01Draft language follows the entity’s client language when set; else English.
R-DR-02Draft is a proposal. Sending is Release only.
R-DR-03Draft failure falls back to the template. The item still reaches AWAITING_RELEASE.

17.2 Release screen ​

Account manager (or ROF reviewer) sees: split PDF, classification, Entity, draft subject/body (editable), implied deadline, Helpdesk ticket link.

Actions: Release · Edit and release · Skip notify · Send back to review (wrong company).

IDRule
R-RL-01No client send without Release. Tier 3. ROF.RELEASE_NOTIFICATION.
R-RL-02Release blocked if Entity unresolved (R-PK-01).
R-RL-03Release blocked if there is no client recipient email. Operator must add a contact or Skip.
R-RL-04Release blocked if implied deadline is set and not flagged for upsert (R-PK-03). The apply path always flags; the UI must show the flag.
R-RL-05Low confidence (R-PK-02) highlights the screen; AM can still Release.
R-RL-06Skip notify is explicit and audited. Ticket remains.
R-RL-07Apply is idempotent: existing ROF_NOTIFICATION Document for that AgentRun → no second send (rof-apply already).
R-RL-08Send uses the Helpdesk/Chatwoot path to the client contact. New thread, not the scanner thread.
R-RL-09After send: ROF_INBOUND + ROF_NOTIFICATION Documents on the Entity; notification row; ENGAGEMENT_SIGNAL.
R-RL-10ROF never files with a regulator (no tier 4 here).

18. Deadline flag rules ​

IDRule
R-DL-01Only when impliedDeadlineType is set.
R-DL-02Writer is upsertStatutoryDeadline (shared). Types: ANNUAL_RETURN, AGM, ECI, TAX_RETURN, GST_RETURN, PASS_RENEWAL.
R-DL-03Due date = extracted date if present; otherwise do not invent a statutory calendar — store a flag the AM can edit on Release.
R-DL-04Flag is not “Secretarial has filed”. Status stays UPCOMING until that module meets it.
R-DL-05Cannot Release an implied-deadline letter without the flag (R-PK-03).
R-DL-06Ticket description includes the deadline type so the working team sees it.

19. Sidebar and screens ​

Registered Office is a Module, next to Payroll / Bookkeeping / Helpdesk. Not a child of Helpdesk.

Modules
  Payroll
  Bookkeeping
  Financials
  Registered Office
      Inbox            /dashboard/rof
      Needs review     /dashboard/rof/review
      Release          /dashboard/rof/release
  Helpdesk
      Dashboard / tickets / teams
      Settings             ← mailbox switch lives here

Harness nav already declares { path: '/dashboard/rof', label: 'Registered office mail' }. Use that.

ScreenWhoWhat
InboxROF operatorRuns: status, MRID, item counts, aging, ticket counts, release counts
Run detailROF operatorOriginal PDF, item cards, rematch/defer/close, ticket + release links, scanner-reply preview
Needs reviewROF operatorOnly items that fail §13
ReleaseAccount manager / ROF reviewerScan + draft; Release / Skip
Company documentsAnyone with entity accessROF_INBOUND + ROF_NOTIFICATION on the company
Helpdesk ticketHelpdesk rolesWork the letter; link back to the item
IDRule
R-UI-01CORRESPONDENCE_OPERATOR / ROF_OPERATOR see Inbox + Review.
R-UI-02Release additionally needs ROF.RELEASE_NOTIFICATION / reviewer / AM on that entity.
R-UI-03Helpdesk settings (mailbox flag) stay on Helpdesk admin. ROF Inbox may show a read-only “these inboxes are RO”.
R-UI-04Do not show scan runs inside Helpdesk → All tickets.
R-UI-05Aging: minutes since received on Review; hours since drafted on Release.

Staff-only upload (Chatwoot down) is allowed on Inbox after slice 1. No public AIMA-style upload page.


20. Storage and tenancy ​

IDRule
R-ST-01RLS on every new table. No tenant context → zero rows.
R-ST-02Original scan: spade/_inbox/yyyy/mm/dd/<id>.pdf — no client filename.
R-ST-03After match: file split PDF under …/<entity>/registered-office/documents/<period>/….
R-ST-04Keys never appear in request or response. Signed download only.
R-ST-05SHA-256 on ingest.

21. Jobs and API (conceptual) ​

JobDoes
helpdesk.ingestIf flag: create RofRun, no ticket. Else: today.
helpdesk.attachmentFetch bytes into File.
rof.process-runAIMA pipeline on the run.
rof.complete-mailroomTickets + scanner reply; start ROF.HANDLE_MAIL per item.
agent.run-start / resumeExisting harness for draft + release.
helpdesk.replyScanner done-reply; later, client send from apply if that is the transport.

Staff API under /ops/rof (list/detail/review actions) plus existing /ops/rof/mail-intake, /ops/helpdesk/admin/mailboxes, and the shared review-inbox approve for ROF.RELEASE_NOTIFICATION.

Do not revive archived /ops/correspondence against old models.


22. Frozen FR mapping ​

RequirementConnected module
FR-ROF-001 ingest, run per itemChatwoot RO inbox; RofRun per email; RofItem per letter
FR-ROF-002 extract sender, entity, typeAIMA classify + extract
FR-ROF-003 resolve entity + AM§12
FR-ROF-004 draft client notification§17.1
FR-ROF-005 human release, tier 3§17.2
FR-ROF-006 send and record deliveryrof-apply + Chatwoot to client
FR-ROF-007 store original + notificationROF_INBOUND / ROF_NOTIFICATION
FR-ROF-008 queue + agingInbox / Review / Release
FR-ROF-009 deadline flag, do not execute§18
FR-ROF-010 eventsRUN_LIFECYCLE, STEP_EXECUTED, HUMAN_DECISION, ENGAGEMENT_SIGNAL

D1-5-ROF open item “mail source”: decided — Chatwoot inbox with registeredOfficeIntake. M365 is not this build.
Open item “templates”: default table in §17.1; tenant override later.

Helpdesk tickets are how ROF “may create a TICKET” (D1-5-ROF §6). In this build they are always created for ticketable items, because that is the firm’s work queue.


23. What stays out of the app ​

Physical MI0–MI4, envelope stamping, MIE tray, Gmail labels, consolidating many letters into one client email per window, SOP D0–D7 names, address match, AIMA public upload, live Odoo match, a second Gmail poller.


24. Delivery slices ​

Each slice is shippable. Do not skip 1.

SliceShipsProve
1 ForkMailbox flag. Run, no ticket.RO inbox → no HD. Support inbox → HD.
2 PipelineAIMA OCR/split/classify/extract/match.Fixture → N items, UEN match.
3 Inbox + Review/dashboard/rof + rematch.Unmatched item fixed by hand.
4 Tickets + scanner replyHD per item, done-reply.Two clients → two tickets, one scanner email.
5 Draft + ReleaseROF.HANDLE_MAIL for real extract; Release screen; rof-apply.No client email without Release. Notification stored on Entity.
6 DeadlinesFlag + R-PK-03.AR letter cannot Release without a deadline row.

Slices 1–4 are the mailroom loop (old “phase 1”). Slices 5–6 complete ROF. The module is not done at slice 4.


25. Acceptance (full module) ​

  • AIMA is not deployed as the product.
  • An admin can mark the RO inbox from Helpdesk Settings. Default off.
  • That inbox never opens a ticket on arrival. Other inboxes unchanged.
  • Multi-client PDF → many items → many tickets, companies set, split PDFs attached.
  • Ambiguous / unreadable items stay on Needs review. No silent tickets.
  • Scanner thread gets one done-reply citing HD-….
  • Sidebar has Registered Office (Inbox, Needs review, Release) linked to Helpdesk both ways.
  • A draft is proposed per ticketed item. No client send without Release.
  • Release stores inbound + notification on the Entity.
  • Implied statutory letter raises a deadline flag; ROF does not file.
  • Low-confidence extract is highlighted on Release.
  • Unresolved addressee cannot Release.
  • Apply is idempotent (no double send).
  • Every transition is audited.
  • No Gmail poller, no Supabase, no second Next app.

26. Reference ​

Frozen: greenfield_prd/Phase-E-Module-Specifications/D1-5-ROF-registered-office.md
Harness (keep, connect): packages/agent-runtime/src/modules/rof.ts, rof-apply.ts, adapters/rof-adapters.ts
Rules: packages/rules/src/packs/sg-rof.ts
AIMA behaviour: aima-mvp/src/lib/document-classify.ts, ocr-clients-pipeline.ts, ocr-clients-review.ts, odoo-match-helpers.ts
Prefer monorepo copies: packages/correspondence/src/logic/, src/ocr/
Helpdesk: packages/helpdesk/src/intake.ts, apps/worker/src/handlers/helpdesk/ingest.ts, Mailboxes tab
Parked first port (do not implement from): correspondence-module.md


27. One-line summary ​

Registered Office is its own sidebar module. A flagged Helpdesk inbox feeds AIMA’s pipeline. Each letter becomes a Helpdesk ticket and a drafted client email. The mailroom gets a done-reply. The client gets nothing until Release. Deadlines are flagged, not filed. AIMA is the reference. The monorepo is the product.

Internal use only — BreezyCorp