@haaaiawd/second-nature 0.2.9 → 0.2.13

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 (115) hide show
  1. package/index.js +102 -6
  2. package/openclaw.plugin.json +2 -5
  3. package/package.json +1 -1
  4. package/runtime/cli/commands/index.js +85 -11
  5. package/runtime/cli/host-capability/host-discovery-port.d.ts +85 -0
  6. package/runtime/cli/host-capability/host-discovery-port.js +137 -0
  7. package/runtime/cli/host-capability/probe-host-capability.js +1 -1
  8. package/runtime/cli/host-capability/types.d.ts +2 -7
  9. package/runtime/cli/host-capability/types.js +0 -5
  10. package/runtime/cli/ops/heartbeat-surface.d.ts +5 -3
  11. package/runtime/cli/ops/heartbeat-surface.js +38 -8
  12. package/runtime/cli/ops/ops-router.d.ts +6 -2
  13. package/runtime/cli/ops/ops-router.js +1275 -1147
  14. package/runtime/cli/ops/workspace-heartbeat-runner.js +2 -5
  15. package/runtime/connectors/base/normalized-evidence-content.d.ts +4 -0
  16. package/runtime/connectors/base/normalized-evidence-content.js +21 -2
  17. package/runtime/connectors/evidence-normalizer.js +32 -1
  18. package/runtime/connectors/manifest/manifest-schema.d.ts +2 -2
  19. package/runtime/connectors/registry/dynamic-connector-registry.js +16 -1
  20. package/runtime/connectors/services/connector-executor-adapter.js +54 -35
  21. package/runtime/core/second-nature/action/action-closure-recorder.d.ts +4 -0
  22. package/runtime/core/second-nature/action/action-closure-recorder.js +51 -38
  23. package/runtime/core/second-nature/action/action-proposal-builder.js +8 -34
  24. package/runtime/core/second-nature/action/policy-bound-dispatch.d.ts +2 -0
  25. package/runtime/core/second-nature/action/policy-bound-dispatch.js +10 -5
  26. package/runtime/core/second-nature/control-plane/accepted-projection-loader.js +1 -11
  27. package/runtime/core/second-nature/control-plane/cycle-finalizer.d.ts +82 -0
  28. package/runtime/core/second-nature/control-plane/cycle-finalizer.js +187 -0
  29. package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.d.ts +1 -0
  30. package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.js +23 -15
  31. package/runtime/core/second-nature/control-plane/real-runtime-spine.d.ts +2 -0
  32. package/runtime/core/second-nature/control-plane/real-runtime-spine.js +2 -1
  33. package/runtime/core/second-nature/guidance/guidance-proposal-consumer.d.ts +2 -1
  34. package/runtime/core/second-nature/guidance/guidance-proposal-consumer.js +4 -2
  35. package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +4 -3
  36. package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +3 -2
  37. package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +3 -2
  38. package/runtime/core/second-nature/orchestrator/intent-planner.js +4 -2
  39. package/runtime/core/second-nature/orchestrator/narrative-update.js +1 -2
  40. package/runtime/core/second-nature/orchestrator/platform-capability-router.d.ts +2 -2
  41. package/runtime/core/second-nature/orchestrator/platform-capability-router.js +1 -1
  42. package/runtime/core/second-nature/outreach/build-outreach-draft-request.js +2 -3
  43. package/runtime/core/second-nature/outreach/dispatch-user-outreach.d.ts +2 -2
  44. package/runtime/core/second-nature/outreach/dispatch-user-outreach.js +6 -1
  45. package/runtime/core/second-nature/outreach/judge-input-from-snapshot.js +3 -14
  46. package/runtime/core/second-nature/outreach/judge-outreach.d.ts +6 -5
  47. package/runtime/core/second-nature/perception/judgment-engine.js +10 -16
  48. package/runtime/core/second-nature/perception/perception-builder.js +15 -11
  49. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +13 -15
  50. package/runtime/core/second-nature/quiet-dream/daily-rhythm-scheduler.js +40 -16
  51. package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.d.ts +5 -1
  52. package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.js +68 -29
  53. package/runtime/core/second-nature/quiet-dream/dream-scheduler.js +2 -3
  54. package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.js +2 -13
  55. package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.d.ts +1 -0
  56. package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.js +34 -11
  57. package/runtime/core/second-nature/types.d.ts +2 -9
  58. package/runtime/dream/dream-engine.js +11 -4
  59. package/runtime/guidance/outreach-draft-schema.d.ts +12 -12
  60. package/runtime/guidance/persona-selection.js +5 -0
  61. package/runtime/guidance/template-registry.js +6 -1
  62. package/runtime/guidance/types.d.ts +2 -2
  63. package/runtime/observability/causal-loop-health.d.ts +2 -1
  64. package/runtime/observability/causal-loop-health.js +7 -0
  65. package/runtime/observability/living-loop-health-gate.js +2 -8
  66. package/runtime/observability/loop-stage-event-sink.js +6 -2
  67. package/runtime/observability/loop-status.d.ts +2 -0
  68. package/runtime/observability/loop-status.js +14 -1
  69. package/runtime/observability/services/heartbeat-digest-assembler.d.ts +3 -0
  70. package/runtime/observability/services/heartbeat-digest-assembler.js +9 -0
  71. package/runtime/observability/services/lived-experience-audit.d.ts +7 -7
  72. package/runtime/shared/degraded-status-classifier.d.ts +16 -0
  73. package/runtime/shared/degraded-status-classifier.js +68 -0
  74. package/runtime/shared/evidence-level-classifier.d.ts +61 -0
  75. package/runtime/shared/evidence-level-classifier.js +116 -0
  76. package/runtime/shared/provenance-tier.d.ts +37 -0
  77. package/runtime/shared/provenance-tier.js +97 -0
  78. package/runtime/shared/serialization.d.ts +17 -0
  79. package/runtime/shared/serialization.js +27 -0
  80. package/runtime/shared/setup-ack.d.ts +54 -0
  81. package/runtime/shared/setup-ack.js +108 -0
  82. package/runtime/shared/source-ref-compat.d.ts +26 -0
  83. package/runtime/shared/source-ref-compat.js +64 -0
  84. package/runtime/shared/types/goal.d.ts +4 -4
  85. package/runtime/shared/types/goal.js +1 -1
  86. package/runtime/shared/types/index.d.ts +1 -0
  87. package/runtime/shared/types/index.js +1 -3
  88. package/runtime/shared/types/source-ref.d.ts +2 -2
  89. package/runtime/shared/types/v7-entities.d.ts +5 -5
  90. package/runtime/shared/types/v7-entities.js +1 -1
  91. package/runtime/shared/types/v8-contracts.d.ts +13 -2
  92. package/runtime/storage/db/index.js +60 -12
  93. package/runtime/storage/db/migrations/index.js +4 -0
  94. package/runtime/storage/db/migrations/v8-004-schema-closure.d.ts +19 -0
  95. package/runtime/storage/db/migrations/v8-004-schema-closure.js +74 -0
  96. package/runtime/storage/db/migrations/v8-005-single-status-schema.d.ts +11 -0
  97. package/runtime/storage/db/migrations/v8-005-single-status-schema.js +16 -0
  98. package/runtime/storage/db/migrations/v8-006-loop-stage-event-proof-trace-columns.d.ts +9 -0
  99. package/runtime/storage/db/migrations/v8-006-loop-stage-event-proof-trace-columns.js +15 -0
  100. package/runtime/storage/db/schema/v8-entities.d.ts +65 -84
  101. package/runtime/storage/db/schema/v8-entities.js +8 -7
  102. package/runtime/storage/delivery/types.d.ts +2 -2
  103. package/runtime/storage/fallback/load-operator-fallback.d.ts +2 -2
  104. package/runtime/storage/fallback/operator-fallback-types.d.ts +2 -2
  105. package/runtime/storage/fallback/operator-fallback-view.d.ts +2 -2
  106. package/runtime/storage/index.d.ts +1 -1
  107. package/runtime/storage/life-evidence/types.d.ts +5 -5
  108. package/runtime/storage/quiet/quiet-artifact-types.d.ts +4 -4
  109. package/runtime/storage/quiet/quiet-artifact-writer.d.ts +2 -2
  110. package/runtime/storage/services/write-validation-gate.d.ts +1 -1
  111. package/runtime/storage/services/write-validation-gate.js +15 -3
  112. package/runtime/storage/snapshots/types.d.ts +8 -8
  113. package/runtime/storage/user-interest/types.d.ts +3 -3
  114. package/runtime/storage/v8-state-stores.d.ts +15 -3
  115. package/runtime/storage/v8-state-stores.js +60 -39
@@ -4,6 +4,7 @@ import { createAgentGoalStore } from "../../storage/goal/agent-goal-store.js";
4
4
  import { createNarrativeStateStore } from "../../storage/narrative/narrative-state-store.js";
5
5
  import { createRelationshipMemoryStore } from "../../storage/relationship/relationship-memory-store.js";
6
6
  import { createIdentityProfileStore } from "../../storage/services/identity-profile-store.js";
7
+ import { toCanonicalSourceRef } from "../../shared/source-ref-compat.js";
7
8
  import { generateHeartbeatDigest, } from "../../observability/services/heartbeat-digest-assembler.js";
8
9
  import { createHistoryDigestStore } from "../../storage/services/history-digest-store.js";
9
10
  export async function loadSnapshotInputsForWorkspaceHeartbeat(readModels, options = {}) {
@@ -26,11 +27,7 @@ export async function loadSnapshotInputsForWorkspaceHeartbeat(readModels, option
26
27
  const snapshot = await loadLifeEvidenceSnapshot(options.state, options.workspaceRoot, { limit: 50 },
27
28
  // Skip repair gate here — runner is called inside a live cycle; gate ran at startup.
28
29
  { runRepairGate: false });
29
- lifeEvidenceRefs = snapshot.evidenceRefs.map((ref) => ({
30
- id: ref.id,
31
- kind: ref.kind,
32
- uri: ref.uri,
33
- }));
30
+ lifeEvidenceRefs = snapshot.evidenceRefs.map((ref) => toCanonicalSourceRef(ref));
34
31
  platformEventCount = snapshot.platformEvents.length;
35
32
  workEventCount = snapshot.workEvents.length;
36
33
  if (snapshot.empty) {
@@ -23,6 +23,8 @@
23
23
  export declare const NORMALIZED_EVIDENCE_SCHEMA_VERSION = 1;
24
24
  export type EvidenceSourceKind = "post" | "comment" | "profile" | "task" | "event" | "game_state" | "notification" | "document" | "unknown";
25
25
  export type SummaryProducer = "connector_rules" | "model_assist" | "operator_supplied";
26
+ export type EvidenceContentStatus = "content_present" | "content_missing" | "content_redacted";
27
+ export type EvidenceContentMissingReason = "id_only" | "empty_payload" | "unsupported_shape" | "redacted_private";
26
28
  export interface EvidenceActor {
27
29
  id?: string;
28
30
  displayName?: string;
@@ -36,6 +38,8 @@ export interface NormalizedEvidenceContent {
36
38
  externalId?: string;
37
39
  title?: string;
38
40
  summary: string;
41
+ contentStatus: EvidenceContentStatus;
42
+ contentMissingReason?: EvidenceContentMissingReason;
39
43
  excerpt?: string;
40
44
  canonicalText?: string;
41
45
  actor?: EvidenceActor;
@@ -226,8 +226,25 @@ function normalizeSingleItem(item, options) {
226
226
  const tags = extractTags(item);
227
227
  const entities = extractEntities(item);
228
228
  const metrics = extractMetrics(item);
229
- const rawText = bodyText ?? title ?? "[no readable content]";
230
- const summary = truncate(rawText, 160);
229
+ const hasReadableContent = isNonEmptyString(bodyText) || isNonEmptyString(title);
230
+ const hasContentMarkers = isNonEmptyString(url) ||
231
+ (actor?.displayName && actor.displayName.length > 0) ||
232
+ tags.length > 0 ||
233
+ entities.length > 0 ||
234
+ (metrics && Object.keys(metrics).length > 0);
235
+ let contentStatus = "content_present";
236
+ let contentMissingReason;
237
+ let summary;
238
+ if (!hasReadableContent && !hasContentMarkers) {
239
+ contentStatus = "content_missing";
240
+ contentMissingReason = externalId ? "id_only" : "empty_payload";
241
+ summary = `Content missing: ${contentMissingReason === "id_only" ? "id-only evidence" : "empty payload"}`;
242
+ }
243
+ else {
244
+ const rawText = bodyText ?? title ?? "[no readable content]";
245
+ summary = truncate(rawText, 160);
246
+ }
247
+ const rawText = bodyText ?? title ?? "";
231
248
  const excerpt = rawText.length > summary.length ? truncate(rawText, options.excerptMaxChars ?? 240) : undefined;
232
249
  const canonicalText = truncate(rawText, options.canonicalTextMaxChars ?? 2000);
233
250
  return {
@@ -238,6 +255,8 @@ function normalizeSingleItem(item, options) {
238
255
  externalId,
239
256
  title,
240
257
  summary,
258
+ contentStatus,
259
+ contentMissingReason,
241
260
  excerpt,
242
261
  canonicalText,
243
262
  actor,
@@ -31,6 +31,11 @@ import { extractNormalizedEvidenceItems, computeEvidenceContentHashSync, } from
31
31
  function computeLegacyContentHash(content) {
32
32
  return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
33
33
  }
34
+ function truncate(text, maxChars) {
35
+ if (text.length <= maxChars)
36
+ return text;
37
+ return `${text.slice(0, maxChars)}…`;
38
+ }
34
39
  function buildSourceRef(platformId, capabilityId, itemId, observedAt) {
35
40
  return {
36
41
  uri: `sn://connector_result/${platformId}/${capabilityId}/${itemId}`,
@@ -191,6 +196,32 @@ async function normalizeLegacyConnectorEvidence(db, result, now) {
191
196
  const observedAt = result.observedAt ?? now;
192
197
  const sourceRef = buildSourceRef(result.platformId, result.capabilityId, itemId, observedAt);
193
198
  const sensitivityHint = mergeSensitivityHint(inferSensitivityHint(item.content), item.sensitivityHint);
199
+ const trimmedContent = item.content.trim();
200
+ const looksLikeIdOnly = /^[a-z0-9_-]+$/i.test(trimmedContent) && trimmedContent.length < 64;
201
+ const contentStatus = looksLikeIdOnly || trimmedContent.length === 0
202
+ ? "content_missing"
203
+ : "content_present";
204
+ const contentMissingReason = contentStatus === "content_missing"
205
+ ? trimmedContent.length === 0
206
+ ? "empty_payload"
207
+ : "id_only"
208
+ : undefined;
209
+ const normalized = {
210
+ schemaVersion: 1,
211
+ sourceKind: "unknown",
212
+ platformId: result.platformId,
213
+ capabilityId: result.capabilityId,
214
+ externalId: item.id,
215
+ summary: contentStatus === "content_missing"
216
+ ? `Content missing: ${contentMissingReason === "id_only" ? "id-only evidence" : "empty payload"}`
217
+ : truncate(trimmedContent, 160),
218
+ contentStatus,
219
+ contentMissingReason,
220
+ excerpt: contentStatus === "content_present" ? truncate(trimmedContent, 240) : undefined,
221
+ canonicalText: contentStatus === "content_present" ? truncate(trimmedContent, 2000) : undefined,
222
+ observedAt,
223
+ summaryProducer: "connector_rules",
224
+ };
194
225
  const writeResult = await writeEvidenceItem(db, {
195
226
  id: `ev_${result.platformId}_${itemId}_${observedAt.replace(/[:.]/g, "")}`,
196
227
  createdAt: now,
@@ -201,7 +232,7 @@ async function normalizeLegacyConnectorEvidence(db, result, now) {
201
232
  sourceRefs: [sourceRef],
202
233
  redactionClass: sensitivityHint === "sensitive" ? "blocked" : "none",
203
234
  lifecycleStatus: "pending",
204
- payloadJson: item.metadata ? JSON.stringify(item.metadata) : null,
235
+ payloadJson: JSON.stringify(normalized),
205
236
  });
206
237
  if ("id" in writeResult) {
207
238
  evidenceIds.push(writeResult.id);
@@ -116,7 +116,7 @@ export declare const connectorManifestV6Schema: z.ZodObject<{
116
116
  export type ConnectorManifestV6 = z.infer<typeof connectorManifestV6Schema>;
117
117
  export interface ConnectorConflict {
118
118
  platformId: string;
119
- existingSource: "built_in" | "workspace";
119
+ existingSource: "built_in" | "workspace" | "workspace_shadow";
120
120
  attemptedSource: "built_in" | "workspace";
121
121
  reason: string;
122
122
  }
@@ -127,7 +127,7 @@ export interface ConnectorManifestValidationError {
127
127
  }
128
128
  export interface ConnectorInventoryEntry {
129
129
  platformId: string;
130
- source: "built_in" | "workspace";
130
+ source: "built_in" | "workspace" | "workspace_shadow";
131
131
  manifestPath?: string;
132
132
  trustStatus: ConnectorTrustStatus;
133
133
  executable: boolean;
@@ -41,6 +41,12 @@ function manifestToInventoryEntry(manifest, source, manifestPath) {
41
41
  validationErrors: [],
42
42
  };
43
43
  }
44
+ function isSafeBuiltInShadow(manifest) {
45
+ const reason = manifest.trust?.reason?.trim();
46
+ if (!manifest.trust?.override || !reason)
47
+ return false;
48
+ return manifest.runner.kind === "declarative_http" || manifest.runner.kind === "scriptable_node";
49
+ }
44
50
  /**
45
51
  * DynamicConnectorRegistry scans workspace manifests, validates, classifies trust,
46
52
  * merges built-in entries, applies fail-closed conflict policy, and publishes
@@ -79,11 +85,20 @@ export class DynamicConnectorRegistry {
79
85
  }
80
86
  const manifest = parseResult.manifest;
81
87
  const manifestPath = path.relative(workspaceRoot, file.path);
82
- // Duplicate platformId without override -> fail-closed
88
+ // Duplicate platformId without explicit safe override -> fail-closed.
89
+ // Built-ins may be shadowed only by an auditable workspace manifest with
90
+ // a trusted runner kind, so operators can repair endpoint config without
91
+ // silently replacing native code.
83
92
  if (builtInMap.has(manifest.platformId) || dynamicMap.has(manifest.platformId)) {
84
93
  const existing = builtInMap.get(manifest.platformId) ?? dynamicMap.get(manifest.platformId);
85
94
  const allowOverride = manifest.trust?.override === true;
86
95
  const isTrustedSource = existing.source === "built_in";
96
+ if (isTrustedSource && isSafeBuiltInShadow(manifest)) {
97
+ const entry = manifestToInventoryEntry(manifest, "workspace_shadow", manifestPath);
98
+ dynamicMap.set(manifest.platformId, entry);
99
+ registered++;
100
+ continue;
101
+ }
87
102
  if (!allowOverride || isTrustedSource) {
88
103
  conflicts.push({
89
104
  platformId: manifest.platformId,
@@ -51,7 +51,13 @@ function channelPriorityForRunner(manifest) {
51
51
  return ["browser"];
52
52
  return ["api_rest"];
53
53
  }
54
- function registerWorkspaceManifests(registry, workspaceRoot) {
54
+ function isSafeBuiltInShadow(manifest) {
55
+ const reason = manifest.trust?.reason?.trim();
56
+ if (!manifest.trust?.override || !reason)
57
+ return false;
58
+ return manifest.runner.kind === "declarative_http" || manifest.runner.kind === "scriptable_node";
59
+ }
60
+ function registerWorkspaceManifests(registry, builtInPlatformIds, workspaceRoot) {
55
61
  if (!workspaceRoot)
56
62
  return;
57
63
  for (const file of scanConnectorManifests(workspaceRoot)) {
@@ -59,6 +65,12 @@ function registerWorkspaceManifests(registry, workspaceRoot) {
59
65
  if (!parsed.ok)
60
66
  continue;
61
67
  const manifest = parsed.manifest;
68
+ // Built-in platforms may only be shadowed by auditable, trusted-runner manifests.
69
+ // Unsafe shadows are ignored by execution registry; they remain visible in
70
+ // DynamicConnectorRegistry conflicts for diagnostics.
71
+ if (builtInPlatformIds.has(manifest.platformId) && !isSafeBuiltInShadow(manifest)) {
72
+ continue;
73
+ }
62
74
  try {
63
75
  registry.register({
64
76
  platformId: manifest.platformId,
@@ -201,8 +213,8 @@ function createMoltbookMockRunner(workspaceRoot) {
201
213
  latencyMs: Date.now() - started,
202
214
  success: false,
203
215
  error: {
204
- code: "mock_read_error",
205
- detail: String(err),
216
+ code: "configuration_missing",
217
+ detail: `moltbook mock unreadable: ${err instanceof Error ? err.message : String(err)}`,
206
218
  },
207
219
  };
208
220
  }
@@ -424,9 +436,13 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
424
436
  const workspaceManifest = workspaceManifestResult?.manifest;
425
437
  const isBuiltInPlatform = platformId === "moltbook" ||
426
438
  platformId === "evomap" ||
427
- platformId === "agent-world";
439
+ platformId === "agent-world" ||
440
+ platformId === "instreet";
441
+ const effectiveWorkspaceManifest = workspaceManifest && (!isBuiltInPlatform || isSafeBuiltInShadow(workspaceManifest))
442
+ ? workspaceManifest
443
+ : undefined;
428
444
  const requiresCredential = isBuiltInPlatform ||
429
- Boolean(workspaceManifest?.credentials.some((credential) => credential.required !== false));
445
+ Boolean(effectiveWorkspaceManifest?.credentials.some((credential) => credential.required !== false));
430
446
  const credential = requiresCredential
431
447
  ? await vault.loadCredentialContext(platformId)
432
448
  : undefined;
@@ -448,9 +464,33 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
448
464
  const activeCredential = credential?.status === "active" && credential.encryptedValue
449
465
  ? { encryptedValue: credential.encryptedValue }
450
466
  : undefined;
467
+ // Safe workspace shadows of built-in platforms take precedence over built-in runners.
468
+ if (effectiveWorkspaceManifest && effectiveWorkspaceManifest.runner.kind === "declarative_http") {
469
+ const httpRunner = createDeclarativeHttpRunner(effectiveWorkspaceManifest, activeCredential);
470
+ return httpRunner.run(_plan, request);
471
+ }
472
+ if (effectiveWorkspaceManifest && effectiveWorkspaceManifest.runner.kind === "scriptable_node") {
473
+ if (!workspaceManifestResult) {
474
+ return {
475
+ platformId,
476
+ channel: request.preferredChannel ?? "api_rest",
477
+ latencyMs: Date.now() - started,
478
+ success: false,
479
+ error: {
480
+ code: "configuration_missing",
481
+ detail: "scriptable_node requires workspace manifest with manifestDir",
482
+ },
483
+ };
484
+ }
485
+ const scriptRunner = createScriptableNodeRunner(effectiveWorkspaceManifest, workspaceManifestResult.manifestDir, activeCredential);
486
+ return scriptRunner.run(_plan, request);
487
+ }
451
488
  if (platformId === "moltbook") {
452
489
  const baseUrl = process.env.SECOND_NATURE_MOLTBOOK_BASE_URL;
453
490
  if (baseUrl) {
491
+ const effectivePlan = request.intent === "feed.read" && _plan.channel !== "api_rest"
492
+ ? { ..._plan, channel: "api_rest", endpointMode: "rest_json", degraded: false }
493
+ : _plan;
454
494
  const apiClient = createMoltbookApiClient({
455
495
  baseUrl,
456
496
  accessToken: activeCredential.encryptedValue,
@@ -461,13 +501,13 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
461
501
  skillRunner: {
462
502
  run: async () => {
463
503
  throw {
464
- code: "protocol_mismatch",
504
+ code: "configuration_missing",
465
505
  detail: "moltbook_skill_runner_not_configured",
466
506
  };
467
507
  },
468
508
  },
469
509
  });
470
- return runner.run(_plan, request);
510
+ return runner.run(effectivePlan, request);
471
511
  }
472
512
  // Mock fallback when real API is not configured
473
513
  const mockRunner = createMoltbookMockRunner(workspaceRoot);
@@ -583,28 +623,6 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
583
623
  },
584
624
  };
585
625
  }
586
- // Wave 83: workspace declarative_http connector fallback
587
- if (workspaceManifest && workspaceManifest.runner.kind === "declarative_http") {
588
- const httpRunner = createDeclarativeHttpRunner(workspaceManifest, activeCredential);
589
- return httpRunner.run(_plan, request);
590
- }
591
- // Wave 90: workspace scriptable_node connector
592
- if (workspaceManifest && workspaceManifest.runner.kind === "scriptable_node") {
593
- if (!workspaceManifestResult) {
594
- return {
595
- platformId,
596
- channel: request.preferredChannel ?? "api_rest",
597
- latencyMs: Date.now() - started,
598
- success: false,
599
- error: {
600
- code: "configuration_missing",
601
- detail: "scriptable_node requires workspace manifest with manifestDir",
602
- },
603
- };
604
- }
605
- const scriptRunner = createScriptableNodeRunner(workspaceManifest, workspaceManifestResult.manifestDir, activeCredential);
606
- return scriptRunner.run(_plan, request);
607
- }
608
626
  return {
609
627
  platformId,
610
628
  channel: request.preferredChannel ?? "api_rest",
@@ -621,11 +639,12 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
621
639
  export function createConnectorExecutorAdapter(options) {
622
640
  const vault = createCredentialVault(options.stateDb.db);
623
641
  const registry = new CapabilityContractRegistry();
624
- registry.register({ ...moltbookManifest });
625
- registry.register({ ...evomapManifest });
626
- registry.register({ ...agentWorldManifest });
627
- registry.register({ ...instreetManifest });
628
- registerWorkspaceManifests(registry, options.workspaceRoot);
642
+ const builtInManifests = [moltbookManifest, evomapManifest, agentWorldManifest, instreetManifest];
643
+ const builtInPlatformIds = new Set(builtInManifests.map((m) => m.platformId));
644
+ for (const manifest of builtInManifests) {
645
+ registry.register({ ...manifest });
646
+ }
647
+ registerWorkspaceManifests(registry, builtInPlatformIds, options.workspaceRoot);
629
648
  const cooldownPort = createConnectorCooldownPort(options.stateDb);
630
649
  const routeContextPort = createCredentialRouteContextPort(vault, options.stateDb);
631
650
  const routePlanner = new ConnectorRoutePlanner(registry, routeContextPort, new ChannelHealthStore());
@@ -641,7 +660,7 @@ export function createConnectorExecutorAdapter(options) {
641
660
  });
642
661
  return {
643
662
  async executeEffect(input) {
644
- registerWorkspaceManifests(registry, options.workspaceRoot);
663
+ registerWorkspaceManifests(registry, builtInPlatformIds, options.workspaceRoot);
645
664
  return policy.executeWithPolicy(input.intent, {
646
665
  platformId: input.platformId,
647
666
  intent: input.intent,
@@ -38,11 +38,15 @@ export interface ActionClosureRecord {
38
38
  nextState: string;
39
39
  reason: V8ReasonCode;
40
40
  sourceRefs: SourceRef[];
41
+ proofRefs?: SourceRef[];
42
+ traceRefs?: SourceRef[];
41
43
  memoryReviewCandidate?: MemoryReviewCandidateClosure;
42
44
  closedAt: string;
43
45
  }
44
46
  export interface RecordClosureOptions {
45
47
  now?: string;
48
+ platformId?: string;
49
+ capabilityId?: string;
46
50
  }
47
51
  export type RecordClosureResult = {
48
52
  status: "recorded";
@@ -21,6 +21,7 @@
21
21
  * Test coverage: tests/unit/action/action-closure-recorder.test.ts
22
22
  */
23
23
  import { writeActionClosureRecord, readActionClosuresByCycle, } from "../../../storage/v8-state-stores.js";
24
+ import { buildClosureProvenance, cycleTraceRef, closureTraceRef, decisionProofRef, } from "../../../shared/provenance-tier.js";
24
25
  // ───────────────────────────────────────────────────────────────
25
26
  // Helpers
26
27
  // ───────────────────────────────────────────────────────────────
@@ -50,7 +51,8 @@ export async function recordNoActionClosure(db, cycleId, noActionReason, options
50
51
  status: "no_action",
51
52
  reason: noActionReason,
52
53
  nextState: "await_next_cycle",
53
- sourceRefs: [
54
+ sourceRefs: [cycleTraceRef(cycleId)],
55
+ proofRefs: [
54
56
  {
55
57
  uri: `sn://closure/no_action/${cycleId}`,
56
58
  family: "action_closure",
@@ -59,9 +61,9 @@ export async function recordNoActionClosure(db, cycleId, noActionReason, options
59
61
  resolveStatus: "resolvable",
60
62
  },
61
63
  ],
64
+ traceRefs: [cycleTraceRef(cycleId)],
62
65
  redactionClass: "none",
63
- lifecycleStatus: "closed",
64
- payloadJson: JSON.stringify({ dispatchAttempt: 0, inputSummary: "no-action" }),
66
+ payload: { dispatchAttempt: 0, inputSummary: "no-action" },
65
67
  });
66
68
  if ("reason" in result) {
67
69
  return result;
@@ -74,21 +76,28 @@ export async function recordNoActionClosure(db, cycleId, noActionReason, options
74
76
  export async function recordRememberClosure(db, cycleId, memoryReviewCandidate, options) {
75
77
  const now = options?.now ?? new Date().toISOString();
76
78
  const closureId = `cls_remember_${cycleId}_${now.replace(/[:.]/g, "")}`;
79
+ const provenance = buildClosureProvenance({
80
+ sourceRefs: memoryReviewCandidate.sourceRefs,
81
+ traceRefs: [cycleTraceRef(cycleId)],
82
+ });
77
83
  const result = await writeActionClosureRecord(db, {
78
84
  id: closureId,
79
85
  createdAt: now,
80
86
  cycleId,
87
+ platformId: options?.platformId ?? "heartbeat",
88
+ capabilityId: options?.capabilityId,
81
89
  status: "completed",
82
90
  reason: "remember_for_review",
83
91
  nextState: "pending_daily_review",
84
- sourceRefs: memoryReviewCandidate.sourceRefs,
92
+ sourceRefs: provenance.sourceRefs,
93
+ proofRefs: provenance.proofRefs,
94
+ traceRefs: provenance.traceRefs,
85
95
  redactionClass: "none",
86
- lifecycleStatus: "closed",
87
- payloadJson: JSON.stringify({
96
+ payload: {
88
97
  memoryReviewCandidate,
89
98
  dispatchAttempt: 1,
90
99
  inputSummary: `remember_for_review topic=${memoryReviewCandidate.topicKey}`,
91
- }),
100
+ },
92
101
  });
93
102
  if ("reason" in result) {
94
103
  return result;
@@ -101,24 +110,21 @@ export async function recordRememberClosure(db, cycleId, memoryReviewCandidate,
101
110
  export async function recordPolicyOutcomeClosure(db, cycleId, closureStatus, reason, params, options) {
102
111
  const now = options?.now ?? new Date().toISOString();
103
112
  const closureId = `cls_${closureStatus}_${cycleId}_${now.replace(/[:.]/g, "")}`;
104
- const sourceRefs = [
105
- {
106
- uri: `sn://closure/${closureStatus}/${cycleId}`,
107
- family: "action_closure",
108
- id: cycleId,
109
- redactionClass: "none",
110
- resolveStatus: "resolvable",
111
- },
113
+ const sourceRefs = params.proposalId
114
+ ? [
115
+ {
116
+ uri: `sn://proposal/${params.proposalId}`,
117
+ family: "action_closure",
118
+ id: params.proposalId,
119
+ redactionClass: "none",
120
+ resolveStatus: "resolvable",
121
+ },
122
+ ]
123
+ : [];
124
+ const proofRefs = [
125
+ closureTraceRef(closureId),
126
+ ...(params.decisionId ? [decisionProofRef(params.decisionId)] : []),
112
127
  ];
113
- if (params.decisionId) {
114
- sourceRefs.push({
115
- uri: `sn://decision/${params.decisionId}`,
116
- family: "action_closure",
117
- id: params.decisionId,
118
- redactionClass: "none",
119
- resolveStatus: "resolvable",
120
- });
121
- }
122
128
  const result = await writeActionClosureRecord(db, {
123
129
  id: closureId,
124
130
  createdAt: now,
@@ -131,14 +137,15 @@ export async function recordPolicyOutcomeClosure(db, cycleId, closureStatus, rea
131
137
  reason,
132
138
  nextState: params.nextState ?? "await_next_cycle",
133
139
  sourceRefs,
140
+ proofRefs,
141
+ traceRefs: [cycleTraceRef(cycleId)],
134
142
  redactionClass: "none",
135
- lifecycleStatus: "closed",
136
- payloadJson: JSON.stringify({
143
+ payload: {
137
144
  dispatchAttempt: 1,
138
145
  inputSummary: buildInputSummary(params.proposalId, params.decisionId),
139
146
  postProcessing: params.postProcessing ?? [],
140
147
  downgradedActionKind: params.downgradedActionKind,
141
- }),
148
+ },
142
149
  });
143
150
  if ("reason" in result) {
144
151
  return result;
@@ -151,14 +158,19 @@ export async function recordPolicyOutcomeClosure(db, cycleId, closureStatus, rea
151
158
  export async function recordExecutionClosure(db, cycleId, closureStatus, reason, params, options) {
152
159
  const now = options?.now ?? new Date().toISOString();
153
160
  const closureId = `cls_exec_${closureStatus}_${cycleId}_${now.replace(/[:.]/g, "")}`;
154
- const sourceRefs = [
155
- {
156
- uri: `sn://closure/${closureStatus}/${cycleId}`,
157
- family: "action_closure",
158
- id: cycleId,
159
- redactionClass: "none",
160
- resolveStatus: "resolvable",
161
- },
161
+ const sourceRefs = params.executionResultRef
162
+ ? [
163
+ {
164
+ uri: params.executionResultRef,
165
+ family: "connector_result",
166
+ id: params.executionResultRef,
167
+ redactionClass: "none",
168
+ resolveStatus: "resolvable",
169
+ },
170
+ ]
171
+ : [];
172
+ const proofRefs = [
173
+ closureTraceRef(closureId),
162
174
  ];
163
175
  const result = await writeActionClosureRecord(db, {
164
176
  id: closureId,
@@ -172,15 +184,16 @@ export async function recordExecutionClosure(db, cycleId, closureStatus, reason,
172
184
  reason,
173
185
  nextState: params.nextState ?? (closureStatus === "completed" ? "await_next_cycle" : "retryable"),
174
186
  sourceRefs,
187
+ proofRefs,
188
+ traceRefs: [cycleTraceRef(cycleId)],
175
189
  redactionClass: "none",
176
- lifecycleStatus: "closed",
177
- payloadJson: JSON.stringify({
190
+ payload: {
178
191
  dispatchAttempt: 1,
179
192
  executionResultRef: params.executionResultRef,
180
193
  outputSummary: params.outputSummary,
181
194
  inputSummary: buildInputSummary(params.proposalId, params.decisionId),
182
195
  retryable: params.retryable ?? closureStatus === "failed",
183
- }),
196
+ },
184
197
  });
185
198
  if ("reason" in result) {
186
199
  return result;
@@ -22,22 +22,13 @@
22
22
  *
23
23
  * Test coverage: tests/unit/action/action-proposal-builder.test.ts
24
24
  */
25
- import { readJudgmentVerdictById, writeActionClosureRecord, } from "../../../storage/v8-state-stores.js";
25
+ import { readJudgmentVerdictById, } from "../../../storage/v8-state-stores.js";
26
+ import { parseSourceRefs } from "../../../shared/serialization.js";
26
27
  import { ACTION_KIND_REGISTRY } from "../../../shared/types/v8-contracts.js";
28
+ import { classifyDegradedStatus } from "../../../shared/degraded-status-classifier.js";
27
29
  // ───────────────────────────────────────────────────────────────
28
30
  // Helpers
29
31
  // ───────────────────────────────────────────────────────────────
30
- function parseVerdictSourceRefs(json) {
31
- if (!json)
32
- return [];
33
- try {
34
- const parsed = JSON.parse(json);
35
- return Array.isArray(parsed) ? parsed : [];
36
- }
37
- catch {
38
- return [];
39
- }
40
- }
41
32
  function buildExpectedOutput(actionKind) {
42
33
  switch (actionKind) {
43
34
  case "ignore":
@@ -80,7 +71,7 @@ export async function buildActionProposal(db, judgmentVerdictId, options) {
80
71
  const verdict = readResult.row;
81
72
  if (!verdict) {
82
73
  return {
83
- status: "degraded",
74
+ status: classifyDegradedStatus("state_unreadable"),
84
75
  reason: "state_unreadable",
85
76
  ownerStage: "policy",
86
77
  sourceRefs: [],
@@ -99,8 +90,8 @@ export async function buildActionProposal(db, judgmentVerdictId, options) {
99
90
  judgmentVerdictId,
100
91
  };
101
92
  }
102
- const sourceRefs = parseVerdictSourceRefs(verdict.sourceRefsJson);
103
- // remember → memory review candidate closure (no direct projection)
93
+ const sourceRefs = parseSourceRefs(verdict.sourceRefsJson);
94
+ // remember → memory review candidate (no direct projection; orchestrator writes closure)
104
95
  if (actionKind === "remember") {
105
96
  const candidate = {
106
97
  closureSubtype: "remember_for_review",
@@ -131,27 +122,10 @@ export async function buildActionProposal(db, judgmentVerdictId, options) {
131
122
  },
132
123
  ]),
133
124
  };
134
- const closureId = `cls_remember_${judgmentVerdictId}_${now.replace(/[:.]/g, "")}`;
135
- const writeResult = await writeActionClosureRecord(db, {
136
- id: closureId,
137
- createdAt: now,
138
- cycleId,
139
- platformId: "heartbeat",
140
- status: "completed",
141
- reason: "remember_for_review",
142
- nextState: "pending_daily_review",
143
- sourceRefs: candidate.sourceRefs,
144
- redactionClass: "none",
145
- lifecycleStatus: "closed",
146
- payloadJson: JSON.stringify({ memoryReviewCandidate: candidate }),
147
- });
148
- if ("reason" in writeResult) {
149
- return writeResult;
150
- }
151
125
  return {
152
126
  status: "remember_for_review",
153
127
  memoryReviewCandidate: candidate,
154
- closureId,
128
+ closureId: `cls_remember_${judgmentVerdictId}_${now.replace(/[:.]/g, "")}`,
155
129
  };
156
130
  }
157
131
  // Actionable verdict → build proposal
@@ -201,7 +175,7 @@ export async function buildActionProposals(db, judgmentVerdictIds, options) {
201
175
  else if ("status" in result && result.status === "remember_for_review") {
202
176
  rememberForReviews.push(result);
203
177
  }
204
- else if ("status" in result && result.status === "degraded") {
178
+ else if ("operatorNextAction" in result) {
205
179
  failed.push({
206
180
  judgmentVerdictId,
207
181
  degraded: result,
@@ -33,6 +33,7 @@ export interface ConnectorDispatchRequest {
33
33
  decision: string;
34
34
  };
35
35
  sourceRefs: string;
36
+ proofRefs: string;
36
37
  }
37
38
  export interface GuidanceDispatchRequest {
38
39
  type: "guidance";
@@ -43,6 +44,7 @@ export interface GuidanceDispatchRequest {
43
44
  decision: string;
44
45
  };
45
46
  sourceRefs: string;
47
+ proofRefs: string;
46
48
  }
47
49
  export interface NoDispatchResult {
48
50
  type: "none";