pi-radar 0.10.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 +6 -10
  2. package/dist/extension/baseline.js +147 -272
  3. package/dist/extension/radar-children.js +28 -242
  4. package/dist/extension/radar-client.js +92 -13
  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 +0 -92
  8. package/dist/extension/radar-publication.js +26 -17
  9. package/dist/extension/radar-runtime.js +1 -1
  10. package/dist/index.js +150 -295
  11. package/extension/generated-contracts.ts +352 -11
  12. package/extension/radar-children.ts +43 -184
  13. package/extension/radar-client.ts +114 -17
  14. package/extension/radar-inspection.ts +2 -77
  15. package/extension/radar-launch.ts +0 -177
  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 -13
  20. package/schema/public/v1/observation-get-response.serialize.schema.json +5 -13
  21. package/schema/public/v1/observation-snapshot.deserialize.schema.json +5 -13
  22. package/schema/public/v1/observation-snapshot.serialize.schema.json +5 -13
  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
@@ -8,11 +8,10 @@ import { lstatSync } from "node:fs";
8
8
  import { createConnection } from "node:net";
9
9
  import { dirname, join } from "node:path";
10
10
  var RADAR_PROTOCOL_VERSION = 2;
11
- var RADAR_MINIMUM_REVISION = 8;
11
+ var RADAR_MINIMUM_REVISION = 9;
12
12
  var RADAR_REGISTRY_CONTRACT = "registry.v1";
13
13
  var RADAR_REGISTRY_FACTS_CONTRACT = "registry.facts.v1";
14
14
  var RADAR_SPAWN_CONTRACT = "spawn.v1";
15
- var RADAR_SPAWN_LIST_CONTRACT = "spawn.list.v1";
16
15
  var RADAR_SPAWN_CLOSE_CONTRACT = "spawn.close.v1";
17
16
  var RADAR_LIFECYCLE_RESTART_CONTRACT = "lifecycle.restart.v1";
18
17
  var RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT = "spawn.launch_context.v1";
@@ -349,6 +348,14 @@ var PROCESS_VERDICTS = [
349
348
  "mismatched",
350
349
  "unavailable"
351
350
  ];
351
+ function subjectAt(value) {
352
+ const subjectId = recordAt(value, "subject_id");
353
+ const session = recordAt(value, "session_id");
354
+ const state = recordAt(value, "state");
355
+ if (!isUuid(subjectId) || session != null && !isUuid(session) || !["starting", "live", "detached", "unknown"].includes(String(state)))
356
+ return protocol("subject read carries no valid identity or attachment state");
357
+ return { ok: true, value };
358
+ }
352
359
  function agentProcessAt(agent) {
353
360
  const process2 = recordAt(agent, "process");
354
361
  const claimed = recordAt(process2, "claimed");
@@ -683,7 +690,9 @@ function createRadarClient(options = {}) {
683
690
  return withRegistry(async () => {
684
691
  const answer = await call("agent.presence", { ...request });
685
692
  if (!answer.ok) return answer;
686
- return decodePresenceAnswer(answer.value);
693
+ const decoded = decodePresenceAnswer(answer.value);
694
+ if (decoded.ok) processRegistrationId = decoded.value.registration_id;
695
+ return decoded;
687
696
  });
688
697
  },
689
698
  async releasePresence(request) {
@@ -718,9 +727,13 @@ function createRadarClient(options = {}) {
718
727
  },
719
728
  async acquire(request) {
720
729
  return withRegistryChannel(request.channel, async () => {
721
- const answer = await call("agent.acquire", {
722
- registration_id: request.registration_id,
723
- channel: request.channel,
730
+ const assignment = request.channel === "assignment";
731
+ if (assignment && processRegistrationId === void 0)
732
+ return { ok: false, code: "refused", message: "assignment requires a granted parent" };
733
+ if (assignment && request.subject_id === void 0)
734
+ return { ok: false, code: "bad_params", message: "an assignment names a child subject" };
735
+ const answer = await call(assignment ? "subject.assignment.acquire" : "agent.acquire", {
736
+ ...assignment ? { registration_id: processRegistrationId, subject_id: request.subject_id } : { registration_id: request.registration_id, channel: request.channel },
724
737
  publisher: request.publisher,
725
738
  ...request.replace !== void 0 ? { replace: request.replace } : {}
726
739
  });
@@ -747,15 +760,19 @@ function createRadarClient(options = {}) {
747
760
  }
748
761
  }
749
762
  return withRegistryChannel(request.channel, async () => {
750
- const answer = await call("agent.publish", {
751
- registration_id: request.registration_id,
752
- channel: request.channel,
763
+ const update = {
753
764
  writer_handle: request.writer_handle,
754
765
  sequence: request.sequence,
755
766
  ...request.lease_ms !== void 0 ? { lease_ms: request.lease_ms } : {},
756
767
  ...request.observed_at !== void 0 ? { observed_at: request.observed_at } : {},
757
768
  snapshot
758
- });
769
+ };
770
+ const assignment = request.channel === "assignment";
771
+ if (assignment && processRegistrationId === void 0)
772
+ return { ok: false, code: "refused", message: "assignment requires a granted parent" };
773
+ if (assignment && request.subject_id === void 0)
774
+ return { ok: false, code: "bad_params", message: "an assignment names a child subject" };
775
+ const answer = await call(assignment ? "subject.assignment.publish" : "agent.publish", assignment ? { registration_id: processRegistrationId, subject_id: request.subject_id, update } : { registration_id: request.registration_id, channel: request.channel, ...update });
759
776
  if (answer.ok === false) return answer;
760
777
  const channel = answer.value.channel;
761
778
  const writer = channel?.writer;
@@ -796,7 +813,15 @@ function createRadarClient(options = {}) {
796
813
  },
797
814
  async retire(request) {
798
815
  return withRegistryChannel(request.channel, async () => {
799
- const answer = await call("agent.retire", request);
816
+ const assignment = request.channel === "assignment";
817
+ if (assignment && processRegistrationId === void 0)
818
+ return { ok: false, code: "refused", message: "assignment requires a granted parent" };
819
+ if (assignment && request.subject_id === void 0)
820
+ return { ok: false, code: "bad_params", message: "an assignment names a child subject" };
821
+ const answer = await call(assignment ? "subject.assignment.retire" : "agent.retire", {
822
+ ...assignment ? { registration_id: processRegistrationId, subject_id: request.subject_id } : { registration_id: request.registration_id, channel: request.channel },
823
+ writer_handle: request.writer_handle
824
+ });
800
825
  return answer.ok === true ? { ok: true, value: void 0 } : answer;
801
826
  });
802
827
  },
@@ -878,11 +903,66 @@ function createRadarClient(options = {}) {
878
903
  return { ok: true, value: { ...settled.value, launch_request_id: launch } };
879
904
  });
880
905
  },
906
+ async subjectChildren(request = {}) {
907
+ const registrationId = processRegistrationId;
908
+ if (registrationId === void 0)
909
+ return { ok: false, code: "refused", message: "this process has no Radar registration" };
910
+ return withRegistry(async () => {
911
+ const params = { ...request, registration_id: registrationId };
912
+ const answer = await call("subject.children", { ...params });
913
+ if (!answer.ok) return answer;
914
+ const subjects = recordAt(answer.value, "subjects");
915
+ const next = recordAt(answer.value, "next");
916
+ if (!Array.isArray(subjects) || next != null && !isUuid(next))
917
+ return protocol("subject.children reply carries no valid page");
918
+ for (const subject of subjects) {
919
+ const decoded = subjectAt(subject);
920
+ if (!decoded.ok) return decoded;
921
+ }
922
+ return { ok: true, value: answer.value };
923
+ });
924
+ },
925
+ async childGet(subject_id) {
926
+ const registrationId = processRegistrationId;
927
+ if (registrationId === void 0)
928
+ return { ok: false, code: "refused", message: "this process has no Radar registration" };
929
+ if (!isUuid(subject_id))
930
+ return { ok: false, code: "bad_params", message: "child requires a subject UUID" };
931
+ return withRegistry(async () => {
932
+ const params = { registration_id: registrationId, subject_id };
933
+ const answer = await call("subject.child.get", { ...params });
934
+ return answer.ok ? subjectAt(recordAt(answer.value, "subject")) : answer;
935
+ });
936
+ },
937
+ async childInspect(subject_id) {
938
+ const registration_id = processRegistrationId;
939
+ if (registration_id === void 0)
940
+ return { ok: false, code: "refused", message: "this process has no Radar registration" };
941
+ return withRegistry(async () => {
942
+ const answer = await call("subject.child.inspect", { registration_id, subject_id });
943
+ if (!answer.ok) return answer;
944
+ const subject = subjectAt(recordAt(answer.value, "subject"));
945
+ if (!subject.ok) return subject;
946
+ const output = recordAt(answer.value, "output");
947
+ const text = stringAt(output, "text");
948
+ if (text === void 0 || typeof recordAt(output, "truncated") !== "boolean")
949
+ return protocol("child inspection has no output evidence");
950
+ const foreground = recordAt(answer.value, "foreground");
951
+ const activity = subject.value.execution?.snapshot?.snapshot;
952
+ return { ok: true, value: {
953
+ capturedAt: Date.now(),
954
+ recentOutputTruncated: recordAt(output, "truncated"),
955
+ ...text ? { recentOutput: text } : {},
956
+ ...foreground ? { foreground } : {},
957
+ ...activity && "activity" in activity ? { activity: activity.activity } : {}
958
+ } };
959
+ });
960
+ },
881
961
  async spawnList(request) {
882
962
  const registrationId = processRegistrationId;
883
963
  if (registrationId === void 0)
884
964
  return { ok: false, code: "refused", message: "spawn.list requires this client's process registration" };
885
- return withContract(RADAR_SPAWN_LIST_CONTRACT, async () => {
965
+ return withRegistry(async () => {
886
966
  const params = {
887
967
  registration_id: registrationId,
888
968
  ...request.after !== void 0 ? { after: request.after } : {},
@@ -1248,125 +1328,8 @@ function boundedRadarSnapshot(snapshot) {
1248
1328
  // extension/radar-children.ts
1249
1329
  import { randomUUID as randomUUID2 } from "node:crypto";
1250
1330
 
1251
- // extension/radar-inspection.ts
1252
- var INSPECTION_LINES = 80;
1253
- var INSPECTION_OUTPUT_BYTES = 16 * 1024;
1254
- async function provedRead(client, target) {
1255
- const read = await client.agentGet(target.agentId);
1256
- 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;
1257
- }
1258
- async function inspectRadarChild(client, target) {
1259
- const before = await provedRead(client, target);
1260
- if (before === void 0)
1261
- throw new Error("Inspection target identity did not match");
1262
- const output = await client.output({
1263
- pane_id: target.paneId,
1264
- source: "recent_unwrapped",
1265
- lines: INSPECTION_LINES
1266
- });
1267
- if (output.ok === false)
1268
- throw new Error(
1269
- `Inspection output unavailable (${output.code}: ${output.message})`
1270
- );
1271
- const foreground = await client.processInfo({ pane_id: target.paneId });
1272
- const after = await provedRead(client, target);
1273
- if (after === void 0)
1274
- throw new Error("Inspection target changed during capture");
1275
- const lines = output.value.text.trim().split(/\r?\n/).slice(-INSPECTION_LINES);
1276
- const bounded2 = utf8Tail(lines.join("\n"), INSPECTION_OUTPUT_BYTES);
1277
- return Object.freeze({
1278
- capturedAt: Date.now(),
1279
- recentOutputTruncated: output.value.truncated || bounded2.truncated,
1280
- ...bounded2.text ? { recentOutput: bounded2.text } : {},
1281
- ...foreground.ok === true ? { foreground: foreground.value } : {},
1282
- ...after.activity !== void 0 ? { activity: after.activity } : {}
1283
- });
1284
- }
1285
- function utf8Tail(value, maxBytes) {
1286
- const bytes = Buffer.from(value);
1287
- if (bytes.length <= maxBytes) return { text: value, truncated: false };
1288
- let start = bytes.length - maxBytes;
1289
- while (start < bytes.length && (bytes[start] & 192) === 128) start++;
1290
- return { text: bytes.subarray(start).toString(), truncated: true };
1291
- }
1292
-
1293
1331
  // extension/radar-launch.ts
1294
1332
  import { setTimeout as delay } from "node:timers/promises";
1295
- function unknownChild(reason) {
1296
- return { kind: "unknown", diagnostic: trajectory(reason) };
1297
- }
1298
- function trajectory(reason) {
1299
- return `the daemon's current record of this launch's child is ${reason}`;
1300
- }
1301
- async function observeRadarChild(client, provenance) {
1302
- if (provenance.registration_id === void 0)
1303
- return unknownChild("not bound to a child by this launch");
1304
- if (provenance.pane_id === void 0)
1305
- return unknownChild("without a pane this launch recorded for it");
1306
- const read = await client.spawnGet(provenance.request_id);
1307
- if (read.ok === false)
1308
- return unknownChild(
1309
- `not readable (${read.code}: ${read.message})`
1310
- );
1311
- const topology = read.value;
1312
- const edge = topology.edge;
1313
- if (edge.bound === void 0 || edge.bound.registration_id === void 0)
1314
- return unknownChild("not bound to a child by the daemon");
1315
- if (edge.bound.registration_id !== provenance.registration_id)
1316
- return unknownChild(
1317
- `bound to another subject (${edge.bound.registration_id}), not to ${provenance.registration_id}`
1318
- );
1319
- if (edge.location?.pane === void 0)
1320
- return unknownChild("recorded without a pane");
1321
- if (edge.location.pane !== provenance.pane_id)
1322
- return unknownChild(
1323
- `recorded on pane ${edge.location.pane}, not on ${provenance.pane_id}`
1324
- );
1325
- if (edge.exit_observation?.state !== "unknown")
1326
- return unknownChild("recorded with an observed process exit");
1327
- if (topology.freshness !== "fresh")
1328
- return unknownChild(
1329
- "on a pane the daemon's own inventory does not currently report"
1330
- );
1331
- if (topology.state !== "bound")
1332
- return unknownChild(
1333
- `not currently placed (state ${topology.state ?? "unreported"})`
1334
- );
1335
- const reply = await client.agentGet(edge.bound.registration_id);
1336
- if (reply.ok === false)
1337
- return unknownChild(
1338
- `process verdict not readable (${reply.code}: ${reply.message})`
1339
- );
1340
- const { verification, location, contextSession } = reply.value;
1341
- if (verification === "absent" || verification === "mismatched")
1342
- return {
1343
- kind: "gone",
1344
- verification,
1345
- diagnostic: trajectory(`the registered process is ${verification}`)
1346
- };
1347
- if (verification !== "verified")
1348
- return unknownChild(`process verdict is ${verification}`);
1349
- if (location?.pane !== provenance.pane_id)
1350
- return unknownChild(
1351
- `registered on ${location?.pane ?? "no pane"}, not on ${provenance.pane_id}`
1352
- );
1353
- if (provenance.session_id === void 0)
1354
- return unknownChild("without a session this launch recorded for it");
1355
- if (contextSession !== provenance.session_id)
1356
- return unknownChild(
1357
- `publishing session ${contextSession ?? "none"}, not ${provenance.session_id}`
1358
- );
1359
- return {
1360
- kind: "live",
1361
- evidence: {
1362
- request_id: provenance.request_id,
1363
- registration_id: edge.bound.registration_id,
1364
- source: edge.bound.source,
1365
- incarnation: edge.bound.incarnation,
1366
- pane_id: edge.location.pane
1367
- }
1368
- };
1369
- }
1370
1333
  function refusedLaunch(message) {
1371
1334
  return { kind: "failed", message };
1372
1335
  }
@@ -1435,145 +1398,48 @@ async function waitForChild(client, requestId, options) {
1435
1398
  }
1436
1399
 
1437
1400
  // extension/radar-children.ts
1438
- var PAGE = 100;
1439
- var MAX_PAGES = 20;
1440
1401
  function createRadarChildren(client, publication) {
1441
- const held = /* @__PURE__ */ new Map();
1442
- async function resolveAll() {
1443
- const newest = /* @__PURE__ */ new Map();
1444
- let after;
1445
- for (let page = 0; page < MAX_PAGES; page++) {
1446
- const read = await client.spawnList({ ...after !== void 0 ? { after } : {}, limit: PAGE });
1447
- if (read.ok === false) return read;
1448
- for (const edge of read.value.spawns) {
1449
- const bound = edge.bound;
1450
- if (!bound?.subject_id || !bound.registration_id || edge.granted_at == null || edge.withdrawn != null) continue;
1451
- const at = Date.parse(bound.bound_at);
1452
- if (Number.isNaN(at)) continue;
1453
- const current = newest.get(bound.subject_id);
1454
- if (current !== void 0 && current.at >= at) continue;
1455
- newest.set(bound.subject_id, {
1456
- at,
1457
- registration_id: bound.registration_id,
1458
- child: {
1459
- subject_id: bound.subject_id,
1460
- request_id: edge.request_id,
1461
- ...edge.location?.pane ? { pane_id: edge.location.pane } : {},
1462
- state: edge.state,
1463
- exited: edge.exit_observation != null && edge.exit_observation.state !== "unknown"
1464
- }
1465
- });
1466
- }
1467
- if (!read.value.next) return { ok: true, value: newest };
1468
- after = read.value.next;
1469
- }
1470
- return { ok: false, code: "protocol", message: "the caller's launches exceed the readable pages" };
1471
- }
1472
- async function resolve2(subject) {
1473
- const all = await resolveAll();
1474
- return all.ok ? all.value.get(subject) : void 0;
1475
- }
1476
- async function resolveAndFollow(subject) {
1477
- const all = await resolveAll();
1478
- if (!all.ok) return void 0;
1479
- follow(all.value);
1480
- return all.value.get(subject);
1481
- }
1482
- function follow(all) {
1483
- for (const [subject, entry] of held) {
1484
- const target = all.get(subject);
1485
- if (target !== void 0 && entry.published !== target.registration_id)
1486
- entry.publish?.(target);
1487
- }
1488
- }
1489
1402
  return {
1490
- async list() {
1491
- const all = await resolveAll();
1492
- if (!all.ok) return all;
1493
- follow(all.value);
1494
- return { ok: true, value: [...all.value.values()].map((entry) => entry.child) };
1403
+ async list(selection) {
1404
+ const children = [];
1405
+ let after;
1406
+ do {
1407
+ const page = await client.subjectChildren({ selection, after, limit: 100 });
1408
+ if (!page.ok) return page;
1409
+ children.push(...page.value.subjects);
1410
+ if (page.value.next === after)
1411
+ return { ok: false, code: "protocol", message: "child page did not advance" };
1412
+ after = page.value.next ?? void 0;
1413
+ } while (after !== void 0);
1414
+ return { ok: true, value: children };
1495
1415
  },
1496
1416
  async restart(subject, program, options) {
1497
1417
  const parent = await publication.subject();
1498
1418
  if (parent?.subject_id === void 0)
1499
- return { kind: "failed", message: "this process has no Radar registration to restart a child under" };
1419
+ return { kind: "failed", message: "this process has no granted subject" };
1500
1420
  const request_id = randomUUID2();
1501
- const requested = await client.restart({
1502
- request_id,
1503
- parent: parent.registration_id,
1504
- subject_id: subject,
1505
- ...program
1506
- });
1507
- if (requested.ok === false)
1508
- return launchFailure(requested, request_id);
1421
+ const requested = await client.restart({ request_id, parent: parent.registration_id, subject_id: subject, ...program });
1422
+ if (!requested.ok) return launchFailure(requested, request_id);
1509
1423
  const bound = await waitForChild(client, requested.value.launch_request_id, {
1510
1424
  pollMs: options?.pollMs ?? 250,
1511
1425
  ...options?.signal !== void 0 ? { signal: options.signal } : {}
1512
1426
  });
1513
1427
  if (bound.kind !== "bound") return bound;
1514
- await resolveAndFollow(subject);
1515
- return {
1516
- kind: "ready",
1517
- value: {
1518
- request_id: requested.value.launch_request_id,
1519
- subject_id: bound.subject_id,
1520
- pane_id: bound.pane_id
1521
- }
1522
- };
1523
- },
1524
- assignment(subject, owner) {
1525
- const existing = held.get(subject);
1526
- if (existing !== void 0) return existing.writer;
1527
- let latest;
1528
- const entry = { writer: void 0 };
1529
- const send = (target) => {
1530
- if (latest === void 0) return;
1531
- if (entry.published !== void 0 && entry.published !== target.registration_id)
1532
- publication.retireAssignment(entry.published);
1533
- entry.published = target.registration_id;
1534
- publication.assignment(target.registration_id, owner).update(latest);
1535
- };
1536
- entry.publish = send;
1537
- entry.writer = {
1538
- update(snapshot) {
1539
- latest = snapshot;
1540
- void resolve2(subject).then((target) => {
1541
- if (target !== void 0) send(target);
1542
- });
1543
- },
1544
- retire() {
1545
- latest = void 0;
1546
- if (entry.published !== void 0) publication.retireAssignment(entry.published);
1547
- entry.published = void 0;
1548
- }
1549
- };
1550
- held.set(subject, entry);
1551
- return entry.writer;
1428
+ return { kind: "ready", value: { request_id: requested.value.launch_request_id, subject_id: bound.subject_id, pane_id: bound.pane_id } };
1552
1429
  },
1553
- retire(subject) {
1554
- held.get(subject)?.writer.retire();
1555
- held.delete(subject);
1430
+ assignment: (subject, owner) => publication.assignment(subject, owner),
1431
+ retire: (subject) => publication.retireAssignment(subject),
1432
+ async observe(subject) {
1433
+ const read = await client.childGet(subject);
1434
+ if (!read.ok) return { kind: "unknown", diagnostic: `${read.code}: ${read.message}` };
1435
+ if (read.value.state === "live") return { kind: "live", subject: read.value };
1436
+ if (read.value.state === "detached") return { kind: "gone", subject: read.value };
1437
+ return { kind: "unknown", diagnostic: `child attachment is ${read.value.state}` };
1556
1438
  },
1557
- async observe(subject, session_id) {
1558
- const target = await resolveAndFollow(subject);
1559
- if (target === void 0)
1560
- return { kind: "unknown", diagnostic: "the daemon records no bound launch for this child" };
1561
- return observeRadarChild(client, {
1562
- request_id: target.child.request_id,
1563
- pane_id: target.child.pane_id,
1564
- registration_id: target.registration_id,
1565
- session_id
1566
- });
1567
- },
1568
- async inspect(subject, session_id) {
1569
- const target = await resolveAndFollow(subject);
1570
- if (target?.child.pane_id === void 0)
1571
- throw new Error("Inspection target is not a bound child with a pane");
1572
- return inspectRadarChild(client, {
1573
- agentId: target.registration_id,
1574
- paneId: target.child.pane_id,
1575
- ...session_id !== void 0 ? { session: session_id } : {}
1576
- });
1439
+ async inspect(subject) {
1440
+ const read = await client.childInspect(subject);
1441
+ if (!read.ok) throw new Error(`Child inspection unavailable (${read.code}: ${read.message})`);
1442
+ return read.value;
1577
1443
  }
1578
1444
  };
1579
1445
  }
@@ -1959,15 +1825,18 @@ function createChannelWriter(input) {
1959
1825
  function blank(agentId) {
1960
1826
  return {
1961
1827
  version: RADAR_PUBLICATION_VERSION,
1962
- registration_id: agentId,
1828
+ target_id: agentId,
1963
1829
  channel,
1964
1830
  publisher,
1965
1831
  sequence: 0
1966
1832
  };
1967
1833
  }
1834
+ function addressed(target) {
1835
+ return channel === "assignment" ? { subject_id: target } : { registration_id: target };
1836
+ }
1968
1837
  function loadRecord() {
1969
1838
  const value = store.read(recordName);
1970
- const agentId = textAt(value, "registration_id");
1839
+ const agentId = textAt(value, "target_id");
1971
1840
  const storedPublisher = recordAt2(value, "publisher");
1972
1841
  if (numberAt2(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)
1973
1842
  return blank("");
@@ -1982,7 +1851,7 @@ function createChannelWriter(input) {
1982
1851
  const sentAt = numberAt2(acceptedValue, "sent_at");
1983
1852
  return {
1984
1853
  version: RADAR_PUBLICATION_VERSION,
1985
- registration_id: agentId,
1854
+ target_id: agentId,
1986
1855
  channel,
1987
1856
  publisher,
1988
1857
  ...handle !== void 0 && generation !== void 0 ? { writer: { handle, generation } } : {},
@@ -2023,15 +1892,11 @@ function createChannelWriter(input) {
2023
1892
  if (record.writer !== void 0) return true;
2024
1893
  const agentId = await input.subject(recovering);
2025
1894
  if (agentId === void 0) return false;
2026
- if (record.registration_id !== agentId) {
1895
+ if (record.target_id !== agentId) {
2027
1896
  record = blank(agentId);
2028
1897
  save();
2029
1898
  }
2030
- const answer = await client.acquire({
2031
- registration_id: agentId,
2032
- channel,
2033
- publisher
2034
- });
1899
+ const answer = await client.acquire({ ...addressed(agentId), channel, publisher });
2035
1900
  if (answer.ok === false) {
2036
1901
  if (answer.code === "not_found") {
2037
1902
  recovering = true;
@@ -2058,7 +1923,7 @@ function createChannelWriter(input) {
2058
1923
  const writer = record.writer;
2059
1924
  if (writer === void 0) return;
2060
1925
  const answer = await client.publish({
2061
- registration_id: record.registration_id,
1926
+ ...addressed(record.target_id),
2062
1927
  channel,
2063
1928
  writer_handle: writer.handle,
2064
1929
  sequence: pending.sequence,
@@ -2152,9 +2017,9 @@ function createChannelWriter(input) {
2152
2017
  stopPump();
2153
2018
  const writer = record.writer;
2154
2019
  if (writer === void 0) return;
2155
- const agentId = record.registration_id;
2020
+ const agentId = record.target_id;
2156
2021
  try {
2157
- void client.retire({ registration_id: agentId, channel, writer_handle: writer.handle }).then((answer) => {
2022
+ void client.retire({ ...addressed(agentId), channel, writer_handle: writer.handle }).then((answer) => {
2158
2023
  if (answer.ok === true) {
2159
2024
  try {
2160
2025
  record.retired_at = new Date(scheduler.now()).toISOString();
@@ -2244,6 +2109,7 @@ function createPresenceReporter(input) {
2244
2109
  }
2245
2110
  claim.registration_id = answer.value.registration_id;
2246
2111
  claim.subject_id = answer.value.standing === "granted" ? answer.value.subject_id ?? void 0 : void 0;
2112
+ if (claim.subject_id !== void 0) input.granted?.(claim.subject_id);
2247
2113
  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" };
2248
2114
  diagnostic = answer.value.standing === "duplicate" ? DUPLICATE_SESSION_DIAGNOSTIC : void 0;
2249
2115
  save();
@@ -2425,7 +2291,7 @@ function createRadarPublicationInner(options) {
2425
2291
  agentId = registered;
2426
2292
  processRecord = { ...record, registration_id: agentId };
2427
2293
  store.write(registrationName, processRecord);
2428
- if (options.binding !== void 0) writeBinding(options.binding);
2294
+ if (options.binding !== void 0) writeBinding(options.binding, grantedSubject());
2429
2295
  return agentId;
2430
2296
  })();
2431
2297
  try {
@@ -2434,11 +2300,16 @@ function createRadarPublicationInner(options) {
2434
2300
  subjectReady = void 0;
2435
2301
  }
2436
2302
  }
2437
- function writeBinding(binding) {
2303
+ function grantedSubject() {
2304
+ const standing = presence.management();
2305
+ return standing?.state === "managed" ? standing.subject_id : void 0;
2306
+ }
2307
+ function writeBinding(binding, subjectId) {
2438
2308
  if (agentId === void 0) return;
2439
2309
  store.write(`bindings/${binding.key}.json`, {
2440
2310
  version: RADAR_PUBLICATION_VERSION,
2441
2311
  registration_id: agentId,
2312
+ ...subjectId !== void 0 ? { subject_id: subjectId } : {},
2442
2313
  incarnation,
2443
2314
  run: binding.run,
2444
2315
  owner: binding.owner,
@@ -2478,6 +2349,9 @@ function createRadarPublicationInner(options) {
2478
2349
  ...launchRegistrationId ? { registration_id: launchRegistrationId } : {}
2479
2350
  };
2480
2351
  },
2352
+ granted: (subjectId) => {
2353
+ if (options.binding !== void 0) writeBinding(options.binding, subjectId);
2354
+ },
2481
2355
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
2482
2356
  });
2483
2357
  const facts = createChannelWriter({
@@ -2509,8 +2383,7 @@ function createRadarPublicationInner(options) {
2509
2383
  channel: "assignment",
2510
2384
  publisher: publisher("herdsman-owner", reportingOwner),
2511
2385
  codec: assignmentCodec,
2512
- // The owner publishes about a subject it did not register: the exact
2513
- // registration_id from the child's sidecar is the whole correlation.
2386
+ // Radar owns this assignment across the child's process replacements.
2514
2387
  subject: async () => target,
2515
2388
  leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
2516
2389
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
@@ -2531,16 +2404,18 @@ function createRadarPublicationInner(options) {
2531
2404
  return void 0;
2532
2405
  }
2533
2406
  const bound = textAt(value, "registration_id");
2407
+ const subjectId = textAt(value, "subject_id");
2534
2408
  const boundIncarnation = textAt(value, "incarnation");
2535
2409
  const run = textAt(value, "run");
2536
2410
  const owner = textAt(value, "owner");
2537
2411
  const label = textAt(value, "label");
2538
2412
  const observedAt = textAt(value, "observed_at");
2539
- if (!isUuid(bound) || !isUuid(boundIncarnation) || run === void 0 || owner === void 0 || label === void 0 || observedAt === void 0)
2413
+ if (!isUuid(bound) || subjectId !== void 0 && !isUuid(subjectId) || !isUuid(boundIncarnation) || run === void 0 || owner === void 0 || label === void 0 || observedAt === void 0)
2540
2414
  return void 0;
2541
2415
  return {
2542
2416
  version: RADAR_PUBLICATION_VERSION,
2543
2417
  registration_id: bound,
2418
+ ...subjectId !== void 0 ? { subject_id: subjectId } : {},
2544
2419
  incarnation: boundIncarnation,
2545
2420
  run,
2546
2421
  owner,
@@ -2934,7 +2809,7 @@ async function resolveRadarProcessLocation(client, env = process.env) {
2934
2809
 
2935
2810
  // extension/radar-runtime.ts
2936
2811
  var RADAR_RUNTIME_REQUEST = "pi-radar/runtime-request";
2937
- var RADAR_RUNTIME_VERSION = 4;
2812
+ var RADAR_RUNTIME_VERSION = 5;
2938
2813
  function lazyRadarRuntime(start) {
2939
2814
  let pending;
2940
2815
  return {