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.mdin 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.
What pulled us in
Section titled “What pulled us in”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 Concept
Section titled “The Concept”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.
1. The birth certificate is the baseline
Section titled “1. The birth certificate is the baseline”At jinflow init, the tenant records, once, an immutable manifest:
# afs/.pack-init.yml — written once at init, never modified.pack_name: numetrixpack_origin: https://github.com/jazzisnow/jinflow-pack-numetrixpack_commit: a3b5c7f8...init_at: 2026-05-15T10:00:00Zfiles: 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 birthThis 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.
2. Classes are derived, not declared
Section titled “2. Classes are derived, not declared”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.
3. Reasons travel via git, not metadata
Section titled “3. Reasons travel via git, not metadata”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 deliveryNow the conflict has content. The human chooses with reasons in hand, not just bytes.
The Verbs
Section titled “The Verbs”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.
| Verb | Means |
|---|---|
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 ancestry | Global 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.
What This Does Not Replace
Section titled “What This Does Not Replace”- 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-tenantflags. 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).
What Falls Apart Without It
Section titled “What Falls Apart Without It”Without a birth certificate:
- Three-way merge is impossible. Today’s
.pack-sync.jsonrecords “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.
Implementation Sketch
Section titled “Implementation Sketch”A retrofit path exists for tenants that don’t have a .pack-init.yml
today:
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/ | headgives the import commit; pair with pack’s git history). Record allafs/files present at that import.- 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. - New tenants get a real birth certificate from
jinflow initdirectly.
Why “Lineage”
Section titled “Why “Lineage””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.
How this manifests in jinflow
Section titled “How this manifests in jinflow”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.yamlper 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.
Open Questions
Section titled “Open Questions”- What happens at a pack rebase? If the pack rewrites history
(rare, but possible), the tenant’s recorded
pack_commitbecomes unreachable. Do we recordpack_originplus a tarball hash as a backup anchor? - 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/. - 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
ancestryproduce a per-pack rollup that bubbles up to the pack repo? (This crosses into Sense 20 promote territory.) - 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 →