pi-radar 0.6.0 → 0.7.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.
package/README.md CHANGED
@@ -19,9 +19,13 @@ The baseline reports session presence through one flat request. The first manage
19
19
 
20
20
  ## Daemon contract
21
21
 
22
+ **Managed execution:** a launch Radar admits arrives with `PI_RADAR_REGISTRATION_ID` in its environment. The baseline captures it, removes it from this process's environment before any child starts, and reports it as `registration_id`, so a managed launch binds the registration Radar already allocated instead of registering itself. A session claim is still fresh per load, and a reload or `/resume` takes a new one under the same registration; nothing about the process identity is presented or reused as a claim. `launch.admit` is the method `radar run` uses and is not part of the client surface. `agent.list` entries and observation agents carry `scoped`: `true` for a process Radar launched into a scope, `false` for an observed process it did not.
23
+
24
+ **0.7.0 upgrade:** update Radar (0.3.0) and pi-radar together; the floor is wire revision 6. A managed launch now carries `PI_RADAR_REGISTRATION_ID` instead of a claim id, and pi-radar always presents its own fresh claim. Nothing in your code changes unless you read the launch environment directly.
25
+
22
26
  **0.6.0 upgrade:** update Radar and pi-radar together. Lifecycle controls require both `subject_id` and `claim_id`; handoff identities and spawn edges use `claim_id`, with no execution generation. Do not pass an agent's subject id where an attempt is required. Existing private launch records remain readable.
23
27
 
24
- pi-radar 0.6.0 speaks protocol 2 and requires wire revision 5. Revision 4 added Pi presence (`agent.presence`/`agent.release`), which is how a session acquires its subject. Revision 5 names the agent `subject_id` and one attempt `claim_id`, drops the execution-generation ordinal, and requires lifecycle controls to name the claim they were issued against, so a revision-4 client would read those fields with the old meanings: the floor is 5. Handoff methods additionally require the independently negotiated `communication.handoff.v1` contract; its timestamps remain exactly representable as JSON numbers. Clients that control managed executions must not target the legacy per-execution ID semantics. It reads `revision` and the public `contracts` list from `daemon.ping`, preserves unfamiliar contract names, and preflights only contracts needed for a multi-step or versioned operation. Backend `mux.*` primitives are private to the daemon; direct mux methods are dispatched and return the daemon's backend refusal when unavailable.
28
+ pi-radar 0.7.0 speaks protocol 2 and requires wire revision 6. Revision 6 gives a managed launch its process identity up front: Radar mints the `registration_id`, passes it as `PI_RADAR_REGISTRATION_ID`, and holds a `reserved` claim that grants nothing until the first verified report replaces it with the session's own claim, so a 0.6.0 client, which spends the spawn token as its first claim, cannot bind. Revision 4 added Pi presence (`agent.presence`/`agent.release`), which is how a session acquires its subject. Revision 5 names the agent `subject_id` and one attempt `claim_id`, drops the execution-generation ordinal, and requires lifecycle controls to name the claim they were issued against, so a revision-4 client would read those fields with the old meanings. Handoff methods additionally require the independently negotiated `communication.handoff.v1` contract; its timestamps remain exactly representable as JSON numbers. Clients that control managed executions must not target the legacy per-execution ID semantics. It reads `revision` and the public `contracts` list from `daemon.ping`, preserves unfamiliar contract names, and preflights only contracts needed for a multi-step or versioned operation. Backend `mux.*` primitives are private to the daemon; direct mux methods are dispatched and return the daemon's backend refusal when unavailable.
25
29
 
26
30
  The client uses `registry.v1` for registry operations and additionally requires `registry.facts.v1` for the facts channel and assignment extensions. Managed children require `spawn.v1`, child-pane removal requires `spawn.close.v1`, event reads require `events.v1`, and handoff operations require `communication.handoff.v1`. The expected wire examples are maintained in this repository's `docs/control-plane.fixture.jsonl` and `docs/agent-registration.fixture.jsonl` and are exercised by the package tests.
27
31
 
@@ -8,7 +8,7 @@ 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 = 5;
11
+ var RADAR_MINIMUM_REVISION = 6;
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";
@@ -119,7 +119,7 @@ function decodePresenceAnswer(value) {
119
119
  const claimId = recordAt(value, "claim_id");
120
120
  const standing = recordAt(value, "standing");
121
121
  const subjectId = recordAt(value, "subject_id");
122
- if (!isUuid(registrationId) || !isUuid(claimId) || !["pending", "granted", "duplicate", "released"].includes(String(standing)) || subjectId !== void 0 && !isUuid(subjectId) || standing === "granted" && !isUuid(subjectId))
122
+ if (!isUuid(registrationId) || !isUuid(claimId) || !["reserved", "pending", "granted", "duplicate", "released"].includes(String(standing)) || subjectId !== void 0 && !isUuid(subjectId) || standing === "granted" && !isUuid(subjectId))
123
123
  return protocol("presence reply carries no valid claim standing");
124
124
  return { ok: true, value };
125
125
  }
@@ -264,7 +264,7 @@ function unresolvedOperation(requestId, detail) {
264
264
  message: `focus ${requestId} is unresolved (${detail})`
265
265
  };
266
266
  }
267
- var RADAR_SPAWN_TOKEN_ENV = "PI_RADAR_SPAWN_TOKEN";
267
+ var RADAR_REGISTRATION_ENV = "PI_RADAR_REGISTRATION_ID";
268
268
  var RADAR_MANAGED_IDENTITY_ENV = /* @__PURE__ */ new Set([
269
269
  "PI_RADAR_MANAGED_SUBJECT_ID",
270
270
  "PI_RADAR_MANAGED_LAUNCH_ID",
@@ -413,8 +413,8 @@ function spawnRequestShape(request) {
413
413
  const env = Object.entries(request.env ?? {});
414
414
  if (env.length > 256) return "spawn env names more than 256 entries";
415
415
  for (const [name, value] of env) {
416
- if (name === RADAR_SPAWN_TOKEN_ENV)
417
- return `spawn env cannot set the reserved ${RADAR_SPAWN_TOKEN_ENV}`;
416
+ if (name === RADAR_REGISTRATION_ENV)
417
+ return `spawn env cannot set the reserved ${RADAR_REGISTRATION_ENV}`;
418
418
  if (RADAR_MANAGED_IDENTITY_ENV.has(name))
419
419
  return `spawn env cannot set Radar-issued identity variable ${name}`;
420
420
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || new RegExp("\\p{Cc}", "u").test(value))
@@ -1418,7 +1418,13 @@ function createRadarProcessSlot() {
1418
1418
  }
1419
1419
  function radarProcessSlot() {
1420
1420
  const host = globalThis;
1421
- return host[PROCESS_SLOT] ??= createRadarProcessSlot();
1421
+ if (!host[PROCESS_SLOT]) {
1422
+ const slot = createRadarProcessSlot();
1423
+ slot.launchRegistrationId = process.env.PI_RADAR_REGISTRATION_ID;
1424
+ delete process.env.PI_RADAR_REGISTRATION_ID;
1425
+ host[PROCESS_SLOT] = slot;
1426
+ }
1427
+ return host[PROCESS_SLOT];
1422
1428
  }
1423
1429
  function radarProcessIncarnation(slot = radarProcessSlot()) {
1424
1430
  return slot.incarnation ??= randomUUID2();
@@ -1848,11 +1854,6 @@ function createPresenceReporter(input) {
1848
1854
  await releasePending();
1849
1855
  const registration = await input.registration();
1850
1856
  if (!registration?.process || state !== claim || stopped) return void 0;
1851
- if (claim.initial_load) {
1852
- if (registration.spawn_token) claim.claim_id = registration.spawn_token;
1853
- claim.initial_load = false;
1854
- if (!save()) return void 0;
1855
- }
1856
1857
  const answer = await client.presence({
1857
1858
  ...registration,
1858
1859
  process: registration.process,
@@ -1891,7 +1892,7 @@ function createPresenceReporter(input) {
1891
1892
  releases.push({ registration_id: state.registration_id, claim_id: state.claim_id });
1892
1893
  }
1893
1894
  standing = void 0;
1894
- state = { version: 2, session, claim_id: randomUUID2(), initial_load: state === void 0, releases };
1895
+ state = { version: 2, session, claim_id: randomUUID2(), releases };
1895
1896
  save();
1896
1897
  stopped = false;
1897
1898
  void ensure();
@@ -1974,8 +1975,8 @@ function createRadarPublicationInner(options) {
1974
1975
  const store = options.store ?? createPublicationStore(dataRoot);
1975
1976
  const scheduler = options.scheduler ?? systemScheduler;
1976
1977
  const client = options.client;
1977
- const spawnToken = options.spawnToken;
1978
1978
  const slot = options.slot ?? radarProcessSlot();
1979
+ const launchRegistrationId = options.launchRegistrationId ?? slot.launchRegistrationId;
1979
1980
  const incarnation = options.incarnation ?? radarProcessIncarnation(slot);
1980
1981
  const cacheKey = `${options.store === void 0 ? dataRoot : storeIdentity(options.store)}\0${incarnation}`;
1981
1982
  const cached = slot.publications.get(cacheKey);
@@ -2099,7 +2100,7 @@ function createRadarPublicationInner(options) {
2099
2100
  return {
2100
2101
  ...identity,
2101
2102
  ...location?.state === "resolved" ? { location: location.location } : {},
2102
- ...spawnToken ? { spawn_token: spawnToken } : {}
2103
+ ...launchRegistrationId ? { registration_id: launchRegistrationId } : {}
2103
2104
  };
2104
2105
  },
2105
2106
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
@@ -2286,7 +2287,7 @@ function createRadarExecutionAdapter(pi, options) {
2286
2287
  },
2287
2288
  ...options.location !== void 0 ? { location: options.location } : {},
2288
2289
  ...managed !== void 0 ? { binding: managed } : {},
2289
- ...options.spawnToken !== void 0 ? { spawnToken: options.spawnToken } : {}
2290
+ ...options.launchRegistrationId !== void 0 ? { launchRegistrationId: options.launchRegistrationId } : {}
2290
2291
  });
2291
2292
  } catch {
2292
2293
  publication = unavailablePublication2();
@@ -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 = 5;
7
+ var RADAR_MINIMUM_REVISION = 6;
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";
@@ -115,7 +115,7 @@ function decodePresenceAnswer(value) {
115
115
  const claimId = recordAt(value, "claim_id");
116
116
  const standing = recordAt(value, "standing");
117
117
  const subjectId = recordAt(value, "subject_id");
118
- if (!isUuid(registrationId) || !isUuid(claimId) || !["pending", "granted", "duplicate", "released"].includes(String(standing)) || subjectId !== void 0 && !isUuid(subjectId) || standing === "granted" && !isUuid(subjectId))
118
+ if (!isUuid(registrationId) || !isUuid(claimId) || !["reserved", "pending", "granted", "duplicate", "released"].includes(String(standing)) || subjectId !== void 0 && !isUuid(subjectId) || standing === "granted" && !isUuid(subjectId))
119
119
  return protocol("presence reply carries no valid claim standing");
120
120
  return { ok: true, value };
121
121
  }
@@ -264,7 +264,7 @@ function unresolvedOperation(requestId, detail) {
264
264
  message: `focus ${requestId} is unresolved (${detail})`
265
265
  };
266
266
  }
267
- var RADAR_SPAWN_TOKEN_ENV = "PI_RADAR_SPAWN_TOKEN";
267
+ var RADAR_REGISTRATION_ENV = "PI_RADAR_REGISTRATION_ID";
268
268
  var RADAR_MANAGED_IDENTITY_ENV = /* @__PURE__ */ new Set([
269
269
  "PI_RADAR_MANAGED_SUBJECT_ID",
270
270
  "PI_RADAR_MANAGED_LAUNCH_ID",
@@ -413,8 +413,8 @@ function spawnRequestShape(request) {
413
413
  const env = Object.entries(request.env ?? {});
414
414
  if (env.length > 256) return "spawn env names more than 256 entries";
415
415
  for (const [name, value] of env) {
416
- if (name === RADAR_SPAWN_TOKEN_ENV)
417
- return `spawn env cannot set the reserved ${RADAR_SPAWN_TOKEN_ENV}`;
416
+ if (name === RADAR_REGISTRATION_ENV)
417
+ return `spawn env cannot set the reserved ${RADAR_REGISTRATION_ENV}`;
418
418
  if (RADAR_MANAGED_IDENTITY_ENV.has(name))
419
419
  return `spawn env cannot set Radar-issued identity variable ${name}`;
420
420
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || new RegExp("\\p{Cc}", "u").test(value))
@@ -1270,6 +1270,7 @@ export {
1270
1270
  RADAR_MAX_LINE_BYTES,
1271
1271
  RADAR_MINIMUM_REVISION,
1272
1272
  RADAR_PROTOCOL_VERSION,
1273
+ RADAR_REGISTRATION_ENV,
1273
1274
  RADAR_REGISTRY_CONTRACT,
1274
1275
  RADAR_REGISTRY_FACTS_CONTRACT,
1275
1276
  RADAR_REQUESTER,
@@ -1277,7 +1278,6 @@ export {
1277
1278
  RADAR_SPAWN_CONTRACT,
1278
1279
  RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT,
1279
1280
  RADAR_SPAWN_LIST_CONTRACT,
1280
- RADAR_SPAWN_TOKEN_ENV,
1281
1281
  boundedRadarAssignment,
1282
1282
  boundedRadarFacts,
1283
1283
  boundedRadarSnapshot,
@@ -272,7 +272,13 @@ function createRadarProcessSlot() {
272
272
  }
273
273
  function radarProcessSlot() {
274
274
  const host = globalThis;
275
- return host[PROCESS_SLOT] ??= createRadarProcessSlot();
275
+ if (!host[PROCESS_SLOT]) {
276
+ const slot = createRadarProcessSlot();
277
+ slot.launchRegistrationId = process.env.PI_RADAR_REGISTRATION_ID;
278
+ delete process.env.PI_RADAR_REGISTRATION_ID;
279
+ host[PROCESS_SLOT] = slot;
280
+ }
281
+ return host[PROCESS_SLOT];
276
282
  }
277
283
  function radarProcessIncarnation(slot = radarProcessSlot()) {
278
284
  return slot.incarnation ??= randomUUID();
@@ -702,11 +708,6 @@ function createPresenceReporter(input) {
702
708
  await releasePending();
703
709
  const registration = await input.registration();
704
710
  if (!registration?.process || state !== claim || stopped) return void 0;
705
- if (claim.initial_load) {
706
- if (registration.spawn_token) claim.claim_id = registration.spawn_token;
707
- claim.initial_load = false;
708
- if (!save()) return void 0;
709
- }
710
711
  const answer = await client.presence({
711
712
  ...registration,
712
713
  process: registration.process,
@@ -745,7 +746,7 @@ function createPresenceReporter(input) {
745
746
  releases.push({ registration_id: state.registration_id, claim_id: state.claim_id });
746
747
  }
747
748
  standing = void 0;
748
- state = { version: 2, session, claim_id: randomUUID(), initial_load: state === void 0, releases };
749
+ state = { version: 2, session, claim_id: randomUUID(), releases };
749
750
  save();
750
751
  stopped = false;
751
752
  void ensure();
@@ -828,8 +829,8 @@ function createRadarPublicationInner(options) {
828
829
  const store = options.store ?? createPublicationStore(dataRoot);
829
830
  const scheduler = options.scheduler ?? systemScheduler;
830
831
  const client = options.client;
831
- const spawnToken = options.spawnToken;
832
832
  const slot = options.slot ?? radarProcessSlot();
833
+ const launchRegistrationId = options.launchRegistrationId ?? slot.launchRegistrationId;
833
834
  const incarnation = options.incarnation ?? radarProcessIncarnation(slot);
834
835
  const cacheKey = `${options.store === void 0 ? dataRoot : storeIdentity(options.store)}\0${incarnation}`;
835
836
  const cached = slot.publications.get(cacheKey);
@@ -953,7 +954,7 @@ function createRadarPublicationInner(options) {
953
954
  return {
954
955
  ...identity,
955
956
  ...location?.state === "resolved" ? { location: location.location } : {},
956
- ...spawnToken ? { spawn_token: spawnToken } : {}
957
+ ...launchRegistrationId ? { registration_id: launchRegistrationId } : {}
957
958
  };
958
959
  },
959
960
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
@@ -1146,7 +1147,7 @@ function createRadarExecutionAdapter(pi, options) {
1146
1147
  },
1147
1148
  ...options.location !== void 0 ? { location: options.location } : {},
1148
1149
  ...managed !== void 0 ? { binding: managed } : {},
1149
- ...options.spawnToken !== void 0 ? { spawnToken: options.spawnToken } : {}
1150
+ ...options.launchRegistrationId !== void 0 ? { launchRegistrationId: options.launchRegistrationId } : {}
1150
1151
  });
1151
1152
  } catch {
1152
1153
  publication = unavailablePublication2();
@@ -274,7 +274,13 @@ function createRadarProcessSlot() {
274
274
  }
275
275
  function radarProcessSlot() {
276
276
  const host = globalThis;
277
- return host[PROCESS_SLOT] ??= createRadarProcessSlot();
277
+ if (!host[PROCESS_SLOT]) {
278
+ const slot = createRadarProcessSlot();
279
+ slot.launchRegistrationId = process.env.PI_RADAR_REGISTRATION_ID;
280
+ delete process.env.PI_RADAR_REGISTRATION_ID;
281
+ host[PROCESS_SLOT] = slot;
282
+ }
283
+ return host[PROCESS_SLOT];
278
284
  }
279
285
  function radarProcessIncarnation(slot = radarProcessSlot()) {
280
286
  return slot.incarnation ??= randomUUID();
@@ -707,11 +713,6 @@ function createPresenceReporter(input) {
707
713
  await releasePending();
708
714
  const registration = await input.registration();
709
715
  if (!registration?.process || state !== claim || stopped) return void 0;
710
- if (claim.initial_load) {
711
- if (registration.spawn_token) claim.claim_id = registration.spawn_token;
712
- claim.initial_load = false;
713
- if (!save()) return void 0;
714
- }
715
716
  const answer = await client.presence({
716
717
  ...registration,
717
718
  process: registration.process,
@@ -750,7 +751,7 @@ function createPresenceReporter(input) {
750
751
  releases.push({ registration_id: state.registration_id, claim_id: state.claim_id });
751
752
  }
752
753
  standing = void 0;
753
- state = { version: 2, session, claim_id: randomUUID(), initial_load: state === void 0, releases };
754
+ state = { version: 2, session, claim_id: randomUUID(), releases };
754
755
  save();
755
756
  stopped = false;
756
757
  void ensure();
@@ -833,8 +834,8 @@ function createRadarPublicationInner(options) {
833
834
  const store = options.store ?? createPublicationStore(dataRoot);
834
835
  const scheduler = options.scheduler ?? systemScheduler;
835
836
  const client = options.client;
836
- const spawnToken = options.spawnToken;
837
837
  const slot = options.slot ?? radarProcessSlot();
838
+ const launchRegistrationId = options.launchRegistrationId ?? slot.launchRegistrationId;
838
839
  const incarnation = options.incarnation ?? radarProcessIncarnation(slot);
839
840
  const cacheKey = `${options.store === void 0 ? dataRoot : storeIdentity(options.store)}\0${incarnation}`;
840
841
  const cached = slot.publications.get(cacheKey);
@@ -958,7 +959,7 @@ function createRadarPublicationInner(options) {
958
959
  return {
959
960
  ...identity,
960
961
  ...location?.state === "resolved" ? { location: location.location } : {},
961
- ...spawnToken ? { spawn_token: spawnToken } : {}
962
+ ...launchRegistrationId ? { registration_id: launchRegistrationId } : {}
962
963
  };
963
964
  },
964
965
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ __export(radar_client_exports, {
16
16
  RADAR_MAX_LINE_BYTES: () => RADAR_MAX_LINE_BYTES,
17
17
  RADAR_MINIMUM_REVISION: () => RADAR_MINIMUM_REVISION,
18
18
  RADAR_PROTOCOL_VERSION: () => RADAR_PROTOCOL_VERSION,
19
+ RADAR_REGISTRATION_ENV: () => RADAR_REGISTRATION_ENV,
19
20
  RADAR_REGISTRY_CONTRACT: () => RADAR_REGISTRY_CONTRACT,
20
21
  RADAR_REGISTRY_FACTS_CONTRACT: () => RADAR_REGISTRY_FACTS_CONTRACT,
21
22
  RADAR_REQUESTER: () => RADAR_REQUESTER,
@@ -23,7 +24,6 @@ __export(radar_client_exports, {
23
24
  RADAR_SPAWN_CONTRACT: () => RADAR_SPAWN_CONTRACT,
24
25
  RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT: () => RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT,
25
26
  RADAR_SPAWN_LIST_CONTRACT: () => RADAR_SPAWN_LIST_CONTRACT,
26
- RADAR_SPAWN_TOKEN_ENV: () => RADAR_SPAWN_TOKEN_ENV,
27
27
  boundedRadarAssignment: () => boundedRadarAssignment,
28
28
  boundedRadarFacts: () => boundedRadarFacts,
29
29
  boundedRadarSnapshot: () => boundedRadarSnapshot,
@@ -40,7 +40,7 @@ import { lstatSync } from "node:fs";
40
40
  import { createConnection } from "node:net";
41
41
  import { dirname, join } from "node:path";
42
42
  var RADAR_PROTOCOL_VERSION = 2;
43
- var RADAR_MINIMUM_REVISION = 5;
43
+ var RADAR_MINIMUM_REVISION = 6;
44
44
  var RADAR_REGISTRY_CONTRACT = "registry.v1";
45
45
  var RADAR_REGISTRY_FACTS_CONTRACT = "registry.facts.v1";
46
46
  var RADAR_SPAWN_CONTRACT = "spawn.v1";
@@ -151,7 +151,7 @@ function decodePresenceAnswer(value) {
151
151
  const claimId = recordAt(value, "claim_id");
152
152
  const standing = recordAt(value, "standing");
153
153
  const subjectId = recordAt(value, "subject_id");
154
- if (!isUuid(registrationId) || !isUuid(claimId) || !["pending", "granted", "duplicate", "released"].includes(String(standing)) || subjectId !== void 0 && !isUuid(subjectId) || standing === "granted" && !isUuid(subjectId))
154
+ if (!isUuid(registrationId) || !isUuid(claimId) || !["reserved", "pending", "granted", "duplicate", "released"].includes(String(standing)) || subjectId !== void 0 && !isUuid(subjectId) || standing === "granted" && !isUuid(subjectId))
155
155
  return protocol("presence reply carries no valid claim standing");
156
156
  return { ok: true, value };
157
157
  }
@@ -300,7 +300,7 @@ function unresolvedOperation(requestId, detail) {
300
300
  message: `focus ${requestId} is unresolved (${detail})`
301
301
  };
302
302
  }
303
- var RADAR_SPAWN_TOKEN_ENV = "PI_RADAR_SPAWN_TOKEN";
303
+ var RADAR_REGISTRATION_ENV = "PI_RADAR_REGISTRATION_ID";
304
304
  var RADAR_MANAGED_IDENTITY_ENV = /* @__PURE__ */ new Set([
305
305
  "PI_RADAR_MANAGED_SUBJECT_ID",
306
306
  "PI_RADAR_MANAGED_LAUNCH_ID",
@@ -449,8 +449,8 @@ function spawnRequestShape(request) {
449
449
  const env = Object.entries(request.env ?? {});
450
450
  if (env.length > 256) return "spawn env names more than 256 entries";
451
451
  for (const [name, value] of env) {
452
- if (name === RADAR_SPAWN_TOKEN_ENV)
453
- return `spawn env cannot set the reserved ${RADAR_SPAWN_TOKEN_ENV}`;
452
+ if (name === RADAR_REGISTRATION_ENV)
453
+ return `spawn env cannot set the reserved ${RADAR_REGISTRATION_ENV}`;
454
454
  if (RADAR_MANAGED_IDENTITY_ENV.has(name))
455
455
  return `spawn env cannot set Radar-issued identity variable ${name}`;
456
456
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || new RegExp("\\p{Cc}", "u").test(value))
@@ -1538,7 +1538,13 @@ function createRadarProcessSlot() {
1538
1538
  }
1539
1539
  function radarProcessSlot() {
1540
1540
  const host = globalThis;
1541
- return host[PROCESS_SLOT] ??= createRadarProcessSlot();
1541
+ if (!host[PROCESS_SLOT]) {
1542
+ const slot = createRadarProcessSlot();
1543
+ slot.launchRegistrationId = process.env.PI_RADAR_REGISTRATION_ID;
1544
+ delete process.env.PI_RADAR_REGISTRATION_ID;
1545
+ host[PROCESS_SLOT] = slot;
1546
+ }
1547
+ return host[PROCESS_SLOT];
1542
1548
  }
1543
1549
  function radarProcessIncarnation(slot = radarProcessSlot()) {
1544
1550
  return slot.incarnation ??= randomUUID2();
@@ -1971,11 +1977,6 @@ function createPresenceReporter(input) {
1971
1977
  await releasePending();
1972
1978
  const registration = await input.registration();
1973
1979
  if (!registration?.process || state !== claim || stopped) return void 0;
1974
- if (claim.initial_load) {
1975
- if (registration.spawn_token) claim.claim_id = registration.spawn_token;
1976
- claim.initial_load = false;
1977
- if (!save()) return void 0;
1978
- }
1979
1980
  const answer = await client.presence({
1980
1981
  ...registration,
1981
1982
  process: registration.process,
@@ -2014,7 +2015,7 @@ function createPresenceReporter(input) {
2014
2015
  releases.push({ registration_id: state.registration_id, claim_id: state.claim_id });
2015
2016
  }
2016
2017
  standing = void 0;
2017
- state = { version: 2, session, claim_id: randomUUID2(), initial_load: state === void 0, releases };
2018
+ state = { version: 2, session, claim_id: randomUUID2(), releases };
2018
2019
  save();
2019
2020
  stopped = false;
2020
2021
  void ensure();
@@ -2097,8 +2098,8 @@ function createRadarPublicationInner(options) {
2097
2098
  const store = options.store ?? createPublicationStore(dataRoot);
2098
2099
  const scheduler = options.scheduler ?? systemScheduler;
2099
2100
  const client = options.client;
2100
- const spawnToken = options.spawnToken;
2101
2101
  const slot = options.slot ?? radarProcessSlot();
2102
+ const launchRegistrationId = options.launchRegistrationId ?? slot.launchRegistrationId;
2102
2103
  const incarnation = options.incarnation ?? radarProcessIncarnation(slot);
2103
2104
  const cacheKey = `${options.store === void 0 ? dataRoot : storeIdentity(options.store)}\0${incarnation}`;
2104
2105
  const cached = slot.publications.get(cacheKey);
@@ -2222,7 +2223,7 @@ function createRadarPublicationInner(options) {
2222
2223
  return {
2223
2224
  ...identity,
2224
2225
  ...location?.state === "resolved" ? { location: location.location } : {},
2225
- ...spawnToken ? { spawn_token: spawnToken } : {}
2226
+ ...launchRegistrationId ? { registration_id: launchRegistrationId } : {}
2226
2227
  };
2227
2228
  },
2228
2229
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
@@ -2415,7 +2416,7 @@ function createRadarExecutionAdapter(pi, options) {
2415
2416
  },
2416
2417
  ...options.location !== void 0 ? { location: options.location } : {},
2417
2418
  ...managed !== void 0 ? { binding: managed } : {},
2418
- ...options.spawnToken !== void 0 ? { spawnToken: options.spawnToken } : {}
2419
+ ...options.launchRegistrationId !== void 0 ? { launchRegistrationId: options.launchRegistrationId } : {}
2419
2420
  });
2420
2421
  } catch {
2421
2422
  publication = unavailablePublication2();
@@ -11,7 +11,6 @@ export interface PresenceReportDeserialize {
11
11
  run?: string | null;
12
12
  session_id: string;
13
13
  source: string;
14
- spawn_token?: string | null;
15
14
  subject_id?: string | null;
16
15
  }
17
16
  /**
@@ -54,7 +53,7 @@ export interface PresenceReportProcessIdentity {
54
53
  start_ticks: number;
55
54
  [k: string]: unknown;
56
55
  }
57
- export type PresenceAnswerStanding = "pending" | "granted" | "duplicate" | "released";
56
+ export type PresenceAnswerStanding = "reserved" | "pending" | "granted" | "duplicate" | "released";
58
57
 
59
58
  /**
60
59
  * The small public receipt; session/location context stays inside Radar.
@@ -284,7 +283,7 @@ export type Freshness = "fresh" | "stale";
284
283
  * refused rather than silently dropped.
285
284
  */
286
285
  export type Report = Snapshot | AssignmentSnapshot | FactsSnapshot;
287
- export type Standing = "pending" | "granted" | "duplicate" | "released";
286
+ export type Standing = "reserved" | "pending" | "granted" | "duplicate" | "released";
288
287
  /**
289
288
  * How a registration relates to the live panes.
290
289
  */
@@ -675,6 +674,11 @@ export interface RegistryAgent {
675
674
  facts: PublicChannelFacts | null;
676
675
  process: ProcessEvidence;
677
676
  registration: PublicRegistration;
677
+ /**
678
+ * Whether this process runs inside a Radar-established execution scope. A
679
+ * process started outside the managed launch path has none and is observed.
680
+ */
681
+ scoped?: boolean | null;
678
682
  }
679
683
  /**
680
684
  * A channel read for a reader that is not its writer: the same identity,
@@ -36,7 +36,7 @@ export const RADAR_PROTOCOL_VERSION = 2;
36
36
  * Revision 5 names attempts by claim_id and requires exact-claim controls.
37
37
  * Earlier daemons expose different identity fields; require a coordinated update.
38
38
  */
39
- export const RADAR_MINIMUM_REVISION = 5;
39
+ export const RADAR_MINIMUM_REVISION = 6;
40
40
  /** Public daemon contracts this client may preflight. */
41
41
  export const RADAR_REGISTRY_CONTRACT = "registry.v1";
42
42
  export const RADAR_REGISTRY_FACTS_CONTRACT = "registry.facts.v1";
@@ -517,7 +517,7 @@ function decodePresenceAnswer(value: unknown): RadarResult<RadarPresenceAnswer>
517
517
  if (
518
518
  !isUuid(registrationId) ||
519
519
  !isUuid(claimId) ||
520
- !["pending", "granted", "duplicate", "released"].includes(String(standing)) ||
520
+ !["reserved", "pending", "granted", "duplicate", "released"].includes(String(standing)) ||
521
521
  (subjectId !== undefined && !isUuid(subjectId)) ||
522
522
  (standing === "granted" && !isUuid(subjectId))
523
523
  )
@@ -745,7 +745,7 @@ function unresolvedOperation(requestId: string, detail: string): RadarResult<nev
745
745
  };
746
746
  }
747
747
 
748
- export const RADAR_SPAWN_TOKEN_ENV = "PI_RADAR_SPAWN_TOKEN";
748
+ export const RADAR_REGISTRATION_ENV = "PI_RADAR_REGISTRATION_ID";
749
749
  const RADAR_MANAGED_IDENTITY_ENV = new Set([
750
750
  "PI_RADAR_MANAGED_SUBJECT_ID",
751
751
  "PI_RADAR_MANAGED_LAUNCH_ID",
@@ -975,8 +975,8 @@ function spawnRequestShape(request: {
975
975
  const env = Object.entries(request.env ?? {});
976
976
  if (env.length > 256) return "spawn env names more than 256 entries";
977
977
  for (const [name, value] of env) {
978
- if (name === RADAR_SPAWN_TOKEN_ENV)
979
- return `spawn env cannot set the reserved ${RADAR_SPAWN_TOKEN_ENV}`;
978
+ if (name === RADAR_REGISTRATION_ENV)
979
+ return `spawn env cannot set the reserved ${RADAR_REGISTRATION_ENV}`;
980
980
  if (RADAR_MANAGED_IDENTITY_ENV.has(name))
981
981
  return `spawn env cannot set Radar-issued identity variable ${name}`;
982
982
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || /\p{Cc}/u.test(value))
@@ -51,10 +51,10 @@ export type RadarExecutionOptions = {
51
51
  */
52
52
  location?: () => Promise<RadarLocationResolution>;
53
53
  /**
54
- * A daemon-spawned child's one-use launch token, captured before this process
55
- * could observe anything else. It travels with the registration request only.
54
+ * Registration allocated by Radar before this process launched. Normally
55
+ * captured from the launch environment by the process-local publication.
56
56
  */
57
- spawnToken?: string;
57
+ launchRegistrationId?: string;
58
58
  /** Only a real fixed managed assignment is part of immutable registration. */
59
59
  managedBinding?: { key: string; run: string; owner: string; label: string };
60
60
  /**
@@ -207,8 +207,8 @@ export function createRadarExecutionAdapter(
207
207
  ? { location: options.location }
208
208
  : {}),
209
209
  ...(managed !== undefined ? { binding: managed } : {}),
210
- ...(options.spawnToken !== undefined
211
- ? { spawnToken: options.spawnToken }
210
+ ...(options.launchRegistrationId !== undefined
211
+ ? { launchRegistrationId: options.launchRegistrationId }
212
212
  : {}),
213
213
  });
214
214
  } catch {
@@ -269,6 +269,7 @@ export function createPublicationStore(root: string): PublicationStore {
269
269
  */
270
270
  export type RadarProcessSlot = {
271
271
  incarnation?: string;
272
+ launchRegistrationId?: string;
272
273
  publications: Map<string, RadarPublication>;
273
274
  };
274
275
 
@@ -280,7 +281,13 @@ export function radarProcessSlot(): RadarProcessSlot {
280
281
  const host = globalThis as typeof globalThis & {
281
282
  [PROCESS_SLOT]?: RadarProcessSlot;
282
283
  };
283
- return (host[PROCESS_SLOT] ??= createRadarProcessSlot());
284
+ if (!host[PROCESS_SLOT]) {
285
+ const slot = createRadarProcessSlot();
286
+ slot.launchRegistrationId = process.env.PI_RADAR_REGISTRATION_ID;
287
+ delete process.env.PI_RADAR_REGISTRATION_ID;
288
+ host[PROCESS_SLOT] = slot;
289
+ }
290
+ return host[PROCESS_SLOT];
284
291
  }
285
292
 
286
293
  /** The process incarnation, created on first use and kept for the process. */
@@ -975,7 +982,6 @@ function createPresenceReporter(input: PresenceReporterInput): RadarPresenceRepo
975
982
  type State = {
976
983
  version: 2;
977
984
  claim_id: string;
978
- initial_load?: boolean;
979
985
  session: string;
980
986
  registration_id?: string;
981
987
  subject_id?: string;
@@ -1033,13 +1039,6 @@ function createPresenceReporter(input: PresenceReporterInput): RadarPresenceRepo
1033
1039
  await releasePending();
1034
1040
  const registration = await input.registration();
1035
1041
  if (!registration?.process || state !== claim || stopped) return undefined;
1036
- // The first load spends the pre-minted launch claim. Reloads get a fresh
1037
- // claim; they must not reuse the released launch credential.
1038
- if (claim.initial_load) {
1039
- if (registration.spawn_token) claim.claim_id = registration.spawn_token;
1040
- claim.initial_load = false;
1041
- if (!save()) return undefined;
1042
- }
1043
1042
  const answer = await client.presence({
1044
1043
  ...registration, process: registration.process,
1045
1044
  session_id: claim.session, claim_id: claim.claim_id,
@@ -1079,7 +1078,7 @@ function createPresenceReporter(input: PresenceReporterInput): RadarPresenceRepo
1079
1078
  releases.push({ registration_id: state.registration_id, claim_id: state.claim_id });
1080
1079
  }
1081
1080
  standing = undefined;
1082
- state = { version: 2, session, claim_id: randomUUID(), initial_load: state === undefined, releases };
1081
+ state = { version: 2, session, claim_id: randomUUID(), releases };
1083
1082
  save(); stopped = false;
1084
1083
  void ensure();
1085
1084
  },
@@ -1159,7 +1158,7 @@ export type RadarPublicationOptions = {
1159
1158
  /** Set on a managed child: the sidecar its owner correlates the subject with. */
1160
1159
  binding?: { key: string; run: string; owner: string; label: string };
1161
1160
  /** A daemon-spawned child's one-use launch token; sent privately, never stored in the process record. */
1162
- spawnToken?: string;
1161
+ launchRegistrationId?: string;
1163
1162
  leaseMs?: number;
1164
1163
  heartbeatMs?: number;
1165
1164
  };
@@ -1234,8 +1233,8 @@ function createRadarPublicationInner(
1234
1233
  const store = options.store ?? createPublicationStore(dataRoot);
1235
1234
  const scheduler = options.scheduler ?? systemScheduler;
1236
1235
  const client = options.client;
1237
- const spawnToken = options.spawnToken;
1238
1236
  const slot = options.slot ?? radarProcessSlot();
1237
+ const launchRegistrationId = options.launchRegistrationId ?? slot.launchRegistrationId;
1239
1238
  const incarnation = options.incarnation ?? radarProcessIncarnation(slot);
1240
1239
 
1241
1240
  const cacheKey = `${
@@ -1424,7 +1423,7 @@ function createRadarPublicationInner(
1424
1423
  const { location: _initialLocation, ...identity } = record.registration;
1425
1424
  return { ...identity,
1426
1425
  ...(location?.state === "resolved" ? { location: location.location } : {}),
1427
- ...(spawnToken ? { spawn_token: spawnToken } : {}),
1426
+ ...(launchRegistrationId ? { registration_id: launchRegistrationId } : {}),
1428
1427
  };
1429
1428
  },
1430
1429
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-radar",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "Radar daemon client, communication, publication, execution, location, launch and inspection surface for Pi extensions.",
6
6
  "license": "Apache-2.0",
@@ -2098,6 +2098,13 @@
2098
2098
  },
2099
2099
  "registration": {
2100
2100
  "$ref": "#/$defs/PublicRegistration"
2101
+ },
2102
+ "scoped": {
2103
+ "description": "Whether this process runs inside a Radar-established execution scope. A\nprocess started outside the managed launch path has none and is observed.",
2104
+ "type": [
2105
+ "boolean",
2106
+ "null"
2107
+ ]
2101
2108
  }
2102
2109
  },
2103
2110
  "required": [
@@ -2424,6 +2431,7 @@
2424
2431
  },
2425
2432
  "Standing": {
2426
2433
  "enum": [
2434
+ "reserved",
2427
2435
  "pending",
2428
2436
  "granted",
2429
2437
  "duplicate",
@@ -2201,6 +2201,13 @@
2201
2201
  },
2202
2202
  "registration": {
2203
2203
  "$ref": "#/$defs/PublicRegistration"
2204
+ },
2205
+ "scoped": {
2206
+ "description": "Whether this process runs inside a Radar-established execution scope. A\nprocess started outside the managed launch path has none and is observed.",
2207
+ "type": [
2208
+ "boolean",
2209
+ "null"
2210
+ ]
2204
2211
  }
2205
2212
  },
2206
2213
  "required": [
@@ -2543,6 +2550,7 @@
2543
2550
  },
2544
2551
  "Standing": {
2545
2552
  "enum": [
2553
+ "reserved",
2546
2554
  "pending",
2547
2555
  "granted",
2548
2556
  "duplicate",
@@ -1963,6 +1963,13 @@
1963
1963
  },
1964
1964
  "registration": {
1965
1965
  "$ref": "#/$defs/PublicRegistration"
1966
+ },
1967
+ "scoped": {
1968
+ "description": "Whether this process runs inside a Radar-established execution scope. A\nprocess started outside the managed launch path has none and is observed.",
1969
+ "type": [
1970
+ "boolean",
1971
+ "null"
1972
+ ]
1966
1973
  }
1967
1974
  },
1968
1975
  "required": [
@@ -2289,6 +2296,7 @@
2289
2296
  },
2290
2297
  "Standing": {
2291
2298
  "enum": [
2299
+ "reserved",
2292
2300
  "pending",
2293
2301
  "granted",
2294
2302
  "duplicate",
@@ -2057,6 +2057,13 @@
2057
2057
  },
2058
2058
  "registration": {
2059
2059
  "$ref": "#/$defs/PublicRegistration"
2060
+ },
2061
+ "scoped": {
2062
+ "description": "Whether this process runs inside a Radar-established execution scope. A\nprocess started outside the managed launch path has none and is observed.",
2063
+ "type": [
2064
+ "boolean",
2065
+ "null"
2066
+ ]
2060
2067
  }
2061
2068
  },
2062
2069
  "required": [
@@ -2399,6 +2406,7 @@
2399
2406
  },
2400
2407
  "Standing": {
2401
2408
  "enum": [
2409
+ "reserved",
2402
2410
  "pending",
2403
2411
  "granted",
2404
2412
  "duplicate",
@@ -2,6 +2,7 @@
2
2
  "$defs": {
3
3
  "Standing": {
4
4
  "enum": [
5
+ "reserved",
5
6
  "pending",
6
7
  "granted",
7
8
  "duplicate",
@@ -2,6 +2,7 @@
2
2
  "$defs": {
3
3
  "Standing": {
4
4
  "enum": [
5
+ "reserved",
5
6
  "pending",
6
7
  "granted",
7
8
  "duplicate",
@@ -122,13 +122,6 @@
122
122
  "source": {
123
123
  "type": "string"
124
124
  },
125
- "spawn_token": {
126
- "default": null,
127
- "type": [
128
- "string",
129
- "null"
130
- ]
131
- },
132
125
  "subject_id": {
133
126
  "default": null,
134
127
  "type": [
@@ -123,13 +123,6 @@
123
123
  "source": {
124
124
  "type": "string"
125
125
  },
126
- "spawn_token": {
127
- "default": null,
128
- "type": [
129
- "string",
130
- "null"
131
- ]
132
- },
133
126
  "subject_id": {
134
127
  "default": null,
135
128
  "type": [
@@ -147,7 +140,6 @@
147
140
  "registration_id",
148
141
  "subject_id",
149
142
  "location",
150
- "spawn_token",
151
143
  "owner",
152
144
  "run",
153
145
  "label"