Repository navigation
docs: comprehensive 2.0 documentation — new guides, accuracy and toolkit parity - #128
Conversation
There was a problem hiding this comment.
Code Review
Current Commit: 0bf49c1
Summary
This is a high-quality documentation PR. The three new guides are accurate, well-structured and teach the right mental models. The backfill of 03-services.md, the error table, the Octokit-cast seam write-up and the Action.run type-erasure note are all genuinely useful additions that close real comprehension gaps.
There is one structural accuracy issue worth resolving before merge: the "Upgrading to 2.0" section in 10-architecture.md documents breaking changes (fromApp Scope requirement, fromEnv() function call) that live in ws2-resilient-github-client and ws3-effect-quality-observability — branches not yet merged to main. This creates a direct internal contradiction: the GitHubClientLive constructor table on line 349 correctly describes fromEnv as a property (consistent with the source), while the migration note two pages down tells consumers to call fromEnv(). See inline comments for details.
Checks
| Check | Result |
|---|---|
| PR Title Validation | ✅ success |
| Conventional Commits | ✅ success |
| Code Quality | ✅ success |
| Markdown | ✅ success |
| Tests | ✅ success |
What's good
05-slsa-attestations.md — The layer-stack walkthrough is the right way to introduce four cooperating services. Documenting that listForSubject's predicateType filter is client-side (not a server query parameter, 404 = empty array) is exactly the kind of non-obvious contract consumers need to see written down. Path A vs. Path B for sbom is clear without being verbose.
06-publishing.md — The two-digest table at the top (digest = npm sha512/base64 for registry comparison, sha256Hex = lowercase hex for attestations) is one of the best single pieces of new content in this PR. That distinction is a silent footgun if it's not explained here, and the explanation is concise and precise.
07-step-logging.md — defaultSummary gets its own section rather than a footnote, which is the right call — it's a common pattern. The collapse unbounded-concurrency note ("meant for the small fan-out… typically two to four") sets appropriate expectations without over-constraining the API.
10-architecture.md new sections — The Octokit-cast seam write-up is unusually honest documentation: it names the runtime guard, explains why the compile-time guarantee was deliberately traded away, and says what the cost is. The Action.run type-erasure section does the same for the any cast. Both are the kind of thing that would otherwise only be discoverable by reading the source.
09-error-handling.md — The eight-entry error table with reason / operation discriminators and the retryable flag on GitHubArtifactMetadataError surfaces exactly what a caller needs for precise catchTag recovery. The SlsaError "claims" vs "env" distinction (config problem vs transient) is a nice illustration of why discriminators matter.
Issue: "Upgrading to 2.0" describes unmerged WS2/WS3 changes
The migration section has two items that don't match the current codebase (see inline comments):
-
fromEnvfunction call — line 271 says "is now a function — callfromEnv()", butsrc/layers/GitHubClientLive.tshasconst fromEnv: Layer.Layer<GitHubClient, GitHubClientError>(a property). The constructor table in the same file (line 349) says "property, no arguments" — accurate per the source. -
fromAppScope requirement — line 256 saysfromApprequiresScope; the source returnsLayer.Layer<GitHubClient, GitHubAppError>with no Scope.
The Redacted signatures and HttpClient requirement items are in the same situation: commit 58c2456 (ws3-effect-quality-observability) shows those as planned changes, but they're not in main yet.
Suggested path: If WS2/WS3 are gated to land before this PR, the docs are fine as written and just need the PR ordering enforced. If not, the safest fix is to scope the note: "These changes ship in the WS2/WS3 PRs. If you're on 1.x today, fromEnv is still a property and fromApp does not require Scope." That keeps the forward guidance while preventing consumers from acting on instructions that don't apply yet.
Minor observations (no action needed)
Step.failureis mentioned once at the end of thegroupStepsection and listed in the exports at the top of07-step-logging.md, but has no dedicated section. Given it's the non-fatal path (lets the surrounding loop continue), a one-paragraph treatment could help — but given it's intentionally secondary, the current placement is defensible.- The
JSON.parse(json) as Record<string, unknown>in the Path B SBOM example (05-slsa-attestations.mdline 133) is the right pattern (serialization normalises the CycloneDX structure before attesting), but a one-liner comment next to it explaining why you round-trip through string form would help readers who'd otherwise wonder why you serialize only to immediately parse back.
|
|
||
| ### Scopes and the fromApp / fromEnv constructors | ||
|
|
||
| `GitHubClientLive.fromApp` now requires a `Scope` in its requirements channel — it acquires and releases the installation token within a scope. Wrap a bare `Effect.provide` in `Effect.scoped`: |
There was a problem hiding this comment.
Same pre-merge ordering concern as the fromEnv note below: the current source at src/layers/GitHubClientLive.ts shows fromApp returning Layer.Layer<GitHubClient, GitHubAppError> with no Scope in the requirements channel. The Scope requirement is a WS2/WS3 change not yet in main.
Minor additional nit: the constructor table on line 351 doesn't reflect the Scope requirement either, so when WS2 lands the table will also need updating. It might be worth a TODO comment in the table to keep them in sync.
…kit parity Adds four new guides (best practices, toolkit parity, logging and error handling, resilient GitHub API calls), documents the full 2.0 surface (Glob, IoUtil, Artifact, ActionInput, paginateStream, typed event payload, PathUtils, notice), corrects stale API descriptions (fromEnv is now a function, fromToken is Redacted-only, fromApp is a scoped layer), verifies the Upgrading to 2.0 migration note, and renumbers docs to a contiguous 01-16 reading order. Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
0bf49c1 to
4dae7bb
Compare
|
|
||
| ### Scopes and the fromApp / fromEnv constructors | ||
|
|
||
| `GitHubClientLive.fromApp` is now a scoped layer: it acquires the installation token when built and revokes it on scope close, so building the layer needs a `Scope`. Wrap a bare `Effect.provide` in `Effect.scoped`. `fromApp` also requires `HttpClient.HttpClient` and takes `privateKey` as a `Redacted<string>`: |
There was a problem hiding this comment.
The phrase "building the layer needs a Scope" is inaccurate based on the current type signature.
fromApp returns Layer.Layer<GitHubClient, GitHubAppError, HttpClient.HttpClient> — Scope is not in the requirements channel. Layer.scoped (which fromApp uses internally) produces a type without Scope in requirements; the layer itself manages the scope lifecycle. Callers do not need to supply a Scope via the requirements channel.
Where Effect.scoped matters in practice:
Layer.memoize—Layer.memoize(fromApp(...))returns aScope-requiring effect, soEffect.scopedIS needed there (as the JSDoc example shows).- Plain
Effect.provide—program.pipe(Effect.provide(fromApp(...)))already closes the scope when the provided effect completes;Effect.scopedis optional here (the token is still revoked at that boundary).
Suggest revising to something like: "fromApp is a scoped layer that manages its own scope internally — the minted token is revoked when the layer's scope closes. A plain Effect.provide handles this automatically; you only need an explicit Effect.scoped wrapper when using Layer.memoize to share one token across multiple sub-programs."
(This was flagged in the previous review on the old docs/10-architecture.md; the same inaccuracy carried over into the rewritten section.)
| const recent = yield* client.paginateStream<{ number: number; closed_at: string | null }>( | ||
| "listIssues", | ||
| (octokit, page, perPage) => | ||
| octokit.issues.listForRepo({ owner: "org", repo: "repo", state: "all", page, per_page: perPage }), |
There was a problem hiding this comment.
This example calls octokit.issues.listForRepo(...) without a type cast, but the paginateStream callback receives octokit: unknown (see GitHubClient service definition). This would be a TypeScript compile error in real consumer code.
The docs/08-resilient-github-api.md guide (also added in this PR) handles this correctly with an explicit cast. For consistency, the paginateStream example here should either:
// Option A — inline structural cast (same style as the guide):
(octokit as { issues: { listForRepo: (p: unknown) => Promise<{ data: Array<{ number: number; closed_at: string | null }> }> } }).issues.listForRepo(...)
// Option B — cast to Octokit directly (@octokit/rest is a direct dep):
import { Octokit } from "@octokit/rest"
(octokit as Octokit).rest.issues.listForRepo(...)Option B is cleaner for a docs example since @octokit/rest is a direct dependency. Either way, the uncast octokit.issues.listForRepo(...) would fail tsc --strict.
Code ReviewCurrent Commit: 4dae7bb SummaryThis is a comprehensive docs-only PR (22 files, +1845/−161) adding four new guides, backfilling service documentation, and renumbering the docs directory to a clean 01–16 reading order. The one source-code change is a comment-reference update in What's well done
Issues found1.
|
| Thread | Status |
|---|---|
fromEnv() as function vs. property (comment 3279658228, docs/14-architecture.md) |
✅ Resolved — source and constructor table now correctly describe fromEnv as a function. Thread replied to and marked resolved. |
fromApp Scope requirement (comment 3279658892, old docs/10-architecture.md) |
docs/14-architecture.md in this commit. The concern partially remains as issue #1 above. |
Merge note
The PR description calls out a filename collision with #125: docs/09-error-handling.md (this PR) vs docs/09-filesystem-io.md (WS4/PR #125). Whichever merges second needs a rename — no link rot expected since the architecture substitution map references the filesystem guide generically.
fromApp is a scoped layer, but Scope is not in its requirements channel: a plain Effect.provide revokes the installation token when the program finishes, and Effect.scoped is only needed when sharing one token via Layer.memoize. Correct that wording in the services and architecture guides and the fromApp JSDoc, and cast the unknown octokit handle in the rest, paginate, and paginateStream examples so they compile under tsc strict mode, matching the resilient GitHub API guide. Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
WS6 — Documentation
Sixth workstream of the reviewer-improvements 2.0.0 effort. Comprehensive documentation pass covering the full 2.0 release surface. Bump: minor. Single clean commit; base
main.Accuracy against the final 2.0 API
Corrects stale descriptions throughout
docs/:GitHubClientLive.fromEnv()is now a function (not a bare layer).fromTokentakesRedacted<string>only (WS3 tightened the signature).fromAppis a scoped layer — revokes its token on scope close, requiresHttpClient.HttpClient.@actions/*substitution map re-verified and expanded.New services documented
Glob(glob +@actions/glob-compatiblehashFiles),IoUtil(which/whichOrFail/findInPath, notingwhich/findInPatherror channel isnever),Artifact(upload/list/get/download/delete, with the "must run inside a JS action, not arun:step" constraint),ActionInput(YAML 1.2 Core Schemaboolean,multiline), the typed event payload (ActionEnvironment.payload/repo/issue/isDebug+WebhookPayload),PathUtils,ActionLogger.notice+WorkflowCommandnotice/stop-commands/echo,GithubMarkdown.image/quote,paginateStream.Four new guides
05-best-practices.md— "Building a robust action" (layer composition, dry-run, permission checks, idempotency, secrets, error accumulation).06-toolkit-parity.md— "Coming from@actions/*" (narrative walkthrough with worked examples per package; cross-references the substitution map in architecture).07-logging-and-error-handling.md—ActionLogger/Step/notice/annotations/formatCause/setSecret.08-resilient-github-api.md— Retry, rate-limit awareness,Retry-After/x-ratelimit-reset,ResilienceOptions,paginateStream.Structure
docs/renumbered to a contiguous 01–16 reading order (4 new guides at 05–08; existing guides/reference tail at 09–16). Full cross-link sweep and humanizer pass applied.Verification
pnpm run lint:md— 0 errors (40 files) · cross-link/anchor integrity confirmed · all APIs spot-checked againstsrc/.Signed-off-by: C. Spencer Beggs spencer@savvyweb.systems