Skip to content

Example: Run lifecycle

The run lifecycle document traces a single run of a SAIF.Framework.Windows console application from Main through argument resolution, dependency registration, and the Initialize / Execute / Finish sequence. Doc gen produces one per application at docs/applications/<app-slug>/run-lifecycle.md.

What reviewers should check

  • The ## Execution flow diagram uses a standard ```mermaid fence; a legacy Azure DevOps colon-prefixed fence is a blocker.
  • The diagram matches the framework flow — Main, Program.Run(), CreateArgumentContainer, ResolveArguments, RegisterAppDependencies, container build, Initialize, Execute, Finish, Dispose/footer — and keeps both the invalid-argument branch and the SaifConsoleException and unhandled-exception branches.
  • ## Dependency registration names the real DatabaseConfig instances, class-map assemblies, and application registrations, not just "registers dependencies".
  • Initialize, Execute, and Finish each have substantive prose, and the step tables state the exception thrown and the resulting exit code on failure rather than a generic "error".
  • ## Exit codes keeps every row of the SaifConsoleException hierarchy and marks codes the application cannot produce as "Not used" instead of deleting the row or inventing a trigger.
  • Recoverable and fatal errors are separated, and each fatal condition maps to a code in the table above it.
  • Detailed per-operation logic is summarised here and left to the pages under operations/; a step-by-step algorithm in Execute is a defect.

Example

The document below is a complete, correct console-run-lifecycle.md for a fictional .NET Framework console application.

View rendered example — the same document as a real page, so you can compare rendering as well as source.

---
title: Run lifecycle
summary: Execution flow from the entry point through Initialize, Execute, and Finish.
status: draft
owner: Policy Systems Team
last_reviewed: 2026-09-10
---

# Run lifecycle

A run of `SAIF.Policy.RenewalNotice` starts in `Main` in `Program.cs`, which does
nothing but call `Program.Run()` on the `SAIF.Framework.Windows` runner. The
runner builds the `Arguments` container from the command line, resolves and
validates it, registers the application's dependencies in an Autofac container,
and constructs `RenewalNoticeApplication`. The application then runs
`Initialize` to acquire its configuration, database session, and API client,
`Execute` to generate and queue the notice batch, and `Finish` to write totals
and send the operations summary. Every exit path ends with the framework
disposing the container, writing the footer, and echoing `Exit Code: n` as the
final line of console output.

## Execution flow

```mermaid
flowchart TD
    MAIN[Main] --> RUN["Program.Run()"]
    RUN --> ARGS["CreateArgumentContainer(args)"]
    ARGS --> RESOLVE["Arguments.ResolveArguments()"]
    RESOLVE -->|Invalid| FAIL["Write failures to Console.Error<br/>ExitCode = 1<br/>application never runs"]
    RESOLVE -->|Valid| REG["RegisterAppDependencies(builder)"]
    REG --> BUILD["Build container + CreateApplication(context)"]
    BUILD --> INIT["Initialize()"]
    INIT --> ZERO["ExitCode = 0"]
    ZERO --> EXEC["Execute()"]
    EXEC --> FIN["Finish()"]
    FIN --> DISPOSE["Dispose() + WriteFooter()"]
    DISPOSE --> ECHO["Print 'Exit Code: n'"]
    INIT -.->|SaifConsoleException| ERR["ExitCode = ex.ExitCode"]
    EXEC -.->|SaifConsoleException| ERR
    EXEC -.->|Unhandled Exception| ERR5["ExitCode = 5"]
```

## Argument resolution

`CreateArgumentContainer(args)` constructs the `Arguments` class, which derives
from `StandardArgumentContainerBase<Arguments>` and exposes `NoticeDate`,
`NoticeType`, `BatchSize`, and `WhatIf` as writable properties.
`BuildArgumentResolvers` configures `NoticeDate` and `NoticeType` explicitly and
marks `BatchSize` and `WhatIf` as not required with defaults of `500` and
`false`. `ResolveArguments()` then matches each command-line key to a property
name case-insensitively, converts the value to the property type, and collects
every failure before returning. Because resolution runs before the container is
built, an invalid command line writes its messages to `Console.Error` and exits
with code `1` without opening a database session or calling the Agency API.

See [Arguments](arguments.md) for the complete argument contract.

## Dependency registration

`RegisterAppDependencies(builder)` calls `RegisterCommonDependencies` with a
single `OracleConfig` named `policyDb`, whose instance name is resolved from the
`policy.db.instance` environment config key, and passes the assembly containing
`src/SAIF.Policy.RenewalNotice/Mappings` so that the NHibernate class maps for
`PolicyTerm`, `RenewalNotice`, `RenewalNoticeQueueItem`, and `NoticeTemplate`
are added to the session factory. It then registers the application's own
services: `NoticeGenerator`, `NoticeQueueWriter`, `AgentResolver`, and the
`IAgencyClient` implementation from `SAIF.Agency.Api.Client`, built from the
`agency.api.baseUrl` and `agency.api.apiKey` settings. The resolved `Arguments`
instance is registered as a single instance so every component reads the same
notice date, notice type, and `WhatIf` state.

## Initialize

`Initialize()` performs the one-time setup that must succeed before any notice
is generated. It retrieves the application settings from the Environment API,
resolves the `policyDb` `IUnitOfWork`, loads the `POLICY.NOTICE_TEMPLATE` row
for the requested notice type, verifies that the print vendor drop path is
writable, and registers the `file0`-`file2` log destinations with
`IConsoleLogService`.

| Step | Action | On failure |
| --- | --- | --- |
| 1 | Retrieve `renewalnotice.*` and `agency.api.*` settings through `IEnvironmentService` | `BadParameterException` when a required setting is missing; exit code 1 |
| 2 | Open the `policyDb` unit of work and run the session probe | `DatabaseErrorException`; exit code 2 |
| 3 | Load the `POLICY.NOTICE_TEMPLATE` row for `NoticeType` and its statutory lead days | `DataErrorException` when no template row exists for the notice type; exit code 3 |
| 4 | Verify the print vendor drop path in `renewalnotice.printQueuePath` exists and is writable | `FileInputOutputException`; exit code 4 |
| 5 | Register the `file0`-`file2` log destinations and write the header block | `FileInputOutputException`; exit code 4 |
| 6 | Construct the `IAgencyClient` and call `GetAgency` for the health-probe agency number | Unhandled exception surfaced by the client; exit code 5 |

## Execute

`Execute()` selects the eligible policy terms and generates the batch. It queries
`POLICY.POLICY_TERM` for terms whose renewal effective date falls inside the
window — the notice date plus the statutory lead days for the notice type — that
have no `POLICY.RENEWAL_NOTICE` row for this renewal and notice type, ordered by
renewal effective date and policy number and capped at `BatchSize`. If the query
returns no rows the run throws `NoDataFoundException` and exits with code 6.
Otherwise each term is processed in turn: the servicing agent is resolved
through `SAIF.Agency.Api.Client`, the notice document is produced by
`NoticeGenerator`, and — unless `WhatIf` is set — the document is written to the
print vendor drop path and a `POLICY.RENEWAL_NOTICE_QUEUE` row is inserted
alongside the `POLICY.RENEWAL_NOTICE` record inside one unit-of-work
transaction. The per-notice logic is documented in the operations pages; this
page records only the shape of the loop.

### Processing pattern

| Step | Action | On success | On failure |
| --- | --- | --- | --- |
| 1 | Query eligible policy terms for the notice window, capped at `BatchSize` | Continue to the loop | `DatabaseErrorException` (exit code 2); an empty result throws `NoDataFoundException` (exit code 6) |
| 2 | Resolve the servicing agency and agent through `SAIF.Agency.Api.Client` | Continue to generation | Log to `file2`, count the notice as skipped, continue with the next term |
| 3 | Generate the notice document from the loaded template | Continue to queuing | `DataErrorException` when a required merge field is missing on the term (exit code 3) |
| 4 | Write the print file and insert the queue and notice rows in one transaction | Commit, log the notice to `file1` | Roll back the term's transaction, log to `file2`, continue with the next term |
| 5 | Write run totals: terms read, notices queued, skipped, failed | — | — |

When `WhatIf` is set, steps 4 and 5 still run but the print file write and both
inserts are skipped; the notice is logged to `file1` with a `WHATIF` marker so a
dry run and a real run produce comparable detail reports.

## Finish

`Finish()` flushes the `file0`-`file2` streams, writes the totals block to
`file0`, and sends the operations summary mail to the addresses in
`renewalnotice.notificationRecipients` with the counts and the elapsed time. When
the skipped or failed count is above zero the summary names the first ten
affected policy numbers so operations can triage without opening the log. The
unit of work and the `IAgencyClient` are released here; the framework then
disposes the container, writes the footer with the total running time, and
echoes the exit code.

## Exit codes

Exit codes are inherited from the `SaifConsoleException` hierarchy. The table below records which conditions in **this application** produce each code.

| Exit code | Exception | Triggered by |
| --- | --- | --- |
| 0 | — | Successful completion; all eligible terms generated and queued, or logged when `WhatIf` is set |
| 1 | Argument validation / `BadParameterException` | A missing or unparseable argument, a `NoticeType` outside `Standard`, `NonRenewal`, `Conditional`, a `BatchSize` outside 1-5000, or a missing required Environment API setting |
| 2 | `DatabaseErrorException` | The `policyDb` session cannot be opened, or the eligible-term query or a notice insert fails |
| 3 | `DataErrorException` | No `POLICY.NOTICE_TEMPLATE` row exists for the requested notice type, or a term is missing a merge field the template requires |
| 4 | `FileInputOutputException` | The print vendor drop path is unreachable or not writable, or a `file0`-`file2` log destination cannot be opened |
| 5 | Unhandled exception | Any unexpected failure, including an Agency API fault during the `Initialize` health probe |
| 6 | `NoDataFoundException` | No policy terms fall inside the notice window for the requested notice date and notice type |

The application defines no `CustomErrorException` subclass, so no exit code above
6 is produced.

## Error handling

### Recoverable errors

A failure that affects one policy term does not stop the run. Agent resolution
failures — an agency the Agency API does not know, or an agency with no
appointed servicing agent — are logged to `file2` with the policy number and the
agency number, counted as skipped, and left for the next run to retry. A
transaction failure while writing the print file or the queue and notice rows is
rolled back so no partial notice survives, logged to `file2` with the policy
number and the exception message, counted as failed, and followed by the next
term. Terms that are skipped or fail keep no `POLICY.RENEWAL_NOTICE` row, so
they remain eligible and are picked up again on the following run.

### Fatal errors

Anything that makes the whole batch impossible ends the run immediately with the
mapped exit code: a failure in any `Initialize` step, a database error on the
eligible-term query, or a missing notice template. An empty result set is
treated as fatal rather than as success, so a night where the window is empty
ends with exit code 6; operations treats that as an expected outcome for the
`NonRenewal` and `Conditional` runs and as an alert for the nightly `Standard`
run. Because notices are recorded only when their transaction commits, a fatal
exit part way through a batch is safe to rerun with the same arguments.

## Related documentation
- [Application index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Arguments](arguments.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [Data model](data-model.md)
- [Operational runbook](operational-runbook.md)
- [Tech debt](tech-debt.md)

## Sources
- `src/SAIF.Policy.RenewalNotice/Program.cs`
- `src/SAIF.Policy.RenewalNotice/Arguments.cs`
- `src/SAIF.Policy.RenewalNotice/RenewalNoticeApplication.cs`
- `src/SAIF.Policy.RenewalNotice/NoticeGenerator.cs`
- `src/SAIF.Policy.RenewalNotice/NoticeQueueWriter.cs`
- `src/SAIF.Policy.RenewalNotice/Mappings/PolicyTermMap.cs`