# SG/Sentinel Tabletop Simulation Part 1: Generic Flows And Logging

**version** v0.27.58
**date** 18 May 2026
**from** Human (project lead)
**to** Architect, Developer (lead), Security, @Dev
**type** Arch brief (tabletop simulation)

---

## What This Is

A tabletop simulation of SG/Sentinel: a series of simulated requests, traced through the layers, with the response at each layer mapped out, assuming the engine works as the rules-of-the-game spec describes. The purpose, per the voice memo, is to **discover gaps, contradictions, and unanswered questions before any code is written**, so the implementation has solid acceptance criteria and few decisions left to make at coding time.

This is part 1: **generic flows and logging**. Part 2 covers blocking malicious activity. The rules-of-the-game behavioural spec is the answer key these simulations run against.

The simulation is run honestly: where a request reveals a gap or contradiction, it is flagged as a **GAP**, not glossed over. The voice memo expects gaps to appear; finding them here is the point. Each gap gets a proposed resolution to fold back into the spec.

## How To Read These Simulations

Each simulation traces one request:

- **The request:** what arrives.
- **Layer-by-layer:** what each layer does, per the spec.
- **The outcome:** the final verdict and what was logged.
- **Gaps found:** where the behaviour was unclear, contradictory, or incomplete.

## Simulation 1: A Legitimate Static-Page Request (Happy Path)

**The request:** `GET /static/index.html` from a first-time visitor (no fingerprint yet), IP 198.51.100.2, normal browser headers.

**Layer 1:**
- Assigns request ID `sim-0001`.
- Logs: method GET, path /static/index.html, IP (per privacy config), timing.
- Checks deterministic rules: IP not banned, region not banned, not malformed, path is a valid static path (not an obvious-bad pattern), no scanner signature. All pass.
- No fingerprint match (first-time visitor), so no fast-track.
- Decision: pass-on to Layer 2.

**Layer 2:**
- Validates against known-good profile: `/static/index.html` is a known valid path on a static site. Valid.
- Request shape valid; no auth required for a public static page.
- Anomaly score: 0.
- Decision: pass to application.

**Application:** serves index.html.

**Layer 3 (async):** records the request in the evidence graph for IP 198.51.100.2 (benign activity).

**S3:** the finalised log (verdict: allow) lands in S3.

**Outcome:** allowed, served, logged. Happy path works.

**GAPS FOUND:**

- **GAP 1.1:** The spec says Layer 1 logs at Step 1, but the log is finalised with the verdict after the decision. For a request that passes through all layers, *when* is the S3 log written? If at Layer 1, it lacks the final verdict; if at the end, which layer writes it? **Proposed resolution:** Layer 1 captures and assigns the ID; the verdict is appended as the request progresses; the final write to S3 happens from the layer that makes the terminal decision (Layer 1 if blocked there, Layer 2 if it passes or blocks there). This needs a defined log-finalisation owner per path.

- **GAP 1.2:** For a static site, does Layer 2 even run? The execution-model brief said Layer 2 can run on every request or on cache miss. If `/static/index.html` is cached at CloudFront, does the request reach Layer 2 at all, or does Layer 1 pass it straight from cache? **Proposed resolution:** define which layers run on cache hit vs cache miss. Likely: Layer 1 always runs; Layer 2 runs on cache miss (origin fetch) but may be skipped on cache hit. This means a cached static page may only see Layer 1, so Layer 1 must be sufficient for the static-page happy path.

## Simulation 2: A Returning Authenticated User (Fast-Track)

**The request:** `GET /vault/john-cv` from a returning, authenticated user, IP 203.0.113.5, valid session token, matching a known-good fingerprint.

**Layer 1:**
- Assigns ID `sim-0002`, logs.
- Deterministic rules: all pass.
- Fingerprint check: matches a known-good fingerprint on the allowlist. Fast-track.
- Decision: fast-track (pass toward application, skipping deeper checks).

**Layer 2:** (if fast-tracked, is it skipped?)

**Application:** serves the vault content.

**S3:** log (verdict: allow, fast-tracked) lands in S3.

**Outcome:** fast-tracked, served, logged.

**GAPS FOUND:**

- **GAP 2.1 (major):** The fingerprint allowlist storage problem. Layer 1 (CloudFront Function) has no I/O and only embedded data. How does Layer 1 check a fingerprint allowlist that changes dynamically (users log in and out continuously)? An embedded list refreshed by redeploy cannot track live sessions. **Proposed resolution options:** (a) the fast-track decision moves to Layer 2 (which has I/O and can check a live allowlist store), meaning Layer 1 always passes-on authenticated-looking requests and Layer 2 fast-tracks; or (b) the fingerprint is carried in a signed token the user presents, which Layer 1 can validate cryptographically without a lookup (stateless fast-track). Option (b) is more elegant and fits the PKI direction. **This gap must be resolved before coding; it changes where fast-track lives.**

- **GAP 2.2:** If fast-track skips Layer 2, it also skips the known-good-profile validation. Is that safe? A fast-tracked user could still make an invalid request (a bug, or a compromised session). **Proposed resolution:** fast-track should skip the *expensive* checks but not the *cheap structural* validation. Even fast-tracked requests should be validated against the known-good profile (which is cheap if the profile is embedded). So fast-track means "skip threat-intel and heavy analysis", not "skip all validation". This refines the spec.

- **GAP 2.3:** What signed/stored state proves the fingerprint? The spec says "IP, auth state, session, stable headers", but IP can change (mobile networks), and the GreyNoise finding shows IP is unreliable. **Proposed resolution:** the fingerprint should lean on the signed session token (cryptographic, reliable) rather than IP (unreliable). IP is a weak signal; the auth token is the strong one. This aligns with GAP 2.1 option (b).

## Simulation 3: A Logging-Only Request (Pure Observation)

**The request:** `GET /static/about.html`, a benign request, with IP-logging configured OFF for privacy.

**Layer 1:**
- Assigns ID `sim-0003`.
- Logs: method, path, timing, but NOT the IP (privacy config off).
- Deterministic rules: all pass.
- Decision: pass-on (or fast-track if fingerprinted; assume first-time, so pass-on).

**Layer 2:** validates (valid), passes.

**S3:** log (verdict: allow, IP omitted) lands in S3.

**Outcome:** allowed, logged without IP.

**GAPS FOUND:**

- **GAP 3.1:** If IP-logging is off, but the request later turns out to be part of an attack (detected over a window at Layer 3), we have logged without the IP and cannot now identify the source. **Proposed resolution:** the privacy config needs a "capture IP but encrypt/escrow it" mode, where the IP is captured but only decryptable under defined conditions (threat suspected). This satisfies privacy (IP not in plain logs) while preserving the ability to act on threats. This is a meaningful refinement: "IP logging off" is too binary; we need "IP escrowed".

- **GAP 3.2:** The evidence graph (Layer 3) is keyed by IP/user. If the IP is not logged, how does the evidence graph accumulate evidence for this source? **Proposed resolution:** the evidence graph may key on the fingerprint (which can exist without storing the raw IP) or on the escrowed IP. The anonymity-mode design (from the developer-friendliness brief) must reconcile with the evidence graph keying. Another refinement: the anonymity mode and the evidence-graph key are coupled and must be designed together.

## Simulation 4: A Cache-Hit Request (No Origin Fetch)

**The request:** `GET /static/logo.png`, already cached at CloudFront, from any visitor.

**Layer 1:**
- Assigns ID, logs.
- Deterministic rules: pass.
- Decision: pass (served from cache).

**Layer 2:** NOT run (cache hit; no origin fetch).

**Application:** NOT reached (served from cache).

**S3:** log lands.

**Outcome:** served from cache, logged at Layer 1 only.

**GAPS FOUND:**

- **GAP 4.1:** On a cache hit, only Layer 1 runs. So all the protection for cached content is Layer-1-only (deterministic, embedded-data rules). Is that sufficient? For static cached content, probably yes (the art-of-the-possible reduction: static pages have a tiny valid space). But it means **Layer 2's app-coupled validation never applies to cached content**. **Proposed resolution:** confirm that cached content only needs Layer 1 protection (likely true for static assets), and ensure nothing requiring Layer 2 validation is cacheable. This is a cache-policy-meets-security-policy coupling that must be explicit.

- **GAP 4.2:** Logging on cache hit: does the CloudFront Function reliably run (and log) on every cache hit? CloudFront Functions run on viewer-request, which is before the cache check, so yes, Layer 1 sees every request including cache hits. But the timing/verdict for a cache hit differs (no origin latency). **Proposed resolution:** the log schema must distinguish cache-hit from cache-miss, and the timing field means different things for each. Minor but needs specifying.

## Simulation 5: A Request To An Unknown-But-Not-Malicious Path

**The request:** `GET /static/new-page.html`, a page that was just deployed but the known-good profile has not been updated yet (the symmetry-principle gap).

**Layer 1:**
- Assigns ID, logs.
- Deterministic rules: not an obvious-bad pattern, passes.
- Decision: pass-on to Layer 2.

**Layer 2:**
- Validates against known-good profile: `/static/new-page.html` is NOT in the profile (it was just deployed, profile not updated).
- Per the no-invalid-request principle: this is invalid. Block.

**Outcome:** a legitimate new page is BLOCKED because the profile was not updated.

**GAPS FOUND:**

- **GAP 5.1 (major):** This is the symmetry-principle gap made concrete. The delegation brief said deploying an endpoint must update the edge, but if a deploy happens without updating Sentinel's profile, legitimate traffic is blocked. In observe-mode this is just a flag; in enforce-mode it is a broken deploy. **Proposed resolution:** the symmetry must be enforced at deploy time (the deploy pipeline updates the Sentinel profile atomically with the application deploy), AND there should be a grace mechanism: a newly-seen path on a static site might be allowed-but-flagged for a short window while async analysis confirms it is legitimate, rather than hard-blocked. This is a real tension between no-invalid-request (block the unknown) and not-breaking-deploys (allow the new). The resolution is the atomic-deploy coupling plus observe-mode-first.

- **GAP 5.2:** This reveals that "the known-good profile" needs a clear update mechanism and ownership. Who updates it, when, how atomically with deploys? **Proposed resolution:** the profile is a deploy artefact (generated from the app's routes/OpenAPI), deployed atomically with the app, owned by the same pipeline. Until that exists, Sentinel runs in observe-mode (flag, do not block, unknown paths).

## Summary Of Gaps From Part 1

| Gap | Severity | Resolution Direction |
|-----|----------|---------------------|
| 1.1 Log finalisation owner | Medium | Define which layer writes the final log per path |
| 1.2 Which layers run on cache hit | Medium | Layer 1 always; Layer 2 on cache miss; Layer 1 must suffice for cached |
| 2.1 Fingerprint allowlist storage at Layer 1 | **Major** | Signed-token fast-track (stateless) or move fast-track to Layer 2 |
| 2.2 Fast-track skipping validation | Medium | Fast-track skips expensive checks, not cheap structural validation |
| 2.3 Fingerprint signal reliability | Medium | Lean on signed token, not IP |
| 3.1 IP-logging-off loses threat attribution | Medium | "IP escrowed" mode, not binary on/off |
| 3.2 Evidence graph keying without IP | Medium | Key on fingerprint; couple with anonymity mode |
| 4.1 Cache-hit is Layer-1-only protection | Medium | Confirm cached content needs only Layer 1; couple cache and security policy |
| 4.2 Cache-hit logging schema | Minor | Distinguish cache-hit/miss in the log schema |
| 5.1 Symmetry gap blocks new deploys | **Major** | Atomic profile-with-deploy; observe-mode-first; grace window |
| 5.2 Profile update ownership | Medium | Profile is a deploy artefact, owned by the pipeline |

The two major gaps (2.1 fingerprint storage, 5.1 symmetry-blocks-deploys) are the most important to resolve before coding, because they change the architecture (where fast-track lives, how deploys couple to the profile).

## What Part 1 Establishes

The generic and logging flows mostly work as specified, but the simulation surfaced eleven gaps, two of them major. This is exactly the value the voice memo wanted: **these are decisions better made now than during implementation.** The proposed resolutions fold back into the rules-of-the-game spec, tightening the acceptance criteria.

Part 2 (blocking malicious activity) will surface more, particularly around the detect-before-damage window and the anomaly-scoring thresholds.

## Relationship To Previous Briefs

| Date | Document | Relationship |
|---|---|---|
| 18 May | `v0.27.58__arch-brief__sg-sentinel-rules-of-the-game-behavioural-spec.md` | The spec these simulations test |
| 18 May | `v0.27.58__arch-brief__sg-sentinel-architecture-and-data-flows.md` | The architecture being simulated |
| 18 May | `v0.27.58__arch-brief__sg-sentinel-time-as-first-class-dimension.md` | The fingerprint/fast-track being probed |
| 18 May | `v0.27.58__arch-brief__sg-sentinel-delegation-and-choke-points.md` | The symmetry principle gap 5.1 exposes |

---

## Acceptance Criteria

| # | Criterion | Verification |
|---|-----------|-------------|
| 1 | The happy-path flows are traced and work | Simulations 1-4 pass (with gaps noted) |
| 2 | Gaps are surfaced honestly, not glossed | 11 gaps found |
| 3 | Each gap has a proposed resolution | All gaps have directions |
| 4 | The two major gaps are flagged for pre-coding resolution | 2.1 and 5.1 highlighted |
| 5 | The resolutions fold back into the spec | Spec tightened |
| 6 | The logging flow is validated end to end | Logging behaviour confirmed (with gaps) |

---

This document is released under the Creative Commons Attribution 4.0 International licence (CC BY 4.0).
