@vzn/vx-dte-server 0.0.652 → 0.0.653

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/PROTOCOL.md CHANGED
@@ -102,7 +102,12 @@ A participant sends:
102
102
  fails, `n` then `null`.
103
103
  - A frame that fails is answered `{ "n", "status", "error" }`; one that is
104
104
  no JSON object, or none of the above, `400`; one over 1 MiB `413`,
105
- answered by its `n` when the frame starts `{"n":`.
105
+ answered by its `n` when the frame starts `{"n":`. One past its
106
+ runtime's own limit (16 MiB on Bun, 32 MiB on Workers) never reaches
107
+ the relay: the runtime closes the socket `1009`.
108
+ - A send whose `body` the relay cannot pass (nested deeper than its
109
+ runtime serializes) is `400` in its result; its receiver keeps its
110
+ socket.
106
111
  - A removed peer's socket is closed `4404` after what waited for it; the
107
112
  host is not told it left.
108
113
 
@@ -113,7 +118,8 @@ socket, or a push that failed) is dropped. Delivery is the clients': a
113
118
  sender numbers each send, the receiver acknowledges it end to end, and
114
119
  the sender sends again what is not acknowledged, at once on a new socket.
115
120
  How they number and acknowledge is inside `body`, their own protocol;
116
- `@vzn/vx-dte` puts `{ "n" }` and `{ "acked" }` beside its payload.
121
+ `@vzn/vx-dte` sends `{ "n", "b", "a"? }`: its number, its payload and
122
+ its ack.
117
123
 
118
124
  Only the relay's notices wait, in memory, for the participant's next
119
125
  socket.
package/README.md CHANGED
@@ -163,4 +163,5 @@ The same relay as a `fetch` handler, to mount in a server of your own
163
163
  repository: `repository` for GitHub Actions, `project_path` otherwise.
164
164
  - `workflowClaimOf(iss)` names the claim that names the workflow a
165
165
  token's job is defined in: `job_workflow_ref` for GitHub Actions,
166
- `ci_config_ref_uri` otherwise.
166
+ `ci_config_ref_uri` otherwise. The relay matches it up to its last `@`,
167
+ before the ref or SHA.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx-dte-server",
3
- "version": "0.0.652",
3
+ "version": "0.0.653",
4
4
  "license": "MIT",
5
5
  "description": "The reference relay for @vzn/vx-dte: carries messages between a vx run's orchestrator and its CI agents, admitting only CI OIDC tokens of the same repository.",
6
6
  "keywords": [
package/src/oidc.ts CHANGED
@@ -127,7 +127,7 @@ export function parseAllow(list: string): Allowed[] {
127
127
  })
128
128
  }
129
129
 
130
- /** What `workflowClaimOf(iss)` says, up to its `@`, for a job of `workflow` in `repository`. */
130
+ /** What `workflowClaimOf(iss)` says, up to its last `@`, for a job of `workflow` in `repository`. */
131
131
  function workflowPath(iss: string, repository: string, workflow: string): string {
132
132
  return iss === GITHUB
133
133
  ? `${repository}/.github/workflows/${workflow}`
@@ -232,17 +232,15 @@ export function createVerifier(opts: OidcOptions): (token: string) => Promise<Ve
232
232
  const ref = claims[claim]
233
233
  if (typeof ref !== 'string') throw new TokenError(`no ${claim} claim`, 403)
234
234
  const repo = String(claims[repositoryClaimOf(iss)])
235
- // The path is the claim up to an `@` that a ref follows: a file name may hold `@` too.
236
- const named = ofRepo.some((a) => {
237
- const p = `${workflowPath(iss, a.repository ?? repo, a.workflow!)}@`
238
- return same(ref.slice(0, p.length), p)
239
- })
240
- if (!named) {
241
- throw new TokenError(
242
- `workflow ${ref.replace(/@.*$/, '')} is not in this relay's ALLOW`,
243
- 403,
244
- )
245
- }
235
+ // Cut at the last `@`, as vx-devtools does: the ref after it is
236
+ // `refs/…` or a SHA (a reusable workflow pinned by one), and GitLab's
237
+ // CI file may itself hold one (`.gitlab-ci.yml@group/project`, a file
238
+ // kept in another project, which a match by the prefix `<path>@` took
239
+ // as this project's own). A ref holding an `@` is refused, never widened.
240
+ const at = ref.lastIndexOf('@')
241
+ const workflow = at < 0 ? ref : ref.slice(0, at)
242
+ if (!ofRepo.some((a) => same(workflow, workflowPath(iss, a.repository ?? repo, a.workflow!))))
243
+ throw new TokenError(`workflow ${workflow} is not in this relay's ALLOW`, 403)
246
244
  }
247
245
  const run = (opts.runClaims?.[iss] ?? runClaimsOf(iss)).map((c) => {
248
246
  const v = claims[c]
package/src/relay.ts CHANGED
@@ -198,10 +198,16 @@ export function createRelay(opts: RelayOptions): Relay {
198
198
  * relay's own notices (`notice`: joined, lost, closed) have no sender to
199
199
  * resend them, so they wait for the next socket.
200
200
  */
201
- const deliver = (session: Session, p: Participant, m: Message, notice = false): void => {
201
+ const deliver = (
202
+ session: Session,
203
+ p: Participant,
204
+ m: Message,
205
+ notice = false,
206
+ text?: string,
207
+ ): void => {
202
208
  if (p.socket !== undefined && p.queue.length === 0) {
203
209
  try {
204
- p.socket.send(JSON.stringify({ messages: [m] }))
210
+ p.socket.send(text ?? JSON.stringify({ messages: [m] }))
205
211
  return
206
212
  } catch {
207
213
  // quiet: a socket that cannot send is unplugged; the participant rejoins
@@ -443,10 +449,16 @@ export function createRelay(opts: RelayOptions): Relay {
443
449
  ? session.byId.get(body.to)
444
450
  : undefined
445
451
  if (to === undefined || to === from) throw new HttpError(404, 'unknown to')
446
- deliver(session, to, {
447
- from: from.role === 'host' ? 'host' : from.id,
448
- body: body.body,
449
- })
452
+ const m = { from: from.role === 'host' ? 'host' : from.id, body: body.body }
453
+ // Serialized before the push: a body parsed but nested past what the
454
+ // runtime serializes threw in it, and that unplugged the receiver.
455
+ let text: string
456
+ try {
457
+ text = JSON.stringify({ messages: [m] })
458
+ } catch {
459
+ throw new HttpError(400, 'body nests too deep to pass')
460
+ }
461
+ deliver(session, to, m, false, text)
450
462
  }
451
463
 
452
464
  const live = (key: string): Session => {
package/src/serve.ts CHANGED
@@ -39,6 +39,11 @@ export function serveRelay(opts: ServeOptions): RelayServer {
39
39
  idleTimeout: 60,
40
40
  fetch: (req) => relay.fetch(req),
41
41
  websocket: {
42
+ // Bun's default, said here because PROTOCOL.md names it: past it Bun
43
+ // closes the socket 1009 before the relay sees the frame (Workers
44
+ // does so past 32 MiB). Under it a frame over 1 MiB is a 413; more
45
+ // would only hold more of a sender's bytes per socket.
46
+ maxPayloadLength: 16 << 20,
42
47
  // A socket with no frame in three heartbeats has no peer listening
43
48
  // either (each probes a quiet one within two): closed, it loses its
44
49
  // participant, as a half-open one on no other host would be.