@effected/github-actions 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import { NodeServices } from "@effect/platform-node";
2
2
  import { Cause, Config, ConfigProvider, Context, Duration, Effect, FileSystem, Layer, Logger, Option, Path, Redacted, Result, Schema } from "effect";
3
3
  import { HttpClient } from "effect/unstable/http";
4
4
  import { ChildProcessSpawner } from "effect/unstable/process";
5
+ import { IdentityToken, SlsaProvenance } from "@effected/sbom";
5
6
  import { BotIdentity, GitHubApp, GitHubClient, InstallationToken, PermissionLevel, RetryPolicy } from "@effected/github";
6
7
  //#region src/ActionEnvironment.d.ts
7
8
  declare const ActionEnvironmentError_base: Schema.Class<ActionEnvironmentError, Schema.TaggedStruct<"ActionEnvironmentError", {
@@ -237,6 +238,24 @@ declare class WorkflowCommand {
237
238
  }
238
239
  //#endregion
239
240
  //#region src/ActionLogger.d.ts
241
+ /**
242
+ * Options for {@link ActionLoggerShape.withBuffer}.
243
+ *
244
+ * @public
245
+ */
246
+ interface WithBufferOptions {
247
+ /**
248
+ * What happens to the captured transcript when the step **succeeds**.
249
+ *
250
+ * @remarks
251
+ * `"flush"` — the default — replays it, so a clean run still prints what it
252
+ * did. `"discard"` drops it, which is what keeps a green release log to one
253
+ * line per step: the transcript exists only as the failure report. A failure,
254
+ * a defect or an interruption flushes under either setting — the choice is
255
+ * only about what a success is worth in the log.
256
+ */
257
+ readonly onSuccess?: "flush" | "discard";
258
+ }
240
259
  /**
241
260
  * The {@link ActionLogger} service shape.
242
261
  *
@@ -252,13 +271,18 @@ interface ActionLoggerShape {
252
271
  * Warnings and errors are **not** buffered — they go out as they happen, so a
253
272
  * long step still reports trouble while it is running. Everything at `Info`
254
273
  * and below is held and flushed on every exit path, including a defect or an
255
- * interruption, so a clean run still prints its transcript.
274
+ * interruption, so a clean run still prints its transcript. A step that
275
+ * should be **quiet** when it succeeds passes
276
+ * `{ onSuccess: "discard" }` ({@link WithBufferOptions}); failure still
277
+ * spills the transcript either way.
256
278
  *
257
279
  * Buffering is skipped entirely when the runner has step debugging enabled or
258
280
  * the ambient minimum log level is already `Debug` or lower — someone asking
259
- * for verbose output wants it live.
281
+ * for verbose output wants it live, and that overrides
282
+ * `onSuccess: "discard"` too: asking for debug output means wanting to see
283
+ * what a green step did.
260
284
  */
261
- readonly withBuffer: <A, E, R>(label: string, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
285
+ readonly withBuffer: <A, E, R>(label: string, effect: Effect.Effect<A, E, R>, options?: WithBufferOptions) => Effect.Effect<A, E, R>;
262
286
  /**
263
287
  * Emit a `::notice::` annotation.
264
288
  *
@@ -1176,6 +1200,288 @@ declare class ActionInput {
1176
1200
  static readonly layerDefault: Layer.Layer<never>;
1177
1201
  }
1178
1202
  //#endregion
1203
+ //#region src/OidcTokenIssuer.d.ts
1204
+ declare const OidcTokenError_base: Schema.Class<OidcTokenError, Schema.TaggedStruct<"OidcTokenError", {
1205
+ /**
1206
+ * `unavailable` — the runner did not publish the token-service variables,
1207
+ * which almost always means the workflow is missing `permissions: id-token:
1208
+ * write`. `requestFailed` — the token service could not be reached or refused
1209
+ * the request. `malformedResponse` — it answered with something that is not a
1210
+ * token envelope. `malformedToken` — the token is not a decodable JWT.
1211
+ * `missingClaims` — it decoded, but without the claims a provenance statement
1212
+ * needs.
1213
+ */
1214
+ readonly reason: Schema.Literals<readonly ["unavailable", "requestFailed", "malformedResponse", "malformedToken", "missingClaims"]>;
1215
+ /** What was wrong, in one line. */
1216
+ readonly detail: Schema.optionalKey<Schema.String>;
1217
+ /** The HTTP status, when the token service answered. */
1218
+ readonly status: Schema.optionalKey<Schema.Number>;
1219
+ /** The underlying failure, preserved structurally. */
1220
+ readonly cause: Schema.optionalKey<Schema.Defect>;
1221
+ }>, import("effect/Cause").YieldableError>;
1222
+ /**
1223
+ * Raised when an OIDC token cannot be issued or read.
1224
+ *
1225
+ * @public
1226
+ */
1227
+ declare class OidcTokenError extends OidcTokenError_base {
1228
+ get message(): string;
1229
+ }
1230
+ declare const OidcClaims_base: Schema.Class<OidcClaims, Schema.Struct<{
1231
+ /** The issuer, e.g. `https://token.actions.githubusercontent.com`. */
1232
+ readonly iss: Schema.String;
1233
+ /** The full ref the workflow ran on. */
1234
+ readonly ref: Schema.String;
1235
+ /** The commit. */
1236
+ readonly sha: Schema.String;
1237
+ /** `owner/repo`. */
1238
+ readonly repository: Schema.String;
1239
+ /** The event that triggered the run. */
1240
+ readonly event_name: Schema.String;
1241
+ /** The reusable-workflow ref, which is what a verifier pins against. */
1242
+ readonly job_workflow_ref: Schema.String;
1243
+ /** The calling workflow's ref. */
1244
+ readonly workflow_ref: Schema.String;
1245
+ /** The numeric repository id, as a string. */
1246
+ readonly repository_id: Schema.String;
1247
+ /** The numeric owner id, as a string. */
1248
+ readonly repository_owner_id: Schema.String;
1249
+ /** `github-hosted` or `self-hosted`. */
1250
+ readonly runner_environment: Schema.String;
1251
+ /** The run id, as a string. */
1252
+ readonly run_id: Schema.String;
1253
+ /** The run attempt, as a string. */
1254
+ readonly run_attempt: Schema.String;
1255
+ }>, {}>;
1256
+ /**
1257
+ * The claims a GitHub Actions OIDC token carries about the workflow that ran.
1258
+ *
1259
+ * @remarks
1260
+ * The field names are GitHub's own JWT claim names, kept verbatim rather than
1261
+ * translated to the kit's casing. They are an **external vocabulary** — the
1262
+ * same strings that appear in a SLSA provenance predicate and in every
1263
+ * published example of an Actions OIDC policy — and renaming them would put a
1264
+ * translation layer between a consumer and the specification they are reading.
1265
+ *
1266
+ * @public
1267
+ */
1268
+ declare class OidcClaims extends OidcClaims_base {}
1269
+ /**
1270
+ * The {@link OidcTokenIssuer} service shape.
1271
+ *
1272
+ * @public
1273
+ */
1274
+ interface OidcTokenIssuerShape {
1275
+ /**
1276
+ * Request an ID token, optionally bound to an audience.
1277
+ *
1278
+ * @remarks
1279
+ * Omitting the audience sends no `audience` parameter at all, matching
1280
+ * `@actions/core.getIDToken` — the runner's default audience is not the empty
1281
+ * string.
1282
+ */
1283
+ readonly token: (audience?: string) => Effect.Effect<Redacted.Redacted<string>, OidcTokenError>;
1284
+ /**
1285
+ * The token's claims, decoded.
1286
+ *
1287
+ * @remarks
1288
+ * **The signature is deliberately not verified**, for three reasons that are
1289
+ * recorded so nobody "fixes" it:
1290
+ *
1291
+ * 1. The token comes from the runner's own token-service endpoint over TLS.
1292
+ * The transport is the trust boundary, and the process asking for the
1293
+ * token is the process that received it.
1294
+ * 2. The claims populate a provenance predicate, not a trust decision.
1295
+ * Nothing branches on them for authorization; they are recorded as
1296
+ * attested facts about the workflow that ran.
1297
+ * 3. Verifying would need a JWKS fetch, which turns a decode into a network
1298
+ * call — untestable without a fixture server, and dependent on GitHub's
1299
+ * key endpoint being reachable at attestation time.
1300
+ *
1301
+ * A consumer that needs a *verified* token needs a different operation with a
1302
+ * different name and a different error channel, not an option on this one.
1303
+ *
1304
+ * Claims are on the surface rather than left to the call site because that is
1305
+ * what makes the provenance path reachable in a test: a double built with
1306
+ * {@link OidcTokenIssuer.layerFor} answers with real, decodable claims, where
1307
+ * the synthetic non-JWT its predecessor returned made every consumer's
1308
+ * `decodeJwtClaims` yield nothing and silently skipped the path under test.
1309
+ */
1310
+ readonly claims: (audience?: string) => Effect.Effect<OidcClaims, OidcTokenError>;
1311
+ }
1312
+ declare const OidcTokenIssuer_base: Context.ServiceClass<OidcTokenIssuer, "@effected/github-actions/OidcTokenIssuer", OidcTokenIssuerShape>;
1313
+ /**
1314
+ * The runner's OIDC token service.
1315
+ *
1316
+ * @remarks
1317
+ * Lives here rather than with attestation because it reads
1318
+ * `ACTIONS_ID_TOKEN_REQUEST_TOKEN` and `ACTIONS_ID_TOKEN_REQUEST_URL`, which
1319
+ * exist only when a workflow declares `permissions: id-token: write` — that is
1320
+ * a fact about the runner, not about signing.
1321
+ *
1322
+ * @example
1323
+ * ```ts
1324
+ * import { OidcTokenIssuer } from "@effected/github-actions";
1325
+ * import { Effect } from "effect";
1326
+ *
1327
+ * const program = Effect.gen(function* () {
1328
+ * const issuer = yield* OidcTokenIssuer;
1329
+ * const claims = yield* issuer.claims("sigstore");
1330
+ * return claims.job_workflow_ref;
1331
+ * });
1332
+ * ```
1333
+ *
1334
+ * @public
1335
+ */
1336
+ declare class OidcTokenIssuer extends OidcTokenIssuer_base {
1337
+ static readonly layer: Layer.Layer<OidcTokenIssuer, never, ActionEnvironment | HttpClient.HttpClient>;
1338
+ /**
1339
+ * An **unsigned** JWT carrying these claims.
1340
+ *
1341
+ * @remarks
1342
+ * For building test doubles, and nothing else: the signature segment is a
1343
+ * placeholder, so this token would fail any verifier. It exists because the
1344
+ * alternative — a double returning a synthetic non-JWT — is what made the
1345
+ * provenance path structurally unreachable in the source package, drawing
1346
+ * four separate apologetic comments from one consumer.
1347
+ */
1348
+ static readonly unsignedTokenFor: (claims: OidcClaims) => Redacted.Redacted<string>;
1349
+ /** A test double. Unstubbed members die rather than answering with a non-token. */
1350
+ static readonly makeTest: (overrides?: Partial<OidcTokenIssuerShape>) => OidcTokenIssuerShape;
1351
+ /** {@link OidcTokenIssuer.makeTest} behind `Layer.succeed`. */
1352
+ static readonly layerTest: (overrides?: Partial<OidcTokenIssuerShape>) => Layer.Layer<OidcTokenIssuer>;
1353
+ /**
1354
+ * A double that answers with these claims, consistently on both members.
1355
+ *
1356
+ * @remarks
1357
+ * `token()` returns a real decodable JWT built from the same claims
1358
+ * `claims()` returns, so a consumer that decodes the token itself and a
1359
+ * consumer that asks the service both see the same thing. A double whose two
1360
+ * members can disagree is how a test proves a path works while production
1361
+ * takes the other one.
1362
+ */
1363
+ static readonly layerFor: (claims: OidcClaims) => Layer.Layer<OidcTokenIssuer>;
1364
+ }
1365
+ //#endregion
1366
+ //#region src/ActionsIdentityToken.d.ts
1367
+ /**
1368
+ * The Actions side of `@effected/sbom`'s inverted OIDC contract.
1369
+ *
1370
+ * @remarks
1371
+ * `@effected/sbom` declares `IdentityToken` — one method, one audience,
1372
+ * one redacted token — precisely so that signing does not drag the Actions
1373
+ * runtime into every consumer that only wants to emit an SBOM. This is the
1374
+ * layer that closes the inversion from the other side: it serves that contract
1375
+ * from the runner's own OIDC token service ({@link OidcTokenIssuer}), the same
1376
+ * arrangement as `@effected/workspaces` implementing `@effected/commands`'
1377
+ * `LocalExec`.
1378
+ *
1379
+ * The audience is forwarded verbatim. `SigstoreSigner` asks its identity
1380
+ * contract for the `sigstore` audience itself (`SIGSTORE_OIDC_AUDIENCE` is the
1381
+ * signing protocol's requirement, owned there), so nothing here — and nothing
1382
+ * in a consumer — needs to spell it.
1383
+ *
1384
+ * An `OidcTokenError` is rewrapped as `IdentityTokenError` with the
1385
+ * original preserved structurally in `cause`, so a refused exchange still says
1386
+ * which audience it was minting for **and** why the runner declined —
1387
+ * `reason: "unavailable"` almost always means the workflow is missing
1388
+ * `permissions: id-token: write`.
1389
+ *
1390
+ * @example
1391
+ * ```ts
1392
+ * import { ActionsIdentityToken, OidcTokenIssuer } from "@effected/github-actions";
1393
+ * import { SigstoreSigner } from "@effected/sbom";
1394
+ * import { Layer } from "effect";
1395
+ *
1396
+ * const signing = SigstoreSigner.layer.pipe(
1397
+ * Layer.provide(ActionsIdentityToken.layer),
1398
+ * Layer.provide(OidcTokenIssuer.layer),
1399
+ * );
1400
+ * ```
1401
+ *
1402
+ * @public
1403
+ */
1404
+ declare class ActionsIdentityToken {
1405
+ private constructor();
1406
+ /**
1407
+ * `IdentityToken` served by the runner's OIDC token service.
1408
+ *
1409
+ * @remarks
1410
+ * Requires {@link OidcTokenIssuer} rather than composing
1411
+ * `OidcTokenIssuer.layer` in, so an action that already wired the issuer —
1412
+ * every action using `ActionRuntime.layer` has its requirements at hand —
1413
+ * does not construct a second one.
1414
+ */
1415
+ static readonly layer: Layer.Layer<IdentityToken, never, OidcTokenIssuer>;
1416
+ }
1417
+ //#endregion
1418
+ //#region src/ActionsProvenance.d.ts
1419
+ /**
1420
+ * The Actions side of `@effected/sbom`'s SLSA provenance constructor.
1421
+ *
1422
+ * @remarks
1423
+ * `SlsaProvenance.forGitHubWorkflow` is total and does the real work, but it
1424
+ * takes a camelCase input record — and the only input anyone inside a workflow
1425
+ * actually holds is the runner's own OIDC claims, spelled in GitHub's
1426
+ * snake_case claim names. The rename between the two is eleven all-string
1427
+ * fields, so transposing `repository_id` and `repository_owner_id` compiles,
1428
+ * passes every type check, and produces a validly signed attestation asserting
1429
+ * the **wrong** provenance. This module owns that rename once, on the same
1430
+ * `github-actions` ↔ `sbom` seam as {@link ActionsIdentityToken}: `sbom` must
1431
+ * not depend on the Actions runtime, so the adapter lives here, beside the
1432
+ * services it reads.
1433
+ *
1434
+ * The construct ends at the predicate. Assembling the in-toto statement,
1435
+ * signing it and uploading the bundle stay with the consumer — that pipeline
1436
+ * is deliberately consumer glue (see `@effected/sbom`'s `SigstoreSigner` and
1437
+ * `@effected/github`'s `Attestation.upload`).
1438
+ *
1439
+ * @example
1440
+ * ```ts
1441
+ * import { ActionsProvenance } from "@effected/github-actions";
1442
+ * import { Effect } from "effect";
1443
+ *
1444
+ * const program = Effect.gen(function* () {
1445
+ * const provenance = yield* ActionsProvenance.capture();
1446
+ * return provenance.runDetails.builder.id;
1447
+ * });
1448
+ * ```
1449
+ *
1450
+ * @public
1451
+ */
1452
+ declare class ActionsProvenance {
1453
+ private constructor();
1454
+ /**
1455
+ * The current run's SLSA provenance, built from the runner's OIDC claims.
1456
+ *
1457
+ * @remarks
1458
+ * Every field `GitHubWorkflowProvenance` declares is populated from
1459
+ * {@link OidcClaims}, except `serverUrl`, which is read from
1460
+ * `GITHUB_SERVER_URL` through {@link ActionEnvironment}'s `getOptional`
1461
+ * with `https://github.com` as the default — **not** through the
1462
+ * `GitHubContext` projection. The projection fails typed when a `GITHUB_*`
1463
+ * variable is missing, and a missing server URL is not a failure: it has a
1464
+ * correct default. GHES consumers set the variable, github.com consumers
1465
+ * never should have to, and neither should think about it. (Upstream
1466
+ * `@actions/attest` reads the same variable with no default and writes the
1467
+ * literal string `undefined` into every URL it builds — the hazard
1468
+ * `GitHubWorkflowProvenance.serverUrl` being a required field exists to
1469
+ * prevent.)
1470
+ *
1471
+ * The audience is forwarded to {@link OidcTokenIssuerShape.claims}
1472
+ * verbatim; the claims a provenance reads do not depend on it, so omitting
1473
+ * it is fine and spends no extra token mint.
1474
+ *
1475
+ * An {@link OidcTokenError} passes through **untouched** — not caught, not
1476
+ * defaulted, not wrapped. Whether attestation is mandatory or best-effort
1477
+ * is the consumer's policy: an action that must not publish unattested
1478
+ * lets the error propagate, and one that publishes anyway catches it at
1479
+ * its own boundary. `reason: "unavailable"` almost always means the
1480
+ * workflow is missing `permissions: id-token: write`.
1481
+ */
1482
+ static readonly capture: (audience?: string) => Effect.Effect<SlsaProvenance, OidcTokenError, OidcTokenIssuer | ActionEnvironment>;
1483
+ }
1484
+ //#endregion
1179
1485
  //#region src/Artifact.d.ts
1180
1486
  declare const ArtifactError_base: Schema.Class<ArtifactError, Schema.TaggedStruct<"ArtifactError", {
1181
1487
  /**
@@ -1625,55 +1931,338 @@ declare class GitHubCacheBlobStore {
1625
1931
  static readonly layerWith: (transfer: DataBlobTransfer) => Layer.Layer<BlobStore, never, HttpClient.HttpClient | ActionEnvironment>;
1626
1932
  }
1627
1933
  //#endregion
1628
- //#region src/DetachedProcess.d.ts
1629
- declare const DetachedProcessError_base: Schema.Class<DetachedProcessError, Schema.TaggedStruct<"DetachedProcessError", {
1630
- /**
1631
- * `logUnavailable` — the log file could not be opened, so there is nowhere
1632
- * for the child's output to go. `spawnFailed` — the child did not start.
1633
- * `invalidPid` — a non-positive pid was handed to {@link DetachedProcess.reap};
1634
- * see there for why this is the most important value in the module.
1635
- * `signalFailed` — the signal was refused, e.g. the process belongs to
1636
- * another user. `notReady` — the readiness probe never held.
1637
- */
1638
- readonly reason: Schema.Literals<readonly ["logUnavailable", "spawnFailed", "invalidPid", "signalFailed", "notReady"]>;
1639
- /** The pid involved, when one is. */
1640
- readonly pid: Schema.optionalKey<Schema.Number>;
1641
- /** The path involved, when one is. */
1642
- readonly path: Schema.optionalKey<Schema.String>;
1643
- /** The underlying failure, preserved structurally. */
1934
+ //#region src/CheckDocument.d.ts
1935
+ declare const CheckReport_base: Schema.Class<CheckReport, Schema.Struct<{
1936
+ /** Where the check is now. The last reported state is the authoritative one. */
1937
+ readonly state: Schema.Literals<readonly ["running", "pass", "fail", "warn", "user_interaction_required", "skipped", "timeout"]>;
1938
+ /** Display name, when it differs from the registry key. */
1939
+ readonly title: Schema.optionalKey<Schema.String>;
1940
+ /** A one-line verdict, e.g. `in progress...` or `3 packages ready`. */
1941
+ readonly outcome: Schema.optionalKey<Schema.String>;
1942
+ /** A pre-rendered markdown block of per-check detail. */
1943
+ readonly detail: Schema.optionalKey<Schema.String>;
1944
+ /** Where the check's name should link. */
1945
+ readonly url: Schema.optionalKey<Schema.String>;
1946
+ }>, {}>;
1947
+ /**
1948
+ * What a run knows about one of its checks: the authoritative state, and the
1949
+ * presentation facts a renderer projects into the document.
1950
+ *
1951
+ * @remarks
1952
+ * Only `state` is required. The rest exist because a status table needs them:
1953
+ * `title` when the display name differs from the registry key, `outcome` for
1954
+ * the one-line verdict cell, `url` for linking the check's name, and `detail`
1955
+ * for the per-check block a body region carries. A later report for the same
1956
+ * key **replaces** the whole entry — last write wins, per field and per check.
1957
+ *
1958
+ * @public
1959
+ */
1960
+ declare class CheckReport extends CheckReport_base {}
1961
+ declare const CheckDocumentError_base: Schema.Class<CheckDocumentError, Schema.TaggedStruct<"CheckDocumentError", {
1962
+ /** Which half failed: regenerating the document, or writing it. */
1963
+ readonly kind: Schema.Literals<readonly ["render", "sink"]>;
1964
+ /** The underlying failure. */
1644
1965
  readonly cause: Schema.optionalKey<Schema.Defect>;
1645
1966
  }>, import("effect/Cause").YieldableError>;
1646
1967
  /**
1647
- * Raised when a detached child cannot be started, waited for, or reaped.
1968
+ * Raised when the check document cannot be regenerated or written.
1969
+ *
1970
+ * @remarks
1971
+ * `render` — projecting the registry onto the document failed, almost always
1972
+ * because a reported `detail` contains a line the region scanner would read
1973
+ * as a marker; the underlying `ManagedDocumentError` rides in `cause`.
1974
+ * `sink` — the consumer's write effect failed; its error rides in `cause`.
1975
+ * Callers of `flush` branch on `kind`; the background reconciler never fails
1976
+ * a fiber with this — it logs and retries on the next report.
1648
1977
  *
1649
1978
  * @public
1650
1979
  */
1651
- declare class DetachedProcessError extends DetachedProcessError_base {
1980
+ declare class CheckDocumentError extends CheckDocumentError_base {
1652
1981
  get message(): string;
1653
1982
  }
1654
1983
  /**
1655
- * The schema a pid crosses the phase boundary through.
1984
+ * How a rendered document leaves the process: the narrow sink contract.
1656
1985
  *
1657
1986
  * @remarks
1658
- * A pid is persisted in `GITHUB_STATE` by `main` and read back by `post`, which
1659
- * means it makes the round trip as **text**. This is the boundary check on the
1660
- * way back in: a truncated file, an absent key or a `Number("")` all decode to
1661
- * `0`, and `0` is not a process — it is the current process's whole group.
1662
- * Refusing it here means the bad value never reaches {@link DetachedProcess.reap}
1663
- * in the first place, and reap's own guard is the second of two defenses rather
1664
- * than the only one.
1987
+ * One rendered document in, one write effect out — nothing else. That is
1988
+ * what lets the sticky PR comment (`PullRequestComment.upsert`), the PR
1989
+ * description (`PATCH` on the pull request) and a test's recording `Ref` all
1990
+ * fit the same reconciler. The success value is ignored; a failure surfaces
1991
+ * as {@link CheckDocumentError} with `kind: "sink"`. Provide any services the
1992
+ * write needs when constructing the sink — the reconciler runs it as given.
1665
1993
  *
1666
- * **The brand is core's**, from `ChildProcessSpawner` — the subprocess
1667
- * vocabulary belongs to core and this package does not get to re-declare it.
1668
- * What core does not have is a *validating* way to reconstruct one: its
1669
- * constructor is `Brand.nominal`, which applies no runtime check, so
1670
- * `ChildProcessSpawner.ProcessId(0)` succeeds. That is exactly right for a pid
1671
- * the spawner just produced, and exactly wrong for a pid that has been through a
1672
- * text file. This schema is that missing check, and it decodes to core's type so
1673
- * the two are interchangeable.
1994
+ * @public
1995
+ */
1996
+ type CheckDocumentSink = (rendered: string) => Effect.Effect<unknown, unknown>;
1997
+ /**
1998
+ * Options for {@link CheckDocument.layer}.
1674
1999
  *
1675
- * @example
1676
- * ```ts
2000
+ * @public
2001
+ */
2002
+ interface CheckDocumentOptions {
2003
+ /** The managed document's namespace, e.g. your action's name. */
2004
+ readonly namespace: string;
2005
+ /** Which document, within that namespace. */
2006
+ readonly key: string;
2007
+ /**
2008
+ * The document text to reconcile against initially — a PR body fetched
2009
+ * once at startup, so human content outside the managed regions survives.
2010
+ * Defaults to empty: a fresh document.
2011
+ */
2012
+ readonly initial?: string | undefined;
2013
+ /**
2014
+ * The pure projection from registry state to region entries.
2015
+ *
2016
+ * @remarks
2017
+ * Called with the full snapshot on every reconcile pass; returns the
2018
+ * regions to replace, keyed however the consumer's document is laid out —
2019
+ * typically one region per check plus a shared status table. It must be a
2020
+ * pure function of the snapshot: rendering the same state twice must
2021
+ * produce the same entries, because byte-identical output is what
2022
+ * suppresses the write.
2023
+ */
2024
+ readonly render: (checks: ReadonlyMap<string, CheckReport>) => ReadonlyArray<readonly [key: string, content: string]>;
2025
+ /** Where the rendered document goes. */
2026
+ readonly sink: CheckDocumentSink;
2027
+ /**
2028
+ * Debounce parameters: `quiet` is the trailing settle window, `maxWait`
2029
+ * the staleness bound under a steady stream of reports. Defaults: 500
2030
+ * milliseconds and 3 seconds.
2031
+ */
2032
+ readonly debounce?: {
2033
+ readonly quiet?: Duration.Input | undefined;
2034
+ readonly maxWait?: Duration.Input | undefined;
2035
+ } | undefined;
2036
+ /**
2037
+ * How long one sink write may run before the pass fails with
2038
+ * `kind: "sink"`. Default: 30 seconds.
2039
+ *
2040
+ * @remarks
2041
+ * The reconciler serializes passes behind one permit, and the finalizer's
2042
+ * last flush waits on that same permit — so a sink that never resolves
2043
+ * would otherwise stall every later pass AND scope teardown. The bound
2044
+ * turns a hung write into the ordinary sink failure path: a background
2045
+ * pass logs and retries on the next report, `flush` surfaces the typed
2046
+ * error.
2047
+ */
2048
+ readonly sinkTimeout?: Duration.Input | undefined;
2049
+ }
2050
+ /**
2051
+ * The check-run → document reconciler's surface.
2052
+ *
2053
+ * @public
2054
+ */
2055
+ interface CheckDocumentShape {
2056
+ /**
2057
+ * Record a check's current state and schedule a reconcile.
2058
+ *
2059
+ * @remarks
2060
+ * Never fails and never blocks on the write — the state lands in the
2061
+ * registry, and the debounced background pass projects it. Reporting the
2062
+ * same key again replaces the entry; resolution is **not** terminal, so
2063
+ * `pass` followed by `fail` leaves `fail` even inside one debounce window.
2064
+ */
2065
+ readonly report: (check: string, report: CheckReport) => Effect.Effect<void>;
2066
+ /** The registry as of now, in first-report order. */
2067
+ readonly checks: Effect.Effect<ReadonlyMap<string, CheckReport>>;
2068
+ /**
2069
+ * Reconcile immediately, skipping the debounce.
2070
+ *
2071
+ * @remarks
2072
+ * Rendering unchanged state issues no write. Call it at the end of a run;
2073
+ * the layer's finalizer also runs it, so the last burst of reports lands
2074
+ * even when the scope closes inside the quiet window.
2075
+ */
2076
+ readonly flush: Effect.Effect<void, CheckDocumentError>;
2077
+ }
2078
+ declare const CheckDocument_base: Context.ServiceClass<CheckDocument, "@effected/github-actions/CheckDocument", CheckDocumentShape>;
2079
+ /**
2080
+ * A living document of check state: an in-process registry the run reports
2081
+ * into, debounced onto a marker-delimited document, written through a narrow
2082
+ * sink.
2083
+ *
2084
+ * @remarks
2085
+ * **Push, not pull.** The run owns its checks and knows when they resolve;
2086
+ * nothing here polls GitHub. Consumers call `report` as states change, and a
2087
+ * background fiber projects the registry onto a {@link ManagedDocument} —
2088
+ * regions replaced from current state, so a check that resolves many times
2089
+ * updates its region in place instead of appending.
2090
+ *
2091
+ * The debounce is **trailing with a max-wait, never leading**: a burst of
2092
+ * reports coalesces into one write carrying the *final* state, and a steady
2093
+ * stream still surfaces progress every `maxWait`. A pass that renders
2094
+ * byte-identical text issues no write at all.
2095
+ *
2096
+ * `layer` mints fresh state per call — one layer per document. Bind it to a
2097
+ * `const` if two parts of a program must share one registry.
2098
+ *
2099
+ * @example
2100
+ * ```ts
2101
+ * import { CheckDocument, CheckReport, GitHubMarkdown } from "@effected/github-actions";
2102
+ * import { Effect } from "effect";
2103
+ *
2104
+ * const layer = CheckDocument.layer({
2105
+ * namespace: "my-action",
2106
+ * key: "release-validation",
2107
+ * render: (checks) => [
2108
+ * [
2109
+ * "header",
2110
+ * GitHubMarkdown.table(
2111
+ * ["Check", "Outcome"],
2112
+ * [...checks].map(([key, check]) => [key, check.outcome ?? check.state]),
2113
+ * ),
2114
+ * ],
2115
+ * ],
2116
+ * sink: (rendered) => Effect.log(rendered),
2117
+ * });
2118
+ *
2119
+ * const program = Effect.gen(function* () {
2120
+ * const doc = yield* CheckDocument;
2121
+ * yield* doc.report("build", CheckReport.make({ state: "running" }));
2122
+ * yield* doc.report("build", CheckReport.make({ state: "pass", outcome: "clean" }));
2123
+ * yield* doc.flush;
2124
+ * });
2125
+ * ```
2126
+ *
2127
+ * @public
2128
+ */
2129
+ declare class CheckDocument extends CheckDocument_base {
2130
+ /**
2131
+ * The reconciler over one document.
2132
+ *
2133
+ * @remarks
2134
+ * Forks the debounce fiber into the layer's scope and registers a
2135
+ * finalizer that flushes once more on the way out, so the scope closing
2136
+ * mid-window cannot strand the final state. A background pass that fails
2137
+ * logs a structured warning and leaves the registry intact — the next
2138
+ * report retries it; only `flush` surfaces the typed error.
2139
+ */
2140
+ static layer(options: CheckDocumentOptions): Layer.Layer<CheckDocument>;
2141
+ /** An inert double; unstubbed members die naming themselves. */
2142
+ static readonly makeTest: (overrides?: Partial<CheckDocumentShape>) => CheckDocumentShape;
2143
+ /** {@link CheckDocument.makeTest} behind a `Layer`. */
2144
+ static readonly layerTest: (overrides?: Partial<CheckDocumentShape>) => Layer.Layer<CheckDocument>;
2145
+ }
2146
+ //#endregion
2147
+ //#region src/CheckState.d.ts
2148
+ /**
2149
+ * The kit's check-state vocabulary.
2150
+ *
2151
+ * @remarks
2152
+ * Deliberately **wider than GitHub's own check-run conclusions**: `running` is
2153
+ * a first-class state rather than the absence of a conclusion, and
2154
+ * `user_interaction_required` names a release pipeline waiting on a human. A
2155
+ * check's lifecycle is a *sequence* of these states, and only the last one is
2156
+ * authoritative — resolution is not a terminal transition, so a check may run
2157
+ * `running`, `pass`, `running`, `fail` in order, and that means `fail`.
2158
+ *
2159
+ * Every state projects onto GitHub's check-run wire vocabulary through
2160
+ * {@link projectCheckState}. GitHub's `cancelled` conclusion has no
2161
+ * counterpart here on purpose: cancellation is something the runner does *to*
2162
+ * a run, not a state a pipeline reports about its own checks.
2163
+ *
2164
+ * @public
2165
+ */
2166
+ declare const CheckState: Schema.Literals<readonly ["running", "pass", "fail", "warn", "user_interaction_required", "skipped", "timeout"]>;
2167
+ /**
2168
+ * The type of `CheckState`.
2169
+ *
2170
+ * @public
2171
+ */
2172
+ type CheckState = typeof CheckState.Type;
2173
+ /**
2174
+ * The check-run conclusions the kit vocabulary can produce.
2175
+ *
2176
+ * @remarks
2177
+ * A subset of GitHub's conclusion set, spelled structurally so a pure
2178
+ * vocabulary module does not put `@effected/github` — and through it the
2179
+ * octokit runtime — on its import graph. A structural assertion in the test
2180
+ * suite pins these literals against `CheckConclusion` in `@effected/github`,
2181
+ * so drift between the two spellings fails a test rather than a consumer.
2182
+ *
2183
+ * @public
2184
+ */
2185
+ type CheckRunConclusion = "success" | "failure" | "neutral" | "skipped" | "timed_out" | "action_required";
2186
+ /**
2187
+ * A `CheckState` on GitHub's check-run wire: a status, and a conclusion
2188
+ * exactly when the status is `completed`.
2189
+ *
2190
+ * @remarks
2191
+ * A discriminated union rather than two optional fields, because the wire
2192
+ * protocol's own invariant is conditional: an in-progress check run *has* no
2193
+ * conclusion, and a completed one always does.
2194
+ *
2195
+ * @public
2196
+ */
2197
+ type CheckRunProjection = {
2198
+ readonly status: "in_progress";
2199
+ } | {
2200
+ readonly status: "completed";
2201
+ readonly conclusion: CheckRunConclusion;
2202
+ };
2203
+ /**
2204
+ * Project a kit check state onto GitHub's check-run wire vocabulary.
2205
+ *
2206
+ * @remarks
2207
+ * The canonical mapping: `running` is `in_progress`; everything else is
2208
+ * `completed` with `pass → success`, `fail → failure`, `warn → neutral`
2209
+ * (GitHub has no warning conclusion, and `neutral` is its non-failing,
2210
+ * non-success verdict), `user_interaction_required → action_required`,
2211
+ * `skipped → skipped` and `timeout → timed_out`.
2212
+ *
2213
+ * @public
2214
+ */
2215
+ declare const projectCheckState: (state: CheckState) => CheckRunProjection;
2216
+ //#endregion
2217
+ //#region src/DetachedProcess.d.ts
2218
+ declare const DetachedProcessError_base: Schema.Class<DetachedProcessError, Schema.TaggedStruct<"DetachedProcessError", {
2219
+ /**
2220
+ * `logUnavailable` — the log file could not be opened, so there is nowhere
2221
+ * for the child's output to go. `spawnFailed` — the child did not start.
2222
+ * `invalidPid` — a non-positive pid was handed to {@link DetachedProcess.reap};
2223
+ * see there for why this is the most important value in the module.
2224
+ * `signalFailed` — the signal was refused, e.g. the process belongs to
2225
+ * another user. `notReady` — the readiness probe never held.
2226
+ */
2227
+ readonly reason: Schema.Literals<readonly ["logUnavailable", "spawnFailed", "invalidPid", "signalFailed", "notReady"]>;
2228
+ /** The pid involved, when one is. */
2229
+ readonly pid: Schema.optionalKey<Schema.Number>;
2230
+ /** The path involved, when one is. */
2231
+ readonly path: Schema.optionalKey<Schema.String>;
2232
+ /** The underlying failure, preserved structurally. */
2233
+ readonly cause: Schema.optionalKey<Schema.Defect>;
2234
+ }>, import("effect/Cause").YieldableError>;
2235
+ /**
2236
+ * Raised when a detached child cannot be started, waited for, or reaped.
2237
+ *
2238
+ * @public
2239
+ */
2240
+ declare class DetachedProcessError extends DetachedProcessError_base {
2241
+ get message(): string;
2242
+ }
2243
+ /**
2244
+ * The schema a pid crosses the phase boundary through.
2245
+ *
2246
+ * @remarks
2247
+ * A pid is persisted in `GITHUB_STATE` by `main` and read back by `post`, which
2248
+ * means it makes the round trip as **text**. This is the boundary check on the
2249
+ * way back in: a truncated file, an absent key or a `Number("")` all decode to
2250
+ * `0`, and `0` is not a process — it is the current process's whole group.
2251
+ * Refusing it here means the bad value never reaches {@link DetachedProcess.reap}
2252
+ * in the first place, and reap's own guard is the second of two defenses rather
2253
+ * than the only one.
2254
+ *
2255
+ * **The brand is core's**, from `ChildProcessSpawner` — the subprocess
2256
+ * vocabulary belongs to core and this package does not get to re-declare it.
2257
+ * What core does not have is a *validating* way to reconstruct one: its
2258
+ * constructor is `Brand.nominal`, which applies no runtime check, so
2259
+ * `ChildProcessSpawner.ProcessId(0)` succeeds. That is exactly right for a pid
2260
+ * the spawner just produced, and exactly wrong for a pid that has been through a
2261
+ * text file. This schema is that missing check, and it decodes to core's type so
2262
+ * the two are interchangeable.
2263
+ *
2264
+ * @example
2265
+ * ```ts
1677
2266
  * import { ActionState, DetachedProcess, ProcessId } from "@effected/github-actions";
1678
2267
  * import { Effect } from "effect";
1679
2268
  *
@@ -1896,6 +2485,230 @@ declare class DryRun extends DryRun_base {
1896
2485
  static readonly layerTest: (overrides?: Partial<DryRunShape>) => Layer.Layer<DryRun>;
1897
2486
  }
1898
2487
  //#endregion
2488
+ //#region src/GitHubMarkdown.d.ts
2489
+ /**
2490
+ * A heading level GitHub renders, `1` through `6`.
2491
+ *
2492
+ * @public
2493
+ */
2494
+ type GitHubHeadingDepth = 1 | 2 | 3 | 4 | 5 | 6;
2495
+ /**
2496
+ * Options for {@link GitHubMarkdown.list}.
2497
+ *
2498
+ * @public
2499
+ */
2500
+ interface GitHubListOptions {
2501
+ /** Render `1.`-numbered items instead of bullets. */
2502
+ readonly ordered?: boolean | undefined;
2503
+ }
2504
+ /**
2505
+ * The row-schema constraint {@link GitHubMarkdown.tableFor} accepts: any
2506
+ * schema exposing a struct field map — `Schema.Struct` and `Schema.Class`
2507
+ * both qualify.
2508
+ *
2509
+ * @public
2510
+ */
2511
+ type GitHubRowSchema = Schema.Constraint & {
2512
+ readonly fields: Schema.Struct.Fields;
2513
+ };
2514
+ /**
2515
+ * The field keys of a row schema whose column REQUIRES an explicit
2516
+ * `format`: fields whose encoded side is not a string (their codec has no
2517
+ * string projection to borrow), and fields whose encoding needs services
2518
+ * (a sync cell render cannot provide them).
2519
+ *
2520
+ * @public
2521
+ */
2522
+ type GitHubSchemaTableFormatRequiredKeys<Fields extends Schema.Struct.Fields> = { [K in keyof Fields]: [Fields[K]["Encoded"]] extends [string | undefined] ? [Fields[K]["EncodingServices"]] extends [never] ? never : K : K; }[keyof Fields];
2523
+ /**
2524
+ * Per-column configuration for {@link GitHubMarkdown.tableFor}.
2525
+ *
2526
+ * @public
2527
+ */
2528
+ interface GitHubSchemaTableColumn<Value> {
2529
+ /**
2530
+ * The column header, overriding the field schema's `title` annotation
2531
+ * and the field-name fallback. Pre-rendered markdown, like every cell.
2532
+ */
2533
+ readonly header?: string | undefined;
2534
+ /**
2535
+ * Render a cell from the decoded field value instead of encoding it
2536
+ * through the field's codec. Required — by type — for a field whose
2537
+ * encoded side is not a string.
2538
+ */
2539
+ readonly format?: ((value: Value) => string) | undefined;
2540
+ }
2541
+ /**
2542
+ * {@link GitHubSchemaTableColumn} with the `format` obligation discharged —
2543
+ * the entry shape a non-string-encoded field demands.
2544
+ *
2545
+ * @public
2546
+ */
2547
+ interface GitHubSchemaTableFormattedColumn<Value> extends GitHubSchemaTableColumn<Value> {
2548
+ readonly format: (value: Value) => string;
2549
+ }
2550
+ /**
2551
+ * The per-field column map for {@link GitHubMarkdown.tableFor}: entries are
2552
+ * optional except where the field's encoded side is not a string, where the
2553
+ * entry — and its `format` — are required.
2554
+ *
2555
+ * @public
2556
+ */
2557
+ type GitHubSchemaTableColumns<Fields extends Schema.Struct.Fields> = { readonly [K in GitHubSchemaTableFormatRequiredKeys<Fields>]: GitHubSchemaTableFormattedColumn<Exclude<Fields[K]["Type"], undefined>>; } & { readonly [K in Exclude<keyof Fields, GitHubSchemaTableFormatRequiredKeys<Fields>>]?: GitHubSchemaTableColumn<Exclude<Fields[K]["Type"], undefined>>; };
2558
+ /**
2559
+ * Options for {@link GitHubMarkdown.tableFor}. The whole bag is optional
2560
+ * exactly when every field's encoded side is a string; one field without a
2561
+ * string projection makes `columns` — and the entry carrying its `format` —
2562
+ * required.
2563
+ *
2564
+ * @public
2565
+ */
2566
+ type GitHubSchemaTableOptions<Fields extends Schema.Struct.Fields> = [GitHubSchemaTableFormatRequiredKeys<Fields>] extends [never] ? {
2567
+ readonly columns?: GitHubSchemaTableColumns<Fields> | undefined;
2568
+ } : {
2569
+ readonly columns: GitHubSchemaTableColumns<Fields>;
2570
+ };
2571
+ /**
2572
+ * A renderer minted by {@link GitHubMarkdown.tableFor}: columns are fixed by
2573
+ * the schema, rows are supplied at each render.
2574
+ *
2575
+ * @public
2576
+ */
2577
+ interface GitHubSchemaTable<Row> {
2578
+ /**
2579
+ * Render the current rows as a GFM table. Pure and repeatable — identical
2580
+ * rows produce identical output, so re-rendering from changed state is
2581
+ * just calling it again.
2582
+ */
2583
+ render(rows: ReadonlyArray<Row>): string;
2584
+ }
2585
+ /**
2586
+ * The fluent markdown writer for GitHub surfaces — PR comments, check-run
2587
+ * summaries, job summaries.
2588
+ *
2589
+ * @remarks
2590
+ * Every member takes **pre-rendered markdown** (links, emphasis, emoji — the
2591
+ * caller's fragments pass through verbatim) and returns a `string`, so
2592
+ * compositions read as plain string assembly. What the writer owns is the
2593
+ * *structure*: wherever a fragment could break the construct around it, the
2594
+ * output goes through `@effected/markdown`'s node classes and serializer
2595
+ * rather than string joining. A table cell carrying `>=1 || <2` renders with
2596
+ * its pipes escaped instead of shifting every column after it; a code block
2597
+ * whose content contains a fence gets a longer fence; inline code containing
2598
+ * a backtick gets a wider delimiter; a URL containing spaces or parentheses
2599
+ * is angle-bracketed.
2600
+ *
2601
+ * This module is the package's **only** importer of `@effected/markdown` — a
2602
+ * structural test pins that, on the same terms as the Azure confinement — so
2603
+ * an action that never renders markdown never links the engine.
2604
+ *
2605
+ * @example
2606
+ * ```ts
2607
+ * import { GitHubMarkdown } from "@effected/github-actions";
2608
+ *
2609
+ * const body = [
2610
+ * GitHubMarkdown.heading("Release Validation"),
2611
+ * GitHubMarkdown.table(["Check", "Outcome"], [["build", "✅ passed"]]),
2612
+ * GitHubMarkdown.details("Logs", GitHubMarkdown.codeBlock(log, "text")),
2613
+ * ].join("\n\n");
2614
+ * ```
2615
+ *
2616
+ * @public
2617
+ */
2618
+ declare class GitHubMarkdown {
2619
+ private constructor();
2620
+ /**
2621
+ * A GFM table.
2622
+ *
2623
+ * @remarks
2624
+ * Cells are pre-rendered markdown; the serializer escapes the pipes GFM
2625
+ * would otherwise read as column breaks. Rows shorter than the header are
2626
+ * padded with empty cells rather than shifting.
2627
+ */
2628
+ static table(headers: ReadonlyArray<string>, rows: ReadonlyArray<ReadonlyArray<string>>): string;
2629
+ /**
2630
+ * A GFM table whose columns are defined ONCE by a row schema.
2631
+ *
2632
+ * @remarks
2633
+ * The schema is the single authority for the table's shape: column order
2634
+ * is field declaration order, each header is the field's `title`
2635
+ * annotation (fall back: the property name, overridable per column), and
2636
+ * each cell is the field value's **encoded** string form — a branded or
2637
+ * typed field projects through its own codec, so the vocabulary lives
2638
+ * with the schema instead of being respelled at every call site, and a
2639
+ * row can no longer transpose columns because it is a typed object, not
2640
+ * a positional array.
2641
+ *
2642
+ * A field whose encoded side is not a string has no string projection to
2643
+ * borrow; the type of `options` makes its column's `format` — and
2644
+ * therefore `columns` and `options` themselves — required. An absent or
2645
+ * `undefined` optional field renders as an empty cell without consulting
2646
+ * codec or formatter.
2647
+ *
2648
+ * The renderer is a pure value: `render` maps current rows through
2649
+ * {@link GitHubMarkdown.table}, so escaping is inherited and identical
2650
+ * rows produce identical output. Rows are already-decoded values of the
2651
+ * schema's type, so encoding them is total in practice; a value smuggled
2652
+ * past the types throws the codec's `SchemaError` as a defect — same
2653
+ * posture as the serializer arm above, **not** a silent fallback.
2654
+ *
2655
+ * Only string-keyed fields become columns; symbol keys are not
2656
+ * enumerable table columns.
2657
+ *
2658
+ * @example
2659
+ * ```ts
2660
+ * import { GitHubMarkdown } from "@effected/github-actions";
2661
+ * import { Schema } from "effect";
2662
+ *
2663
+ * const CheckRow = Schema.Struct({
2664
+ * name: Schema.String.annotate({ title: "Check" }),
2665
+ * outcome: Schema.Literals(["passed", "failed"]),
2666
+ * });
2667
+ * const checks = GitHubMarkdown.tableFor(CheckRow);
2668
+ * const body = checks.render([{ name: "build", outcome: "passed" }]);
2669
+ * ```
2670
+ */
2671
+ static tableFor<S extends GitHubRowSchema>(schema: S, ...options: [GitHubSchemaTableFormatRequiredKeys<S["fields"]>] extends [never] ? [options?: GitHubSchemaTableOptions<S["fields"]>] : [options: GitHubSchemaTableOptions<S["fields"]>]): GitHubSchemaTable<S["Type"]>;
2672
+ /** An ATX heading; the level defaults to `2`. */
2673
+ static heading(text: string, level?: GitHubHeadingDepth): string;
2674
+ /** A link. URLs the destination cannot carry raw are angle-bracketed. */
2675
+ static link(text: string, url: string): string;
2676
+ /** Inline code. Content containing backticks widens the delimiter. */
2677
+ static code(text: string): string;
2678
+ /** A fenced code block. Content containing a fence lengthens the fence. */
2679
+ static codeBlock(content: string, language?: string): string;
2680
+ /**
2681
+ * A list of pre-rendered markdown items.
2682
+ *
2683
+ * @remarks
2684
+ * A multi-line item is indented onto continuation lines, so it stays part
2685
+ * of its bullet instead of terminating the list.
2686
+ */
2687
+ static list(items: ReadonlyArray<string>, options?: GitHubListOptions): string;
2688
+ /**
2689
+ * A collapsible `<details>` block.
2690
+ *
2691
+ * @remarks
2692
+ * mdast has no details node, so this is the one construct the writer emits
2693
+ * as raw HTML. The blank lines around `body` are what make GitHub render
2694
+ * the markdown inside it; `summary` is HTML context — markdown is **not**
2695
+ * rendered there, so bold a summary with `<strong>`, not `**`.
2696
+ */
2697
+ static details(summary: string, body: string): string;
2698
+ /**
2699
+ * Pre-rendered markdown, passed through by name.
2700
+ *
2701
+ * @remarks
2702
+ * The identity function, deliberately: every writer member already treats
2703
+ * its fragments as markdown, so `raw` adds no behavior — it adds the
2704
+ * *statement* that a value is already rendered. Use it where a fragment
2705
+ * from elsewhere enters a composition and a reader would otherwise wonder
2706
+ * whether it still needs escaping. It replaces the `Html.make` idiom,
2707
+ * which passes markdown through correctly but announces the wrong intent.
2708
+ */
2709
+ static raw(markdown: string): string;
2710
+ }
2711
+ //#endregion
1899
2712
  //#region src/GitHubToken.d.ts
1900
2713
  declare const GitHubTokenError_base: Schema.Class<GitHubTokenError, Schema.TaggedStruct<"GitHubTokenError", {
1901
2714
  /**
@@ -2087,167 +2900,183 @@ declare class GitHubToken {
2087
2900
  static readonly dispose: (options?: ReadOptions | undefined) => Effect.Effect<void, ActionStateError | import("@effected/github").GitHubAppError, ActionState | GitHubApp>;
2088
2901
  }
2089
2902
  //#endregion
2090
- //#region src/OidcTokenIssuer.d.ts
2091
- declare const OidcTokenError_base: Schema.Class<OidcTokenError, Schema.TaggedStruct<"OidcTokenError", {
2092
- /**
2093
- * `unavailable` — the runner did not publish the token-service variables,
2094
- * which almost always means the workflow is missing `permissions: id-token:
2095
- * write`. `requestFailed` — the token service could not be reached or refused
2096
- * the request. `malformedResponse` — it answered with something that is not a
2097
- * token envelope. `malformedToken` — the token is not a decodable JWT.
2098
- * `missingClaims` — it decoded, but without the claims a provenance statement
2099
- * needs.
2100
- */
2101
- readonly reason: Schema.Literals<readonly ["unavailable", "requestFailed", "malformedResponse", "malformedToken", "missingClaims"]>;
2102
- /** What was wrong, in one line. */
2103
- readonly detail: Schema.optionalKey<Schema.String>;
2104
- /** The HTTP status, when the token service answered. */
2105
- readonly status: Schema.optionalKey<Schema.Number>;
2106
- /** The underlying failure, preserved structurally. */
2107
- readonly cause: Schema.optionalKey<Schema.Defect>;
2903
+ //#region src/ManagedDocument.d.ts
2904
+ declare const ManagedDocumentError_base: Schema.Class<ManagedDocumentError, Schema.TaggedStruct<"ManagedDocumentError", {
2905
+ /** Which ambiguity or refusal was found. */
2906
+ readonly kind: Schema.Literals<readonly ["unterminatedRegion", "orphanedEnd", "overlappingRegions", "duplicateRegion", "markerInContent", "duplicateDeclaration"]>;
2907
+ /** 1-based line of the offending marker, for the structural kinds. */
2908
+ readonly line: Schema.optionalKey<Schema.Number>;
2909
+ /** The region key involved, when the failure names one. */
2910
+ readonly key: Schema.optionalKey<Schema.String>;
2108
2911
  }>, import("effect/Cause").YieldableError>;
2109
2912
  /**
2110
- * Raised when an OIDC token cannot be issued or read.
2913
+ * Raised when a managed document cannot be read or regenerated without
2914
+ * guessing.
2915
+ *
2916
+ * @remarks
2917
+ * The four structural kinds (`unterminatedRegion`, `orphanedEnd`,
2918
+ * `overlappingRegions`, `duplicateRegion`) surface at parse time and name an
2919
+ * ambiguity whose silent resolution would corrupt content a human wrote by
2920
+ * hand. The two declaration kinds surface when regions are applied:
2921
+ * `markerInContent` refuses region content that itself contains a line the
2922
+ * scanner would read as a region marker, and `duplicateDeclaration` refuses
2923
+ * one call that states two intentions for the same region.
2111
2924
  *
2112
2925
  * @public
2113
2926
  */
2114
- declare class OidcTokenError extends OidcTokenError_base {
2927
+ declare class ManagedDocumentError extends ManagedDocumentError_base {
2115
2928
  get message(): string;
2116
2929
  }
2117
- declare const OidcClaims_base: Schema.Class<OidcClaims, Schema.Struct<{
2118
- /** The issuer, e.g. `https://token.actions.githubusercontent.com`. */
2119
- readonly iss: Schema.String;
2120
- /** The full ref the workflow ran on. */
2121
- readonly ref: Schema.String;
2122
- /** The commit. */
2123
- readonly sha: Schema.String;
2124
- /** `owner/repo`. */
2125
- readonly repository: Schema.String;
2126
- /** The event that triggered the run. */
2127
- readonly event_name: Schema.String;
2128
- /** The reusable-workflow ref, which is what a verifier pins against. */
2129
- readonly job_workflow_ref: Schema.String;
2130
- /** The calling workflow's ref. */
2131
- readonly workflow_ref: Schema.String;
2132
- /** The numeric repository id, as a string. */
2133
- readonly repository_id: Schema.String;
2134
- /** The numeric owner id, as a string. */
2135
- readonly repository_owner_id: Schema.String;
2136
- /** `github-hosted` or `self-hosted`. */
2137
- readonly runner_environment: Schema.String;
2138
- /** The run id, as a string. */
2139
- readonly run_id: Schema.String;
2140
- /** The run attempt, as a string. */
2141
- readonly run_attempt: Schema.String;
2142
- }>, {}>;
2143
2930
  /**
2144
- * The claims a GitHub Actions OIDC token carries about the workflow that ran.
2145
- *
2146
- * @remarks
2147
- * The field names are GitHub's own JWT claim names, kept verbatim rather than
2148
- * translated to the kit's casing. They are an **external vocabulary** — the
2149
- * same strings that appear in a SLSA provenance predicate and in every
2150
- * published example of an Actions OIDC policy — and renaming them would put a
2151
- * translation layer between a consumer and the specification they are reading.
2931
+ * Options identifying a managed document inside a text that may not carry one
2932
+ * yet.
2152
2933
  *
2153
2934
  * @public
2154
2935
  */
2155
- declare class OidcClaims extends OidcClaims_base {}
2936
+ interface ManagedDocumentSource {
2937
+ /** Whose document this is, e.g. your action's name. */
2938
+ readonly namespace: string;
2939
+ /** Which document, within that namespace. */
2940
+ readonly key: string;
2941
+ /**
2942
+ * The current text — a fetched comment body, a PR description — or absent
2943
+ * when there is nothing yet. An absent text and an empty one mean the same
2944
+ * thing: a fresh document.
2945
+ */
2946
+ readonly text?: string | undefined;
2947
+ }
2948
+ declare const ManagedDocument_base: Schema.Class<ManagedDocument, Schema.Struct<{
2949
+ /** Whose document this is. */
2950
+ readonly namespace: Schema.String;
2951
+ /** Which document, within that namespace. */
2952
+ readonly key: Schema.String;
2953
+ /** The document's full text, sentinel and regions included. */
2954
+ readonly text: Schema.String;
2955
+ }>, {}>;
2156
2956
  /**
2157
- * The {@link OidcTokenIssuer} service shape.
2957
+ * A marker-delimited document: named regions a tool owns, inside text a human
2958
+ * may also edit.
2959
+ *
2960
+ * @remarks
2961
+ * The one primitive behind both the sticky PR comment and the managed PR
2962
+ * description. A sentinel HTML comment (`<!-- namespace:key -->`) identifies
2963
+ * the document; each region is delimited by HTML-comment markers keyed
2964
+ * `namespace.key.region`; and everything outside the managed regions is
2965
+ * preserved byte-for-byte on regeneration. Regions are **replaced from
2966
+ * current state, never appended** — applying the same region twice yields one
2967
+ * region carrying the latest content, which is what makes a check that
2968
+ * resolves many times safe to re-render.
2969
+ *
2970
+ * The region engine is `@effected/templates`' pure `SectionDocument` —
2971
+ * reused, not reinvented — fixed to the HTML comment style and a
2972
+ * region-specific phrase. Everything here is string → string; the GitHub
2973
+ * write path composes this with `PullRequestComment` (whose `CommentMarker`
2974
+ * renders the *same* sentinel for the same namespace and key) or a PR-body
2975
+ * update in `@effected/github`.
2976
+ *
2977
+ * Construct with {@link ManagedDocument.parseResult} — create-or-update in
2978
+ * one call, so the consumer never branches on whether the document exists
2979
+ * yet. `ManagedDocument.make` is the raw schema constructor and performs no
2980
+ * structural validation.
2981
+ *
2982
+ * @example
2983
+ * ```ts
2984
+ * import { ManagedDocument } from "@effected/github-actions";
2985
+ * import { Result } from "effect";
2986
+ *
2987
+ * const doc = ManagedDocument.parseResult({ namespace: "my-action", key: "release", text: fetched });
2988
+ * if (Result.isSuccess(doc)) {
2989
+ * const next = doc.success.withRegionsResult([
2990
+ * ["header", "## Validating Release"],
2991
+ * ["footer", "generated by my-action"],
2992
+ * ]);
2993
+ * }
2994
+ * ```
2158
2995
  *
2159
2996
  * @public
2160
2997
  */
2161
- interface OidcTokenIssuerShape {
2998
+ declare class ManagedDocument extends ManagedDocument_base {
2162
2999
  /**
2163
- * Request an ID token, optionally bound to an audience.
3000
+ * Read a document out of existing text, or begin a fresh one.
2164
3001
  *
2165
3002
  * @remarks
2166
- * Omitting the audience sends no `audience` parameter at all, matching
2167
- * `@actions/core.getIDToken` — the runner's default audience is not the empty
2168
- * string.
3003
+ * The synchronous primitive; {@link ManagedDocument.parse} derives from it.
3004
+ * Absent text, empty text and text that carries no sentinel are all legal —
3005
+ * the sentinel and regions are added by the first
3006
+ * {@link ManagedDocument.withRegionsResult}. Only a *structurally corrupt*
3007
+ * region layout fails, because every structural ambiguity is a case where
3008
+ * a silent choice would destroy content a human wrote.
2169
3009
  */
2170
- readonly token: (audience?: string) => Effect.Effect<Redacted.Redacted<string>, OidcTokenError>;
3010
+ static parseResult(source: ManagedDocumentSource): Result.Result<ManagedDocument, ManagedDocumentError>;
2171
3011
  /**
2172
- * The token's claims, decoded.
3012
+ * Read a document out of existing text, in `Effect`.
2173
3013
  *
2174
3014
  * @remarks
2175
- * **The signature is deliberately not verified**, for three reasons that are
2176
- * recorded so nobody "fixes" it:
3015
+ * Defined in terms of {@link ManagedDocument.parseResult} — synchronous
3016
+ * callers can use that variant directly.
3017
+ */
3018
+ static readonly parse: (source: ManagedDocumentSource) => Effect.Effect<ManagedDocument, ManagedDocumentError, never>;
3019
+ /** The HTML comment identifying this document, `<!-- namespace:key -->`. */
3020
+ get sentinel(): string;
3021
+ /** Does this text carry the document's sentinel? */
3022
+ matches(text: string): boolean;
3023
+ /** The content of one region, if the document carries it. */
3024
+ region(key: string): Option.Option<string>;
3025
+ /** Every region this document carries, in document order. */
3026
+ get regions(): ReadonlyArray<{
3027
+ readonly key: string;
3028
+ readonly content: string;
3029
+ }>;
3030
+ /**
3031
+ * The document with these regions replaced from current state.
2177
3032
  *
2178
- * 1. The token comes from the runner's own token-service endpoint over TLS.
2179
- * The transport is the trust boundary, and the process asking for the
2180
- * token is the process that received it.
2181
- * 2. The claims populate a provenance predicate, not a trust decision.
2182
- * Nothing branches on them for authorization; they are recorded as
2183
- * attested facts about the workflow that ran.
2184
- * 3. Verifying would need a JWKS fetch, which turns a decode into a network
2185
- * call — untestable without a fixture server, and dependent on GitHub's
2186
- * key endpoint being reachable at attestation time.
3033
+ * @remarks
3034
+ * The synchronous primitive; {@link ManagedDocument.withRegions} derives
3035
+ * from it. Each entry pairs a region key with its full new content. A
3036
+ * region already present is **replaced in place**; a region not present
3037
+ * yet is added; regions not named are left exactly as they are; and every
3038
+ * byte outside a managed region survives untouched. Applying entries that
3039
+ * change nothing returns byte-identical text, which is what lets a caller
3040
+ * compare-and-skip instead of issuing a write.
2187
3041
  *
2188
- * A consumer that needs a *verified* token needs a different operation with a
2189
- * different name and a different error channel, not an option on this one.
3042
+ * Region keys share the name grammar `namespace` and `key` obey; a key
3043
+ * outside it is a wiring error and throws at construction rather than
3044
+ * surfacing in the typed channel.
3045
+ */
3046
+ withRegionsResult(entries: ReadonlyArray<readonly [key: string, content: string]>): Result.Result<ManagedDocument, ManagedDocumentError>;
3047
+ /**
3048
+ * The document with these regions replaced, in `Effect`.
2190
3049
  *
2191
- * Claims are on the surface rather than left to the call site because that is
2192
- * what makes the provenance path reachable in a test: a double built with
2193
- * {@link OidcTokenIssuer.layerFor} answers with real, decodable claims, where
2194
- * the synthetic non-JWT its predecessor returned made every consumer's
2195
- * `decodeJwtClaims` yield nothing and silently skipped the path under test.
3050
+ * @remarks
3051
+ * Defined in terms of {@link ManagedDocument.withRegionsResult} —
3052
+ * synchronous callers can use that variant directly.
2196
3053
  */
2197
- readonly claims: (audience?: string) => Effect.Effect<OidcClaims, OidcTokenError>;
2198
- }
2199
- declare const OidcTokenIssuer_base: Context.ServiceClass<OidcTokenIssuer, "@effected/github-actions/OidcTokenIssuer", OidcTokenIssuerShape>;
2200
- /**
2201
- * The runner's OIDC token service.
2202
- *
2203
- * @remarks
2204
- * Lives here rather than with attestation because it reads
2205
- * `ACTIONS_ID_TOKEN_REQUEST_TOKEN` and `ACTIONS_ID_TOKEN_REQUEST_URL`, which
2206
- * exist only when a workflow declares `permissions: id-token: write` — that is
2207
- * a fact about the runner, not about signing.
2208
- *
2209
- * @example
2210
- * ```ts
2211
- * import { OidcTokenIssuer } from "@effected/github-actions";
2212
- * import { Effect } from "effect";
2213
- *
2214
- * const program = Effect.gen(function* () {
2215
- * const issuer = yield* OidcTokenIssuer;
2216
- * const claims = yield* issuer.claims("sigstore");
2217
- * return claims.job_workflow_ref;
2218
- * });
2219
- * ```
2220
- *
2221
- * @public
2222
- */
2223
- declare class OidcTokenIssuer extends OidcTokenIssuer_base {
2224
- static readonly layer: Layer.Layer<OidcTokenIssuer, never, ActionEnvironment | HttpClient.HttpClient>;
3054
+ withRegions(entries: ReadonlyArray<readonly [key: string, content: string]>): Effect.Effect<ManagedDocument, ManagedDocumentError>;
3055
+ /** The marker key a region is written under: `namespace.key.region`. */
3056
+ private wireKey;
3057
+ /** The region key back out of a wire key, for error reporting. */
3058
+ private regionKeyOf;
2225
3059
  /**
2226
- * An **unsigned** JWT carrying these claims.
3060
+ * This document's own regions, in document order.
2227
3061
  *
2228
3062
  * @remarks
2229
- * For building test doubles, and nothing else: the signature segment is a
2230
- * placeholder, so this token would fail any verifier. It exists because the
2231
- * alternative — a double returning a synthetic non-JWT — is what made the
2232
- * provenance path structurally unreachable in the source package, drawing
2233
- * four separate apologetic comments from one consumer.
3063
+ * A document a human already edited can be structurally corrupt, and a
3064
+ * read has no error channel worth having — a corrupt document simply has
3065
+ * no readable regions; {@link ManagedDocument.parseResult} is where
3066
+ * corruption fails typed. Regions belonging to a *different* document in
3067
+ * the same text (another namespace or key) are not this document's and are
3068
+ * never reported.
2234
3069
  */
2235
- static readonly unsignedTokenFor: (claims: OidcClaims) => Redacted.Redacted<string>;
2236
- /** A test double. Unstubbed members die rather than answering with a non-token. */
2237
- static readonly makeTest: (overrides?: Partial<OidcTokenIssuerShape>) => OidcTokenIssuerShape;
2238
- /** {@link OidcTokenIssuer.makeTest} behind `Layer.succeed`. */
2239
- static readonly layerTest: (overrides?: Partial<OidcTokenIssuerShape>) => Layer.Layer<OidcTokenIssuer>;
3070
+ private ownRegions;
2240
3071
  /**
2241
- * A double that answers with these claims, consistently on both members.
3072
+ * Append the sentinel when the text does not carry it yet.
2242
3073
  *
2243
3074
  * @remarks
2244
- * `token()` returns a real decodable JWT built from the same claims
2245
- * `claims()` returns, so a consumer that decodes the token itself and a
2246
- * consumer that asks the service both see the same thing. A double whose two
2247
- * members can disagree is how a test proves a path works while production
2248
- * takes the other one.
3075
+ * Appended at the end — the same position `PullRequestComment.upsert`
3076
+ * writes its marker to — so a document rendered here and a comment written
3077
+ * there agree about where the identity line lives.
2249
3078
  */
2250
- static readonly layerFor: (claims: OidcClaims) => Layer.Layer<OidcTokenIssuer>;
3079
+ private ensureSentinel;
2251
3080
  }
2252
3081
  //#endregion
2253
3082
  //#region src/Secret.d.ts
@@ -2316,6 +3145,19 @@ declare class Secret {
2316
3145
  * over a `Redacted`, so a signing key has to become a string somewhere, and
2317
3146
  * the point of this module is that "somewhere" is here and nowhere else.
2318
3147
  *
3148
+ * Signing named the member; it does not gate it. **Any in-process use that
3149
+ * needs the raw value without writing it to a runner file is this
3150
+ * operation** — handing a key to a library that takes a plain string. The
3151
+ * dividing line is where the value goes, not what it is for: a value
3152
+ * crossing into a **child's** environment is {@link Secret.forChildEnv},
3153
+ * one written to `GITHUB_STATE` or `GITHUB_OUTPUT` is
3154
+ * {@link Secret.forRunnerFile}, and one that stays in this process is this
3155
+ * member. A third-party SDK that reads only the ambient environment is the
3156
+ * hard case: this package never mutates `process.env` (reads are seeded
3157
+ * once through `ActionEnvironment`), so if a consumer chooses that bridge,
3158
+ * the mutation — and its restore discipline — lives in consumer code as
3159
+ * the consumer's own tradeoff, not as a pattern this member recommends.
3160
+ *
2319
3161
  * It masks even though the value is never *written* anywhere, and that is
2320
3162
  * deliberate rather than superstitious: a signing key that leaks does so
2321
3163
  * through something nobody audited — a debug log of outgoing headers, a
@@ -2463,5 +3305,5 @@ declare class ToolInstaller extends ToolInstaller_base {
2463
3305
  static readonly layerTest: (overrides?: Partial<ToolInstallerShape>) => Layer.Layer<ToolInstaller>;
2464
3306
  }
2465
3307
  //#endregion
2466
- export { Action, ActionCache, ActionCacheError, type ActionCacheShape, ActionEnvironment, ActionEnvironmentError, type ActionEnvironmentShape, ActionInput, ActionLogger, type ActionLoggerShape, ActionOutputError, ActionOutputs, type ActionOutputsShape, type ActionRunOptions, ActionRuntime, type ActionServices, ActionState, ActionStateError, type ActionStateShape, type AnnotationProperties, Artifact, ArtifactError, type ArtifactItem, type ArtifactRef, type ArtifactShape, type Blob, BlobEnvelope, BlobEnvelopeError, BlobStore, BlobStoreError, type BlobStoreShape, BlobTransferError, CacheKey, CacheKeyError, type ClientLayerOptions, type DataBlobTransfer, DetachedProcess, DetachedProcessError, type DetachedSpawnOptions, type DownloadOptions, type DownloadResult, DryRun, type DryRunShape, type ExtractOptions, type FileBlobTransfer, GitHubCacheBlobStore, GitHubContext, GitHubToken, GitHubTokenError, OidcClaims, OidcTokenError, OidcTokenIssuer, type OidcTokenIssuerShape, ProcessId, type ProvisionOptions, type ReadOptions, type ReadinessOptions, RunnerContext, type S3Config, Secret, ToolInstaller, ToolInstallerError, type ToolInstallerShape, type UploadOptions, type UploadResult, WorkflowCommand, describeCause };
3308
+ export { Action, ActionCache, ActionCacheError, type ActionCacheShape, ActionEnvironment, ActionEnvironmentError, type ActionEnvironmentShape, ActionInput, ActionLogger, type ActionLoggerShape, ActionOutputError, ActionOutputs, type ActionOutputsShape, type ActionRunOptions, ActionRuntime, type ActionServices, ActionState, ActionStateError, type ActionStateShape, ActionsIdentityToken, ActionsProvenance, type AnnotationProperties, Artifact, ArtifactError, type ArtifactItem, type ArtifactRef, type ArtifactShape, type Blob, BlobEnvelope, BlobEnvelopeError, BlobStore, BlobStoreError, type BlobStoreShape, BlobTransferError, CacheKey, CacheKeyError, CheckDocument, CheckDocumentError, type CheckDocumentOptions, type CheckDocumentShape, type CheckDocumentSink, CheckReport, type CheckRunConclusion, type CheckRunProjection, CheckState, type ClientLayerOptions, type DataBlobTransfer, DetachedProcess, DetachedProcessError, type DetachedSpawnOptions, type DownloadOptions, type DownloadResult, DryRun, type DryRunShape, type ExtractOptions, type FileBlobTransfer, GitHubCacheBlobStore, GitHubContext, type GitHubHeadingDepth, type GitHubListOptions, GitHubMarkdown, type GitHubRowSchema, type GitHubSchemaTable, type GitHubSchemaTableColumn, type GitHubSchemaTableColumns, type GitHubSchemaTableFormatRequiredKeys, type GitHubSchemaTableFormattedColumn, type GitHubSchemaTableOptions, GitHubToken, GitHubTokenError, ManagedDocument, ManagedDocumentError, type ManagedDocumentSource, OidcClaims, OidcTokenError, OidcTokenIssuer, type OidcTokenIssuerShape, ProcessId, type ProvisionOptions, type ReadOptions, type ReadinessOptions, RunnerContext, type S3Config, Secret, ToolInstaller, ToolInstallerError, type ToolInstallerShape, type UploadOptions, type UploadResult, type WithBufferOptions, WorkflowCommand, describeCause, projectCheckState };
2467
3309
  //# sourceMappingURL=index.d.ts.map