OSIIX Library

The Read-Only Boundary

A record of trial and error building a security monitoring platform with AI assistance

Type
Development record
Published
2026-08-11
Updated
2026-08-11
Author
mars70
Overview

1. Introduction

As I worked with AI coding agents (ChatGPT/Codex/Claude) on a personal project, there was a point where it started to feel like "features keep getting added, but somehow I trust this less."

In my case, most of the cause wasn't the AI's raw capability — it was that the AI, out of "helpfulness," reached beyond what was actually asked. An unrequested refactor, an extra DB schema field, a near-duplicate of existing logic sitting next to the original — this kind of over-helping was hard to catch in code review, and by the time I noticed, it was often scattered across the codebase.

The subject here is a personally operated security monitoring platform. It's built with the roles split between a side that collects and parses raw logs (Producer) and a side that displays and evaluates the results on screen (Consumer). The rough flow looked like this:

PhaseContent
R1 & R2Settling the design approach
R3–R6Implementing monitoring targets, defining contracts with external data
R7ASeparating the semantics of evidence and evaluation
R7B & R7CContracting the read-only UI and deploying it to production

This article covers R1 through R7C of that flow: a record of trial and error with the design principles and governance used to keep AI from overstepping. It does not claim the whole project is finished — only this bounded phase.

Example

2. Deciding roles before duplication happened

This project has a separate component (a Producer) that parses raw logs to extract likely attack patterns. Early on, it turned out that a separate design corpus sitting locally could have "proceeded as a competing implementation path" against the main project.

The response taken was to fix the role before writing code. The policy — "this corpus is treated as requirements input, not implementation. It does not proceed as a competing independent implementation" — was fixed in writing first.

It looks like a minor call, but it stayed consistent with everything that followed in the design. Decide what's allowed before letting the AI build anything — that was the discipline kept throughout.

Design

3. Contract-driven ordering

In much of my past work, the data model got decided while building the screen. For this project, I deliberately reversed that order:

Contract (what counts as truth)
  → Meaning (what each piece of data means)
  → Data boundary (who can write what)
  → Implementation
  → UI

The UI is never given the role of deciding what's true. Instead of "it looks fine on screen, so mark it PASS," the conditions under which PASS may be shown are fixed as a contract first, and the UI isn't allowed to decide until those conditions are met.

Guardrails

4. Constraints as guardrails

The R7C UI implementation ran under constraints like these:

  • No schema changes
  • The DB is opened only through a read-only connection
  • Correlation results are never persisted to the DB — recomputed on every request
  • No filling gaps in the display with mock data

Checking the R7C UI implementation directly: the DB connection is consistently opened via a read-only URI, and nowhere does anything resembling a write occur. The correlation function is likewise just called fresh on each request, never writing back to the DB. (The R3/R5/R6 collector scripts, on the producer side, obviously do write to the DB — the read-only property is specific to the R7C UI layer.)

These constraints weren't put in place as mere caution. What they actually function as is a wall that keeps a UI request from casually reaching back and changing the Producer's data model. In this setup, adding one schema field for the UI's convenience could cascade into producer changes, importer changes, migrations, and deployment coupling. Staying a read-only consumer means, at minimum, the UI is never given a direct path to rewrite the Producer's data.

Separation

5. Separating ownership of meaning between Producer and Consumer

Even when a Producer reports "the artifact is AVAILABLE," the rule is that the Consumer must not reinterpret that as "the system is Healthy."

Producer: what was observed
Consumer: how that evidence is displayed and related
Health evaluation: what contract must be met to be called Health

Mixing these three makes it easy for "AVAILABLE" to be mistaken for "HEALTHY." But "evidence can be handled" and "the target is healthy" are, fundamentally, different claims. Keeping that distinction is also what connects directly to how UNKNOWN is handled next.

State model

6. UNKNOWN isn't a weak PASS

The conversion this project wanted to avoid is this one:

No evidence → No evidence of failure → Probably fine → PASS

This project was designed not to make that conversion. Health is handled as four states — PASS/WARN/FAIL/UNKNOWN — and "not observed" or "insufficient evidence" is treated as UNKNOWN, never converted to PASS or FAIL. (Separately, when Correlation has no relationship target to evaluate, that's expressed with its own vocabulary, NOT_APPLICABLE, deliberately kept apart from Health's UNKNOWN.)

"Has not failed" and "confirmed to be healthy" are, fundamentally, entirely different claims. Giving that distinction its own state is treated as part of the domain model, not error handling.

Maturity

7. Looking live vs. being operationally mature

Some monitoring targets handled real data, but the constraints — "manual execution only," "no scheduler," "no uptime guarantee" — were documented alongside them. Real data flowing through does not, by itself, mean production monitoring is complete.

A monitor that only runs manually looks fine while it's running. But nothing tells you what happened between one manual run and the next, unless something separate is built to watch for it. "Looking fine right now" and "being continuously checked" sound similar but rest on different assumptions.

Similarly, for another target, the distinction "connectivity was verified with synthetic data, but not yet with real data" was kept explicit.

Verification

8. Verification status

Per the commit history, this record spans from the R1 closeout at 19:21 JST on August 8, 2026 to the R7C closeout at 14:44 JST on August 9, 2026, recorded with explicit closeouts at each phase boundary. R8 (later rollout) is outside the scope of this record and has not started.

As of this writing, the tests in scope were run: 124 tests, 122 passed, 2 skipped, 0 failed. (The 2 skips are a known, environment-dependent constraint — Python 3.9 isn't installed in the local verification environment.)

Counting the commit history, this range consists of 25 commits, of which 3 are fix: commits. As far as could be checked in the local reflog, there's no record of a git reset or a revert commit. This isn't a claim that the guardrails produced zero rework — three fixes did happen. But at least in the Git history, nothing suggests an entire unit of work was thrown out.

Summary

9. Summary

None of this is genuinely new — it overlaps with existing ideas like CQRS's separation of writes from reads, and the general practice of separating evidence from evaluation in monitoring design. (It is not, however, Event Sourcing in the strict sense of reconstructing state by replaying an event log — this project holds snapshots and observations directly, rather than rebuilding state from an event log.) If there's value here, it's in having held onto "concepts already known" all the way through, under the pressure of a personal project where an AI does the implementing.

Distilled into four principles:

  1. Don't treat absence of evidence as evidence of health
  2. A Consumer may interpret evidence, but must not rewrite what the Producer actually observed
  3. Keep derived conclusions recomputable from the original evidence wherever practical
  4. Use architectural guardrails to define who is responsible for what, not just as a list of prohibitions

What mattered when handing implementation to AI wasn't how instructions were phrased — it was the line drawn between how far AI is trusted and where a human judgment takes over. A quiet, unglamorous design decision, but one that mattered in practice.