pi-radar 0.1.0 → 0.2.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.
@@ -4,7 +4,7 @@ import { lstatSync } from "node:fs";
4
4
  import { createConnection } from "node:net";
5
5
  import { dirname, join } from "node:path";
6
6
  var RADAR_PROTOCOL_VERSION = 2;
7
- var RADAR_MINIMUM_REVISION = 1;
7
+ var RADAR_MINIMUM_REVISION = 3;
8
8
  var RADAR_REGISTRY_CONTRACT = "registry.v1";
9
9
  var RADAR_REGISTRY_FACTS_CONTRACT = "registry.facts.v1";
10
10
  var RADAR_SPAWN_CONTRACT = "spawn.v1";
@@ -224,6 +224,11 @@ function unresolvedOperation(requestId, detail) {
224
224
  };
225
225
  }
226
226
  var RADAR_SPAWN_TOKEN_ENV = "PI_RADAR_SPAWN_TOKEN";
227
+ var RADAR_MANAGED_IDENTITY_ENV = /* @__PURE__ */ new Set([
228
+ "PI_RADAR_MANAGED_SUBJECT_ID",
229
+ "PI_RADAR_MANAGED_LAUNCH_ID",
230
+ "PI_RADAR_MANAGED_EXECUTION_GENERATION"
231
+ ]);
227
232
  function unresolvedSpawn(requestId, detail) {
228
233
  return {
229
234
  ok: false,
@@ -368,9 +373,23 @@ function spawnRequestShape(request) {
368
373
  for (const [name, value] of env) {
369
374
  if (name === RADAR_SPAWN_TOKEN_ENV)
370
375
  return `spawn env cannot set the reserved ${RADAR_SPAWN_TOKEN_ENV}`;
376
+ if (RADAR_MANAGED_IDENTITY_ENV.has(name))
377
+ return `spawn env cannot set Radar-issued identity variable ${name}`;
371
378
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || new RegExp("\\p{Cc}", "u").test(value))
372
379
  return `spawn env contains an invalid name or value: ${name}`;
373
380
  }
381
+ if (request.managed !== void 0) {
382
+ if (typeof request.managed !== "object" || request.managed === null)
383
+ return "managed launch metadata must be an object";
384
+ for (const [name, value] of Object.entries(request.managed)) {
385
+ if (!["owner", "run", "label", "role", "parent_subject_id"].includes(name))
386
+ return `unknown managed launch metadata field: ${name}`;
387
+ if (value != null && (typeof value !== "string" || value.trim() === "" || new TextEncoder().encode(value).length > 256 || new RegExp("\\p{Cc}", "u").test(value)))
388
+ return `managed launch ${name} must be non-empty, control-free, and at most 256 UTF-8 bytes`;
389
+ }
390
+ if (request.managed.parent_subject_id != null && !isUuid(request.managed.parent_subject_id))
391
+ return "managed parent_subject_id must be a canonical UUID";
392
+ }
374
393
  return void 0;
375
394
  }
376
395
  function focusOutcome(record, requestId) {
@@ -781,7 +800,8 @@ function createRadarClient(options = {}) {
781
800
  executable: request.executable,
782
801
  argv: request.argv,
783
802
  ...request.cwd !== void 0 ? { cwd: request.cwd } : {},
784
- ...carriesContext && request.env !== void 0 ? { env: request.env } : {}
803
+ ...carriesContext && request.env !== void 0 ? { env: request.env } : {},
804
+ ...request.managed !== void 0 ? { managed: request.managed } : {}
785
805
  },
786
806
  requester
787
807
  },
@@ -110,6 +110,77 @@ import {
110
110
  writeSync
111
111
  } from "node:fs";
112
112
  import { dirname, join, parse, resolve, sep } from "node:path";
113
+
114
+ // extension/radar-session-marker.ts
115
+ var RADAR_LAUNCH_MARKER_TYPE = "pi-radar.launch-identity";
116
+ var RADAR_LAUNCH_MARKER_VERSION = 1;
117
+ var RADAR_MAX_EXECUTION_GENERATION = 4294967295;
118
+ var RADAR_MANAGED_SUBJECT_ENV = "PI_RADAR_MANAGED_SUBJECT_ID";
119
+ var RADAR_MANAGED_LAUNCH_ENV = "PI_RADAR_MANAGED_LAUNCH_ID";
120
+ var RADAR_MANAGED_GENERATION_ENV = "PI_RADAR_MANAGED_EXECUTION_GENERATION";
121
+ var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
122
+ function decodeRadarLaunchMarker(value) {
123
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
124
+ const record = value;
125
+ if (Object.keys(record).sort().join(",") !== "execution_generation,launch_id,subject_id,version") {
126
+ return void 0;
127
+ }
128
+ if (record.version !== RADAR_LAUNCH_MARKER_VERSION || typeof record.subject_id !== "string" || !UUID2.test(record.subject_id) || typeof record.launch_id !== "string" || !UUID2.test(record.launch_id) || !Number.isSafeInteger(record.execution_generation) || record.execution_generation < 1 || record.execution_generation > RADAR_MAX_EXECUTION_GENERATION) {
129
+ return void 0;
130
+ }
131
+ return {
132
+ version: RADAR_LAUNCH_MARKER_VERSION,
133
+ subject_id: record.subject_id,
134
+ launch_id: record.launch_id,
135
+ execution_generation: record.execution_generation
136
+ };
137
+ }
138
+ function appendRadarLaunchMarker(appendEntry, marker) {
139
+ const validated = decodeRadarLaunchMarker(marker);
140
+ if (!validated) throw new Error("invalid Radar launch identity marker");
141
+ appendEntry(RADAR_LAUNCH_MARKER_TYPE, validated);
142
+ }
143
+ function latestRadarLaunchMarker(branch) {
144
+ let latest;
145
+ for (const entry of branch) {
146
+ if (entry.type === "custom" && entry.customType === RADAR_LAUNCH_MARKER_TYPE) {
147
+ latest = entry;
148
+ }
149
+ }
150
+ if (!latest) return { status: "absent" };
151
+ const marker = decodeRadarLaunchMarker(latest.data);
152
+ return marker ? { status: "valid", marker } : { status: "invalid" };
153
+ }
154
+ function radarLaunchMarkerFromEnvironment(env = process.env) {
155
+ const rawGeneration = env[RADAR_MANAGED_GENERATION_ENV];
156
+ if (env[RADAR_MANAGED_SUBJECT_ENV] === void 0 || env[RADAR_MANAGED_LAUNCH_ENV] === void 0 || rawGeneration === void 0 || !/^[1-9][0-9]*$/.test(rawGeneration)) {
157
+ return void 0;
158
+ }
159
+ return decodeRadarLaunchMarker({
160
+ version: RADAR_LAUNCH_MARKER_VERSION,
161
+ subject_id: env[RADAR_MANAGED_SUBJECT_ENV],
162
+ launch_id: env[RADAR_MANAGED_LAUNCH_ENV],
163
+ execution_generation: Number(rawGeneration)
164
+ });
165
+ }
166
+ function activeRadarLaunchMarker(host) {
167
+ if (!host.sessionManager) return { status: "absent" };
168
+ return latestRadarLaunchMarker(host.sessionManager.getBranch());
169
+ }
170
+ function persistRadarLaunchMarker(host, env = process.env) {
171
+ const marker = radarLaunchMarkerFromEnvironment(env);
172
+ if (!marker) return "absent";
173
+ if (!host.appendEntry || !host.sessionManager) return "unsupported";
174
+ const current = latestRadarLaunchMarker(host.sessionManager.getBranch());
175
+ if (current.status === "valid" && sameMarker(current.marker, marker)) return "unchanged";
176
+ appendRadarLaunchMarker(host.appendEntry, marker);
177
+ return "written";
178
+ }
179
+ function sameMarker(a, b) {
180
+ return a.version === b.version && a.subject_id === b.subject_id && a.launch_id === b.launch_id && a.execution_generation === b.execution_generation;
181
+ }
182
+
183
+ // extension/radar-publication.ts
113
184
  var RADAR_PUBLICATION_VERSION = 1;
114
185
  var RADAR_LEASE_MS = 3e4;
115
186
  var RADAR_HEARTBEAT_MS = 15e3;
@@ -417,15 +488,21 @@ function pendingAt(value, keys = PENDING_KEYS, codec = executionCodec) {
417
488
  return { sequence, snapshot, lease_ms: lease, observed_at: observed };
418
489
  }
419
490
  var CONTEXT_PENDING_KEYS = "lease_ms,observed_at,sequence,session";
491
+ var CONTEXT_PENDING_MARKED_KEYS = "launch_identity,lease_ms,observed_at,sequence,session";
420
492
  var CONTEXT_ACCEPTED_KEYS = "lease_ms,observed_at,sent_at,sequence,session";
493
+ var CONTEXT_ACCEPTED_MARKED_KEYS = "launch_identity,lease_ms,observed_at,sent_at,sequence,session";
421
494
  function sessionAt(value) {
422
495
  if (typeof value !== "object" || value === null) return void 0;
423
496
  const found = value.session;
424
497
  if (found === null) return null;
425
498
  return typeof found === "string" ? found : void 0;
426
499
  }
427
- function contextReportAt(value, keys) {
428
- if (!exactKeys(value, keys)) return void 0;
500
+ function sameLaunchIdentity(a, b) {
501
+ return a === b || a !== void 0 && b !== void 0 && a.version === b.version && a.subject_id === b.subject_id && a.launch_id === b.launch_id && a.execution_generation === b.execution_generation;
502
+ }
503
+ function contextReportAt(value, keys, markedKeys) {
504
+ const marked = exactKeys(value, markedKeys);
505
+ if (!exactKeys(value, keys) && !marked) return void 0;
429
506
  const sequence = numberAt(value, "sequence");
430
507
  const lease = numberAt(value, "lease_ms");
431
508
  const observed = textAt(value, "observed_at");
@@ -433,7 +510,15 @@ function contextReportAt(value, keys) {
433
510
  if (sequence === void 0 || sequence < 1) return void 0;
434
511
  if (lease === void 0 || observed === void 0 || session === void 0)
435
512
  return void 0;
436
- return { sequence, session, lease_ms: lease, observed_at: observed };
513
+ const marker = marked ? decodeRadarLaunchMarker(value.launch_identity) : void 0;
514
+ if (marked && marker === void 0) return void 0;
515
+ return {
516
+ sequence,
517
+ session,
518
+ lease_ms: lease,
519
+ observed_at: observed,
520
+ ...marker !== void 0 ? { launch_identity: marker } : {}
521
+ };
437
522
  }
438
523
  function createChannelWriter(input) {
439
524
  const { store, client, scheduler, recordName, channel, publisher } = input;
@@ -672,6 +757,7 @@ function createContextWriter(input) {
672
757
  const { store, client, scheduler, recordName, publisher } = input;
673
758
  let record = loadRecord();
674
759
  let desired;
760
+ let desiredMarker;
675
761
  let running = false;
676
762
  let again = false;
677
763
  let recovering = false;
@@ -697,9 +783,17 @@ function createContextWriter(input) {
697
783
  const generation = numberAt(writer, "generation");
698
784
  if (writer !== void 0 && (!exactKeys(writer, WRITER_KEYS) || !isUuid(handle) || generation === void 0))
699
785
  return blank(agentId);
700
- const pending = contextReportAt(recordAt(value, "pending"), CONTEXT_PENDING_KEYS);
786
+ const pending = contextReportAt(
787
+ recordAt(value, "pending"),
788
+ CONTEXT_PENDING_KEYS,
789
+ CONTEXT_PENDING_MARKED_KEYS
790
+ );
701
791
  const acceptedValue = recordAt(value, "accepted");
702
- const accepted = contextReportAt(acceptedValue, CONTEXT_ACCEPTED_KEYS);
792
+ const accepted = contextReportAt(
793
+ acceptedValue,
794
+ CONTEXT_ACCEPTED_KEYS,
795
+ CONTEXT_ACCEPTED_MARKED_KEYS
796
+ );
703
797
  const sentAt = numberAt(acceptedValue, "sent_at");
704
798
  return {
705
799
  version: RADAR_PUBLICATION_VERSION,
@@ -759,7 +853,10 @@ function createContextWriter(input) {
759
853
  sequence: pending.sequence,
760
854
  lease_ms: pending.lease_ms,
761
855
  observed_at: pending.observed_at,
762
- context: { session: pending.session }
856
+ context: {
857
+ session: pending.session,
858
+ ...pending.launch_identity !== void 0 ? { launch_identity: pending.launch_identity } : {}
859
+ }
763
860
  });
764
861
  if (answer.ok === true) {
765
862
  recovering = false;
@@ -790,12 +887,13 @@ function createContextWriter(input) {
790
887
  return;
791
888
  }
792
889
  }
793
- async function stage(session) {
890
+ async function stage(session, launchIdentity) {
794
891
  const pending = {
795
892
  sequence: record.sequence + 1,
796
893
  session,
797
894
  lease_ms: input.leaseMs,
798
- observed_at: new Date(scheduler.now()).toISOString()
895
+ observed_at: new Date(scheduler.now()).toISOString(),
896
+ ...launchIdentity !== void 0 ? { launch_identity: launchIdentity } : {}
799
897
  };
800
898
  record.pending = pending;
801
899
  save();
@@ -810,11 +908,11 @@ function createContextWriter(input) {
810
908
  if (desired === void 0) return;
811
909
  if (!await ensureSubject()) return;
812
910
  if (fenced) return;
813
- if (record.writer === void 0 || record.accepted === void 0 || record.accepted.session !== desired) {
814
- await stage(desired);
911
+ if (record.writer === void 0 || record.accepted === void 0 || record.accepted.session !== desired || !sameLaunchIdentity(record.accepted.launch_identity, desiredMarker)) {
912
+ await stage(desired, desiredMarker);
815
913
  return;
816
914
  }
817
- if (heartbeatDue()) await stage(record.accepted.session);
915
+ if (heartbeatDue()) await stage(record.accepted.session, desiredMarker);
818
916
  }
819
917
  async function drain() {
820
918
  if (running || stopped || fenced) return;
@@ -846,9 +944,11 @@ function createContextWriter(input) {
846
944
  void drain().catch(() => void 0);
847
945
  }
848
946
  return {
849
- update(session) {
850
- if (fenced || session === desired) return;
947
+ update(session, launchIdentity) {
948
+ if (fenced || session === desired && sameLaunchIdentity(desiredMarker, launchIdentity))
949
+ return;
851
950
  desired = session;
951
+ desiredMarker = launchIdentity;
852
952
  if (stopped) return;
853
953
  kick();
854
954
  },
@@ -1260,11 +1360,11 @@ function createRadarExecutionAdapter(pi, options) {
1260
1360
  ...lastOutcome !== void 0 ? { last_outcome: lastOutcome } : {}
1261
1361
  };
1262
1362
  }
1263
- function reportSession(context) {
1363
+ function reportSession(context, launchIdentity) {
1264
1364
  const session = context?.sessionManager?.getSessionId?.();
1265
1365
  if (!isUuid(session)) return;
1266
1366
  try {
1267
- publication.context.update(session);
1367
+ publication.context.update(session, launchIdentity);
1268
1368
  } catch {
1269
1369
  }
1270
1370
  }
@@ -1328,8 +1428,38 @@ function createRadarExecutionAdapter(pi, options) {
1328
1428
  })
1329
1429
  );
1330
1430
  on("session_start", (_event, context) => {
1431
+ const sessionHost = {
1432
+ appendEntry: pi.appendEntry ? (customType, data) => pi.appendEntry?.(customType, data) : void 0,
1433
+ sessionManager: context?.sessionManager
1434
+ };
1435
+ let launchIdentity;
1436
+ try {
1437
+ const result = persistRadarLaunchMarker(
1438
+ sessionHost,
1439
+ options.markerEnvironment
1440
+ );
1441
+ if (result === "written" || result === "unchanged") {
1442
+ const expected = radarLaunchMarkerFromEnvironment(options.markerEnvironment);
1443
+ const activeMarker = activeRadarLaunchMarker(sessionHost);
1444
+ if (expected && activeMarker.status === "valid" && JSON.stringify(activeMarker.marker) === JSON.stringify(expected)) {
1445
+ launchIdentity = activeMarker.marker;
1446
+ } else {
1447
+ options.diagnostic?.("Radar launch marker was not visible on the active Pi session branch");
1448
+ }
1449
+ } else if (result === "unsupported") {
1450
+ options.diagnostic?.("Pi host does not support Radar launch identity session entries");
1451
+ } else if (options.markerEnvironment && [
1452
+ "PI_RADAR_MANAGED_SUBJECT_ID",
1453
+ "PI_RADAR_MANAGED_LAUNCH_ID",
1454
+ "PI_RADAR_MANAGED_EXECUTION_GENERATION"
1455
+ ].some((key) => options.markerEnvironment?.[key] !== void 0)) {
1456
+ options.diagnostic?.("incomplete or invalid Radar launch identity environment");
1457
+ }
1458
+ } catch (error) {
1459
+ options.diagnostic?.(`could not persist Radar launch marker: ${String(error)}`);
1460
+ }
1331
1461
  rootSession = true;
1332
- reportSession(context);
1462
+ reportSession(context, launchIdentity);
1333
1463
  publishFacts(context);
1334
1464
  publication.reopen();
1335
1465
  active = context?.isIdle?.() === false;
@@ -63,7 +63,8 @@ async function resolveRadarProcessLocation(client, env = process.env) {
63
63
  const backend = ready.value.backend;
64
64
  if (backend === void 0 || backend === "none")
65
65
  return { state: "absent", reason: "the daemon serves no physical backend" };
66
- const own = qualifiedOwnPane(env, backend, ready.value.instance);
66
+ const instance = ready.value.instance ?? void 0;
67
+ const own = qualifiedOwnPane(env, backend, instance);
67
68
  if ("absent" in own) return { state: "absent", reason: own.absent };
68
69
  if ("pending" in own) return { state: "pending", reason: own.pending };
69
70
  const paneId = own.pane;
@@ -95,7 +96,6 @@ async function resolveRadarProcessLocation(client, env = process.env) {
95
96
  if ("ambiguous" in containment)
96
97
  return { state: "pending", reason: containment.ambiguous };
97
98
  const row = containment.pane;
98
- const instance = ready.value.instance;
99
99
  return {
100
100
  state: "resolved",
101
101
  location: {
@@ -15,6 +15,27 @@ import {
15
15
  } from "node:fs";
16
16
  import { dirname, join, parse, resolve, sep } from "node:path";
17
17
 
18
+ // extension/radar-session-marker.ts
19
+ var RADAR_LAUNCH_MARKER_VERSION = 1;
20
+ var RADAR_MAX_EXECUTION_GENERATION = 4294967295;
21
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
22
+ function decodeRadarLaunchMarker(value) {
23
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
24
+ const record = value;
25
+ if (Object.keys(record).sort().join(",") !== "execution_generation,launch_id,subject_id,version") {
26
+ return void 0;
27
+ }
28
+ if (record.version !== RADAR_LAUNCH_MARKER_VERSION || typeof record.subject_id !== "string" || !UUID.test(record.subject_id) || typeof record.launch_id !== "string" || !UUID.test(record.launch_id) || !Number.isSafeInteger(record.execution_generation) || record.execution_generation < 1 || record.execution_generation > RADAR_MAX_EXECUTION_GENERATION) {
29
+ return void 0;
30
+ }
31
+ return {
32
+ version: RADAR_LAUNCH_MARKER_VERSION,
33
+ subject_id: record.subject_id,
34
+ launch_id: record.launch_id,
35
+ execution_generation: record.execution_generation
36
+ };
37
+ }
38
+
18
39
  // extension/radar-client.ts
19
40
  var RADAR_MAX_LINE_BYTES = 1024 * 1024;
20
41
  var wallClock = { setTimeout, clearTimeout, setInterval, clearInterval };
@@ -32,9 +53,9 @@ function bounded(text, bytes) {
32
53
  end -= 1;
33
54
  return text.slice(0, end);
34
55
  }
35
- var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
56
+ var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
36
57
  function isUuid(value) {
37
- return typeof value === "string" && UUID.test(value);
58
+ return typeof value === "string" && UUID2.test(value);
38
59
  }
39
60
  var RADAR_MAX_BACKGROUND_TASKS = 64;
40
61
  var RADAR_MAX_AWAITED = 64;
@@ -422,15 +443,21 @@ function pendingAt(value, keys = PENDING_KEYS, codec = executionCodec) {
422
443
  return { sequence, snapshot, lease_ms: lease, observed_at: observed };
423
444
  }
424
445
  var CONTEXT_PENDING_KEYS = "lease_ms,observed_at,sequence,session";
446
+ var CONTEXT_PENDING_MARKED_KEYS = "launch_identity,lease_ms,observed_at,sequence,session";
425
447
  var CONTEXT_ACCEPTED_KEYS = "lease_ms,observed_at,sent_at,sequence,session";
448
+ var CONTEXT_ACCEPTED_MARKED_KEYS = "launch_identity,lease_ms,observed_at,sent_at,sequence,session";
426
449
  function sessionAt(value) {
427
450
  if (typeof value !== "object" || value === null) return void 0;
428
451
  const found = value.session;
429
452
  if (found === null) return null;
430
453
  return typeof found === "string" ? found : void 0;
431
454
  }
432
- function contextReportAt(value, keys) {
433
- if (!exactKeys(value, keys)) return void 0;
455
+ function sameLaunchIdentity(a, b) {
456
+ return a === b || a !== void 0 && b !== void 0 && a.version === b.version && a.subject_id === b.subject_id && a.launch_id === b.launch_id && a.execution_generation === b.execution_generation;
457
+ }
458
+ function contextReportAt(value, keys, markedKeys) {
459
+ const marked = exactKeys(value, markedKeys);
460
+ if (!exactKeys(value, keys) && !marked) return void 0;
434
461
  const sequence = numberAt(value, "sequence");
435
462
  const lease = numberAt(value, "lease_ms");
436
463
  const observed = textAt(value, "observed_at");
@@ -438,7 +465,15 @@ function contextReportAt(value, keys) {
438
465
  if (sequence === void 0 || sequence < 1) return void 0;
439
466
  if (lease === void 0 || observed === void 0 || session === void 0)
440
467
  return void 0;
441
- return { sequence, session, lease_ms: lease, observed_at: observed };
468
+ const marker = marked ? decodeRadarLaunchMarker(value.launch_identity) : void 0;
469
+ if (marked && marker === void 0) return void 0;
470
+ return {
471
+ sequence,
472
+ session,
473
+ lease_ms: lease,
474
+ observed_at: observed,
475
+ ...marker !== void 0 ? { launch_identity: marker } : {}
476
+ };
442
477
  }
443
478
  function createChannelWriter(input) {
444
479
  const { store, client, scheduler, recordName, channel, publisher } = input;
@@ -677,6 +712,7 @@ function createContextWriter(input) {
677
712
  const { store, client, scheduler, recordName, publisher } = input;
678
713
  let record = loadRecord();
679
714
  let desired;
715
+ let desiredMarker;
680
716
  let running = false;
681
717
  let again = false;
682
718
  let recovering = false;
@@ -702,9 +738,17 @@ function createContextWriter(input) {
702
738
  const generation = numberAt(writer, "generation");
703
739
  if (writer !== void 0 && (!exactKeys(writer, WRITER_KEYS) || !isUuid(handle) || generation === void 0))
704
740
  return blank(agentId);
705
- const pending = contextReportAt(recordAt(value, "pending"), CONTEXT_PENDING_KEYS);
741
+ const pending = contextReportAt(
742
+ recordAt(value, "pending"),
743
+ CONTEXT_PENDING_KEYS,
744
+ CONTEXT_PENDING_MARKED_KEYS
745
+ );
706
746
  const acceptedValue = recordAt(value, "accepted");
707
- const accepted = contextReportAt(acceptedValue, CONTEXT_ACCEPTED_KEYS);
747
+ const accepted = contextReportAt(
748
+ acceptedValue,
749
+ CONTEXT_ACCEPTED_KEYS,
750
+ CONTEXT_ACCEPTED_MARKED_KEYS
751
+ );
708
752
  const sentAt = numberAt(acceptedValue, "sent_at");
709
753
  return {
710
754
  version: RADAR_PUBLICATION_VERSION,
@@ -764,7 +808,10 @@ function createContextWriter(input) {
764
808
  sequence: pending.sequence,
765
809
  lease_ms: pending.lease_ms,
766
810
  observed_at: pending.observed_at,
767
- context: { session: pending.session }
811
+ context: {
812
+ session: pending.session,
813
+ ...pending.launch_identity !== void 0 ? { launch_identity: pending.launch_identity } : {}
814
+ }
768
815
  });
769
816
  if (answer.ok === true) {
770
817
  recovering = false;
@@ -795,12 +842,13 @@ function createContextWriter(input) {
795
842
  return;
796
843
  }
797
844
  }
798
- async function stage(session) {
845
+ async function stage(session, launchIdentity) {
799
846
  const pending = {
800
847
  sequence: record.sequence + 1,
801
848
  session,
802
849
  lease_ms: input.leaseMs,
803
- observed_at: new Date(scheduler.now()).toISOString()
850
+ observed_at: new Date(scheduler.now()).toISOString(),
851
+ ...launchIdentity !== void 0 ? { launch_identity: launchIdentity } : {}
804
852
  };
805
853
  record.pending = pending;
806
854
  save();
@@ -815,11 +863,11 @@ function createContextWriter(input) {
815
863
  if (desired === void 0) return;
816
864
  if (!await ensureSubject()) return;
817
865
  if (fenced) return;
818
- if (record.writer === void 0 || record.accepted === void 0 || record.accepted.session !== desired) {
819
- await stage(desired);
866
+ if (record.writer === void 0 || record.accepted === void 0 || record.accepted.session !== desired || !sameLaunchIdentity(record.accepted.launch_identity, desiredMarker)) {
867
+ await stage(desired, desiredMarker);
820
868
  return;
821
869
  }
822
- if (heartbeatDue()) await stage(record.accepted.session);
870
+ if (heartbeatDue()) await stage(record.accepted.session, desiredMarker);
823
871
  }
824
872
  async function drain() {
825
873
  if (running || stopped || fenced) return;
@@ -851,9 +899,11 @@ function createContextWriter(input) {
851
899
  void drain().catch(() => void 0);
852
900
  }
853
901
  return {
854
- update(session) {
855
- if (fenced || session === desired) return;
902
+ update(session, launchIdentity) {
903
+ if (fenced || session === desired && sameLaunchIdentity(desiredMarker, launchIdentity))
904
+ return;
856
905
  desired = session;
906
+ desiredMarker = launchIdentity;
857
907
  if (stopped) return;
858
908
  kick();
859
909
  },
@@ -0,0 +1,42 @@
1
+ // extension/radar-runtime.ts
2
+ var RADAR_RUNTIME_REQUEST = "pi-radar/runtime-request";
3
+ var RADAR_RUNTIME_VERSION = 1;
4
+ function lazyRadarRuntime(start) {
5
+ let pending;
6
+ return {
7
+ version: RADAR_RUNTIME_VERSION,
8
+ start: () => pending ??= Promise.resolve().then(start)
9
+ };
10
+ }
11
+ function provideRadarRuntime(events, handle) {
12
+ return events.on(RADAR_RUNTIME_REQUEST, (value) => {
13
+ if (!isRuntimeRequest(value)) return;
14
+ value.provide(handle);
15
+ });
16
+ }
17
+ function requestRadarRuntime(events) {
18
+ const handles = [];
19
+ events.emit(RADAR_RUNTIME_REQUEST, {
20
+ version: RADAR_RUNTIME_VERSION,
21
+ provide: (candidate) => {
22
+ if (isRadarRuntimeHandle(candidate)) handles.push(candidate);
23
+ }
24
+ });
25
+ if (handles.length === 1) return { ok: true, handle: handles[0] };
26
+ return { ok: false, reason: handles.length === 0 ? "missing" : "duplicate" };
27
+ }
28
+ function isRuntimeRequest(value) {
29
+ return typeof value === "object" && value !== null && value.version === RADAR_RUNTIME_VERSION && typeof value.provide === "function";
30
+ }
31
+ function isRadarRuntimeHandle(value) {
32
+ if (typeof value !== "object" || value === null) return false;
33
+ const handle = value;
34
+ return handle.version === RADAR_RUNTIME_VERSION && typeof handle.start === "function";
35
+ }
36
+ export {
37
+ RADAR_RUNTIME_REQUEST,
38
+ RADAR_RUNTIME_VERSION,
39
+ lazyRadarRuntime,
40
+ provideRadarRuntime,
41
+ requestRadarRuntime
42
+ };
@@ -0,0 +1,82 @@
1
+ // extension/radar-session-marker.ts
2
+ var RADAR_LAUNCH_MARKER_TYPE = "pi-radar.launch-identity";
3
+ var RADAR_LAUNCH_MARKER_VERSION = 1;
4
+ var RADAR_MAX_EXECUTION_GENERATION = 4294967295;
5
+ var RADAR_MANAGED_SUBJECT_ENV = "PI_RADAR_MANAGED_SUBJECT_ID";
6
+ var RADAR_MANAGED_LAUNCH_ENV = "PI_RADAR_MANAGED_LAUNCH_ID";
7
+ var RADAR_MANAGED_GENERATION_ENV = "PI_RADAR_MANAGED_EXECUTION_GENERATION";
8
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
9
+ function decodeRadarLaunchMarker(value) {
10
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
11
+ const record = value;
12
+ if (Object.keys(record).sort().join(",") !== "execution_generation,launch_id,subject_id,version") {
13
+ return void 0;
14
+ }
15
+ if (record.version !== RADAR_LAUNCH_MARKER_VERSION || typeof record.subject_id !== "string" || !UUID.test(record.subject_id) || typeof record.launch_id !== "string" || !UUID.test(record.launch_id) || !Number.isSafeInteger(record.execution_generation) || record.execution_generation < 1 || record.execution_generation > RADAR_MAX_EXECUTION_GENERATION) {
16
+ return void 0;
17
+ }
18
+ return {
19
+ version: RADAR_LAUNCH_MARKER_VERSION,
20
+ subject_id: record.subject_id,
21
+ launch_id: record.launch_id,
22
+ execution_generation: record.execution_generation
23
+ };
24
+ }
25
+ function appendRadarLaunchMarker(appendEntry, marker) {
26
+ const validated = decodeRadarLaunchMarker(marker);
27
+ if (!validated) throw new Error("invalid Radar launch identity marker");
28
+ appendEntry(RADAR_LAUNCH_MARKER_TYPE, validated);
29
+ }
30
+ function latestRadarLaunchMarker(branch) {
31
+ let latest;
32
+ for (const entry of branch) {
33
+ if (entry.type === "custom" && entry.customType === RADAR_LAUNCH_MARKER_TYPE) {
34
+ latest = entry;
35
+ }
36
+ }
37
+ if (!latest) return { status: "absent" };
38
+ const marker = decodeRadarLaunchMarker(latest.data);
39
+ return marker ? { status: "valid", marker } : { status: "invalid" };
40
+ }
41
+ function radarLaunchMarkerFromEnvironment(env = process.env) {
42
+ const rawGeneration = env[RADAR_MANAGED_GENERATION_ENV];
43
+ if (env[RADAR_MANAGED_SUBJECT_ENV] === void 0 || env[RADAR_MANAGED_LAUNCH_ENV] === void 0 || rawGeneration === void 0 || !/^[1-9][0-9]*$/.test(rawGeneration)) {
44
+ return void 0;
45
+ }
46
+ return decodeRadarLaunchMarker({
47
+ version: RADAR_LAUNCH_MARKER_VERSION,
48
+ subject_id: env[RADAR_MANAGED_SUBJECT_ENV],
49
+ launch_id: env[RADAR_MANAGED_LAUNCH_ENV],
50
+ execution_generation: Number(rawGeneration)
51
+ });
52
+ }
53
+ function activeRadarLaunchMarker(host) {
54
+ if (!host.sessionManager) return { status: "absent" };
55
+ return latestRadarLaunchMarker(host.sessionManager.getBranch());
56
+ }
57
+ function persistRadarLaunchMarker(host, env = process.env) {
58
+ const marker = radarLaunchMarkerFromEnvironment(env);
59
+ if (!marker) return "absent";
60
+ if (!host.appendEntry || !host.sessionManager) return "unsupported";
61
+ const current = latestRadarLaunchMarker(host.sessionManager.getBranch());
62
+ if (current.status === "valid" && sameMarker(current.marker, marker)) return "unchanged";
63
+ appendRadarLaunchMarker(host.appendEntry, marker);
64
+ return "written";
65
+ }
66
+ function sameMarker(a, b) {
67
+ return a.version === b.version && a.subject_id === b.subject_id && a.launch_id === b.launch_id && a.execution_generation === b.execution_generation;
68
+ }
69
+ export {
70
+ RADAR_LAUNCH_MARKER_TYPE,
71
+ RADAR_LAUNCH_MARKER_VERSION,
72
+ RADAR_MANAGED_GENERATION_ENV,
73
+ RADAR_MANAGED_LAUNCH_ENV,
74
+ RADAR_MANAGED_SUBJECT_ENV,
75
+ RADAR_MAX_EXECUTION_GENERATION,
76
+ activeRadarLaunchMarker,
77
+ appendRadarLaunchMarker,
78
+ decodeRadarLaunchMarker,
79
+ latestRadarLaunchMarker,
80
+ persistRadarLaunchMarker,
81
+ radarLaunchMarkerFromEnvironment
82
+ };