Skip to content

Sense 36: The Lineage — A Tenant Remembers Where It Came From

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

  • last_verified: 2026-07-27

Synced from docs/design/sense_36_the_lineage.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 autonomous after birth.

But autonomy is not amnesia. The tenant carries a record of its origin, and from that record everything else can be derived.

Status: Phase 1 shipped 2026-05-16 (engine commit e6698999). Phase 2 (verbs + three-way merge) queued. Date: 2026-05-15 (proposed); 2026-05-16 (Phase 1 landed) Author: the owner + Claude (afternoon, after the rmc implant-events conflict) Family: anchors Sense 20 The Seam; adjacent to Sense 25 The Pass / Pack-To-Go sketch.


jinflow afs update today does a two-way filesystem diff. When both the pack and the tenant have edited the same file since the last sync, it raises a conflict and refuses to choose. The only escape hatch is --force, which is all-or-nothing — every conflict in the run resolves the same way.

That model has three failures.

It cannot classify. human_dbt/macros/source_system_columns.sql is pack-managed. scripts/pipeline.yml is tenant-managed by design (CLAUDE.md says so). Today the engine treats both identically and asks the human to disambiguate every time. Manual classification — declaring per-file ownership in a pack_scope.yml — was considered and rejected: the pack has hundreds of artifacts, packs are still moving, and the cost is permanent.

It cannot reason. When two edits collide, the engine shows two blobs of bytes. It does not show why either edit was made. Both sides have a reason — the pack edited the file because some pattern generalised across tenants; the tenant edited it because of some local truth. Without those reasons the human has to reverse-engineer intent from a diff.

It has no memory of origin. The current baseline is “the hash at the last successful update”. That is brittle: a --force overwrite re-baselines, and the trace back to the original pack-init moment is gone. The tenant has no immutable birth record.

The Seam (Sense 20) named the boundary. This Sense names the relationship that the boundary protects.


The Lineage is the durable, anchored relationship between a tenant and its origin pack.

It is not a command. It is not a configuration file in the pack. It is a small piece of irrevocable history that the tenant carries, from which every later judgement about what is mine, what was inherited, what has drifted can be derived.

Three principles fall out of it.

At jinflow init, the tenant records, once, an immutable manifest:

# afs/.pack-init.yml — written once at init, never modified.
pack_name: numetrix
pack_origin: https://github.com/jazzisnow/jinflow-pack-numetrix
pack_commit: a3b5c7f8...
init_at: 2026-05-15T10:00:00Z
files:
human_dbt/macros/source_system_columns.sql: <sha256>
scripts/enrich_csvs.py: <sha256>
signals/signal_revenue_leakage.yaml: <sha256>
# … every file that came from the pack at birth

This file is read-only after init. It is the tenant’s birth certificate. The engine never rewrites it. New pack-sourced files learned during later updates extend a separate, mutable, additive sibling — they do not mutate the birth manifest.

Once the birth manifest exists, every file in afs/ falls into one of two classes — and the engine reads which from the filesystem alone:

  • Shared = a file whose path appears in the birth manifest. Pack and tenant can both speak about it.
  • Tenant-native = a file in afs/ whose path does not appear in the birth manifest. The pack has nothing to say about it.

No pack_scope.yml. No tagging. No upfront classification work. The tenant’s birth certificate already says which artifacts are pack-derived; everything else is the tenant’s own life.

Both sides are git repositories: the pack repo and the tenant’s AFS repo. Both sides already record reasons in commit messages. The engine does not need a new metadata channel — it needs to surface the existing one.

When a shared file conflicts, update shows the human:

! human_dbt/macros/source_system_columns.sql
pack edits since init (1):
e9c2fc7 fix: bronze_implant_events dispatch — OPALE-only
tenant edits since init (1):
b42f1aa rmc: keep sap_ish branch for legacy implant_events delivery

Now the conflict has content. The human chooses with reasons in hand, not just bytes.


Once the birth certificate and the derived class exist, a small, coherent verb set falls out — and each verb is only possible because the lineage exists.

VerbMeans
jinflow afs origin <file>Is this file shared or tenant-native? If shared: SHA at init, pack drift since init, tenant drift since init, reasons on each leg.
jinflow afs ancestryGlobal drift report. For each shared file: untouched on both sides / pack-evolved / tenant-evolved / both-evolved. A health snapshot of the tenant’s relationship to its birth pack.
jinflow afs adopt <file>Tenant-native → shared. The pack later added a file at this path; you choose to align with the pack. Adds the file’s pack-current SHA into a sibling .pack-adoptions.yml.
jinflow afs orphan <file>Shared → tenant-native. “I forked this file for keeps; stop offering pack updates on it.” Future update runs skip it silently. Recorded in .pack-orphans.yml.

update and promote (Sense 20) continue to exist; they get easier, because every decision they make can now point at the lineage record for the file in question.


  • The Seam (Sense 20). The Seam is the directional boundary — the rules and named acts of pack→tenant and tenant→pack. The Lineage is the underlying topology that makes Seam decisions legible. They compose: Seam acts, Lineage remembers.
  • pack_scope.yml (Sense 25 sketch). Pack-To-Go declares what is in a pack. The Lineage records what crossed into a tenant at birth. Different artifacts, different audiences.
  • Per-file --keep-pack / --keep-tenant flags. Those are still fine ergonomics on top of Lineage; the Lineage just makes them make sense (you keep pack on a shared file with pack drift; you orphan permanently when you don’t want the relationship at all).

Without a birth certificate:

  • Three-way merge is impossible. Today’s .pack-sync.json records “the hash at the last sync.” That is the wrong baseline — it moves every time. The lineage baseline is the init state, which never moves.
  • Adoption and orphaning have nowhere to anchor. “Make this tenant-only forever” needs to know what was shared. Without a birth manifest, that information is gone after the first --force.
  • Drift cannot be measured. “How far has rmc drifted from numetrix@v0.45?” is unanswerable today. The lineage makes it a one-command report.

A retrofit path exists for tenants that don’t have a .pack-init.yml today:

  1. jinflow afs lineage init — for an existing tenant, compute the birth certificate retroactively. Resolve the pack’s commit at the tenant’s first AFS commit (heuristic: git log --reverse afs/ | head gives the import commit; pair with pack’s git history). Record all afs/ files present at that import.
  2. Where retroactive lineage cannot be computed cleanly, fall back to: record the current state as a synthetic birth certificate, with a flag synthetic: true. This is honest about the gap.
  3. New tenants get a real birth certificate from jinflow init directly.

It is a thread back in time, not a tag in the present. It implies family — descent, inheritance, occasional estrangement (orphan), and the possibility of reconciliation. It carries no judgement: a tenant that has drifted far from its pack is not broken, just its own self now. The relationship is the artifact, not the alignment.


Every tenant AFS carries a lineage/origin.yaml record that pins the pack it was born from — pack name, pack commit SHA, init date, source-system. jin afs lineage init stamps it at tenant birth; jin afs lineage ancestry walks it back to render the genealogy. Future verbs (rebase, three-way merge for pack upgrades) compose against the same record.

  • CLI: jin afs lineage init (stamp the origin record at tenant init) · jin afs lineage ancestry (walk the lineage chain)
  • Files: afs/lineage/origin.yaml per tenant · afs/lineage/upgrades.yaml (planned for Phase 2)

Status: Phase 1 shipped (origin pinning + ancestry walk, engine commit e6698999, May 2026). Phase 2 (rebase verb, three-way merge for pack upgrades) is queued.


  1. What happens at a pack rebase? If the pack rewrites history (rare, but possible), the tenant’s recorded pack_commit becomes unreachable. Do we record pack_origin plus a tarball hash as a backup anchor?
  2. Multiple lineages. Can a tenant be the child of two packs (e.g. hrcentral fork that also pulls some numetrix infrastructure)? The birth manifest is per-pack; the format already permits multiple manifests in afs/.lineage/.
  3. The orphans list as a public statement. When a tenant orphans a shared file, the pack maintainer might want to know — it could signal a generalisation opportunity. Should ancestry produce a per-pack rollup that bubbles up to the pack repo? (This crosses into Sense 20 promote territory.)
  4. Retroactive lineage for existing tenants (numetrix.rmc, inspire, vai; hrcentral.vai). Worth doing? When?

Numerical neighbors:Sense 35: The Drift — Server-to-Browser Ambient System Events · Sense 37: The Snapshot — A Witness Made Citable

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