@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.
- package/README.md +54 -10
- package/dist/core/siblingBuildFreshness.d.ts +21 -0
- package/dist/core/siblingBuildFreshness.d.ts.map +1 -0
- package/dist/core/siblingBuildFreshness.js +25 -0
- package/dist/core/types.d.ts +10 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/examples/exampleCausalId.d.ts +3 -0
- package/dist/examples/exampleCausalId.d.ts.map +1 -0
- package/dist/examples/exampleCausalId.js +8 -0
- package/dist/examples/jungian-play-org.js +3 -2
- package/dist/examples/jungian-triad-org.js +3 -2
- package/dist/examples/jungianPlayAgent.d.ts +1 -1
- package/dist/examples/jungianPlayAgent.d.ts.map +1 -1
- package/dist/examples/jungianPlayAgent.js +1 -1
- package/dist/examples/jungianProfiles.d.ts +1 -1
- package/dist/examples/jungianProfiles.d.ts.map +1 -1
- package/dist/examples/jungianTriadProfiles.d.ts +1 -1
- package/dist/examples/jungianTriadProfiles.d.ts.map +1 -1
- package/dist/examples/pi-agent.js +3 -2
- package/dist/examples/pi-memory-org.js +6 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/mcp/toolServer.d.ts +14 -0
- package/dist/mcp/toolServer.d.ts.map +1 -0
- package/dist/mcp/toolServer.js +105 -0
- package/dist/observability/causalEvents.d.ts +118 -0
- package/dist/observability/causalEvents.d.ts.map +1 -0
- package/dist/observability/causalEvents.js +135 -0
- package/dist/observability/controlCausal.d.ts +117 -0
- package/dist/observability/controlCausal.d.ts.map +1 -0
- package/dist/observability/controlCausal.js +107 -0
- package/dist/observability/emitCausalFixture.d.ts +25 -0
- package/dist/observability/emitCausalFixture.d.ts.map +1 -0
- package/dist/observability/emitCausalFixture.js +98 -0
- package/dist/observability/index.d.ts +2 -0
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +2 -0
- package/dist/pi/cliSession.d.ts +46 -0
- package/dist/pi/cliSession.d.ts.map +1 -0
- package/dist/pi/cliSession.js +224 -0
- package/dist/pi/index.d.ts +5 -0
- package/dist/pi/index.d.ts.map +1 -1
- package/dist/pi/index.js +5 -0
- package/dist/pi/memoryTools.d.ts +55 -1
- package/dist/pi/memoryTools.d.ts.map +1 -1
- package/dist/pi/memoryTools.js +144 -24
- package/dist/pi/modelRegistry.d.ts +13 -0
- package/dist/pi/modelRegistry.d.ts.map +1 -0
- package/dist/pi/modelRegistry.js +19 -0
- package/dist/pi/piAgentHandle.d.ts +45 -0
- package/dist/pi/piAgentHandle.d.ts.map +1 -0
- package/dist/pi/piAgentHandle.js +324 -0
- package/dist/pi/piAgentWakeSupport.d.ts +68 -0
- package/dist/pi/piAgentWakeSupport.d.ts.map +1 -0
- package/dist/pi/piAgentWakeSupport.js +147 -0
- package/dist/pi/piHarness.d.ts +30 -4
- package/dist/pi/piHarness.d.ts.map +1 -1
- package/dist/pi/piHarness.js +97 -255
- package/dist/pi/prompts.d.ts +9 -0
- package/dist/pi/prompts.d.ts.map +1 -0
- package/dist/pi/prompts.js +39 -0
- package/dist/pi/rawTrainingCapture.d.ts +59 -0
- package/dist/pi/rawTrainingCapture.d.ts.map +1 -0
- package/dist/pi/rawTrainingCapture.js +175 -0
- package/dist/pi/turnCausal.d.ts +73 -0
- package/dist/pi/turnCausal.d.ts.map +1 -0
- package/dist/pi/turnCausal.js +75 -0
- package/dist/pi/turnTrace.d.ts +128 -0
- package/dist/pi/turnTrace.d.ts.map +1 -0
- package/dist/pi/turnTrace.js +141 -0
- package/dist/pi/wakeAcceptance.d.ts +49 -0
- package/dist/pi/wakeAcceptance.d.ts.map +1 -0
- package/dist/pi/wakeAcceptance.js +281 -0
- package/dist/pi/wakeAcceptanceFs.d.ts +44 -0
- package/dist/pi/wakeAcceptanceFs.d.ts.map +1 -0
- package/dist/pi/wakeAcceptanceFs.js +318 -0
- package/dist/pi/wakeAcceptanceSchema.d.ts +75 -0
- package/dist/pi/wakeAcceptanceSchema.d.ts.map +1 -0
- package/dist/pi/wakeAcceptanceSchema.js +265 -0
- package/dist/pi/wakeModes.d.ts +9 -0
- package/dist/pi/wakeModes.d.ts.map +1 -0
- package/dist/pi/wakeModes.js +19 -0
- package/dist/pi/worldNudge.d.ts +22 -0
- package/dist/pi/worldNudge.d.ts.map +1 -0
- package/dist/pi/worldNudge.js +62 -0
- package/dist/pi/worldToolProtocol.d.ts +13 -0
- package/dist/pi/worldToolProtocol.d.ts.map +1 -0
- package/dist/pi/worldToolProtocol.js +49 -0
- package/dist/pi/worldTools.d.ts +32 -0
- package/dist/pi/worldTools.d.ts.map +1 -0
- package/dist/pi/worldTools.js +373 -0
- package/dist/pi/worldTrajectory.d.ts +52 -0
- package/dist/pi/worldTrajectory.d.ts.map +1 -0
- package/dist/pi/worldTrajectory.js +131 -0
- package/package.json +10 -13
- package/dist/examples/mixed-engine-org.d.ts +0 -2
- package/dist/examples/mixed-engine-org.d.ts.map +0 -1
- package/dist/examples/mixed-engine-org.js +0 -351
- package/dist/examples/mixedEngineCli.d.ts +0 -15
- package/dist/examples/mixedEngineCli.d.ts.map +0 -1
- 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 +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"}
|