pi-radar 0.5.0 → 0.5.1

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,7 +19,7 @@ The baseline reports session presence through one flat request. A fresh `session
19
19
 
20
20
  ## Daemon contract
21
21
 
22
- The client speaks protocol 2 and requires wire revision 3 or newer for its existing lifecycle operations. Revision 3 made lifecycle `subject_id` a stable logical identity and exposed the exact current `execution_id` and `u32` generation separately. Handoff methods additionally require the independently negotiated `communication.handoff.v1` contract introduced at wire revision 4; 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.
22
+ The client speaks protocol 2 and requires wire revision 4. Revision 3 made lifecycle `subject_id` a stable logical identity and exposed the exact current `execution_id` and `u32` generation separately; revision 4 added Pi presence (`agent.presence`/`agent.release`), which is how a session acquires its subject, so a revision-3 daemon would accept the handshake and then leave every Pi session unmanaged. 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.
23
23
 
24
24
  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.
25
25
 
@@ -31,10 +31,38 @@ The handoff API stores immutable bounded content separately from the recipient r
31
31
 
32
32
  ## Generated protocol contracts
33
33
 
34
- The selected Rust wire DTOs are the source for the draft 2020-12 schemas in `schema/public/v1/`, shipped and exported under `pi-radar/schema/`. Serialize and deserialize schemas are separate because Serde defaults, null handling and omission differ by direction. The SDK's ping, handoff inbox, publication-parameter, immutable handoff, receipt, managed-launch-metadata input, facts-input, spawn-exit, and observation request/snapshot/response types are generated into `extension/generated-contracts.ts`; do not edit that file by hand.
34
+ The selected Rust wire DTOs are the source for the draft 2020-12 schemas in `schema/public/v1/`, shipped and exported under `pi-radar/schema/`. They are this package's contract of record: a daemon change lands as a schema regeneration, and the method-to-schema index lives in `docs/agent-registration.md` in the Radar repository. Serialize and deserialize schemas are separate because Serde defaults, null handling and omission differ by direction. The SDK's ping, Pi presence report/answer/release, handoff inbox, publication-parameter, immutable handoff, receipt, managed-launch-metadata input, facts-input, spawn-exit, and observation request/snapshot/response types are generated into `extension/generated-contracts.ts`; do not edit that file by hand.
35
+
36
+ The public process identifier is `registration_id` — a clean rename from the pre-0.4 `agent_id` with no alias, because a mixed fleet would otherwise read a missing field as an absent identity. Durable agent identity is `subject_id`, which survives process replacement; a Pi session's subject is granted by `agent.presence` and released by `agent.release`. Breaking wire changes bump this package's minor version, so a consumer must move its pin and restart the daemon in one step.
35
37
 
36
38
  From the repository checkout, run `npm run generate:contracts` in this directory to regenerate schemas and TypeScript declarations. `npm test` checks the generated artifacts are current, typechecks the public SDK aliases, validates canonical wire fixtures and integer bounds against the schemas, and runs the package tests. Generation uses the locked Schemars crate plus pinned `json-schema-to-typescript` and TypeScript development dependencies; it adds no runtime package dependency.
37
39
 
38
40
  Lifecycle execution generations are bounded to `u32` and remain exactly representable as JavaScript numbers. The initial schema set leaves operation sequences and event cursors unchanged; their long-horizon representation remains a separate wire-compatibility decision.
39
41
 
40
42
  Run `npm pack` to build the bundled runtime entrypoints before creating the tarball.
43
+
44
+ ## Releasing
45
+
46
+ Publishing is one tag push and it runs in CI; no npm token is stored anywhere.
47
+ `.github/workflows/publish-pi-radar.yml` publishes on a `pi-radar-vX.Y.Z` tag
48
+ push, authenticating with npm trusted publishing (OIDC) and emitting provenance.
49
+
50
+ One-time setup, on npmjs.com under the `pi-radar` package's settings: add a
51
+ trusted publisher of type GitHub Actions naming owner `Shrub24`, repository
52
+ `agent-radar`, workflow filename `publish-pi-radar.yml`, and no environment.
53
+ The workflow sets no `environment:`, so a non-empty value here stops it from
54
+ publishing; the same is true of an `NPM_TOKEN` secret, which trusted publishing
55
+ does not use.
56
+
57
+ To release:
58
+
59
+ 1. Bump `version` in `package.json` and `package-lock.json` on `main` and push.
60
+ Semantics: a breaking wire change bumps the minor, additive changes the patch.
61
+ 2. Tag that commit and push the tag: `git tag pi-radar-vX.Y.Z && git push origin pi-radar-vX.Y.Z`.
62
+
63
+ The workflow refuses to publish unless the tag's version matches
64
+ `package.json`, the tagged commit is on `main`, and that version is not already
65
+ on npm; then it installs from the lockfile, runs `npm test`, preflights the
66
+ tarball with `npm pack --dry-run` (which builds `dist/` through `prepack`), and
67
+ publishes. A failed run is re-runnable by fixing the cause and re-pushing the
68
+ tag; an already-published version fails by design rather than republishing.
@@ -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 = 3;
11
+ var RADAR_MINIMUM_REVISION = 4;
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";
@@ -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 = 3;
7
+ var RADAR_MINIMUM_REVISION = 4;
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";
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ import { lstatSync } from "node:fs";
39
39
  import { createConnection } from "node:net";
40
40
  import { dirname, join } from "node:path";
41
41
  var RADAR_PROTOCOL_VERSION = 2;
42
- var RADAR_MINIMUM_REVISION = 3;
42
+ var RADAR_MINIMUM_REVISION = 4;
43
43
  var RADAR_REGISTRY_CONTRACT = "registry.v1";
44
44
  var RADAR_REGISTRY_FACTS_CONTRACT = "registry.facts.v1";
45
45
  var RADAR_SPAWN_CONTRACT = "spawn.v1";
@@ -30,8 +30,13 @@ import type {
30
30
  * served under the old name.
31
31
  */
32
32
  export const RADAR_PROTOCOL_VERSION = 2;
33
- /** The minimum daemon wire revision this client understands. */
34
- export const RADAR_MINIMUM_REVISION = 3;
33
+ /**
34
+ * The minimum daemon wire revision this client understands. Revision 4 is where
35
+ * Pi presence and exact session release arrived; a revision-3 daemon would pass
36
+ * this handshake and then refuse every presence report, leaving a Pi session
37
+ * silently unmanaged instead of telling the operator to restart the daemon.
38
+ */
39
+ export const RADAR_MINIMUM_REVISION = 4;
35
40
  /** Public daemon contracts this client may preflight. */
36
41
  export const RADAR_REGISTRY_CONTRACT = "registry.v1";
37
42
  export const RADAR_REGISTRY_FACTS_CONTRACT = "registry.facts.v1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-radar",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
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",
@@ -1,82 +0,0 @@
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
- };