Skip to content

docs(consumer): ship an operable caller workflow and onboarding guide - #83

Merged
nnennandukwe merged 2 commits into
mainfrom
docs/consumer-onboarding-and-caller-template
Jul 31, 2026
Merged

nnennandukwe merged 2 commits into
mainfrom
docs/consumer-onboarding-and-caller-template

Conversation

@nnennandukwe

Copy link
Copy Markdown
Owner

Summary

A supervised consumer pilot could not obtain signed evidence, and every blocker turned out to be an undocumented prerequisite rather than a code defect. This ships the missing template and the ordered prerequisites.

ThreadLoop documented only a caller workflow with hardcoded inputs. No operator can use that shape — session id, proof-plan digest, and pull-request number are known only at dispatch time. Both pilot attempts therefore wrote their own caller, and both carried the same defect.

Related issue

Refs #79, #80

Does not close them: #79 also asks that the review path be exercised end to end against a live sensor, which is still blocked, and #80 carries the init --json versioning decision (resolved here as documentation, see below).

Changes

  • examples/threadloop-caller-workflow.yml — complete dispatch-driven caller for both sensors, with pull_request_number: ${{ fromJSON(inputs.pull_request_number) }}.
  • docs/consumer-onboarding.md — ordered prerequisites: default-branch requirement, get-the-caller-right-first, self-provisioning gates, CLI location, projection authority, and the init rationale.
  • docs/attestations/receipt-v1.md, docs/attestations/review-v1.md — point at the operable template rather than leaving the literal snippet as the only guidance.
  • tests/unit/caller-template.test.ts — 5 tests.
  • README.md — docs index entry.

Impact

  • CLI commands, flags, help text, or exit behavior
  • Machine-readable JSON or protocol output
  • Persisted state, schema, or migration behavior
  • Generated Markdown or review artifacts
  • Git integration, daemon, or reconciliation behavior
  • Installation, packaging, or supported runtimes
  • Documentation only

No src/ change. The new test covers an example file and the existing sensor workflows.

Validation

Check Result Notes
npm run check Pass 313 tests, 30 files, exit 0.
Mutation test Pass Removing the fromJSON cast fails "casts the numeric review input"; dropping a required input fails "passes exactly the inputs each sensor declares". Both assertions are load-bearing.
Pre-commit hooks Pass prettier, eslint, markdownlint, typecheck, dead-code, community check.

Why the test cross-checks the sensors

tests/unit/caller-template.test.ts reads the sensors' own workflow_call.inputs and asserts the template passes exactly the required ones. That guards the specific failure mode that cost the pilot the most time: an input mismatch fails workflow_call validation before any job is created, so it surfaces as a job that never appears, with no step and no log to read. This turns a silent, near-undiagnosable CI failure into a local test failure.

The init --json decision

#80 asked whether init should accept --json. Resolved here as documentation rather than code. Adding the flag changes the published protocol contract for init, and protocol: 4 is pinned in six places including the runner skill, which requires it exactly. Bumping to 5 across the skill, tests, docs, and every pinned consumer is disproportionate for a cosmetic gain, especially since the runner is already forbidden from invoking init. If you would rather have the flag and the version bump, say so and I will do it separately.

Risk and recovery

Low risk, documentation only. The one judgement worth checking is whether the guidance in section 3 is the behaviour you want to commit to, since it tells adopters to restructure their verify target. #78 tracks whether ThreadLoop should instead let the proof plan declare setup steps — if that lands, section 3 needs rewriting.

Reviewer guidance

  1. examples/threadloop-caller-workflow.yml — whether the permissions split is right: id-token: write on both sensor calls, pull-requests: read only on review.
  2. docs/consumer-onboarding.md section 2 — the claim that an in-flight session cannot absorb a caller-workflow fix. That follows from HEAD-bound evidence, and it is the most consequential statement in the document.
  3. Whether examples/ is the right home for a YAML template, given it currently holds only Markdown artifact examples.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

docs(consumer): Add dispatchable caller template and ordered onboarding guide

📝 Documentation 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Add dispatchable caller workflow template for gate/review sensors, casting numeric PR input.
• Document ordered consumer onboarding prerequisites, including default-branch requirement and
 init rationale.
• Link attestation docs to the template and add tests that track sensor inputs.
Diagram

graph TD
  readme["README docs"] --> onboarding["Onboarding guide"] --> template["Caller template"]
  attest["Attestation docs"] --> template
  template --> gate["Gate sensor"]
  template --> review["Review sensor"]
  test["Template tests"] --> template
  test --> gate
  test --> review
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Ship a reusable “caller” workflow in ThreadLoop (no copy/paste)
  • ➕ Avoids consumer-side drift; consumers call a single upstream workflow
  • ➕ Upstream can encode casting/permissions correctly once
  • ➖ Still requires consumers to have a dispatch entrypoint on default branch
  • ➖ Less flexible for consumer-specific defaults (gate id/json, naming, permissions)
  • ➖ Adds another public contract to maintain/version
2. Provide a CLI scaffold command (e.g., `threadloop init --add-workflow`)
  • ➕ Automates correct workflow creation and pinning guidance
  • ➕ Can validate repository prerequisites interactively
  • ➖ Introduces product surface area and requires src/ changes
  • ➖ Harder to use for locked-down environments where operators can only edit GitHub UI
3. Generate template docs from sensor workflows (single source of truth)
  • ➕ Eliminates manual drift between docs/template and sensor inputs
  • ➕ Can be enforced in CI without parsing YAML in tests
  • ➖ Adds build tooling complexity
  • ➖ Still needs a human-friendly, copyable artifact for consumers

Recommendation: The current approach (operable template + explicit onboarding prerequisites + unit tests that assert contract alignment) is the best near-term solution because it addresses the real failure mode (workflow_call validation failing before jobs exist) without expanding product surface area. If template maintenance becomes burdensome, consider a reusable upstream caller workflow or a CLI scaffold as a follow-on.

Files changed (6) +286 / -2

Tests (1) +101 / -0
caller-template.test.tsAdd tests to prevent caller template drift from sensor contracts +101/-0

Add tests to prevent caller template drift from sensor contracts

• Adds unit tests that parse the example workflow and sensor workflows to assert required inputs match exactly, the numeric PR input is cast, permissions stay least-privilege, and sensor invocations stay commit-SHA pinned. This guards against silent workflow_call validation failures where jobs never appear.

tests/unit/caller-template.test.ts

Documentation (5) +185 / -2
README.mdAdd consumer onboarding to docs index +1/-0

Add consumer onboarding to docs index

• Adds a README Docs section link pointing readers to the new consumer onboarding guide.

README.md

receipt-v1.mdPoint gate receipt docs to the operable caller template +5/-1

Point gate receipt docs to the operable caller template

• Clarifies that the embedded YAML snippet is contract-only and links to the dispatchable caller workflow template. Adds a pointer to consumer onboarding for operational prerequisites (e.g., self-provisioning gate toolchain).

docs/attestations/receipt-v1.md

review-v1.mdPoint review receipt docs to the operable caller template +5/-1

Point review receipt docs to the operable caller template

• Updates the reusable-workflow guidance to reference the new dispatch-driven caller template and highlights the need to cast the numeric pull request input. Links to consumer onboarding for setup prerequisites.

docs/attestations/review-v1.md

consumer-onboarding.mdAdd ordered consumer onboarding prerequisites and operator rationale +94/-0

Add ordered consumer onboarding prerequisites and operator rationale

• Introduces a step-by-step onboarding guide derived from pilot failures, including default-branch workflow requirements, why caller fixes force new sessions, self-provisioning gate expectations, and CLI location considerations. Documents why 'threadloop init' remains human-facing (no '--json') and how migrations are surfaced as operator handoffs.

docs/consumer-onboarding.md

threadloop-caller-workflow.ymlAdd dispatchable caller workflow template for gate and review sensors +80/-0

Add dispatchable caller workflow template for gate and review sensors

• Adds an operator-dispatchable workflow that calls either the gate or review reusable workflow, pinned by commit SHA. Ensures least-privilege permissions and casts 'pull_request_number' via 'fromJSON' to satisfy the review sensor's numeric input contract.

examples/threadloop-caller-workflow.yml

@qodo-code-review

qodo-code-review Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

Context used
✅ Compliance rules (platform): 15 rules

Action required

1. Unreplaced repo placeholder ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new caller template references reusable workflows under OWNER/threadloop, but the new
onboarding/attestation docs instruct operators only to replace @FULL_COMMIT_SHA. Copying the
template as directed will typically fail because GitHub Actions cannot resolve reusable workflows
from a non-existent OWNER/threadloop repository path.
Code

examples/threadloop-caller-workflow.yml[R61-75]

+    uses: OWNER/threadloop/.github/workflows/threadloop-gate-sensor.yml@FULL_COMMIT_SHA
+    with:
+      session_id: ${{ inputs.session_id }}
+      plan_sha256: ${{ inputs.plan_sha256 }}
+      gate_id: ${{ inputs.gate_id }}
+      gate_json: ${{ inputs.gate_json }}
+
+  review:
+    if: ${{ inputs.evidence == 'review' }}
+    permissions:
+      contents: read
+      pull-requests: read
+      id-token: write
+    uses: OWNER/threadloop/.github/workflows/threadloop-review-sensor.yml@FULL_COMMIT_SHA
+    with:
Evidence
The template hardcodes uses: OWNER/threadloop/..., while the onboarding step only mentions
replacing the SHA pins, and the updated attestation docs direct readers to copy this template as the
operable workflow—so following the documented steps leaves an invalid repo reference.

examples/threadloop-caller-workflow.yml[55-80]
docs/consumer-onboarding.md[9-14]
docs/attestations/receipt-v1.md[52-58]
docs/attestations/review-v1.md[45-51]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`examples/threadloop-caller-workflow.yml` uses a placeholder reusable-workflow repository path (`OWNER/threadloop/...@FULL_COMMIT_SHA`). The docs newly added/updated in this PR tell consumers to copy the template and replace only the commit SHA, which leaves the `OWNER/threadloop` placeholder intact and breaks dispatch.

## Issue Context
This PR positions the example as an “operable” caller and now links to it from the attestation docs and onboarding guide, so the copy/paste path must be complete and unambiguous.

## Fix Focus Areas
- examples/threadloop-caller-workflow.yml[61-75]
- docs/consumer-onboarding.md[11-13]
- docs/attestations/receipt-v1.md[54-58]
- docs/attestations/review-v1.md[47-51]

## Suggested fix
Choose one (or both) of:
1) Replace `OWNER/threadloop` in the template with the canonical repository slug used elsewhere in the docs (so users only need to replace the SHA), and keep the SHA placeholder.
2) If the repo slug must remain a placeholder, explicitly instruct users (in onboarding + attestation docs) to replace BOTH the repo slug and the commit SHA, e.g. “replace `OWNER/threadloop` with `<actual-owner>/threadloop` and replace `@FULL_COMMIT_SHA` with a reviewed commit SHA`.”

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


To customize comments, go to the Qodo configuration screen, or learn more in the docs.

@qodo-code-review

Copy link
Copy Markdown

PR approved by Qodo

All merge criteria satisfied — approved by default policy

@qodo-code-review

Copy link
Copy Markdown

Qodo Fixer

No findings are available for this PR yet. Findings appear here once Qodo has reviewed the PR.

@nnennandukwe
nnennandukwe force-pushed the docs/consumer-onboarding-and-caller-template branch from b7a9b37 to 8358b25 Compare July 31, 2026 02:02
Comment thread examples/threadloop-caller-workflow.yml Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 8358b25

A supervised consumer pilot could not obtain signed evidence, and every
blocker was an undocumented prerequisite rather than a code defect.

ThreadLoop documented only a caller workflow with hardcoded inputs. No
operator can use that shape, because session id, proof-plan digest, and pull
request number are known only at dispatch time. So both pilot attempts wrote
their own caller, and both carried the same defect: `workflow_dispatch`
delivers every input as a string, including one declared `type: number`, and
passing that string into the review sensor's numeric input fails
`workflow_call` validation before any job is created. The failure presents as
a job that never appears, with no step and no log.

Add `examples/threadloop-caller-workflow.yml` as a complete dispatch-driven
caller for both sensors, casting the numeric input with `fromJSON`.

Add `docs/consumer-onboarding.md` for the ordered prerequisites the pilot
surfaced:

- the caller workflow must be on the consumer's default branch before the
  first governed session, because GitHub resolves `workflow_dispatch` targets
  from the default branch only, so a consumer cannot produce signed evidence
  for its first governed feature branch;
- the caller must be correct before starting, because it lives in the governed
  repository and editing it moves HEAD, which stales every HEAD-bound receipt
  and recorded pre-PR review, so an in-flight session cannot absorb the fix;
- a declared gate must provision its own toolchain, because the sensor runs
  exactly the declared command with only Node set up while a typical verify
  target assumes CI already provisioned the environment;
- CLI location is environment rather than a fifth wake input; and
- `session next` outranks any planned step order.

Also record that `init` has no `--json` by design: it is an operator action,
and the runner contract forbids a wake from invoking it.

`tests/unit/caller-template.test.ts` cross-checks the template against the
sensors' own declared `workflow_call` inputs, so drift fails locally instead
of as an invisible missing job. Mutation-tested: removing the `fromJSON` cast
and dropping a required input each fail a distinct assertion.

Refs #79, #80
Qodo review, Action required, Bug/Correctness: the template used
`OWNER/threadloop` while the template header and onboarding guide both told
consumers to replace only `@FULL_COMMIT_SHA`. Copying it as directed left the
owner placeholder intact and dispatch failed against a repository that does not
exist. The attestation docs already used the real slug, so the template was the
only thing out of step -- the same "documented but unusable" defect this PR set
out to remove.

Use the real slug in both `uses:` pins, so "replace only the SHA" is true.

The slug names the repository hosting the sensors, not the consumer's own, so
consumers leave it alone. Say that in the template and the onboarding guide,
along with what to change when consuming a fork.

Declare `repository` in package.json, which was missing, and derive the
expected slug from it in the test rather than hardcoding an owner. Hardcoding
would bake this repository's ownership into an assertion, so a fork would ship
a template pointing at upstream with a test enforcing it. Deriving gives the
slug one source of truth: a fork updates `repository` and the test then
requires the template to match.

Verified by mutation: the original `OWNER` placeholder fails, a fork that
updates package.json but forgets the template fails, and a fork that updates
both passes.
@nnennandukwe
nnennandukwe force-pushed the docs/consumer-onboarding-and-caller-template branch from 8358b25 to 686ebdd Compare July 31, 2026 10:29
@nnennandukwe

Copy link
Copy Markdown
Owner Author

Qodo Fix Summary — Round 1

Fixed (1)

1. Unreplaced repo placeholder 🐞 Bug · ≡ Correctness — examples/threadloop-caller-workflow.yml:75

The template used an owner placeholder while the template header and onboarding guide both said to replace only
@FULL_COMMIT_SHA. Copying it as directed left the placeholder intact and dispatch failed against a repository
that does not exist. The attestation docs already used the real slug, so the template was the only thing out of
step — the same "documented but unusable" defect this PR set out to remove.

Applied Qodo's option 1: use the real slug in both uses: pins.

Two additions beyond the finding, from reviewer feedback on the first attempt at this fix:

  • The slug names the repository hosting the sensors, not the consumer's own, so consumers leave it alone. The
    template and onboarding guide now say that, plus what to change when consuming a fork.
  • repository was missing from package.json. It is now declared, and the test derives the expected slug from
    it instead of hardcoding an owner. Hardcoding would bake this repository's ownership into an assertion, so a
    fork would ship a template pointing at upstream with a test enforcing it. Deriving gives the slug one source
    of truth.

Verified by mutation, three ways: the original placeholder fails; a fork that updates package.json but forgets
the template fails; a fork that updates both passes.

Validation

npm run check — pass, 313 tests across 30 files, exit 0.

Rebased onto current main (278c671, Apache-2.0) before pushing, since this branch predated that merge and
both branches touched README.md.


Generated by Qodo PR Resolver skill

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 686ebdd

@nnennandukwe
nnennandukwe merged commit 308f573 into main Jul 31, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant