Decision log¶
Status: Living document. Append only — a superseded decision is marked, never deleted. Version: 0.1 Date: 2026-08-11
One row per decision. A decision recorded here has been made; an open question lives on the relevant engine or spec page until it becomes one of these.
KD-001 — Return templates are configuration, not code¶
Date: 2026-08-11 · Status: Accepted
Context. The requirement asks for a return-template engine so the supervisor can add, retire, or amend templates without a redevelopment cycle. The alternative is a screen and a model per return.
Decision. No return may be hard-coded. Templates, fields, formulas, rules, checklists, and due-date policy are versioned configuration in Return Studio.
Consequences. The UI must render any template generically, which is harder than thirty bespoke screens and is the reason Phase A exists to falsify it early. Field-level type safety moves from the database to Assay.
Rejected. A screen per return — fails the core premise. A code-generation approach that emits a module per template — moves the release cycle rather than removing it.
KD-002 — Submitted values stored as jsonb, validated against the template version¶
Date: 2026-08-11 · Status: Accepted
Context. A configurable template means the column set is unknown at build time.
Decision. One jsonb document per submission, keyed by field id, conformance-checked against its
template version on write. Analysts read flattened typed columns from
Projections.
Consequences. The database will not enforce field types, so the write path must. Expression indexes cover hot fields. Performance is an assumption until load-tested — an explicit roadmap gate.
Rejected. A table per return type generated by DDL — migrations become a runtime concern. Entity-attribute-value — every read becomes a pivot. Full argument in the stack.
KD-003 — Validation and computation as Kogito DMN on a separate deployable¶
Date: 2026-08-11 · Status: Accepted
Context. Validation rules and prudential formulas change with directives and are the part of the
system least appropriate for developers to own. Kogito 10.2.0 pins Quarkus 3.27.2; the record path
wants Quarkus 3.38.1. Bundling them shares a classpath and loses every Quarkus upgrade until Kogito
catches up — the same problem fords-automation already solved by shipping a platform parent.
Decision. Rules compile to DMN decision tables executed by Kogito, but only inside
kovent-automation, which parents fords-automation-platform. kovent-server orchestrates Assay
and calls the automation host over REST. Worked examples from the requirement become fixtures on the
automation side.
Consequences. A standard with tooling and a specification, readable by the supervisor who wrote
the directive. The write path gains one network hop for decision evaluation — accepted as the cost
of not pinning the record path. Fordsworth standard extensions split cleanly: fords-scheduler and
fords-comm on the server line, fords-automation on the automation line.
Rejected. Embedding Kogito in kovent-server — classpath conflict with Quarkus 3.38.1. Rules in
Java — every threshold change becomes a release. A bespoke rule DSL — ours would have neither tooling
nor a specification.
KD-011 — every Fordsworth extension owns its own Postgres schema¶
Date: 2026-08-11 · Status: Accepted
Context. fords-scheduler and fords-comm are both Fordsworth standards mounted into
kovent-server. Both originally published Flyway migrations under classpath:db/migration, and
both independently number from V1.0.0. Flyway tracks one version history per schema, so the two
collided the moment both extensions were on the classpath together — fords-comm was temporarily
dropped rather than fix this properly. Namespacing the migration folders
(db/migration/scheduler, db/migration/comm) only fixed classpath resolution, not the underlying
problem: a single shared flyway_schema_history table still can't hold two independent V1.0.0s.
Decision. Every extension gets its own Postgres schema, its own named Flyway configuration
(quarkus.flyway.<name>.*), and therefore its own <name>.flyway_schema_history — completely
decoupled from every other module's version numbering, permanently, not just for the versions in
use today. kovent-server's own migrations (classpath:db/kovent) are the one thing that stays on
the default Flyway instance in public, because Kovent is the host application, not a guest module.
Each extension's named datasource (quarkus.datasource.<name>.*) mirrors the default datasource's
JDBC URL/credentials rather than provisioning a second container — one physical database, one Dev
Services (or local) Postgres, N logically isolated schemas. The default datasource's JDBC
currentSchema search path lists every schema (public,scheduler,comm,...) so each extension's own
unqualified queries keep working against its schema with zero code changes on the extension side.
This is applied uniformly to both extensions from day one — not "isolate the one that's currently
colliding and leave the other in public until it also breaks."
Consequences. Cadence and Notices both ship. A third extension follows the same three-property
shape (flyway.<name>.*, datasource.<name>.*, add <name> to currentSchema) instead of prompting
a redesign. Hibernate's dev-mode schema validator only inspects the first schema on the search path,
so it false-positives on tables outside public — silenced with
quarkus.hibernate-orm.validate-in-dev-mode=false rather than worked around.
Rejected. Copying or shading migrations out of the jars — fights the extensions instead of fixing
the real problem. A second Postgres container per extension — needless resource cost when schemas
give the same isolation inside one database. Leaving fords-scheduler in public while only
fords-comm gets its own schema — works today, but is an arbitrary asymmetry between two modules of
the same kind, waiting to confuse the next person who reads the config.
KD-004 — Append-only versioning rather than event sourcing¶
Date: 2026-08-11 · Status: Accepted
Context. The requirement demands a legally defensible trail and that resubmissions retain original version history.
Decision. Submissions and decisions are append-only versioned rows in Strata, with a current-version pointer. Not an event-sourced aggregate.
Consequences. We get the audit properties without replay complexity, and the current state is a row rather than a fold. Revisit only if a requirement genuinely needs temporal replay.
Rejected. Event sourcing — the audit benefit is available more cheaply and the operational cost is real. Soft deletes and status flags — insufficient for evidence.
KD-005 — Audit trail is hash-chained, in-transaction, and owned by an engine¶
Date: 2026-08-11 · Status: Accepted
Context. "Legally defensible" has to mean something a firm's counsel cannot dismiss.
Decision. Seal writes hash-chained entries in the same transaction as the state change, with periodic publication of the chain head.
Consequences. Alteration becomes demonstrable rather than deniable. Storage growth is linear and permanent — pruning is not available by design. Chaining alone does not stop a privileged actor rewriting from the point of alteration forward, which is why the published head is the anchor.
Rejected. An append-only table alone — protects against accident, not against a privileged actor. A logging library per engine — produces a log, not evidence, and cannot prove its own integrity.
KD-006 — Penalties are recommended, never issued automatically¶
Date: 2026-08-11 · Status: Accepted
Context. The requirement asks for configurable penalty parameters and calculation hooks.
Decision. Kovent computes a recommendation showing the parameters and facts relied on. A human issues.
Consequences. Slower, and correct. An automatically issued penalty is an administrative act taken by software, which a firm will challenge on process grounds before arguing the merits. Supervisory discretion is part of the legal framework, not a gap to automate.
Rejected. Automatic penalty issuance on overdue status.
KD-007 — Production front-end is web components, prototype is React¶
Date: 2026-08-11 · Status: Superseded by KD-013
Context. The design system needed to move fast; the product needs a ten-year life.
Decision. The design system and prototype are Next.js and Tailwind. The production UI is web components with lit-html, served by Quinoa. Tokens in the prototype are the source of truth for both.
Consequences. Two expressions of the system, with drift risk mitigated by sharing token values —
this documentation site uses the same oklch values for the same reason. A framework rewrite in
year four is avoided.
Rejected. React in production — a supervisory tool should not inherit a framework's churn. Skipping the prototype — design velocity would have collapsed.
Supersession. Product UI shipped as Angular under Quinoa; see KD-013. Prototype remains React.
KD-008 — Engine boundaries are modules, not services¶
Date: 2026-08-11 · Status: Accepted
Context. Ten engines invites a service per engine.
Decision. One deployable. Engines are business components with boundary, control, and entity layers, enforced by package structure and build-time dependency rules.
Consequences. Transactional integrity across the write path is straightforward, which matters because Seal and the outbox must commit with the state change. Extraction later is possible because the boundaries are already explicit. A regulator does not need a distributed systems problem on day one.
Rejected. Microservice per engine — a distributed transaction problem in exchange for scaling we have no evidence of needing.
KD-009 — Documentation is MkDocs Material on Cloudflare Pages¶
Date: 2026-08-11 · Status: Accepted
Context. Kovent needs a browsable source of truth, matching how Limen publishes.
Decision. Markdown in docs/, MkDocs Material, strict build as a CI gate, Cloudflare Pages
deploying main to docs.kovent.fordsworth.com.
Consequences. Docs live in the same pull request as the change they describe. A broken internal link fails the build. See Publish docs.
Rejected. A wiki — drifts from the code and has no review gate. Docs in the design-system app — couples documentation to a prototype's lifecycle.
KD-010 — Three validation severities, not two¶
Date: 2026-08-11 · Status: Accepted
Context. A binary pass/fail model forces a choice between blocking honest disclosure and letting real breaches through.
Decision. FAIL blocks acceptance, ADVISORY flags without blocking, INFO records context. A
reported capital deficit is ADVISORY; a missing explanation for it is FAIL.
Consequences. Reviewers keep trust in the gate, because the gate only fires on genuine process failures. Spec authors must choose severity deliberately — guidance here.
Rejected. Binary pass/fail — over-blocking trains reviewers to override, and an override habit is worse than no gate.
KD-012 — Obligation rule vs instance; one category, many templates¶
Date: 2026-08-15 · Status: Accepted
Context. Supervisors need recurring duties (e.g. quarterly capital) and deliberate once-off
assignments. Early spine code conflated frequency=ADHOC with rule type, linked one optional
template, and the UI briefly allowed multi-category selection — which does not match how rules are
authored.
Decision.
- Vocabulary.
obligation_ruleis Studio config;obligationis the Cadence runtime instance; Cadence is the engine BC. Do not introduceobligation_schedule. - Rule types.
rule_typeisRECURRINGorADHOC. Frequency isMONTHLY|QUARTERLY|ANNUALfor recurring, alwaysONCEfor ad-hoc. Two Studio menus (/studio/obligations/recurring,/studio/obligations/once-off) so the path is deliberate — no type toggle on a shared form. - Linking. Each rule targets one licence category (searchable) when category-scoped, and
many return templates via
obligation_rule_templatewith exactly one primary. Explicit firms only for once-offEXPLICIT_FIRMS. - Materialisation. Recurring rules generate period windows for active licences; once-off rules
create obligation rows immediately on save. Instances pin
template_idandtemplate_version_id.
Consequences. Studio and Cadence stay separable; changing a template does not rewrite historic obligations. Notification fatigue tables are reserved; the delivery engine comes later.
Rejected. Multi-category rules in v1 — one category per rule matches supervisory authoring.
A third noun (obligation_schedule) — synonym noise. Embedding rule type as a dropdown on one page
— users must choose Recurring vs Once-off deliberately.
KD-013 — Production front-end is Angular under Quinoa (supersedes KD-007 product UI)¶
Date: 2026-08-15 · Status: Accepted · Supersedes: KD-007 product-UI clause
Context. KD-007 chose web components + lit-html for production. Bootstrap velocity and the
Fordsworth UI kit (@fordsworth/ui) landed on Angular 22 + Tailwind 4 under Quinoa instead, with
conventions documented in Front-end.
Decision. The product UI is Angular served by Quinoa (backend/kovent-server/src/main/webui).
The Next.js tree under design/ui/ remains design velocity only. Prefer @fordsworth/ui controls
(fw-checkbox, fw-combobox, fw-dialog, …) over ad-hoc native controls for standard patterns.
Consequences. One SPA stack to maintain; design tokens still flow from the prototype. Web components remain an option for embeddable surfaces later if needed — not the bootstrap path.
Rejected. Rewriting the shipping UI to lit-html mid-bootstrap — cost without product gain.
KD-014 — Vault is properties-driven with document classes¶
Date: 2026-08-15 · Status: Accepted
Context. Attachments need a single store with interchangeable backends. Operators also need a stable taxonomy for what a file is, separate from why it is linked. An early sketch (fordsvault / DB-backed storage templates) is too heavy for the spine.
Decision.
- Repositories. Enum
local(default) ·s3·azureviaVaultStorageRepositoryand@LookupIfProperty(kovent.vault.repository). GCS and OneDrive deferred. - Config. v1 is application properties (
kovent.vault.*plusquarkus.s3.*/quarkus.azure.storage.blob.*). No DB catalogue of backends yet; UI shows read-only status. - Classification. Table
vault_document_class(seeded, maintainable under Vault → Document classes). Upload requires a class. Linkdocument_purposeremains why the file is attached. - UI. Platform catalogue at
/vault/documents; firm/person Documents tabs; drop-zone upload with class + purpose. Storage templates / keys screens stay stubs.
Consequences. Switching backend is a deploy/config change. LocalStack (or real AWS/Azure) is operator-supplied — Quarkus Dev Services for S3/Azure are off by default.
Rejected. Free-text category on documents. DB-backed multi-backend templates in v1. Dummy S3/Azure
stubs once Quarkus clients are on the classpath.
KD-015 — Return templates are hybrid sections, not questionnaires¶
Date: 2026-08-15 · Status: Accepted
Context. SECZim-style returns (e.g. AMC quarterly Excel with 40+ sheets) are hierarchical statements, period columns, repeating schedules, and cross-sheet formulas. A pure “questionnaire / form builder” cannot represent that. Full XBRL taxonomy authoring is specialist-heavy. Shipping an in-browser spreadsheet engine for v1 is high complexity for little gain. Excel-only filing loses validation, audit, and analytics.
Decision. Hybrid Return Studio model:
- Section types.
HEADER·STATEMENT·REPEATING·DECLARATIONunderreturn_template_version. - Statements. Tree of
return_line_item(code, label, parent,is_calculated, formula) plusreturn_section_column(YTD / CURRENT / PREVIOUS, …). Platform owns roll-ups; firms enter leaves. - Repeating schedules.
return_repeating_fielddefinitions now; firm UX = grid and/or Excel upload later — not 200-row pure web forms as the default. - Formulas. Constrained language (
SUM_CHILDREN,SUM(codes…),REF(section.line, column), laterPREVIOUS) — not raw Excel cell formulas as source of truth. Assay evaluates; Studio defines. - Licence scope. Separate templates per major return type, linked via obligation rules — not one conditional mega-template in v1.
- Excel import. Semi-automatic assist + human review in Studio (Phase 2). Auto-detect sheets / indentation / simple SUMs; never claim magic conversion of arbitrary workbooks.
- Values. Strata stays generic and analytics-friendly (KD-002 jsonb direction). Do not
introduce
value_decimal/value_text/value_booleancolumns as the long-term store.
Consequences. Studio grows a line-item editor and structure API; portal rendering becomes section-type-aware; Assay gains a formula evaluator. XBRL export remains a later option, not the authoring path.
Rejected. Pure questionnaire builder for AMC-class returns. XBRL-first authoring for SECZim maturity. In-browser spreadsheet runtime for v1. Mega-template with licence-conditional sections as the primary model.