@fungi.computer/oyster 0.4.0 → 0.5.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.
Files changed (63) hide show
  1. package/dist/agents.d.ts +2 -1
  2. package/dist/agents.js +63 -8
  3. package/dist/app-builds.d.ts +1 -1
  4. package/dist/app-builds.js +16 -6
  5. package/dist/apps/gate.d.ts +4 -0
  6. package/dist/apps/gate.js +3 -0
  7. package/dist/boot-capability.d.ts +2 -1
  8. package/dist/boot-capability.js +7 -5
  9. package/dist/boot-host-script.js +1 -1
  10. package/dist/build/build-jobs.d.ts +3 -3
  11. package/dist/build/build-jobs.js +10 -5
  12. package/dist/build/candidate-cli.js +5 -3
  13. package/dist/build/candidate-cli.mjs +238 -5
  14. package/dist/build/candidate-diagnostic.d.ts +2 -0
  15. package/dist/build/candidate-diagnostic.js +16 -0
  16. package/dist/channels/binding.d.ts +63 -0
  17. package/dist/channels/binding.js +85 -0
  18. package/dist/channels/conversations.d.ts +235 -0
  19. package/dist/channels/conversations.js +705 -0
  20. package/dist/channels/ids.d.ts +8 -0
  21. package/dist/channels/ids.js +15 -0
  22. package/dist/host.d.ts +1 -1
  23. package/dist/host.js +21 -4
  24. package/dist/keys.d.ts +4 -1
  25. package/dist/keys.js +15 -4
  26. package/dist/machine-link/authority.d.ts +7 -0
  27. package/dist/machine-link/delivery.d.ts +12 -0
  28. package/dist/machine-link/delivery.js +22 -1
  29. package/dist/machine-link/disposition.d.ts +16 -0
  30. package/dist/machine-link/disposition.js +65 -0
  31. package/dist/machine-link/host.d.ts +11 -2
  32. package/dist/machine-link/host.js +125 -5
  33. package/dist/machine-link/link.d.ts +4 -0
  34. package/dist/machine-link/link.js +47 -1
  35. package/dist/page-script.js +4 -4
  36. package/dist/standalone/agent-mail.js +4 -1
  37. package/dist/standalone/agent-model-choice.d.ts +4 -1
  38. package/dist/standalone/agents.d.ts +8 -0
  39. package/dist/standalone/agents.js +21 -2
  40. package/dist/standalone/app-set.d.ts +8 -1
  41. package/dist/standalone/app-set.js +46 -31
  42. package/dist/standalone/channel-session-door.d.ts +4 -0
  43. package/dist/standalone/channel-session-door.js +42 -0
  44. package/dist/standalone/install-apps-bin.d.ts +2 -0
  45. package/dist/standalone/install-apps-bin.js +3 -0
  46. package/dist/standalone/install-apps-cli.d.ts +3 -1
  47. package/dist/standalone/install-apps-cli.js +27 -17
  48. package/dist/standalone/machine-link.d.ts +2 -0
  49. package/dist/standalone/machine-link.js +34 -1
  50. package/dist/standalone/registry.d.ts +1 -1
  51. package/dist/testing/capabilities.d.ts +2 -0
  52. package/dist/testing/capabilities.js +3 -1
  53. package/dist/workers-git.js +1 -1
  54. package/dist/workers.d.ts +3 -0
  55. package/dist/workers.js +9 -1
  56. package/docs/api/agents.md +8 -4
  57. package/docs/api/apps.md +8 -4
  58. package/docs/api/browser.md +40 -16
  59. package/docs/api/keys.md +25 -3
  60. package/docs/api/workers.md +21 -14
  61. package/docs/site/agent-quickstart.mdx +23 -0
  62. package/examples/standalone/wrangler.jsonc +3 -4
  63. package/package.json +29 -17
@@ -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;
@@ -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(["running", "succeeded", "failed", "revoked"]),
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
- : "Build exited with " +
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 a failed build to the host's build output: its kind, never its text. */
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 ids = /* @__PURE__ */ Object.create(null);
16841
+ const ids2 = /* @__PURE__ */ Object.create(null);
16810
16842
  let counter = 0;
16811
16843
  for (const key of normalized.keys) {
16812
- ids[key] = `key_${counter++}`;
16844
+ ids2[key] = `key_${counter++}`;
16813
16845
  }
16814
16846
  doc.write(`const newResult = {};`);
16815
16847
  for (const key of normalized.keys) {
16816
- const id = ids[key];
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,204 @@ 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 BirdDogSystemIdentitySchema = Struct({
21744
+ kind: Literal2("system"),
21745
+ systemId: Literal2("hub")
21746
+ });
21747
+ var BIRD_DOG_DIAGNOSTIC_MAX_LENGTH = 200;
21748
+ var SettledBirdDogReplySchema = Struct({
21749
+ commandId: Identifier,
21750
+ outcome: Literals([
21751
+ "completed",
21752
+ "failed",
21753
+ "interrupted",
21754
+ "cancelled",
21755
+ "outcome_unknown"
21756
+ ]),
21757
+ diagnostic: optionalKey2(String4.check(isMinLength(1), isMaxLength(BIRD_DOG_DIAGNOSTIC_MAX_LENGTH)))
21758
+ });
21759
+ var BirdDogUndeliverableReasonSchema = Literals([
21760
+ "recipient_offline",
21761
+ "recipient_left"
21762
+ ]);
21763
+ var UndeliverableBirdDogReplySchema = Struct({
21764
+ outcome: Literal2("undeliverable"),
21765
+ reason: BirdDogUndeliverableReasonSchema
21766
+ });
21767
+ var AcceptedBirdDogReplySchema = Struct({
21768
+ outcome: Literal2("accepted"),
21769
+ commandId: Identifier
21770
+ });
21771
+ var BirdDogReplySchema = Union2([
21772
+ SettledBirdDogReplySchema,
21773
+ UndeliverableBirdDogReplySchema,
21774
+ AcceptedBirdDogReplySchema
21775
+ ]);
21776
+ var BirdDogAgentReplySchema = Union2([
21777
+ SettledBirdDogReplySchema,
21778
+ AcceptedBirdDogReplySchema,
21779
+ Struct({
21780
+ outcome: Literal2("undeliverable"),
21781
+ reason: Literal2("recipient_left")
21782
+ })
21783
+ ]);
21784
+ var REDACTED = "[redacted]";
21785
+ var schemeCredential = /\b(bearer|basic)\s+[^\s"',;]+/giu;
21786
+ var namedCredential = /\b((?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password|authorization)["']?\s*[:=]\s*["']?)(?!(?:bearer|basic)\s)[^\s"',;&]+/giu;
21787
+ var opaqueToken = /[A-Za-z0-9][\w-]{23,}/gu;
21788
+ function redactCredentials(text) {
21789
+ 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);
21790
+ }
21791
+ function boundedDiagnostic(text) {
21792
+ const line = redactCredentials(text.replace(/[\p{Cc}\p{Zl}\p{Zp}\s]+/gu, " ").trim()).slice(0, BIRD_DOG_DIAGNOSTIC_MAX_LENGTH).trimEnd();
21793
+ return line.length === 0 ? void 0 : line;
21794
+ }
21795
+ var BirdDogMailFields = {
21796
+ key: Identifier,
21797
+ to: BirdDogAgentRecipientReferenceSchema,
21798
+ replyTo: ArraySchema(BirdDogAgentReferenceSchema),
21799
+ content: MailContent,
21800
+ createdAt: Number5.check(isGreaterThanOrEqualTo2(0)),
21801
+ /** The sender's opaque host metadata; BirdDog never reads it. */
21802
+ meta: optionalKey2(Json2)
21803
+ };
21804
+ var BirdDogMailSchema = Union2([
21805
+ Struct({
21806
+ ...BirdDogMailFields,
21807
+ from: BirdDogAgentReferenceSchema,
21808
+ /** Settlement or acceptance facts, present only on a reply. */
21809
+ reply: optionalKey2(BirdDogAgentReplySchema)
21810
+ }),
21811
+ Struct({
21812
+ ...BirdDogMailFields,
21813
+ from: BirdDogSystemIdentitySchema,
21814
+ /** Only the Hub may author this offline disposition. */
21815
+ reply: Struct({
21816
+ outcome: Literal2("undeliverable"),
21817
+ reason: Literal2("recipient_offline")
21818
+ })
21819
+ })
21820
+ ]);
21821
+ var BirdDogAgentWorkSchema = Struct({
21822
+ jobId: Identifier,
21823
+ sessionId: Identifier,
21824
+ commandFactId: Identifier,
21825
+ recovery: optional2(Struct({
21826
+ maxRecoveries: Int.check(isGreaterThanOrEqualTo2(0))
21827
+ }))
21828
+ });
21829
+ var BirdDogAgentJobPayloadSchema = Struct({
21830
+ work: Struct({
21831
+ sessionId: Identifier,
21832
+ commandFactId: Identifier
21833
+ }),
21834
+ mail: optional2(BirdDogMailSchema)
21835
+ });
21836
+ var BirdDogAgentJobInputSchema = Struct({
21837
+ work: BirdDogAgentWorkSchema,
21838
+ mail: optional2(BirdDogMailSchema)
21839
+ });
21840
+
21841
+ // src/build/candidate-diagnostic.ts
21842
+ function candidateFailureDiagnostic(cause) {
21843
+ const messages = [];
21844
+ let current = cause;
21845
+ for (let depth = 0; depth < 3; depth += 1) {
21846
+ if (!(current instanceof Error)) {
21847
+ if (depth === 0) messages.push("Non-error thrown");
21848
+ break;
21849
+ }
21850
+ messages.push(
21851
+ `${depth === 0 ? "" : "Caused by: "}${current.message || current.name}`
21852
+ );
21853
+ current = current.cause;
21854
+ }
21855
+ return boundedDiagnostic(messages.join("; ")) ?? "Build failed";
21856
+ }
21857
+
21628
21858
  // ../puffball/dist/assets.js
21629
21859
  var positiveInteger = Number5.pipe(check(isInt()), check(isGreaterThan2(0)), check(makeFilter2(Number.isSafeInteger)));
21630
21860
  var assetBuildPolicySchema = Struct({
@@ -36087,10 +36317,14 @@ async function buildTeamCandidate(input) {
36087
36317
  var uploadConcurrency = 4;
36088
36318
  var uploadAttempts = 3;
36089
36319
  function reportFailure2(cause) {
36320
+ const diagnostic = candidateFailureDiagnostic(cause);
36321
+ process.stderr.write(`Team App source/build failed: ${diagnostic}
36322
+ `);
36090
36323
  process.stderr.write(
36091
36324
  `${JSON.stringify({
36092
36325
  source: "oyster-candidate-builder",
36093
- failureKind: cause instanceof Error ? "Error" : "NonErrorThrown"
36326
+ failureKind: cause instanceof Error ? "Error" : "NonErrorThrown",
36327
+ diagnostic
36094
36328
  })}
36095
36329
  `
36096
36330
  );
@@ -36199,7 +36433,6 @@ try {
36199
36433
  );
36200
36434
  } catch (cause) {
36201
36435
  reportFailure2(cause);
36202
- process.stderr.write("Team App source/build failed\n");
36203
36436
  process.exitCode = 1;
36204
36437
  }
36205
36438
  /*! Bundled license information:
@@ -0,0 +1,2 @@
1
+ /** The builder's error and at most two causes, safe for a build record. */
2
+ export declare function candidateFailureDiagnostic(cause: unknown): string;
@@ -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
+ }
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ /** The runtime needs a binding, not a Team or an authority projection. */
3
+ export type ConversationBinding = Readonly<{
4
+ bindingId: string;
5
+ lifecycle: "active" | "suspended";
6
+ selector: Readonly<{
7
+ provider: "event";
8
+ target: "new" | "named";
9
+ verifier: "fungi-v1" | "github" | "secret-url";
10
+ eventsPerMinute: number;
11
+ }> | Readonly<{
12
+ provider: "web" | "discord" | "email" | "game";
13
+ }>;
14
+ /** Zod's optional binding projection may carry an explicit undefined. */
15
+ limits?: BindingLimits | undefined;
16
+ }>;
17
+ /** Per-party edge limits applied before any work is accepted. */
18
+ export declare const bindingLimitsSchema: z.ZodObject<{
19
+ messagesPerPartyPerDay: z.ZodNumber;
20
+ charsPerMessage: z.ZodNumber;
21
+ newPartiesPerHour: z.ZodNumber;
22
+ }, z.core.$strict>;
23
+ /** Per-binding edge limits. */
24
+ export type BindingLimits = z.infer<typeof bindingLimitsSchema>;
25
+ /** Edge limits of a binding whose owner set none. */
26
+ export declare const DEFAULT_BINDING_LIMITS: BindingLimits;
27
+ /**
28
+ * How long a GitHub body digest keeps a changed-id replay out: the verifiers'
29
+ * signed-timestamp skew (Veil's `WEBHOOK_SIGNATURE_MAX_SKEW_SECONDS`; a test
30
+ * pins them equal). GitHub signs no timestamp, so this window is the bound.
31
+ */
32
+ export declare const EVENT_REPLAY_WINDOW_SECONDS = 300;
33
+ /** The edge limits one binding runs under: its owner's, else its kind's default. */
34
+ export declare function limitsOf(binding: ConversationBinding): BindingLimits;
35
+ export declare const discordSnowflakeSchema: z.ZodString;
36
+ /** The party on the other side of a channel conversation; never a member. */
37
+ export declare const channelPartySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
38
+ kind: z.ZodLiteral<"visitor">;
39
+ visitorId: z.ZodString;
40
+ }, z.core.$strict>, z.ZodObject<{
41
+ kind: z.ZodLiteral<"discord-user">;
42
+ guildId: z.ZodString;
43
+ userId: z.ZodString;
44
+ }, z.core.$strict>, z.ZodObject<{
45
+ kind: z.ZodLiteral<"email-sender">;
46
+ address: z.ZodEmail;
47
+ dmarc: z.ZodEnum<{
48
+ none: "none";
49
+ pass: "pass";
50
+ fail: "fail";
51
+ }>;
52
+ }, z.core.$strict>, z.ZodObject<{
53
+ kind: z.ZodLiteral<"player">;
54
+ worldId: z.ZodString;
55
+ playerId: z.ZodString;
56
+ }, z.core.$strict>, z.ZodObject<{
57
+ kind: z.ZodLiteral<"source">;
58
+ source: z.ZodString;
59
+ }, z.core.$strict>], "kind">;
60
+ /** One channel party. */
61
+ export type ChannelParty = z.infer<typeof channelPartySchema>;
62
+ /** Short human label for a party, used in author metadata and owner rows. */
63
+ export declare function partyLabel(party: ChannelParty): string;
@@ -0,0 +1,85 @@
1
+ import { z } from "zod";
2
+ import { channelIds } from "./ids.js";
3
+ /** Per-party edge limits applied before any work is accepted. */
4
+ export const bindingLimitsSchema = z.strictObject({
5
+ messagesPerPartyPerDay: z.number().int().min(1).max(10_000),
6
+ charsPerMessage: z.number().int().min(1).max(16_000),
7
+ newPartiesPerHour: z.number().int().min(1).max(100_000),
8
+ });
9
+ /** Edge limits of a binding whose owner set none. */
10
+ export const DEFAULT_BINDING_LIMITS = Object.freeze({
11
+ messagesPerPartyPerDay: 30,
12
+ charsPerMessage: 4_000,
13
+ newPartiesPerHour: 200,
14
+ });
15
+ /**
16
+ * Edge limits of an incoming-events binding whose owner set none. Events are
17
+ * not people: `eventsPerMinute` bounds intake, so the
18
+ * per-party and new-party limits sit at their ceilings, and one event may
19
+ * carry the longest message intake accepts.
20
+ */
21
+ const DEFAULT_EVENT_LIMITS = Object.freeze({
22
+ messagesPerPartyPerDay: 10_000,
23
+ charsPerMessage: 16_000,
24
+ newPartiesPerHour: 100_000,
25
+ });
26
+ /**
27
+ * How long a GitHub body digest keeps a changed-id replay out: the verifiers'
28
+ * signed-timestamp skew (Veil's `WEBHOOK_SIGNATURE_MAX_SKEW_SECONDS`; a test
29
+ * pins them equal). GitHub signs no timestamp, so this window is the bound.
30
+ */
31
+ export const EVENT_REPLAY_WINDOW_SECONDS = 300;
32
+ /** The edge limits one binding runs under: its owner's, else its kind's default. */
33
+ export function limitsOf(binding) {
34
+ return (binding.limits ??
35
+ (binding.selector.provider === "event"
36
+ ? DEFAULT_EVENT_LIMITS
37
+ : DEFAULT_BINDING_LIMITS));
38
+ }
39
+ export const discordSnowflakeSchema = z.string().regex(/^\d{17,20}$/u);
40
+ /** The party on the other side of a channel conversation; never a member. */
41
+ export const channelPartySchema = z.discriminatedUnion("kind", [
42
+ z.strictObject({
43
+ kind: z.literal("visitor"),
44
+ visitorId: z
45
+ .string()
46
+ .refine((value) => channelIds.visitor.is(value), "Invalid visitor"),
47
+ }),
48
+ z.strictObject({
49
+ kind: z.literal("discord-user"),
50
+ guildId: discordSnowflakeSchema,
51
+ userId: discordSnowflakeSchema,
52
+ }),
53
+ z.strictObject({
54
+ kind: z.literal("email-sender"),
55
+ address: z.email(),
56
+ dmarc: z.enum(["pass", "fail", "none"]),
57
+ }),
58
+ z.strictObject({
59
+ kind: z.literal("player"),
60
+ worldId: z.string().min(1).max(160),
61
+ playerId: z.string().min(1).max(160),
62
+ }),
63
+ z.strictObject({
64
+ kind: z.literal("source"),
65
+ /** The sender's declared source identity, such as `github`. */
66
+ source: z.string().regex(/^[a-z0-9][a-z0-9._-]{0,63}$/u),
67
+ }),
68
+ ]);
69
+ /** Short human label for a party, used in author metadata and owner rows. */
70
+ export function partyLabel(party) {
71
+ switch (party.kind) {
72
+ case "visitor":
73
+ return `Visitor ${party.visitorId.slice(-4)}`;
74
+ case "discord-user":
75
+ return `Discord user ${party.userId}`;
76
+ case "email-sender":
77
+ return party.dmarc === "pass"
78
+ ? party.address
79
+ : `${party.address} (unverified)`;
80
+ case "player":
81
+ return `Player ${party.playerId}`;
82
+ case "source":
83
+ return party.source;
84
+ }
85
+ }