@usine/github 0.1.0 → 0.1.1
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/README.md +14 -4
- package/dist/access.d.ts +48 -0
- package/dist/access.d.ts.map +1 -0
- package/dist/access.js +53 -0
- package/dist/access.js.map +1 -0
- package/dist/credentials.d.ts +28 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +22 -0
- package/dist/credentials.js.map +1 -0
- package/dist/error.d.ts +36 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +48 -0
- package/dist/error.js.map +1 -0
- package/dist/github-app.d.ts +17 -10
- package/dist/github-app.d.ts.map +1 -1
- package/dist/github-app.js +31 -16
- package/dist/github-app.js.map +1 -1
- package/dist/http.d.ts +3 -4
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +4 -4
- package/dist/http.js.map +1 -1
- package/dist/installation.d.ts +48 -0
- package/dist/installation.d.ts.map +1 -0
- package/dist/installation.js +40 -0
- package/dist/installation.js.map +1 -0
- package/dist/item-writer.d.ts +7 -6
- package/dist/item-writer.d.ts.map +1 -1
- package/dist/item-writer.js +5 -4
- package/dist/item-writer.js.map +1 -1
- package/dist/jwt.d.ts +6 -5
- package/dist/jwt.d.ts.map +1 -1
- package/dist/jwt.js +4 -4
- package/dist/jwt.js.map +1 -1
- package/dist/label-writer.d.ts +23 -0
- package/dist/label-writer.d.ts.map +1 -0
- package/dist/label-writer.js +54 -0
- package/dist/label-writer.js.map +1 -0
- package/dist/local-secrets.d.ts +5 -5
- package/dist/local-secrets.d.ts.map +1 -1
- package/dist/local-secrets.js +5 -5
- package/dist/local-secrets.js.map +1 -1
- package/dist/polling/content.d.ts +3 -0
- package/dist/polling/content.d.ts.map +1 -0
- package/dist/polling/content.js +24 -0
- package/dist/polling/content.js.map +1 -0
- package/dist/polling/detection.d.ts +55 -0
- package/dist/polling/detection.d.ts.map +1 -0
- package/dist/polling/detection.js +49 -0
- package/dist/polling/detection.js.map +1 -0
- package/dist/polling/labels.d.ts +31 -0
- package/dist/polling/labels.d.ts.map +1 -0
- package/dist/polling/labels.js +29 -0
- package/dist/polling/labels.js.map +1 -0
- package/dist/polling/{model.d.ts → payload.d.ts} +7 -66
- package/dist/polling/payload.d.ts.map +1 -0
- package/dist/polling/{model.js → payload.js} +12 -72
- package/dist/polling/payload.js.map +1 -0
- package/dist/polling/polling.d.ts +15 -9
- package/dist/polling/polling.d.ts.map +1 -1
- package/dist/polling/polling.js +49 -44
- package/dist/polling/polling.js.map +1 -1
- package/package.json +17 -5
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -7
- package/dist/index.js.map +0 -1
- package/dist/model.d.ts +0 -128
- package/dist/model.d.ts.map +0 -1
- package/dist/model.js +0 -133
- package/dist/model.js.map +0 -1
- package/dist/polling/model.d.ts.map +0 -1
- package/dist/polling/model.js.map +0 -1
package/README.md
CHANGED
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
- an installation-wide token for orchestrator polling and writes;
|
|
6
6
|
- a token restricted to one repository for a worker attempt.
|
|
7
7
|
|
|
8
|
-
Both token requests use a one-hour maximum lifetime. Token values and the App private key stay inside Effect `Redacted` values.
|
|
8
|
+
Both token requests use a one-hour maximum lifetime. Expiry validation tolerates up to three minutes of clock skew while capping the local lease at one hour; expired tokens are rejected. Token values and the App private key stay inside Effect `Redacted` values.
|
|
9
|
+
|
|
10
|
+
Import `GitHubApp`, credentials, access results, and typed App errors from `@usine/github/github-app`. `@usine/github/polling` provides `GitHubPolling` with its cursors, change-detection results, and payload snapshots. Item and label writes use `/item-writer` and `/label-writer`; local credential loading uses `/local-secrets`.
|
|
11
|
+
|
|
12
|
+
`/polling` also exports `RepositoryLabel`, `RepositoryLabelCatalog`, and
|
|
13
|
+
`RepositoryLabelsNotModified` for reading label catalogs and handling conditional
|
|
14
|
+
responses. These contracts share the same entry point as `GitHubPolling`.
|
|
9
15
|
|
|
10
16
|
## Create the App manually
|
|
11
17
|
|
|
@@ -55,13 +61,13 @@ The package root has no Node dependency. Supply an HTTP client plus platform fil
|
|
|
55
61
|
|
|
56
62
|
```ts
|
|
57
63
|
import { NodeFileSystem, NodePath } from "@effect/platform-node"
|
|
58
|
-
import {
|
|
59
|
-
import { GitHubApp } from "@usine/github"
|
|
64
|
+
import { InstanceDirectory } from "@usine/core/instance"
|
|
65
|
+
import { GitHubApp } from "@usine/github/github-app"
|
|
60
66
|
import { localSecretsLayer } from "@usine/github/local-secrets"
|
|
61
67
|
import { Layer } from "effect"
|
|
62
68
|
import { FetchHttpClient } from "effect/unstable/http"
|
|
63
69
|
|
|
64
|
-
const instance =
|
|
70
|
+
const instance = InstanceDirectory.make("/path/to/instance")
|
|
65
71
|
const platformLayer = Layer.merge(NodeFileSystem.layer, NodePath.layer)
|
|
66
72
|
const credentialsLayer = localSecretsLayer(instance).pipe(
|
|
67
73
|
Layer.provide(platformLayer),
|
|
@@ -87,12 +93,16 @@ Pass a `CoveredProject` to `GitHubApp.workerToken`. GitHub must confirm in the t
|
|
|
87
93
|
|
|
88
94
|
- `changes(repository, cursor)` makes one conditional Issues REST request. It returns changed issue and pull-request numbers, the next ETag and watermark, plus any rate-limit delay GitHub requested.
|
|
89
95
|
- `labels(repository)` reads every page of the repository label catalog for Board lane proposals.
|
|
96
|
+
|
|
97
|
+
Change-detection results distinguish `nextPollAt`, the earliest next conditional poll, from `backoffUntil`, a request delay caused by `Retry-After` or rate-limit pressure. Self-write Hints can fetch an Item snapshot during the normal poll interval while respecting request backoff.
|
|
90
98
|
- `snapshot(repository, item)` fetches the issue or pull-request payload through GraphQL and computes its SHA-256 content hash. The stored payload retains timestamps, while the hash excludes timestamp fields at every nesting level.
|
|
91
99
|
|
|
92
100
|
The REST cursor overlaps its watermark by five seconds. Repeated items are expected; the engine's permanent Event ledger owns deduplication. Provide `GitHubPollingLayer` with `GitHubApp`, an Effect HTTP client, and `Crypto.Crypto`. The package root remains platform-free.
|
|
93
101
|
|
|
94
102
|
`GitHubItemWriter.updateLabels(repository, item, mutation)` adds destination lane labels and removes only explicitly lane-owned labels from an issue or pull request. It never replaces the complete applied-label set or deletes repository label definitions, so unrelated and concurrently added labels survive stale or truncated Item projections. The service requires `GitHubApp` and an Effect HTTP client.
|
|
95
103
|
|
|
104
|
+
`GitHubLabelWriter.create(repository, label)` creates one repository label through `POST /repos/{owner}/{repo}/labels` using the installation-wide App token. Provide a name; color and description are optional. Reconciliation policy belongs to the orchestrator, so this service exposes creation only and never edits or deletes label definitions.
|
|
105
|
+
|
|
96
106
|
## Real-App integration check
|
|
97
107
|
|
|
98
108
|
Run the opt-in integration test against a disposable issue in a repository covered by the App:
|
package/dist/access.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { RepositoryName } from "@usine/core/repository";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
import { RepositoryId, Installation } from "./installation.ts";
|
|
4
|
+
/** Repository grants required for polling, label updates, and agent-authored changes. */
|
|
5
|
+
export declare const REQUIRED_REPOSITORY_PERMISSIONS: {
|
|
6
|
+
readonly checks: "read";
|
|
7
|
+
readonly contents: "write";
|
|
8
|
+
readonly issues: "write";
|
|
9
|
+
readonly metadata: "read";
|
|
10
|
+
readonly pull_requests: "write";
|
|
11
|
+
};
|
|
12
|
+
export type RequiredRepositoryPermission = keyof typeof REQUIRED_REPOSITORY_PERMISSIONS;
|
|
13
|
+
/** The required repository permissions an installation grant does not satisfy, as `name:level` strings. */
|
|
14
|
+
export declare const missingPermissions: (granted: Readonly<Record<string, string>>) => ReadonlyArray<string>;
|
|
15
|
+
declare const CoveredProject_base: Schema.Class<CoveredProject, Schema.TaggedStruct<"CoveredProject", {
|
|
16
|
+
readonly installationId: Schema.brand<Schema.Int, "GitHubInstallationId">;
|
|
17
|
+
readonly repository: Schema.brand<Schema.String, "RepositoryName">;
|
|
18
|
+
readonly repositoryId: Schema.brand<Schema.Int, "GitHubRepositoryId">;
|
|
19
|
+
}>, {}>;
|
|
20
|
+
/** A registered repository covered by the discovered GitHub App installation. */
|
|
21
|
+
export declare class CoveredProject extends CoveredProject_base {
|
|
22
|
+
}
|
|
23
|
+
declare const ProjectNotInstalledError_base: Schema.Class<ProjectNotInstalledError, Schema.TaggedStruct<"ProjectNotInstalledError", {
|
|
24
|
+
readonly repository: Schema.brand<Schema.String, "RepositoryName">;
|
|
25
|
+
readonly message: Schema.NonEmptyString;
|
|
26
|
+
}>, import("effect/Cause").YieldableError>;
|
|
27
|
+
/** A registry repository the installation does not cover. Raised per project, so covered siblings still load. */
|
|
28
|
+
export declare class ProjectNotInstalledError extends ProjectNotInstalledError_base {
|
|
29
|
+
}
|
|
30
|
+
/** The installation coverage result for one registered repository. */
|
|
31
|
+
export declare const ProjectAccess: Schema.Union<readonly [typeof CoveredProject, typeof ProjectNotInstalledError]>;
|
|
32
|
+
export type ProjectAccess = typeof ProjectAccess.Type;
|
|
33
|
+
/** A repository reported by GitHub for an installation, identified by its numeric id and full name. */
|
|
34
|
+
export interface InstalledRepository {
|
|
35
|
+
readonly id: RepositoryId;
|
|
36
|
+
readonly name: RepositoryName;
|
|
37
|
+
}
|
|
38
|
+
/** Checks each registry repository against the installation's repositories and returns one `ProjectAccess` per project. */
|
|
39
|
+
export declare const deriveProjectAccess: (installation: Installation, registry: ReadonlyArray<RepositoryName>, installed: ReadonlyArray<InstalledRepository>) => ReadonlyArray<ProjectAccess>;
|
|
40
|
+
declare const AppPermissionError_base: Schema.Class<AppPermissionError, Schema.TaggedStruct<"AppPermissionError", {
|
|
41
|
+
readonly missing: Schema.$Array<Schema.NonEmptyString>;
|
|
42
|
+
readonly message: Schema.NonEmptyString;
|
|
43
|
+
}>, import("effect/Cause").YieldableError>;
|
|
44
|
+
/** The installation lacks required repository grants; missing entries use name:level form. */
|
|
45
|
+
export declare class AppPermissionError extends AppPermissionError_base {
|
|
46
|
+
}
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAkB,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE/E,yFAAyF;AACzF,eAAO,MAAM,+BAA+B;aAC1C,MAAM,EAAE,MAAM;aACd,QAAQ,EAAE,OAAO;aACjB,MAAM,EAAE,OAAO;aACf,QAAQ,EAAE,MAAM;aAChB,aAAa,EAAE,OAAO;CACd,CAAC;AAEX,MAAM,MAAM,4BAA4B,GAAG,MAAM,OAAO,+BAA+B,CAAC;AAKxF,2GAA2G;AAC3G,eAAO,MAAM,kBAAkB,YACpB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KACxC,aAAa,CAAC,MAAM,CAGpB,CAAC;;;;;;AAEJ,iFAAiF;AACjF,qBAAa,cAAe,SAAQ,mBAMlC;CAAG;;;;;AAEL,iHAAiH;AACjH,qBAAa,wBAAyB,SAAQ,6BAK5C;CAAG;AAEL,sEAAsE;AACtE,eAAO,MAAM,aAAa,iFAA2D,CAAC;AACtF,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;AAEtD,uGAAuG;AACvG,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;CAC/B;AAED,2HAA2H;AAC3H,eAAO,MAAM,mBAAmB,iBAChB,YAAY,YAChB,aAAa,CAAC,cAAc,CAAC,aAC5B,aAAa,CAAC,mBAAmB,CAAC,KAC5C,aAAa,CAAC,aAAa,CAe7B,CAAC;;;;;AAEF,8FAA8F;AAC9F,qBAAa,kBAAmB,SAAQ,uBAKtC;CAAG"}
|
package/dist/access.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { RepositoryName } from "@usine/core/repository";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
import { InstallationId, RepositoryId, Installation } from "./installation.js";
|
|
4
|
+
/** Repository grants required for polling, label updates, and agent-authored changes. */
|
|
5
|
+
export const REQUIRED_REPOSITORY_PERMISSIONS = {
|
|
6
|
+
checks: "read",
|
|
7
|
+
contents: "write",
|
|
8
|
+
issues: "write",
|
|
9
|
+
metadata: "read",
|
|
10
|
+
pull_requests: "write",
|
|
11
|
+
};
|
|
12
|
+
const grants = (actual, required) => actual === required || (required === "read" && actual === "write");
|
|
13
|
+
/** The required repository permissions an installation grant does not satisfy, as `name:level` strings. */
|
|
14
|
+
export const missingPermissions = (granted) => Object.entries(REQUIRED_REPOSITORY_PERMISSIONS).flatMap(([permission, required]) => grants(granted[permission], required) ? [] : [`${permission}:${required}`]);
|
|
15
|
+
/** A registered repository covered by the discovered GitHub App installation. */
|
|
16
|
+
export class CoveredProject extends Schema.TaggedClass("@usine/github/ProjectAccess/CoveredProject")("CoveredProject", {
|
|
17
|
+
installationId: InstallationId,
|
|
18
|
+
repository: RepositoryName,
|
|
19
|
+
repositoryId: RepositoryId,
|
|
20
|
+
}) {
|
|
21
|
+
}
|
|
22
|
+
/** A registry repository the installation does not cover. Raised per project, so covered siblings still load. */
|
|
23
|
+
export class ProjectNotInstalledError extends Schema.TaggedError("@usine/github/ProjectNotInstalledError")("ProjectNotInstalledError", {
|
|
24
|
+
repository: RepositoryName,
|
|
25
|
+
message: Schema.NonEmptyString,
|
|
26
|
+
}) {
|
|
27
|
+
}
|
|
28
|
+
/** The installation coverage result for one registered repository. */
|
|
29
|
+
export const ProjectAccess = Schema.Union([CoveredProject, ProjectNotInstalledError]);
|
|
30
|
+
/** Checks each registry repository against the installation's repositories and returns one `ProjectAccess` per project. */
|
|
31
|
+
export const deriveProjectAccess = (installation, registry, installed) => {
|
|
32
|
+
const byName = new Map(installed.map((repo) => [repo.name.toLowerCase(), repo]));
|
|
33
|
+
return registry.map((repository) => {
|
|
34
|
+
const match = byName.get(repository.toLowerCase());
|
|
35
|
+
return match === undefined
|
|
36
|
+
? new ProjectNotInstalledError({
|
|
37
|
+
repository,
|
|
38
|
+
message: `The GitHub App installation does not cover ${repository}`,
|
|
39
|
+
})
|
|
40
|
+
: new CoveredProject({
|
|
41
|
+
installationId: installation.id,
|
|
42
|
+
repository,
|
|
43
|
+
repositoryId: match.id,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
/** The installation lacks required repository grants; missing entries use name:level form. */
|
|
48
|
+
export class AppPermissionError extends Schema.TaggedError("@usine/github/AppPermissionError")("AppPermissionError", {
|
|
49
|
+
missing: Schema.Array(Schema.NonEmptyString).check(Schema.isNonEmpty()),
|
|
50
|
+
message: Schema.NonEmptyString,
|
|
51
|
+
}) {
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.js","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Context, Schema } from "effect";
|
|
2
|
+
/** The numeric GitHub App identity included in signed App JWTs. */
|
|
3
|
+
export declare const AppId: Schema.brand<Schema.Int, "GitHubAppId">;
|
|
4
|
+
export type AppId = typeof AppId.Type;
|
|
5
|
+
/** A PEM-encoded RSA private key used to authenticate the GitHub App. */
|
|
6
|
+
export declare const PrivateKey: Schema.brand<Schema.NonEmptyString, "GitHubAppPrivateKey">;
|
|
7
|
+
export type PrivateKey = typeof PrivateKey.Type;
|
|
8
|
+
/** An App signing key that stays redacted in diagnostics and cannot be encoded. */
|
|
9
|
+
export declare const RedactedPrivateKey: Schema.RedactedFromValue<Schema.brand<Schema.NonEmptyString, "GitHubAppPrivateKey">>;
|
|
10
|
+
declare const AppCredentials_base: Schema.Class<AppCredentials, Schema.Struct<{
|
|
11
|
+
readonly appId: Schema.brand<Schema.Int, "GitHubAppId">;
|
|
12
|
+
readonly privateKey: Schema.RedactedFromValue<Schema.brand<Schema.NonEmptyString, "GitHubAppPrivateKey">>;
|
|
13
|
+
}>, {}>;
|
|
14
|
+
/** The App credentials the orchestrator keeps and never passes to a worker. */
|
|
15
|
+
export declare class AppCredentials extends AppCredentials_base {
|
|
16
|
+
}
|
|
17
|
+
declare const GitHubCredentials_base: Context.ServiceClass<GitHubCredentials, "@usine/github/GitHubCredentials", AppCredentials>;
|
|
18
|
+
/** Supplies `AppCredentials`, so where credentials come from stays separate from how the App uses them. */
|
|
19
|
+
export declare class GitHubCredentials extends GitHubCredentials_base {
|
|
20
|
+
}
|
|
21
|
+
declare const AppAuthenticationError_base: Schema.Class<AppAuthenticationError, Schema.TaggedStruct<"AppAuthenticationError", {
|
|
22
|
+
readonly message: Schema.NonEmptyString;
|
|
23
|
+
}>, import("effect/Cause").YieldableError>;
|
|
24
|
+
/** The App private key cannot sign. Raised once, when the App layer is built. */
|
|
25
|
+
export declare class AppAuthenticationError extends AppAuthenticationError_base {
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEzC,mEAAmE;AACnE,eAAO,MAAM,KAAK,yCAEjB,CAAC;AACF,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAC;AAEtC,yEAAyE;AACzE,eAAO,MAAM,UAAU,4DAKoB,CAAC;AAC5C,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAC;AAEhD,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,sFAAiE,CAAC;;;;;AAEjG,+EAA+E;AAC/E,qBAAa,cAAe,SAAQ,mBAGlC;CAAG;;AAEL,2GAA2G;AAC3G,qBAAa,iBAAkB,SAAQ,sBAEtC;CAAG;;;;AAEJ,iFAAiF;AACjF,qBAAa,sBAAuB,SAAQ,2BAI1C;CAAG"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Context, Schema } from "effect";
|
|
2
|
+
/** The numeric GitHub App identity included in signed App JWTs. */
|
|
3
|
+
export const AppId = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)).pipe(Schema.brand("GitHubAppId"));
|
|
4
|
+
/** A PEM-encoded RSA private key used to authenticate the GitHub App. */
|
|
5
|
+
export const PrivateKey = Schema.NonEmptyString.check(Schema.isPattern(/^-----BEGIN (?:RSA )?PRIVATE KEY-----[\s\S]+-----END (?:RSA )?PRIVATE KEY-----\s*$/u, { message: "Expected a PEM-encoded RSA private key" })).pipe(Schema.brand("GitHubAppPrivateKey"));
|
|
6
|
+
/** An App signing key that stays redacted in diagnostics and cannot be encoded. */
|
|
7
|
+
export const RedactedPrivateKey = Schema.RedactedFromValue(PrivateKey, { disallowEncode: true });
|
|
8
|
+
/** The App credentials the orchestrator keeps and never passes to a worker. */
|
|
9
|
+
export class AppCredentials extends Schema.Class("@usine/github/AppCredentials")({
|
|
10
|
+
appId: AppId,
|
|
11
|
+
privateKey: RedactedPrivateKey,
|
|
12
|
+
}) {
|
|
13
|
+
}
|
|
14
|
+
/** Supplies `AppCredentials`, so where credentials come from stays separate from how the App uses them. */
|
|
15
|
+
export class GitHubCredentials extends Context.Service()("@usine/github/GitHubCredentials") {
|
|
16
|
+
}
|
|
17
|
+
/** The App private key cannot sign. Raised once, when the App layer is built. */
|
|
18
|
+
export class AppAuthenticationError extends Schema.TaggedError("@usine/github/AppAuthenticationError")("AppAuthenticationError", {
|
|
19
|
+
message: Schema.NonEmptyString,
|
|
20
|
+
}) {
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEzC,mEAAmE;AACnE,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC1E,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAC5B,CAAC;AAGF,yEAAyE;AACzE,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CACnD,MAAM,CAAC,SAAS,CACd,qFAAqF,EACrF,EAAE,OAAO,EAAE,wCAAwC,EAAE,CACtD,CACF,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAC;AAG5C,mFAAmF;AACnF,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,iBAAiB,CAAC,UAAU,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;AAEjG,+EAA+E;AAC/E,MAAM,OAAO,cAAe,SAAQ,MAAM,CAAC,KAAK,CAAiB,8BAA8B,CAAC,CAAC;IAC/F,KAAK,EAAE,KAAK;IACZ,UAAU,EAAE,kBAAkB;CAC/B,CAAC;CAAG;AAEL,2GAA2G;AAC3G,MAAM,OAAO,iBAAkB,SAAQ,OAAO,CAAC,OAAO,EAAqC,CACzF,iCAAiC,CAClC;CAAG;AAEJ,iFAAiF;AACjF,MAAM,OAAO,sBAAuB,SAAQ,MAAM,CAAC,WAAW,CAC5D,sCAAsC,CACvC,CAAC,wBAAwB,EAAE;IAC1B,OAAO,EAAE,MAAM,CAAC,cAAc;CAC/B,CAAC;CAAG"}
|
package/dist/error.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { InstallationDiscoveryError } from "./installation.ts";
|
|
3
|
+
import { AppPermissionError } from "./access.ts";
|
|
4
|
+
/** The GitHub request that failed, shared by API, rate-limit, and response errors. */
|
|
5
|
+
export declare const GitHubOperation: Schema.Literals<readonly ["create-repository-label", "discover-installation", "fetch-item-labels", "fetch-issue-snapshot", "fetch-pull-request-snapshot", "list-installation-repositories", "list-repository-labels", "mint-installation-token", "mint-worker-token", "poll-repository-changes", "update-item-labels"]>;
|
|
6
|
+
export type GitHubOperation = typeof GitHubOperation.Type;
|
|
7
|
+
declare const GitHubApiError_base: Schema.Class<GitHubApiError, Schema.TaggedStruct<"GitHubApiError", {
|
|
8
|
+
readonly operation: Schema.Literals<readonly ["create-repository-label", "discover-installation", "fetch-item-labels", "fetch-issue-snapshot", "fetch-pull-request-snapshot", "list-installation-repositories", "list-repository-labels", "mint-installation-token", "mint-worker-token", "poll-repository-changes", "update-item-labels"]>;
|
|
9
|
+
readonly status: Schema.Option<Schema.Int>;
|
|
10
|
+
readonly message: Schema.NonEmptyString;
|
|
11
|
+
}>, import("effect/Cause").YieldableError>;
|
|
12
|
+
/** A failed GitHub request, with an HTTP status when a response was received. */
|
|
13
|
+
export declare class GitHubApiError extends GitHubApiError_base {
|
|
14
|
+
}
|
|
15
|
+
declare const GitHubRateLimitError_base: Schema.Class<GitHubRateLimitError, Schema.TaggedStruct<"GitHubRateLimitError", {
|
|
16
|
+
readonly operation: Schema.Literals<readonly ["create-repository-label", "discover-installation", "fetch-item-labels", "fetch-issue-snapshot", "fetch-pull-request-snapshot", "list-installation-repositories", "list-repository-labels", "mint-installation-token", "mint-worker-token", "poll-repository-changes", "update-item-labels"]>;
|
|
17
|
+
readonly retryAt: Schema.DateTimeUtc;
|
|
18
|
+
readonly message: Schema.NonEmptyString;
|
|
19
|
+
}>, import("effect/Cause").YieldableError>;
|
|
20
|
+
/** GitHub deferred the request until retryAt; callers can schedule a later attempt. */
|
|
21
|
+
export declare class GitHubRateLimitError extends GitHubRateLimitError_base {
|
|
22
|
+
}
|
|
23
|
+
declare const InvalidGitHubResponseError_base: Schema.Class<InvalidGitHubResponseError, Schema.TaggedStruct<"InvalidGitHubResponseError", {
|
|
24
|
+
readonly operation: Schema.Literals<readonly ["create-repository-label", "discover-installation", "fetch-item-labels", "fetch-issue-snapshot", "fetch-pull-request-snapshot", "list-installation-repositories", "list-repository-labels", "mint-installation-token", "mint-worker-token", "poll-repository-changes", "update-item-labels"]>;
|
|
25
|
+
readonly message: Schema.NonEmptyString;
|
|
26
|
+
/** What made the response invalid: the decode path, the missing field, or the GraphQL errors. */
|
|
27
|
+
readonly detail: Schema.NonEmptyString;
|
|
28
|
+
}>, import("effect/Cause").YieldableError>;
|
|
29
|
+
/** GitHub returned a response that cannot be decoded or is missing required content. */
|
|
30
|
+
export declare class InvalidGitHubResponseError extends InvalidGitHubResponseError_base {
|
|
31
|
+
}
|
|
32
|
+
/** Failures from discovering installation access or making an authenticated GitHub request. */
|
|
33
|
+
export declare const GitHubAppError: Schema.Union<readonly [typeof InstallationDiscoveryError, typeof AppPermissionError, typeof GitHubApiError, typeof GitHubRateLimitError, typeof InvalidGitHubResponseError]>;
|
|
34
|
+
export type GitHubAppError = typeof GitHubAppError.Type;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,sFAAsF;AACtF,eAAO,MAAM,eAAe,yTAY1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAC,IAAI,CAAC;;;;;;AAE1D,iFAAiF;AACjF,qBAAa,cAAe,SAAQ,mBAMlC;CAAG;;;;;;AAEL,uFAAuF;AACvF,qBAAa,oBAAqB,SAAQ,yBAMxC;CAAG;;;;IAQH,iGAAiG;;;AANnG,wFAAwF;AACxF,qBAAa,0BAA2B,SAAQ,+BAO9C;CAAG;AAEL,+FAA+F;AAC/F,eAAO,MAAM,cAAc,8KAMzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAC"}
|
package/dist/error.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { InstallationDiscoveryError } from "./installation.js";
|
|
3
|
+
import { AppPermissionError } from "./access.js";
|
|
4
|
+
/** The GitHub request that failed, shared by API, rate-limit, and response errors. */
|
|
5
|
+
export const GitHubOperation = Schema.Literals([
|
|
6
|
+
"create-repository-label",
|
|
7
|
+
"discover-installation",
|
|
8
|
+
"fetch-item-labels",
|
|
9
|
+
"fetch-issue-snapshot",
|
|
10
|
+
"fetch-pull-request-snapshot",
|
|
11
|
+
"list-installation-repositories",
|
|
12
|
+
"list-repository-labels",
|
|
13
|
+
"mint-installation-token",
|
|
14
|
+
"mint-worker-token",
|
|
15
|
+
"poll-repository-changes",
|
|
16
|
+
"update-item-labels",
|
|
17
|
+
]);
|
|
18
|
+
/** A failed GitHub request, with an HTTP status when a response was received. */
|
|
19
|
+
export class GitHubApiError extends Schema.TaggedError("@usine/github/GitHubApiError")("GitHubApiError", {
|
|
20
|
+
operation: GitHubOperation,
|
|
21
|
+
status: Schema.Option(Schema.Int),
|
|
22
|
+
message: Schema.NonEmptyString,
|
|
23
|
+
}) {
|
|
24
|
+
}
|
|
25
|
+
/** GitHub deferred the request until retryAt; callers can schedule a later attempt. */
|
|
26
|
+
export class GitHubRateLimitError extends Schema.TaggedError("@usine/github/GitHubRateLimitError")("GitHubRateLimitError", {
|
|
27
|
+
operation: GitHubOperation,
|
|
28
|
+
retryAt: Schema.DateTimeUtc,
|
|
29
|
+
message: Schema.NonEmptyString,
|
|
30
|
+
}) {
|
|
31
|
+
}
|
|
32
|
+
/** GitHub returned a response that cannot be decoded or is missing required content. */
|
|
33
|
+
export class InvalidGitHubResponseError extends Schema.TaggedError("@usine/github/InvalidGitHubResponseError")("InvalidGitHubResponseError", {
|
|
34
|
+
operation: GitHubOperation,
|
|
35
|
+
message: Schema.NonEmptyString,
|
|
36
|
+
/** What made the response invalid: the decode path, the missing field, or the GraphQL errors. */
|
|
37
|
+
detail: Schema.NonEmptyString,
|
|
38
|
+
}) {
|
|
39
|
+
}
|
|
40
|
+
/** Failures from discovering installation access or making an authenticated GitHub request. */
|
|
41
|
+
export const GitHubAppError = Schema.Union([
|
|
42
|
+
InstallationDiscoveryError,
|
|
43
|
+
AppPermissionError,
|
|
44
|
+
GitHubApiError,
|
|
45
|
+
GitHubRateLimitError,
|
|
46
|
+
InvalidGitHubResponseError,
|
|
47
|
+
]);
|
|
48
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,sFAAsF;AACtF,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC7C,yBAAyB;IACzB,uBAAuB;IACvB,mBAAmB;IACnB,sBAAsB;IACtB,6BAA6B;IAC7B,gCAAgC;IAChC,wBAAwB;IACxB,yBAAyB;IACzB,mBAAmB;IACnB,yBAAyB;IACzB,oBAAoB;CACrB,CAAC,CAAC;AAGH,iFAAiF;AACjF,MAAM,OAAO,cAAe,SAAQ,MAAM,CAAC,WAAW,CACpD,8BAA8B,CAC/B,CAAC,gBAAgB,EAAE;IAClB,SAAS,EAAE,eAAe;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC,cAAc;CAC/B,CAAC;CAAG;AAEL,uFAAuF;AACvF,MAAM,OAAO,oBAAqB,SAAQ,MAAM,CAAC,WAAW,CAC1D,oCAAoC,CACrC,CAAC,sBAAsB,EAAE;IACxB,SAAS,EAAE,eAAe;IAC1B,OAAO,EAAE,MAAM,CAAC,WAAW;IAC3B,OAAO,EAAE,MAAM,CAAC,cAAc;CAC/B,CAAC;CAAG;AAEL,wFAAwF;AACxF,MAAM,OAAO,0BAA2B,SAAQ,MAAM,CAAC,WAAW,CAChE,0CAA0C,CAC3C,CAAC,4BAA4B,EAAE;IAC9B,SAAS,EAAE,eAAe;IAC1B,OAAO,EAAE,MAAM,CAAC,cAAc;IAC9B,iGAAiG;IACjG,MAAM,EAAE,MAAM,CAAC,cAAc;CAC9B,CAAC;CAAG;AAEL,+FAA+F;AAC/F,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC;IACzC,0BAA0B;IAC1B,kBAAkB;IAClB,cAAc;IACd,oBAAoB;IACpB,0BAA0B;CAC3B,CAAC,CAAC"}
|
package/dist/github-app.d.ts
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RepositoryName } from "@usine/core/repository";
|
|
2
2
|
import { Context, Effect, Layer } from "effect";
|
|
3
3
|
import { HttpClient } from "effect/unstable/http";
|
|
4
|
-
import { type AppAuthenticationError,
|
|
4
|
+
import { type AppAuthenticationError, GitHubCredentials } from "./credentials.ts";
|
|
5
|
+
import { type CoveredProject, type ProjectAccess } from "./access.ts";
|
|
6
|
+
import { type GitHubAppError } from "./error.ts";
|
|
7
|
+
import { Installation, InstallationToken } from "./installation.ts";
|
|
5
8
|
declare const GitHubApp_base: Context.ServiceClass<GitHubApp, "@usine/github/GitHubApp", {
|
|
9
|
+
/** Returns the App installation after checking its required permissions. */
|
|
6
10
|
readonly installation: () => Effect.Effect<Installation, GitHubAppError>;
|
|
11
|
+
/** Returns the shared installation-wide token for orchestrator operations, renewing it near expiry. */
|
|
7
12
|
readonly installationToken: () => Effect.Effect<InstallationToken, GitHubAppError>;
|
|
8
|
-
|
|
13
|
+
/** Reports access for every registry repository, retaining uncovered Projects as individual errors. */
|
|
14
|
+
readonly projectAccess: (registry: ReadonlyArray<RepositoryName>) => Effect.Effect<ReadonlyArray<ProjectAccess>, GitHubAppError>;
|
|
15
|
+
/** Mints a fresh token restricted to the covered Project's repository for a Worker. */
|
|
9
16
|
readonly workerToken: (project: CoveredProject) => Effect.Effect<InstallationToken, GitHubAppError>;
|
|
10
17
|
}>;
|
|
11
18
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* The orchestrator-side operations share one lease — a single discovery and
|
|
16
|
-
* installation-wide mint reused until the token nears expiry — so polling does
|
|
17
|
-
* not spend two App requests per call. Worker tokens are minted per call.
|
|
19
|
+
* The factory's GitHub identity and repository access.
|
|
20
|
+
* Orchestrator operations share installation-wide access; each Worker token is
|
|
21
|
+
* newly minted for the repository assigned to its Attempt.
|
|
18
22
|
*/
|
|
19
23
|
export declare class GitHubApp extends GitHubApp_base {
|
|
20
24
|
static readonly layer: Layer.Layer<GitHubApp, AppAuthenticationError, GitHubCredentials | HttpClient.HttpClient>;
|
|
21
25
|
}
|
|
22
|
-
export {};
|
|
26
|
+
export { REQUIRED_REPOSITORY_PERMISSIONS, deriveProjectAccess, missingPermissions, type InstalledRepository, type RequiredRepositoryPermission, CoveredProject, ProjectNotInstalledError, ProjectAccess, AppPermissionError, } from "./access.ts";
|
|
27
|
+
export { AppId, PrivateKey, RedactedPrivateKey, AppCredentials, GitHubCredentials, AppAuthenticationError, } from "./credentials.ts";
|
|
28
|
+
export { InstallationId, RepositoryId, Installation, InstallationWide, SingleRepository, TokenScope, InstallationToken, InstallationDiscoveryReason, InstallationDiscoveryError, } from "./installation.ts";
|
|
29
|
+
export { GitHubOperation, GitHubApiError, GitHubRateLimitError, InvalidGitHubResponseError, GitHubAppError, } from "./error.ts";
|
|
23
30
|
//# sourceMappingURL=github-app.d.ts.map
|
package/dist/github-app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-app.d.ts","sourceRoot":"","sources":["../src/github-app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"github-app.d.ts","sourceRoot":"","sources":["../src/github-app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAEL,OAAO,EAEP,MAAM,EACN,KAAK,EAKN,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAE,UAAU,EAAyC,MAAM,sBAAsB,CAAC;AAGzF,OAAO,EAAE,KAAK,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAEL,KAAK,cAAc,EAEnB,KAAK,aAAa,EAInB,MAAM,aAAa,CAAC;AACrB,OAAO,EAEL,KAAK,cAAc,EAIpB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,YAAY,EAGZ,iBAAiB,EAKlB,MAAM,mBAAmB,CAAC;;IA8DvB,4EAA4E;2BACrD,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,cAAc,CAAC;IACxE,uGAAuG;gCAC3E,MAAM,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,cAAc,CAAC;IAClF,uGAAuG;4BAC/E,CACtB,QAAQ,EAAE,aAAa,CAAC,cAAc,CAAC,KACpC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,EAAE,cAAc,CAAC;IAChE,uFAAuF;0BACjE,CACpB,OAAO,EAAE,cAAc,KACpB,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,cAAc,CAAC;;AAnBzD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAgBD;IAC5B,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAChC,SAAS,EACT,sBAAsB,EACtB,iBAAiB,GAAG,UAAU,CAAC,UAAU,CAC1C,CAuLC;CACH;AAED,OAAO,EACL,+BAA+B,EAC/B,mBAAmB,EACnB,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,4BAA4B,EACjC,cAAc,EACd,wBAAwB,EACxB,aAAa,EACb,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,EACL,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,iBAAiB,EACjB,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,0BAA0B,EAC1B,cAAc,GACf,MAAM,YAAY,CAAC"}
|
package/dist/github-app.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RepositoryName } from "@usine/core/repository";
|
|
2
2
|
import { Clock, Context, DateTime, Effect, Layer, Option, Redacted, Schema, SynchronizedRef, } from "effect";
|
|
3
3
|
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
|
|
4
4
|
import { appJwtSigner } from "./jwt.js";
|
|
5
5
|
import { API_ROOT, authorized, invalidResponse, statusError, transportError } from "./http.js";
|
|
6
|
-
import {
|
|
6
|
+
import { GitHubCredentials } from "./credentials.js";
|
|
7
|
+
import { AppPermissionError, REQUIRED_REPOSITORY_PERMISSIONS, deriveProjectAccess, missingPermissions, } from "./access.js";
|
|
8
|
+
import { InvalidGitHubResponseError, } from "./error.js";
|
|
9
|
+
import { Installation, InstallationDiscoveryError, InstallationId, InstallationToken, InstallationWide, RepositoryId, SingleRepository, } from "./installation.js";
|
|
7
10
|
const TOKEN_MAX_LIFETIME_MS = 60 * 60 * 1_000;
|
|
8
|
-
/**
|
|
11
|
+
/** GitHub's expiry uses its own clock; ordinary worker/orchestrator skew is not a bad token. */
|
|
12
|
+
const TOKEN_CLOCK_SKEW_MARGIN_MS = 3 * 60 * 1_000;
|
|
13
|
+
/** Renew the shared lease when its token has less than this long left. */
|
|
9
14
|
const LEASE_RENEWAL_MARGIN_MS = 5 * 60 * 1_000;
|
|
10
15
|
const InstallationResponse = Schema.Struct({
|
|
11
16
|
id: InstallationId,
|
|
@@ -16,7 +21,7 @@ const InstallationResponse = Schema.Struct({
|
|
|
16
21
|
const InstallationsResponse = Schema.Array(InstallationResponse);
|
|
17
22
|
const RepositoryResponse = Schema.Struct({
|
|
18
23
|
id: RepositoryId,
|
|
19
|
-
name:
|
|
24
|
+
name: RepositoryName,
|
|
20
25
|
}).pipe(Schema.encodeKeys({ name: "full_name" }));
|
|
21
26
|
const RepositoriesResponse = Schema.Struct({
|
|
22
27
|
totalCount: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
@@ -27,7 +32,7 @@ const TokenResponse = Schema.Struct({
|
|
|
27
32
|
expiresAt: Schema.DateTimeUtcFromString,
|
|
28
33
|
repositories: Schema.OptionFromOptionalKey(Schema.Array(RepositoryResponse)),
|
|
29
34
|
}).pipe(Schema.encodeKeys({ expiresAt: "expires_at" }));
|
|
30
|
-
/**
|
|
35
|
+
/** The operation name and request body for minting a token of this scope. */
|
|
31
36
|
const mintPlan = (scope) => scope._tag === "InstallationWide"
|
|
32
37
|
? {
|
|
33
38
|
operation: "mint-installation-token",
|
|
@@ -41,19 +46,16 @@ const mintPlan = (scope) => scope._tag === "InstallationWide"
|
|
|
41
46
|
},
|
|
42
47
|
};
|
|
43
48
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* The orchestrator-side operations share one lease — a single discovery and
|
|
48
|
-
* installation-wide mint reused until the token nears expiry — so polling does
|
|
49
|
-
* not spend two App requests per call. Worker tokens are minted per call.
|
|
49
|
+
* The factory's GitHub identity and repository access.
|
|
50
|
+
* Orchestrator operations share installation-wide access; each Worker token is
|
|
51
|
+
* newly minted for the repository assigned to its Attempt.
|
|
50
52
|
*/
|
|
51
53
|
export class GitHubApp extends Context.Service()("@usine/github/GitHubApp") {
|
|
52
54
|
static layer = Layer.effect(GitHubApp, Effect.gen(function* () {
|
|
53
55
|
const credentials = yield* GitHubCredentials;
|
|
54
56
|
const sign = yield* appJwtSigner(credentials);
|
|
55
57
|
const http = yield* HttpClient.HttpClient;
|
|
56
|
-
/**
|
|
58
|
+
/** Sends one request and decodes its JSON body. Any other outcome is a typed GitHub error. */
|
|
57
59
|
const fetchJson = (operation, request, schema) => Effect.gen(function* () {
|
|
58
60
|
const response = yield* http
|
|
59
61
|
.execute(request)
|
|
@@ -95,16 +97,25 @@ export class GitHubApp extends Context.Service()("@usine/github/GitHubApp") {
|
|
|
95
97
|
});
|
|
96
98
|
const mintToken = Effect.fn("GitHubApp.mintToken")(function* (installationId, scope) {
|
|
97
99
|
const { operation, body } = mintPlan(scope);
|
|
100
|
+
yield* Effect.annotateCurrentSpan({ installationId, operation, scope: scope._tag });
|
|
98
101
|
const jwt = yield* sign();
|
|
99
102
|
const response = yield* fetchJson(operation, HttpClientRequest.post(`${API_ROOT}/app/installations/${installationId}/access_tokens`).pipe(HttpClientRequest.bodyJsonUnsafe(body), authorized(jwt)), TokenResponse);
|
|
100
|
-
const
|
|
101
|
-
|
|
103
|
+
const now = yield* Clock.currentTimeMillis;
|
|
104
|
+
const lifetime = DateTime.toEpochMillis(response.expiresAt) - now;
|
|
105
|
+
if (lifetime <= 0 || lifetime > TOKEN_MAX_LIFETIME_MS + TOKEN_CLOCK_SKEW_MARGIN_MS) {
|
|
102
106
|
return yield* new InvalidGitHubResponseError({
|
|
103
107
|
operation,
|
|
104
108
|
message: "GitHub returned an installation token outside the one-hour lifetime bound",
|
|
105
109
|
detail: `expires_at ${DateTime.formatIso(response.expiresAt)} leaves ${lifetime}ms to live`,
|
|
106
110
|
});
|
|
107
111
|
}
|
|
112
|
+
if (lifetime > TOKEN_MAX_LIFETIME_MS) {
|
|
113
|
+
yield* Effect.logInfo("Accepted GitHub token clock skew; capped the local lease", {
|
|
114
|
+
installationId,
|
|
115
|
+
operation,
|
|
116
|
+
skewMillis: lifetime - TOKEN_MAX_LIFETIME_MS,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
108
119
|
const confirmed = Option.getOrElse(response.repositories, () => []);
|
|
109
120
|
if (scope._tag === "SingleRepository" &&
|
|
110
121
|
!(confirmed.length === 1 && confirmed.every((repo) => repo.id === scope.repositoryId))) {
|
|
@@ -116,7 +127,7 @@ export class GitHubApp extends Context.Service()("@usine/github/GitHubApp") {
|
|
|
116
127
|
}
|
|
117
128
|
return new InstallationToken({
|
|
118
129
|
value: Redacted.make(response.token),
|
|
119
|
-
expiresAt:
|
|
130
|
+
expiresAt: DateTime.makeUnsafe(now + Math.min(lifetime, TOKEN_MAX_LIFETIME_MS)),
|
|
120
131
|
scope,
|
|
121
132
|
});
|
|
122
133
|
});
|
|
@@ -137,7 +148,7 @@ export class GitHubApp extends Context.Service()("@usine/github/GitHubApp") {
|
|
|
137
148
|
}
|
|
138
149
|
});
|
|
139
150
|
const leaseRef = yield* SynchronizedRef.make(Option.none());
|
|
140
|
-
/**
|
|
151
|
+
/** Returns the current lease, renewing it inside the ref when its token is under the margin. */
|
|
141
152
|
const lease = Effect.fn("GitHubApp.lease")(() => SynchronizedRef.modifyEffect(leaseRef, (current) => Effect.gen(function* () {
|
|
142
153
|
const now = yield* Clock.currentTimeMillis;
|
|
143
154
|
if (Option.isSome(current) &&
|
|
@@ -166,4 +177,8 @@ export class GitHubApp extends Context.Service()("@usine/github/GitHubApp") {
|
|
|
166
177
|
};
|
|
167
178
|
}));
|
|
168
179
|
}
|
|
180
|
+
export { REQUIRED_REPOSITORY_PERMISSIONS, deriveProjectAccess, missingPermissions, CoveredProject, ProjectNotInstalledError, ProjectAccess, AppPermissionError, } from "./access.js";
|
|
181
|
+
export { AppId, PrivateKey, RedactedPrivateKey, AppCredentials, GitHubCredentials, AppAuthenticationError, } from "./credentials.js";
|
|
182
|
+
export { InstallationId, RepositoryId, Installation, InstallationWide, SingleRepository, TokenScope, InstallationToken, InstallationDiscoveryReason, InstallationDiscoveryError, } from "./installation.js";
|
|
183
|
+
export { GitHubOperation, GitHubApiError, GitHubRateLimitError, InvalidGitHubResponseError, GitHubAppError, } from "./error.js";
|
|
169
184
|
//# sourceMappingURL=github-app.js.map
|
package/dist/github-app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-app.js","sourceRoot":"","sources":["../src/github-app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"github-app.js","sourceRoot":"","sources":["../src/github-app.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { Option, Redacted } from "effect";
|
|
2
2
|
import { HttpClientRequest, type HttpClientResponse } from "effect/unstable/http";
|
|
3
3
|
import type { HttpClientError } from "effect/unstable/http/HttpClientError";
|
|
4
|
-
import { GitHubApiError, GitHubRateLimitError, InvalidGitHubResponseError, type GitHubOperation } from "./
|
|
4
|
+
import { GitHubApiError, GitHubRateLimitError, InvalidGitHubResponseError, type GitHubOperation } from "./error.ts";
|
|
5
5
|
export declare const API_ROOT = "https://api.github.com";
|
|
6
|
-
export declare const API_VERSION = "2026-03-10";
|
|
7
6
|
export declare const authorized: (credential: Redacted.Redacted<string>) => (self: HttpClientRequest.HttpClientRequest) => HttpClientRequest.HttpClientRequest;
|
|
8
7
|
export declare const transportError: (operation: GitHubOperation, error: HttpClientError) => GitHubApiError;
|
|
9
8
|
export declare const invalidResponse: (operation: GitHubOperation, detail: string) => InvalidGitHubResponseError;
|
|
@@ -11,9 +10,9 @@ type Headers = HttpClientResponse.HttpClientResponse["headers"];
|
|
|
11
10
|
export declare const retryAfterMillis: (value: string | undefined, now: number) => Option.Option<number>;
|
|
12
11
|
export declare const rateLimitResetMillis: (headers: Headers, threshold: number) => Option.Option<number>;
|
|
13
12
|
export declare const pollIntervalMillis: (headers: Headers, now: number) => Option.Option<number>;
|
|
14
|
-
/**
|
|
13
|
+
/** The epoch when GitHub allows the next request, from `retry-after` and an exhausted primary rate-limit budget. */
|
|
15
14
|
export declare const rateLimitRetryEpoch: (headers: Headers, now: number) => Option.Option<number>;
|
|
16
|
-
/**
|
|
15
|
+
/** Turns a non-2xx GitHub response into a typed error. A rate-limit status with a known retry time becomes a wait, not a failure. */
|
|
17
16
|
export declare const statusError: (operation: GitHubOperation, response: HttpClientResponse.HttpClientResponse, now: number) => GitHubApiError | GitHubRateLimitError;
|
|
18
17
|
export {};
|
|
19
18
|
//# sourceMappingURL=http.d.ts.map
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,0BAA0B,EAC1B,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,QAAQ,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,0BAA0B,EAC1B,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,QAAQ,2BAA2B,CAAC;AAGjD,eAAO,MAAM,UAAU,eAAgB,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,uFAM5D,CAAC;AAEL,eAAO,MAAM,cAAc,cAAe,eAAe,SAAS,eAAe,mBAQ7E,CAAC;AAEL,eAAO,MAAM,eAAe,cAAe,eAAe,UAAU,MAAM,+BAKtE,CAAC;AAEL,KAAK,OAAO,GAAG,kBAAkB,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;AAQhE,eAAO,MAAM,gBAAgB,UAAW,MAAM,GAAG,SAAS,OAAO,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAM7F,CAAC;AAEF,eAAO,MAAM,oBAAoB,YACtB,OAAO,aACL,MAAM,KAChB,MAAM,CAAC,MAAM,CAAC,MAAM,CAMtB,CAAC;AAEF,eAAO,MAAM,kBAAkB,YAAa,OAAO,OAAO,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CACG,CAAC;AAS3F,oHAAoH;AACpH,eAAO,MAAM,mBAAmB,YAAa,OAAO,OAAO,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CACQ,CAAC;AAEjG,qIAAqI;AACrI,eAAO,MAAM,WAAW,cACX,eAAe,YAChB,kBAAkB,CAAC,kBAAkB,OAC1C,MAAM,KACV,cAAc,GAAG,oBAanB,CAAC"}
|
package/dist/http.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { DateTime, Option, Redacted } from "effect";
|
|
2
2
|
import { HttpClientRequest } from "effect/unstable/http";
|
|
3
|
-
import { GitHubApiError, GitHubRateLimitError, InvalidGitHubResponseError, } from "./
|
|
3
|
+
import { GitHubApiError, GitHubRateLimitError, InvalidGitHubResponseError, } from "./error.js";
|
|
4
4
|
export const API_ROOT = "https://api.github.com";
|
|
5
|
-
|
|
5
|
+
const API_VERSION = "2026-03-10";
|
|
6
6
|
export const authorized = (credential) => HttpClientRequest.setHeaders({
|
|
7
7
|
accept: "application/vnd.github+json",
|
|
8
8
|
authorization: `Bearer ${Redacted.value(credential)}`,
|
|
@@ -48,9 +48,9 @@ const latestEpoch = (candidates) => {
|
|
|
48
48
|
const epochs = candidates.flatMap((candidate) => Option.match(candidate, { onNone: () => [], onSome: (value) => [value] }));
|
|
49
49
|
return epochs.length === 0 ? Option.none() : Option.some(Math.max(...epochs));
|
|
50
50
|
};
|
|
51
|
-
/**
|
|
51
|
+
/** The epoch when GitHub allows the next request, from `retry-after` and an exhausted primary rate-limit budget. */
|
|
52
52
|
export const rateLimitRetryEpoch = (headers, now) => latestEpoch([retryAfterMillis(headers["retry-after"], now), rateLimitResetMillis(headers, 0)]);
|
|
53
|
-
/**
|
|
53
|
+
/** Turns a non-2xx GitHub response into a typed error. A rate-limit status with a known retry time becomes a wait, not a failure. */
|
|
54
54
|
export const statusError = (operation, response, now) => {
|
|
55
55
|
const retryAt = Option.flatMap(rateLimitRetryEpoch(response.headers, now), DateTime.make);
|
|
56
56
|
return (response.status === 403 || response.status === 429) && Option.isSome(retryAt)
|