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 21 system requirements
under idd/vision/, idd/goals/,
idd/user-requirements/,
idd/system-requirements/, and idd/non-goals/, and
published to idd/docs/spec.md. The graph is gated by tl-compose -C idd check --strict and the document by tl-compose -C idd 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 -C idd checkcomposes the declared[[sources]]into a union graph and validates it, andtl-compose -C idd docsrenders the published document over that same union, resolving borrowed (namespace:UID) targets (SR-0007). Each source resolves from either a localpathor a pinned giturl+refinto a per-user cache (SR-0006). Still pending: thetl-compose source add/update/pinsubcommands 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 sameSR-0001may 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.tomlbinds each source to a short name it chooses (import X as Y). A qualified reference likegds:SR-0001denotes 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/rhodium-org/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+refis the durable, shareable form. Therefpins the exact edition — normally a release tag, but any git ref (branch or commit SHA) works.tl-composefetches 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.pathis 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
urlmust carry aref. Declaring bothpathandurl, or aurlwith noref, is rejected at check time — a dependency can never silently track a moving default. (Arefalongside apathis likewise rejected: a ref only pins aurl.)
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-composeis a strict superset oftl(SR-0003). Local-graph commands are forwarded to the throughline library unchanged; the union-awarecheckanddocsare overridden to compose, validate, and render the combined graph. (Thesourcesubcommands 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
Projectand runs throughline's existingvalidate,Index, andfingerprintover that union — no second validation engine. A composed graph is exactly as sound as a native one. - Bare
tl checkfails fast on unresolved cross-source refs (SR-0005). If you run coretlin a composed repo by habit, a namespace-qualified reference it cannot resolve makes it stop and point you attl-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 -C idd context # agent brief, generated from idd/throughline.toml
tl-compose -C idd check --strict # gate the whole graph
tl-compose -C idd docs --check # gate published-document freshness
tl-compose -C idd docs # regenerate idd/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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file throughline_compose-0.4.0.tar.gz.
File metadata
- Download URL: throughline_compose-0.4.0.tar.gz
- Upload date:
- Size: 45.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e340fa508595c85e1abff45fe351fdf8c6cd17b83b9bc7af94896d81a6c23b1a
|
|
| MD5 |
cf37034c55a67ecc0acdc05855787ec2
|
|
| BLAKE2b-256 |
45ca5bb7ad1df324ef2c76025bef8fdf990db548ff62bacb9fdf0821384039e7
|
Provenance
The following attestation bundles were made for throughline_compose-0.4.0.tar.gz:
Publisher:
release.yml on rhodium-org/throughline-compose
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
throughline_compose-0.4.0.tar.gz -
Subject digest:
e340fa508595c85e1abff45fe351fdf8c6cd17b83b9bc7af94896d81a6c23b1a - Sigstore transparency entry: 2256409598
- Sigstore integration time:
-
Permalink:
rhodium-org/throughline-compose@078943343f84de12641e6cc0c4f268d802c8b4d8 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/rhodium-org
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@078943343f84de12641e6cc0c4f268d802c8b4d8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file throughline_compose-0.4.0-py3-none-any.whl.
File metadata
- Download URL: throughline_compose-0.4.0-py3-none-any.whl
- Upload date:
- Size: 35.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
007ff5f2ec2e8c435ff5f1694360e4485b194779175f325d7c1977900b623eda
|
|
| MD5 |
7097af3dae7c4e3d8c89b7df12bd8286
|
|
| BLAKE2b-256 |
6a21806e6d1b940588805794edfb6938dfa43e54bb13ed432692a8a1f6160141
|
Provenance
The following attestation bundles were made for throughline_compose-0.4.0-py3-none-any.whl:
Publisher:
release.yml on rhodium-org/throughline-compose
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
throughline_compose-0.4.0-py3-none-any.whl -
Subject digest:
007ff5f2ec2e8c435ff5f1694360e4485b194779175f325d7c1977900b623eda - Sigstore transparency entry: 2256409610
- Sigstore integration time:
-
Permalink:
rhodium-org/throughline-compose@078943343f84de12641e6cc0c4f268d802c8b4d8 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/rhodium-org
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@078943343f84de12641e6cc0c4f268d802c8b4d8 -
Trigger Event:
release
-
Statement type: