Skip to main content

throughline-compose

Compose one requirements graph from many reusable throughline sources — a house style guide, a platform standard, a regulatory baseline — alongside the requirements you write yourself, and work the combined graph as one.

This repository is itself a throughline project: its own design is captured as a grounded IDD spine of 7 user requirements and 20 system requirements under vision/, goals/, user-requirements/, system-requirements/, and non-goals/, and published to docs/spec.md. The graph is gated by tl-compose check --strict and the document by tl-compose docs --check; these two counts are rendered from the live spine by the tl:count directive, so they cannot drift.

Status: alpha. The composition engine is built. tl-compose check composes the declared [[sources]] into a union graph and validates it, and tl-compose docs renders the published document over that same union, resolving borrowed (namespace:UID) targets (SR-0007). Each source resolves from either a local path or a pinned git url + ref into a per-user cache (SR-0006). Still pending: the tl-compose source add/update/pin subcommands for managing source declarations from the CLI (today you edit the [[sources]] tables by hand).

The idea

A team should be able to adopt standard requirement sets by reference, not by copy-paste, and receive upstream revisions without ever forking. Two identity rules make that safe:

  • Imported items keep their source-native UID (SR-0002). Composition never renumbers or copies. Canonical identity is the pair (source-namespace, UID), so the same SR-0001 may legitimately exist in two sources without collision — the immutable-UID rule is never violated.
  • The composer controls the namespaces (SR-0001). The consumer's own throughline.toml binds each source to a short name it chooses (import X as Y). A qualified reference like gds:SR-0001 denotes a borrowed item; a bare UID is always local. Renaming a namespace is a local-only change with a bounded blast radius.

Declaring sources

A consumer names the sources it composes in an array of [[sources]] tables in its own throughline.toml. Each entry binds a namespace to one source, located either by a pinned git url or by a local path (SR-0006):

# Adopt a published standard by reference, pinned to an edition.
[[sources]]
namespace = "asvs"
url = "https://github.com/timebacksolutions/throughline-asvs"
ref = "v4.0.3"                       # a git tag (normal form), branch, or commit SHA

# Develop a source and its consumer side by side.
[[sources]]
namespace = "house-style"
path = "../house-style"              # a directory relative to this project
  • url + ref is the durable, shareable form. The ref pins the exact edition — normally a release tag, but any git ref (branch or commit SHA) works. tl-compose fetches the source from its origin on first use into a per-user cache that lives outside any project tree ($TL_COMPOSE_CACHE, else $XDG_CACHE_HOME, else ~/.cache/throughline-compose/sources/), keyed by (url, ref). Resolution is idempotent and offline thereafter: a source already cached at the pinned ref is reused, never refetched. Nothing is vendored into your repo, so your own item scan never ingests a borrowed graph.
  • path is for local development. A directory, relative to the consumer, for working on a source alongside the project that consumes it.
  • The two are mutually exclusive, and a url must carry a ref. Declaring both path and url, or a url with no ref, is rejected at check time — a dependency can never silently track a moving default. (A ref alongside a path is likewise rejected: a ref only pins a url.)

Re-exporting a transitive source

Composition is one level deep and flat: if a source you adopt itself cites another namespace — say house-style internally references asvs:SR-0001 — that asvs must be a namespace your consumer also declares, or the compose fails on an undeclared namespace. A re-export lets you pull that transitive source forward through the intermediate one without restating its url/ref (SR-0014):

[[sources]]
namespace = "house-style"
path = "../house-style"
reexport = ["asvs"]                   # pull house-style's `asvs` forward, same name

The re-exported source inherits the intermediate source's pin — you do not (and cannot) restate its edition here; it is whatever house-style itself declared. An array re-exports each namespace under its own name; a table binds a consumer-chosen alias instead (UR-0005):

reexport = { asvs = "owasp" }         # the same source, bound in your union as `owasp`

Every reference the intermediate source wrote against its own label (asvs:SR-0001) resolves to the aliased union namespace. Re-export is opt-in and per-namespace: nothing is hoisted automatically, so adopting a source never silently expands your union.

A namespace bound to two different editions fails fast — never a silent merge or an arbitrary winner (SR-0015). If you declare asvs directly at one ref and also re-export a source's asvs at a different edition, the compose stops and names both the why (the same namespace reaches your union at two editions) and the fix (pin asvs explicitly to one edition, or alias the two apart so they coexist).

Moving to a new upstream edition is a one-line change to the ref; the borrowed graph is never edited. See rhodium-org/idd-example for a complete worked consumer that adopts throughline-asvs this way.

One tool, one set of guarantees

In a composed project you drive everything through tl-compose, never tl directly (UR-0002). The architecture keeps that honest:

  • tl-compose is a strict superset of tl (SR-0003). Local-graph commands are forwarded to the throughline library unchanged; the union-aware check and docs are overridden to compose, validate, and render the combined graph. (The source subcommands for editing declarations are the remaining superset surface — see the status note above.) The core command set is obtained programmatically, so the two surfaces cannot drift apart.
  • Composition reuses throughline unchanged (SR-0004). It merges the sources into one in-memory Project and runs throughline's existing validate, Index, and fingerprint over that union — no second validation engine. A composed graph is exactly as sound as a native one.
  • Bare tl check fails fast on unresolved cross-source refs (SR-0005). If you run core tl in a composed repo by habit, a namespace-qualified reference it cannot resolve makes it stop and point you at tl-compose — never a false clean result. Free external references (a URL, a linked standard) stay opaque, as intended.

Composition deliberately lives here, not in the throughline core (NG-0001) — the core stays a single-purpose, offline tool over one graph, consumed here as a library.

Working here

pip install .            # pulls throughline transitively; installs tl and tl-compose
tl-compose context       # agent brief, generated from throughline.toml
tl-compose check --strict # gate the whole graph
tl-compose docs --check  # gate published-document freshness
tl-compose docs          # regenerate docs/spec.md from the graph

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

throughline_compose-0.3.1.tar.gz (44.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

throughline_compose-0.3.1-py3-none-any.whl (35.0 kB view details)

Uploaded Python 3

File details

Details for the file throughline_compose-0.3.1.tar.gz.

File metadata

  • Download URL: throughline_compose-0.3.1.tar.gz
  • Upload date:
  • Size: 44.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for throughline_compose-0.3.1.tar.gz
Algorithm Hash digest
SHA256 55cdf76821a46495423d243a8db6de9ccc5d7f3d74421e93a5de497e4751df55
MD5 c99f69b7fae8fedbda01dc8ceeedafdd
BLAKE2b-256 ceac851cd04a3dd3879e173b7526c13bad839346da94fa40a4863bc2c41a727a

See more details on using hashes here.

Provenance

The following attestation bundles were made for throughline_compose-0.3.1.tar.gz:

Publisher: release.yml on timebacksolutions/throughline-compose

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file throughline_compose-0.3.1-py3-none-any.whl.

File metadata

File hashes

Hashes for throughline_compose-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 905f4ff0845e4a550f90383fabdf80de37a2192e35fac5151b2d34f0bdcc0994
MD5 f263f133476db1b47553bbdc9b8910c2
BLAKE2b-256 a6df6c9bc24e26ffbec2d80f515bcde89fb4444b0e10e4ae0d4764dff8090ce0

See more details on using hashes here.

Provenance

The following attestation bundles were made for throughline_compose-0.3.1-py3-none-any.whl:

Publisher: release.yml on timebacksolutions/throughline-compose

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.16.1

2 files

0.16.0

2 files

0.15.0

2 files

0.14.0

2 files

0.13.0

2 files

0.12.1

2 files

0.12.0

2 files

0.11.0

2 files

0.10.0

2 files

0.9.0

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.5

2 files

0.4.4

2 files

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

0.3.2

2 files

This release

0.3.1 This release

2 files

0.3.0

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page