pi-radar 0.9.0 → 0.11.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 (42) hide show
  1. package/README.md +8 -10
  2. package/dist/extension/baseline.js +343 -361
  3. package/dist/extension/radar-children.js +108 -218
  4. package/dist/extension/radar-client.js +103 -15
  5. package/dist/extension/radar-execution.js +26 -17
  6. package/dist/extension/radar-inspection.js +0 -45
  7. package/dist/extension/radar-launch.js +24 -185
  8. package/dist/extension/radar-publication.js +26 -17
  9. package/dist/extension/radar-runtime.js +1 -1
  10. package/dist/index.js +317 -488
  11. package/extension/generated-contracts.ts +362 -13
  12. package/extension/radar-children.ts +60 -155
  13. package/extension/radar-client.ts +130 -19
  14. package/extension/radar-inspection.ts +2 -77
  15. package/extension/radar-launch.ts +52 -391
  16. package/extension/radar-publication.ts +36 -53
  17. package/extension/radar-runtime.ts +1 -1
  18. package/package.json +1 -1
  19. package/schema/public/v1/observation-get-response.deserialize.schema.json +5 -25
  20. package/schema/public/v1/observation-get-response.serialize.schema.json +7 -29
  21. package/schema/public/v1/observation-snapshot.deserialize.schema.json +5 -25
  22. package/schema/public/v1/observation-snapshot.serialize.schema.json +7 -29
  23. package/schema/public/v1/spawn-list-page.deserialize.schema.json +40 -17
  24. package/schema/public/v1/spawn-list-page.serialize.schema.json +40 -17
  25. package/schema/public/v1/subject-assignment-acquire.deserialize.schema.json +78 -0
  26. package/schema/public/v1/subject-assignment-acquire.serialize.schema.json +79 -0
  27. package/schema/public/v1/subject-assignment-publish.deserialize.schema.json +310 -0
  28. package/schema/public/v1/subject-assignment-publish.serialize.schema.json +310 -0
  29. package/schema/public/v1/subject-assignment-retire.deserialize.schema.json +22 -0
  30. package/schema/public/v1/subject-assignment-retire.serialize.schema.json +22 -0
  31. package/schema/public/v1/subject-child-get-params.deserialize.schema.json +18 -0
  32. package/schema/public/v1/subject-child-get-params.serialize.schema.json +18 -0
  33. package/schema/public/v1/subject-children-params.deserialize.schema.json +51 -0
  34. package/schema/public/v1/subject-children-params.serialize.schema.json +54 -0
  35. package/schema/public/v1/subject-get-params.deserialize.schema.json +14 -0
  36. package/schema/public/v1/subject-get-params.serialize.schema.json +14 -0
  37. package/schema/public/v1/subject-get-result.deserialize.schema.json +566 -0
  38. package/schema/public/v1/subject-get-result.serialize.schema.json +573 -0
  39. package/schema/public/v1/subject-list-params.deserialize.schema.json +38 -0
  40. package/schema/public/v1/subject-list-params.serialize.schema.json +43 -0
  41. package/schema/public/v1/subject-list-result.deserialize.schema.json +575 -0
  42. package/schema/public/v1/subject-list-result.serialize.schema.json +583 -0
@@ -438,15 +438,18 @@ function createChannelWriter(input) {
438
438
  function blank(agentId) {
439
439
  return {
440
440
  version: RADAR_PUBLICATION_VERSION,
441
- registration_id: agentId,
441
+ target_id: agentId,
442
442
  channel,
443
443
  publisher,
444
444
  sequence: 0
445
445
  };
446
446
  }
447
+ function addressed(target) {
448
+ return channel === "assignment" ? { subject_id: target } : { registration_id: target };
449
+ }
447
450
  function loadRecord() {
448
451
  const value = store.read(recordName);
449
- const agentId = textAt(value, "registration_id");
452
+ const agentId = textAt(value, "target_id");
450
453
  const storedPublisher = recordAt(value, "publisher");
451
454
  if (numberAt(value, "version") !== RADAR_PUBLICATION_VERSION || agentId === void 0 || !isUuid(agentId) || textAt(value, "channel") !== channel || textAt(storedPublisher, "source") !== publisher.source || textAt(storedPublisher, "incarnation") !== publisher.incarnation || textAt(storedPublisher, "reporting_owner") !== publisher.reporting_owner)
452
455
  return blank("");
@@ -461,7 +464,7 @@ function createChannelWriter(input) {
461
464
  const sentAt = numberAt(acceptedValue, "sent_at");
462
465
  return {
463
466
  version: RADAR_PUBLICATION_VERSION,
464
- registration_id: agentId,
467
+ target_id: agentId,
465
468
  channel,
466
469
  publisher,
467
470
  ...handle !== void 0 && generation !== void 0 ? { writer: { handle, generation } } : {},
@@ -502,15 +505,11 @@ function createChannelWriter(input) {
502
505
  if (record.writer !== void 0) return true;
503
506
  const agentId = await input.subject(recovering);
504
507
  if (agentId === void 0) return false;
505
- if (record.registration_id !== agentId) {
508
+ if (record.target_id !== agentId) {
506
509
  record = blank(agentId);
507
510
  save();
508
511
  }
509
- const answer = await client.acquire({
510
- registration_id: agentId,
511
- channel,
512
- publisher
513
- });
512
+ const answer = await client.acquire({ ...addressed(agentId), channel, publisher });
514
513
  if (answer.ok === false) {
515
514
  if (answer.code === "not_found") {
516
515
  recovering = true;
@@ -537,7 +536,7 @@ function createChannelWriter(input) {
537
536
  const writer = record.writer;
538
537
  if (writer === void 0) return;
539
538
  const answer = await client.publish({
540
- registration_id: record.registration_id,
539
+ ...addressed(record.target_id),
541
540
  channel,
542
541
  writer_handle: writer.handle,
543
542
  sequence: pending.sequence,
@@ -631,9 +630,9 @@ function createChannelWriter(input) {
631
630
  stopPump();
632
631
  const writer = record.writer;
633
632
  if (writer === void 0) return;
634
- const agentId = record.registration_id;
633
+ const agentId = record.target_id;
635
634
  try {
636
- void client.retire({ registration_id: agentId, channel, writer_handle: writer.handle }).then((answer) => {
635
+ void client.retire({ ...addressed(agentId), channel, writer_handle: writer.handle }).then((answer) => {
637
636
  if (answer.ok === true) {
638
637
  try {
639
638
  record.retired_at = new Date(scheduler.now()).toISOString();
@@ -723,6 +722,7 @@ function createPresenceReporter(input) {
723
722
  }
724
723
  claim.registration_id = answer.value.registration_id;
725
724
  claim.subject_id = answer.value.standing === "granted" ? answer.value.subject_id ?? void 0 : void 0;
725
+ if (claim.subject_id !== void 0) input.granted?.(claim.subject_id);
726
726
  standing = answer.value.standing === "granted" ? { state: "managed", subject_id: claim.subject_id } : answer.value.standing === "duplicate" ? { state: "duplicate" } : answer.value.standing === "pending" ? { state: "pending" } : { state: "unmanaged" };
727
727
  diagnostic = answer.value.standing === "duplicate" ? DUPLICATE_SESSION_DIAGNOSTIC : void 0;
728
728
  save();
@@ -904,7 +904,7 @@ function createRadarPublicationInner(options) {
904
904
  agentId = registered;
905
905
  processRecord = { ...record, registration_id: agentId };
906
906
  store.write(registrationName, processRecord);
907
- if (options.binding !== void 0) writeBinding(options.binding);
907
+ if (options.binding !== void 0) writeBinding(options.binding, grantedSubject());
908
908
  return agentId;
909
909
  })();
910
910
  try {
@@ -913,11 +913,16 @@ function createRadarPublicationInner(options) {
913
913
  subjectReady = void 0;
914
914
  }
915
915
  }
916
- function writeBinding(binding) {
916
+ function grantedSubject() {
917
+ const standing = presence.management();
918
+ return standing?.state === "managed" ? standing.subject_id : void 0;
919
+ }
920
+ function writeBinding(binding, subjectId) {
917
921
  if (agentId === void 0) return;
918
922
  store.write(`bindings/${binding.key}.json`, {
919
923
  version: RADAR_PUBLICATION_VERSION,
920
924
  registration_id: agentId,
925
+ ...subjectId !== void 0 ? { subject_id: subjectId } : {},
921
926
  incarnation,
922
927
  run: binding.run,
923
928
  owner: binding.owner,
@@ -957,6 +962,9 @@ function createRadarPublicationInner(options) {
957
962
  ...launchRegistrationId ? { registration_id: launchRegistrationId } : {}
958
963
  };
959
964
  },
965
+ granted: (subjectId) => {
966
+ if (options.binding !== void 0) writeBinding(options.binding, subjectId);
967
+ },
960
968
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
961
969
  });
962
970
  const facts = createChannelWriter({
@@ -988,8 +996,7 @@ function createRadarPublicationInner(options) {
988
996
  channel: "assignment",
989
997
  publisher: publisher("herdsman-owner", reportingOwner),
990
998
  codec: assignmentCodec,
991
- // The owner publishes about a subject it did not register: the exact
992
- // registration_id from the child's sidecar is the whole correlation.
999
+ // Radar owns this assignment across the child's process replacements.
993
1000
  subject: async () => target,
994
1001
  leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
995
1002
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
@@ -1010,16 +1017,18 @@ function createRadarPublicationInner(options) {
1010
1017
  return void 0;
1011
1018
  }
1012
1019
  const bound = textAt(value, "registration_id");
1020
+ const subjectId = textAt(value, "subject_id");
1013
1021
  const boundIncarnation = textAt(value, "incarnation");
1014
1022
  const run = textAt(value, "run");
1015
1023
  const owner = textAt(value, "owner");
1016
1024
  const label = textAt(value, "label");
1017
1025
  const observedAt = textAt(value, "observed_at");
1018
- if (!isUuid(bound) || !isUuid(boundIncarnation) || run === void 0 || owner === void 0 || label === void 0 || observedAt === void 0)
1026
+ if (!isUuid(bound) || subjectId !== void 0 && !isUuid(subjectId) || !isUuid(boundIncarnation) || run === void 0 || owner === void 0 || label === void 0 || observedAt === void 0)
1019
1027
  return void 0;
1020
1028
  return {
1021
1029
  version: RADAR_PUBLICATION_VERSION,
1022
1030
  registration_id: bound,
1031
+ ...subjectId !== void 0 ? { subject_id: subjectId } : {},
1023
1032
  incarnation: boundIncarnation,
1024
1033
  run,
1025
1034
  owner,
@@ -1,45 +0,0 @@
1
- // extension/radar-inspection.ts
2
- var INSPECTION_LINES = 80;
3
- var INSPECTION_OUTPUT_BYTES = 16 * 1024;
4
- async function provedRead(client, target) {
5
- const read = await client.agentGet(target.agentId);
6
- return read.ok === true && read.value.verification === "verified" && read.value.location?.pane === target.paneId && (target.session === void 0 || read.value.contextSession === target.session) ? read.value : void 0;
7
- }
8
- async function inspectRadarChild(client, target) {
9
- const before = await provedRead(client, target);
10
- if (before === void 0)
11
- throw new Error("Inspection target identity did not match");
12
- const output = await client.output({
13
- pane_id: target.paneId,
14
- source: "recent_unwrapped",
15
- lines: INSPECTION_LINES
16
- });
17
- if (output.ok === false)
18
- throw new Error(
19
- `Inspection output unavailable (${output.code}: ${output.message})`
20
- );
21
- const foreground = await client.processInfo({ pane_id: target.paneId });
22
- const after = await provedRead(client, target);
23
- if (after === void 0)
24
- throw new Error("Inspection target changed during capture");
25
- const lines = output.value.text.trim().split(/\r?\n/).slice(-INSPECTION_LINES);
26
- const bounded = utf8Tail(lines.join("\n"), INSPECTION_OUTPUT_BYTES);
27
- return Object.freeze({
28
- capturedAt: Date.now(),
29
- recentOutputTruncated: output.value.truncated || bounded.truncated,
30
- ...bounded.text ? { recentOutput: bounded.text } : {},
31
- ...foreground.ok === true ? { foreground: foreground.value } : {},
32
- ...after.activity !== void 0 ? { activity: after.activity } : {}
33
- });
34
- }
35
- function utf8Tail(value, maxBytes) {
36
- const bytes = Buffer.from(value);
37
- if (bytes.length <= maxBytes) return { text: value, truncated: false };
38
- let start = bytes.length - maxBytes;
39
- while (start < bytes.length && (bytes[start] & 192) === 128) start++;
40
- return { text: bytes.subarray(start).toString(), truncated: true };
41
- }
42
- export {
43
- inspectRadarChild,
44
- provedRead
45
- };
@@ -8,103 +8,9 @@ function isUuid(value) {
8
8
  return typeof value === "string" && UUID.test(value);
9
9
  }
10
10
 
11
- // extension/radar-location.ts
12
- function radarPaneHandle(qualified) {
13
- return qualified.slice(qualified.lastIndexOf(":") + 1);
14
- }
15
-
16
11
  // extension/radar-launch.ts
17
- function unknownChild(reason) {
18
- return { kind: "unknown", diagnostic: trajectory(reason) };
19
- }
20
- function trajectory(reason) {
21
- return `the daemon's current record of this launch's child is ${reason}`;
22
- }
23
- async function observeRadarChild(client, provenance) {
24
- if (provenance.registration_id === void 0)
25
- return unknownChild("not bound to a child by this launch");
26
- if (provenance.pane_id === void 0)
27
- return unknownChild("without a pane this launch recorded for it");
28
- const read = await client.spawnGet(provenance.request_id);
29
- if (read.ok === false)
30
- return unknownChild(
31
- `not readable (${read.code}: ${read.message})`
32
- );
33
- const topology = read.value;
34
- const edge = topology.edge;
35
- if (edge.bound === void 0 || edge.bound.registration_id === void 0)
36
- return unknownChild("not bound to a child by the daemon");
37
- if (edge.bound.registration_id !== provenance.registration_id)
38
- return unknownChild(
39
- `bound to another subject (${edge.bound.registration_id}), not to ${provenance.registration_id}`
40
- );
41
- if (edge.location?.pane === void 0)
42
- return unknownChild("recorded without a pane");
43
- if (edge.location.pane !== provenance.pane_id)
44
- return unknownChild(
45
- `recorded on pane ${edge.location.pane}, not on ${provenance.pane_id}`
46
- );
47
- if (edge.exit_observation?.state !== "unknown")
48
- return unknownChild("recorded with an observed process exit");
49
- if (topology.freshness !== "fresh")
50
- return unknownChild(
51
- "on a pane the daemon's own inventory does not currently report"
52
- );
53
- if (topology.state !== "bound")
54
- return unknownChild(
55
- `not currently placed (state ${topology.state ?? "unreported"})`
56
- );
57
- const reply = await client.agentGet(edge.bound.registration_id);
58
- if (reply.ok === false)
59
- return unknownChild(
60
- `process verdict not readable (${reply.code}: ${reply.message})`
61
- );
62
- const { verification, location, contextSession } = reply.value;
63
- if (verification === "absent" || verification === "mismatched")
64
- return {
65
- kind: "gone",
66
- verification,
67
- diagnostic: trajectory(`the registered process is ${verification}`)
68
- };
69
- if (verification !== "verified")
70
- return unknownChild(`process verdict is ${verification}`);
71
- if (location?.pane !== provenance.pane_id)
72
- return unknownChild(
73
- `registered on ${location?.pane ?? "no pane"}, not on ${provenance.pane_id}`
74
- );
75
- if (provenance.session_id === void 0)
76
- return unknownChild("without a session this launch recorded for it");
77
- if (contextSession !== provenance.session_id)
78
- return unknownChild(
79
- `publishing session ${contextSession ?? "none"}, not ${provenance.session_id}`
80
- );
81
- return {
82
- kind: "live",
83
- evidence: {
84
- request_id: provenance.request_id,
85
- registration_id: edge.bound.registration_id,
86
- source: edge.bound.source,
87
- incarnation: edge.bound.incarnation,
88
- pane_id: edge.location.pane
89
- }
90
- };
91
- }
92
- async function readProcessGone(client, agentId) {
93
- const read = await client.agentGet(agentId);
94
- if (read.ok === false) return void 0;
95
- const { verification } = read.value;
96
- return verification === "absent" || verification === "mismatched" ? verification : void 0;
97
- }
98
- async function relaunchRefusalForChild(client, agentId) {
99
- const read = await client.agentGet(agentId);
100
- if (read.ok === false)
101
- return `the child's process cannot be read (${read.code}: ${read.message})`;
102
- const { verification } = read.value;
103
- if (verification === "absent" || verification === "mismatched") return void 0;
104
- return `the child's registered process is ${verification === "verified" ? "still running" : "not proved gone"}`;
105
- }
106
12
  function refusedLaunch(message) {
107
- return { kind: "refused", message };
13
+ return { kind: "failed", message };
108
14
  }
109
15
  function unresolvedLaunch(requestId, message) {
110
16
  return {
@@ -113,14 +19,6 @@ function unresolvedLaunch(requestId, message) {
113
19
  message: `spawn ${requestId} is unresolved (${message})`
114
20
  };
115
21
  }
116
- function edgeStanding(edge) {
117
- if (edge.created === "refused" || edge.launched === "refused")
118
- return "refused";
119
- return edge.created === "completed" && edge.launched === "completed" ? "launched" : "unknown";
120
- }
121
- function edgeMessage(edge) {
122
- return `the daemon's spawn record is created ${edge.created} and launched ${edge.launched}`;
123
- }
124
22
  var PRE_EFFECT_CODES = [
125
23
  "absent",
126
24
  "unsupported",
@@ -133,14 +31,12 @@ var PRE_EFFECT_CODES = [
133
31
  "busy",
134
32
  "backend_unavailable"
135
33
  ];
136
- function spawnFailure(result, requestId) {
34
+ function launchFailure(result, requestId) {
137
35
  return PRE_EFFECT_CODES.includes(result.code) ? refusedLaunch(result.message) : unresolvedLaunch(requestId, result.message);
138
36
  }
139
37
  async function launchRadarWorker(options) {
140
38
  const { client, launch, signal } = options;
141
39
  const pollMs = options.pollMs ?? 250;
142
- const deadline = Date.now() + options.timeoutMs;
143
- const remaining = () => Math.max(0, deadline - Date.now());
144
40
  const parent = await options.publication.subject();
145
41
  if (parent === void 0 || !isUuid(parent.registration_id))
146
42
  return refusedLaunch(
@@ -171,55 +67,22 @@ async function launchRadarWorker(options) {
171
67
  ...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
172
68
  });
173
69
  if (spawned.ok === false) {
174
- const refusal = spawnFailure(spawned, launch.request_id);
175
- if (refusal.kind === "refused") return refusal;
176
- }
177
- if (spawned.ok === true) {
178
- const standing = edgeStanding(spawned.value);
179
- if (standing === "refused")
180
- return refusedLaunch(edgeMessage(spawned.value));
181
- if (standing === "unknown")
182
- return unresolvedLaunch(launch.request_id, edgeMessage(spawned.value));
70
+ const refusal = launchFailure(spawned, launch.request_id);
71
+ if (refusal.kind === "failed") return refusal;
183
72
  }
184
73
  const bound = await waitForChild(client, launch.request_id, {
185
- remaining,
186
74
  pollMs,
187
75
  ...signal !== void 0 ? { signal } : {}
188
76
  });
189
77
  if (bound.kind !== "bound") return bound;
190
- const agentId = bound.registration_id;
191
- try {
192
- const state = await options.readers.waitForState(
193
- launch.mailbox,
194
- (candidate) => options.readers.startupAckMatches(candidate, {
195
- generation: launch.generation,
196
- runId: launch.identity.runId,
197
- ownerSessionId: launch.identity.ownerSessionId,
198
- workspaceId: launch.identity.workspaceId,
199
- agentLabel: launch.identity.agentLabel,
200
- // The child writes the raw TMUX_PANE of the pane it runs in. Requiring
201
- // the pane the daemon created is what refuses a child that inherited
202
- // another process's coordinates.
203
- paneId: radarPaneHandle(bound.pane_id)
204
- }),
205
- { timeoutMs: remaining(), ...signal !== void 0 ? { signal } : {} }
206
- );
207
- return {
208
- kind: "ready",
209
- value: {
210
- request_id: launch.request_id,
211
- subject_id: bound.subject_id,
212
- pane_id: bound.pane_id,
213
- state
214
- }
215
- };
216
- } catch (error) {
217
- const claimed = options.readers.readState(launch.mailbox)?.paneId;
218
- return unresolvedLaunch(
219
- launch.request_id,
220
- claimed !== void 0 && claimed !== radarPaneHandle(bound.pane_id) ? `the bound child ${agentId} claims the pane ${claimed}, not the pane ${bound.pane_id} this launch created for it` : `the bound child ${agentId} has not acknowledged this launch's startup (${String(error)})`
221
- );
222
- }
78
+ return {
79
+ kind: "ready",
80
+ value: {
81
+ request_id: launch.request_id,
82
+ subject_id: bound.subject_id,
83
+ pane_id: bound.pane_id
84
+ }
85
+ };
223
86
  }
224
87
  async function waitForChild(client, requestId, options) {
225
88
  let last = "the daemon has not answered";
@@ -228,37 +91,21 @@ async function waitForChild(client, requestId, options) {
228
91
  return unresolvedLaunch(requestId, `the launch was cancelled (${last})`);
229
92
  const answer = await client.spawnGet(requestId);
230
93
  if (answer.ok === true) {
231
- const { edge, state, freshness } = answer.value;
232
- const standing = edgeStanding(edge);
233
- if (standing === "refused")
234
- return refusedLaunch(`${edgeMessage(edge)}`);
235
- if (standing === "unknown")
236
- return unresolvedLaunch(requestId, edgeMessage(edge));
94
+ const { edge } = answer.value;
95
+ if (edge.withdrawn !== void 0)
96
+ return refusedLaunch(`the daemon withdrew this launch: ${edge.withdrawn.reason}`);
97
+ if (edge.granted_at === void 0) {
98
+ await delay(options.pollMs);
99
+ continue;
100
+ }
237
101
  const pane = edge.location?.pane;
238
102
  if (pane === void 0 || pane === "")
239
103
  return unresolvedLaunch(
240
104
  requestId,
241
105
  "the daemon's launch recorded no pane to verify the child against"
242
106
  );
243
- if (freshness !== "fresh")
244
- return unresolvedLaunch(
245
- requestId,
246
- state === "unresolved" ? `the daemon's inventory does not report the pane ${pane} its own record names for this child` : `the daemon cannot confirm the pane ${pane} it recorded for this child`
247
- );
248
- if (edge.exit_observation?.state === "exited")
249
- return unresolvedLaunch(
250
- requestId,
251
- "the daemon observed the launched child's process exit"
252
- );
253
107
  const agentId = edge.bound?.registration_id;
254
108
  if (agentId !== void 0 && isUuid(agentId)) {
255
- const source = edge.bound?.source;
256
- const incarnation = edge.bound?.incarnation;
257
- if (source === void 0 || source === "" || incarnation === void 0 || incarnation === "")
258
- return unresolvedLaunch(
259
- requestId,
260
- `the daemon bound the child ${agentId} without recording the process it registered as`
261
- );
262
109
  const subjectId = edge.bound?.subject_id;
263
110
  if (subjectId === void 0 || !isUuid(subjectId))
264
111
  return unresolvedLaunch(
@@ -268,26 +115,18 @@ async function waitForChild(client, requestId, options) {
268
115
  return {
269
116
  kind: "bound",
270
117
  subject_id: subjectId,
271
- registration_id: agentId,
272
- pane_id: pane,
273
- source,
274
- incarnation
118
+ pane_id: pane
275
119
  };
276
120
  }
277
121
  last = "no child has registered with this launch's token yet";
278
122
  } else {
279
- last = answer.message;
123
+ return unresolvedLaunch(requestId, answer.message);
280
124
  }
281
- if (options.remaining() <= 0)
282
- return unresolvedLaunch(requestId, last);
283
- await delay(options.pollMs, void 0, {
284
- ...options.signal !== void 0 ? { signal: options.signal } : {}
285
- });
125
+ await delay(options.pollMs);
286
126
  }
287
127
  }
288
128
  export {
129
+ launchFailure,
289
130
  launchRadarWorker,
290
- observeRadarChild,
291
- readProcessGone,
292
- relaunchRefusalForChild
131
+ waitForChild
293
132
  };
@@ -443,15 +443,18 @@ function createChannelWriter(input) {
443
443
  function blank(agentId) {
444
444
  return {
445
445
  version: RADAR_PUBLICATION_VERSION,
446
- registration_id: agentId,
446
+ target_id: agentId,
447
447
  channel,
448
448
  publisher,
449
449
  sequence: 0
450
450
  };
451
451
  }
452
+ function addressed(target) {
453
+ return channel === "assignment" ? { subject_id: target } : { registration_id: target };
454
+ }
452
455
  function loadRecord() {
453
456
  const value = store.read(recordName);
454
- const agentId = textAt(value, "registration_id");
457
+ const agentId = textAt(value, "target_id");
455
458
  const storedPublisher = recordAt(value, "publisher");
456
459
  if (numberAt(value, "version") !== RADAR_PUBLICATION_VERSION || agentId === void 0 || !isUuid(agentId) || textAt(value, "channel") !== channel || textAt(storedPublisher, "source") !== publisher.source || textAt(storedPublisher, "incarnation") !== publisher.incarnation || textAt(storedPublisher, "reporting_owner") !== publisher.reporting_owner)
457
460
  return blank("");
@@ -466,7 +469,7 @@ function createChannelWriter(input) {
466
469
  const sentAt = numberAt(acceptedValue, "sent_at");
467
470
  return {
468
471
  version: RADAR_PUBLICATION_VERSION,
469
- registration_id: agentId,
472
+ target_id: agentId,
470
473
  channel,
471
474
  publisher,
472
475
  ...handle !== void 0 && generation !== void 0 ? { writer: { handle, generation } } : {},
@@ -507,15 +510,11 @@ function createChannelWriter(input) {
507
510
  if (record.writer !== void 0) return true;
508
511
  const agentId = await input.subject(recovering);
509
512
  if (agentId === void 0) return false;
510
- if (record.registration_id !== agentId) {
513
+ if (record.target_id !== agentId) {
511
514
  record = blank(agentId);
512
515
  save();
513
516
  }
514
- const answer = await client.acquire({
515
- registration_id: agentId,
516
- channel,
517
- publisher
518
- });
517
+ const answer = await client.acquire({ ...addressed(agentId), channel, publisher });
519
518
  if (answer.ok === false) {
520
519
  if (answer.code === "not_found") {
521
520
  recovering = true;
@@ -542,7 +541,7 @@ function createChannelWriter(input) {
542
541
  const writer = record.writer;
543
542
  if (writer === void 0) return;
544
543
  const answer = await client.publish({
545
- registration_id: record.registration_id,
544
+ ...addressed(record.target_id),
546
545
  channel,
547
546
  writer_handle: writer.handle,
548
547
  sequence: pending.sequence,
@@ -636,9 +635,9 @@ function createChannelWriter(input) {
636
635
  stopPump();
637
636
  const writer = record.writer;
638
637
  if (writer === void 0) return;
639
- const agentId = record.registration_id;
638
+ const agentId = record.target_id;
640
639
  try {
641
- void client.retire({ registration_id: agentId, channel, writer_handle: writer.handle }).then((answer) => {
640
+ void client.retire({ ...addressed(agentId), channel, writer_handle: writer.handle }).then((answer) => {
642
641
  if (answer.ok === true) {
643
642
  try {
644
643
  record.retired_at = new Date(scheduler.now()).toISOString();
@@ -728,6 +727,7 @@ function createPresenceReporter(input) {
728
727
  }
729
728
  claim.registration_id = answer.value.registration_id;
730
729
  claim.subject_id = answer.value.standing === "granted" ? answer.value.subject_id ?? void 0 : void 0;
730
+ if (claim.subject_id !== void 0) input.granted?.(claim.subject_id);
731
731
  standing = answer.value.standing === "granted" ? { state: "managed", subject_id: claim.subject_id } : answer.value.standing === "duplicate" ? { state: "duplicate" } : answer.value.standing === "pending" ? { state: "pending" } : { state: "unmanaged" };
732
732
  diagnostic = answer.value.standing === "duplicate" ? DUPLICATE_SESSION_DIAGNOSTIC : void 0;
733
733
  save();
@@ -909,7 +909,7 @@ function createRadarPublicationInner(options) {
909
909
  agentId = registered;
910
910
  processRecord = { ...record, registration_id: agentId };
911
911
  store.write(registrationName, processRecord);
912
- if (options.binding !== void 0) writeBinding(options.binding);
912
+ if (options.binding !== void 0) writeBinding(options.binding, grantedSubject());
913
913
  return agentId;
914
914
  })();
915
915
  try {
@@ -918,11 +918,16 @@ function createRadarPublicationInner(options) {
918
918
  subjectReady = void 0;
919
919
  }
920
920
  }
921
- function writeBinding(binding) {
921
+ function grantedSubject() {
922
+ const standing = presence.management();
923
+ return standing?.state === "managed" ? standing.subject_id : void 0;
924
+ }
925
+ function writeBinding(binding, subjectId) {
922
926
  if (agentId === void 0) return;
923
927
  store.write(`bindings/${binding.key}.json`, {
924
928
  version: RADAR_PUBLICATION_VERSION,
925
929
  registration_id: agentId,
930
+ ...subjectId !== void 0 ? { subject_id: subjectId } : {},
926
931
  incarnation,
927
932
  run: binding.run,
928
933
  owner: binding.owner,
@@ -962,6 +967,9 @@ function createRadarPublicationInner(options) {
962
967
  ...launchRegistrationId ? { registration_id: launchRegistrationId } : {}
963
968
  };
964
969
  },
970
+ granted: (subjectId) => {
971
+ if (options.binding !== void 0) writeBinding(options.binding, subjectId);
972
+ },
965
973
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
966
974
  });
967
975
  const facts = createChannelWriter({
@@ -993,8 +1001,7 @@ function createRadarPublicationInner(options) {
993
1001
  channel: "assignment",
994
1002
  publisher: publisher("herdsman-owner", reportingOwner),
995
1003
  codec: assignmentCodec,
996
- // The owner publishes about a subject it did not register: the exact
997
- // registration_id from the child's sidecar is the whole correlation.
1004
+ // Radar owns this assignment across the child's process replacements.
998
1005
  subject: async () => target,
999
1006
  leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
1000
1007
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
@@ -1015,16 +1022,18 @@ function createRadarPublicationInner(options) {
1015
1022
  return void 0;
1016
1023
  }
1017
1024
  const bound = textAt(value, "registration_id");
1025
+ const subjectId = textAt(value, "subject_id");
1018
1026
  const boundIncarnation = textAt(value, "incarnation");
1019
1027
  const run = textAt(value, "run");
1020
1028
  const owner = textAt(value, "owner");
1021
1029
  const label = textAt(value, "label");
1022
1030
  const observedAt = textAt(value, "observed_at");
1023
- if (!isUuid(bound) || !isUuid(boundIncarnation) || run === void 0 || owner === void 0 || label === void 0 || observedAt === void 0)
1031
+ if (!isUuid(bound) || subjectId !== void 0 && !isUuid(subjectId) || !isUuid(boundIncarnation) || run === void 0 || owner === void 0 || label === void 0 || observedAt === void 0)
1024
1032
  return void 0;
1025
1033
  return {
1026
1034
  version: RADAR_PUBLICATION_VERSION,
1027
1035
  registration_id: bound,
1036
+ ...subjectId !== void 0 ? { subject_id: subjectId } : {},
1028
1037
  incarnation: boundIncarnation,
1029
1038
  run,
1030
1039
  owner,
@@ -1,6 +1,6 @@
1
1
  // extension/radar-runtime.ts
2
2
  var RADAR_RUNTIME_REQUEST = "pi-radar/runtime-request";
3
- var RADAR_RUNTIME_VERSION = 3;
3
+ var RADAR_RUNTIME_VERSION = 5;
4
4
  function lazyRadarRuntime(start) {
5
5
  let pending;
6
6
  return {