The Simulation — From Recommendation to Modeled Impact
Severity says how loud. Capability says who may act. Relevance says for whom this fires. Simulation answers what happens if.
The Simulation (Sense 19) is the last rung in the jinflow Experience: the path from noticing something is wrong (Signals → Theses → Verdicts → Observations) all the way through modeling the world that would result from acting on it (Suggestion → Intervention → Scenario → Simulation).
This page is the architectural companion to the Simulation guide. The guide walks a user through authoring + building + comparing; this page names the structural choices that make that work.
Why the four nouns
Section titled “Why the four nouns”Most analytics tools collapse “what to do” and “how to do it” into a single recommendation field. That conflation breaks the moment you want to try the same change against last quarter’s data, or combine three independently-authored changes into one scenario, or have an analyst propose a recommendation that a pack author later realises in two different deterministic forms.
jinflow splits them four ways:
| Noun | Tier | Mutability | Shape |
|---|---|---|---|
| Suggestion | A-tier | free-floating prose | what to do (recommendation) |
| Intervention | B-tier | deterministic edit | how to do it (mechanism) |
| Scenario | git branch | composed | an analytical world |
| Simulation | KLS file | computed | the evidence |
The Suggestion ↔ Intervention split mirrors Sense 15’s Observation ↔ Explanation split: prose layer (intent) above mechanism layer (execution), with a soft FK between them. The Scenario ↔ Simulation pair is reality vs. evidence — the branch declares the world, the KLS measures it.
A-tier vs B-tier — the structural choice
Section titled “A-tier vs B-tier — the structural choice”This is the single most important architectural decision in Sense 19.
Suggestions are A-tier — they live on the suggestions orphan
branch alongside notes, bookmarks, recents, and bell resolutions.
That means they’re naturally shared across every scenario you
create. The same Suggestion remains visible whether you’re on
main, scenario/lower_thresholds, or
scenario/zeta_onboarding — because the orphan branch sits
outside the scenario DAG entirely.
Interventions are B-tier — they live on whatever branch you
authored them on. main and each scenario carry their own
intervention set. The same Suggestion can spawn different
Interventions on different branches — that’s not a bug, it’s the
mechanism by which scenarios are different.
If we’d made Interventions A-tier too, every scenario would inherit every Intervention ever authored — and you’d lose the ability to say “this scenario applies exactly these three interventions.” If we’d made Suggestions B-tier, you’d have to re-author the same recommendation on every branch you wanted it visible on. Splitting them was the move that made the whole chain compose cleanly.
tenant AFS git repo├── main ← B-tier home (smebits, signals, interventions)├── scenario/lower_thresholds ← B-tier alternative (different interventions applied)├── scenario/zeta_onboarding ← another B-tier alternative│├── notes ← A-tier orphan├── bookmarks ← A-tier orphan├── recents ← A-tier orphan├── bell ← A-tier orphan└── suggestions ← A-tier orphan (joined the family 2026-05-22)A scenario reads its B-tier from its own branch and its A-tier from the orphan branches. Compare against another scenario or production swaps the B-tier while the A-tier stays identical.
The KLS tables
Section titled “The KLS tables”A built KLS — production or scenario — carries the artifacts of
Sense 19 in four tables, all under the tenant’s meta schema
(_<tenant>):
| Table | Source | Populated when |
|---|---|---|
suggestion_registry | afs/suggestions/*.yaml | Always (orphan branch is pulled into the working tree at Phase 1c of jin make) |
intervention_registry | afs/interventions/*.yaml | Always (the file is on whatever branch is being built) |
scenario_metadata | The scenario branch’s commit message | Only when building from a scenario/<id> branch |
notifications | The Bell aggregator (Sense 33) | Always |
The asymmetry — scenario_metadata only on scenario builds — is the
feature-detection mechanism the Compare UI uses to distinguish
scenario KLSes from snapshot KLSes. A KLS that has
scenario_metadata rows is a scenario; one that doesn’t isn’t.
The apply mechanism
Section titled “The apply mechanism”The Phase 2 verb jin scenario create is mostly orchestration. The
load-bearing piece is
jinflow/scenarios/apply.py::apply_intervention() — the
deterministic yaml_edit walker.
Three guarantees:
- Deterministic. Same intervention + same starting file → same output. No randomness, no network, no system clock.
- Drift-checked. Every change carries a
fromvalue the intervention expects to find at the path. Mismatch → refuse to apply. This is what makes interventions reliable across time: if reality drifted, you find out at apply, not at simulation. - Format-preserving. Uses
ruamel.yamlfor round-trip read + write. Comments survive. Key order survives. The diff against base is exactly the bytes the intervention changes — reviewable as a one-line PR even when the target file has dense formatting.
The path syntax is dotted-with-bracket: thresholds.amount_chf
for nested dicts, severity_rules[0].severity for list elements.
The apply refuses to create missing keys — an intervention can’t
invent a new top-level field. That’s an artifact_add-shape
change, which Phase 2 doesn’t implement.
The apply refuses absolute target_file paths and any path
containing .. segments — defense in depth before the load-bearing
filesystem write. The Phase 1 checker (interventioncheck.py)
catches these at author time; the apply re-verifies because
defense in depth matters when the load-bearing operation is a
file write.
The scenario branch
Section titled “The scenario branch”A scenario is a git branch named scenario/<id> in the tenant AFS,
nothing more. Its uniqueness is captured by:
- The base — what it was forked from. Recorded in the structured
commit message as
Base: <branch>@<sha8>. - The interventions applied — listed in the same commit message
under
Interventions applied:.
create_scenario’s commit-message shape is the authoritative
record of “this is what made the scenario.” It’s git’s canonical
history of that act, and the Phase 4 metadata bake reads it back
to populate _<tenant>.scenario_metadata.
Why commit-message-as-source-of-truth rather than a baked manifest file inside the AFS? Three reasons:
- Git already records who, when, with what commit message — we’d be duplicating its job.
- A manifest file would itself be a B-tier artifact that intervention-edits could accidentally modify.
- Amending the commit (a normal git operation) needs zero bookkeeping — the bake reads whatever’s there at make time.
The trade-off: if someone amends the commit message into an unstructured shape, the bake silently no-ops rather than write a partial row. The check has explicit assertions; the contract is “the structured shape is required for the metadata to land.” A future verb that re-bakes metadata from a sidecar file could exist if the constraint ever bites; today it doesn’t.
The simulation
Section titled “The simulation”A simulation is just jin make on a scenario branch. The existing
--branch flag for jin make already does the heavy lifting:
- Creates a temporary git worktree at the requested branch
- Runs the full make pipeline against the worktree
- Writes the KLS to a branch-named path:
<pack>_<tenant>_<safe_branch>_kls.duckdb
Where safe_branch is the branch name with / and \ replaced by
_. For scenario/tighter, that’s scenario_tighter. The
resulting KLS filename is
<pack>_<tenant>_scenario_tighter_kls.duckdb.
jin scenario build <id> is a thin wrapper over
jin make --branch scenario/<id> — it exists for ergonomics and
to catch missing-branch errors earlier (clearer message than what
jin make would surface).
The KLS-naming convention is pinned in two places:
make.pyline ~2136 (the source of truth — where the build writes)scenario.py::_scenario_kls_path()(where the list verb looks for build status)
The test test_scenario_kls_path_convention enforces both stay in
sync. Drift would silently break “is this scenario built?”
detection in the list verb.
The Compare integration
Section titled “The Compare integration”JinDesk’s /[tenant]/compare route reads every *_kls.duckdb
in the store. For each, it opens read-only, queries summary stats,
and (Phase 4) queries _<tenant>.scenario_metadata to see if the
KLS is a scenario.
KLSes with baked scenario metadata are routed to the “Scenarios” optgroup in the version-selector dropdowns; the rest land in “Production.” A scenario card renders an amber panel listing the interventions applied + a “Compare to production” shortcut that sets the other side to the working KLS.
No new infrastructure was needed in Compare — the snapshot-
comparison machinery already supported arbitrary *_kls.duckdb
files. The Phase 4 work was just teaching it to read one extra
table and group the rows accordingly.
Where the verbs live
Section titled “Where the verbs live”| Layer | Path | Role |
|---|---|---|
| Contracts | contracts/suggestion_contract.v1.json, contracts/intervention_contract.v1.json | JSON-Schema declarations |
| Checkers | scripts/suggestioncheck.py, scripts/interventioncheck.py | Pre-bake validation, security checks on target_file |
| Apply mechanism | jinflow/scenarios/apply.py | Deterministic yaml_edit, drift check, ruamel round-trip |
| Scenario creator | jinflow/scenarios/create.py | Branch + apply + commit with rollback |
| Bakers | jinflow/cli/commands/baking.py::bake_suggestion_registry, bake_intervention_registry, bake_scenario_metadata | Source YAMLs → KLS tables |
| CLI | jinflow/cli/commands/scenario.py | jin scenario create / build / list |
| JinDesk | explorer/src/routes/[tenant]/compare/* | Optgroup, scenario panel, Compare-to-production button |
| Sense doc | docs/design/sense_19_the_simulation.md | Canonical specification, all four phases |
What the Simulation is not
Section titled “What the Simulation is not”- Not a recommender system. Suggestions are authored by humans (analysts, pack authors, automated agents in the future). The Bell, the Atelier, and the Notebook surface suggestions; nothing invents them autonomously.
- Not a database migration tool. Interventions are deterministic
YAML edits. They don’t run SQL, don’t migrate schemas, don’t
modify the KLS. The KLS is computed from the post-intervention
AFS by
jin make, the same way production builds frommain. - Not a what-if calculator. Scenarios run the full analytical pipeline — the same compilers, the same validators, the same signal-finding emission. The simulation result is as trustworthy as production because it is production, just against different inputs.
- Not a permissioning surface. Capability gating (Sense 25) and identity (Sense 18) compose orthogonally with this. Authoring a Suggestion requires the same write capability as authoring a note; creating a scenario requires the same as branching the AFS manually.
Composition with other Senses
Section titled “Composition with other Senses”- Sense 15 (Observation/Explanation) — the prose/mechanism split repeats one level up. Observation is to Explanation as Suggestion is to Intervention.
- Sense 25 (Identity & Passes) — controls who can author + who can apply. The verb-level capability gate.
- Sense 31 (The Inlet) — the source-data pipeline a scenario
rebuilds with. Channel-level changes can themselves be an
Intervention type (
channel_change, not yet implemented). - Sense 33 (The Bell) — surfaces things the system noticed.
A future verb
jin bell acted-via sug_Xcloses the loop: acting on a ring by authoring a Suggestion. - Sense 37 (The Snapshot) — a scenario KLS is, conceptually, a
snapshot of an alternative world. They share the
_<tenant>.snapshotschema and the same Compare machinery. - Sense 38 (The Scribe) — when a viewer creates a scenario via a future cloud-JinDesk, the Scribe identity governs the commit attribution.
Open questions
Section titled “Open questions”- Scenario lifecycle. Today scenarios are git branches with their own KLS files. Should they auto-prune after N days of inactivity? Get adopted into main with a verb? Versioning policy is not yet specified.
- Compound scenarios from multiple suggestions. A Suggestion can spawn multiple Interventions. A Scenario can apply multiple Interventions. But can a Suggestion be referenced from multiple Scenarios? Today yes (the Suggestion is orphan-branch, free-floating); the UX for “show me all Scenarios derived from sug_X” doesn’t exist yet.
- Non-yaml_edit Intervention types. The contract enumerates
five (
yaml_edit,artifact_add,artifact_remove,config_change,seed_edit); only the first is implemented. The others land when the use case arrives. - Scenario sharing across machines. Scenarios are git branches — they push and pull like any other. But the scenario KLS is a build artifact; it doesn’t travel via git. Two analysts wanting to share a simulation need to either both build, or use the existing snapshot-sharing affordances.
- The “What if?” inline UX (Phase 5). A button on a Verdict detail page that flows through to scenario authoring + background build + notification + Simulation Results view. Substrate is ready; UI not built.
Quotable
Section titled “Quotable”A jinflow analysis tells you what is happening. A thesis tells you why. A verdict tells you the root cause. A simulation tells you what would happen if you fixed it. The loop closes.