Skip to content

Sense 20: The Seam — Where Pack and Tenant Meet

Sense 20 · Steady · Last touched 2026-07-27

  • last_verified: 2026-07-27

Synced from docs/design/sense_20_the_seam.md in the engine repo — that’s the source; this page is a build-time mirror.

A pack is a starter kit. A tenant is a life.

They touch each other at a seam, and the seam has its own rules.

Status: proposed Date: 2026-04-24 Author: the owner + Claude (conversation, Santa Pola)


Every jinflow tenant begins as a copy of a pack. jinflow init --pack numetrix clones the starter kit into a fresh AFS; from that moment the tenant has its own git history, its own notebooks, its own Subject Matter, its own evolving understanding of the business it describes.

The pack, meanwhile, keeps moving. New signals are added. Macros are refined. The bronze layer grows an as_of_date column. A pattern discovered in one tenant is generalised and codified. The pack wants to come back into the tenant’s life. Sometimes the tenant wants to send something back the other way.

Today that movement is a single command — jinflow afs update — and it is too blunt for the thing it is trying to do. It reads: is this file the same on both sides? If so, do nothing. If the pack changed but the tenant didn’t, overwrite. If the tenant changed but the pack didn’t, leave alone. If both changed, refuse and print a conflict.

That model is correct as a filesystem diff. It is wrong as a model of how packs and tenants relate.

Three things it misses.

First, direction is not symmetric. Pack → tenant is “inherit a starter kit and keep your specialisation.” Tenant → pack is “harvest a learning without leaking your identity.” The same file, moving the same way in git, means opposite things depending on which direction you read. A tool that treats them as mirrored operations will corrupt both.

Second, “conflict” is one word for several different situations. Two edits to the same file can be a semantic collision, or a coincidental overlap in unrelated regions, or a legitimate specialisation — tenant narrowed a pack default on purpose, and the pack has since evolved its default. Each deserves a different response. Today they are all the same red exclamation mark.

Third, the pack has no language for its own identity. It ships a starter kit but cannot say what is pack-shaped and what was merely the first tenant’s content that happened to be there. Without that language the write-back direction is impossible without reintroducing tenant names into the pack — which violates the pack’s portability.

This Sense is the vocabulary for fixing all three.


The Seam is the boundary where pack and tenant meet.

It is not a file. It is not a command. It is the set of rules, conventions, and named acts that govern what crosses, in which direction, under what conditions, with what memory of what came before.

A healthy seam has four properties:

  1. It knows its sides. The pack knows what a pack looks like. The tenant knows what is tenant-authored. Neither side needs to know the other’s identity.
  2. It remembers. Every artifact that crosses carries a trace — where it came from, when, through which merge base.
  3. It names its crossings. Moving from pack to tenant is not the same act as moving from tenant to pack. Each has a name, a ritual, and a review surface.
  4. It refuses ambiguity. When the seam cannot tell one kind of conflict from another, it stops and asks a human. It never silently picks a winner.

The rest of this document is the vocabulary that makes those four properties possible.


A pack, today, is a directory tree. It cannot say what it is — only what files it happens to contain. If yesterday a notebook named nb_rmc_pricing ended up in the pack because a generous copy-paste swept it up, the pack cannot tell the notebook that it does not belong there.

Pack Scope is the pack’s first-person declaration of what belongs to it. It is inclusive, not exclusive. The pack says:

These are the kinds of things I contain. These are the paths I own. These are the frontmatter shapes I recognise as mine.

Everything that matches is pack-eligible. Everything else is assumed tenant content by default.

Scope is written in the pack and only in the pack. It never names a tenant. It never references a tenant-specific file or folder. A new tenant named cardinal that has never existed must not change anything about what the pack thinks is its scope. A pack whose scope file grows whenever a new tenant is onboarded is broken by definition.

Scope is expressed in layers. Most of what a pack contains matches a small number of structural rules — all YAML under signals/, all SQL under models/, contracts, macros, entity definitions. A few files need explicit per-file declarations — a demo notebook the pack ships, a seed file, a starter dossier. The pack may also recognise frontmatter self-declarations: an artifact that writes scope: pack in its header opts in regardless of path.

The exact syntax is an implementation concern, not a Sense concern. What matters at this layer is the principle:

The pack declares itself. The seam enforces the declaration.


Once Pack Scope defines the shape of a pack, every artifact in any AFS can be classified by where it came from and how it got there. That classification is its provenance.

Four values cover the common cases:

  • pack — authored in the pack, arrived in the tenant via afs update. If the tenant has not touched it since, it is a faithful pack copy.
  • tenant — authored in the tenant, local and unshared. A notebook about yesterday’s incident; a Subject Matter pinned to this hospital.
  • promoted — authored in the tenant, then crossed the seam into the pack. The pack now owns it, but the memory that it was first learned in a tenant is kept.
  • specialised — authored in the pack, then locally modified in the tenant. A macro the pack ships generically that this tenant had to narrow for its source-system quirk.

Provenance is a mix of signals, not a single field. Some of it lives in frontmatter (origin: tenant, promoted_from: rmc, based_on_pack: 41eba07). Some of it is inferred from the merge base and current file state (the seam can tell that a file matches the last pack version it saw, or differs from it, and how). Some of it is implicit in path matching against Pack Scope.

The tenant reads provenance to answer “am I free to edit this?” The seam reads provenance to answer “what should happen if both sides edit this?” The future pack-back tool reads provenance to answer “what in this tenant is promotion-eligible?”

Provenance is how the seam remembers.


Git has it. We do not use it. Every tenant AFS knows which pack commit it last synchronised from — today that is stamped somewhere in the tenant manifest, but the afs update command treats it as a label, not as a merge base.

A real seam treats it as the merge base: the common ancestor from which the tenant has evolved in one direction and the pack in another. With the merge base, three-way reasoning becomes possible:

  • If the base and the pack agree, and the tenant has changed, the change is a tenant specialisation. Keep it.
  • If the base and the tenant agree, and the pack has changed, the change is a pack evolution. Take it.
  • If all three differ, it is a true conflict, and the seam must name its kind (below) rather than give up.

The merge base is not a new field. It is an existing fact the seam must begin respecting.


Not all “both changed” is one thing. At the seam, conflicts come in three kinds, and each wants a different answer.

Coincidental conflict. Both sides touched the file, but in different regions. The pack added a column to the bottom of a YAML; the tenant added a filter to the top. A textual three-way merge can almost always resolve these automatically, the same way git’s merge driver does for source files. The seam should attempt it silently and only escalate if the merge fails.

Semantic conflict. Both sides changed the same thing, differently. The pack renamed a column priority → is_priority; the tenant renamed the same column priority → priority_flag. No textual three-way merge can help. This is a genuine disagreement about what the concept should be called or how it should behave. The seam must stop and surface both versions with full context — what each side intended — and wait for human judgment.

Intent conflict. The pack evolved its default; the tenant had already specialised it. The pack’s bronze_abt_kst_mapping.sql changed its matcher from strict to lenient; the tenant had already narrowed the matcher to a single source-system value. Neither side is wrong. The question is whether the tenant’s specialisation is still needed after the pack’s evolution, or whether the pack’s new default now subsumes the specialisation. The seam must present this as a decision, not as a collision. “The pack now handles your case generically — keep your override, or fold back to the default?”

Today all three are reported identically. A proper seam names them and treats them accordingly: coincidental conflicts resolve quietly, semantic conflicts block loudly, intent conflicts surface as a conversation.


Most of what happens at the seam is unremarkable. The pack evolves; tenants pull. Tenants specialise; packs leave them alone.

But sometimes a tenant learns something worth sharing. A signal pattern discovered in rmc that would benefit every future tenant. A macro that solves a problem no pack ever anticipated. An entity definition that is more honest than the one the pack ships. The tenant has made it better, and the pack should take the lesson.

This act has a name: promotion.

Promotion is not silent sync. It is not afs update in reverse. It is a reviewed, conscious crossing of the seam with the following shape:

  1. The tenant identifies an artifact it wants to offer.
  2. A dry-run lists everything in the tenant that matches Pack Scope and differs from what the pack has.
  3. A human reviews the list and accepts or rejects each entry.
  4. Accepted entries are copied to the pack, with their tenant-specific identity stripped. Frontmatter fields that reference the source tenant are removed or generalised. The pack-blindness invariant is maintained by review, not by magic.
  5. The promoted artifact carries provenance promoted_from: <tenant> in its metadata — the pack knows it was learned, not invented.

The symmetric act is demotion: something the pack shipped that, in hindsight, was too specific. A notebook that was never generic enough. A signal that only one source system can produce. Demotion removes the artifact from the pack and returns it, with provenance intact, to any tenant that was carrying it.

Promotion and demotion are the explicit crossings. Everything else at the seam is inheritance and specialisation.


With Pack Scope, Provenance, Merge Base, and Conflict Taxonomy in place, the two directions of the seam become coherent operations.

Pack → Tenant (afs update, today). For each file, consult Pack Scope to decide whether it is pack-eligible. Use the merge base to do three-way reasoning. Resolve coincidental conflicts silently. Present intent conflicts as specialisation decisions. Halt on semantic conflicts. Never silently overwrite a tenant specialisation.

Tenant → Pack (pack-back, the subject of this Sense’s motivation). For each artifact in the tenant AFS, consult Pack Scope to decide whether it is pack-eligible. Of the eligible set, list those that differ from the pack or do not exist in the pack. Present each as a promotion candidate. On acceptance, strip tenant identity, copy to pack, stamp provenance. Commit.

Tenant → Tenant is the third direction, and it deserves a name but probably not an immediate implementation. If rmc learns something inspire would benefit from, the honest path is rmc → pack → inspire. Direct tenant-to-tenant sharing exists as a shortcut, but it circumvents Pack Scope’s review surface and Provenance’s memory. It may be worth shipping later as a power-user operation; the default should be that learnings flow through the pack.


Four things the seam must never violate, even when it would be convenient.

Pack blindness. The pack contains no reference to any specific tenant. No tenant name appears in Pack Scope, in provenance defaults, in promoted artifact content, or in any rule the seam applies. A pack shipped to a new customer tomorrow must behave identically for them as for the customer who contributed its most recent promotion.

Tenant sovereignty. The tenant’s local edits are never silently overwritten. Even when the pack has “better” content for the same path, a tenant specialisation must be surfaced as a decision, not erased as a conflict. The tenant is the authority on its own AFS.

No silent regressions. When a pack change would remove or rename something the tenant still references — a column dropped from a gold view that a tenant signal selects, a macro renamed that a tenant SQL calls — the seam must refuse to apply the change without explicit acknowledgement. The is_priority incident and the ASOF JOIN incident were both instances of this invariant being absent.

Pack-out-of-reach. When a tenant is operating or evolving, the pack is not present. Not as a network call, not as a file lookup, not as a runtime fall-through, not as an inheritance chain to chase. A tenant’s AFS is fully self-sufficient: reading any of its files tells you everything needed to render its meaning, with no implicit context borrowed from elsewhere. The pack only enters the picture at discrete alignment events (an afs update, a promotion review), where it acts as a source of copies into the tenant — never as a source of references. After the alignment dust settles, the pack disappears again. Tenants must be able to operate indefinitely against a pack that has been moved, decommissioned, or simply left unmaintained. This separates the Seam pattern from the runtime config-cascade used by config.yml, which is operational scaffolding rather than semantic content.


A Sense is not a feature. It is a shape that, once named, lets many features agree on what they are doing. The Seam earns its place in the numbered list because five otherwise unrelated problems turn out to be the same problem once the seam has a name:

  • The is_priority regression (pack overwrote tenant optimisation).
  • The ASOF JOIN regression (same pattern, different column).
  • The 112-file catch-up the inspire tenant is avoiding (coarse afs-update forces all-or-nothing).
  • The pack-back plan for harvesting rmc’s Stage 3 ingestion work (no vocabulary today for “promote without leaking identity”).
  • The future hrcentral pack, which needs to inherit from a numetrix that has learned from three years of tenant-specific work (cross-pack promotion, same seam concepts applied one level up).

Each of these wants the same four things: know the sides, remember the history, name the crossings, refuse ambiguity. This Sense gives them a shared language. The implementation can then proceed a piece at a time without reinventing the vocabulary each time.

The seam is where jinflow’s promise of reusable starter kits plus tenant sovereignty actually lives or dies. It is worth naming.


  • No file formats. Pack Scope, provenance frontmatter, merge-base storage are all implementation decisions. This Sense fixes the concepts; the HOW is a follow-up design doc.
  • No CLI. Whether promotion is a command, a REPL mode, a review-UI, or a dry-run of an existing command is open. This Sense does not prescribe an interface.
  • No migration plan. Existing tenants have no Pack Scope, no provenance, no merge-base. How to retrofit is a separate concern.
  • No scoring. Whether the seam ranks promotion candidates by staleness, by usage, or by author intent is out of scope here.

These are the next conversations. This Sense is the place they can begin from a shared vocabulary.


The is_priority incident began as an innocent column rename and ended as a three-day forensic trail across pack history, two tenant AFS repos, and one confused build log. It was not a bug in any single file. It was a symptom of the seam being unnamed.

Name it, and the next incident — which will come, wearing a different column’s name — can be handled in minutes instead of days. Name it, and pack-back becomes possible without leaking tenant identity. Name it, and inspire’s 112-file deficit becomes a curated catch-up instead of a procrastinated migration.

The seam was always there. This Sense is just the door finally being carved into it.


The Seam is the pack-to-tenant boundary’s mechanical surface. Three CLI verbs handle the moves: jin afs update syncs forward from pack to tenant while preserving tenant-local edits, jin afs diff previews what would change, jin afs reset hard-resets the tenant to pure pack state. Together they make the pack-out-of-reach invariant operational — between upgrade events the pack is genuinely absent; only jin afs * brings it back into view.

  • CLI: jin afs update (dry-run by default; --do-it to apply; --all for every tenant) · jin afs diff (preview pack vs tenant) · jin afs reset (hard-reset to pack state, dry-run by default)

  • Invariant: pack-out-of-reach — between upgrade events, no runtime path can read from the pack

Status: shipped — steady. Core infrastructure underneath every pack-based tenant.


Numerical neighbors:Sense 19.2: The Incremental Scenario · Sense 21: The Heartbeat — keeping the system in rhythm

jazzisnow jinflow is a jazzisnow product
v0.64.7 · built 2026-09-20 19:48 UTC