@noopolis/daimon 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +54 -10
  2. package/dist/core/siblingBuildFreshness.d.ts +21 -0
  3. package/dist/core/siblingBuildFreshness.d.ts.map +1 -0
  4. package/dist/core/siblingBuildFreshness.js +25 -0
  5. package/dist/core/types.d.ts +10 -1
  6. package/dist/core/types.d.ts.map +1 -1
  7. package/dist/examples/exampleCausalId.d.ts +3 -0
  8. package/dist/examples/exampleCausalId.d.ts.map +1 -0
  9. package/dist/examples/exampleCausalId.js +8 -0
  10. package/dist/examples/jungian-play-org.js +3 -2
  11. package/dist/examples/jungian-triad-org.js +3 -2
  12. package/dist/examples/jungianPlayAgent.d.ts +1 -1
  13. package/dist/examples/jungianPlayAgent.d.ts.map +1 -1
  14. package/dist/examples/jungianPlayAgent.js +1 -1
  15. package/dist/examples/jungianProfiles.d.ts +1 -1
  16. package/dist/examples/jungianProfiles.d.ts.map +1 -1
  17. package/dist/examples/jungianTriadProfiles.d.ts +1 -1
  18. package/dist/examples/jungianTriadProfiles.d.ts.map +1 -1
  19. package/dist/examples/pi-agent.js +3 -2
  20. package/dist/examples/pi-memory-org.js +6 -5
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -0
  24. package/dist/mcp/toolServer.d.ts +14 -0
  25. package/dist/mcp/toolServer.d.ts.map +1 -0
  26. package/dist/mcp/toolServer.js +105 -0
  27. package/dist/observability/causalEvents.d.ts +118 -0
  28. package/dist/observability/causalEvents.d.ts.map +1 -0
  29. package/dist/observability/causalEvents.js +135 -0
  30. package/dist/observability/controlCausal.d.ts +117 -0
  31. package/dist/observability/controlCausal.d.ts.map +1 -0
  32. package/dist/observability/controlCausal.js +107 -0
  33. package/dist/observability/emitCausalFixture.d.ts +25 -0
  34. package/dist/observability/emitCausalFixture.d.ts.map +1 -0
  35. package/dist/observability/emitCausalFixture.js +98 -0
  36. package/dist/observability/index.d.ts +2 -0
  37. package/dist/observability/index.d.ts.map +1 -1
  38. package/dist/observability/index.js +2 -0
  39. package/dist/pi/cliSession.d.ts +46 -0
  40. package/dist/pi/cliSession.d.ts.map +1 -0
  41. package/dist/pi/cliSession.js +224 -0
  42. package/dist/pi/index.d.ts +5 -0
  43. package/dist/pi/index.d.ts.map +1 -1
  44. package/dist/pi/index.js +5 -0
  45. package/dist/pi/memoryTools.d.ts +55 -1
  46. package/dist/pi/memoryTools.d.ts.map +1 -1
  47. package/dist/pi/memoryTools.js +144 -24
  48. package/dist/pi/modelRegistry.d.ts +13 -0
  49. package/dist/pi/modelRegistry.d.ts.map +1 -0
  50. package/dist/pi/modelRegistry.js +19 -0
  51. package/dist/pi/piAgentHandle.d.ts +45 -0
  52. package/dist/pi/piAgentHandle.d.ts.map +1 -0
  53. package/dist/pi/piAgentHandle.js +324 -0
  54. package/dist/pi/piAgentWakeSupport.d.ts +68 -0
  55. package/dist/pi/piAgentWakeSupport.d.ts.map +1 -0
  56. package/dist/pi/piAgentWakeSupport.js +147 -0
  57. package/dist/pi/piHarness.d.ts +30 -4
  58. package/dist/pi/piHarness.d.ts.map +1 -1
  59. package/dist/pi/piHarness.js +97 -255
  60. package/dist/pi/prompts.d.ts +9 -0
  61. package/dist/pi/prompts.d.ts.map +1 -0
  62. package/dist/pi/prompts.js +39 -0
  63. package/dist/pi/rawTrainingCapture.d.ts +59 -0
  64. package/dist/pi/rawTrainingCapture.d.ts.map +1 -0
  65. package/dist/pi/rawTrainingCapture.js +175 -0
  66. package/dist/pi/turnCausal.d.ts +73 -0
  67. package/dist/pi/turnCausal.d.ts.map +1 -0
  68. package/dist/pi/turnCausal.js +75 -0
  69. package/dist/pi/turnTrace.d.ts +128 -0
  70. package/dist/pi/turnTrace.d.ts.map +1 -0
  71. package/dist/pi/turnTrace.js +141 -0
  72. package/dist/pi/wakeAcceptance.d.ts +49 -0
  73. package/dist/pi/wakeAcceptance.d.ts.map +1 -0
  74. package/dist/pi/wakeAcceptance.js +281 -0
  75. package/dist/pi/wakeAcceptanceFs.d.ts +44 -0
  76. package/dist/pi/wakeAcceptanceFs.d.ts.map +1 -0
  77. package/dist/pi/wakeAcceptanceFs.js +318 -0
  78. package/dist/pi/wakeAcceptanceSchema.d.ts +75 -0
  79. package/dist/pi/wakeAcceptanceSchema.d.ts.map +1 -0
  80. package/dist/pi/wakeAcceptanceSchema.js +265 -0
  81. package/dist/pi/wakeModes.d.ts +9 -0
  82. package/dist/pi/wakeModes.d.ts.map +1 -0
  83. package/dist/pi/wakeModes.js +19 -0
  84. package/dist/pi/worldNudge.d.ts +22 -0
  85. package/dist/pi/worldNudge.d.ts.map +1 -0
  86. package/dist/pi/worldNudge.js +62 -0
  87. package/dist/pi/worldToolProtocol.d.ts +13 -0
  88. package/dist/pi/worldToolProtocol.d.ts.map +1 -0
  89. package/dist/pi/worldToolProtocol.js +49 -0
  90. package/dist/pi/worldTools.d.ts +32 -0
  91. package/dist/pi/worldTools.d.ts.map +1 -0
  92. package/dist/pi/worldTools.js +373 -0
  93. package/dist/pi/worldTrajectory.d.ts +52 -0
  94. package/dist/pi/worldTrajectory.d.ts.map +1 -0
  95. package/dist/pi/worldTrajectory.js +131 -0
  96. package/package.json +10 -13
  97. package/dist/examples/mixed-engine-org.d.ts +0 -2
  98. package/dist/examples/mixed-engine-org.d.ts.map +0 -1
  99. package/dist/examples/mixed-engine-org.js +0 -351
  100. package/dist/examples/mixedEngineCli.d.ts +0 -15
  101. package/dist/examples/mixedEngineCli.d.ts.map +0 -1
  102. package/dist/examples/mixedEngineCli.js +0 -174
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Daimon's own copy of the `noopolis.causal-event.v1` wire envelope. Field-
3
+ * for-field the canonical shape in `specs/causal-event.v1.schema.json` and
4
+ * `specs/CAUSAL.md` (root repo) — daimon is an independent repo and does not
5
+ * import that schema, it only conforms to it. The wire JSON emitted by
6
+ * `appendCausalEvent` is the actual contract; this type is local sugar over
7
+ * it.
8
+ */
9
+ export declare const CAUSAL_EVENT_VERSION: "noopolis.causal-event.v1";
10
+ export type CausalEventSystem = "simfile" | "moltnet" | "mneme" | "daimon";
11
+ export interface CausalEventEmitter {
12
+ system: CausalEventSystem;
13
+ stream_id: string;
14
+ seq: number;
15
+ }
16
+ export interface CausalEvent<TPayload = Record<string, unknown>> {
17
+ cause_event_ids: string[];
18
+ emitter: CausalEventEmitter;
19
+ event_id: string;
20
+ payload: TPayload;
21
+ principal_id: string;
22
+ recorded_at: string;
23
+ run_id: string;
24
+ type: string;
25
+ version: typeof CAUSAL_EVENT_VERSION;
26
+ }
27
+ /** Payload for `turn.input.submitted`, stamped just before a Pi session prompt call. */
28
+ export interface TurnInputSubmittedPayload extends Record<string, unknown> {
29
+ input_content_sha256: string;
30
+ input_message_ids: string[];
31
+ prompt_sha256: string;
32
+ turn_id: string;
33
+ }
34
+ /** Payload for `turn.output.completed`, stamped once a Pi turn finishes successfully. */
35
+ export interface TurnOutputCompletedPayload extends Record<string, unknown> {
36
+ output_sha256: string;
37
+ turn_id: string;
38
+ }
39
+ export declare const TURN_INPUT_SUBMITTED_TYPE: "turn.input.submitted";
40
+ export declare const TURN_OUTPUT_COMPLETED_TYPE: "turn.output.completed";
41
+ /** Name of the environment variable every Noopolis authority reads `run_id` from. Never model output. */
42
+ export declare const NOOPOLIS_RUN_ID_ENV = "NOOPOLIS_RUN_ID";
43
+ /**
44
+ * Resolves `run_id` from the `NOOPOLIS_RUN_ID` environment variable, per
45
+ * `specs/CAUSAL.md`. Never derived from a WakeEvent, model output, or any
46
+ * other in-turn data. A causal event cannot be emitted without a real run id.
47
+ */
48
+ export declare const resolveRunId: (env?: NodeJS.ProcessEnv) => string;
49
+ export declare const sha256Hex: (value: string) => string;
50
+ /** Deterministic, non-model-derived event id for a turn's `turn.input.submitted` record. */
51
+ export declare const turnInputSubmittedEventId: (turnId: string) => string;
52
+ /** Deterministic, non-model-derived event id for a turn's `turn.output.completed` record. */
53
+ export declare const turnOutputCompletedEventId: (turnId: string) => string;
54
+ /**
55
+ * The `cause_event_ids` an outbound Moltnet reply for `turnId` should carry.
56
+ * Pure and deterministic from `turn_id` alone — the harness owns this value,
57
+ * never the model. Daimon does not construct Moltnet `SendMessageRequest`
58
+ * values itself (see repo AGENTS.md: Moltnet wiring belongs to the caller,
59
+ * not this package), so this helper is what a caller sending the reply on
60
+ * Daimon's behalf attaches to that request's `cause_event_ids`.
61
+ */
62
+ export declare const replyCauseEventIds: (turnId: string) => string[];
63
+ /**
64
+ * Allocates the next contiguous seq number for `(run_id, stream_id)`,
65
+ * persisted under `runtimeHome/telemetry/causal.seq.json`. Daimon runs at
66
+ * most one wake at a time per agent (`PiAgentHandle.wakeQueue` serializes
67
+ * them), so read-modify-write here does not need extra locking.
68
+ */
69
+ export declare const nextCausalSeq: (input: {
70
+ runId: string;
71
+ runtimeHomePath: string;
72
+ streamId: string;
73
+ }) => Promise<number>;
74
+ /** Appends one CausalEvent record as a line of `runtimeHome/telemetry/causal.jsonl`. */
75
+ export declare const appendCausalEvent: (runtimeHomePath: string, event: CausalEvent) => Promise<void>;
76
+ export interface EmitCausalEventInput<TPayload extends Record<string, unknown>> {
77
+ agentId: string;
78
+ causeEventIds: string[];
79
+ eventId: string;
80
+ payload: TPayload;
81
+ principalId: string;
82
+ runId: string;
83
+ runtimeHomePath: string;
84
+ type: string;
85
+ }
86
+ /**
87
+ * Stamps and appends one causal event for this agent's stream
88
+ * (`agent:<agentId>`). `run_id` and `principal_id` are always caller-
89
+ * supplied values (never read from `payload` or model output) — see
90
+ * `piHarness.ts`, which is the only caller and always passes the resolved
91
+ * `NOOPOLIS_RUN_ID` and the authenticated agent identity.
92
+ */
93
+ export declare const emitCausalEvent: <TPayload extends Record<string, unknown>>(input: EmitCausalEventInput<TPayload>) => Promise<CausalEvent<TPayload>>;
94
+ export interface EmitTurnInputSubmittedInput {
95
+ agentId: string;
96
+ causeEventIds: string[];
97
+ inputContentSha256: string;
98
+ inputMessageIds: string[];
99
+ principalId: string;
100
+ promptSha256: string;
101
+ runId: string;
102
+ runtimeHomePath: string;
103
+ turnId: string;
104
+ }
105
+ /** Stamps `turn.input.submitted` for one Pi turn, right before the engine prompt call. */
106
+ export declare const emitTurnInputSubmitted: (input: EmitTurnInputSubmittedInput) => Promise<CausalEvent<TurnInputSubmittedPayload>>;
107
+ export interface EmitTurnOutputCompletedInput {
108
+ agentId: string;
109
+ causeEventIds: string[];
110
+ outputSha256: string;
111
+ principalId: string;
112
+ runId: string;
113
+ runtimeHomePath: string;
114
+ turnId: string;
115
+ }
116
+ /** Stamps `turn.output.completed` once a Pi turn finishes successfully. */
117
+ export declare const emitTurnOutputCompleted: (input: EmitTurnOutputCompletedInput) => Promise<CausalEvent<TurnOutputCompletedPayload>>;
118
+ //# sourceMappingURL=causalEvents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"causalEvents.d.ts","sourceRoot":"","sources":["../../src/observability/causalEvents.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,135 @@
1
+ import { createHash } from "node:crypto";
2
+ import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ /**
5
+ * Daimon's own copy of the `noopolis.causal-event.v1` wire envelope. Field-
6
+ * for-field the canonical shape in `specs/causal-event.v1.schema.json` and
7
+ * `specs/CAUSAL.md` (root repo) — daimon is an independent repo and does not
8
+ * import that schema, it only conforms to it. The wire JSON emitted by
9
+ * `appendCausalEvent` is the actual contract; this type is local sugar over
10
+ * it.
11
+ */
12
+ export const CAUSAL_EVENT_VERSION = "noopolis.causal-event.v1";
13
+ export const TURN_INPUT_SUBMITTED_TYPE = "turn.input.submitted";
14
+ export const TURN_OUTPUT_COMPLETED_TYPE = "turn.output.completed";
15
+ /** Name of the environment variable every Noopolis authority reads `run_id` from. Never model output. */
16
+ export const NOOPOLIS_RUN_ID_ENV = "NOOPOLIS_RUN_ID";
17
+ /**
18
+ * Resolves `run_id` from the `NOOPOLIS_RUN_ID` environment variable, per
19
+ * `specs/CAUSAL.md`. Never derived from a WakeEvent, model output, or any
20
+ * other in-turn data. A causal event cannot be emitted without a real run id.
21
+ */
22
+ export const resolveRunId = (env = process.env) => {
23
+ const value = env[NOOPOLIS_RUN_ID_ENV];
24
+ if (typeof value !== "string" || value.trim().length === 0) {
25
+ throw new Error(`${NOOPOLIS_RUN_ID_ENV} must be set to a non-blank value`);
26
+ }
27
+ return value.trim();
28
+ };
29
+ export const sha256Hex = (value) => createHash("sha256").update(value, "utf8").digest("hex");
30
+ /** Deterministic, non-model-derived event id for a turn's `turn.input.submitted` record. */
31
+ export const turnInputSubmittedEventId = (turnId) => `daimon:${turnId}:turn.input.submitted`;
32
+ /** Deterministic, non-model-derived event id for a turn's `turn.output.completed` record. */
33
+ export const turnOutputCompletedEventId = (turnId) => `daimon:${turnId}:turn.output.completed`;
34
+ /**
35
+ * The `cause_event_ids` an outbound Moltnet reply for `turnId` should carry.
36
+ * Pure and deterministic from `turn_id` alone — the harness owns this value,
37
+ * never the model. Daimon does not construct Moltnet `SendMessageRequest`
38
+ * values itself (see repo AGENTS.md: Moltnet wiring belongs to the caller,
39
+ * not this package), so this helper is what a caller sending the reply on
40
+ * Daimon's behalf attaches to that request's `cause_event_ids`.
41
+ */
42
+ export const replyCauseEventIds = (turnId) => [turnOutputCompletedEventId(turnId)];
43
+ const telemetryDir = (runtimeHomePath) => path.join(runtimeHomePath, "telemetry");
44
+ const seqFilePath = (runtimeHomePath) => path.join(telemetryDir(runtimeHomePath), "causal.seq.json");
45
+ const jsonlFilePath = (runtimeHomePath) => path.join(telemetryDir(runtimeHomePath), "causal.jsonl");
46
+ const readSeqStore = async (runtimeHomePath) => {
47
+ try {
48
+ const raw = await readFile(seqFilePath(runtimeHomePath), "utf8");
49
+ return JSON.parse(raw);
50
+ }
51
+ catch (error) {
52
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
53
+ return {};
54
+ }
55
+ throw error;
56
+ }
57
+ };
58
+ const writeSeqStore = async (runtimeHomePath, store) => {
59
+ await mkdir(telemetryDir(runtimeHomePath), { recursive: true });
60
+ await writeFile(seqFilePath(runtimeHomePath), `${JSON.stringify(store, null, 2)}\n`, "utf8");
61
+ };
62
+ /**
63
+ * Allocates the next contiguous seq number for `(run_id, stream_id)`,
64
+ * persisted under `runtimeHome/telemetry/causal.seq.json`. Daimon runs at
65
+ * most one wake at a time per agent (`PiAgentHandle.wakeQueue` serializes
66
+ * them), so read-modify-write here does not need extra locking.
67
+ */
68
+ export const nextCausalSeq = async (input) => {
69
+ const store = await readSeqStore(input.runtimeHomePath);
70
+ const forRun = store[input.runId] ?? {};
71
+ const next = (forRun[input.streamId] ?? 0) + 1;
72
+ forRun[input.streamId] = next;
73
+ store[input.runId] = forRun;
74
+ await writeSeqStore(input.runtimeHomePath, store);
75
+ return next;
76
+ };
77
+ /** Appends one CausalEvent record as a line of `runtimeHome/telemetry/causal.jsonl`. */
78
+ export const appendCausalEvent = async (runtimeHomePath, event) => {
79
+ await mkdir(telemetryDir(runtimeHomePath), { recursive: true });
80
+ await appendFile(jsonlFilePath(runtimeHomePath), `${JSON.stringify(event)}\n`, "utf8");
81
+ };
82
+ /**
83
+ * Stamps and appends one causal event for this agent's stream
84
+ * (`agent:<agentId>`). `run_id` and `principal_id` are always caller-
85
+ * supplied values (never read from `payload` or model output) — see
86
+ * `piHarness.ts`, which is the only caller and always passes the resolved
87
+ * `NOOPOLIS_RUN_ID` and the authenticated agent identity.
88
+ */
89
+ export const emitCausalEvent = async (input) => {
90
+ const streamId = `agent:${input.agentId}`;
91
+ const seq = await nextCausalSeq({ runId: input.runId, runtimeHomePath: input.runtimeHomePath, streamId });
92
+ const event = {
93
+ cause_event_ids: [...input.causeEventIds],
94
+ emitter: { system: "daimon", stream_id: streamId, seq },
95
+ event_id: input.eventId,
96
+ payload: input.payload,
97
+ principal_id: input.principalId,
98
+ recorded_at: new Date().toISOString(),
99
+ run_id: input.runId,
100
+ type: input.type,
101
+ version: CAUSAL_EVENT_VERSION
102
+ };
103
+ await appendCausalEvent(input.runtimeHomePath, event);
104
+ return event;
105
+ };
106
+ /** Stamps `turn.input.submitted` for one Pi turn, right before the engine prompt call. */
107
+ export const emitTurnInputSubmitted = (input) => emitCausalEvent({
108
+ agentId: input.agentId,
109
+ causeEventIds: input.causeEventIds,
110
+ eventId: turnInputSubmittedEventId(input.turnId),
111
+ payload: {
112
+ input_content_sha256: input.inputContentSha256,
113
+ input_message_ids: [...input.inputMessageIds],
114
+ prompt_sha256: input.promptSha256,
115
+ turn_id: input.turnId
116
+ },
117
+ principalId: input.principalId,
118
+ runId: input.runId,
119
+ runtimeHomePath: input.runtimeHomePath,
120
+ type: TURN_INPUT_SUBMITTED_TYPE
121
+ });
122
+ /** Stamps `turn.output.completed` once a Pi turn finishes successfully. */
123
+ export const emitTurnOutputCompleted = (input) => emitCausalEvent({
124
+ agentId: input.agentId,
125
+ causeEventIds: input.causeEventIds,
126
+ eventId: turnOutputCompletedEventId(input.turnId),
127
+ payload: {
128
+ output_sha256: input.outputSha256,
129
+ turn_id: input.turnId
130
+ },
131
+ principalId: input.principalId,
132
+ runId: input.runId,
133
+ runtimeHomePath: input.runtimeHomePath,
134
+ type: TURN_OUTPUT_COMPLETED_TYPE
135
+ });
@@ -0,0 +1,117 @@
1
+ import { type CausalEvent } from "./causalEvents.js";
2
+ /**
3
+ * Control-wake causal emitters for root's two wake-acceptance surfaces
4
+ * (`src/runtime/pi/appControlSource.ts`, root repo, out of scope here):
5
+ * the operator-only control endpoint and the Moltnet loopback delivery
6
+ * endpoint (`/agents/:slug/wake`). Both stamp `control.wake.accepted` —
7
+ * enforcement point #3 in `specs/CAUSAL.md` §"Enforcement points" — but with
8
+ * different `principal_id` authorities, per the B62 fix (Option B): the
9
+ * operator endpoint stamps `operator:<name>` (`emitControlWakeAccepted`),
10
+ * the delivery endpoint stamps the fixed `system:moltnet` authority
11
+ * (`emitDeliveryWakeAccepted`), never a caller-supplied `from`/agent field.
12
+ * `control.wake.denied` (`emitControlWakeDenied`) stays operator-only: the
13
+ * delivery endpoint has no bearer-token deny path to stamp. Root's generated
14
+ * app calls these directly; this file has no knowledge of HTTP, tokens, or
15
+ * the request shape, only the causal envelope contract.
16
+ */
17
+ export declare const CONTROL_WAKE_ACCEPTED_TYPE: "control.wake.accepted";
18
+ export declare const CONTROL_WAKE_DENIED_TYPE: "control.wake.denied";
19
+ /** Payload for `control.wake.accepted`. */
20
+ export interface ControlWakeAcceptedPayload extends Record<string, unknown> {
21
+ target_agent_id: string;
22
+ wake_kind: string;
23
+ }
24
+ /** Payload for `control.wake.denied`. */
25
+ export interface ControlWakeDeniedPayload extends Record<string, unknown> {
26
+ reason: string;
27
+ target_agent_id: string;
28
+ }
29
+ /**
30
+ * Payload for a Moltnet-delivered `control.wake.accepted` (same event type
31
+ * as the operator path — it is still an accepted wake; `principal_id` is
32
+ * what distinguishes authority). Adds `delivered_by` so downstream readers
33
+ * can tell a delivery-stamped event from an operator-stamped one without
34
+ * inspecting `principal_id`.
35
+ */
36
+ export interface DeliveryWakeAcceptedPayload extends ControlWakeAcceptedPayload {
37
+ delivered_by: "moltnet";
38
+ }
39
+ /**
40
+ * Principal grammar per `specs/CAUSAL.md` §3 (`^(agent|operator|system):.+`):
41
+ * an authenticated operator identity, e.g. `operator:control`. Callers must
42
+ * pass the identity behind the verified bearer token, never a value read
43
+ * from the request body or model output.
44
+ */
45
+ export declare const operatorPrincipalId: (operatorName: string) => string;
46
+ /**
47
+ * Fixed principal for wakes accepted through the Moltnet loopback delivery
48
+ * endpoint (`/agents/:slug/wake`): the delivering authority itself, never an
49
+ * identity derived from a caller-supplied `from`/agent field on the request.
50
+ */
51
+ export declare const DELIVERY_PRINCIPAL_ID: "system:moltnet";
52
+ /** Deterministic, non-model-derived event id for a control wake acceptance. */
53
+ export declare const controlWakeAcceptedEventId: (requestId: string) => string;
54
+ /** Deterministic, non-model-derived event id for a control wake denial. */
55
+ export declare const controlWakeDeniedEventId: (requestId: string) => string;
56
+ /**
57
+ * Deterministic, non-model-derived event id for a Moltnet-delivered wake
58
+ * acceptance. Distinct from `controlWakeAcceptedEventId` (own `.delivery.`
59
+ * segment) so the two paths never collide even if a future caller reused a
60
+ * `requestId` across both endpoints for the same target.
61
+ */
62
+ export declare const deliveryWakeAcceptedEventId: (requestId: string) => string;
63
+ export interface EmitControlWakeAcceptedInput {
64
+ causeEventIds?: string[];
65
+ operatorName: string;
66
+ requestId: string;
67
+ runId?: string;
68
+ runtimeHomePath: string;
69
+ targetAgentId: string;
70
+ wakeKind: string;
71
+ }
72
+ /**
73
+ * Stamps `control.wake.accepted` once root's operator-control endpoint has
74
+ * verified the bearer token and is about to wake `targetAgentId`.
75
+ * `principal_id` is always `operator:<operatorName>` (the authenticated
76
+ * operator behind the verified token), never derived from the request body.
77
+ * `run_id` defaults to `resolveRunId()` like every other daimon emitter.
78
+ */
79
+ export declare const emitControlWakeAccepted: (input: EmitControlWakeAcceptedInput) => Promise<CausalEvent<ControlWakeAcceptedPayload>>;
80
+ export interface EmitDeliveryWakeAcceptedInput {
81
+ causeEventIds?: string[];
82
+ requestId: string;
83
+ runId?: string;
84
+ runtimeHomePath: string;
85
+ targetAgentId: string;
86
+ wakeKind: string;
87
+ }
88
+ /**
89
+ * Stamps `control.wake.accepted` once root's Moltnet loopback delivery
90
+ * endpoint (`/agents/:slug/wake`) is about to wake `targetAgentId` on behalf
91
+ * of an inter-agent message. `principal_id` is always the fixed
92
+ * `DELIVERY_PRINCIPAL_ID` (`system:moltnet`) — the delivering authority —
93
+ * never derived from a caller-supplied `from`/agent field, and never
94
+ * `operator:<name>` (that principal is reserved for the operator-only
95
+ * control endpoint; see `emitControlWakeAccepted`). `run_id` defaults to
96
+ * `resolveRunId()` like every other daimon emitter.
97
+ */
98
+ export declare const emitDeliveryWakeAccepted: (input: EmitDeliveryWakeAcceptedInput) => Promise<CausalEvent<DeliveryWakeAcceptedPayload>>;
99
+ export interface EmitControlWakeDeniedInput {
100
+ causeEventIds?: string[];
101
+ operatorName: string;
102
+ reason: string;
103
+ requestId: string;
104
+ runId?: string;
105
+ runtimeHomePath: string;
106
+ targetAgentId: string;
107
+ }
108
+ /**
109
+ * Stamps `control.wake.denied` when root's operator-control endpoint rejects
110
+ * a wake request over a missing or invalid bearer token. Deny paths must
111
+ * never drop silently (see `specs/CAUSAL.md` enforcement point #3 / T5):
112
+ * every 401 the endpoint returns should carry exactly one of these. As with
113
+ * `emitControlWakeAccepted`, `principal_id` is always the authenticated
114
+ * operator identity, never a value read from the request.
115
+ */
116
+ export declare const emitControlWakeDenied: (input: EmitControlWakeDeniedInput) => Promise<CausalEvent<ControlWakeDeniedPayload>>;
117
+ //# sourceMappingURL=controlCausal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controlCausal.d.ts","sourceRoot":"","sources":["../../src/observability/controlCausal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEpF;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,0BAA0B,EAAG,uBAAgC,CAAC;AAC3E,eAAO,MAAM,wBAAwB,EAAG,qBAA8B,CAAC;AAEvE,2CAA2C;AAC3C,MAAM,WAAW,0BAA2B,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACzE,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,yCAAyC;AACzC,MAAM,WAAW,wBAAyB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvE,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,2BAA4B,SAAQ,0BAA0B;IAC7E,YAAY,EAAE,SAAS,CAAC;CACzB;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,cAAc,MAAM,KAAG,MAAoC,CAAC;AAEhG;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAG,gBAAyB,CAAC;AAE/D,+EAA+E;AAC/E,eAAO,MAAM,0BAA0B,GAAI,WAAW,MAAM,KAAG,MAClB,CAAC;AAE9C,2EAA2E;AAC3E,eAAO,MAAM,wBAAwB,GAAI,WAAW,MAAM,KAAG,MAAmD,CAAC;AAEjH;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,GAAI,WAAW,MAAM,KAAG,MAClB,CAAC;AAE/C,MAAM,WAAW,4BAA4B;IAC3C,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GAClC,OAAO,4BAA4B,KAClC,OAAO,CAAC,WAAW,CAAC,0BAA0B,CAAC,CAa9C,CAAC;AAEL,MAAM,WAAW,6BAA6B;IAC5C,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,GACnC,OAAO,6BAA6B,KACnC,OAAO,CAAC,WAAW,CAAC,2BAA2B,CAAC,CAc/C,CAAC;AAEL,MAAM,WAAW,0BAA0B;IACzC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAChC,OAAO,0BAA0B,KAChC,OAAO,CAAC,WAAW,CAAC,wBAAwB,CAAC,CAa5C,CAAC"}
@@ -0,0 +1,107 @@
1
+ import { emitCausalEvent, resolveRunId } from "./causalEvents.js";
2
+ /**
3
+ * Control-wake causal emitters for root's two wake-acceptance surfaces
4
+ * (`src/runtime/pi/appControlSource.ts`, root repo, out of scope here):
5
+ * the operator-only control endpoint and the Moltnet loopback delivery
6
+ * endpoint (`/agents/:slug/wake`). Both stamp `control.wake.accepted` —
7
+ * enforcement point #3 in `specs/CAUSAL.md` §"Enforcement points" — but with
8
+ * different `principal_id` authorities, per the B62 fix (Option B): the
9
+ * operator endpoint stamps `operator:<name>` (`emitControlWakeAccepted`),
10
+ * the delivery endpoint stamps the fixed `system:moltnet` authority
11
+ * (`emitDeliveryWakeAccepted`), never a caller-supplied `from`/agent field.
12
+ * `control.wake.denied` (`emitControlWakeDenied`) stays operator-only: the
13
+ * delivery endpoint has no bearer-token deny path to stamp. Root's generated
14
+ * app calls these directly; this file has no knowledge of HTTP, tokens, or
15
+ * the request shape, only the causal envelope contract.
16
+ */
17
+ export const CONTROL_WAKE_ACCEPTED_TYPE = "control.wake.accepted";
18
+ export const CONTROL_WAKE_DENIED_TYPE = "control.wake.denied";
19
+ /**
20
+ * Principal grammar per `specs/CAUSAL.md` §3 (`^(agent|operator|system):.+`):
21
+ * an authenticated operator identity, e.g. `operator:control`. Callers must
22
+ * pass the identity behind the verified bearer token, never a value read
23
+ * from the request body or model output.
24
+ */
25
+ export const operatorPrincipalId = (operatorName) => `operator:${operatorName}`;
26
+ /**
27
+ * Fixed principal for wakes accepted through the Moltnet loopback delivery
28
+ * endpoint (`/agents/:slug/wake`): the delivering authority itself, never an
29
+ * identity derived from a caller-supplied `from`/agent field on the request.
30
+ */
31
+ export const DELIVERY_PRINCIPAL_ID = "system:moltnet";
32
+ /** Deterministic, non-model-derived event id for a control wake acceptance. */
33
+ export const controlWakeAcceptedEventId = (requestId) => `daimon:${requestId}:control.wake.accepted`;
34
+ /** Deterministic, non-model-derived event id for a control wake denial. */
35
+ export const controlWakeDeniedEventId = (requestId) => `daimon:${requestId}:control.wake.denied`;
36
+ /**
37
+ * Deterministic, non-model-derived event id for a Moltnet-delivered wake
38
+ * acceptance. Distinct from `controlWakeAcceptedEventId` (own `.delivery.`
39
+ * segment) so the two paths never collide even if a future caller reused a
40
+ * `requestId` across both endpoints for the same target.
41
+ */
42
+ export const deliveryWakeAcceptedEventId = (requestId) => `daimon:${requestId}:delivery.wake.accepted`;
43
+ /**
44
+ * Stamps `control.wake.accepted` once root's operator-control endpoint has
45
+ * verified the bearer token and is about to wake `targetAgentId`.
46
+ * `principal_id` is always `operator:<operatorName>` (the authenticated
47
+ * operator behind the verified token), never derived from the request body.
48
+ * `run_id` defaults to `resolveRunId()` like every other daimon emitter.
49
+ */
50
+ export const emitControlWakeAccepted = (input) => emitCausalEvent({
51
+ agentId: input.targetAgentId,
52
+ causeEventIds: input.causeEventIds ?? [],
53
+ eventId: controlWakeAcceptedEventId(input.requestId),
54
+ payload: {
55
+ target_agent_id: input.targetAgentId,
56
+ wake_kind: input.wakeKind
57
+ },
58
+ principalId: operatorPrincipalId(input.operatorName),
59
+ runId: input.runId ?? resolveRunId(),
60
+ runtimeHomePath: input.runtimeHomePath,
61
+ type: CONTROL_WAKE_ACCEPTED_TYPE
62
+ });
63
+ /**
64
+ * Stamps `control.wake.accepted` once root's Moltnet loopback delivery
65
+ * endpoint (`/agents/:slug/wake`) is about to wake `targetAgentId` on behalf
66
+ * of an inter-agent message. `principal_id` is always the fixed
67
+ * `DELIVERY_PRINCIPAL_ID` (`system:moltnet`) — the delivering authority —
68
+ * never derived from a caller-supplied `from`/agent field, and never
69
+ * `operator:<name>` (that principal is reserved for the operator-only
70
+ * control endpoint; see `emitControlWakeAccepted`). `run_id` defaults to
71
+ * `resolveRunId()` like every other daimon emitter.
72
+ */
73
+ export const emitDeliveryWakeAccepted = (input) => emitCausalEvent({
74
+ agentId: input.targetAgentId,
75
+ causeEventIds: input.causeEventIds ?? [],
76
+ eventId: deliveryWakeAcceptedEventId(input.requestId),
77
+ payload: {
78
+ delivered_by: "moltnet",
79
+ target_agent_id: input.targetAgentId,
80
+ wake_kind: input.wakeKind
81
+ },
82
+ principalId: DELIVERY_PRINCIPAL_ID,
83
+ runId: input.runId ?? resolveRunId(),
84
+ runtimeHomePath: input.runtimeHomePath,
85
+ type: CONTROL_WAKE_ACCEPTED_TYPE
86
+ });
87
+ /**
88
+ * Stamps `control.wake.denied` when root's operator-control endpoint rejects
89
+ * a wake request over a missing or invalid bearer token. Deny paths must
90
+ * never drop silently (see `specs/CAUSAL.md` enforcement point #3 / T5):
91
+ * every 401 the endpoint returns should carry exactly one of these. As with
92
+ * `emitControlWakeAccepted`, `principal_id` is always the authenticated
93
+ * operator identity, never a value read from the request.
94
+ */
95
+ export const emitControlWakeDenied = (input) => emitCausalEvent({
96
+ agentId: input.targetAgentId,
97
+ causeEventIds: input.causeEventIds ?? [],
98
+ eventId: controlWakeDeniedEventId(input.requestId),
99
+ payload: {
100
+ reason: input.reason,
101
+ target_agent_id: input.targetAgentId
102
+ },
103
+ principalId: operatorPrincipalId(input.operatorName),
104
+ runId: input.runId ?? resolveRunId(),
105
+ runtimeHomePath: input.runtimeHomePath,
106
+ type: CONTROL_WAKE_DENIED_TYPE
107
+ });
@@ -0,0 +1,25 @@
1
+ import { type CausalEvent } from "./causalEvents.js";
2
+ export interface CausalFixtureResult {
3
+ events: [CausalEvent, CausalEvent];
4
+ jsonlPath: string;
5
+ }
6
+ export interface RunCausalFixtureOptions {
7
+ /** Overrides the scratch runtime home; defaults to a fixed `.runtime/` path keyed on `spoof`. */
8
+ runtimeHomePath?: string;
9
+ /**
10
+ * Adversarial mode: the fixture's "wake text" (standing in for untrusted
11
+ * model/request input) embeds a forged identity claim, but the emitted
12
+ * `principal_id` on both events must remain the authenticated
13
+ * `agent:<agentId>` regardless — never the claimed identity. Mirrors the
14
+ * spoof invariant already covered live in `piHarnessCausal.test.ts`
15
+ * ("model output cannot set principal_id...").
16
+ */
17
+ spoof?: boolean;
18
+ }
19
+ /**
20
+ * Runs the fixture and returns the two stamped events plus the jsonl path,
21
+ * without touching `process.stdout`/`process.exitCode` — the CLI entry
22
+ * point below wraps this for `npm run emit-causal-fixture`.
23
+ */
24
+ export declare const runCausalFixture: (options?: RunCausalFixtureOptions) => Promise<CausalFixtureResult>;
25
+ //# sourceMappingURL=emitCausalFixture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emitCausalFixture.d.ts","sourceRoot":"","sources":["../../src/observability/emitCausalFixture.ts"],"names":[],"mappings":"AAIA,OAAO,EAA4E,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA0B/H,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,iGAAiG;IACjG,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAID;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAU,UAAS,uBAA4B,KAAG,OAAO,CAAC,mBAAmB,CA0DzG,CAAC"}
@@ -0,0 +1,98 @@
1
+ import { mkdir, rm } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { emitTurnInputSubmitted, emitTurnOutputCompleted, resolveRunId, sha256Hex } from "./causalEvents.js";
5
+ /**
6
+ * Standalone fixture emitter, run via `npm run emit-causal-fixture`
7
+ * (or `npm run emit-causal-fixture:spoof` for the adversarial mode below).
8
+ *
9
+ * Stamps one synthetic `turn.input.submitted` -> `turn.output.completed`
10
+ * chain into a scratch runtime home under `.runtime/causal-fixture[-spoof]/`,
11
+ * using the same `causalEvents.ts` functions `turnCausal.ts` uses for real
12
+ * turns. This is a fixture, not a live engine run: no Pi session, no mneme
13
+ * recall.
14
+ *
15
+ * `principal_id` here is stamped as `agent:<agentId>` directly (rather than
16
+ * importing `turnCausal.ts`'s `agentPrincipalId` helper) because this file
17
+ * lives under `src/observability/`, which stays engine-neutral and must not
18
+ * import `src/pi/` implementation details (see this folder's `AGENTS.md`).
19
+ * The value is the same grammar (`specs/CAUSAL.md` §3) either way.
20
+ *
21
+ * Exact invocation contract for a future cross-repo conformance harness
22
+ * (B92/B62, out of scope here) is not defined yet, so this script picks the
23
+ * simplest reasonable convention: write the fixture under a fixed relative
24
+ * path and print its absolute location as the last line of stdout.
25
+ */
26
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
+ const daimonRoot = path.resolve(__dirname, "../..");
28
+ const SPOOFED_AGENT_ID = "attacker-agent";
29
+ /**
30
+ * Runs the fixture and returns the two stamped events plus the jsonl path,
31
+ * without touching `process.stdout`/`process.exitCode` — the CLI entry
32
+ * point below wraps this for `npm run emit-causal-fixture`.
33
+ */
34
+ export const runCausalFixture = async (options = {}) => {
35
+ const spoof = options.spoof ?? false;
36
+ const runtimeHomePath = options.runtimeHomePath ?? path.join(daimonRoot, ".runtime", spoof ? "causal-fixture-spoof" : "causal-fixture");
37
+ await rm(runtimeHomePath, { recursive: true, force: true });
38
+ await mkdir(runtimeHomePath, { recursive: true });
39
+ const agentId = "fixture-agent";
40
+ const principalId = `agent:${agentId}`;
41
+ const turnId = "fixture-turn-1";
42
+ const runId = resolveRunId();
43
+ const inputText = spoof
44
+ ? `Fixture wake text for the causal conformance harness. ` +
45
+ `SPOOF CLAIM (must be ignored): ${JSON.stringify({ from: SPOOFED_AGENT_ID, principal_id: `agent:${SPOOFED_AGENT_ID}` })}`
46
+ : "Fixture wake text for the causal conformance harness.";
47
+ const promptText = "Fixture prompt text.";
48
+ const outputText = spoof
49
+ ? `Fixture reply text. SPOOF CLAIM (must be ignored): ${JSON.stringify({ principal_id: `agent:${SPOOFED_AGENT_ID}` })}`
50
+ : "Fixture reply text.";
51
+ const inputSubmitted = await emitTurnInputSubmitted({
52
+ agentId,
53
+ causeEventIds: [turnId],
54
+ inputContentSha256: sha256Hex(inputText),
55
+ inputMessageIds: [turnId],
56
+ principalId,
57
+ promptSha256: sha256Hex(promptText),
58
+ runId,
59
+ runtimeHomePath,
60
+ turnId
61
+ });
62
+ const outputCompleted = await emitTurnOutputCompleted({
63
+ agentId,
64
+ causeEventIds: [inputSubmitted.event_id],
65
+ outputSha256: sha256Hex(outputText),
66
+ principalId,
67
+ runId,
68
+ runtimeHomePath,
69
+ turnId
70
+ });
71
+ // The whole point of spoof mode: the forged claim above must never reach
72
+ // the stamped envelope. Fail loudly here rather than let a future refactor
73
+ // silently regress this invariant.
74
+ for (const event of [inputSubmitted, outputCompleted]) {
75
+ if (event.principal_id !== principalId) {
76
+ throw new Error(`causal fixture invariant violated: principal_id was "${event.principal_id}", expected "${principalId}"`);
77
+ }
78
+ }
79
+ return {
80
+ events: [inputSubmitted, outputCompleted],
81
+ jsonlPath: path.join(runtimeHomePath, "telemetry", "causal.jsonl")
82
+ };
83
+ };
84
+ const isMainModule = () => {
85
+ const invoked = process.argv[1] ? path.resolve(process.argv[1]) : undefined;
86
+ return invoked !== undefined && invoked === path.resolve(fileURLToPath(import.meta.url));
87
+ };
88
+ if (isMainModule()) {
89
+ const spoof = process.argv.includes("--spoof");
90
+ runCausalFixture({ spoof })
91
+ .then((result) => {
92
+ console.log(result.jsonlPath);
93
+ })
94
+ .catch((error) => {
95
+ console.error(error instanceof Error ? error.stack ?? error.message : String(error));
96
+ process.exitCode = 1;
97
+ });
98
+ }
@@ -1,2 +1,4 @@
1
+ export * from "./causalEvents.js";
2
+ export * from "./controlCausal.js";
1
3
  export * from "./orgObserver.js";
2
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/observability/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/observability/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC"}
@@ -1 +1,3 @@
1
+ export * from "./causalEvents.js";
2
+ export * from "./controlCausal.js";
1
3
  export * from "./orgObserver.js";