Sense 23: The Engine's Seam — Where the Engine and the Tenant Disagree
Sense 23 · Forming · Last touched 2026-08-20
- last_verified: 2026-07-27
Synced from
docs/design/sense_23_the_engines_seam.mdin the engine repo — that’s the source; this page is a build-time mirror.
The pack is a starter kit; the tenant is a life. The engine, it turns out, is a third party at the table — and it has been evolving without asking anyone’s permission.
Status: proposed Date: 2026-05-01 Author: the owner + Claude (travel-day conversation) Sibling of: Sense 20 — The Seam
What pulled us in
Section titled “What pulled us in”Sense 20 named the boundary between pack and tenant. It carried four invariants — pack blindness, tenant sovereignty, no silent regressions, pack-out-of-reach — and gave them vocabulary: scope, provenance, merge base, conflict taxonomy, promotion.
But Sense 20 didn’t notice a third party in the room.
The engine evolves too. Compilers grow new fields. Registry tables gain columns. Macros get renamed. Schema expectations shift. None of this is pack/tenant territory; the engine moves on its own cadence, shaped by code commits in the engine repo.
When the engine moves, every tenant in the world is affected. Yet
tenants are not in the room when the engine ships a release. They find
out the next time someone runs jin make.
Three incidents — the same shape.
| Date | What moved | What broke |
|---|---|---|
| 2026-04-04 | Engine renamed probe_entity_type → signal_entity_type (Sense 14 vocabulary rename) | Tenant entity_registry.sql still emitted the old column name; JinDesk queries against the new one returned errors |
| 2026-04-17 (rmc) 2026-04-21 (inspire) | Engine + pack rolled forward on is_priority boolean form | Tenant SQL referencing the old priority (string) silently produced wrong results until the build failed downstream |
| 2026-03-26 (intermittent) | Engine’s afs update overwrote a tenant’s deliberate ASOF JOIN optimization | Tenant lost an analytical optimization without anyone noticing for a week |
Each looked like a bug. Each was a symptom of the same missing thing: the engine has no governed relationship with the tenant. It moves; the tenant catches up by accident.
This is the gap Sense 23 fills.
The Concept
Section titled “The Concept”The Engine’s Seam is the boundary between the engine and any tenant that runs on it.
It is not the same as The Seam (Sense 20). The Seam is bilateral between two repositories of analytical content (pack ↔ tenant). The Engine’s Seam is unilateral in shape: the engine moves; tenants accept or are stuck. But the invariants of Sense 20 still apply, with different weight per invariant.
A healthy Engine’s Seam has the same four properties Sense 20 named, adjusted for the engine-tenant axis:
- It knows its sides. The engine declares what AFS shape it accepts. The AFS declares what engine version it was authored against. Neither side has to guess.
- It remembers. Every engine release that changed the AFS shape carries a migration script. The migration is the trace of what moved.
- It names its crossings. Soft deprecations are loud warnings. Hard breakages refuse to run and point at the migration. No silent rewrites.
- It refuses ambiguity. If the AFS predates a migration the
engine considers mandatory,
jin makehalts. It does not “do its best” with old shapes.
The rest of this Sense is the vocabulary that makes those four properties operable.
Engine Version
Section titled “Engine Version”Every engine release carries a version. Today this is set in
pyproject.toml and reachable as jinflow.__version__. Tenant AFSes
already declare a compatibility hint in jinflow.yml:
jinflow_engine_version: ">=0.1.0"In Sense 23 this becomes a contract, not a hint:
- The AFS declares the engine version it was authored against. Set
on
jinflow initand updated by migrations. - The AFS also declares the migration baseline — the oldest engine version whose shape this AFS still matches.
- The engine, on every
jin make, reads both and refuses to proceed if the AFS sits below the engine’s current migration floor.
The two-version tracking matters because authoring intent (when this AFS was first laid down) is different from compatibility state (what shape this AFS is currently in). After a migration, the second moves; the first is permanent history.
Migration
Section titled “Migration”A migration is an idempotent script the engine ships that updates an AFS from version N to version N+1.
Migrations are first-class engine artifacts:
jinflow/migrations/ v0.43_signal_vocabulary_rename.py # probe → signal, etc. v0.46_classifications_block_optional.py v0.47_thresholds_via_dbt_vars.py ...Each migration:
- Names the engine version it bridges to.
- Reads the AFS, writes the AFS. Pure file-tree transformation.
- Is idempotent — running twice is a no-op.
- Carries a one-line description (“renamed
probe_entity_typetosignal_entity_typein entity YAMLs”) and a longer explanation the operator can read before running. - Is reviewed by the engine team like any code change. The engine’s migration directory is the canonical record of every shape-shift.
Migrations run via an explicit command:
jinflow migrate numetrix.rmcjinflow migrate numetrix.rmc --check # dry-runjinflow migrate numetrix.rmc --to 0.47.0 # explicit targetNever silently inside jin make. The boundary between “I know what’s
happening to my AFS” and “I don’t” is the migration command. jin make may suggest running it; it never runs it.
Compatibility Handshake
Section titled “Compatibility Handshake”At the start of every jin make, the engine performs a handshake:
- Read engine’s current version:
0.47.0 - Read AFS’s authored version:
0.43.0 - Read AFS’s current migration baseline:
0.45.2 - Look up engine’s migration window: “I accept AFSes from baseline ≥ 0.45 without migration; ≥ 0.40 with auto-migration; below 0.40 refuse.”
- Decide:
- Match → proceed silently.
- Auto-migratable gap → emit a soft warning (“AFS is at 0.45.2;
0.46 added classifications support — auto-applying. Run
jinflow migratefor clarity.”), apply minimal migrations in memory, build. - Migration required → halt. Print the migration command. Refuse to build.
- AFS ahead of engine → halt. “This AFS expects engine ≥ 0.50; you are running 0.47. Upgrade the engine.”
The handshake is the named crossing. Without it, every jin make is a
silent gamble.
Soft Deprecation vs Hard Breakage
Section titled “Soft Deprecation vs Hard Breakage”Engine moves are not all the same. Sense 23 names two kinds:
Soft deprecation — the engine recognizes both old and new shapes
for a window, prefers the new, and warns when it sees the old. Example:
if a future release renames tooltips: to legends: (it shouldn’t,
but as a worked example), the engine reads either, builds correctly,
and emits “you’re using tooltips: — this works for now; please
update by v0.50.”
Soft deprecation has a window. The release notes name the version where the old shape stops being read.
Hard breakage — the engine no longer accepts the old shape at
all. Migration is mandatory before jin make will run. Reserved for
shape-shifts where dual support is genuinely costly (registry column
removal, file-layout changes, semantic incompatibilities).
The discipline:
- Every breaking change ships with a migration.
- Major version bumps are the only place hard breakages happen.
- Minor versions only soft-deprecate.
- Soft deprecations announce their hard-breakage version in their warning text.
This is not novel — it’s the discipline most mature systems carry. The Engine’s Seam is the formalization of it for jinflow.
The Four Invariants, Engine-Side
Section titled “The Four Invariants, Engine-Side”The same four invariants Sense 20 named, in engine-tenant language. Two of them already hold; two are gaps that have bitten us.
Engine blindness ✓ already
Section titled “Engine blindness ✓ already”The engine knows nothing about any specific tenant. No tenant name,
no domain, no business context appears in engine code. A new
hospital tomorrow runs jin make against the same engine as the
hospital that contributed yesterday’s bug fix. This invariant is
already true and worth preserving — code that “knows about rmc” or
“handles inspire’s special case” should never land in the engine.
Tenant sovereignty against engine drift ✗ gap
Section titled “Tenant sovereignty against engine drift ✗ gap”Currently jin make may auto-commit changes to tenant YAMLs as a
side effect of building. We’ve seen this happen in surprising ways —
a jin make auto-commit has captured changes the operator didn’t
expect to be committed. This invariant says: the engine does not
silently rewrite the tenant’s authored content. Migrations rewrite,
named explicitly. Compilation reads, never writes back to authoring
artifacts.
This is the Phase 2 of consistency_plan.md spirit, generalized to
the engine boundary. Today’s jin make violates this in places it
should not.
Engine deprecations are loud ✗ gap
Section titled “Engine deprecations are loud ✗ gap”Column renames, schema removals, behavior changes — these have all happened silently. The engine moved; the tenant found out by debugging. This invariant says: every engine evolution that affects the AFS announces itself in a release-note entry that the operator sees, and (for soft deprecations) at runtime when the old shape is detected.
Today there is no such announcement infrastructure. The engine ships;
release notes describe code-level changes; nothing tells the operator
“your tenant’s entity_registry.sql was generated against an old
column name; your queries may fail.”
Engine-out-of-reach at runtime ✓ already
Section titled “Engine-out-of-reach at runtime ✓ already”The engine is not a runtime dependency of tenant data. The KLS does
not call back to engine code; JinDesk reads the KLS directly;
SIS does not invoke compilers. An engine version mismatch breaks
jin make (the boundary) but does not break a built KLS that’s
already on disk. This invariant already holds and should never be
weakened. (P2P2P depends on it; the Heartbeat depends on it; many
operating modes depend on it.)
The Three Layers
Section titled “The Three Layers”With Sense 23 named, the full picture has three seams, all governed by the same four invariants:
Engine ────────── ? ──────────► Tenant (Sense 23) ▲ │ (Sense 20) │ ▼ Pack ▲ │ (deferred: consortium) │ ▼ Sister tenantThe engine evolves behind every pack and every tenant. The pack evolves through promotion from tenants. The tenant lives its life between alignment events with both.
A given jin make involves negotiating both seams: the engine must
agree to run against this tenant’s AFS shape, and the AFS must be
internally consistent with whatever pack-borrowed content it carries.
Two compatibility checks, two named crossings.
What This Sense Deliberately Does Not Say
Section titled “What This Sense Deliberately Does Not Say”- No migration framework code. The migration directory layout, the script API, the dependency declaration — those are implementation. This Sense fixes the concept and the responsibilities.
- No version-bumping policy. When does a change deserve a major version? That’s a release-engineering decision, not a Sense decision. This Sense only says: major versions are where hard breakage lives.
- No specific deprecation list. The current engine probably has a handful of “you should be using X now” cases that haven’t been surfaced as deprecation warnings. Naming and warning each is follow-up work.
- No retrofit for tenants below the migration baseline. If someone has a tenant on engine 0.13 and we’re shipping 0.50, the migration chain may not connect cleanly. That’s a content-rescue problem, not a Sense problem. We accept that some tenants may need manual triage.
- No tooling for engine-team review. When an engine PR introduces a breaking change, who decides if it qualifies for major-version promotion vs auto-migration? That’s a code-review discipline question, addressed by the team’s process, not the Sense.
These are the next conversations. Sense 23 is the place they can begin from a shared vocabulary.
Why a Sense
Section titled “Why a Sense”Three things this Sense unlocks once named:
- The is_priority class of incident becomes detectable. Today each one is forensic; with engine version handshake + migration discipline, the same incident next time announces itself before it bites.
- Pack-back becomes safer. Sense 20’s pack-back ritual depends on the pack and tenant agreeing on shape. When the pack moves forward via engine migration AND a tenant moves forward via afs update, the two crossings need to agree. Without Sense 23, that agreement is implicit; with it, it’s contractual.
- The engine’s release cadence becomes legible. Operators of tenants can read the release notes and see “this is a soft change, this is a hard one, this needs migration.” Today they have to grep diffs.
The Engine’s Seam is the door we did not notice was already in the wall. Naming it lets the team finally walk through deliberately instead of stumbling across it.
Connection to Other Senses
Section titled “Connection to Other Senses”- Sense 20 — The Seam. Direct sibling. Same four invariants,
different bilateral. Both seams operate during a
jin make/afs updatecycle; both must agree. - Sense 21 — The Heartbeat. Beat 4 (sync) and Beat 5 (notify) carry engine-evolution news. A new engine release should pulse the operator: “engine 0.50 is out; your AFS is at 0.45 baseline; one minor migration available.”
consistency_plan.md. Phase 2’s “make is read-only” matches the tenant-sovereignty invariant here. Phase 4’s schema migrations are exactly the engine-side migrations this Sense names.sense_25_identity_and_passes.md. The pass to runjinflow migrateis a higher-authority capability thanjin make. Migrations rewrite authored content; only operators with write authority on the AFS should be able to run them.
Closing
Section titled “Closing”The pack and the tenant have always known about each other. The engine has been pretending it doesn’t matter. Three regressions in two months say otherwise.
Naming the Engine’s Seam is the door we did not notice was already in the wall. The mechanics — version handshake, migrations, soft deprecation, hard breakage discipline — are familiar from every mature system that ships software people depend on. We are catching up to common practice, not inventing it.
What we keep, and what makes it ours: the four invariants. Engine blindness, tenant sovereignty, loud deprecations, runtime out-of-reach. The same four that govern The Seam, the same four that will govern The Federation when we name it. The shape of healthy seams everywhere.
How this manifests in jinflow
Section titled “How this manifests in jinflow”Not yet manifest in the engine. Status: proposed. See the implementation phases / open questions above for the path from this paper to running code.
Numerical neighbors: ← Sense 22: The Legend — labels that explain themselves · Sense 24: The Steward — jinflow Operating On Itself →