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.
- package/README.md +9 -5
- package/dist/extension/baseline.js +2626 -0
- package/dist/extension/radar-client.js +22 -2
- package/dist/extension/radar-execution.js +146 -16
- package/dist/extension/radar-location.js +2 -2
- package/dist/extension/radar-publication.js +65 -15
- package/dist/extension/radar-runtime.js +42 -0
- package/dist/extension/radar-session-marker.js +82 -0
- package/dist/index.js +216 -21
- package/extension/baseline.ts +37 -0
- package/extension/generated-contracts.ts +21 -0
- package/extension/radar-client.ts +35 -1
- package/extension/radar-execution.ts +56 -3
- package/extension/radar-location.ts +2 -2
- package/extension/radar-publication.ts +69 -12
- package/extension/radar-runtime.ts +94 -0
- package/extension/radar-session-marker.ts +146 -0
- package/index.ts +1 -0
- package/package.json +18 -4
- package/schema/public/v1/launch-identity-marker.deserialize.schema.json +32 -0
- package/schema/public/v1/launch-identity-marker.serialize.schema.json +32 -0
- package/schema/public/v1/managed-launch-metadata.deserialize.schema.json +39 -0
- package/schema/public/v1/managed-launch-metadata.serialize.schema.json +39 -0
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ import { lstatSync } from "node:fs";
|
|
|
38
38
|
import { createConnection } from "node:net";
|
|
39
39
|
import { dirname, join } from "node:path";
|
|
40
40
|
var RADAR_PROTOCOL_VERSION = 2;
|
|
41
|
-
var RADAR_MINIMUM_REVISION =
|
|
41
|
+
var RADAR_MINIMUM_REVISION = 3;
|
|
42
42
|
var RADAR_REGISTRY_CONTRACT = "registry.v1";
|
|
43
43
|
var RADAR_REGISTRY_FACTS_CONTRACT = "registry.facts.v1";
|
|
44
44
|
var RADAR_SPAWN_CONTRACT = "spawn.v1";
|
|
@@ -258,6 +258,11 @@ function unresolvedOperation(requestId, detail) {
|
|
|
258
258
|
};
|
|
259
259
|
}
|
|
260
260
|
var RADAR_SPAWN_TOKEN_ENV = "PI_RADAR_SPAWN_TOKEN";
|
|
261
|
+
var RADAR_MANAGED_IDENTITY_ENV = /* @__PURE__ */ new Set([
|
|
262
|
+
"PI_RADAR_MANAGED_SUBJECT_ID",
|
|
263
|
+
"PI_RADAR_MANAGED_LAUNCH_ID",
|
|
264
|
+
"PI_RADAR_MANAGED_EXECUTION_GENERATION"
|
|
265
|
+
]);
|
|
261
266
|
function unresolvedSpawn(requestId, detail) {
|
|
262
267
|
return {
|
|
263
268
|
ok: false,
|
|
@@ -402,9 +407,23 @@ function spawnRequestShape(request) {
|
|
|
402
407
|
for (const [name, value] of env) {
|
|
403
408
|
if (name === RADAR_SPAWN_TOKEN_ENV)
|
|
404
409
|
return `spawn env cannot set the reserved ${RADAR_SPAWN_TOKEN_ENV}`;
|
|
410
|
+
if (RADAR_MANAGED_IDENTITY_ENV.has(name))
|
|
411
|
+
return `spawn env cannot set Radar-issued identity variable ${name}`;
|
|
405
412
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || new RegExp("\\p{Cc}", "u").test(value))
|
|
406
413
|
return `spawn env contains an invalid name or value: ${name}`;
|
|
407
414
|
}
|
|
415
|
+
if (request.managed !== void 0) {
|
|
416
|
+
if (typeof request.managed !== "object" || request.managed === null)
|
|
417
|
+
return "managed launch metadata must be an object";
|
|
418
|
+
for (const [name, value] of Object.entries(request.managed)) {
|
|
419
|
+
if (!["owner", "run", "label", "role", "parent_subject_id"].includes(name))
|
|
420
|
+
return `unknown managed launch metadata field: ${name}`;
|
|
421
|
+
if (value != null && (typeof value !== "string" || value.trim() === "" || new TextEncoder().encode(value).length > 256 || new RegExp("\\p{Cc}", "u").test(value)))
|
|
422
|
+
return `managed launch ${name} must be non-empty, control-free, and at most 256 UTF-8 bytes`;
|
|
423
|
+
}
|
|
424
|
+
if (request.managed.parent_subject_id != null && !isUuid(request.managed.parent_subject_id))
|
|
425
|
+
return "managed parent_subject_id must be a canonical UUID";
|
|
426
|
+
}
|
|
408
427
|
return void 0;
|
|
409
428
|
}
|
|
410
429
|
function focusOutcome(record, requestId) {
|
|
@@ -815,7 +834,8 @@ function createRadarClient(options = {}) {
|
|
|
815
834
|
executable: request.executable,
|
|
816
835
|
argv: request.argv,
|
|
817
836
|
...request.cwd !== void 0 ? { cwd: request.cwd } : {},
|
|
818
|
-
...carriesContext && request.env !== void 0 ? { env: request.env } : {}
|
|
837
|
+
...carriesContext && request.env !== void 0 ? { env: request.env } : {},
|
|
838
|
+
...request.managed !== void 0 ? { managed: request.managed } : {}
|
|
819
839
|
},
|
|
820
840
|
requester
|
|
821
841
|
},
|
|
@@ -1173,6 +1193,77 @@ import {
|
|
|
1173
1193
|
writeSync
|
|
1174
1194
|
} from "node:fs";
|
|
1175
1195
|
import { dirname as dirname2, join as join2, parse, resolve, sep } from "node:path";
|
|
1196
|
+
|
|
1197
|
+
// extension/radar-session-marker.ts
|
|
1198
|
+
var RADAR_LAUNCH_MARKER_TYPE = "pi-radar.launch-identity";
|
|
1199
|
+
var RADAR_LAUNCH_MARKER_VERSION = 1;
|
|
1200
|
+
var RADAR_MAX_EXECUTION_GENERATION = 4294967295;
|
|
1201
|
+
var RADAR_MANAGED_SUBJECT_ENV = "PI_RADAR_MANAGED_SUBJECT_ID";
|
|
1202
|
+
var RADAR_MANAGED_LAUNCH_ENV = "PI_RADAR_MANAGED_LAUNCH_ID";
|
|
1203
|
+
var RADAR_MANAGED_GENERATION_ENV = "PI_RADAR_MANAGED_EXECUTION_GENERATION";
|
|
1204
|
+
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;
|
|
1205
|
+
function decodeRadarLaunchMarker(value) {
|
|
1206
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
1207
|
+
const record = value;
|
|
1208
|
+
if (Object.keys(record).sort().join(",") !== "execution_generation,launch_id,subject_id,version") {
|
|
1209
|
+
return void 0;
|
|
1210
|
+
}
|
|
1211
|
+
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) {
|
|
1212
|
+
return void 0;
|
|
1213
|
+
}
|
|
1214
|
+
return {
|
|
1215
|
+
version: RADAR_LAUNCH_MARKER_VERSION,
|
|
1216
|
+
subject_id: record.subject_id,
|
|
1217
|
+
launch_id: record.launch_id,
|
|
1218
|
+
execution_generation: record.execution_generation
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
function appendRadarLaunchMarker(appendEntry, marker) {
|
|
1222
|
+
const validated = decodeRadarLaunchMarker(marker);
|
|
1223
|
+
if (!validated) throw new Error("invalid Radar launch identity marker");
|
|
1224
|
+
appendEntry(RADAR_LAUNCH_MARKER_TYPE, validated);
|
|
1225
|
+
}
|
|
1226
|
+
function latestRadarLaunchMarker(branch) {
|
|
1227
|
+
let latest;
|
|
1228
|
+
for (const entry of branch) {
|
|
1229
|
+
if (entry.type === "custom" && entry.customType === RADAR_LAUNCH_MARKER_TYPE) {
|
|
1230
|
+
latest = entry;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
if (!latest) return { status: "absent" };
|
|
1234
|
+
const marker = decodeRadarLaunchMarker(latest.data);
|
|
1235
|
+
return marker ? { status: "valid", marker } : { status: "invalid" };
|
|
1236
|
+
}
|
|
1237
|
+
function radarLaunchMarkerFromEnvironment(env = process.env) {
|
|
1238
|
+
const rawGeneration = env[RADAR_MANAGED_GENERATION_ENV];
|
|
1239
|
+
if (env[RADAR_MANAGED_SUBJECT_ENV] === void 0 || env[RADAR_MANAGED_LAUNCH_ENV] === void 0 || rawGeneration === void 0 || !/^[1-9][0-9]*$/.test(rawGeneration)) {
|
|
1240
|
+
return void 0;
|
|
1241
|
+
}
|
|
1242
|
+
return decodeRadarLaunchMarker({
|
|
1243
|
+
version: RADAR_LAUNCH_MARKER_VERSION,
|
|
1244
|
+
subject_id: env[RADAR_MANAGED_SUBJECT_ENV],
|
|
1245
|
+
launch_id: env[RADAR_MANAGED_LAUNCH_ENV],
|
|
1246
|
+
execution_generation: Number(rawGeneration)
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
function activeRadarLaunchMarker(host) {
|
|
1250
|
+
if (!host.sessionManager) return { status: "absent" };
|
|
1251
|
+
return latestRadarLaunchMarker(host.sessionManager.getBranch());
|
|
1252
|
+
}
|
|
1253
|
+
function persistRadarLaunchMarker(host, env = process.env) {
|
|
1254
|
+
const marker = radarLaunchMarkerFromEnvironment(env);
|
|
1255
|
+
if (!marker) return "absent";
|
|
1256
|
+
if (!host.appendEntry || !host.sessionManager) return "unsupported";
|
|
1257
|
+
const current = latestRadarLaunchMarker(host.sessionManager.getBranch());
|
|
1258
|
+
if (current.status === "valid" && sameMarker(current.marker, marker)) return "unchanged";
|
|
1259
|
+
appendRadarLaunchMarker(host.appendEntry, marker);
|
|
1260
|
+
return "written";
|
|
1261
|
+
}
|
|
1262
|
+
function sameMarker(a, b) {
|
|
1263
|
+
return a.version === b.version && a.subject_id === b.subject_id && a.launch_id === b.launch_id && a.execution_generation === b.execution_generation;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// extension/radar-publication.ts
|
|
1176
1267
|
var RADAR_PUBLICATION_VERSION = 1;
|
|
1177
1268
|
var RADAR_LEASE_MS = 3e4;
|
|
1178
1269
|
var RADAR_HEARTBEAT_MS = 15e3;
|
|
@@ -1483,15 +1574,21 @@ function pendingAt(value, keys = PENDING_KEYS, codec = executionCodec) {
|
|
|
1483
1574
|
return { sequence, snapshot, lease_ms: lease, observed_at: observed };
|
|
1484
1575
|
}
|
|
1485
1576
|
var CONTEXT_PENDING_KEYS = "lease_ms,observed_at,sequence,session";
|
|
1577
|
+
var CONTEXT_PENDING_MARKED_KEYS = "launch_identity,lease_ms,observed_at,sequence,session";
|
|
1486
1578
|
var CONTEXT_ACCEPTED_KEYS = "lease_ms,observed_at,sent_at,sequence,session";
|
|
1579
|
+
var CONTEXT_ACCEPTED_MARKED_KEYS = "launch_identity,lease_ms,observed_at,sent_at,sequence,session";
|
|
1487
1580
|
function sessionAt(value) {
|
|
1488
1581
|
if (typeof value !== "object" || value === null) return void 0;
|
|
1489
1582
|
const found = value.session;
|
|
1490
1583
|
if (found === null) return null;
|
|
1491
1584
|
return typeof found === "string" ? found : void 0;
|
|
1492
1585
|
}
|
|
1493
|
-
function
|
|
1494
|
-
|
|
1586
|
+
function sameLaunchIdentity(a, b) {
|
|
1587
|
+
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;
|
|
1588
|
+
}
|
|
1589
|
+
function contextReportAt(value, keys, markedKeys) {
|
|
1590
|
+
const marked = exactKeys(value, markedKeys);
|
|
1591
|
+
if (!exactKeys(value, keys) && !marked) return void 0;
|
|
1495
1592
|
const sequence = numberAt2(value, "sequence");
|
|
1496
1593
|
const lease = numberAt2(value, "lease_ms");
|
|
1497
1594
|
const observed = textAt(value, "observed_at");
|
|
@@ -1499,7 +1596,15 @@ function contextReportAt(value, keys) {
|
|
|
1499
1596
|
if (sequence === void 0 || sequence < 1) return void 0;
|
|
1500
1597
|
if (lease === void 0 || observed === void 0 || session === void 0)
|
|
1501
1598
|
return void 0;
|
|
1502
|
-
|
|
1599
|
+
const marker = marked ? decodeRadarLaunchMarker(value.launch_identity) : void 0;
|
|
1600
|
+
if (marked && marker === void 0) return void 0;
|
|
1601
|
+
return {
|
|
1602
|
+
sequence,
|
|
1603
|
+
session,
|
|
1604
|
+
lease_ms: lease,
|
|
1605
|
+
observed_at: observed,
|
|
1606
|
+
...marker !== void 0 ? { launch_identity: marker } : {}
|
|
1607
|
+
};
|
|
1503
1608
|
}
|
|
1504
1609
|
function createChannelWriter(input) {
|
|
1505
1610
|
const { store, client, scheduler, recordName, channel, publisher } = input;
|
|
@@ -1738,6 +1843,7 @@ function createContextWriter(input) {
|
|
|
1738
1843
|
const { store, client, scheduler, recordName, publisher } = input;
|
|
1739
1844
|
let record = loadRecord();
|
|
1740
1845
|
let desired;
|
|
1846
|
+
let desiredMarker;
|
|
1741
1847
|
let running = false;
|
|
1742
1848
|
let again = false;
|
|
1743
1849
|
let recovering = false;
|
|
@@ -1763,9 +1869,17 @@ function createContextWriter(input) {
|
|
|
1763
1869
|
const generation = numberAt2(writer, "generation");
|
|
1764
1870
|
if (writer !== void 0 && (!exactKeys(writer, WRITER_KEYS) || !isUuid(handle) || generation === void 0))
|
|
1765
1871
|
return blank(agentId);
|
|
1766
|
-
const pending = contextReportAt(
|
|
1872
|
+
const pending = contextReportAt(
|
|
1873
|
+
recordAt2(value, "pending"),
|
|
1874
|
+
CONTEXT_PENDING_KEYS,
|
|
1875
|
+
CONTEXT_PENDING_MARKED_KEYS
|
|
1876
|
+
);
|
|
1767
1877
|
const acceptedValue = recordAt2(value, "accepted");
|
|
1768
|
-
const accepted = contextReportAt(
|
|
1878
|
+
const accepted = contextReportAt(
|
|
1879
|
+
acceptedValue,
|
|
1880
|
+
CONTEXT_ACCEPTED_KEYS,
|
|
1881
|
+
CONTEXT_ACCEPTED_MARKED_KEYS
|
|
1882
|
+
);
|
|
1769
1883
|
const sentAt = numberAt2(acceptedValue, "sent_at");
|
|
1770
1884
|
return {
|
|
1771
1885
|
version: RADAR_PUBLICATION_VERSION,
|
|
@@ -1825,7 +1939,10 @@ function createContextWriter(input) {
|
|
|
1825
1939
|
sequence: pending.sequence,
|
|
1826
1940
|
lease_ms: pending.lease_ms,
|
|
1827
1941
|
observed_at: pending.observed_at,
|
|
1828
|
-
context: {
|
|
1942
|
+
context: {
|
|
1943
|
+
session: pending.session,
|
|
1944
|
+
...pending.launch_identity !== void 0 ? { launch_identity: pending.launch_identity } : {}
|
|
1945
|
+
}
|
|
1829
1946
|
});
|
|
1830
1947
|
if (answer.ok === true) {
|
|
1831
1948
|
recovering = false;
|
|
@@ -1856,12 +1973,13 @@ function createContextWriter(input) {
|
|
|
1856
1973
|
return;
|
|
1857
1974
|
}
|
|
1858
1975
|
}
|
|
1859
|
-
async function stage(session) {
|
|
1976
|
+
async function stage(session, launchIdentity) {
|
|
1860
1977
|
const pending = {
|
|
1861
1978
|
sequence: record.sequence + 1,
|
|
1862
1979
|
session,
|
|
1863
1980
|
lease_ms: input.leaseMs,
|
|
1864
|
-
observed_at: new Date(scheduler.now()).toISOString()
|
|
1981
|
+
observed_at: new Date(scheduler.now()).toISOString(),
|
|
1982
|
+
...launchIdentity !== void 0 ? { launch_identity: launchIdentity } : {}
|
|
1865
1983
|
};
|
|
1866
1984
|
record.pending = pending;
|
|
1867
1985
|
save();
|
|
@@ -1876,11 +1994,11 @@ function createContextWriter(input) {
|
|
|
1876
1994
|
if (desired === void 0) return;
|
|
1877
1995
|
if (!await ensureSubject()) return;
|
|
1878
1996
|
if (fenced) return;
|
|
1879
|
-
if (record.writer === void 0 || record.accepted === void 0 || record.accepted.session !== desired) {
|
|
1880
|
-
await stage(desired);
|
|
1997
|
+
if (record.writer === void 0 || record.accepted === void 0 || record.accepted.session !== desired || !sameLaunchIdentity(record.accepted.launch_identity, desiredMarker)) {
|
|
1998
|
+
await stage(desired, desiredMarker);
|
|
1881
1999
|
return;
|
|
1882
2000
|
}
|
|
1883
|
-
if (heartbeatDue()) await stage(record.accepted.session);
|
|
2001
|
+
if (heartbeatDue()) await stage(record.accepted.session, desiredMarker);
|
|
1884
2002
|
}
|
|
1885
2003
|
async function drain() {
|
|
1886
2004
|
if (running || stopped || fenced) return;
|
|
@@ -1912,9 +2030,11 @@ function createContextWriter(input) {
|
|
|
1912
2030
|
void drain().catch(() => void 0);
|
|
1913
2031
|
}
|
|
1914
2032
|
return {
|
|
1915
|
-
update(session) {
|
|
1916
|
-
if (fenced || session === desired)
|
|
2033
|
+
update(session, launchIdentity) {
|
|
2034
|
+
if (fenced || session === desired && sameLaunchIdentity(desiredMarker, launchIdentity))
|
|
2035
|
+
return;
|
|
1917
2036
|
desired = session;
|
|
2037
|
+
desiredMarker = launchIdentity;
|
|
1918
2038
|
if (stopped) return;
|
|
1919
2039
|
kick();
|
|
1920
2040
|
},
|
|
@@ -2326,11 +2446,11 @@ function createRadarExecutionAdapter(pi, options) {
|
|
|
2326
2446
|
...lastOutcome !== void 0 ? { last_outcome: lastOutcome } : {}
|
|
2327
2447
|
};
|
|
2328
2448
|
}
|
|
2329
|
-
function reportSession(context) {
|
|
2449
|
+
function reportSession(context, launchIdentity) {
|
|
2330
2450
|
const session = context?.sessionManager?.getSessionId?.();
|
|
2331
2451
|
if (!isUuid(session)) return;
|
|
2332
2452
|
try {
|
|
2333
|
-
publication.context.update(session);
|
|
2453
|
+
publication.context.update(session, launchIdentity);
|
|
2334
2454
|
} catch {
|
|
2335
2455
|
}
|
|
2336
2456
|
}
|
|
@@ -2394,8 +2514,38 @@ function createRadarExecutionAdapter(pi, options) {
|
|
|
2394
2514
|
})
|
|
2395
2515
|
);
|
|
2396
2516
|
on("session_start", (_event, context) => {
|
|
2517
|
+
const sessionHost = {
|
|
2518
|
+
appendEntry: pi.appendEntry ? (customType, data) => pi.appendEntry?.(customType, data) : void 0,
|
|
2519
|
+
sessionManager: context?.sessionManager
|
|
2520
|
+
};
|
|
2521
|
+
let launchIdentity;
|
|
2522
|
+
try {
|
|
2523
|
+
const result = persistRadarLaunchMarker(
|
|
2524
|
+
sessionHost,
|
|
2525
|
+
options.markerEnvironment
|
|
2526
|
+
);
|
|
2527
|
+
if (result === "written" || result === "unchanged") {
|
|
2528
|
+
const expected = radarLaunchMarkerFromEnvironment(options.markerEnvironment);
|
|
2529
|
+
const activeMarker = activeRadarLaunchMarker(sessionHost);
|
|
2530
|
+
if (expected && activeMarker.status === "valid" && JSON.stringify(activeMarker.marker) === JSON.stringify(expected)) {
|
|
2531
|
+
launchIdentity = activeMarker.marker;
|
|
2532
|
+
} else {
|
|
2533
|
+
options.diagnostic?.("Radar launch marker was not visible on the active Pi session branch");
|
|
2534
|
+
}
|
|
2535
|
+
} else if (result === "unsupported") {
|
|
2536
|
+
options.diagnostic?.("Pi host does not support Radar launch identity session entries");
|
|
2537
|
+
} else if (options.markerEnvironment && [
|
|
2538
|
+
"PI_RADAR_MANAGED_SUBJECT_ID",
|
|
2539
|
+
"PI_RADAR_MANAGED_LAUNCH_ID",
|
|
2540
|
+
"PI_RADAR_MANAGED_EXECUTION_GENERATION"
|
|
2541
|
+
].some((key) => options.markerEnvironment?.[key] !== void 0)) {
|
|
2542
|
+
options.diagnostic?.("incomplete or invalid Radar launch identity environment");
|
|
2543
|
+
}
|
|
2544
|
+
} catch (error) {
|
|
2545
|
+
options.diagnostic?.(`could not persist Radar launch marker: ${String(error)}`);
|
|
2546
|
+
}
|
|
2397
2547
|
rootSession = true;
|
|
2398
|
-
reportSession(context);
|
|
2548
|
+
reportSession(context, launchIdentity);
|
|
2399
2549
|
publishFacts(context);
|
|
2400
2550
|
publication.reopen();
|
|
2401
2551
|
active = context?.isIdle?.() === false;
|
|
@@ -2582,7 +2732,8 @@ async function resolveRadarProcessLocation(client, env = process.env) {
|
|
|
2582
2732
|
const backend = ready.value.backend;
|
|
2583
2733
|
if (backend === void 0 || backend === "none")
|
|
2584
2734
|
return { state: "absent", reason: "the daemon serves no physical backend" };
|
|
2585
|
-
const
|
|
2735
|
+
const instance = ready.value.instance ?? void 0;
|
|
2736
|
+
const own = qualifiedOwnPane(env, backend, instance);
|
|
2586
2737
|
if ("absent" in own) return { state: "absent", reason: own.absent };
|
|
2587
2738
|
if ("pending" in own) return { state: "pending", reason: own.pending };
|
|
2588
2739
|
const paneId = own.pane;
|
|
@@ -2614,7 +2765,6 @@ async function resolveRadarProcessLocation(client, env = process.env) {
|
|
|
2614
2765
|
if ("ambiguous" in containment)
|
|
2615
2766
|
return { state: "pending", reason: containment.ambiguous };
|
|
2616
2767
|
const row = containment.pane;
|
|
2617
|
-
const instance = ready.value.instance;
|
|
2618
2768
|
return {
|
|
2619
2769
|
state: "resolved",
|
|
2620
2770
|
location: {
|
|
@@ -2895,11 +3045,56 @@ async function waitForChild(client, requestId, options) {
|
|
|
2895
3045
|
});
|
|
2896
3046
|
}
|
|
2897
3047
|
}
|
|
3048
|
+
|
|
3049
|
+
// extension/radar-runtime.ts
|
|
3050
|
+
var radar_runtime_exports = {};
|
|
3051
|
+
__export(radar_runtime_exports, {
|
|
3052
|
+
RADAR_RUNTIME_REQUEST: () => RADAR_RUNTIME_REQUEST,
|
|
3053
|
+
RADAR_RUNTIME_VERSION: () => RADAR_RUNTIME_VERSION,
|
|
3054
|
+
lazyRadarRuntime: () => lazyRadarRuntime,
|
|
3055
|
+
provideRadarRuntime: () => provideRadarRuntime,
|
|
3056
|
+
requestRadarRuntime: () => requestRadarRuntime
|
|
3057
|
+
});
|
|
3058
|
+
var RADAR_RUNTIME_REQUEST = "pi-radar/runtime-request";
|
|
3059
|
+
var RADAR_RUNTIME_VERSION = 1;
|
|
3060
|
+
function lazyRadarRuntime(start) {
|
|
3061
|
+
let pending;
|
|
3062
|
+
return {
|
|
3063
|
+
version: RADAR_RUNTIME_VERSION,
|
|
3064
|
+
start: () => pending ??= Promise.resolve().then(start)
|
|
3065
|
+
};
|
|
3066
|
+
}
|
|
3067
|
+
function provideRadarRuntime(events, handle) {
|
|
3068
|
+
return events.on(RADAR_RUNTIME_REQUEST, (value) => {
|
|
3069
|
+
if (!isRuntimeRequest(value)) return;
|
|
3070
|
+
value.provide(handle);
|
|
3071
|
+
});
|
|
3072
|
+
}
|
|
3073
|
+
function requestRadarRuntime(events) {
|
|
3074
|
+
const handles = [];
|
|
3075
|
+
events.emit(RADAR_RUNTIME_REQUEST, {
|
|
3076
|
+
version: RADAR_RUNTIME_VERSION,
|
|
3077
|
+
provide: (candidate) => {
|
|
3078
|
+
if (isRadarRuntimeHandle(candidate)) handles.push(candidate);
|
|
3079
|
+
}
|
|
3080
|
+
});
|
|
3081
|
+
if (handles.length === 1) return { ok: true, handle: handles[0] };
|
|
3082
|
+
return { ok: false, reason: handles.length === 0 ? "missing" : "duplicate" };
|
|
3083
|
+
}
|
|
3084
|
+
function isRuntimeRequest(value) {
|
|
3085
|
+
return typeof value === "object" && value !== null && value.version === RADAR_RUNTIME_VERSION && typeof value.provide === "function";
|
|
3086
|
+
}
|
|
3087
|
+
function isRadarRuntimeHandle(value) {
|
|
3088
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3089
|
+
const handle = value;
|
|
3090
|
+
return handle.version === RADAR_RUNTIME_VERSION && typeof handle.start === "function";
|
|
3091
|
+
}
|
|
2898
3092
|
export {
|
|
2899
3093
|
radar_client_exports as client,
|
|
2900
3094
|
radar_execution_exports as execution,
|
|
2901
3095
|
radar_inspection_exports as inspection,
|
|
2902
3096
|
radar_launch_exports as launch,
|
|
2903
3097
|
radar_location_exports as location,
|
|
2904
|
-
radar_publication_exports as publication
|
|
3098
|
+
radar_publication_exports as publication,
|
|
3099
|
+
radar_runtime_exports as runtime
|
|
2905
3100
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { createRadarClient } from "./radar-client.ts";
|
|
4
|
+
import {
|
|
5
|
+
createRadarExecutionAdapter,
|
|
6
|
+
type RadarExecutionEventApi,
|
|
7
|
+
} from "./radar-execution.ts";
|
|
8
|
+
import { resolveRadarProcessLocation } from "./radar-location.ts";
|
|
9
|
+
import {
|
|
10
|
+
lazyRadarRuntime,
|
|
11
|
+
provideRadarRuntime,
|
|
12
|
+
type RadarExtensionEventBus,
|
|
13
|
+
} from "./radar-runtime.ts";
|
|
14
|
+
|
|
15
|
+
/** Minimal structural Pi API used by the baseline extension. */
|
|
16
|
+
type PiRadarHost = RadarExecutionEventApi & {
|
|
17
|
+
events: RadarExtensionEventBus;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Register pi-radar as the one baseline registrar for this Pi process.
|
|
22
|
+
* Herdsman integrations request the same session-scoped runtime handle over
|
|
23
|
+
* `pi.events`; they add orchestration data without creating another publisher.
|
|
24
|
+
*/
|
|
25
|
+
export default function registerPiRadar(pi: PiRadarHost): void {
|
|
26
|
+
const client = createRadarClient();
|
|
27
|
+
const execution = createRadarExecutionAdapter(pi, {
|
|
28
|
+
client,
|
|
29
|
+
dataRoot: join(homedir(), ".pi", "agent", "pi-radar"),
|
|
30
|
+
location: () => resolveRadarProcessLocation(client),
|
|
31
|
+
});
|
|
32
|
+
const runtime = lazyRadarRuntime(async () => ({
|
|
33
|
+
publication: execution.publication,
|
|
34
|
+
execution,
|
|
35
|
+
}));
|
|
36
|
+
provideRadarRuntime(pi.events, runtime);
|
|
37
|
+
}
|
|
@@ -19,6 +19,27 @@ export interface TaskLaneStatus {
|
|
|
19
19
|
available: boolean;
|
|
20
20
|
error: string | null;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Managed owner/parent metadata accepted with a spawn intent. Domain validation
|
|
24
|
+
* and parent-binding checks remain in the daemon handler.
|
|
25
|
+
*/
|
|
26
|
+
export interface ManagedLaunchMetadataDeserialize {
|
|
27
|
+
label?: string | null;
|
|
28
|
+
owner?: string | null;
|
|
29
|
+
parent_subject_id?: string | null;
|
|
30
|
+
role?: string | null;
|
|
31
|
+
run?: string | null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A non-secret Pi-session breadcrumb issued by Radar. This is correlation
|
|
35
|
+
* evidence only and cannot establish lifecycle authority.
|
|
36
|
+
*/
|
|
37
|
+
export interface LaunchIdentityMarkerSerialize {
|
|
38
|
+
execution_generation: number;
|
|
39
|
+
launch_id: string;
|
|
40
|
+
subject_id: string;
|
|
41
|
+
version: number;
|
|
42
|
+
}
|
|
22
43
|
/**
|
|
23
44
|
* The agent's own process facts. Self-authored by any Pi session, with no owner
|
|
24
45
|
* or assignment, and kept separate from execution and assignment. Every field
|
|
@@ -4,7 +4,9 @@ import { createConnection } from "node:net";
|
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import type {
|
|
6
6
|
DaemonPingSerialize,
|
|
7
|
+
LaunchIdentityMarkerSerialize,
|
|
7
8
|
FactsSnapshotDeserialize,
|
|
9
|
+
ManagedLaunchMetadataDeserialize,
|
|
8
10
|
SpawnExitObservationSerialize,
|
|
9
11
|
} from "./generated-contracts.ts";
|
|
10
12
|
|
|
@@ -26,7 +28,7 @@ import type {
|
|
|
26
28
|
*/
|
|
27
29
|
export const RADAR_PROTOCOL_VERSION = 2;
|
|
28
30
|
/** The minimum daemon wire revision this client understands. */
|
|
29
|
-
export const RADAR_MINIMUM_REVISION =
|
|
31
|
+
export const RADAR_MINIMUM_REVISION = 3;
|
|
30
32
|
/** Public daemon contracts this client may preflight. */
|
|
31
33
|
export const RADAR_REGISTRY_CONTRACT = "registry.v1";
|
|
32
34
|
export const RADAR_REGISTRY_FACTS_CONTRACT = "registry.facts.v1";
|
|
@@ -194,6 +196,7 @@ export type RadarWriterBinding = {
|
|
|
194
196
|
*/
|
|
195
197
|
export type RadarContextValue = {
|
|
196
198
|
session: string | null;
|
|
199
|
+
launch_identity?: LaunchIdentityMarkerSerialize;
|
|
197
200
|
};
|
|
198
201
|
|
|
199
202
|
/**
|
|
@@ -296,6 +299,10 @@ export type RadarSpawnEdge = {
|
|
|
296
299
|
* no exit was observed.
|
|
297
300
|
*/
|
|
298
301
|
export type RadarSpawnExitObservation = SpawnExitObservationSerialize;
|
|
302
|
+
export type RadarLaunchIdentityMarker = LaunchIdentityMarkerSerialize;
|
|
303
|
+
|
|
304
|
+
/** Durable Radar-owned metadata accepted with a managed launch intent. */
|
|
305
|
+
export type RadarManagedLaunchMetadata = ManagedLaunchMetadataDeserialize;
|
|
299
306
|
|
|
300
307
|
/**
|
|
301
308
|
* One edge with what *that read* learned about the location it recorded.
|
|
@@ -651,6 +658,11 @@ function unresolvedOperation(requestId: string, detail: string): RadarResult<nev
|
|
|
651
658
|
}
|
|
652
659
|
|
|
653
660
|
export const RADAR_SPAWN_TOKEN_ENV = "PI_RADAR_SPAWN_TOKEN";
|
|
661
|
+
const RADAR_MANAGED_IDENTITY_ENV = new Set([
|
|
662
|
+
"PI_RADAR_MANAGED_SUBJECT_ID",
|
|
663
|
+
"PI_RADAR_MANAGED_LAUNCH_ID",
|
|
664
|
+
"PI_RADAR_MANAGED_EXECUTION_GENERATION",
|
|
665
|
+
]);
|
|
654
666
|
|
|
655
667
|
/**
|
|
656
668
|
* A spawn this process may still have caused.
|
|
@@ -857,6 +869,7 @@ function spawnRequestShape(request: {
|
|
|
857
869
|
argv: string[];
|
|
858
870
|
cwd?: string;
|
|
859
871
|
env?: Record<string, string>;
|
|
872
|
+
managed?: RadarManagedLaunchMetadata;
|
|
860
873
|
}): string | undefined {
|
|
861
874
|
if (!isUuid(request.request_id))
|
|
862
875
|
return "spawn requires a canonical request UUID";
|
|
@@ -875,9 +888,26 @@ function spawnRequestShape(request: {
|
|
|
875
888
|
for (const [name, value] of env) {
|
|
876
889
|
if (name === RADAR_SPAWN_TOKEN_ENV)
|
|
877
890
|
return `spawn env cannot set the reserved ${RADAR_SPAWN_TOKEN_ENV}`;
|
|
891
|
+
if (RADAR_MANAGED_IDENTITY_ENV.has(name))
|
|
892
|
+
return `spawn env cannot set Radar-issued identity variable ${name}`;
|
|
878
893
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || /\p{Cc}/u.test(value))
|
|
879
894
|
return `spawn env contains an invalid name or value: ${name}`;
|
|
880
895
|
}
|
|
896
|
+
if (request.managed !== undefined) {
|
|
897
|
+
if (typeof request.managed !== "object" || request.managed === null)
|
|
898
|
+
return "managed launch metadata must be an object";
|
|
899
|
+
for (const [name, value] of Object.entries(request.managed)) {
|
|
900
|
+
if (!["owner", "run", "label", "role", "parent_subject_id"].includes(name))
|
|
901
|
+
return `unknown managed launch metadata field: ${name}`;
|
|
902
|
+
if (value != null && (typeof value !== "string" || value.trim() === "" || new TextEncoder().encode(value).length > 256 || /\p{Cc}/u.test(value)))
|
|
903
|
+
return `managed launch ${name} must be non-empty, control-free, and at most 256 UTF-8 bytes`;
|
|
904
|
+
}
|
|
905
|
+
if (
|
|
906
|
+
request.managed.parent_subject_id != null &&
|
|
907
|
+
!isUuid(request.managed.parent_subject_id)
|
|
908
|
+
)
|
|
909
|
+
return "managed parent_subject_id must be a canonical UUID";
|
|
910
|
+
}
|
|
881
911
|
return undefined;
|
|
882
912
|
}
|
|
883
913
|
|
|
@@ -978,6 +1008,7 @@ export type RadarClient = {
|
|
|
978
1008
|
argv: string[];
|
|
979
1009
|
cwd?: string;
|
|
980
1010
|
env?: Record<string, string>;
|
|
1011
|
+
managed?: RadarManagedLaunchMetadata;
|
|
981
1012
|
}): Promise<RadarResult<RadarSpawnEdge>>;
|
|
982
1013
|
spawnGet(request_id: string): Promise<RadarResult<RadarSpawnTopology>>;
|
|
983
1014
|
/** The registered child's process verdict, read from its immutable claim. */
|
|
@@ -1513,6 +1544,9 @@ export function createRadarClient(options: RadarClientOptions = {}): RadarClient
|
|
|
1513
1544
|
...(carriesContext && request.env !== undefined
|
|
1514
1545
|
? { env: request.env }
|
|
1515
1546
|
: {}),
|
|
1547
|
+
...(request.managed !== undefined
|
|
1548
|
+
? { managed: request.managed }
|
|
1549
|
+
: {}),
|
|
1516
1550
|
},
|
|
1517
1551
|
requester,
|
|
1518
1552
|
},
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
isUuid,
|
|
4
4
|
type RadarScheduler,
|
|
5
5
|
type RadarSnapshot,
|
|
6
|
+
type RadarLaunchIdentityMarker,
|
|
6
7
|
} from "./radar-client.ts";
|
|
7
8
|
import type { RadarLocationResolution } from "./radar-location.ts";
|
|
8
9
|
import type { RadarFactsSnapshot } from "./radar-client.ts";
|
|
@@ -19,6 +20,11 @@ import {
|
|
|
19
20
|
type RadarPublication,
|
|
20
21
|
type RadarChannelWriter,
|
|
21
22
|
} from "./radar-publication.ts";
|
|
23
|
+
import {
|
|
24
|
+
activeRadarLaunchMarker,
|
|
25
|
+
persistRadarLaunchMarker,
|
|
26
|
+
radarLaunchMarkerFromEnvironment,
|
|
27
|
+
} from "./radar-session-marker.ts";
|
|
22
28
|
|
|
23
29
|
/**
|
|
24
30
|
* The minimal Pi surface this adapter uses. It is declared structurally so the
|
|
@@ -26,6 +32,7 @@ import {
|
|
|
26
32
|
* be lifted into its own extension without touching this module.
|
|
27
33
|
*/
|
|
28
34
|
export type RadarExecutionEventApi = {
|
|
35
|
+
appendEntry?: (customType: string, data?: unknown) => void;
|
|
29
36
|
on(name: string, handler: (event: any, context: any) => unknown): unknown;
|
|
30
37
|
events?: {
|
|
31
38
|
on(name: string, handler: (event: any) => unknown): unknown;
|
|
@@ -63,6 +70,10 @@ export type RadarExecutionOptions = {
|
|
|
63
70
|
* where no such event exists.
|
|
64
71
|
*/
|
|
65
72
|
questionnaireEvent?: string;
|
|
73
|
+
/** Best-effort diagnostic for durable session marker failures. */
|
|
74
|
+
diagnostic?: (message: string) => void;
|
|
75
|
+
/** Injectable only for tests; production reads the daemon-issued process env. */
|
|
76
|
+
markerEnvironment?: Record<string, string | undefined>;
|
|
66
77
|
};
|
|
67
78
|
|
|
68
79
|
export type RadarExecutionAdapter = {
|
|
@@ -258,11 +269,14 @@ export function createRadarExecutionAdapter(
|
|
|
258
269
|
* canonical UUID is left unreported: absent context is unknown, and publishing
|
|
259
270
|
* the explicit null would claim there is no session at all.
|
|
260
271
|
*/
|
|
261
|
-
function reportSession(
|
|
272
|
+
function reportSession(
|
|
273
|
+
context: any,
|
|
274
|
+
launchIdentity?: RadarLaunchIdentityMarker,
|
|
275
|
+
): void {
|
|
262
276
|
const session = context?.sessionManager?.getSessionId?.();
|
|
263
277
|
if (!isUuid(session)) return;
|
|
264
278
|
try {
|
|
265
|
-
publication.context.update(session);
|
|
279
|
+
publication.context.update(session, launchIdentity);
|
|
266
280
|
} catch {
|
|
267
281
|
// Optional publication must not interfere with Pi's lifecycle handler.
|
|
268
282
|
}
|
|
@@ -346,10 +360,49 @@ export function createRadarExecutionAdapter(
|
|
|
346
360
|
);
|
|
347
361
|
|
|
348
362
|
on("session_start", (_event, context) => {
|
|
363
|
+
const sessionHost = {
|
|
364
|
+
appendEntry: pi.appendEntry
|
|
365
|
+
? (customType: string, data?: unknown) => pi.appendEntry?.(customType, data)
|
|
366
|
+
: undefined,
|
|
367
|
+
sessionManager: context?.sessionManager,
|
|
368
|
+
};
|
|
369
|
+
let launchIdentity: RadarLaunchIdentityMarker | undefined;
|
|
370
|
+
try {
|
|
371
|
+
const result = persistRadarLaunchMarker(
|
|
372
|
+
sessionHost,
|
|
373
|
+
options.markerEnvironment,
|
|
374
|
+
);
|
|
375
|
+
if (result === "written" || result === "unchanged") {
|
|
376
|
+
const expected = radarLaunchMarkerFromEnvironment(options.markerEnvironment);
|
|
377
|
+
const activeMarker = activeRadarLaunchMarker(sessionHost);
|
|
378
|
+
if (
|
|
379
|
+
expected &&
|
|
380
|
+
activeMarker.status === "valid" &&
|
|
381
|
+
JSON.stringify(activeMarker.marker) === JSON.stringify(expected)
|
|
382
|
+
) {
|
|
383
|
+
launchIdentity = activeMarker.marker;
|
|
384
|
+
} else {
|
|
385
|
+
options.diagnostic?.("Radar launch marker was not visible on the active Pi session branch");
|
|
386
|
+
}
|
|
387
|
+
} else if (result === "unsupported") {
|
|
388
|
+
options.diagnostic?.("Pi host does not support Radar launch identity session entries");
|
|
389
|
+
} else if (
|
|
390
|
+
options.markerEnvironment &&
|
|
391
|
+
[
|
|
392
|
+
"PI_RADAR_MANAGED_SUBJECT_ID",
|
|
393
|
+
"PI_RADAR_MANAGED_LAUNCH_ID",
|
|
394
|
+
"PI_RADAR_MANAGED_EXECUTION_GENERATION",
|
|
395
|
+
].some((key) => options.markerEnvironment?.[key] !== undefined)
|
|
396
|
+
) {
|
|
397
|
+
options.diagnostic?.("incomplete or invalid Radar launch identity environment");
|
|
398
|
+
}
|
|
399
|
+
} catch (error) {
|
|
400
|
+
options.diagnostic?.(`could not persist Radar launch marker: ${String(error)}`);
|
|
401
|
+
}
|
|
349
402
|
rootSession = true;
|
|
350
403
|
// The session comes first: re-arming the publication must renew the session
|
|
351
404
|
// this process is in now, never the one a previous session left behind.
|
|
352
|
-
reportSession(context);
|
|
405
|
+
reportSession(context, launchIdentity);
|
|
353
406
|
// Facts follow the root flag, so they are published only after it is set.
|
|
354
407
|
publishFacts(context);
|
|
355
408
|
// A process can replace or resume its session without a new process, so the
|
|
@@ -153,7 +153,8 @@ export async function resolveRadarProcessLocation(
|
|
|
153
153
|
const backend = ready.value.backend;
|
|
154
154
|
if (backend === undefined || backend === "none")
|
|
155
155
|
return { state: "absent", reason: "the daemon serves no physical backend" };
|
|
156
|
-
const
|
|
156
|
+
const instance = ready.value.instance ?? undefined;
|
|
157
|
+
const own = qualifiedOwnPane(env, backend, instance);
|
|
157
158
|
if ("absent" in own) return { state: "absent", reason: own.absent };
|
|
158
159
|
if ("pending" in own) return { state: "pending", reason: own.pending };
|
|
159
160
|
const paneId = own.pane;
|
|
@@ -186,7 +187,6 @@ export async function resolveRadarProcessLocation(
|
|
|
186
187
|
if ("ambiguous" in containment)
|
|
187
188
|
return { state: "pending", reason: containment.ambiguous };
|
|
188
189
|
const row = containment.pane;
|
|
189
|
-
const instance = ready.value.instance;
|
|
190
190
|
return {
|
|
191
191
|
state: "resolved",
|
|
192
192
|
location: {
|