call: as the composition keystone — the cohesive vision, and the sins it must not inherit
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 9
- Forks
- 0
- Avg merge
- 3h 3m
- Merged PRs (30d)
- 509
Description
PR #168 lands the mechanism. This issue records the whole shape call: is growing into — reuse across teams, remote sources, identity, observability, LSP — so each future slice lands against a stated vision instead of accreting. A focused research pass on how composition went wrong in GHA reusable workflows, Terraform modules, CircleCI orbs, and GitLab includes is running and will be reconciled into this issue; what follows is the position to check that research against.
The load-bearing decision: composition is vendoring, not linking
call: resolves at compile time and embeds the callee whole, digest-recorded. This is the single choice most of GHA's composition sins hang on, so it's worth stating why it's right rather than treating it as an implementation detail:
- A runtime-resolved reference (
uses: org/repo@v1) means the thing that runs is decided later than the review. Tag-moving attacks are just the malicious face of it; the benign face — a run meaning something different on Tuesday than it meant in Monday's review — is equally disqualifying for durable workflows, and invariant 10 already forbids it independently. - Compile-time embedding means the calling author had the callee's exact bytes in front of them when they shipped. The trust boundary is at authoring, where a human (or agent) can actually review, not at run time, where nobody is looking.
source_digestmakes the provenance checkable after the fact.
Consequence for remote workflows: remote sources are a compiler concern, never an engine concern. flow (CLI/LSP) may learn to fetch call: <remote-source> at compile time — with a digest pin required, no floating references, and Terraform's lockfile lessons applied — but the engine never fetches anything: a submitted spec is hermetic, forever. Local-only is the correct requirement for the engine permanently; remote is sugar at the edge. A registry, if ever, is a distribution mechanism for the same digest-pinned bytes, not a new trust model.
Identity at the boundary — the GHA-shaped question, answered precisely
The instinct that this rhymes with GHA's identity problems is right, and the answer needs to be principled rather than incidental. The shape: a callee runs inside the caller's run, under the run's tenant and identity. A callee that names ${secret('env:X')} resolves it with the caller's authority — structurally similar to GHA's secrets: inherit, which is on the sin list. Why it is not the same sin, and where the real line is:
- What's already refused: the caller cannot push a secret across
with:(compile-time refusal, positioned diagnostic). So authority never flows in silently from the caller's side. - What's deliberately allowed: a callee's own spelled-out secret references resolve under the run's authority — because the callee is vendored code the author reviewed, exactly as an imported Go package that reads a credential is the importer's responsibility.
secrets: inherit's sin was runtime-resolved third-party code receiving ambient secrets; vendored, digest-pinned, reviewed bytes receiving explicitly-spelled ones is the trust model working as designed. - What defense in depth adds anyway: the existing per-tenant secret policy rules (auth secret rules, CEL, fail-closed) already bound what any run — composed or not — may resolve; that is the operator's independent control, and it holds regardless of composition depth. If real multi-team usage shows a need for finer grain (a caller constraining a callee harder than the tenant policy does), the candidate is an
allow_secrets:narrowing on the call step — recorded here per the #166 discipline: named, not scheduled, no new surface before a demonstrated need. - Never: an ambient token, an implicit permissions inheritance, or any authority the file doesn't spell.
Observability: the call tree is never flattened
GHA flattens reusable-workflow logs into the caller and operators hate it; Terraform's module paths get addressing right. The rule here: every surface that names a step names the path — progress, pending activities, spans, flow get, error text (workflow "provision": step "deploy": ... already does this). A call step's span nests the callee's spans; flow watch shows the nested structure because the schema carries it, not because the TUI parses names. As call trees deepen, path addressing (caller.callee.step) becomes the one spelling everywhere — logs, filters, diagnostics alike.
LSP and diagnostics: the signature is the ergonomics
The callee's inputs:/outputs: are the contract, so the editor experience should feel like calling a typed function: hover on a call step shows the callee's declared signature and docs; a with: argument of the wrong type is a diagnostic positioned at the argument in the caller's file; a diagnostic inside the callee (when editing it) never phantom-appears in the caller. Terraform's LSP art shows the failure modes to refuse: stale module caches producing phantom errors, and cross-file resolution doing I/O on the keystroke path. The existing rule covers the second — the LSP is path-aware only when a document URI names a real file, and remote fetch (when it exists) is an explicit compile action, never keystroke-triggered.
The footgun ledger (each already held, each kept deliberately)
Depth bounded at execution not just parse (hand-built specs); breadth bounded by CheckSpecSize post-expansion; isolation as the default with zero ambient scope; present-empty outputs so "ran with nothing to say" ≠ "skipped"; suspension transparent so a callee's durable wait is not a surprise; a tolerated callee failure spelled identically on both drivers. Any future slice that would weaken one of these argues against a written reason.
Sequencing: reconcile the research report here; then the reuse story's next real slice is the #165 slice-2 example (a library workflow with two callers) which will pressure-test signature ergonomics before any remote-source work is considered.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reconcile the research on GHA reusable workflows, Terraform modules, CircleCI orbs, and GitLab includes into issue #172. Review PR #168, the CheckSpecSize behavior, and the flow get/flow watch surfaces, then use the planned #165 slice-2 library workflow with two callers to pressure-test the signature ergonomics. Done means the research is reconciled and the next slice has explicit conclusions.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- cli, devtools
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100