@haaaiawd/second-nature 0.2.1 → 0.2.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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/runtime/cli/index.js +5 -1
- package/runtime/cli/ops/heartbeat-surface.d.ts +3 -0
- package/runtime/cli/ops/heartbeat-surface.js +1 -0
- package/runtime/cli/ops/manual-run-dispatcher.d.ts +2 -0
- package/runtime/cli/ops/manual-run-dispatcher.js +10 -0
- package/runtime/cli/ops/ops-router.js +2 -0
- package/runtime/cli/ops/workspace-heartbeat-runner.d.ts +3 -0
- package/runtime/cli/ops/workspace-heartbeat-runner.js +2 -0
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +6 -1
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +11 -0
- package/runtime/core/second-nature/quiet/run-source-backed-quiet.d.ts +3 -0
- package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +42 -1
- package/runtime/observability/services/audit-closure-recorders.d.ts +31 -0
- package/runtime/observability/services/audit-closure-recorders.js +87 -0
- package/runtime/observability/services/heartbeat-digest-assembler.js +13 -3
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "second-nature",
|
|
3
3
|
"name": "Second Nature",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"description": "OpenClaw native plugin with synchronous surface registration and bundled runtime spine. Set SECOND_NATURE_WORKSPACE_ROOT or tool workspaceRoot to the same path as the agent workspace. Agent inner guide is packaged as agent-inner-guide.md. v7 ops surface: self_health, tool_affordance, heartbeat_digest, snapshot:capture, narrative:diff, timeline, restore, runtime_secret_bootstrap, connector:run, guidance_payload.",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onStartup": true,
|
package/package.json
CHANGED
package/runtime/cli/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createActionBridge } from "./action-bridge.js";
|
|
|
5
5
|
import { createCliCommands, } from "./commands/index.js";
|
|
6
6
|
import { createOpsRouter } from "./ops/ops-router.js";
|
|
7
7
|
import { createCliReadModels, } from "./read-models/index.js";
|
|
8
|
+
import { AppendOnlyAuditStore } from "../observability/audit/append-only-audit-store.js";
|
|
8
9
|
import { resolvePackagedRuntime } from "./runtime/runtime-artifact-boundary.js";
|
|
9
10
|
import { createRestoreSnapshotStore, } from "../storage/services/restore-snapshot-store.js";
|
|
10
11
|
import { createRuntimeDecisionRecorder, } from "../observability/services/runtime-decision-recorder.js";
|
|
@@ -221,12 +222,13 @@ export function createCliRuntimeDeps(overrides = {}) {
|
|
|
221
222
|
const stateDb = overrides.stateDb ?? createStateDatabase();
|
|
222
223
|
const observabilityDb = overrides.observabilityDb ?? createObservabilityDatabase();
|
|
223
224
|
const stateApi = overrides.stateApi ?? createStateAPI(stateDb);
|
|
225
|
+
const auditStore = overrides.auditStore ?? overrides.livedExperienceAuditStore ?? new AppendOnlyAuditStore();
|
|
224
226
|
const readModels = overrides.readModels ??
|
|
225
227
|
createCliReadModels({
|
|
226
228
|
stateDb,
|
|
227
229
|
observabilityDb,
|
|
228
230
|
workspaceRoot,
|
|
229
|
-
livedExperienceAuditStore:
|
|
231
|
+
livedExperienceAuditStore: auditStore,
|
|
230
232
|
});
|
|
231
233
|
const actionBridge = overrides.actionBridge ?? createActionBridge(stateApi);
|
|
232
234
|
const runtimeRecorder = overrides.runtimeRecorder ?? createRuntimeDecisionRecorder(observabilityDb);
|
|
@@ -261,6 +263,7 @@ export function createCliRuntimeDeps(overrides = {}) {
|
|
|
261
263
|
narrativeTimelineDeps,
|
|
262
264
|
secretAnchorDeps,
|
|
263
265
|
restoreSnapshotStore,
|
|
266
|
+
auditStore,
|
|
264
267
|
};
|
|
265
268
|
}
|
|
266
269
|
export function createCommandRouter(options = {}) {
|
|
@@ -281,6 +284,7 @@ export function createCommandRouter(options = {}) {
|
|
|
281
284
|
narrativeTimelineDeps: runtime.narrativeTimelineDeps,
|
|
282
285
|
secretAnchorDeps: runtime.secretAnchorDeps,
|
|
283
286
|
restoreSnapshotStore: runtime.restoreSnapshotStore,
|
|
287
|
+
auditStore: runtime.auditStore,
|
|
284
288
|
});
|
|
285
289
|
const commands = createCliCommands({
|
|
286
290
|
readModels: runtime.readModels,
|
|
@@ -18,6 +18,7 @@ import type { HeartbeatDigestAssemblerDeps } from "../../observability/services/
|
|
|
18
18
|
import type { GoalLifecyclePolicy } from "../../core/second-nature/heartbeat/goal-lifecycle-policy.js";
|
|
19
19
|
import type { IdleCuriosityPolicy } from "../../core/second-nature/heartbeat/idle-curiosity-policy.js";
|
|
20
20
|
import type { CircuitBreakerManager } from "../../core/second-nature/body/circuit-breaker/circuit-breaker-manager.js";
|
|
21
|
+
import type { AppendOnlyAuditStore } from "../../observability/audit/append-only-audit-store.js";
|
|
21
22
|
export type HeartbeatSurfaceStatus = "heartbeat_ok" | "intent_selected" | "denied" | "deferred" | "runtime_carrier_only" | "delivery_unavailable";
|
|
22
23
|
export interface HeartbeatSurfaceResult {
|
|
23
24
|
ok: boolean;
|
|
@@ -80,5 +81,7 @@ export interface HeartbeatCheckInput {
|
|
|
80
81
|
idleCuriosityPolicy?: IdleCuriosityPolicy;
|
|
81
82
|
/** v7 T-BTS.C.5: circuit breaker manager for connector execution health. */
|
|
82
83
|
circuitBreakerManager?: CircuitBreakerManager;
|
|
84
|
+
/** T-OBS.R.1: shared audit sink for connector/Quiet events consumed by heartbeat_digest. */
|
|
85
|
+
auditStore?: AppendOnlyAuditStore;
|
|
83
86
|
}
|
|
84
87
|
export declare function heartbeatCheck(input: HeartbeatCheckInput): Promise<HeartbeatSurfaceResult>;
|
|
@@ -82,6 +82,7 @@ export async function heartbeatCheck(input) {
|
|
|
82
82
|
goalLifecyclePolicy: input.goalLifecyclePolicy,
|
|
83
83
|
idleCuriosityPolicy: input.idleCuriosityPolicy,
|
|
84
84
|
circuitBreakerManager: input.circuitBreakerManager,
|
|
85
|
+
auditStore: input.auditStore,
|
|
85
86
|
});
|
|
86
87
|
try {
|
|
87
88
|
const cycle = await run(signal);
|
|
@@ -26,6 +26,7 @@ import type { ConnectorExecutor, ConnectorResult } from "../../connectors/base/c
|
|
|
26
26
|
import type { ExperienceWriter } from "../../core/second-nature/body/tool-experience/experience-writer.js";
|
|
27
27
|
import type { WetProbeRunner } from "../../connectors/base/wet-probe-runner.js";
|
|
28
28
|
import type { CapabilityContractRegistryV7 } from "../../connectors/base/manifest-v7.js";
|
|
29
|
+
import type { AppendOnlyAuditStore } from "../../observability/audit/append-only-audit-store.js";
|
|
29
30
|
import { type HeartbeatCheckInput, type HeartbeatSurfaceResult } from "./heartbeat-surface.js";
|
|
30
31
|
import type { RuntimeOpsEnvelope } from "./ops-router.js";
|
|
31
32
|
export interface ManualTriggerContext {
|
|
@@ -75,5 +76,6 @@ export interface ManualRunDispatcherDeps {
|
|
|
75
76
|
experienceWriter: ExperienceWriter;
|
|
76
77
|
wetProbeRunner: WetProbeRunner;
|
|
77
78
|
registryV7: CapabilityContractRegistryV7;
|
|
79
|
+
auditStore?: AppendOnlyAuditStore;
|
|
78
80
|
}
|
|
79
81
|
export declare function createManualRunDispatcher(deps: ManualRunDispatcherDeps): ManualRunDispatcher;
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* Test coverage: tests/unit/ops/manual-run-dispatcher.test.ts
|
|
24
24
|
*/
|
|
25
25
|
import * as crypto from "node:crypto";
|
|
26
|
+
import { recordConnectorAttemptAudit } from "../../observability/services/audit-closure-recorders.js";
|
|
26
27
|
import { heartbeatCheck, } from "./heartbeat-surface.js";
|
|
27
28
|
function buildManualContext(input) {
|
|
28
29
|
return {
|
|
@@ -54,6 +55,15 @@ export function createManualRunDispatcher(deps) {
|
|
|
54
55
|
result: connectorResult,
|
|
55
56
|
triggerSource: ctx.triggerSource,
|
|
56
57
|
});
|
|
58
|
+
recordConnectorAttemptAudit({
|
|
59
|
+
auditStore: deps.auditStore,
|
|
60
|
+
platformId: input.platformId,
|
|
61
|
+
capability: input.capabilityId,
|
|
62
|
+
result: connectorResult,
|
|
63
|
+
triggerSource: ctx.triggerSource,
|
|
64
|
+
decisionId,
|
|
65
|
+
intentId,
|
|
66
|
+
});
|
|
57
67
|
const runResult = {
|
|
58
68
|
connectorResult,
|
|
59
69
|
experienceId,
|
|
@@ -441,6 +441,7 @@ export function createOpsRouter(deps) {
|
|
|
441
441
|
experienceWriter,
|
|
442
442
|
digestOpts,
|
|
443
443
|
dreamSchedulePort,
|
|
444
|
+
auditStore: deps.auditStore,
|
|
444
445
|
goalLifecyclePolicy,
|
|
445
446
|
idleCuriosityPolicy,
|
|
446
447
|
circuitBreakerManager,
|
|
@@ -780,6 +781,7 @@ export function createOpsRouter(deps) {
|
|
|
780
781
|
experienceWriter,
|
|
781
782
|
wetProbeRunner,
|
|
782
783
|
registryV7,
|
|
784
|
+
auditStore: deps.auditStore,
|
|
783
785
|
});
|
|
784
786
|
return dispatcher.runConnector({
|
|
785
787
|
platformId,
|
|
@@ -26,6 +26,7 @@ import { type HeartbeatDigestAssemblerDeps } from "../../observability/services/
|
|
|
26
26
|
import type { GoalLifecyclePolicy } from "../../core/second-nature/heartbeat/goal-lifecycle-policy.js";
|
|
27
27
|
import type { IdleCuriosityPolicy } from "../../core/second-nature/heartbeat/idle-curiosity-policy.js";
|
|
28
28
|
import type { CircuitBreakerManager } from "../../core/second-nature/body/circuit-breaker/circuit-breaker-manager.js";
|
|
29
|
+
import type { AppendOnlyAuditStore } from "../../observability/audit/append-only-audit-store.js";
|
|
29
30
|
export interface WorkspaceHeartbeatRunnerOptions {
|
|
30
31
|
/** When supplied, the runner persists the cycle so `loadStatus` can read it (T1.2.3). */
|
|
31
32
|
runtimeRecorder?: RuntimeDecisionRecorder;
|
|
@@ -76,6 +77,8 @@ export interface WorkspaceHeartbeatRunnerOptions {
|
|
|
76
77
|
*/
|
|
77
78
|
digestWindowHour?: number;
|
|
78
79
|
};
|
|
80
|
+
/** T-OBS.R.1: shared audit sink for heartbeat connector and Quiet outcomes. */
|
|
81
|
+
auditStore?: AppendOnlyAuditStore;
|
|
79
82
|
}
|
|
80
83
|
export declare function loadSnapshotInputsForWorkspaceHeartbeat(readModels: CliReadModels, options?: {
|
|
81
84
|
state?: StateDatabase;
|
|
@@ -145,6 +145,7 @@ export function createWorkspaceHeartbeatRunner(readModels, options = {}) {
|
|
|
145
145
|
workspaceRoot: options.workspaceRoot,
|
|
146
146
|
// v7 T-V7C.C.3: pass Dream schedule port so Quiet completion triggers Dream.
|
|
147
147
|
dreamSchedulePort: options.dreamSchedulePort,
|
|
148
|
+
auditStore: options.auditStore,
|
|
148
149
|
}
|
|
149
150
|
: undefined,
|
|
150
151
|
connectorExecutor: options.connectorExecutor,
|
|
@@ -162,6 +163,7 @@ export function createWorkspaceHeartbeatRunner(readModels, options = {}) {
|
|
|
162
163
|
idleCuriosityPolicy: options.idleCuriosityPolicy,
|
|
163
164
|
// v7 T-BTS.C.5: pass circuit breaker manager for execution health tracking.
|
|
164
165
|
circuitBreakerManager: options.circuitBreakerManager,
|
|
166
|
+
auditStore: options.auditStore,
|
|
165
167
|
},
|
|
166
168
|
});
|
|
167
169
|
if (options.runtimeRecorder) {
|
|
@@ -25,6 +25,7 @@ import type { NarrativeStateStore } from "../../../storage/narrative/narrative-s
|
|
|
25
25
|
import type { NarrativeTracePayload } from "../../../observability/services/lived-experience-audit.js";
|
|
26
26
|
import type { ExperienceWriter } from "../body/tool-experience/experience-writer.js";
|
|
27
27
|
import type { QuietDreamSchedulePort } from "../quiet/run-source-backed-quiet.js";
|
|
28
|
+
import type { AppendOnlyAuditStore } from "../../../observability/audit/append-only-audit-store.js";
|
|
28
29
|
import type { GoalLifecyclePolicy } from "./goal-lifecycle-policy.js";
|
|
29
30
|
import type { IdleCuriosityPolicy } from "./idle-curiosity-policy.js";
|
|
30
31
|
import type { CircuitBreakerManager } from "../body/circuit-breaker/circuit-breaker-manager.js";
|
|
@@ -50,12 +51,14 @@ export interface HeartbeatQuietWorkflowDeps {
|
|
|
50
51
|
workspaceRoot: string;
|
|
51
52
|
/** v7 T-V7C.C.3: when present, a successful Quiet write auto-triggers Dream scheduling. */
|
|
52
53
|
dreamSchedulePort?: QuietDreamSchedulePort;
|
|
54
|
+
/** T-OBS.R.1: audit sink for Quiet outcomes consumed by heartbeat_digest. */
|
|
55
|
+
auditStore?: AppendOnlyAuditStore;
|
|
53
56
|
}
|
|
54
57
|
/**
|
|
55
58
|
* Resolves the heartbeat outcome for a guard-allowed intent (outreach dispatch, quiet orchestration, or default).
|
|
56
59
|
* Exported for unit tests (CR-M1 wiring).
|
|
57
60
|
*/
|
|
58
|
-
export declare function resolveAllowedIntentResult(intent: CandidateIntent, runtime: HeartbeatRuntimeSnapshot, inputs: SnapshotInputs, signal: HeartbeatSignal, deps: Pick<HeartbeatDeps, "outreachDispatch" | "quietWorkflow" | "connectorExecutor" | "state" | "workspaceRoot" | "experienceWriter" | "circuitBreakerManager">): Promise<HeartbeatCycleResult>;
|
|
61
|
+
export declare function resolveAllowedIntentResult(intent: CandidateIntent, runtime: HeartbeatRuntimeSnapshot, inputs: SnapshotInputs, signal: HeartbeatSignal, deps: Pick<HeartbeatDeps, "outreachDispatch" | "quietWorkflow" | "connectorExecutor" | "state" | "workspaceRoot" | "experienceWriter" | "circuitBreakerManager" | "auditStore">): Promise<HeartbeatCycleResult>;
|
|
59
62
|
export interface HeartbeatDeps {
|
|
60
63
|
/** Load snapshot inputs from state-system */
|
|
61
64
|
loadSnapshotInputs: () => Promise<SnapshotInputs>;
|
|
@@ -86,6 +89,8 @@ export interface HeartbeatDeps {
|
|
|
86
89
|
idleCuriosityPolicy?: IdleCuriosityPolicy;
|
|
87
90
|
/** v7 T-BTS.C.5: when present, updates breaker state after connector execution. */
|
|
88
91
|
circuitBreakerManager?: CircuitBreakerManager;
|
|
92
|
+
/** T-OBS.R.1: shared audit sink for connector attempts consumed by heartbeat_digest. */
|
|
93
|
+
auditStore?: AppendOnlyAuditStore;
|
|
89
94
|
}
|
|
90
95
|
/**
|
|
91
96
|
* Ingest a heartbeat rhythm signal and drive one full decision round.
|
|
@@ -10,6 +10,7 @@ import { toCapabilityIntent } from "../orchestrator/effect-dispatcher.js";
|
|
|
10
10
|
import { updateNarrativeAfterEffect } from "../orchestrator/narrative-update.js";
|
|
11
11
|
import { mapLifeEvidence } from "../../../connectors/base/map-life-evidence.js";
|
|
12
12
|
import { appendLifeEvidence } from "../../../storage/life-evidence/append-life-evidence.js";
|
|
13
|
+
import { recordConnectorAttemptAudit } from "../../../observability/services/audit-closure-recorders.js";
|
|
13
14
|
/**
|
|
14
15
|
* Resolves the heartbeat outcome for a guard-allowed intent (outreach dispatch, quiet orchestration, or default).
|
|
15
16
|
* Exported for unit tests (CR-M1 wiring).
|
|
@@ -40,6 +41,7 @@ export async function resolveAllowedIntentResult(intent, runtime, inputs, signal
|
|
|
40
41
|
workspaceRoot: deps.quietWorkflow.workspaceRoot,
|
|
41
42
|
// v7 T-V7C.C.3: pass Dream schedule port so Quiet completion triggers Dream.
|
|
42
43
|
dreamSchedulePort: deps.quietWorkflow.dreamSchedulePort,
|
|
44
|
+
auditStore: deps.quietWorkflow.auditStore,
|
|
43
45
|
});
|
|
44
46
|
return quietRun.result;
|
|
45
47
|
}
|
|
@@ -82,6 +84,15 @@ export async function resolveAllowedIntentResult(intent, runtime, inputs, signal
|
|
|
82
84
|
}
|
|
83
85
|
: undefined,
|
|
84
86
|
});
|
|
87
|
+
recordConnectorAttemptAudit({
|
|
88
|
+
auditStore: deps.auditStore,
|
|
89
|
+
platformId: intent.platformId,
|
|
90
|
+
capability: toCapabilityIntent(intent),
|
|
91
|
+
result,
|
|
92
|
+
triggerSource: "heartbeat",
|
|
93
|
+
decisionId,
|
|
94
|
+
intentId: intent.id,
|
|
95
|
+
});
|
|
85
96
|
// T3.3.1: on success, map connector result to life evidence and append.
|
|
86
97
|
// On failure or empty result, no evidence is fabricated — attempt audit
|
|
87
98
|
// is already recorded by the connector policy layer telemetry.
|
|
@@ -10,6 +10,7 @@ import type { HeartbeatRuntimeSnapshot } from "../heartbeat/runtime-snapshot.js"
|
|
|
10
10
|
import type { HeartbeatCycleResult } from "../heartbeat/signal.js";
|
|
11
11
|
import { type QuietArtifactAck } from "../../../storage/quiet/quiet-artifact-writer.js";
|
|
12
12
|
import type { UserInterestSnapshot } from "../../../storage/user-interest/types.js";
|
|
13
|
+
import type { AppendOnlyAuditStore } from "../../../observability/audit/append-only-audit-store.js";
|
|
13
14
|
/**
|
|
14
15
|
* Minimal port for triggering Dream after Quiet completion (T-V7C.C.3).
|
|
15
16
|
* Kept narrow so run-source-backed-quiet does not take a hard dependency on dream-scheduler.
|
|
@@ -32,6 +33,8 @@ export interface RunSourceBackedQuietParams {
|
|
|
32
33
|
workspaceRoot?: string;
|
|
33
34
|
/** v7 T-V7C.C.3: when present, a successful Quiet artifact write auto-triggers Dream scheduling. */
|
|
34
35
|
dreamSchedulePort?: QuietDreamSchedulePort;
|
|
36
|
+
/** T-OBS.R.1: when present, Quiet outcomes write audit truth consumed by heartbeat_digest. */
|
|
37
|
+
auditStore?: AppendOnlyAuditStore;
|
|
35
38
|
}
|
|
36
39
|
export interface RunSourceBackedQuietResult {
|
|
37
40
|
result: HeartbeatCycleResult;
|
|
@@ -2,6 +2,7 @@ import { isLifeEvidenceSliceEmpty } from "../heartbeat/runtime-snapshot.js";
|
|
|
2
2
|
import { writeQuietArtifact } from "../../../storage/quiet/quiet-artifact-writer.js";
|
|
3
3
|
import { persistQuietArtifactToWorkspace } from "../../../storage/quiet/persist-quiet-artifact.js";
|
|
4
4
|
import { buildEvidencePack, buildQuietNarrativeGuidance, selectInterestBasis } from "../../../guidance/evidence-guidance.js";
|
|
5
|
+
import { recordQuietArtifactAudit } from "../../../observability/services/audit-closure-recorders.js";
|
|
5
6
|
function toGuidanceRef(r) {
|
|
6
7
|
return {
|
|
7
8
|
id: r.id,
|
|
@@ -37,7 +38,7 @@ async function maybeScheduleDreamAfterQuiet(dreamSchedulePort, day) {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
export async function runSourceBackedQuiet(params) {
|
|
40
|
-
const { candidate, runtime, day, userInterestSnapshot, workspaceRoot, dreamSchedulePort } = params;
|
|
41
|
+
const { candidate, runtime, day, userInterestSnapshot, workspaceRoot, dreamSchedulePort, auditStore } = params;
|
|
41
42
|
const empty = isLifeEvidenceSliceEmpty(runtime.lifeEvidence);
|
|
42
43
|
if (empty) {
|
|
43
44
|
const input = {
|
|
@@ -54,6 +55,15 @@ export async function runSourceBackedQuiet(params) {
|
|
|
54
55
|
const p = await persistQuietArtifactToWorkspace(workspaceRoot, ack, input);
|
|
55
56
|
persistedRelativePath = p.relativePath;
|
|
56
57
|
}
|
|
58
|
+
recordQuietArtifactAudit({
|
|
59
|
+
auditStore,
|
|
60
|
+
day,
|
|
61
|
+
kind: "empty_state",
|
|
62
|
+
status: "empty",
|
|
63
|
+
reasons: ["quiet_empty_state", "no_fictional_narrative"],
|
|
64
|
+
artifactAck: ack,
|
|
65
|
+
persistedRelativePath,
|
|
66
|
+
});
|
|
57
67
|
return {
|
|
58
68
|
result: {
|
|
59
69
|
scope: "rhythm",
|
|
@@ -68,6 +78,13 @@ export async function runSourceBackedQuiet(params) {
|
|
|
68
78
|
const guidanceRefs = runtime.lifeEvidence.evidenceRefs.map(toGuidanceRef);
|
|
69
79
|
const ep = buildEvidencePack(guidanceRefs);
|
|
70
80
|
if (!ep.ok) {
|
|
81
|
+
recordQuietArtifactAudit({
|
|
82
|
+
auditStore,
|
|
83
|
+
day,
|
|
84
|
+
kind: "daily_report",
|
|
85
|
+
status: "blocked",
|
|
86
|
+
reasons: ep.reasons,
|
|
87
|
+
});
|
|
71
88
|
return {
|
|
72
89
|
result: {
|
|
73
90
|
scope: "rhythm",
|
|
@@ -78,6 +95,13 @@ export async function runSourceBackedQuiet(params) {
|
|
|
78
95
|
};
|
|
79
96
|
}
|
|
80
97
|
if (ep.pack.sensitiveBlocked) {
|
|
98
|
+
recordQuietArtifactAudit({
|
|
99
|
+
auditStore,
|
|
100
|
+
day,
|
|
101
|
+
kind: "daily_report",
|
|
102
|
+
status: "blocked",
|
|
103
|
+
reasons: ["quiet_guidance_sensitive_source_blocked"],
|
|
104
|
+
});
|
|
81
105
|
return {
|
|
82
106
|
result: {
|
|
83
107
|
scope: "rhythm",
|
|
@@ -128,6 +152,14 @@ export async function runSourceBackedQuiet(params) {
|
|
|
128
152
|
outline: claims.map((c) => c.text),
|
|
129
153
|
});
|
|
130
154
|
if (gq.status === "unavailable") {
|
|
155
|
+
recordQuietArtifactAudit({
|
|
156
|
+
auditStore,
|
|
157
|
+
day,
|
|
158
|
+
kind: "daily_report",
|
|
159
|
+
status: "blocked",
|
|
160
|
+
reasons: gq.reasons,
|
|
161
|
+
artifactAck: ack,
|
|
162
|
+
});
|
|
131
163
|
return {
|
|
132
164
|
result: {
|
|
133
165
|
scope: "rhythm",
|
|
@@ -147,6 +179,15 @@ export async function runSourceBackedQuiet(params) {
|
|
|
147
179
|
const reasons = ["quiet_artifact_written", ...gq.hints.slice(0, 2)];
|
|
148
180
|
if (dreamReason)
|
|
149
181
|
reasons.push(dreamReason);
|
|
182
|
+
recordQuietArtifactAudit({
|
|
183
|
+
auditStore,
|
|
184
|
+
day,
|
|
185
|
+
kind: "daily_report",
|
|
186
|
+
status: "completed",
|
|
187
|
+
reasons,
|
|
188
|
+
artifactAck: ack,
|
|
189
|
+
persistedRelativePath,
|
|
190
|
+
});
|
|
150
191
|
return {
|
|
151
192
|
result: {
|
|
152
193
|
scope: "rhythm",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ConnectorResult } from "../../connectors/base/contract.js";
|
|
2
|
+
import type { QuietArtifactAck } from "../../storage/quiet/quiet-artifact-writer.js";
|
|
3
|
+
import type { QuietArtifactWrite } from "../../storage/quiet/quiet-artifact-types.js";
|
|
4
|
+
import type { AppendOnlyAuditStore } from "../audit/append-only-audit-store.js";
|
|
5
|
+
export type ConnectorAttemptAuditOutcome = "success" | "failure" | "circuit_open" | "blocked";
|
|
6
|
+
export interface RecordConnectorAttemptAuditInput {
|
|
7
|
+
auditStore?: AppendOnlyAuditStore;
|
|
8
|
+
platformId: string;
|
|
9
|
+
capability: string;
|
|
10
|
+
result: ConnectorResult<unknown>;
|
|
11
|
+
triggerSource: "manual_run" | "heartbeat";
|
|
12
|
+
decisionId?: string;
|
|
13
|
+
intentId?: string;
|
|
14
|
+
createdAt?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface RecordQuietArtifactAuditInput {
|
|
17
|
+
auditStore?: AppendOnlyAuditStore;
|
|
18
|
+
day: string;
|
|
19
|
+
kind: QuietArtifactWrite["kind"];
|
|
20
|
+
status: "completed" | "empty" | "blocked" | "failed";
|
|
21
|
+
reasons: string[];
|
|
22
|
+
artifactAck?: QuietArtifactAck;
|
|
23
|
+
persistedRelativePath?: string;
|
|
24
|
+
createdAt?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function recordConnectorAttemptAudit(input: RecordConnectorAttemptAuditInput): {
|
|
27
|
+
eventId?: string;
|
|
28
|
+
};
|
|
29
|
+
export declare function recordQuietArtifactAudit(input: RecordQuietArtifactAuditInput): {
|
|
30
|
+
eventId?: string;
|
|
31
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit-backed closure recorders for connector attempts and Quiet artifacts.
|
|
3
|
+
*
|
|
4
|
+
* Core logic: write small redacted audit envelopes for runtime actions that
|
|
5
|
+
* heartbeat_digest depends on. Payloads contain outcome and source linkage only;
|
|
6
|
+
* raw connector payloads and credentials are deliberately excluded.
|
|
7
|
+
*
|
|
8
|
+
* Dependencies: AppendOnlyAuditStore and buildAuditEnvelope hash-chain helpers.
|
|
9
|
+
* Boundary: does not execute connectors, write Quiet artifacts, or mutate state.
|
|
10
|
+
*
|
|
11
|
+
* Test coverage: tests/unit/ops/manual-run-dispatcher.test.ts,
|
|
12
|
+
* tests/unit/observability/heartbeat-digest-assembler.test.ts.
|
|
13
|
+
*/
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
15
|
+
import { buildAuditEnvelope } from "../audit/audit-envelope.js";
|
|
16
|
+
function mapConnectorOutcome(result) {
|
|
17
|
+
if (result.status === "success")
|
|
18
|
+
return "success";
|
|
19
|
+
if (result.failureClass === "cooldown_blocked")
|
|
20
|
+
return "blocked";
|
|
21
|
+
if (result.failureClass === "platform_unavailable")
|
|
22
|
+
return "circuit_open";
|
|
23
|
+
return "failure";
|
|
24
|
+
}
|
|
25
|
+
export function recordConnectorAttemptAudit(input) {
|
|
26
|
+
if (!input.auditStore)
|
|
27
|
+
return {};
|
|
28
|
+
const createdAt = input.createdAt ?? new Date().toISOString();
|
|
29
|
+
const family = "connector.attempt";
|
|
30
|
+
const envelope = buildAuditEnvelope({
|
|
31
|
+
family,
|
|
32
|
+
plane: "telemetry",
|
|
33
|
+
traceId: `connector_attempt:${input.triggerSource}:${randomUUID()}`,
|
|
34
|
+
sequence: input.auditStore.list().length + 1,
|
|
35
|
+
previousHash: input.auditStore.lastRecordHash(family),
|
|
36
|
+
createdAt,
|
|
37
|
+
payload: {
|
|
38
|
+
platformId: input.platformId,
|
|
39
|
+
capability: input.capability,
|
|
40
|
+
outcome: mapConnectorOutcome(input.result),
|
|
41
|
+
triggerSource: input.triggerSource,
|
|
42
|
+
status: input.result.status,
|
|
43
|
+
failureClass: input.result.failureClass,
|
|
44
|
+
channel: input.result.metadata.channel,
|
|
45
|
+
latencyMs: input.result.metadata.latencyMs,
|
|
46
|
+
degraded: Boolean(input.result.metadata.degraded),
|
|
47
|
+
decisionId: input.decisionId,
|
|
48
|
+
intentId: input.intentId,
|
|
49
|
+
createdAt,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
input.auditStore.append(envelope);
|
|
53
|
+
return { eventId: envelope.eventId };
|
|
54
|
+
}
|
|
55
|
+
export function recordQuietArtifactAudit(input) {
|
|
56
|
+
if (!input.auditStore)
|
|
57
|
+
return {};
|
|
58
|
+
const createdAt = input.createdAt ?? new Date().toISOString();
|
|
59
|
+
const family = "source_coverage";
|
|
60
|
+
const sourceRefs = input.artifactAck ? [input.artifactAck.artifactRef] : [];
|
|
61
|
+
const envelope = buildAuditEnvelope({
|
|
62
|
+
family,
|
|
63
|
+
plane: "source_coverage",
|
|
64
|
+
traceId: `quiet_artifact:${input.day}:${randomUUID()}`,
|
|
65
|
+
sequence: input.auditStore.list().length + 1,
|
|
66
|
+
previousHash: input.auditStore.lastRecordHash(family),
|
|
67
|
+
createdAt,
|
|
68
|
+
payload: {
|
|
69
|
+
auditId: `quiet-audit:${input.day}:${randomUUID()}`,
|
|
70
|
+
traceId: `quiet_trace:${input.day}`,
|
|
71
|
+
subjectType: "quiet_artifact",
|
|
72
|
+
subjectRef: input.artifactAck?.artifactRef.uri ?? `quiet:${input.day}:${input.kind}`,
|
|
73
|
+
day: input.day,
|
|
74
|
+
kind: input.kind,
|
|
75
|
+
status: input.status,
|
|
76
|
+
usedSourceRefs: sourceRefs,
|
|
77
|
+
unresolvedRefs: [],
|
|
78
|
+
coverageRatio: input.artifactAck?.sourceCoverage.coverageRatio ?? 0,
|
|
79
|
+
unsupportedClaims: input.artifactAck?.sourceCoverage.unsupportedClaims ?? [],
|
|
80
|
+
persistedRelativePath: input.persistedRelativePath,
|
|
81
|
+
reasonCodes: input.reasons,
|
|
82
|
+
createdAt,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
input.auditStore.append(envelope);
|
|
86
|
+
return { eventId: envelope.eventId };
|
|
87
|
+
}
|
|
@@ -97,10 +97,21 @@ function aggregateHealthSummary(events, dateStr) {
|
|
|
97
97
|
}
|
|
98
98
|
function aggregateQuietDreamFromAudit(events, dateStr) {
|
|
99
99
|
const dreamEvents = filterByDate(events, "dream.trace", dateStr);
|
|
100
|
+
const quietEvents = filterByDate(events, "source_coverage", dateStr)
|
|
101
|
+
.filter((ev) => ev.payload.subjectType === "quiet_artifact");
|
|
102
|
+
let quietRuns = 0;
|
|
103
|
+
let quietSucceeded = 0;
|
|
100
104
|
let dreamRuns = 0;
|
|
101
105
|
let dreamAccepted = 0;
|
|
102
106
|
let dreamSkipped = 0;
|
|
103
107
|
const dreamSkipReasons = [];
|
|
108
|
+
for (const ev of quietEvents) {
|
|
109
|
+
const payload = ev.payload;
|
|
110
|
+
quietRuns++;
|
|
111
|
+
if (payload.status === "completed" || payload.status === "empty") {
|
|
112
|
+
quietSucceeded++;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
104
115
|
for (const ev of dreamEvents) {
|
|
105
116
|
const payload = ev.payload;
|
|
106
117
|
if (payload.event === "dream_started")
|
|
@@ -113,10 +124,9 @@ function aggregateQuietDreamFromAudit(events, dateStr) {
|
|
|
113
124
|
dreamSkipReasons.push(payload.skipReason);
|
|
114
125
|
}
|
|
115
126
|
}
|
|
116
|
-
// Quiet stats not yet in audit (narrative.trace not fully wired) — default to 0
|
|
117
127
|
return {
|
|
118
|
-
quietRuns
|
|
119
|
-
quietSucceeded
|
|
128
|
+
quietRuns,
|
|
129
|
+
quietSucceeded,
|
|
120
130
|
dreamRuns,
|
|
121
131
|
dreamAccepted,
|
|
122
132
|
dreamSkipped,
|