@effected/github-actions 0.1.0 → 0.3.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,7 +2,9 @@ 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";
7
+ import { PackageManagerPin } from "@effected/npm";
6
8
  //#region src/ActionEnvironment.d.ts
7
9
  declare const ActionEnvironmentError_base: Schema.Class<ActionEnvironmentError, Schema.TaggedStruct<"ActionEnvironmentError", {
8
10
  readonly reason: Schema.Literals<readonly ["missing", "malformed"]>;
@@ -237,6 +239,24 @@ declare class WorkflowCommand {
237
239
  }
238
240
  //#endregion
239
241
  //#region src/ActionLogger.d.ts
242
+ /**
243
+ * Options for {@link ActionLoggerShape.withBuffer}.
244
+ *
245
+ * @public
246
+ */
247
+ interface WithBufferOptions {
248
+ /**
249
+ * What happens to the captured transcript when the step **succeeds**.
250
+ *
251
+ * @remarks
252
+ * `"flush"` — the default — replays it, so a clean run still prints what it
253
+ * did. `"discard"` drops it, which is what keeps a green release log to one
254
+ * line per step: the transcript exists only as the failure report. A failure,
255
+ * a defect or an interruption flushes under either setting — the choice is
256
+ * only about what a success is worth in the log.
257
+ */
258
+ readonly onSuccess?: "flush" | "discard";
259
+ }
240
260
  /**
241
261
  * The {@link ActionLogger} service shape.
242
262
  *
@@ -252,13 +272,18 @@ interface ActionLoggerShape {
252
272
  * Warnings and errors are **not** buffered — they go out as they happen, so a
253
273
  * long step still reports trouble while it is running. Everything at `Info`
254
274
  * 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.
275
+ * interruption, so a clean run still prints its transcript. A step that
276
+ * should be **quiet** when it succeeds passes
277
+ * `{ onSuccess: "discard" }` ({@link WithBufferOptions}); failure still
278
+ * spills the transcript either way.
256
279
  *
257
280
  * Buffering is skipped entirely when the runner has step debugging enabled or
258
281
  * the ambient minimum log level is already `Debug` or lower — someone asking
259
- * for verbose output wants it live.
282
+ * for verbose output wants it live, and that overrides
283
+ * `onSuccess: "discard"` too: asking for debug output means wanting to see
284
+ * what a green step did.
260
285
  */
261
- readonly withBuffer: <A, E, R>(label: string, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
286
+ readonly withBuffer: <A, E, R>(label: string, effect: Effect.Effect<A, E, R>, options?: WithBufferOptions) => Effect.Effect<A, E, R>;
262
287
  /**
263
288
  * Emit a `::notice::` annotation.
264
289
  *
@@ -349,9 +374,11 @@ declare const ActionOutputError_base: Schema.Class<ActionOutputError, Schema.Tag
349
374
  * the code is running outside a GitHub runner. `writeFailed` — the file
350
375
  * exists but could not be appended to. `invalidName` — the name would
351
376
  * corrupt the file's block structure. `encodeFailed` — a value did not
352
- * satisfy its schema.
377
+ * satisfy its schema. `detached` — the member was called under
378
+ * {@link ActionOutputs.layerDetached}, where the channel it writes cannot
379
+ * reach the job that would read it.
353
380
  */
354
- readonly reason: Schema.Literals<readonly ["unavailable", "writeFailed", "invalidName", "encodeFailed"]>;
381
+ readonly reason: Schema.Literals<readonly ["unavailable", "writeFailed", "invalidName", "encodeFailed", "detached"]>;
355
382
  /** Which runner file was involved, by environment variable name. */
356
383
  readonly file: Schema.optionalKey<Schema.String>;
357
384
  /** The output or variable name, when one is involved. */
@@ -381,7 +408,17 @@ interface ActionOutputsShape {
381
408
  readonly summary: (content: string) => Effect.Effect<void, ActionOutputError>;
382
409
  /** Export an environment variable to **subsequent** steps. */
383
410
  readonly exportVariable: (name: string, value: string) => Effect.Effect<void, ActionOutputError>;
384
- /** Prepend a directory to `PATH` for subsequent steps. */
411
+ /**
412
+ * Prepend a directory to `PATH` for **subsequent** steps.
413
+ *
414
+ * @remarks
415
+ * This appends to the `GITHUB_PATH` runner file and does **nothing else** —
416
+ * unlike `@actions/core`, it does NOT mutate the live `process.env.PATH` of
417
+ * the current process. A same-process probe of a tool that was just added
418
+ * (`pnpm --version`, a shim, a binary) will not find it by name; probe by
419
+ * absolute path instead. The runner applies the addition when the next step
420
+ * starts, exactly as it does for {@link ActionOutputsShape.exportVariable}.
421
+ */
385
422
  readonly addPath: (path: string) => Effect.Effect<void, ActionOutputError>;
386
423
  /** Emit an error annotation. Does not itself set the exit code. */
387
424
  readonly setFailed: (message: string) => Effect.Effect<void>;
@@ -406,6 +443,45 @@ declare const ActionOutputs_base: Context.ServiceClass<ActionOutputs, "@effected
406
443
  */
407
444
  declare class ActionOutputs extends ActionOutputs_base {
408
445
  static readonly layer: Layer.Layer<ActionOutputs, never, ActionEnvironment | FileSystem.FileSystem>;
446
+ /**
447
+ * The outputs surface that is correct inside a **detached worker**.
448
+ *
449
+ * @remarks
450
+ * The masking model assumes the runner parses stdout. A detached worker's
451
+ * stdout is a **log file no runner parses**, so under the real layer
452
+ * `setSecret` emits `::add-mask::<plaintext>` into that file — a command
453
+ * that is simultaneously **inert** (nothing reads it, so nothing is masked)
454
+ * and a **secret leak** (the plaintext is now sitting verbatim in a log). A
455
+ * production action shipped exactly that for one round: an S3 secret and
456
+ * session token written into a worker's log by the masking call itself.
457
+ * This layer is the worker-side fix; the parent-side rule is that every
458
+ * secret a worker will hold is masked **before** the spawn, by
459
+ * `Secret.forChildEnv` under the real layer.
460
+ *
461
+ * The layer needs no environment and no filesystem — its `R` is `never` —
462
+ * so a worker composing it structurally *cannot* write a runner file.
463
+ * Per-member behavior:
464
+ *
465
+ * - **`setSecret` — silent no-op.** The value must not be written
466
+ * *anywhere*: there is no runner on this channel to hand it to, and any
467
+ * spelling of it in the log IS the leak. Masking is the parent's job,
468
+ * before the worker exists.
469
+ * - **`set`, `setJson`, `exportVariable`, `addPath`, `summary` — fail
470
+ * typed** (`reason: "detached"`, naming the file). Each writes a runner
471
+ * file that configures the parent job's *later steps* — `GITHUB_OUTPUT`,
472
+ * `GITHUB_ENV`, `GITHUB_PATH` — or is collected when the step completes
473
+ * (`GITHUB_STEP_SUMMARY`). A detached worker has no later steps, may
474
+ * outlive the job entirely, and even an inherited file path would be
475
+ * read at a moment unrelated to the write — so a worker calling one of
476
+ * these is a program error worth surfacing, not a write to degrade
477
+ * silently.
478
+ * - **`setFailed` — degrades to a plain log line** (`Console.error`,
479
+ * without the `::error::` syntax nothing here would parse). Unlike the
480
+ * file members it is log-like: "record that something failed" still
481
+ * means something in a worker's log, and its signature carries no error
482
+ * channel to fail through.
483
+ */
484
+ static readonly layerDetached: Layer.Layer<ActionOutputs>;
409
485
  /** A test double. Unstubbed members die rather than silently succeeding. */
410
486
  static readonly makeTest: (overrides?: Partial<ActionOutputsShape>) => ActionOutputsShape;
411
487
  /** {@link ActionOutputs.makeTest} behind `Layer.succeed`. */
@@ -417,10 +493,13 @@ declare const ActionStateError_base: Schema.Class<ActionStateError, Schema.Tagge
417
493
  /**
418
494
  * `missing` — no value was saved under this key in an earlier phase.
419
495
  * `malformed` — a value is there but is not JSON, or does not satisfy the
420
- * schema it was read with. `writeFailed` — the state file could not be
496
+ * schema it was read with. `notPlainJson` — caught at SAVE time: the
497
+ * schema's encoded form does not survive `JSON.stringify`/`JSON.parse`, so
498
+ * persisting it would present one phase later as a `malformed` mystery with
499
+ * no pointer to the cause. `writeFailed` — the state file could not be
421
500
  * appended to.
422
501
  */
423
- readonly reason: Schema.Literals<readonly ["missing", "malformed", "writeFailed"]>;
502
+ readonly reason: Schema.Literals<readonly ["missing", "malformed", "notPlainJson", "writeFailed"]>;
424
503
  readonly key: Schema.String;
425
504
  readonly cause: Schema.optionalKey<Schema.Defect>;
426
505
  }>, import("effect/Cause").YieldableError>;
@@ -438,7 +517,24 @@ declare class ActionStateError extends ActionStateError_base {
438
517
  * @public
439
518
  */
440
519
  interface ActionStateShape {
441
- /** Persist a value for a later phase. */
520
+ /**
521
+ * Persist a value for a later phase.
522
+ *
523
+ * @remarks
524
+ * **The schema's ENCODED form must be plain JSON** — `GITHUB_STATE` is a
525
+ * text file and the value crosses it as `JSON.stringify(encoded)`. A schema
526
+ * whose encoded side is a class instance (`Schema.Option`'s is an `Option`,
527
+ * serialized via its `toJSON` to `{"_id":"Option",…}`) writes something no
528
+ * later phase can decode; use the plain-JSON codec instead —
529
+ * `Schema.OptionFromNullOr` for an optional value.
530
+ *
531
+ * Every save proves the rule: the encoded value is round-tripped through
532
+ * `JSON.stringify`/`JSON.parse` and re-decoded, and a value that does not
533
+ * survive fails HERE, typed (`reason: "notPlainJson"`, naming the key) —
534
+ * rather than one phase later as a `malformed` mystery in `post` that
535
+ * `main` believed it saved. Action state is small by protocol, so the
536
+ * per-save round-trip costs effectively nothing.
537
+ */
442
538
  readonly save: <A, I>(key: string, value: A, schema: Schema.Codec<A, I>) => Effect.Effect<void, ActionStateError>;
443
539
  /** Read a value saved by an earlier phase. */
444
540
  readonly get: <A, I>(key: string, schema: Schema.Codec<A, I>) => Effect.Effect<A, ActionStateError>;
@@ -685,6 +781,14 @@ declare class CacheKeyError extends CacheKeyError_base {
685
781
  declare const CacheKey_base: Schema.Class<CacheKey, Schema.Struct<{
686
782
  /** The components, most general first. */
687
783
  readonly segments: Schema.NonEmptyArray<Schema.String>;
784
+ /**
785
+ * The explicit ladder policy, when one was given: how many leading
786
+ * segments each rung keeps, in the order the rungs are tried. Absent for
787
+ * the default every-prefix ladder; **empty for no ladder at all** —
788
+ * exact-match-only. See {@link CacheKey.withRestoreDepths} and
789
+ * {@link CacheKey.withoutRestoreKeys}.
790
+ */
791
+ readonly restoreDepths: Schema.optionalKey<Schema.$Array<Schema.Number>>;
688
792
  }>, {}>;
689
793
  /**
690
794
  * A GitHub Actions cache key and the restore-key ladder that goes with it.
@@ -697,6 +801,12 @@ declare const CacheKey_base: Schema.Class<CacheKey, Schema.Struct<{
697
801
  * the primary key and its fallbacks impossible to drift apart, which is the
698
802
  * failure that produces a cache that never hits and never says why.
699
803
  *
804
+ * By default every prefix is a rung. When some segments must never be dropped
805
+ * — a rung without a version digest resurrects stale cross-version caches —
806
+ * give the key an explicit ladder policy with
807
+ * {@link CacheKey.withRestoreDepths}, or none at all with
808
+ * {@link CacheKey.withoutRestoreKeys}, instead of bypassing the typed key.
809
+ *
700
810
  * @example
701
811
  * ```ts
702
812
  * import { CacheKey } from "@effected/github-actions";
@@ -715,12 +825,69 @@ declare class CacheKey extends CacheKey_base {
715
825
  * The restore keys, most specific first.
716
826
  *
717
827
  * @remarks
718
- * Each rung drops the last segment and keeps the trailing separator, so a
719
- * three-segment key falls back to two prefixes and a one-segment key falls
720
- * back to nothing — there is no rung that would match every cache in the
721
- * repository.
828
+ * By default each rung drops the last segment and keeps the trailing
829
+ * separator, so a three-segment key falls back to two prefixes and a
830
+ * one-segment key falls back to nothing — there is no rung that would match
831
+ * every cache in the repository.
832
+ *
833
+ * **The default ladder is every prefix**, which is wrong for a key whose
834
+ * later segments must never be dropped alone: a
835
+ * `platform-arch-versionHash-branchHash-lockfileHash` key derives rungs
836
+ * without the version digest, and those resurrect stale cross-version
837
+ * caches. A key built with {@link CacheKey.withRestoreDepths} carries its
838
+ * own ladder instead, and this getter answers exactly that ladder — so
839
+ * `ActionCache.restore` picks the policy up through the same typed-key
840
+ * path, no hand-built restore-key list required. An **empty** policy
841
+ * ({@link CacheKey.withoutRestoreKeys}) answers no rungs at all: only
842
+ * *absence* selects the default ladder, so "none" is never mistaken for
843
+ * "every prefix".
722
844
  */
723
845
  get restoreKeys(): ReadonlyArray<string>;
846
+ /**
847
+ * A copy of this key carrying an explicit restore-key ladder policy.
848
+ *
849
+ * @remarks
850
+ * Each depth is the number of **leading segments** the rung keeps, and the
851
+ * rungs are emitted in exactly the order given — GitHub tries restore keys
852
+ * in order, so the order IS the policy. Descending depths (most specific
853
+ * first) are what a fallback ladder almost always wants; that convention is
854
+ * recommended rather than enforced, because rung ordering is the consumer's
855
+ * policy where commas, newlines and the length limit are the runner's
856
+ * protocol. A depth outside `1..segments.length - 1` is refused at
857
+ * construction: `0` would match every cache in the repository, and
858
+ * `segments.length` would just repeat the primary key.
859
+ *
860
+ * @example
861
+ * ```ts
862
+ * import { CacheKey } from "@effected/github-actions";
863
+ *
864
+ * const key = CacheKey.of("Linux", "X64", "v1hash", "main", "lockhash").withRestoreDepths([4, 3]);
865
+ * key.restoreKeys; // ["Linux-X64-v1hash-main-", "Linux-X64-v1hash-"]
866
+ * ```
867
+ */
868
+ withRestoreDepths(depths: readonly [number, ...Array<number>]): CacheKey;
869
+ /**
870
+ * A copy of this key that restores from its **exact key only** — no ladder.
871
+ *
872
+ * @remarks
873
+ * The third point in the policy space, and the one the typed key had no
874
+ * spelling for: absence of a policy means the default every-prefix ladder,
875
+ * so exact-match-only restores (a cache-bust mode, where a stale partial
876
+ * hit is worse than a cold start) previously forced the caller back to the
877
+ * string form. This is the same `restoreDepths` field carrying **zero
878
+ * rungs** — an honest value, not a sentinel: `ActionCache.restore` sends no
879
+ * `restore_keys` at all, so the backend answers this key or a miss.
880
+ *
881
+ * @example
882
+ * ```ts
883
+ * import { CacheKey } from "@effected/github-actions";
884
+ *
885
+ * const key = CacheKey.of("Linux", "pnpm-store", "abc123").withoutRestoreKeys();
886
+ * key.key; // "Linux-pnpm-store-abc123"
887
+ * key.restoreKeys; // []
888
+ * ```
889
+ */
890
+ withoutRestoreKeys(): CacheKey;
724
891
  /** Build a key from its segments. */
725
892
  static of(...segments: readonly [string, ...ReadonlyArray<string>]): CacheKey;
726
893
  /**
@@ -866,9 +1033,31 @@ declare class ActionCacheError extends ActionCacheError_base {
866
1033
  */
867
1034
  interface ActionCacheShape {
868
1035
  /**
869
- * Archive `paths` and store them under `key`.
1036
+ * Archive the files and directories matching `paths` and store them under
1037
+ * `key`.
870
1038
  *
871
1039
  * @remarks
1040
+ * `paths` is a list of glob **patterns**, resolved with `actions/cache`
1041
+ * parity before `tar` ever sees them: a pattern may match directories (a
1042
+ * matched directory is archived recursively, never expanded into its
1043
+ * descendants), a pattern that matches nothing — including a literal path
1044
+ * that is not on disk — is silently dropped, and a list that resolves to
1045
+ * nothing at all fails typed rather than reserving an entry no archive will
1046
+ * ever back. A leading `~` expands to the caller's home directory, relative
1047
+ * patterns root at `GITHUB_WORKSPACE` (falling back to the working
1048
+ * directory), and a leading `!` marks an exclusion.
1049
+ *
1050
+ * The entry **version** is a digest of the literal pattern list, exactly as
1051
+ * `actions/cache` computes it — resolution feeds `tar` only, never the
1052
+ * version — so {@link ActionCacheShape.restore | restore}, which resolves
1053
+ * nothing, derives the same version from the same list for free.
1054
+ *
1055
+ * One deliberate divergence from the toolkit: where `actions/cache` re-roots
1056
+ * every match relative to the workspace, resolved paths here stay
1057
+ * **absolute** and are archived with `tar -P`, so a restore puts every file
1058
+ * back where it came from regardless of the restoring step's working
1059
+ * directory.
1060
+ *
872
1061
  * A key that already exists is a **success**: cache entries are immutable, so
873
1062
  * another job having written it first is the outcome the caller wanted.
874
1063
  */
@@ -881,6 +1070,11 @@ interface ActionCacheShape {
881
1070
  * `Option.none()` is a miss, not a failure — a cold cache is the normal state
882
1071
  * of a new branch. Passing a {@link CacheKey} supplies its restore-key ladder
883
1072
  * automatically; passing a `string` and no ladder means "this key or nothing".
1073
+ *
1074
+ * `paths` only derives the entry version and MUST be the same literal list
1075
+ * the save used — no glob resolution happens here, because none is needed:
1076
+ * extraction consumes no path list (the archive already names its contents),
1077
+ * and `actions/cache` likewise hashes the un-resolved list on both sides.
884
1078
  */
885
1079
  readonly restore: (paths: ReadonlyArray<string>, key: string | CacheKey, restoreKeys?: ReadonlyArray<string>) => Effect.Effect<Option.Option<string>, ActionCacheError>;
886
1080
  }
@@ -987,6 +1181,21 @@ declare class ActionCache extends ActionCache_base {
987
1181
  */
988
1182
  declare class ActionInput {
989
1183
  private constructor();
1184
+ /**
1185
+ * The exact environment variable the runner publishes an input under.
1186
+ *
1187
+ * @remarks
1188
+ * `variable("biome-version")` is `"INPUT_BIOME-VERSION"` — GitHub
1189
+ * uppercases, replaces **spaces** with underscores, and leaves every other
1190
+ * character (**dashes included**) alone. This is the same derivation every
1191
+ * accessor on this class resolves through, exported so a test that must
1192
+ * spell a runner variable can derive it instead of hand-writing it: a
1193
+ * hand-written `INPUT_BIOME_VERSION` reads as absent on a real runner, the
1194
+ * default silently applies, and the suite goes green against the wrong
1195
+ * value. Prefer keying the test environment by input name (see
1196
+ * {@link ActionInput.provider}) so no variable is spelled at all.
1197
+ */
1198
+ static readonly variable: (name: string) => string;
990
1199
  /**
991
1200
  * A required string input.
992
1201
  *
@@ -1096,14 +1305,37 @@ declare class ActionInput {
1096
1305
  */
1097
1306
  static schema<A, I>(name: string, schema: Schema.Codec<A, I>): Config.Config<A>;
1098
1307
  /**
1099
- * A `ConfigProvider` over a record of runner variables.
1308
+ * A `ConfigProvider` over a record of runner variables **or input names**.
1100
1309
  *
1101
1310
  * @remarks
1102
- * Subsumes the source package's free-standing `ActionsConfigProvider`, whose
1103
- * three behaviors are preserved exactly: the path is joined with `_`, spaces
1104
- * become underscores and the whole is uppercased; and an **empty string
1105
- * reads as absent**, because the runner sets unsupplied inputs to `""` and
1106
- * treating that as present would make every optional input look supplied.
1311
+ * The record dual-accepts, and the split is the `INPUT_` prefix:
1312
+ *
1313
+ * - A key already spelled as a runner variable (`INPUT_BIOME-VERSION`,
1314
+ * `PLAIN_VAR`) is read verbatim, exactly as before.
1315
+ * - Any other key is treated as an **input name**, `with:`-block style —
1316
+ * `{ "biome-version": "2.3.14" }` — and serves the variable this module
1317
+ * derives for it (`ActionInput.variable`). This is the spelling to
1318
+ * prefer in tests: a hand-written `INPUT_BIOME_VERSION` (underscore where
1319
+ * the runner keeps the dash) reads as absent and the default silently
1320
+ * applies, and the input-name form makes that class of bug
1321
+ * unrepresentable — the mangling never leaves this module.
1322
+ *
1323
+ * When both spellings of the same input are present and non-empty, the
1324
+ * explicit `INPUT_`-spelled entry wins.
1325
+ *
1326
+ * A **flat single-segment** lookup additionally tries the `INPUT_`
1327
+ * derivation of the name first, mirroring the order the production runtime
1328
+ * installs ({@link ActionInput.providerOver} via `layerDefault`) — so a
1329
+ * bare `Config.string("biome-version")` resolves under this provider
1330
+ * exactly as it does inside `Action.run`. Nested and numeric paths pass
1331
+ * through as the joined spelling only, as ever.
1332
+ *
1333
+ * The source package's `ActionsConfigProvider` behaviors are otherwise
1334
+ * preserved exactly: the path is joined with `_`, spaces become underscores
1335
+ * and the whole is uppercased; and an **empty string reads as absent**,
1336
+ * because the runner sets unsupplied inputs to `""` and treating that as
1337
+ * present would make every optional input look supplied. That rule applies
1338
+ * to input-name entries too: `{ "flag": "" }` is an unsupplied input.
1107
1339
  *
1108
1340
  * Taking the environment as an argument rather than reading `process.env`
1109
1341
  * is what makes inputs testable without mutating the test process.
@@ -1176,6 +1408,288 @@ declare class ActionInput {
1176
1408
  static readonly layerDefault: Layer.Layer<never>;
1177
1409
  }
1178
1410
  //#endregion
1411
+ //#region src/OidcTokenIssuer.d.ts
1412
+ declare const OidcTokenError_base: Schema.Class<OidcTokenError, Schema.TaggedStruct<"OidcTokenError", {
1413
+ /**
1414
+ * `unavailable` — the runner did not publish the token-service variables,
1415
+ * which almost always means the workflow is missing `permissions: id-token:
1416
+ * write`. `requestFailed` — the token service could not be reached or refused
1417
+ * the request. `malformedResponse` — it answered with something that is not a
1418
+ * token envelope. `malformedToken` — the token is not a decodable JWT.
1419
+ * `missingClaims` — it decoded, but without the claims a provenance statement
1420
+ * needs.
1421
+ */
1422
+ readonly reason: Schema.Literals<readonly ["unavailable", "requestFailed", "malformedResponse", "malformedToken", "missingClaims"]>;
1423
+ /** What was wrong, in one line. */
1424
+ readonly detail: Schema.optionalKey<Schema.String>;
1425
+ /** The HTTP status, when the token service answered. */
1426
+ readonly status: Schema.optionalKey<Schema.Number>;
1427
+ /** The underlying failure, preserved structurally. */
1428
+ readonly cause: Schema.optionalKey<Schema.Defect>;
1429
+ }>, import("effect/Cause").YieldableError>;
1430
+ /**
1431
+ * Raised when an OIDC token cannot be issued or read.
1432
+ *
1433
+ * @public
1434
+ */
1435
+ declare class OidcTokenError extends OidcTokenError_base {
1436
+ get message(): string;
1437
+ }
1438
+ declare const OidcClaims_base: Schema.Class<OidcClaims, Schema.Struct<{
1439
+ /** The issuer, e.g. `https://token.actions.githubusercontent.com`. */
1440
+ readonly iss: Schema.String;
1441
+ /** The full ref the workflow ran on. */
1442
+ readonly ref: Schema.String;
1443
+ /** The commit. */
1444
+ readonly sha: Schema.String;
1445
+ /** `owner/repo`. */
1446
+ readonly repository: Schema.String;
1447
+ /** The event that triggered the run. */
1448
+ readonly event_name: Schema.String;
1449
+ /** The reusable-workflow ref, which is what a verifier pins against. */
1450
+ readonly job_workflow_ref: Schema.String;
1451
+ /** The calling workflow's ref. */
1452
+ readonly workflow_ref: Schema.String;
1453
+ /** The numeric repository id, as a string. */
1454
+ readonly repository_id: Schema.String;
1455
+ /** The numeric owner id, as a string. */
1456
+ readonly repository_owner_id: Schema.String;
1457
+ /** `github-hosted` or `self-hosted`. */
1458
+ readonly runner_environment: Schema.String;
1459
+ /** The run id, as a string. */
1460
+ readonly run_id: Schema.String;
1461
+ /** The run attempt, as a string. */
1462
+ readonly run_attempt: Schema.String;
1463
+ }>, {}>;
1464
+ /**
1465
+ * The claims a GitHub Actions OIDC token carries about the workflow that ran.
1466
+ *
1467
+ * @remarks
1468
+ * The field names are GitHub's own JWT claim names, kept verbatim rather than
1469
+ * translated to the kit's casing. They are an **external vocabulary** — the
1470
+ * same strings that appear in a SLSA provenance predicate and in every
1471
+ * published example of an Actions OIDC policy — and renaming them would put a
1472
+ * translation layer between a consumer and the specification they are reading.
1473
+ *
1474
+ * @public
1475
+ */
1476
+ declare class OidcClaims extends OidcClaims_base {}
1477
+ /**
1478
+ * The {@link OidcTokenIssuer} service shape.
1479
+ *
1480
+ * @public
1481
+ */
1482
+ interface OidcTokenIssuerShape {
1483
+ /**
1484
+ * Request an ID token, optionally bound to an audience.
1485
+ *
1486
+ * @remarks
1487
+ * Omitting the audience sends no `audience` parameter at all, matching
1488
+ * `@actions/core.getIDToken` — the runner's default audience is not the empty
1489
+ * string.
1490
+ */
1491
+ readonly token: (audience?: string) => Effect.Effect<Redacted.Redacted<string>, OidcTokenError>;
1492
+ /**
1493
+ * The token's claims, decoded.
1494
+ *
1495
+ * @remarks
1496
+ * **The signature is deliberately not verified**, for three reasons that are
1497
+ * recorded so nobody "fixes" it:
1498
+ *
1499
+ * 1. The token comes from the runner's own token-service endpoint over TLS.
1500
+ * The transport is the trust boundary, and the process asking for the
1501
+ * token is the process that received it.
1502
+ * 2. The claims populate a provenance predicate, not a trust decision.
1503
+ * Nothing branches on them for authorization; they are recorded as
1504
+ * attested facts about the workflow that ran.
1505
+ * 3. Verifying would need a JWKS fetch, which turns a decode into a network
1506
+ * call — untestable without a fixture server, and dependent on GitHub's
1507
+ * key endpoint being reachable at attestation time.
1508
+ *
1509
+ * A consumer that needs a *verified* token needs a different operation with a
1510
+ * different name and a different error channel, not an option on this one.
1511
+ *
1512
+ * Claims are on the surface rather than left to the call site because that is
1513
+ * what makes the provenance path reachable in a test: a double built with
1514
+ * {@link OidcTokenIssuer.layerFor} answers with real, decodable claims, where
1515
+ * the synthetic non-JWT its predecessor returned made every consumer's
1516
+ * `decodeJwtClaims` yield nothing and silently skipped the path under test.
1517
+ */
1518
+ readonly claims: (audience?: string) => Effect.Effect<OidcClaims, OidcTokenError>;
1519
+ }
1520
+ declare const OidcTokenIssuer_base: Context.ServiceClass<OidcTokenIssuer, "@effected/github-actions/OidcTokenIssuer", OidcTokenIssuerShape>;
1521
+ /**
1522
+ * The runner's OIDC token service.
1523
+ *
1524
+ * @remarks
1525
+ * Lives here rather than with attestation because it reads
1526
+ * `ACTIONS_ID_TOKEN_REQUEST_TOKEN` and `ACTIONS_ID_TOKEN_REQUEST_URL`, which
1527
+ * exist only when a workflow declares `permissions: id-token: write` — that is
1528
+ * a fact about the runner, not about signing.
1529
+ *
1530
+ * @example
1531
+ * ```ts
1532
+ * import { OidcTokenIssuer } from "@effected/github-actions";
1533
+ * import { Effect } from "effect";
1534
+ *
1535
+ * const program = Effect.gen(function* () {
1536
+ * const issuer = yield* OidcTokenIssuer;
1537
+ * const claims = yield* issuer.claims("sigstore");
1538
+ * return claims.job_workflow_ref;
1539
+ * });
1540
+ * ```
1541
+ *
1542
+ * @public
1543
+ */
1544
+ declare class OidcTokenIssuer extends OidcTokenIssuer_base {
1545
+ static readonly layer: Layer.Layer<OidcTokenIssuer, never, ActionEnvironment | HttpClient.HttpClient>;
1546
+ /**
1547
+ * An **unsigned** JWT carrying these claims.
1548
+ *
1549
+ * @remarks
1550
+ * For building test doubles, and nothing else: the signature segment is a
1551
+ * placeholder, so this token would fail any verifier. It exists because the
1552
+ * alternative — a double returning a synthetic non-JWT — is what made the
1553
+ * provenance path structurally unreachable in the source package, drawing
1554
+ * four separate apologetic comments from one consumer.
1555
+ */
1556
+ static readonly unsignedTokenFor: (claims: OidcClaims) => Redacted.Redacted<string>;
1557
+ /** A test double. Unstubbed members die rather than answering with a non-token. */
1558
+ static readonly makeTest: (overrides?: Partial<OidcTokenIssuerShape>) => OidcTokenIssuerShape;
1559
+ /** {@link OidcTokenIssuer.makeTest} behind `Layer.succeed`. */
1560
+ static readonly layerTest: (overrides?: Partial<OidcTokenIssuerShape>) => Layer.Layer<OidcTokenIssuer>;
1561
+ /**
1562
+ * A double that answers with these claims, consistently on both members.
1563
+ *
1564
+ * @remarks
1565
+ * `token()` returns a real decodable JWT built from the same claims
1566
+ * `claims()` returns, so a consumer that decodes the token itself and a
1567
+ * consumer that asks the service both see the same thing. A double whose two
1568
+ * members can disagree is how a test proves a path works while production
1569
+ * takes the other one.
1570
+ */
1571
+ static readonly layerFor: (claims: OidcClaims) => Layer.Layer<OidcTokenIssuer>;
1572
+ }
1573
+ //#endregion
1574
+ //#region src/ActionsIdentityToken.d.ts
1575
+ /**
1576
+ * The Actions side of `@effected/sbom`'s inverted OIDC contract.
1577
+ *
1578
+ * @remarks
1579
+ * `@effected/sbom` declares `IdentityToken` — one method, one audience,
1580
+ * one redacted token — precisely so that signing does not drag the Actions
1581
+ * runtime into every consumer that only wants to emit an SBOM. This is the
1582
+ * layer that closes the inversion from the other side: it serves that contract
1583
+ * from the runner's own OIDC token service ({@link OidcTokenIssuer}), the same
1584
+ * arrangement as `@effected/workspaces` implementing `@effected/commands`'
1585
+ * `LocalExec`.
1586
+ *
1587
+ * The audience is forwarded verbatim. `SigstoreSigner` asks its identity
1588
+ * contract for the `sigstore` audience itself (`SIGSTORE_OIDC_AUDIENCE` is the
1589
+ * signing protocol's requirement, owned there), so nothing here — and nothing
1590
+ * in a consumer — needs to spell it.
1591
+ *
1592
+ * An `OidcTokenError` is rewrapped as `IdentityTokenError` with the
1593
+ * original preserved structurally in `cause`, so a refused exchange still says
1594
+ * which audience it was minting for **and** why the runner declined —
1595
+ * `reason: "unavailable"` almost always means the workflow is missing
1596
+ * `permissions: id-token: write`.
1597
+ *
1598
+ * @example
1599
+ * ```ts
1600
+ * import { ActionsIdentityToken, OidcTokenIssuer } from "@effected/github-actions";
1601
+ * import { SigstoreSigner } from "@effected/sbom";
1602
+ * import { Layer } from "effect";
1603
+ *
1604
+ * const signing = SigstoreSigner.layer.pipe(
1605
+ * Layer.provide(ActionsIdentityToken.layer),
1606
+ * Layer.provide(OidcTokenIssuer.layer),
1607
+ * );
1608
+ * ```
1609
+ *
1610
+ * @public
1611
+ */
1612
+ declare class ActionsIdentityToken {
1613
+ private constructor();
1614
+ /**
1615
+ * `IdentityToken` served by the runner's OIDC token service.
1616
+ *
1617
+ * @remarks
1618
+ * Requires {@link OidcTokenIssuer} rather than composing
1619
+ * `OidcTokenIssuer.layer` in, so an action that already wired the issuer —
1620
+ * every action using `ActionRuntime.layer` has its requirements at hand —
1621
+ * does not construct a second one.
1622
+ */
1623
+ static readonly layer: Layer.Layer<IdentityToken, never, OidcTokenIssuer>;
1624
+ }
1625
+ //#endregion
1626
+ //#region src/ActionsProvenance.d.ts
1627
+ /**
1628
+ * The Actions side of `@effected/sbom`'s SLSA provenance constructor.
1629
+ *
1630
+ * @remarks
1631
+ * `SlsaProvenance.forGitHubWorkflow` is total and does the real work, but it
1632
+ * takes a camelCase input record — and the only input anyone inside a workflow
1633
+ * actually holds is the runner's own OIDC claims, spelled in GitHub's
1634
+ * snake_case claim names. The rename between the two is eleven all-string
1635
+ * fields, so transposing `repository_id` and `repository_owner_id` compiles,
1636
+ * passes every type check, and produces a validly signed attestation asserting
1637
+ * the **wrong** provenance. This module owns that rename once, on the same
1638
+ * `github-actions` ↔ `sbom` seam as {@link ActionsIdentityToken}: `sbom` must
1639
+ * not depend on the Actions runtime, so the adapter lives here, beside the
1640
+ * services it reads.
1641
+ *
1642
+ * The construct ends at the predicate. Assembling the in-toto statement,
1643
+ * signing it and uploading the bundle stay with the consumer — that pipeline
1644
+ * is deliberately consumer glue (see `@effected/sbom`'s `SigstoreSigner` and
1645
+ * `@effected/github`'s `Attestation.upload`).
1646
+ *
1647
+ * @example
1648
+ * ```ts
1649
+ * import { ActionsProvenance } from "@effected/github-actions";
1650
+ * import { Effect } from "effect";
1651
+ *
1652
+ * const program = Effect.gen(function* () {
1653
+ * const provenance = yield* ActionsProvenance.capture();
1654
+ * return provenance.runDetails.builder.id;
1655
+ * });
1656
+ * ```
1657
+ *
1658
+ * @public
1659
+ */
1660
+ declare class ActionsProvenance {
1661
+ private constructor();
1662
+ /**
1663
+ * The current run's SLSA provenance, built from the runner's OIDC claims.
1664
+ *
1665
+ * @remarks
1666
+ * Every field `GitHubWorkflowProvenance` declares is populated from
1667
+ * {@link OidcClaims}, except `serverUrl`, which is read from
1668
+ * `GITHUB_SERVER_URL` through {@link ActionEnvironment}'s `getOptional`
1669
+ * with `https://github.com` as the default — **not** through the
1670
+ * `GitHubContext` projection. The projection fails typed when a `GITHUB_*`
1671
+ * variable is missing, and a missing server URL is not a failure: it has a
1672
+ * correct default. GHES consumers set the variable, github.com consumers
1673
+ * never should have to, and neither should think about it. (Upstream
1674
+ * `@actions/attest` reads the same variable with no default and writes the
1675
+ * literal string `undefined` into every URL it builds — the hazard
1676
+ * `GitHubWorkflowProvenance.serverUrl` being a required field exists to
1677
+ * prevent.)
1678
+ *
1679
+ * The audience is forwarded to {@link OidcTokenIssuerShape.claims}
1680
+ * verbatim; the claims a provenance reads do not depend on it, so omitting
1681
+ * it is fine and spends no extra token mint.
1682
+ *
1683
+ * An {@link OidcTokenError} passes through **untouched** — not caught, not
1684
+ * defaulted, not wrapped. Whether attestation is mandatory or best-effort
1685
+ * is the consumer's policy: an action that must not publish unattested
1686
+ * lets the error propagate, and one that publishes anyway catches it at
1687
+ * its own boundary. `reason: "unavailable"` almost always means the
1688
+ * workflow is missing `permissions: id-token: write`.
1689
+ */
1690
+ static readonly capture: (audience?: string) => Effect.Effect<SlsaProvenance, OidcTokenError, OidcTokenIssuer | ActionEnvironment>;
1691
+ }
1692
+ //#endregion
1179
1693
  //#region src/Artifact.d.ts
1180
1694
  declare const ArtifactError_base: Schema.Class<ArtifactError, Schema.TaggedStruct<"ArtifactError", {
1181
1695
  /**
@@ -1625,9 +2139,292 @@ declare class GitHubCacheBlobStore {
1625
2139
  static readonly layerWith: (transfer: DataBlobTransfer) => Layer.Layer<BlobStore, never, HttpClient.HttpClient | ActionEnvironment>;
1626
2140
  }
1627
2141
  //#endregion
1628
- //#region src/DetachedProcess.d.ts
1629
- declare const DetachedProcessError_base: Schema.Class<DetachedProcessError, Schema.TaggedStruct<"DetachedProcessError", {
1630
- /**
2142
+ //#region src/CheckDocument.d.ts
2143
+ declare const CheckReport_base: Schema.Class<CheckReport, Schema.Struct<{
2144
+ /** Where the check is now. The last reported state is the authoritative one. */
2145
+ readonly state: Schema.Literals<readonly ["running", "pass", "fail", "warn", "user_interaction_required", "skipped", "timeout"]>;
2146
+ /** Display name, when it differs from the registry key. */
2147
+ readonly title: Schema.optionalKey<Schema.String>;
2148
+ /** A one-line verdict, e.g. `in progress...` or `3 packages ready`. */
2149
+ readonly outcome: Schema.optionalKey<Schema.String>;
2150
+ /** A pre-rendered markdown block of per-check detail. */
2151
+ readonly detail: Schema.optionalKey<Schema.String>;
2152
+ /** Where the check's name should link. */
2153
+ readonly url: Schema.optionalKey<Schema.String>;
2154
+ }>, {}>;
2155
+ /**
2156
+ * What a run knows about one of its checks: the authoritative state, and the
2157
+ * presentation facts a renderer projects into the document.
2158
+ *
2159
+ * @remarks
2160
+ * Only `state` is required. The rest exist because a status table needs them:
2161
+ * `title` when the display name differs from the registry key, `outcome` for
2162
+ * the one-line verdict cell, `url` for linking the check's name, and `detail`
2163
+ * for the per-check block a body region carries. A later report for the same
2164
+ * key **replaces** the whole entry — last write wins, per field and per check.
2165
+ *
2166
+ * @public
2167
+ */
2168
+ declare class CheckReport extends CheckReport_base {}
2169
+ declare const CheckDocumentError_base: Schema.Class<CheckDocumentError, Schema.TaggedStruct<"CheckDocumentError", {
2170
+ /** Which half failed: regenerating the document, or writing it. */
2171
+ readonly kind: Schema.Literals<readonly ["render", "sink"]>;
2172
+ /** The underlying failure. */
2173
+ readonly cause: Schema.optionalKey<Schema.Defect>;
2174
+ }>, import("effect/Cause").YieldableError>;
2175
+ /**
2176
+ * Raised when the check document cannot be regenerated or written.
2177
+ *
2178
+ * @remarks
2179
+ * `render` — projecting the registry onto the document failed, almost always
2180
+ * because a reported `detail` contains a line the region scanner would read
2181
+ * as a marker; the underlying `ManagedDocumentError` rides in `cause`.
2182
+ * `sink` — the consumer's write effect failed; its error rides in `cause`.
2183
+ * Callers of `flush` branch on `kind`; the background reconciler never fails
2184
+ * a fiber with this — it logs and retries on the next report.
2185
+ *
2186
+ * @public
2187
+ */
2188
+ declare class CheckDocumentError extends CheckDocumentError_base {
2189
+ get message(): string;
2190
+ }
2191
+ /**
2192
+ * How a rendered document leaves the process: the narrow sink contract.
2193
+ *
2194
+ * @remarks
2195
+ * One rendered document in, one write effect out — nothing else. That is
2196
+ * what lets the sticky PR comment (`PullRequestComment.upsert`), the PR
2197
+ * description (`PATCH` on the pull request) and a test's recording `Ref` all
2198
+ * fit the same reconciler. The success value is ignored; a failure surfaces
2199
+ * as {@link CheckDocumentError} with `kind: "sink"`. Provide any services the
2200
+ * write needs when constructing the sink — the reconciler runs it as given.
2201
+ *
2202
+ * @public
2203
+ */
2204
+ type CheckDocumentSink = (rendered: string) => Effect.Effect<unknown, unknown>;
2205
+ /**
2206
+ * Options for {@link CheckDocument.layer}.
2207
+ *
2208
+ * @public
2209
+ */
2210
+ interface CheckDocumentOptions {
2211
+ /** The managed document's namespace, e.g. your action's name. */
2212
+ readonly namespace: string;
2213
+ /** Which document, within that namespace. */
2214
+ readonly key: string;
2215
+ /**
2216
+ * The document text to reconcile against initially — a PR body fetched
2217
+ * once at startup, so human content outside the managed regions survives.
2218
+ * Defaults to empty: a fresh document.
2219
+ */
2220
+ readonly initial?: string | undefined;
2221
+ /**
2222
+ * The pure projection from registry state to region entries.
2223
+ *
2224
+ * @remarks
2225
+ * Called with the full snapshot on every reconcile pass; returns the
2226
+ * regions to replace, keyed however the consumer's document is laid out —
2227
+ * typically one region per check plus a shared status table. It must be a
2228
+ * pure function of the snapshot: rendering the same state twice must
2229
+ * produce the same entries, because byte-identical output is what
2230
+ * suppresses the write.
2231
+ */
2232
+ readonly render: (checks: ReadonlyMap<string, CheckReport>) => ReadonlyArray<readonly [key: string, content: string]>;
2233
+ /** Where the rendered document goes. */
2234
+ readonly sink: CheckDocumentSink;
2235
+ /**
2236
+ * Debounce parameters: `quiet` is the trailing settle window, `maxWait`
2237
+ * the staleness bound under a steady stream of reports. Defaults: 500
2238
+ * milliseconds and 3 seconds.
2239
+ */
2240
+ readonly debounce?: {
2241
+ readonly quiet?: Duration.Input | undefined;
2242
+ readonly maxWait?: Duration.Input | undefined;
2243
+ } | undefined;
2244
+ /**
2245
+ * How long one sink write may run before the pass fails with
2246
+ * `kind: "sink"`. Default: 30 seconds.
2247
+ *
2248
+ * @remarks
2249
+ * The reconciler serializes passes behind one permit, and the finalizer's
2250
+ * last flush waits on that same permit — so a sink that never resolves
2251
+ * would otherwise stall every later pass AND scope teardown. The bound
2252
+ * turns a hung write into the ordinary sink failure path: a background
2253
+ * pass logs and retries on the next report, `flush` surfaces the typed
2254
+ * error.
2255
+ */
2256
+ readonly sinkTimeout?: Duration.Input | undefined;
2257
+ }
2258
+ /**
2259
+ * The check-run → document reconciler's surface.
2260
+ *
2261
+ * @public
2262
+ */
2263
+ interface CheckDocumentShape {
2264
+ /**
2265
+ * Record a check's current state and schedule a reconcile.
2266
+ *
2267
+ * @remarks
2268
+ * Never fails and never blocks on the write — the state lands in the
2269
+ * registry, and the debounced background pass projects it. Reporting the
2270
+ * same key again replaces the entry; resolution is **not** terminal, so
2271
+ * `pass` followed by `fail` leaves `fail` even inside one debounce window.
2272
+ */
2273
+ readonly report: (check: string, report: CheckReport) => Effect.Effect<void>;
2274
+ /** The registry as of now, in first-report order. */
2275
+ readonly checks: Effect.Effect<ReadonlyMap<string, CheckReport>>;
2276
+ /**
2277
+ * Reconcile immediately, skipping the debounce.
2278
+ *
2279
+ * @remarks
2280
+ * Rendering unchanged state issues no write. Call it at the end of a run;
2281
+ * the layer's finalizer also runs it, so the last burst of reports lands
2282
+ * even when the scope closes inside the quiet window.
2283
+ */
2284
+ readonly flush: Effect.Effect<void, CheckDocumentError>;
2285
+ }
2286
+ declare const CheckDocument_base: Context.ServiceClass<CheckDocument, "@effected/github-actions/CheckDocument", CheckDocumentShape>;
2287
+ /**
2288
+ * A living document of check state: an in-process registry the run reports
2289
+ * into, debounced onto a marker-delimited document, written through a narrow
2290
+ * sink.
2291
+ *
2292
+ * @remarks
2293
+ * **Push, not pull.** The run owns its checks and knows when they resolve;
2294
+ * nothing here polls GitHub. Consumers call `report` as states change, and a
2295
+ * background fiber projects the registry onto a {@link ManagedDocument} —
2296
+ * regions replaced from current state, so a check that resolves many times
2297
+ * updates its region in place instead of appending.
2298
+ *
2299
+ * The debounce is **trailing with a max-wait, never leading**: a burst of
2300
+ * reports coalesces into one write carrying the *final* state, and a steady
2301
+ * stream still surfaces progress every `maxWait`. A pass that renders
2302
+ * byte-identical text issues no write at all.
2303
+ *
2304
+ * `layer` mints fresh state per call — one layer per document. Bind it to a
2305
+ * `const` if two parts of a program must share one registry.
2306
+ *
2307
+ * @example
2308
+ * ```ts
2309
+ * import { CheckDocument, CheckReport, GitHubMarkdown } from "@effected/github-actions";
2310
+ * import { Effect } from "effect";
2311
+ *
2312
+ * const layer = CheckDocument.layer({
2313
+ * namespace: "my-action",
2314
+ * key: "release-validation",
2315
+ * render: (checks) => [
2316
+ * [
2317
+ * "header",
2318
+ * GitHubMarkdown.table(
2319
+ * ["Check", "Outcome"],
2320
+ * [...checks].map(([key, check]) => [key, check.outcome ?? check.state]),
2321
+ * ),
2322
+ * ],
2323
+ * ],
2324
+ * sink: (rendered) => Effect.log(rendered),
2325
+ * });
2326
+ *
2327
+ * const program = Effect.gen(function* () {
2328
+ * const doc = yield* CheckDocument;
2329
+ * yield* doc.report("build", CheckReport.make({ state: "running" }));
2330
+ * yield* doc.report("build", CheckReport.make({ state: "pass", outcome: "clean" }));
2331
+ * yield* doc.flush;
2332
+ * });
2333
+ * ```
2334
+ *
2335
+ * @public
2336
+ */
2337
+ declare class CheckDocument extends CheckDocument_base {
2338
+ /**
2339
+ * The reconciler over one document.
2340
+ *
2341
+ * @remarks
2342
+ * Forks the debounce fiber into the layer's scope and registers a
2343
+ * finalizer that flushes once more on the way out, so the scope closing
2344
+ * mid-window cannot strand the final state. A background pass that fails
2345
+ * logs a structured warning and leaves the registry intact — the next
2346
+ * report retries it; only `flush` surfaces the typed error.
2347
+ */
2348
+ static layer(options: CheckDocumentOptions): Layer.Layer<CheckDocument>;
2349
+ /** An inert double; unstubbed members die naming themselves. */
2350
+ static readonly makeTest: (overrides?: Partial<CheckDocumentShape>) => CheckDocumentShape;
2351
+ /** {@link CheckDocument.makeTest} behind a `Layer`. */
2352
+ static readonly layerTest: (overrides?: Partial<CheckDocumentShape>) => Layer.Layer<CheckDocument>;
2353
+ }
2354
+ //#endregion
2355
+ //#region src/CheckState.d.ts
2356
+ /**
2357
+ * The kit's check-state vocabulary.
2358
+ *
2359
+ * @remarks
2360
+ * Deliberately **wider than GitHub's own check-run conclusions**: `running` is
2361
+ * a first-class state rather than the absence of a conclusion, and
2362
+ * `user_interaction_required` names a release pipeline waiting on a human. A
2363
+ * check's lifecycle is a *sequence* of these states, and only the last one is
2364
+ * authoritative — resolution is not a terminal transition, so a check may run
2365
+ * `running`, `pass`, `running`, `fail` in order, and that means `fail`.
2366
+ *
2367
+ * Every state projects onto GitHub's check-run wire vocabulary through
2368
+ * {@link projectCheckState}. GitHub's `cancelled` conclusion has no
2369
+ * counterpart here on purpose: cancellation is something the runner does *to*
2370
+ * a run, not a state a pipeline reports about its own checks.
2371
+ *
2372
+ * @public
2373
+ */
2374
+ declare const CheckState: Schema.Literals<readonly ["running", "pass", "fail", "warn", "user_interaction_required", "skipped", "timeout"]>;
2375
+ /**
2376
+ * The type of `CheckState`.
2377
+ *
2378
+ * @public
2379
+ */
2380
+ type CheckState = typeof CheckState.Type;
2381
+ /**
2382
+ * The check-run conclusions the kit vocabulary can produce.
2383
+ *
2384
+ * @remarks
2385
+ * A subset of GitHub's conclusion set, spelled structurally so a pure
2386
+ * vocabulary module does not put `@effected/github` — and through it the
2387
+ * octokit runtime — on its import graph. A structural assertion in the test
2388
+ * suite pins these literals against `CheckConclusion` in `@effected/github`,
2389
+ * so drift between the two spellings fails a test rather than a consumer.
2390
+ *
2391
+ * @public
2392
+ */
2393
+ type CheckRunConclusion = "success" | "failure" | "neutral" | "skipped" | "timed_out" | "action_required";
2394
+ /**
2395
+ * A `CheckState` on GitHub's check-run wire: a status, and a conclusion
2396
+ * exactly when the status is `completed`.
2397
+ *
2398
+ * @remarks
2399
+ * A discriminated union rather than two optional fields, because the wire
2400
+ * protocol's own invariant is conditional: an in-progress check run *has* no
2401
+ * conclusion, and a completed one always does.
2402
+ *
2403
+ * @public
2404
+ */
2405
+ type CheckRunProjection = {
2406
+ readonly status: "in_progress";
2407
+ } | {
2408
+ readonly status: "completed";
2409
+ readonly conclusion: CheckRunConclusion;
2410
+ };
2411
+ /**
2412
+ * Project a kit check state onto GitHub's check-run wire vocabulary.
2413
+ *
2414
+ * @remarks
2415
+ * The canonical mapping: `running` is `in_progress`; everything else is
2416
+ * `completed` with `pass → success`, `fail → failure`, `warn → neutral`
2417
+ * (GitHub has no warning conclusion, and `neutral` is its non-failing,
2418
+ * non-success verdict), `user_interaction_required → action_required`,
2419
+ * `skipped → skipped` and `timeout → timed_out`.
2420
+ *
2421
+ * @public
2422
+ */
2423
+ declare const projectCheckState: (state: CheckState) => CheckRunProjection;
2424
+ //#endregion
2425
+ //#region src/DetachedProcess.d.ts
2426
+ declare const DetachedProcessError_base: Schema.Class<DetachedProcessError, Schema.TaggedStruct<"DetachedProcessError", {
2427
+ /**
1631
2428
  * `logUnavailable` — the log file could not be opened, so there is nowhere
1632
2429
  * for the child's output to go. `spawnFailed` — the child did not start.
1633
2430
  * `invalidPid` — a non-positive pid was handed to {@link DetachedProcess.reap};
@@ -1896,6 +2693,230 @@ declare class DryRun extends DryRun_base {
1896
2693
  static readonly layerTest: (overrides?: Partial<DryRunShape>) => Layer.Layer<DryRun>;
1897
2694
  }
1898
2695
  //#endregion
2696
+ //#region src/GitHubMarkdown.d.ts
2697
+ /**
2698
+ * A heading level GitHub renders, `1` through `6`.
2699
+ *
2700
+ * @public
2701
+ */
2702
+ type GitHubHeadingDepth = 1 | 2 | 3 | 4 | 5 | 6;
2703
+ /**
2704
+ * Options for {@link GitHubMarkdown.list}.
2705
+ *
2706
+ * @public
2707
+ */
2708
+ interface GitHubListOptions {
2709
+ /** Render `1.`-numbered items instead of bullets. */
2710
+ readonly ordered?: boolean | undefined;
2711
+ }
2712
+ /**
2713
+ * The row-schema constraint {@link GitHubMarkdown.tableFor} accepts: any
2714
+ * schema exposing a struct field map — `Schema.Struct` and `Schema.Class`
2715
+ * both qualify.
2716
+ *
2717
+ * @public
2718
+ */
2719
+ type GitHubRowSchema = Schema.Constraint & {
2720
+ readonly fields: Schema.Struct.Fields;
2721
+ };
2722
+ /**
2723
+ * The field keys of a row schema whose column REQUIRES an explicit
2724
+ * `format`: fields whose encoded side is not a string (their codec has no
2725
+ * string projection to borrow), and fields whose encoding needs services
2726
+ * (a sync cell render cannot provide them).
2727
+ *
2728
+ * @public
2729
+ */
2730
+ 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];
2731
+ /**
2732
+ * Per-column configuration for {@link GitHubMarkdown.tableFor}.
2733
+ *
2734
+ * @public
2735
+ */
2736
+ interface GitHubSchemaTableColumn<Value> {
2737
+ /**
2738
+ * The column header, overriding the field schema's `title` annotation
2739
+ * and the field-name fallback. Pre-rendered markdown, like every cell.
2740
+ */
2741
+ readonly header?: string | undefined;
2742
+ /**
2743
+ * Render a cell from the decoded field value instead of encoding it
2744
+ * through the field's codec. Required — by type — for a field whose
2745
+ * encoded side is not a string.
2746
+ */
2747
+ readonly format?: ((value: Value) => string) | undefined;
2748
+ }
2749
+ /**
2750
+ * {@link GitHubSchemaTableColumn} with the `format` obligation discharged —
2751
+ * the entry shape a non-string-encoded field demands.
2752
+ *
2753
+ * @public
2754
+ */
2755
+ interface GitHubSchemaTableFormattedColumn<Value> extends GitHubSchemaTableColumn<Value> {
2756
+ readonly format: (value: Value) => string;
2757
+ }
2758
+ /**
2759
+ * The per-field column map for {@link GitHubMarkdown.tableFor}: entries are
2760
+ * optional except where the field's encoded side is not a string, where the
2761
+ * entry — and its `format` — are required.
2762
+ *
2763
+ * @public
2764
+ */
2765
+ 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>>; };
2766
+ /**
2767
+ * Options for {@link GitHubMarkdown.tableFor}. The whole bag is optional
2768
+ * exactly when every field's encoded side is a string; one field without a
2769
+ * string projection makes `columns` — and the entry carrying its `format` —
2770
+ * required.
2771
+ *
2772
+ * @public
2773
+ */
2774
+ type GitHubSchemaTableOptions<Fields extends Schema.Struct.Fields> = [GitHubSchemaTableFormatRequiredKeys<Fields>] extends [never] ? {
2775
+ readonly columns?: GitHubSchemaTableColumns<Fields> | undefined;
2776
+ } : {
2777
+ readonly columns: GitHubSchemaTableColumns<Fields>;
2778
+ };
2779
+ /**
2780
+ * A renderer minted by {@link GitHubMarkdown.tableFor}: columns are fixed by
2781
+ * the schema, rows are supplied at each render.
2782
+ *
2783
+ * @public
2784
+ */
2785
+ interface GitHubSchemaTable<Row> {
2786
+ /**
2787
+ * Render the current rows as a GFM table. Pure and repeatable — identical
2788
+ * rows produce identical output, so re-rendering from changed state is
2789
+ * just calling it again.
2790
+ */
2791
+ render(rows: ReadonlyArray<Row>): string;
2792
+ }
2793
+ /**
2794
+ * The fluent markdown writer for GitHub surfaces — PR comments, check-run
2795
+ * summaries, job summaries.
2796
+ *
2797
+ * @remarks
2798
+ * Every member takes **pre-rendered markdown** (links, emphasis, emoji — the
2799
+ * caller's fragments pass through verbatim) and returns a `string`, so
2800
+ * compositions read as plain string assembly. What the writer owns is the
2801
+ * *structure*: wherever a fragment could break the construct around it, the
2802
+ * output goes through `@effected/markdown`'s node classes and serializer
2803
+ * rather than string joining. A table cell carrying `>=1 || <2` renders with
2804
+ * its pipes escaped instead of shifting every column after it; a code block
2805
+ * whose content contains a fence gets a longer fence; inline code containing
2806
+ * a backtick gets a wider delimiter; a URL containing spaces or parentheses
2807
+ * is angle-bracketed.
2808
+ *
2809
+ * This module is the package's **only** importer of `@effected/markdown` — a
2810
+ * structural test pins that, on the same terms as the Azure confinement — so
2811
+ * an action that never renders markdown never links the engine.
2812
+ *
2813
+ * @example
2814
+ * ```ts
2815
+ * import { GitHubMarkdown } from "@effected/github-actions";
2816
+ *
2817
+ * const body = [
2818
+ * GitHubMarkdown.heading("Release Validation"),
2819
+ * GitHubMarkdown.table(["Check", "Outcome"], [["build", "✅ passed"]]),
2820
+ * GitHubMarkdown.details("Logs", GitHubMarkdown.codeBlock(log, "text")),
2821
+ * ].join("\n\n");
2822
+ * ```
2823
+ *
2824
+ * @public
2825
+ */
2826
+ declare class GitHubMarkdown {
2827
+ private constructor();
2828
+ /**
2829
+ * A GFM table.
2830
+ *
2831
+ * @remarks
2832
+ * Cells are pre-rendered markdown; the serializer escapes the pipes GFM
2833
+ * would otherwise read as column breaks. Rows shorter than the header are
2834
+ * padded with empty cells rather than shifting.
2835
+ */
2836
+ static table(headers: ReadonlyArray<string>, rows: ReadonlyArray<ReadonlyArray<string>>): string;
2837
+ /**
2838
+ * A GFM table whose columns are defined ONCE by a row schema.
2839
+ *
2840
+ * @remarks
2841
+ * The schema is the single authority for the table's shape: column order
2842
+ * is field declaration order, each header is the field's `title`
2843
+ * annotation (fall back: the property name, overridable per column), and
2844
+ * each cell is the field value's **encoded** string form — a branded or
2845
+ * typed field projects through its own codec, so the vocabulary lives
2846
+ * with the schema instead of being respelled at every call site, and a
2847
+ * row can no longer transpose columns because it is a typed object, not
2848
+ * a positional array.
2849
+ *
2850
+ * A field whose encoded side is not a string has no string projection to
2851
+ * borrow; the type of `options` makes its column's `format` — and
2852
+ * therefore `columns` and `options` themselves — required. An absent or
2853
+ * `undefined` optional field renders as an empty cell without consulting
2854
+ * codec or formatter.
2855
+ *
2856
+ * The renderer is a pure value: `render` maps current rows through
2857
+ * {@link GitHubMarkdown.table}, so escaping is inherited and identical
2858
+ * rows produce identical output. Rows are already-decoded values of the
2859
+ * schema's type, so encoding them is total in practice; a value smuggled
2860
+ * past the types throws the codec's `SchemaError` as a defect — same
2861
+ * posture as the serializer arm above, **not** a silent fallback.
2862
+ *
2863
+ * Only string-keyed fields become columns; symbol keys are not
2864
+ * enumerable table columns.
2865
+ *
2866
+ * @example
2867
+ * ```ts
2868
+ * import { GitHubMarkdown } from "@effected/github-actions";
2869
+ * import { Schema } from "effect";
2870
+ *
2871
+ * const CheckRow = Schema.Struct({
2872
+ * name: Schema.String.annotate({ title: "Check" }),
2873
+ * outcome: Schema.Literals(["passed", "failed"]),
2874
+ * });
2875
+ * const checks = GitHubMarkdown.tableFor(CheckRow);
2876
+ * const body = checks.render([{ name: "build", outcome: "passed" }]);
2877
+ * ```
2878
+ */
2879
+ static tableFor<S extends GitHubRowSchema>(schema: S, ...options: [GitHubSchemaTableFormatRequiredKeys<S["fields"]>] extends [never] ? [options?: GitHubSchemaTableOptions<S["fields"]>] : [options: GitHubSchemaTableOptions<S["fields"]>]): GitHubSchemaTable<S["Type"]>;
2880
+ /** An ATX heading; the level defaults to `2`. */
2881
+ static heading(text: string, level?: GitHubHeadingDepth): string;
2882
+ /** A link. URLs the destination cannot carry raw are angle-bracketed. */
2883
+ static link(text: string, url: string): string;
2884
+ /** Inline code. Content containing backticks widens the delimiter. */
2885
+ static code(text: string): string;
2886
+ /** A fenced code block. Content containing a fence lengthens the fence. */
2887
+ static codeBlock(content: string, language?: string): string;
2888
+ /**
2889
+ * A list of pre-rendered markdown items.
2890
+ *
2891
+ * @remarks
2892
+ * A multi-line item is indented onto continuation lines, so it stays part
2893
+ * of its bullet instead of terminating the list.
2894
+ */
2895
+ static list(items: ReadonlyArray<string>, options?: GitHubListOptions): string;
2896
+ /**
2897
+ * A collapsible `<details>` block.
2898
+ *
2899
+ * @remarks
2900
+ * mdast has no details node, so this is the one construct the writer emits
2901
+ * as raw HTML. The blank lines around `body` are what make GitHub render
2902
+ * the markdown inside it; `summary` is HTML context — markdown is **not**
2903
+ * rendered there, so bold a summary with `<strong>`, not `**`.
2904
+ */
2905
+ static details(summary: string, body: string): string;
2906
+ /**
2907
+ * Pre-rendered markdown, passed through by name.
2908
+ *
2909
+ * @remarks
2910
+ * The identity function, deliberately: every writer member already treats
2911
+ * its fragments as markdown, so `raw` adds no behavior — it adds the
2912
+ * *statement* that a value is already rendered. Use it where a fragment
2913
+ * from elsewhere enters a composition and a reader would otherwise wonder
2914
+ * whether it still needs escaping. It replaces the `Html.make` idiom,
2915
+ * which passes markdown through correctly but announces the wrong intent.
2916
+ */
2917
+ static raw(markdown: string): string;
2918
+ }
2919
+ //#endregion
1899
2920
  //#region src/GitHubToken.d.ts
1900
2921
  declare const GitHubTokenError_base: Schema.Class<GitHubTokenError, Schema.TaggedStruct<"GitHubTokenError", {
1901
2922
  /**
@@ -2087,254 +3108,183 @@ declare class GitHubToken {
2087
3108
  static readonly dispose: (options?: ReadOptions | undefined) => Effect.Effect<void, ActionStateError | import("@effected/github").GitHubAppError, ActionState | GitHubApp>;
2088
3109
  }
2089
3110
  //#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>;
3111
+ //#region src/ManagedDocument.d.ts
3112
+ declare const ManagedDocumentError_base: Schema.Class<ManagedDocumentError, Schema.TaggedStruct<"ManagedDocumentError", {
3113
+ /** Which ambiguity or refusal was found. */
3114
+ readonly kind: Schema.Literals<readonly ["unterminatedRegion", "orphanedEnd", "overlappingRegions", "duplicateRegion", "markerInContent", "duplicateDeclaration"]>;
3115
+ /** 1-based line of the offending marker, for the structural kinds. */
3116
+ readonly line: Schema.optionalKey<Schema.Number>;
3117
+ /** The region key involved, when the failure names one. */
3118
+ readonly key: Schema.optionalKey<Schema.String>;
2108
3119
  }>, import("effect/Cause").YieldableError>;
2109
3120
  /**
2110
- * Raised when an OIDC token cannot be issued or read.
3121
+ * Raised when a managed document cannot be read or regenerated without
3122
+ * guessing.
3123
+ *
3124
+ * @remarks
3125
+ * The four structural kinds (`unterminatedRegion`, `orphanedEnd`,
3126
+ * `overlappingRegions`, `duplicateRegion`) surface at parse time and name an
3127
+ * ambiguity whose silent resolution would corrupt content a human wrote by
3128
+ * hand. The two declaration kinds surface when regions are applied:
3129
+ * `markerInContent` refuses region content that itself contains a line the
3130
+ * scanner would read as a region marker, and `duplicateDeclaration` refuses
3131
+ * one call that states two intentions for the same region.
2111
3132
  *
2112
3133
  * @public
2113
3134
  */
2114
- declare class OidcTokenError extends OidcTokenError_base {
3135
+ declare class ManagedDocumentError extends ManagedDocumentError_base {
2115
3136
  get message(): string;
2116
3137
  }
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
3138
  /**
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.
3139
+ * Options identifying a managed document inside a text that may not carry one
3140
+ * yet.
2152
3141
  *
2153
3142
  * @public
2154
3143
  */
2155
- declare class OidcClaims extends OidcClaims_base {}
2156
- /**
2157
- * The {@link OidcTokenIssuer} service shape.
2158
- *
2159
- * @public
2160
- */
2161
- interface OidcTokenIssuerShape {
3144
+ interface ManagedDocumentSource {
3145
+ /** Whose document this is, e.g. your action's name. */
3146
+ readonly namespace: string;
3147
+ /** Which document, within that namespace. */
3148
+ readonly key: string;
2162
3149
  /**
2163
- * Request an ID token, optionally bound to an audience.
2164
- *
2165
- * @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.
3150
+ * The current text — a fetched comment body, a PR description — or absent
3151
+ * when there is nothing yet. An absent text and an empty one mean the same
3152
+ * thing: a fresh document.
2169
3153
  */
2170
- readonly token: (audience?: string) => Effect.Effect<Redacted.Redacted<string>, OidcTokenError>;
2171
- /**
2172
- * The token's claims, decoded.
2173
- *
2174
- * @remarks
2175
- * **The signature is deliberately not verified**, for three reasons that are
2176
- * recorded so nobody "fixes" it:
2177
- *
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.
2187
- *
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.
2190
- *
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.
2196
- */
2197
- readonly claims: (audience?: string) => Effect.Effect<OidcClaims, OidcTokenError>;
3154
+ readonly text?: string | undefined;
2198
3155
  }
2199
- declare const OidcTokenIssuer_base: Context.ServiceClass<OidcTokenIssuer, "@effected/github-actions/OidcTokenIssuer", OidcTokenIssuerShape>;
3156
+ declare const ManagedDocument_base: Schema.Class<ManagedDocument, Schema.Struct<{
3157
+ /** Whose document this is. */
3158
+ readonly namespace: Schema.String;
3159
+ /** Which document, within that namespace. */
3160
+ readonly key: Schema.String;
3161
+ /** The document's full text, sentinel and regions included. */
3162
+ readonly text: Schema.String;
3163
+ }>, {}>;
2200
3164
  /**
2201
- * The runner's OIDC token service.
3165
+ * A marker-delimited document: named regions a tool owns, inside text a human
3166
+ * may also edit.
2202
3167
  *
2203
3168
  * @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.
3169
+ * The one primitive behind both the sticky PR comment and the managed PR
3170
+ * description. A sentinel HTML comment (`<!-- namespace:key -->`) identifies
3171
+ * the document; each region is delimited by HTML-comment markers keyed
3172
+ * `namespace.key.region`; and everything outside the managed regions is
3173
+ * preserved byte-for-byte on regeneration. Regions are **replaced from
3174
+ * current state, never appended** — applying the same region twice yields one
3175
+ * region carrying the latest content, which is what makes a check that
3176
+ * resolves many times safe to re-render.
3177
+ *
3178
+ * The region engine is `@effected/templates`' pure `SectionDocument` —
3179
+ * reused, not reinvented — fixed to the HTML comment style and a
3180
+ * region-specific phrase. Everything here is string → string; the GitHub
3181
+ * write path composes this with `PullRequestComment` (whose `CommentMarker`
3182
+ * renders the *same* sentinel for the same namespace and key) or a PR-body
3183
+ * update in `@effected/github`.
3184
+ *
3185
+ * Construct with {@link ManagedDocument.parseResult} — create-or-update in
3186
+ * one call, so the consumer never branches on whether the document exists
3187
+ * yet. `ManagedDocument.make` is the raw schema constructor and performs no
3188
+ * structural validation.
2208
3189
  *
2209
3190
  * @example
2210
3191
  * ```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
- * });
3192
+ * import { ManagedDocument } from "@effected/github-actions";
3193
+ * import { Result } from "effect";
3194
+ *
3195
+ * const doc = ManagedDocument.parseResult({ namespace: "my-action", key: "release", text: fetched });
3196
+ * if (Result.isSuccess(doc)) {
3197
+ * const next = doc.success.withRegionsResult([
3198
+ * ["header", "## Validating Release"],
3199
+ * ["footer", "generated by my-action"],
3200
+ * ]);
3201
+ * }
2219
3202
  * ```
2220
3203
  *
2221
3204
  * @public
2222
3205
  */
2223
- declare class OidcTokenIssuer extends OidcTokenIssuer_base {
2224
- static readonly layer: Layer.Layer<OidcTokenIssuer, never, ActionEnvironment | HttpClient.HttpClient>;
3206
+ declare class ManagedDocument extends ManagedDocument_base {
2225
3207
  /**
2226
- * An **unsigned** JWT carrying these claims.
3208
+ * Read a document out of existing text, or begin a fresh one.
2227
3209
  *
2228
3210
  * @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.
3211
+ * The synchronous primitive; {@link ManagedDocument.parse} derives from it.
3212
+ * Absent text, empty text and text that carries no sentinel are all legal —
3213
+ * the sentinel and regions are added by the first
3214
+ * {@link ManagedDocument.withRegionsResult}. Only a *structurally corrupt*
3215
+ * region layout fails, because every structural ambiguity is a case where
3216
+ * a silent choice would destroy content a human wrote.
2234
3217
  */
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>;
3218
+ static parseResult(source: ManagedDocumentSource): Result.Result<ManagedDocument, ManagedDocumentError>;
2240
3219
  /**
2241
- * A double that answers with these claims, consistently on both members.
3220
+ * Read a document out of existing text, in `Effect`.
2242
3221
  *
2243
3222
  * @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.
3223
+ * Defined in terms of {@link ManagedDocument.parseResult} — synchronous
3224
+ * callers can use that variant directly.
2249
3225
  */
2250
- static readonly layerFor: (claims: OidcClaims) => Layer.Layer<OidcTokenIssuer>;
2251
- }
2252
- //#endregion
2253
- //#region src/Secret.d.ts
2254
- /**
2255
- * The declassification seam: the **only** place in this package where a
2256
- * `Redacted` value becomes a plain string.
2257
- *
2258
- * @remarks
2259
- * Some boundaries cannot carry a `Redacted`. A detached child process reads
2260
- * its configuration from environment variables; `GITHUB_STATE` is a plaintext
2261
- * file by GitHub's protocol. `Redacted` refusing to serialize is the whole
2262
- * value of the type, so this module does not try to make it serializable —
2263
- * it makes declassification **explicit, auditable, and impossible to do
2264
- * quietly**.
2265
- *
2266
- * The invariant: **masking and declassification are the same call.** Every
2267
- * member registers the value with the runner's log filter (`::add-mask::`)
2268
- * *before* returning plaintext, so a secret cannot reach a child's environment
2269
- * or a state file without the runner already knowing to redact it from logs.
2270
- *
2271
- * `Redacted.value` appears nowhere else in this package, and a test asserts
2272
- * that structurally — a reintroduction elsewhere fails the suite rather than
2273
- * review.
2274
- *
2275
- * What this deliberately does **not** do is encrypt the handoff. The child
2276
- * runs on the same runner, as the same user, as the parent; a key would have
2277
- * to travel the same channel as the secret it protects.
2278
- *
2279
- * @example
2280
- * ```ts
2281
- * import { Secret } from "@effected/github-actions";
2282
- * import { Effect } from "effect";
2283
- *
2284
- * const program = Effect.gen(function* () {
2285
- * const env = yield* Secret.forChildEnv({ MY_TOKEN: theToken });
2286
- * // every value in `env` is already masked in the runner log
2287
- * return env;
2288
- * });
2289
- * ```
2290
- *
2291
- * @public
2292
- */
2293
- declare class Secret {
2294
- private constructor();
3226
+ static readonly parse: (source: ManagedDocumentSource) => Effect.Effect<ManagedDocument, ManagedDocumentError, never>;
3227
+ /** The HTML comment identifying this document, `<!-- namespace:key -->`. */
3228
+ get sentinel(): string;
3229
+ /** Does this text carry the document's sentinel? */
3230
+ matches(text: string): boolean;
3231
+ /** The content of one region, if the document carries it. */
3232
+ region(key: string): Option.Option<string>;
3233
+ /** Every region this document carries, in document order. */
3234
+ get regions(): ReadonlyArray<{
3235
+ readonly key: string;
3236
+ readonly content: string;
3237
+ }>;
2295
3238
  /**
2296
- * Declassify a set of secrets for a child process's environment.
3239
+ * The document with these regions replaced from current state.
2297
3240
  *
2298
3241
  * @remarks
2299
- * Every value is masked before any plaintext is returned — including when
2300
- * one of them fails, because masking happens for the whole set first.
3242
+ * The synchronous primitive; {@link ManagedDocument.withRegions} derives
3243
+ * from it. Each entry pairs a region key with its full new content. A
3244
+ * region already present is **replaced in place**; a region not present
3245
+ * yet is added; regions not named are left exactly as they are; and every
3246
+ * byte outside a managed region survives untouched. Applying entries that
3247
+ * change nothing returns byte-identical text, which is what lets a caller
3248
+ * compare-and-skip instead of issuing a write.
3249
+ *
3250
+ * Region keys share the name grammar `namespace` and `key` obey; a key
3251
+ * outside it is a wiring error and throws at construction rather than
3252
+ * surfacing in the typed channel.
2301
3253
  */
2302
- static readonly forChildEnv: (entries: Readonly<Record<string, Redacted.Redacted<string>>>) => Effect.Effect<Record<string, string>, never, ActionOutputs>;
3254
+ withRegionsResult(entries: ReadonlyArray<readonly [key: string, content: string]>): Result.Result<ManagedDocument, ManagedDocumentError>;
2303
3255
  /**
2304
- * Declassify one secret for a runner file (`GITHUB_STATE`, `GITHUB_OUTPUT`).
3256
+ * The document with these regions replaced, in `Effect`.
2305
3257
  *
2306
3258
  * @remarks
2307
- * Masks first. The runner file is plaintext regardless; the mask is what
2308
- * keeps the value out of the visible log.
3259
+ * Defined in terms of {@link ManagedDocument.withRegionsResult} —
3260
+ * synchronous callers can use that variant directly.
2309
3261
  */
2310
- static readonly forRunnerFile: (secret: Redacted.Redacted<string>) => Effect.Effect<string, never, ActionOutputs>;
3262
+ withRegions(entries: ReadonlyArray<readonly [key: string, content: string]>): Effect.Effect<ManagedDocument, ManagedDocumentError>;
3263
+ /** The marker key a region is written under: `namespace.key.region`. */
3264
+ private wireKey;
3265
+ /** The region key back out of a wire key, for error reporting. */
3266
+ private regionKeyOf;
2311
3267
  /**
2312
- * Declassify one secret for an in-process use that needs the raw bytes.
3268
+ * This document's own regions, in document order.
2313
3269
  *
2314
3270
  * @remarks
2315
- * Request signing is the case this exists for: an HMAC cannot be computed
2316
- * over a `Redacted`, so a signing key has to become a string somewhere, and
2317
- * the point of this module is that "somewhere" is here and nowhere else.
2318
- *
2319
- * It masks even though the value is never *written* anywhere, and that is
2320
- * deliberate rather than superstitious: a signing key that leaks does so
2321
- * through something nobody audited — a debug log of outgoing headers, a
2322
- * serialized error, a stack trace carrying the closure. Registering it with
2323
- * the runner's filter costs one call and makes every one of those redacted.
2324
- *
2325
- * Call it **once, at layer construction**, not per operation: masking is
2326
- * idempotent but a workflow command per request is noise in the log.
3271
+ * A document a human already edited can be structurally corrupt, and a
3272
+ * read has no error channel worth having — a corrupt document simply has
3273
+ * no readable regions; {@link ManagedDocument.parseResult} is where
3274
+ * corruption fails typed. Regions belonging to a *different* document in
3275
+ * the same text (another namespace or key) are not this document's and are
3276
+ * never reported.
2327
3277
  */
2328
- static readonly forSigning: (secret: Redacted.Redacted<string>) => Effect.Effect<string, never, ActionOutputs>;
3278
+ private ownRegions;
2329
3279
  /**
2330
- * The far side of a handoff: re-wrap a plaintext environment variable.
3280
+ * Append the sentinel when the text does not carry it yet.
2331
3281
  *
2332
3282
  * @remarks
2333
- * A `Config`, so a missing or empty handoff is an honest `ConfigError`
2334
- * naming the variable rather than an empty `Redacted` that fails much later
2335
- * as an authentication error.
3283
+ * Appended at the end — the same position `PullRequestComment.upsert`
3284
+ * writes its marker to — so a document rendered here and a comment written
3285
+ * there agree about where the identity line lives.
2336
3286
  */
2337
- static readonly adopt: (name: string) => Config.Config<Redacted.Redacted<string>>;
3287
+ private ensureSentinel;
2338
3288
  }
2339
3289
  //#endregion
2340
3290
  //#region src/ToolInstaller.d.ts
@@ -2380,9 +3330,75 @@ declare class ToolInstallerError extends ToolInstallerError_base {
2380
3330
  interface ExtractOptions {
2381
3331
  /** The destination. A fresh temporary directory when omitted. */
2382
3332
  readonly destination?: string | undefined;
2383
- /** Flags for `tar`. Defaults to `["xzf"]`, i.e. a gzipped tarball. */
3333
+ /**
3334
+ * Flags for `tar`. Defaults to `["xzf"]`, i.e. a gzipped tarball.
3335
+ *
3336
+ * @remarks
3337
+ * The argv is assembled as `[...flags, archive, "-C", destination]` — the
3338
+ * archive path is appended directly AFTER the flags, so the LAST flag must
3339
+ * be the one that consumes the archive operand. `["xzf"]` works because `f`
3340
+ * is last inside the cluster; a custom set like `["xz", "--strip=1", "-f"]`
3341
+ * must end the same way, deliberately rather than by ordering luck.
3342
+ */
2384
3343
  readonly flags?: ReadonlyArray<string> | undefined;
2385
3344
  }
3345
+ /**
3346
+ * How a {@link ToolInstallerShape.download} should behave.
3347
+ *
3348
+ * @public
3349
+ */
3350
+ interface ToolDownloadOptions {
3351
+ /**
3352
+ * The overall time budget for ONE download attempt (request plus streaming
3353
+ * the body to disk). Defaults to five minutes.
3354
+ *
3355
+ * @remarks
3356
+ * The default is deliberately generous: toolchain archives run to hundreds
3357
+ * of megabytes, and a healthy runner connection moves tens of megabytes a
3358
+ * second — even a 500 MB archive on a degraded 2 MB/s link fits in five
3359
+ * minutes. What the budget exists for is the connection that stops moving
3360
+ * entirely: without it, a dead connection is a silent CI hang until the job
3361
+ * timeout; with it, the attempt fails typed as `downloadFailed` with no
3362
+ * status — which `retryable` classifies as a transport fault — and the
3363
+ * download's own retry policy tries again.
3364
+ */
3365
+ readonly timeout?: Duration.Input | undefined;
3366
+ }
3367
+ /**
3368
+ * What {@link ToolInstallerShape.provisionFile} should provision.
3369
+ *
3370
+ * @public
3371
+ */
3372
+ interface ProvisionFileOptions {
3373
+ /** The tool-cache name the binary is provisioned under, e.g. `"biome"`. */
3374
+ readonly tool: string;
3375
+ /** The exact version — a tool-cache path segment, so spell it canonically. */
3376
+ readonly version: string;
3377
+ /** The url the binary downloads from on a cache miss. */
3378
+ readonly url: string;
3379
+ /**
3380
+ * The cached file's name — what the tool is invoked as once its directory
3381
+ * is on the `PATH` (e.g. `"biome"`, or `"biome.exe"` on Windows).
3382
+ */
3383
+ readonly binary: string;
3384
+ }
3385
+ /**
3386
+ * Where {@link ToolInstallerShape.provisionFile} put a single-binary tool.
3387
+ *
3388
+ * @remarks
3389
+ * The same `directory`/`binDir` shape as `PackageManagerInstaller`'s
3390
+ * `CachedPackageManager`, so a consumer publishes either the same way. For a
3391
+ * single binary the two coincide: the cached directory contains exactly the
3392
+ * executable, so it IS the directory to hand to `ActionOutputs.addPath`.
3393
+ *
3394
+ * @public
3395
+ */
3396
+ interface ProvisionedFile {
3397
+ /** The cached entry: `<tool-cache>/<tool>/<version>/<arch>`. */
3398
+ readonly directory: string;
3399
+ /** The directory to `addPath` — for a single binary, the cached directory itself. */
3400
+ readonly binDir: string;
3401
+ }
2386
3402
  /**
2387
3403
  * The {@link ToolInstaller} service shape.
2388
3404
  *
@@ -2395,10 +3411,20 @@ interface ToolInstallerShape {
2395
3411
  * @remarks
2396
3412
  * Absent is not an error, and neither is an unreadable cache: both mean
2397
3413
  * "install it", which is the only thing a caller can do about either.
3414
+ *
3415
+ * **The tool cache is SHARED.** The hosted runner image pre-populates it and
3416
+ * every `setup-*` action writes into it — this package is a co-writer, not
3417
+ * the owner. A hit guarantees only the `<root>/<tool>/<version>/<arch>`
3418
+ * location contract ({@link ToolInstaller.cachePath}); the directory's
3419
+ * *interior* layout is whatever its writer produced, which is not
3420
+ * necessarily what this consumer's own `cacheDir` would have written.
3421
+ * Validate the layout of a foreign hit before relying on it — applying an
3422
+ * assumption from your own install path (an archive-wrapper subdirectory,
3423
+ * say) to a foreign entry is a real shipped bug.
2398
3424
  */
2399
3425
  readonly find: (tool: string, version: string) => Effect.Effect<Option.Option<string>>;
2400
3426
  /** Download a url to a temporary file, retrying what is worth retrying. */
2401
- readonly download: (url: string) => Effect.Effect<string, ToolInstallerError>;
3427
+ readonly download: (url: string, options?: ToolDownloadOptions) => Effect.Effect<string, ToolInstallerError>;
2402
3428
  /** Extract a tarball. Returns the directory its contents landed in. */
2403
3429
  readonly extractTar: (archive: string, options?: ExtractOptions) => Effect.Effect<string, ToolInstallerError>;
2404
3430
  /** Extract a zip. Returns the directory its contents landed in. */
@@ -2407,16 +3433,38 @@ interface ToolInstallerShape {
2407
3433
  readonly cacheDir: (source: string, tool: string, version: string) => Effect.Effect<string, ToolInstallerError>;
2408
3434
  /** Install a single file into the tool cache under `name`. Returns the cached directory. */
2409
3435
  readonly cacheFile: (source: string, name: string, tool: string, version: string) => Effect.Effect<string, ToolInstallerError>;
3436
+ /**
3437
+ * Provision a single-binary tool: the whole `find` → `download` →
3438
+ * chmod → `cacheFile` composition as one call.
3439
+ *
3440
+ * @remarks
3441
+ * The one packaged workflow among the primitives, because the single-binary
3442
+ * shape (biome, and every Rust/Go tool distributed as a bare executable) has
3443
+ * no per-tool variation left to compose: there is no archive, so no
3444
+ * extraction and no layout fixup. A cache hit whose entry contains the
3445
+ * named `binary` short-circuits; a hit *missing* it — a foreign or partial
3446
+ * entry, see {@link ToolInstallerShape.find}'s shared-cache warning — is
3447
+ * treated as a miss and reinstalled over, rather than handing back a
3448
+ * directory that cannot run the tool. On the install path the downloaded
3449
+ * file is made executable (`0o755`) BEFORE caching — skipped when
3450
+ * `RUNNER_OS` is Windows, where the bit does not exist — so the cache only
3451
+ * ever contains a runnable tool; a chmod failure is `cacheFailed` with the
3452
+ * file as its subject.
3453
+ */
3454
+ readonly provisionFile: (options: ProvisionFileOptions) => Effect.Effect<ProvisionedFile, ToolInstallerError>;
2410
3455
  }
2411
3456
  declare const ToolInstaller_base: Context.ServiceClass<ToolInstaller, "@effected/github-actions/ToolInstaller", ToolInstallerShape>;
2412
3457
  /**
2413
3458
  * Download, extract and cache a toolchain in the runner's tool cache.
2414
3459
  *
2415
3460
  * @remarks
2416
- * The primitives, not a workflow: a consumer composes `find` → `download` →
2417
- * `extractTar` → `cacheDir` in whatever order its tool needs, because the
2418
- * orders genuinely differ (a single binary skips extraction, a tarball with a
2419
- * nested root directory needs a path fixup between extract and cache).
3461
+ * The primitives, plus exactly one packaged workflow: a consumer composes
3462
+ * `find` → `download` → `extractTar` → `cacheDir` in whatever order its tool
3463
+ * needs, because the orders genuinely differ (a tarball with a nested root
3464
+ * directory needs a path fixup between extract and cache). The one shape with
3465
+ * no per-tool variation — a single bare binary, which skips extraction and
3466
+ * needs only the executable bit — ships whole as
3467
+ * {@link ToolInstallerShape.provisionFile}.
2420
3468
  *
2421
3469
  * No `@actions/tool-cache` dependency — the cache is a directory layout, and
2422
3470
  * this reproduces the layout rather than importing a package to compute it.
@@ -2463,5 +3511,339 @@ declare class ToolInstaller extends ToolInstaller_base {
2463
3511
  static readonly layerTest: (overrides?: Partial<ToolInstallerShape>) => Layer.Layer<ToolInstaller>;
2464
3512
  }
2465
3513
  //#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 };
3514
+ //#region src/PackageManagerInstaller.d.ts
3515
+ declare const PackageManagerInstallerError_base: Schema.Class<PackageManagerInstallerError, Schema.TaggedStruct<"PackageManagerInstallerError", {
3516
+ /**
3517
+ * `downloadFailed`, `extractFailed` and `cacheFailed` mirror the
3518
+ * {@link ToolInstallerError} that caused them, preserved on `cause`
3519
+ * (`cacheFailed` also covers a shim that could not be written into the
3520
+ * entry). `integrityMismatch` — the downloaded artifact does not hash to
3521
+ * what the pin declares; nothing was cached. `integrityMissing` — the pin
3522
+ * carries no integrity and the caller asked for `requireIntegrity`.
3523
+ * `unsupportedPlatform` — bun publishes no build for this runner's
3524
+ * OS/architecture pair. `layoutUnexpected` — the artifact extracted, but
3525
+ * its contents are not shaped like the package manager it claims to be.
3526
+ */
3527
+ readonly reason: Schema.Literals<readonly ["downloadFailed", "extractFailed", "integrityMismatch", "integrityMissing", "unsupportedPlatform", "layoutUnexpected", "cacheFailed"]>;
3528
+ /** The package manager being installed. */
3529
+ readonly name: Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>;
3530
+ /** The exact version being installed, in string form. */
3531
+ readonly version: Schema.String;
3532
+ /** What was being worked on — a url, a path, or an OS/arch pair. */
3533
+ readonly subject: Schema.optionalKey<Schema.String>;
3534
+ /** The integrity the pin declares (`<algo>.<hex>`), on a mismatch. */
3535
+ readonly expected: Schema.optionalKey<Schema.String>;
3536
+ /** The integrity the artifact actually hashed to, on a mismatch. */
3537
+ readonly actual: Schema.optionalKey<Schema.String>;
3538
+ /** The underlying failure, preserved structurally. */
3539
+ readonly cause: Schema.optionalKey<Schema.Defect>;
3540
+ }>, import("effect/Cause").YieldableError>;
3541
+ /**
3542
+ * Raised when a package manager cannot be provisioned on the runner.
3543
+ *
3544
+ * @public
3545
+ */
3546
+ declare class PackageManagerInstallerError extends PackageManagerInstallerError_base {
3547
+ get message(): string;
3548
+ }
3549
+ /**
3550
+ * How {@link PackageManagerInstallerShape.install} should behave.
3551
+ *
3552
+ * @public
3553
+ */
3554
+ interface PackageManagerInstallOptions {
3555
+ /**
3556
+ * Fail with `integrityMissing` when the pin carries no integrity hash,
3557
+ * instead of proceeding with a logged warning. Off by default, because
3558
+ * in-the-wild `devEngines` pins routinely carry no integrity.
3559
+ */
3560
+ readonly requireIntegrity?: boolean | undefined;
3561
+ /**
3562
+ * The npm registry host the npm, pnpm and yarn tarballs download from.
3563
+ * Defaults to `https://registry.npmjs.org`; a corporate mirror that serves
3564
+ * the standard `/<name>/-/<basename>-<version>.tgz` tarball routes works
3565
+ * unchanged. bun does not ship through a registry — its per-platform zip
3566
+ * always comes from GitHub releases.
3567
+ */
3568
+ readonly registry?: string | undefined;
3569
+ /**
3570
+ * Whether an npm pin may be answered by the runner's own ambient npm.
3571
+ * Defaults to `true`.
3572
+ *
3573
+ * @remarks
3574
+ * Set `false` when the run REPLACES node: a consumer that installs a pinned
3575
+ * node in the same run puts that node's bundled npm ahead of the runner's on
3576
+ * every path that matters afterward (the install child's `PATH`, and every
3577
+ * later workflow step via `GITHUB_PATH`). The ambient probe interrogates the
3578
+ * RUNNER's npm, so its exact-version match can diverge from the npm that
3579
+ * actually executes once the pinned node shadows it. Suppressing the probe
3580
+ * skips it entirely — no `npm --version` is ever spawned — and the install
3581
+ * goes straight to the tool-cache/dist path, answering
3582
+ * `source: "tool-cache"` with its own `binDir` as usual.
3583
+ */
3584
+ readonly allowAmbient?: boolean | undefined;
3585
+ }
3586
+ declare const AmbientPackageManager_base: Schema.Class<AmbientPackageManager, Schema.Struct<{
3587
+ /** The discriminant: the runner's own toolchain answered. */
3588
+ readonly source: Schema.tag<"ambient">;
3589
+ /** The package-manager name (`npm`, `pnpm`, `yarn` or `bun`). */
3590
+ readonly name: Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>;
3591
+ /** The exact installed version, in string form. */
3592
+ readonly version: Schema.String;
3593
+ /** Bin name → the ambient command name that invokes it. */
3594
+ readonly bins: Schema.$Record<Schema.String, Schema.String>;
3595
+ }>, {}>;
3596
+ /**
3597
+ * A package manager the runner's own toolchain already had: nothing was
3598
+ * downloaded and nothing was cached, so there is no directory to publish.
3599
+ *
3600
+ * @remarks
3601
+ * `bins` values are bare command names (`npm`, `npx`) resolved through the
3602
+ * ambient `PATH` — the toolchain already put them there.
3603
+ *
3604
+ * @public
3605
+ */
3606
+ declare class AmbientPackageManager extends AmbientPackageManager_base {}
3607
+ declare const CachedPackageManager_base: Schema.Class<CachedPackageManager, Schema.Struct<{
3608
+ /** The discriminant: the manager lives in the tool cache. */
3609
+ readonly source: Schema.tag<"tool-cache">;
3610
+ /** The package-manager name (`npm`, `pnpm`, `yarn` or `bun`). */
3611
+ readonly name: Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>;
3612
+ /** The exact installed version, in string form. */
3613
+ readonly version: Schema.String;
3614
+ /** The cached entry: `<tool-cache>/<name>/<version>/<arch>`. */
3615
+ readonly directory: Schema.String;
3616
+ /** The directory to `addPath` — executable shims, or bun's own directory. */
3617
+ readonly binDir: Schema.String;
3618
+ /** Bin name → the absolute path of the underlying entry point. */
3619
+ readonly bins: Schema.$Record<Schema.String, Schema.String>;
3620
+ }>, {}>;
3621
+ /**
3622
+ * A package manager living in the runner's tool cache — found there, or
3623
+ * installed into it by this call.
3624
+ *
3625
+ * @remarks
3626
+ * `binDir` is the directory a consumer hands to `ActionOutputs.addPath` to
3627
+ * make the manager invokable by name in subsequent workflow steps. For the
3628
+ * npm-registry managers it is the entry's `.bin` directory of executable
3629
+ * shims this installer writes (`#!/bin/sh` exec wrappers, or `.cmd` wrappers
3630
+ * on Windows); for bun it is the entry directory itself, because the cached
3631
+ * `bun` binary is directly executable and a shim would add nothing but a
3632
+ * fork. `bins` still maps each published bin name to the underlying entry —
3633
+ * a Node script to run with `node` for npm/pnpm/yarn, the native binary for
3634
+ * bun. Calling `addPath` remains deliberately the consumer's move, and note
3635
+ * that `addPath` targets *subsequent* steps: a same-process probe must use
3636
+ * the absolute paths in `bins` or `binDir`.
3637
+ *
3638
+ * @public
3639
+ */
3640
+ declare class CachedPackageManager extends CachedPackageManager_base {}
3641
+ /**
3642
+ * An installed package manager, discriminated by `source`: `ambient` carries
3643
+ * no directory, `tool-cache` carries the cached `directory` and the
3644
+ * `addPath`-able `binDir`. The union is a `Schema`, so the record round-trips
3645
+ * through `ActionState` for a later phase to read back.
3646
+ *
3647
+ * @remarks
3648
+ * **Construct the variant classes, never the union.** The union's inherited
3649
+ * `make` typechecks against *both* variants at once, so a wrong field yields
3650
+ * a confusing two-branch error instead of "this variant wants X" — reach for
3651
+ * {@link AmbientPackageManager}`.make` / {@link CachedPackageManager}`.make`,
3652
+ * whose `Schema.tag` fills `source` for you. The same tag is why an
3653
+ * overrides-style test helper should be typed
3654
+ * `Partial<Omit<typeof CachedPackageManager.Type, "source">>` — a plain
3655
+ * `Partial` offers `source`, which `make` auto-fills and rejects as input.
3656
+ *
3657
+ * @public
3658
+ */
3659
+ declare const InstalledPackageManager: Schema.Union<readonly [typeof AmbientPackageManager, typeof CachedPackageManager]>;
3660
+ /**
3661
+ * The decoded type of {@link (InstalledPackageManager:variable)}:
3662
+ * {@link AmbientPackageManager} `|` {@link CachedPackageManager}.
3663
+ *
3664
+ * @public
3665
+ */
3666
+ type InstalledPackageManager = typeof InstalledPackageManager.Type;
3667
+ /**
3668
+ * The {@link PackageManagerInstaller} service shape.
3669
+ *
3670
+ * @public
3671
+ */
3672
+ interface PackageManagerInstallerShape {
3673
+ /**
3674
+ * Provision the exact package-manager version a pin names, answering with
3675
+ * where it landed and how to invoke it.
3676
+ */
3677
+ readonly install: (pin: PackageManagerPin, options?: PackageManagerInstallOptions) => Effect.Effect<InstalledPackageManager, PackageManagerInstallerError>;
3678
+ }
3679
+ declare const PackageManagerInstaller_base: Context.ServiceClass<PackageManagerInstaller, "@effected/github-actions/PackageManagerInstaller", PackageManagerInstallerShape>;
3680
+ /**
3681
+ * First-class exact-version package-manager provisioning on a GitHub runner.
3682
+ *
3683
+ * @remarks
3684
+ * Takes a corepack pin (`@effected/npm`'s `PackageManagerPin`) and answers
3685
+ * with an installed manager: tool-cache `find` first, then — for npm — an
3686
+ * ambient `npm --version` probe (every Node toolchain ships npm), and only
3687
+ * then the manager's own dist: npm, pnpm and yarn 1.x from their registry
3688
+ * tarballs, yarn 2+ (Berry) from `@yarnpkg/cli-dist`, bun from its
3689
+ * per-platform GitHub-release zip. A pin that carries integrity is verified
3690
+ * fail-closed against what corepack itself hashes; one that does not proceeds
3691
+ * with a logged warning unless `requireIntegrity` says otherwise.
3692
+ *
3693
+ * Every tool-cache answer carries a `binDir` a consumer can hand straight to
3694
+ * `ActionOutputs.addPath`: the npm-registry managers get a `.bin` directory
3695
+ * of executable shims written as part of the cached entry, bun's entry is its
3696
+ * own `binDir`. Caching goes through {@link ToolInstaller}, so the
3697
+ * stage-then-swap invariant holds here for free — shims included, the tool
3698
+ * cache only ever contains complete package managers.
3699
+ *
3700
+ * @example
3701
+ * ```ts
3702
+ * import { ActionOutputs, PackageManagerInstaller } from "@effected/github-actions";
3703
+ * import { PackageManagerPin } from "@effected/npm";
3704
+ * import { Effect } from "effect";
3705
+ *
3706
+ * const provision = Effect.gen(function* () {
3707
+ * const installer = yield* PackageManagerInstaller;
3708
+ * const outputs = yield* ActionOutputs;
3709
+ * const pin = yield* PackageManagerPin.parse("pnpm@10.13.1");
3710
+ * const installed = yield* installer.install(pin);
3711
+ * if (installed.source === "tool-cache") {
3712
+ * yield* outputs.addPath(installed.binDir);
3713
+ * }
3714
+ * });
3715
+ * ```
3716
+ *
3717
+ * @public
3718
+ */
3719
+ declare class PackageManagerInstaller extends PackageManagerInstaller_base {
3720
+ static readonly layer: Layer.Layer<PackageManagerInstaller, never, ActionEnvironment | FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner | ToolInstaller>;
3721
+ /** A test double. The unstubbed member dies rather than reporting an install that did not happen. */
3722
+ static readonly makeTest: (overrides?: Partial<PackageManagerInstallerShape>) => PackageManagerInstallerShape;
3723
+ /** {@link PackageManagerInstaller.makeTest} behind `Layer.succeed`. */
3724
+ static readonly layerTest: (overrides?: Partial<PackageManagerInstallerShape>) => Layer.Layer<PackageManagerInstaller>;
3725
+ }
3726
+ //#endregion
3727
+ //#region src/Secret.d.ts
3728
+ /**
3729
+ * The declassification seam: the **only** place in this package where a
3730
+ * `Redacted` value becomes a plain string.
3731
+ *
3732
+ * @remarks
3733
+ * Some boundaries cannot carry a `Redacted`. A detached child process reads
3734
+ * its configuration from environment variables; `GITHUB_STATE` is a plaintext
3735
+ * file by GitHub's protocol. `Redacted` refusing to serialize is the whole
3736
+ * value of the type, so this module does not try to make it serializable —
3737
+ * it makes declassification **explicit, auditable, and impossible to do
3738
+ * quietly**.
3739
+ *
3740
+ * The invariant: **masking and declassification are the same call.** Every
3741
+ * member registers the value with the runner's log filter (`::add-mask::`)
3742
+ * *before* returning plaintext, so a secret cannot reach a child's environment
3743
+ * or a state file without the runner already knowing to redact it from logs.
3744
+ *
3745
+ * `Redacted.value` appears nowhere else in this package, and a test asserts
3746
+ * that structurally — a reintroduction elsewhere fails the suite rather than
3747
+ * review.
3748
+ *
3749
+ * What this deliberately does **not** do is encrypt the handoff. The child
3750
+ * runs on the same runner, as the same user, as the parent; a key would have
3751
+ * to travel the same channel as the secret it protects.
3752
+ *
3753
+ * @example
3754
+ * ```ts
3755
+ * import { Secret } from "@effected/github-actions";
3756
+ * import { Effect } from "effect";
3757
+ *
3758
+ * const program = Effect.gen(function* () {
3759
+ * const env = yield* Secret.forChildEnv({ MY_TOKEN: theToken });
3760
+ * // every value in `env` is already masked in the runner log
3761
+ * return env;
3762
+ * });
3763
+ * ```
3764
+ *
3765
+ * @public
3766
+ */
3767
+ declare class Secret {
3768
+ private constructor();
3769
+ /**
3770
+ * Declassify a set of secrets for a child process's environment.
3771
+ *
3772
+ * @remarks
3773
+ * Every value is masked before any plaintext is returned — including when
3774
+ * one of them fails, because masking happens for the whole set first.
3775
+ *
3776
+ * **Detached workers invert the masking model.** Masking works because the
3777
+ * runner parses this process's stdout; a *detached* child's stdout is a log
3778
+ * file no runner parses, so a mask emitted **inside** the worker is inert
3779
+ * and writes the plaintext verbatim into that log — a shipped incident, not
3780
+ * a hypothetical. The rule: **the parent masks, before the spawn** — call
3781
+ * this member in the parent, where the runner is listening, and hand the
3782
+ * already-masked plaintext to the worker's environment. Inside the worker,
3783
+ * compose `ActionOutputs.layerDetached`, whose `setSecret` is a documented
3784
+ * no-op, so the same code cannot re-leak what the parent already masked.
3785
+ */
3786
+ static readonly forChildEnv: (entries: Readonly<Record<string, Redacted.Redacted<string>>>) => Effect.Effect<Record<string, string>, never, ActionOutputs>;
3787
+ /**
3788
+ * Declassify one secret for a runner file (`GITHUB_STATE`, `GITHUB_OUTPUT`).
3789
+ *
3790
+ * @remarks
3791
+ * Masks first. The runner file is plaintext regardless; the mask is what
3792
+ * keeps the value out of the visible log.
3793
+ */
3794
+ static readonly forRunnerFile: (secret: Redacted.Redacted<string>) => Effect.Effect<string, never, ActionOutputs>;
3795
+ /**
3796
+ * Declassify one secret for an in-process use that needs the raw bytes.
3797
+ *
3798
+ * @remarks
3799
+ * Request signing is the case this exists for: an HMAC cannot be computed
3800
+ * over a `Redacted`, so a signing key has to become a string somewhere, and
3801
+ * the point of this module is that "somewhere" is here and nowhere else.
3802
+ *
3803
+ * Signing named the member; it does not gate it. **Any in-process use that
3804
+ * needs the raw value without writing it to a runner file is this
3805
+ * operation** — handing a key to a library that takes a plain string. The
3806
+ * dividing line is where the value goes, not what it is for: a value
3807
+ * crossing into a **child's** environment is {@link Secret.forChildEnv},
3808
+ * one written to `GITHUB_STATE` or `GITHUB_OUTPUT` is
3809
+ * {@link Secret.forRunnerFile}, and one that stays in this process is this
3810
+ * member. A third-party SDK that reads only the ambient environment is the
3811
+ * hard case: this package never mutates `process.env` (reads are seeded
3812
+ * once through `ActionEnvironment`), so if a consumer chooses that bridge,
3813
+ * the mutation — and its restore discipline — lives in consumer code as
3814
+ * the consumer's own tradeoff, not as a pattern this member recommends.
3815
+ *
3816
+ * It masks even though the value is never *written* anywhere, and that is
3817
+ * deliberate rather than superstitious: a signing key that leaks does so
3818
+ * through something nobody audited — a debug log of outgoing headers, a
3819
+ * serialized error, a stack trace carrying the closure. Registering it with
3820
+ * the runner's filter costs one call and makes every one of those redacted.
3821
+ *
3822
+ * Call it **once, at layer construction**, not per operation: masking is
3823
+ * idempotent but a workflow command per request is noise in the log.
3824
+ *
3825
+ * **In a detached worker, the mask this member emits is a leak.** The
3826
+ * `::add-mask::` command only masks when the runner parses this process's
3827
+ * stdout; a detached worker's stdout is a log file no runner parses, so
3828
+ * there the command masks nothing *and* spells the plaintext into the log
3829
+ * — a signing key shipped exactly that way for one round. A worker that
3830
+ * signs (an S3-style `BlobStore` is the canonical case) must compose
3831
+ * `ActionOutputs.layerDetached`, under which this member still returns the
3832
+ * raw key for the HMAC but the mask is a documented no-op; the masking
3833
+ * itself is the **parent's** job, done before the spawn via
3834
+ * {@link Secret.forChildEnv} under the real layer.
3835
+ */
3836
+ static readonly forSigning: (secret: Redacted.Redacted<string>) => Effect.Effect<string, never, ActionOutputs>;
3837
+ /**
3838
+ * The far side of a handoff: re-wrap a plaintext environment variable.
3839
+ *
3840
+ * @remarks
3841
+ * A `Config`, so a missing or empty handoff is an honest `ConfigError`
3842
+ * naming the variable rather than an empty `Redacted` that fails much later
3843
+ * as an authentication error.
3844
+ */
3845
+ static readonly adopt: (name: string) => Config.Config<Redacted.Redacted<string>>;
3846
+ }
3847
+ //#endregion
3848
+ 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, AmbientPackageManager, type AnnotationProperties, Artifact, ArtifactError, type ArtifactItem, type ArtifactRef, type ArtifactShape, type Blob, BlobEnvelope, BlobEnvelopeError, BlobStore, BlobStoreError, type BlobStoreShape, BlobTransferError, CacheKey, CacheKeyError, CachedPackageManager, 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, InstalledPackageManager, ManagedDocument, ManagedDocumentError, type ManagedDocumentSource, OidcClaims, OidcTokenError, OidcTokenIssuer, type OidcTokenIssuerShape, type PackageManagerInstallOptions, PackageManagerInstaller, PackageManagerInstallerError, type PackageManagerInstallerShape, ProcessId, type ProvisionFileOptions, type ProvisionOptions, type ProvisionedFile, type ReadOptions, type ReadinessOptions, RunnerContext, type S3Config, Secret, type ToolDownloadOptions, ToolInstaller, ToolInstallerError, type ToolInstallerShape, type UploadOptions, type UploadResult, type WithBufferOptions, WorkflowCommand, describeCause, projectCheckState };
2467
3849
  //# sourceMappingURL=index.d.ts.map