@fungi.computer/oyster 0.4.0 → 0.4.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/dist/app-builds.d.ts +1 -1
- package/dist/app-builds.js +16 -6
- package/dist/boot-capability.d.ts +2 -1
- package/dist/boot-capability.js +7 -5
- package/dist/build/build-jobs.d.ts +3 -3
- package/dist/build/build-jobs.js +10 -5
- package/dist/build/candidate-cli.js +5 -3
- package/dist/build/candidate-cli.mjs +223 -5
- package/dist/build/candidate-diagnostic.d.ts +2 -0
- package/dist/build/candidate-diagnostic.js +16 -0
- package/dist/standalone/agent-model-choice.d.ts +4 -1
- package/dist/standalone/app-set.d.ts +8 -1
- package/dist/standalone/app-set.js +46 -31
- package/dist/standalone/install-apps-bin.d.ts +2 -0
- package/dist/standalone/install-apps-bin.js +3 -0
- package/dist/standalone/install-apps-cli.d.ts +3 -1
- package/dist/standalone/install-apps-cli.js +27 -17
- package/dist/standalone/registry.d.ts +1 -1
- package/dist/workers.js +9 -1
- package/package.json +2 -2
package/dist/app-builds.d.ts
CHANGED
|
@@ -97,7 +97,7 @@ export type OysterBuildView = Readonly<{
|
|
|
97
97
|
appId: string;
|
|
98
98
|
releaseId: string;
|
|
99
99
|
sourceRevision: string;
|
|
100
|
-
status: "running" | "succeeded" | "failed" | "revoked";
|
|
100
|
+
status: "running" | "succeeded" | "failed" | "interrupted" | "revoked";
|
|
101
101
|
detail: string | null;
|
|
102
102
|
release: Release | null;
|
|
103
103
|
installation: OysterBuildInstallation | null;
|
package/dist/app-builds.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import * as Schema from "effect/Schema";
|
|
20
20
|
import * as Data from "effect/Data";
|
|
21
21
|
import * as Option from "effect/Option";
|
|
22
|
+
import { candidateFailureDiagnostic } from "./build/candidate-diagnostic.js";
|
|
22
23
|
import { parseArtifactRef, parseReleaseId, } from "@fungi.computer/puffball";
|
|
23
24
|
import { appIdSchema, releaseSchema, sha256DigestSchema, sourceRevisionSchema, } from "@fungi.computer/puffball/effect";
|
|
24
25
|
import { capturedPinnedRecipeSchema, normalizeAppPublisherRecipe, digestPinnedRecipe, fitsPinnedRecipe, pinnedRecipeObjectBound, } from "./build/effect.js";
|
|
@@ -116,7 +117,13 @@ const jobRow = Schema.Struct({
|
|
|
116
117
|
credential_id: Schema.String,
|
|
117
118
|
upload_grant_digest: Schema.String,
|
|
118
119
|
request_id: Schema.NullOr(Schema.String),
|
|
119
|
-
status: Schema.Literals([
|
|
120
|
+
status: Schema.Literals([
|
|
121
|
+
"running",
|
|
122
|
+
"succeeded",
|
|
123
|
+
"failed",
|
|
124
|
+
"interrupted",
|
|
125
|
+
"revoked",
|
|
126
|
+
]),
|
|
120
127
|
artifact_digest: Schema.NullOr(Schema.String),
|
|
121
128
|
artifact_bytes: Schema.NullOr(Schema.Number),
|
|
122
129
|
detail: Schema.NullOr(Schema.String),
|
|
@@ -186,12 +193,12 @@ export function createOysterAppBuilds(options) {
|
|
|
186
193
|
// A job outlives the owner instance that started it: the Computer keeps
|
|
187
194
|
// building and its own reports (objects, candidate, status) settle the
|
|
188
195
|
// job, so an owner restart must not fail it. A job that reaches its expiry
|
|
189
|
-
// without a result can never settle, so it
|
|
196
|
+
// without a result can never settle, so it is interrupted instead of reading
|
|
190
197
|
// as running for as long as anyone asks.
|
|
191
198
|
const failedInstallation = `CASE WHEN installation IS NULL THEN NULL ELSE
|
|
192
199
|
json_object('kind', 'failed', 'installationId', json_extract(installation, '$.installationId'),
|
|
193
200
|
'detail', COALESCE(?, 'Build did not succeed')) END`;
|
|
194
|
-
const expire = () => owner.sql.exec(`UPDATE oyster_build_jobs SET installation=${failedInstallation}, status='
|
|
201
|
+
const expire = () => owner.sql.exec(`UPDATE oyster_build_jobs SET installation=${failedInstallation}, status='interrupted', detail=? WHERE status='running' AND expires_at<=?`, "Build expired without a result", "Build expired without a result", now());
|
|
195
202
|
expire();
|
|
196
203
|
const decodeRecipe = (recipe) => Schema.decodeUnknownSync(recipeCodec)(recipe);
|
|
197
204
|
const grantAdmits = async (job, grant) => sameDigest(job.upload_grant_digest, await sha256Hex(new TextEncoder().encode(grant)));
|
|
@@ -269,6 +276,9 @@ export function createOysterAppBuilds(options) {
|
|
|
269
276
|
return Promise.resolve();
|
|
270
277
|
},
|
|
271
278
|
execute: async (input) => {
|
|
279
|
+
// The Computer's connection may close without a terminal execution
|
|
280
|
+
// reply. A missing output is not an exit; a rejected RPC carrying an
|
|
281
|
+
// Error (including an admission refusal) remains a definite failure.
|
|
272
282
|
const result = await options.computer.port.runBuild({
|
|
273
283
|
jobId: input.jobId,
|
|
274
284
|
command: input.command,
|
|
@@ -298,10 +308,10 @@ export function createOysterAppBuilds(options) {
|
|
|
298
308
|
});
|
|
299
309
|
}
|
|
300
310
|
catch (cause) {
|
|
301
|
-
//
|
|
302
|
-
//
|
|
311
|
+
// An unknown output can belong to a live builder. Its reports can
|
|
312
|
+
// still succeed; the existing build expiry interrupts it if abandoned.
|
|
303
313
|
if (!(cause instanceof BuildOutcomeUnknown))
|
|
304
|
-
settle(jobId, "failed", cause
|
|
314
|
+
settle(jobId, "failed", candidateFailureDiagnostic(cause));
|
|
305
315
|
}
|
|
306
316
|
return view(required(jobId));
|
|
307
317
|
};
|
|
@@ -75,10 +75,11 @@ export declare function issueBootCapability(input: Readonly<{
|
|
|
75
75
|
ui: BootUi;
|
|
76
76
|
bucket: BootCapabilityBucket;
|
|
77
77
|
capabilities: BootCapabilityStore;
|
|
78
|
-
|
|
78
|
+
now: () => number;
|
|
79
79
|
}>): Promise<Readonly<{
|
|
80
80
|
token: string;
|
|
81
81
|
leaseId: string;
|
|
82
|
+
deadlineAt: number;
|
|
82
83
|
}>>;
|
|
83
84
|
/** What one boot page report needs from its host. */
|
|
84
85
|
export type BootPageReportInput = Readonly<{
|
package/dist/boot-capability.js
CHANGED
|
@@ -73,11 +73,14 @@ export async function issueBootCapability(input) {
|
|
|
73
73
|
throw new BootGateError("ui_mount_failed");
|
|
74
74
|
byteLengths.push(head.size);
|
|
75
75
|
}
|
|
76
|
+
// Metadata may take longer than the page budget; start the lease only once
|
|
77
|
+
// every asset has been verified, immediately before issuing it.
|
|
78
|
+
const deadlineAt = input.now() + BOOT_DEADLINE_MS;
|
|
76
79
|
const lease = {
|
|
77
80
|
id: bootCapabilityIds.lease.generate(),
|
|
78
81
|
identity: input.ui.identity,
|
|
79
82
|
base: input.ui.base,
|
|
80
|
-
expiresAt:
|
|
83
|
+
expiresAt: deadlineAt + BOOT_TRANSPORT_GRACE_MS,
|
|
81
84
|
assets: capabilityAssets(input.ui.assets, byteLengths),
|
|
82
85
|
};
|
|
83
86
|
validateCapabilityAssetSet(lease);
|
|
@@ -87,7 +90,7 @@ export async function issueBootCapability(input) {
|
|
|
87
90
|
.replace(/=+$/u, "");
|
|
88
91
|
const tokenDigest = createHash("sha256").update(token, "utf8").digest("hex");
|
|
89
92
|
await input.capabilities.issue({ lease, tokenDigest });
|
|
90
|
-
return { token, leaseId: lease.id };
|
|
93
|
+
return { token, leaseId: lease.id, deadlineAt };
|
|
91
94
|
}
|
|
92
95
|
/**
|
|
93
96
|
* One UI page check for a Release, for every host, reading no UI bytes:
|
|
@@ -102,14 +105,13 @@ export async function reportBootPage(input) {
|
|
|
102
105
|
return { kind: "passed" };
|
|
103
106
|
if (input.check === null || input.origin === null)
|
|
104
107
|
return { kind: "not_checked", code: "boot_browser_binding_missing" };
|
|
105
|
-
const deadlineAt = input.now() + BOOT_DEADLINE_MS;
|
|
106
108
|
let issued;
|
|
107
109
|
try {
|
|
108
110
|
issued = await issueBootCapability({
|
|
109
111
|
ui,
|
|
110
112
|
bucket: input.bucket,
|
|
111
113
|
capabilities: input.capabilities,
|
|
112
|
-
|
|
114
|
+
now: input.now,
|
|
113
115
|
});
|
|
114
116
|
}
|
|
115
117
|
catch (cause) {
|
|
@@ -123,7 +125,7 @@ export async function reportBootPage(input) {
|
|
|
123
125
|
origin: input.origin,
|
|
124
126
|
releaseId: ui.identity.releaseId,
|
|
125
127
|
capability: { header: BOOT_CAPABILITY_HEADER, token: issued.token },
|
|
126
|
-
deadlineAt,
|
|
128
|
+
deadlineAt: issued.deadlineAt,
|
|
127
129
|
});
|
|
128
130
|
}
|
|
129
131
|
finally {
|
|
@@ -11,7 +11,7 @@ declare const jobSchema: Schema.Struct<{
|
|
|
11
11
|
readonly expiresAt: Schema.Number;
|
|
12
12
|
readonly remote: Schema.NonEmptyString;
|
|
13
13
|
readonly reportUrl: Schema.NonEmptyString;
|
|
14
|
-
readonly status: Schema.Literals<readonly ["running", "succeeded", "failed", "revoked"]>;
|
|
14
|
+
readonly status: Schema.Literals<readonly ["running", "succeeded", "failed", "interrupted", "revoked"]>;
|
|
15
15
|
readonly artifact: Schema.NullOr<Schema.Struct<{
|
|
16
16
|
readonly digest: Schema.NonEmptyString;
|
|
17
17
|
readonly bytes: Schema.Number;
|
|
@@ -87,7 +87,7 @@ export declare function createTeamBuildPublisher(options: Readonly<{
|
|
|
87
87
|
readonly expiresAt: number;
|
|
88
88
|
readonly remote: string;
|
|
89
89
|
readonly reportUrl: string;
|
|
90
|
-
readonly status: "revoked" | "running" | "failed" | "succeeded";
|
|
90
|
+
readonly status: "revoked" | "running" | "failed" | "succeeded" | "interrupted";
|
|
91
91
|
readonly artifact: {
|
|
92
92
|
readonly digest: string;
|
|
93
93
|
readonly bytes: number;
|
|
@@ -103,7 +103,7 @@ export declare function createTeamBuildPublisher(options: Readonly<{
|
|
|
103
103
|
readonly expiresAt: number;
|
|
104
104
|
readonly remote: string;
|
|
105
105
|
readonly reportUrl: string;
|
|
106
|
-
readonly status: "revoked" | "running" | "failed" | "succeeded";
|
|
106
|
+
readonly status: "revoked" | "running" | "failed" | "succeeded" | "interrupted";
|
|
107
107
|
readonly artifact: {
|
|
108
108
|
readonly digest: string;
|
|
109
109
|
readonly bytes: number;
|
package/dist/build/build-jobs.js
CHANGED
|
@@ -53,6 +53,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
53
53
|
import * as Schema from "effect/Schema";
|
|
54
54
|
import { base64url } from "jose";
|
|
55
55
|
import { workspaceBuildPolicy } from "./build-policy.js";
|
|
56
|
+
import { boundedDiagnostic } from "@fungi.computer/bird-dog/effect";
|
|
56
57
|
/** The secret-free Team job; no provider token enters publication. */
|
|
57
58
|
const jobSchema = Schema.Struct({
|
|
58
59
|
jobId: Schema.NonEmptyString,
|
|
@@ -63,7 +64,13 @@ const jobSchema = Schema.Struct({
|
|
|
63
64
|
expiresAt: Schema.Number,
|
|
64
65
|
remote: Schema.NonEmptyString,
|
|
65
66
|
reportUrl: Schema.NonEmptyString,
|
|
66
|
-
status: Schema.Literals([
|
|
67
|
+
status: Schema.Literals([
|
|
68
|
+
"running",
|
|
69
|
+
"succeeded",
|
|
70
|
+
"failed",
|
|
71
|
+
"interrupted",
|
|
72
|
+
"revoked",
|
|
73
|
+
]),
|
|
67
74
|
artifact: Schema.NullOr(Schema.Struct({ digest: Schema.NonEmptyString, bytes: Schema.Number })),
|
|
68
75
|
detail: Schema.NullOr(Schema.String),
|
|
69
76
|
});
|
|
@@ -148,10 +155,8 @@ export function createTeamBuildPublisher(options) {
|
|
|
148
155
|
? "succeeded"
|
|
149
156
|
: "failed", exitCode === 0
|
|
150
157
|
? "Build exited without reporting status"
|
|
151
|
-
:
|
|
152
|
-
String(exitCode)
|
|
153
|
-
": " +
|
|
154
|
-
(result.output?.stderr ?? "").slice(-1_000)));
|
|
158
|
+
: (boundedDiagnostic(result.output?.stderr ?? "") ??
|
|
159
|
+
`Build exited with ${String(exitCode)}`)));
|
|
155
160
|
await build.deliverProjection();
|
|
156
161
|
return completed;
|
|
157
162
|
}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import * as Schema from "effect/Schema";
|
|
2
2
|
import { releaseSchema } from "@fungi.computer/puffball/effect";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { candidateFailureDiagnostic } from "./candidate-diagnostic.js";
|
|
4
5
|
import { candidateManifestObjects, teamAppCandidateManifestSchema, } from "../build-contract.js";
|
|
5
6
|
import { buildTeamCandidate, teamCandidateConfigurationSchema, teamCandidateLayout, TeamCandidateBuildError, } from "./candidate-builder.js";
|
|
6
7
|
/** Objects in flight at once; each is read from disk only while it uploads. */
|
|
7
8
|
const uploadConcurrency = 4;
|
|
8
9
|
/** Attempts per object when its connection drops before a response. */
|
|
9
10
|
const uploadAttempts = 3;
|
|
10
|
-
/** Report
|
|
11
|
+
/** Report bounded diagnostics to the host's build output. */
|
|
11
12
|
function reportFailure(cause) {
|
|
13
|
+
const diagnostic = candidateFailureDiagnostic(cause);
|
|
14
|
+
process.stderr.write(`Team App source/build failed: ${diagnostic}\n`);
|
|
12
15
|
process.stderr.write(`${JSON.stringify({
|
|
13
16
|
source: "oyster-candidate-builder",
|
|
14
17
|
failureKind: cause instanceof Error ? "Error" : "NonErrorThrown",
|
|
18
|
+
diagnostic,
|
|
15
19
|
})}\n`);
|
|
16
20
|
}
|
|
17
21
|
const [configuration, repositoryPath, outputPath] = process.argv.slice(2);
|
|
@@ -113,7 +117,5 @@ try {
|
|
|
113
117
|
}
|
|
114
118
|
catch (cause) {
|
|
115
119
|
reportFailure(cause);
|
|
116
|
-
// Customer source and provider messages can contain secrets; only expose the stage.
|
|
117
|
-
process.stderr.write("Team App source/build failed\n");
|
|
118
120
|
process.exitCode = 1;
|
|
119
121
|
}
|
|
@@ -14544,6 +14544,21 @@ function makeIsGreaterThanOrEqualTo(options) {
|
|
|
14544
14544
|
});
|
|
14545
14545
|
};
|
|
14546
14546
|
}
|
|
14547
|
+
function makeIsLessThanOrEqualTo(options) {
|
|
14548
|
+
const lte = isLessThanOrEqualTo(options.order);
|
|
14549
|
+
const formatter = options.formatter ?? format;
|
|
14550
|
+
return (maximum, annotations) => {
|
|
14551
|
+
return makeFilter2((input) => lte(input, maximum), {
|
|
14552
|
+
expected: `a value less than or equal to ${formatter(maximum)}`,
|
|
14553
|
+
arbitraryConstraint: {
|
|
14554
|
+
order: options.order,
|
|
14555
|
+
maximum
|
|
14556
|
+
},
|
|
14557
|
+
...options.annotate?.(maximum),
|
|
14558
|
+
...annotations
|
|
14559
|
+
});
|
|
14560
|
+
};
|
|
14561
|
+
}
|
|
14547
14562
|
function makeIsBetween(deriveOptions) {
|
|
14548
14563
|
const greaterThanOrEqualTo = isGreaterThanOrEqualTo(deriveOptions.order);
|
|
14549
14564
|
const greaterThan = isGreaterThan(deriveOptions.order);
|
|
@@ -14611,6 +14626,23 @@ var isGreaterThanOrEqualTo2 = /* @__PURE__ */ makeIsGreaterThanOrEqualTo({
|
|
|
14611
14626
|
})
|
|
14612
14627
|
})
|
|
14613
14628
|
});
|
|
14629
|
+
var isLessThanOrEqualTo2 = /* @__PURE__ */ makeIsLessThanOrEqualTo({
|
|
14630
|
+
order: Number2,
|
|
14631
|
+
annotate: (maximum) => ({
|
|
14632
|
+
representation: {
|
|
14633
|
+
id: "effect/schema/isLessThanOrEqualTo",
|
|
14634
|
+
payload: {
|
|
14635
|
+
maximum: encodeNumberPayload(maximum)
|
|
14636
|
+
}
|
|
14637
|
+
},
|
|
14638
|
+
toJsonSchema: () => ({
|
|
14639
|
+
maximum
|
|
14640
|
+
}),
|
|
14641
|
+
toCode: () => ({
|
|
14642
|
+
runtime: `Schema.isLessThanOrEqualTo(${format(maximum)})`
|
|
14643
|
+
})
|
|
14644
|
+
})
|
|
14645
|
+
});
|
|
14614
14646
|
var isBetween = /* @__PURE__ */ makeIsBetween({
|
|
14615
14647
|
order: Number2,
|
|
14616
14648
|
annotate: (options) => {
|
|
@@ -16806,14 +16838,14 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
|
|
|
16806
16838
|
return `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;
|
|
16807
16839
|
};
|
|
16808
16840
|
doc.write(`const input = payload.value;`);
|
|
16809
|
-
const
|
|
16841
|
+
const ids2 = /* @__PURE__ */ Object.create(null);
|
|
16810
16842
|
let counter = 0;
|
|
16811
16843
|
for (const key of normalized.keys) {
|
|
16812
|
-
|
|
16844
|
+
ids2[key] = `key_${counter++}`;
|
|
16813
16845
|
}
|
|
16814
16846
|
doc.write(`const newResult = {};`);
|
|
16815
16847
|
for (const key of normalized.keys) {
|
|
16816
|
-
const id =
|
|
16848
|
+
const id = ids2[key];
|
|
16817
16849
|
const k = esc(key);
|
|
16818
16850
|
const schema2 = shape[key];
|
|
16819
16851
|
const isOptionalIn = schema2?._zod?.optin === "optional";
|
|
@@ -21625,6 +21657,189 @@ var publicRouteConfigurationSchema = Struct({
|
|
|
21625
21657
|
// src/build/candidate-cli.ts
|
|
21626
21658
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
21627
21659
|
|
|
21660
|
+
// ../carrier/dist/internal/post-contract.js
|
|
21661
|
+
var CursorResultSchema = Struct({
|
|
21662
|
+
status: Literals(["accepted", "duplicate"]),
|
|
21663
|
+
cursor: Int.check(isGreaterThanOrEqualTo2(0))
|
|
21664
|
+
});
|
|
21665
|
+
var PostDeliveryResultSchema = Union2([
|
|
21666
|
+
Literals(["accepted", "duplicate", "refused"]),
|
|
21667
|
+
CursorResultSchema,
|
|
21668
|
+
Struct({
|
|
21669
|
+
status: Literals(["accepted", "duplicate", "refused"]),
|
|
21670
|
+
receipt: Json2
|
|
21671
|
+
})
|
|
21672
|
+
]);
|
|
21673
|
+
var PostError = class extends TaggedError2("PostError") {
|
|
21674
|
+
constructor(message, reason) {
|
|
21675
|
+
super(reason === void 0 ? { message } : { message, reason });
|
|
21676
|
+
}
|
|
21677
|
+
};
|
|
21678
|
+
|
|
21679
|
+
// ../carrier/dist/post.js
|
|
21680
|
+
var Key = String4.check(isMinLength(1), isMaxLength(256));
|
|
21681
|
+
var JobPayload = Struct({ postKey: Key });
|
|
21682
|
+
var PageLimit = Int.check(isGreaterThanOrEqualTo2(1), isLessThanOrEqualTo2(64));
|
|
21683
|
+
var maxRouteDeadlineMs = 7 * 24 * 60 * 60 * 1e3;
|
|
21684
|
+
var ids = defineIdFamily({
|
|
21685
|
+
family: "post",
|
|
21686
|
+
version: 1,
|
|
21687
|
+
kinds: { work: { prefix: "post_work" } }
|
|
21688
|
+
});
|
|
21689
|
+
var PostEnvelopeSchema = Struct({
|
|
21690
|
+
key: Key,
|
|
21691
|
+
kind: Key,
|
|
21692
|
+
address: Unknown2,
|
|
21693
|
+
event: Unknown2
|
|
21694
|
+
});
|
|
21695
|
+
var PostRowSchema = fromJsonString2(Union2([
|
|
21696
|
+
Struct({
|
|
21697
|
+
state: Literal2("pending"),
|
|
21698
|
+
envelope: PostEnvelopeSchema,
|
|
21699
|
+
attempts: Int.check(isGreaterThanOrEqualTo2(0)),
|
|
21700
|
+
nextAttemptAt: Number5.check(isGreaterThanOrEqualTo2(0)),
|
|
21701
|
+
deadlineAt: optional2(Number5.check(isGreaterThanOrEqualTo2(0))),
|
|
21702
|
+
jobId: optional2(Key),
|
|
21703
|
+
delivery: optional2(PostEnvelopeSchema),
|
|
21704
|
+
nextKey: optional2(Key)
|
|
21705
|
+
}),
|
|
21706
|
+
Struct({
|
|
21707
|
+
state: Literal2("delivered"),
|
|
21708
|
+
receipt: optional2(Json2),
|
|
21709
|
+
nextKey: optional2(Key)
|
|
21710
|
+
}),
|
|
21711
|
+
Struct({
|
|
21712
|
+
state: Literal2("dead"),
|
|
21713
|
+
envelope: PostEnvelopeSchema,
|
|
21714
|
+
attempts: Int.check(isGreaterThanOrEqualTo2(0)),
|
|
21715
|
+
reason: Literals(["refused", "exhausted", "expired"]),
|
|
21716
|
+
deadlineAt: optional2(Number5.check(isGreaterThanOrEqualTo2(0))),
|
|
21717
|
+
receipt: optional2(Json2),
|
|
21718
|
+
delivery: optional2(PostEnvelopeSchema),
|
|
21719
|
+
nextKey: optional2(Key)
|
|
21720
|
+
})
|
|
21721
|
+
]));
|
|
21722
|
+
var StoredRow = Struct({ key: String4, value: String4 });
|
|
21723
|
+
var DueRow = Struct({
|
|
21724
|
+
due: NullOr(Number5.check(isGreaterThanOrEqualTo2(0)))
|
|
21725
|
+
});
|
|
21726
|
+
|
|
21727
|
+
// ../bird-dog/dist/effect.js
|
|
21728
|
+
var Identifier = String4.check(isMinLength(1), isMaxLength(256));
|
|
21729
|
+
var BIRD_DOG_MAIL_CONTENT_MAX_BYTES = 256 * 1024;
|
|
21730
|
+
var MailContent = String4.check(makeFilter2((content) => new TextEncoder().encode(content).byteLength <= BIRD_DOG_MAIL_CONTENT_MAX_BYTES, {
|
|
21731
|
+
message: `Mail content must be at most ${String(BIRD_DOG_MAIL_CONTENT_MAX_BYTES)} UTF-8 bytes`
|
|
21732
|
+
}));
|
|
21733
|
+
var BirdDogAgentReferenceSchema = Struct({
|
|
21734
|
+
kind: Literal2("agent"),
|
|
21735
|
+
agentId: Identifier,
|
|
21736
|
+
sessionId: Identifier
|
|
21737
|
+
});
|
|
21738
|
+
var BirdDogAgentRecipientReferenceSchema = Struct({
|
|
21739
|
+
kind: Literal2("agent"),
|
|
21740
|
+
agentId: Identifier,
|
|
21741
|
+
sessionId: optionalKey2(Identifier)
|
|
21742
|
+
});
|
|
21743
|
+
var BIRD_DOG_DIAGNOSTIC_MAX_LENGTH = 200;
|
|
21744
|
+
var SettledBirdDogReplySchema = Struct({
|
|
21745
|
+
commandId: Identifier,
|
|
21746
|
+
outcome: Literals([
|
|
21747
|
+
"completed",
|
|
21748
|
+
"failed",
|
|
21749
|
+
"interrupted",
|
|
21750
|
+
"cancelled",
|
|
21751
|
+
"outcome_unknown"
|
|
21752
|
+
]),
|
|
21753
|
+
diagnostic: optionalKey2(String4.check(isMinLength(1), isMaxLength(BIRD_DOG_DIAGNOSTIC_MAX_LENGTH)))
|
|
21754
|
+
});
|
|
21755
|
+
var BirdDogUndeliverableReasonSchema = Literals([
|
|
21756
|
+
"recipient_offline",
|
|
21757
|
+
"recipient_left"
|
|
21758
|
+
]);
|
|
21759
|
+
var UndeliverableBirdDogReplySchema = Struct({
|
|
21760
|
+
outcome: Literal2("undeliverable"),
|
|
21761
|
+
reason: BirdDogUndeliverableReasonSchema
|
|
21762
|
+
});
|
|
21763
|
+
var AcceptedBirdDogReplySchema = Struct({
|
|
21764
|
+
outcome: Literal2("accepted"),
|
|
21765
|
+
commandId: Identifier
|
|
21766
|
+
});
|
|
21767
|
+
var BirdDogReplySchema = Union2([
|
|
21768
|
+
SettledBirdDogReplySchema,
|
|
21769
|
+
UndeliverableBirdDogReplySchema,
|
|
21770
|
+
AcceptedBirdDogReplySchema
|
|
21771
|
+
]);
|
|
21772
|
+
var REDACTED = "[redacted]";
|
|
21773
|
+
var schemeCredential = /\b(bearer|basic)\s+[^\s"',;]+/giu;
|
|
21774
|
+
var namedCredential = /\b((?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password|authorization)["']?\s*[:=]\s*["']?)(?!(?:bearer|basic)\s)[^\s"',;&]+/giu;
|
|
21775
|
+
var opaqueToken = /[A-Za-z0-9][\w-]{23,}/gu;
|
|
21776
|
+
function redactCredentials(text) {
|
|
21777
|
+
return text.replace(schemeCredential, `$1 ${REDACTED}`).replace(namedCredential, `$1${REDACTED}`).replace(opaqueToken, (token) => /\d/u.test(token) && /[A-Za-z]/u.test(token) ? REDACTED : token);
|
|
21778
|
+
}
|
|
21779
|
+
function boundedDiagnostic(text) {
|
|
21780
|
+
const line = redactCredentials(text.replace(/[\p{Cc}\p{Zl}\p{Zp}\s]+/gu, " ").trim()).slice(0, BIRD_DOG_DIAGNOSTIC_MAX_LENGTH).trimEnd();
|
|
21781
|
+
return line.length === 0 ? void 0 : line;
|
|
21782
|
+
}
|
|
21783
|
+
var BirdDogMailFields = {
|
|
21784
|
+
key: Identifier,
|
|
21785
|
+
to: BirdDogAgentRecipientReferenceSchema,
|
|
21786
|
+
replyTo: ArraySchema(BirdDogAgentReferenceSchema),
|
|
21787
|
+
content: MailContent,
|
|
21788
|
+
createdAt: Number5.check(isGreaterThanOrEqualTo2(0)),
|
|
21789
|
+
/** The sender's opaque host metadata; BirdDog never reads it. */
|
|
21790
|
+
meta: optionalKey2(Json2)
|
|
21791
|
+
};
|
|
21792
|
+
var BirdDogMailSchema = Union2([
|
|
21793
|
+
Struct({
|
|
21794
|
+
...BirdDogMailFields,
|
|
21795
|
+
from: BirdDogAgentReferenceSchema,
|
|
21796
|
+
/** Settlement or acceptance facts, present only on a reply. */
|
|
21797
|
+
reply: optionalKey2(BirdDogReplySchema)
|
|
21798
|
+
}),
|
|
21799
|
+
Struct({
|
|
21800
|
+
...BirdDogMailFields,
|
|
21801
|
+
from: BirdDogAgentRecipientReferenceSchema,
|
|
21802
|
+
/** An unstarted recipient can only author an undeliverable reply. */
|
|
21803
|
+
reply: UndeliverableBirdDogReplySchema
|
|
21804
|
+
})
|
|
21805
|
+
]);
|
|
21806
|
+
var BirdDogAgentWorkSchema = Struct({
|
|
21807
|
+
jobId: Identifier,
|
|
21808
|
+
sessionId: Identifier,
|
|
21809
|
+
commandFactId: Identifier,
|
|
21810
|
+
recovery: optional2(Struct({
|
|
21811
|
+
maxRecoveries: Int.check(isGreaterThanOrEqualTo2(0))
|
|
21812
|
+
}))
|
|
21813
|
+
});
|
|
21814
|
+
var BirdDogAgentJobPayloadSchema = Struct({
|
|
21815
|
+
work: Struct({
|
|
21816
|
+
sessionId: Identifier,
|
|
21817
|
+
commandFactId: Identifier
|
|
21818
|
+
}),
|
|
21819
|
+
mail: optional2(BirdDogMailSchema)
|
|
21820
|
+
});
|
|
21821
|
+
var BirdDogAgentJobInputSchema = Struct({
|
|
21822
|
+
work: BirdDogAgentWorkSchema,
|
|
21823
|
+
mail: optional2(BirdDogMailSchema)
|
|
21824
|
+
});
|
|
21825
|
+
|
|
21826
|
+
// src/build/candidate-diagnostic.ts
|
|
21827
|
+
function candidateFailureDiagnostic(cause) {
|
|
21828
|
+
const messages = [];
|
|
21829
|
+
let current = cause;
|
|
21830
|
+
for (let depth = 0; depth < 3; depth += 1) {
|
|
21831
|
+
if (!(current instanceof Error)) {
|
|
21832
|
+
if (depth === 0) messages.push("Non-error thrown");
|
|
21833
|
+
break;
|
|
21834
|
+
}
|
|
21835
|
+
messages.push(
|
|
21836
|
+
`${depth === 0 ? "" : "Caused by: "}${current.message || current.name}`
|
|
21837
|
+
);
|
|
21838
|
+
current = current.cause;
|
|
21839
|
+
}
|
|
21840
|
+
return boundedDiagnostic(messages.join("; ")) ?? "Build failed";
|
|
21841
|
+
}
|
|
21842
|
+
|
|
21628
21843
|
// ../puffball/dist/assets.js
|
|
21629
21844
|
var positiveInteger = Number5.pipe(check(isInt()), check(isGreaterThan2(0)), check(makeFilter2(Number.isSafeInteger)));
|
|
21630
21845
|
var assetBuildPolicySchema = Struct({
|
|
@@ -36087,10 +36302,14 @@ async function buildTeamCandidate(input) {
|
|
|
36087
36302
|
var uploadConcurrency = 4;
|
|
36088
36303
|
var uploadAttempts = 3;
|
|
36089
36304
|
function reportFailure2(cause) {
|
|
36305
|
+
const diagnostic = candidateFailureDiagnostic(cause);
|
|
36306
|
+
process.stderr.write(`Team App source/build failed: ${diagnostic}
|
|
36307
|
+
`);
|
|
36090
36308
|
process.stderr.write(
|
|
36091
36309
|
`${JSON.stringify({
|
|
36092
36310
|
source: "oyster-candidate-builder",
|
|
36093
|
-
failureKind: cause instanceof Error ? "Error" : "NonErrorThrown"
|
|
36311
|
+
failureKind: cause instanceof Error ? "Error" : "NonErrorThrown",
|
|
36312
|
+
diagnostic
|
|
36094
36313
|
})}
|
|
36095
36314
|
`
|
|
36096
36315
|
);
|
|
@@ -36199,7 +36418,6 @@ try {
|
|
|
36199
36418
|
);
|
|
36200
36419
|
} catch (cause) {
|
|
36201
36420
|
reportFailure2(cause);
|
|
36202
|
-
process.stderr.write("Team App source/build failed\n");
|
|
36203
36421
|
process.exitCode = 1;
|
|
36204
36422
|
}
|
|
36205
36423
|
/*! Bundled license information:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { boundedDiagnostic } from "@fungi.computer/bird-dog/effect";
|
|
2
|
+
/** The builder's error and at most two causes, safe for a build record. */
|
|
3
|
+
export function candidateFailureDiagnostic(cause) {
|
|
4
|
+
const messages = [];
|
|
5
|
+
let current = cause;
|
|
6
|
+
for (let depth = 0; depth < 3; depth += 1) {
|
|
7
|
+
if (!(current instanceof Error)) {
|
|
8
|
+
if (depth === 0)
|
|
9
|
+
messages.push("Non-error thrown");
|
|
10
|
+
break;
|
|
11
|
+
}
|
|
12
|
+
messages.push(`${depth === 0 ? "" : "Caused by: "}${current.message || current.name}`);
|
|
13
|
+
current = current.cause;
|
|
14
|
+
}
|
|
15
|
+
return boundedDiagnostic(messages.join("; ")) ?? "Build failed";
|
|
16
|
+
}
|
|
@@ -20,7 +20,10 @@ export declare class AgentModelRefused extends Error {
|
|
|
20
20
|
/** Read the maintained Pi catalog; never accept a model from a different egress. */
|
|
21
21
|
export declare function selectAgentModel(choice: AgentModelChoice): Promise<Model<Api>>;
|
|
22
22
|
/** The Agent DO is the only durable owner. Absence is a new-format default, not an old-env reader. */
|
|
23
|
-
export declare function agentModelChoice(storage:
|
|
23
|
+
export declare function agentModelChoice(storage: Readonly<{
|
|
24
|
+
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
25
|
+
put(key: string, value: AgentModelChoice): Promise<void>;
|
|
26
|
+
}>, admit: (choice: AgentModelChoice) => void): {
|
|
24
27
|
read(): Promise<AgentModelChoice>;
|
|
25
28
|
set(input: AgentModelChoice): Promise<AgentModelChoice>;
|
|
26
29
|
};
|
|
@@ -60,6 +60,13 @@ export type InstalledStarterApp = Readonly<{
|
|
|
60
60
|
release: Schema.Json;
|
|
61
61
|
timings: StarterAppTimings;
|
|
62
62
|
}>;
|
|
63
|
+
export type StarterAppSetResult = Readonly<{
|
|
64
|
+
installed: readonly InstalledStarterApp[];
|
|
65
|
+
failed: readonly Readonly<{
|
|
66
|
+
installationId: string;
|
|
67
|
+
reason: string;
|
|
68
|
+
}>[];
|
|
69
|
+
}>;
|
|
63
70
|
/**
|
|
64
71
|
* Builds the installer runs at once by default. A build's compiler may take a
|
|
65
72
|
* 4 GB heap, so two fit the platform's 12 GiB Computer (standard-4) with room
|
|
@@ -91,4 +98,4 @@ export declare function installStandaloneAppSet(options: Readonly<{
|
|
|
91
98
|
/** The idle wait between build status reads; the real timer by default. */
|
|
92
99
|
idle?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
93
100
|
waitMs?: number;
|
|
94
|
-
}>): Promise<
|
|
101
|
+
}>): Promise<StarterAppSetResult>;
|
|
@@ -72,7 +72,13 @@ const publishedSchema = Schema.Struct({
|
|
|
72
72
|
});
|
|
73
73
|
const buildSchema = Schema.Struct({
|
|
74
74
|
jobId: Schema.String,
|
|
75
|
-
status: Schema.Literals([
|
|
75
|
+
status: Schema.Literals([
|
|
76
|
+
"running",
|
|
77
|
+
"succeeded",
|
|
78
|
+
"failed",
|
|
79
|
+
"interrupted",
|
|
80
|
+
"revoked",
|
|
81
|
+
]),
|
|
76
82
|
detail: Schema.NullOr(Schema.String),
|
|
77
83
|
/** Installed exactly as the host encoded it. */
|
|
78
84
|
release: Schema.NullOr(Schema.Json),
|
|
@@ -271,10 +277,15 @@ export async function installStandaloneAppSet(options) {
|
|
|
271
277
|
}
|
|
272
278
|
};
|
|
273
279
|
const installed = new Map();
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
280
|
+
const failures = new Map();
|
|
281
|
+
const fail = (app, cause) => {
|
|
282
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
283
|
+
failures.set(app.installationId, reason);
|
|
284
|
+
options.report(`Failed ${app.installationId}: ${reason}`);
|
|
285
|
+
};
|
|
286
|
+
const reportFailure = ({ app, cause, }) => {
|
|
287
|
+
fail(app, cause);
|
|
288
|
+
};
|
|
278
289
|
const seededQueue = [];
|
|
279
290
|
const waiting = [];
|
|
280
291
|
const seeding = { finished: false };
|
|
@@ -318,21 +329,19 @@ export async function installStandaloneAppSet(options) {
|
|
|
318
329
|
const seedAll = async () => {
|
|
319
330
|
try {
|
|
320
331
|
for (const app of standaloneAppSet) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
332
|
+
try {
|
|
333
|
+
const seeded = await seedApp(app);
|
|
334
|
+
const waiter = waiting.shift();
|
|
335
|
+
if (waiter === undefined)
|
|
336
|
+
seededQueue.push(seeded);
|
|
337
|
+
else
|
|
338
|
+
waiter(seeded);
|
|
339
|
+
}
|
|
340
|
+
catch (cause) {
|
|
341
|
+
reportFailure({ app, cause });
|
|
342
|
+
}
|
|
329
343
|
}
|
|
330
344
|
}
|
|
331
|
-
catch (cause) {
|
|
332
|
-
stop.requested = true;
|
|
333
|
-
failures.push(cause);
|
|
334
|
-
throw cause;
|
|
335
|
-
}
|
|
336
345
|
finally {
|
|
337
346
|
seeding.finished = true;
|
|
338
347
|
for (const waiter of waiting.splice(0))
|
|
@@ -341,7 +350,7 @@ export async function installStandaloneAppSet(options) {
|
|
|
341
350
|
};
|
|
342
351
|
// `concurrency` builds at once, each installing as its build succeeds.
|
|
343
352
|
const slot = async () => {
|
|
344
|
-
for (let next = await takeSeeded(); next !== undefined
|
|
353
|
+
for (let next = await takeSeeded(); next !== undefined; next = await takeSeeded()) {
|
|
345
354
|
const name = next.app.installationId;
|
|
346
355
|
try {
|
|
347
356
|
const buildStarted = performance.now();
|
|
@@ -416,22 +425,28 @@ export async function installStandaloneAppSet(options) {
|
|
|
416
425
|
});
|
|
417
426
|
}
|
|
418
427
|
catch (cause) {
|
|
419
|
-
|
|
420
|
-
failures.push(cause);
|
|
421
|
-
throw cause;
|
|
428
|
+
reportFailure({ app: next.app, cause });
|
|
422
429
|
}
|
|
423
430
|
}
|
|
424
431
|
};
|
|
425
|
-
// Wait for every seed and build to settle before
|
|
426
|
-
|
|
427
|
-
await Promise.allSettled([
|
|
432
|
+
// Wait for every seed and build to settle before returning the full outcome.
|
|
433
|
+
const settled = await Promise.allSettled([
|
|
428
434
|
seedAll(),
|
|
429
435
|
...Array.from({ length: concurrency }, slot),
|
|
430
436
|
]);
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
+
for (const outcome of settled)
|
|
438
|
+
if (outcome.status === "rejected")
|
|
439
|
+
throw outcome.reason;
|
|
440
|
+
return {
|
|
441
|
+
installed: standaloneAppSet.flatMap((app) => {
|
|
442
|
+
const result = installed.get(app.installationId);
|
|
443
|
+
return result === undefined ? [] : [result];
|
|
444
|
+
}),
|
|
445
|
+
failed: standaloneAppSet.flatMap((app) => {
|
|
446
|
+
const reason = failures.get(app.installationId);
|
|
447
|
+
return reason === undefined
|
|
448
|
+
? []
|
|
449
|
+
: [{ installationId: app.installationId, reason }];
|
|
450
|
+
}),
|
|
451
|
+
};
|
|
437
452
|
}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import { installStandaloneAppSet } from "./app-set.js";
|
|
3
|
+
export declare function installApps(args: readonly string[], install?: typeof installStandaloneAppSet): Promise<void>;
|
|
4
|
+
export declare function runInstallAppsCli(args?: readonly string[]): Promise<void>;
|
|
@@ -76,7 +76,7 @@ function timingTable(results) {
|
|
|
76
76
|
];
|
|
77
77
|
return [header, ...rows].map((row) => row.join("\t")).join("\n");
|
|
78
78
|
}
|
|
79
|
-
async function installApps(args) {
|
|
79
|
+
export async function installApps(args, install = installStandaloneAppSet) {
|
|
80
80
|
const { values } = parseArgs({
|
|
81
81
|
args: [...args],
|
|
82
82
|
options: {
|
|
@@ -121,7 +121,7 @@ async function installApps(args) {
|
|
|
121
121
|
source = { kind: "checkout", directory, revision: `git:${revision}` };
|
|
122
122
|
}
|
|
123
123
|
const started = performance.now();
|
|
124
|
-
const
|
|
124
|
+
const { installed, failed } = await install({
|
|
125
125
|
source,
|
|
126
126
|
url,
|
|
127
127
|
key,
|
|
@@ -131,33 +131,43 @@ async function installApps(args) {
|
|
|
131
131
|
},
|
|
132
132
|
});
|
|
133
133
|
const elapsed = performance.now() - started;
|
|
134
|
-
process.stdout.write(`\n${timingTable(
|
|
135
|
-
process.stdout.write(
|
|
134
|
+
process.stdout.write(`\n${timingTable(installed)}\n`);
|
|
135
|
+
process.stdout.write("\nApp\tResult\n");
|
|
136
|
+
for (const app of installed)
|
|
137
|
+
process.stdout.write(`${app.installationId}\tinstalled\n`);
|
|
138
|
+
for (const app of failed)
|
|
139
|
+
process.stdout.write(`${app.installationId}\tfailed: ${app.reason}\n`);
|
|
140
|
+
process.stdout.write(`\nInstalled ${String(installed.length)} Apps; failed ${String(failed.length)} in ${seconds(elapsed)}.${installed.length > 0 ? ` Open ${url}/ to use the installed Apps.` : ""}\n`);
|
|
136
141
|
if (values.timings !== undefined)
|
|
137
142
|
await writeFile(values.timings, JSON.stringify({
|
|
138
143
|
concurrency,
|
|
139
144
|
elapsedMs: elapsed,
|
|
140
|
-
apps:
|
|
145
|
+
apps: installed.map(({ installationId, jobId, timings }) => ({
|
|
141
146
|
installationId,
|
|
142
147
|
jobId,
|
|
143
148
|
timings,
|
|
144
149
|
})),
|
|
150
|
+
failed,
|
|
145
151
|
}, null, 2) + "\n");
|
|
152
|
+
if (failed.length > 0)
|
|
153
|
+
process.exitCode = 1;
|
|
146
154
|
}
|
|
147
155
|
finally {
|
|
148
156
|
await rm(scratch, { recursive: true, force: true });
|
|
149
157
|
}
|
|
150
158
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
export async function runInstallAppsCli(args = process.argv.slice(2)) {
|
|
160
|
+
const [command, ...rest] = args;
|
|
161
|
+
if (command === "--help" || command === "-h") {
|
|
162
|
+
process.stdout.write(usage + "\n" + installReleaseUsage + "\n");
|
|
163
|
+
}
|
|
164
|
+
else if (command !== "install-apps" && command !== "install") {
|
|
165
|
+
process.stderr.write(`${usage}\n`);
|
|
166
|
+
process.exitCode = 2;
|
|
167
|
+
}
|
|
168
|
+
else
|
|
169
|
+
await (command === "install" ? installReleaseCommand(rest) : installApps(rest)).catch((cause) => {
|
|
170
|
+
process.stderr.write(`${cause instanceof Error ? cause.message : String(cause)}\n`);
|
|
171
|
+
process.exitCode = 1;
|
|
172
|
+
});
|
|
158
173
|
}
|
|
159
|
-
else
|
|
160
|
-
await (command === "install" ? installReleaseCommand(rest) : installApps(rest)).catch((cause) => {
|
|
161
|
-
process.stderr.write(`${cause instanceof Error ? cause.message : String(cause)}\n`);
|
|
162
|
-
process.exitCode = 1;
|
|
163
|
-
});
|
|
@@ -187,7 +187,7 @@ export declare abstract class StandaloneRegistry extends OysterRegistry implemen
|
|
|
187
187
|
appId: string;
|
|
188
188
|
releaseId: string;
|
|
189
189
|
sourceRevision: string;
|
|
190
|
-
status: "running" | "succeeded" | "failed" | "revoked";
|
|
190
|
+
status: "running" | "succeeded" | "failed" | "interrupted" | "revoked";
|
|
191
191
|
detail: string | null;
|
|
192
192
|
release: Release | null;
|
|
193
193
|
installation: import("../app-builds.js").OysterBuildInstallation | null;
|
package/dist/workers.js
CHANGED
|
@@ -48,6 +48,8 @@ import { createOysterRootData } from "./root-data.js";
|
|
|
48
48
|
import { backendCache, resolveBackend } from "./release.js";
|
|
49
49
|
import { ensureAppSourceTables, createOysterAppSourceReceipts, createOysterAppRegistrations, createOysterArtifactsRepositoryMetadataStore, } from "./app-sources.js";
|
|
50
50
|
export { r2ArtifactStore, r2FileSource, ownerKeyAuth };
|
|
51
|
+
/** Issuer-ahead clock allowance at issue only; resolution never adds skew. */
|
|
52
|
+
const BOOT_ISSUER_SKEW_MS = 2_000;
|
|
51
53
|
const recordRow = Schema.Struct({
|
|
52
54
|
installation_id: Schema.String,
|
|
53
55
|
record: Schema.String,
|
|
@@ -433,8 +435,14 @@ export class OysterRegistry extends DurableObject {
|
|
|
433
435
|
const { lease, tokenDigest } = bootCapabilityGrantSchema.parse(input.grant);
|
|
434
436
|
validateCapabilityAssetSet(lease);
|
|
435
437
|
const now = Date.now();
|
|
438
|
+
// The issuer can be at most 2 s ahead of this registry. Apply that
|
|
439
|
+
// allowance only on issue: resolution still uses the stored absolute
|
|
440
|
+
// expiry, and revocation still removes the token after the page check.
|
|
441
|
+
// A lease can therefore live at most 14 s on this clock, not be renewed
|
|
442
|
+
// or replayed once expired.
|
|
436
443
|
if (lease.expiresAt <= now ||
|
|
437
|
-
lease.expiresAt >
|
|
444
|
+
lease.expiresAt >
|
|
445
|
+
now + BOOT_DEADLINE_MS + BOOT_TRANSPORT_GRACE_MS + BOOT_ISSUER_SKEW_MS)
|
|
438
446
|
throw new Error("Boot capability lease is outside its deadline");
|
|
439
447
|
this.ctx.storage.sql.exec("DELETE FROM boot_capabilities WHERE expires_at <= ?", now);
|
|
440
448
|
const count = this.ctx.storage.sql
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/oyster",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"description": "Run your own Apps on Cloudflare Workers or celld: installable App Releases, each with its own Durable Object, SQLite database and declared bindings",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"bin": {
|
|
10
|
-
"oyster": "./dist/standalone/install-apps-
|
|
10
|
+
"oyster": "./dist/standalone/install-apps-bin.js"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|