@haaaiawd/second-nature 0.1.51 → 0.2.0

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 (71) hide show
  1. package/openclaw.plugin.json +29 -29
  2. package/package.json +55 -55
  3. package/runtime/cli/commands/index.js +326 -325
  4. package/runtime/cli/ops/heartbeat-surface.d.ts +84 -84
  5. package/runtime/cli/ops/heartbeat-surface.js +100 -100
  6. package/runtime/cli/ops/ops-router.js +1555 -1482
  7. package/runtime/cli/ops/workspace-heartbeat-runner.d.ts +85 -85
  8. package/runtime/cli/ops/workspace-heartbeat-runner.js +242 -242
  9. package/runtime/connectors/base/contract.d.ts +111 -111
  10. package/runtime/connectors/base/failure-taxonomy.d.ts +13 -13
  11. package/runtime/connectors/base/failure-taxonomy.js +186 -186
  12. package/runtime/connectors/base/map-life-evidence.js +137 -137
  13. package/runtime/connectors/base/policy-layer.js +202 -202
  14. package/runtime/connectors/evidence-normalizer.d.ts +45 -0
  15. package/runtime/connectors/evidence-normalizer.js +115 -0
  16. package/runtime/connectors/manifest/manifest-schema.d.ts +152 -152
  17. package/runtime/connectors/manifest/manifest-schema.js +54 -54
  18. package/runtime/connectors/services/connector-executor-adapter.d.ts +20 -20
  19. package/runtime/connectors/services/connector-executor-adapter.js +645 -645
  20. package/runtime/core/second-nature/action/action-closure-recorder.d.ts +70 -0
  21. package/runtime/core/second-nature/action/action-closure-recorder.js +184 -0
  22. package/runtime/core/second-nature/action/action-proposal-builder.d.ts +70 -0
  23. package/runtime/core/second-nature/action/action-proposal-builder.js +217 -0
  24. package/runtime/core/second-nature/action/autonomy-policy-evaluator.d.ts +43 -0
  25. package/runtime/core/second-nature/action/autonomy-policy-evaluator.js +213 -0
  26. package/runtime/core/second-nature/action/policy-bound-dispatch.d.ts +69 -0
  27. package/runtime/core/second-nature/action/policy-bound-dispatch.js +112 -0
  28. package/runtime/core/second-nature/body/tool-affordance/affordance-side-effect.d.ts +49 -0
  29. package/runtime/core/second-nature/body/tool-affordance/affordance-side-effect.js +100 -0
  30. package/runtime/core/second-nature/control-plane/accepted-projection-loader.d.ts +45 -0
  31. package/runtime/core/second-nature/control-plane/accepted-projection-loader.js +85 -0
  32. package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.d.ts +38 -0
  33. package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.js +165 -0
  34. package/runtime/core/second-nature/guidance/guidance-proposal-consumer.d.ts +51 -0
  35. package/runtime/core/second-nature/guidance/guidance-proposal-consumer.js +113 -0
  36. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.d.ts +24 -24
  37. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.js +61 -61
  38. package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +97 -97
  39. package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +397 -397
  40. package/runtime/core/second-nature/orchestrator/platform-capability-router.js +149 -149
  41. package/runtime/core/second-nature/perception/judgment-engine.d.ts +53 -0
  42. package/runtime/core/second-nature/perception/judgment-engine.js +239 -0
  43. package/runtime/core/second-nature/perception/perception-builder.d.ts +62 -0
  44. package/runtime/core/second-nature/perception/perception-builder.js +208 -0
  45. package/runtime/core/second-nature/perception/sensitivity-classifier.d.ts +37 -0
  46. package/runtime/core/second-nature/perception/sensitivity-classifier.js +87 -0
  47. package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.d.ts +44 -0
  48. package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.js +180 -0
  49. package/runtime/core/second-nature/quiet-dream/dream-scheduler.d.ts +36 -0
  50. package/runtime/core/second-nature/quiet-dream/dream-scheduler.js +105 -0
  51. package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.d.ts +36 -0
  52. package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.js +151 -0
  53. package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.d.ts +46 -0
  54. package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.js +123 -0
  55. package/runtime/observability/causal-loop-health.d.ts +44 -0
  56. package/runtime/observability/causal-loop-health.js +118 -0
  57. package/runtime/observability/diagnostic-redaction.d.ts +43 -0
  58. package/runtime/observability/diagnostic-redaction.js +114 -0
  59. package/runtime/observability/loop-stage-event-sink.d.ts +43 -0
  60. package/runtime/observability/loop-stage-event-sink.js +148 -0
  61. package/runtime/observability/loop-status.d.ts +46 -0
  62. package/runtime/observability/loop-status.js +85 -0
  63. package/runtime/shared/types/index.js +3 -0
  64. package/runtime/shared/types/v8-contracts.d.ts +86 -0
  65. package/runtime/shared/types/v8-contracts.js +84 -0
  66. package/runtime/storage/db/schema/index.d.ts +1 -0
  67. package/runtime/storage/db/schema/index.js +1 -0
  68. package/runtime/storage/db/schema/v8-entities.d.ts +1973 -0
  69. package/runtime/storage/db/schema/v8-entities.js +160 -0
  70. package/runtime/storage/v8-state-stores.d.ts +147 -0
  71. package/runtime/storage/v8-state-stores.js +491 -0
@@ -1,149 +1,149 @@
1
- function kindToCapability(kind) {
2
- if (kind === "exploration")
3
- return "feed.read";
4
- if (kind === "social")
5
- return "comment.reply";
6
- if (kind === "work")
7
- return "work.discover";
8
- if (kind === "outreach")
9
- return "message.send";
10
- return null;
11
- }
12
- function getPlatformIds(registry) {
13
- if (registry) {
14
- return registry.listRegisteredPlatformIds();
15
- }
16
- // Fallback: built-in platforms when registry is absent (backward compat)
17
- return ["moltbook", "instreet", "evomap", "agent-world"];
18
- }
19
- const FALLBACK_PLATFORM_CAPABILITIES = {
20
- "moltbook": ["feed.read", "post.publish", "comment.reply", "message.send"],
21
- "instreet": ["notification.list", "message.send", "comment.reply", "agent.heartbeat"],
22
- "evomap": ["agent.register", "agent.heartbeat", "work.discover", "task.claim"],
23
- "agent-world": ["feed.read", "work.discover", "task.claim"],
24
- };
25
- function fallbackPlatformSupportsCapability(platformId, kind) {
26
- const capability = kindToCapability(kind);
27
- if (!capability)
28
- return false;
29
- return (FALLBACK_PLATFORM_CAPABILITIES[platformId] ?? []).includes(capability);
30
- }
31
- function extractPlatformIdsFromGoals(goals, kind, platformIds) {
32
- const capability = kindToCapability(kind);
33
- const results = new Set();
34
- for (const goal of goals) {
35
- const text = `${goal.description} ${goal.completionCriteria ?? ""}`.toLowerCase();
36
- for (const pid of platformIds) {
37
- if (text.includes(pid)) {
38
- results.add(pid);
39
- }
40
- }
41
- // Also match if goal text contains the capability name (e.g. "feed.read")
42
- if (capability && text.includes(capability.toLowerCase())) {
43
- // capability alone doesn't tell us platform; keep for later
44
- }
45
- }
46
- return [...results];
47
- }
48
- function extractPlatformIdsFromEvidence(refs, platformIds) {
49
- const results = new Set();
50
- for (const ref of refs) {
51
- if (ref.kind === "connector_result" && ref.id) {
52
- for (const pid of platformIds) {
53
- if (ref.id.includes(pid)) {
54
- results.add(pid);
55
- }
56
- }
57
- }
58
- // Parse platform:// URIs (e.g. platform://moltbook/feed.read)
59
- if (ref.uri && ref.uri.startsWith("platform://")) {
60
- const afterScheme = ref.uri.slice("platform://".length);
61
- const platformPart = afterScheme.split("/")[0];
62
- if (platformPart && platformIds.includes(platformPart)) {
63
- results.add(platformPart);
64
- }
65
- }
66
- // L-02: Also support namespace format moltbook:feed.read (connector-system §5.3)
67
- if (ref.uri && !ref.uri.includes("://") && ref.uri.includes(":")) {
68
- const nsPart = ref.uri.split(":")[0];
69
- if (nsPart && platformIds.includes(nsPart)) {
70
- results.add(nsPart);
71
- }
72
- }
73
- }
74
- return [...results];
75
- }
76
- function validatePlatformCapability(platformId, kind, registry) {
77
- const capability = kindToCapability(kind);
78
- if (!capability)
79
- return false;
80
- try {
81
- return registry.hasCapability(platformId, capability);
82
- }
83
- catch (err) {
84
- // H-08: Log registry validation failures for observability.
85
- console.warn(`[platform-capability-router] Registry validation failed for ${platformId}:${capability}`, err);
86
- return false;
87
- }
88
- }
89
- /**
90
- * Resolve an explicit platformId for a candidate intent kind.
91
- * Returns `undefined` when no unambiguous platform can be inferred.
92
- */
93
- export function resolvePlatformForIntent(kind, context, registry) {
94
- const capability = kindToCapability(kind);
95
- if (!capability) {
96
- // Quiet, reflection, maintenance have no connector capability mapping.
97
- return undefined;
98
- }
99
- const platformIds = getPlatformIds(registry);
100
- const candidates = [];
101
- if (context.acceptedGoals && context.acceptedGoals.length > 0) {
102
- candidates.push(...extractPlatformIdsFromGoals(context.acceptedGoals, kind, platformIds));
103
- }
104
- if (context.evidenceRefs && context.evidenceRefs.length > 0) {
105
- candidates.push(...extractPlatformIdsFromEvidence(context.evidenceRefs, platformIds));
106
- }
107
- // Deduplicate while preserving order
108
- const ordered = [...new Set(candidates)];
109
- if (ordered.length > 1) {
110
- // Ambiguous: multiple platforms inferred → do not guess, return undefined.
111
- // Guard layer will deny with "ambiguous_platform" reason.
112
- return undefined;
113
- }
114
- if (ordered.length === 1) {
115
- const single = ordered[0];
116
- if (registry) {
117
- if (validatePlatformCapability(single, kind, registry)) {
118
- return single;
119
- }
120
- // Registry says unsupported → undefined (guard layer will deny)
121
- return undefined;
122
- }
123
- // No registry: keep legacy platform-name fallback, but do not invent an
124
- // unsupported platform/capability pair that later fails as protocol_mismatch.
125
- if (!fallbackPlatformSupportsCapability(single, kind)) {
126
- return undefined;
127
- }
128
- return single;
129
- }
130
- // No candidates inferred from goals/evidence → fallback to a supported platform.
131
- // Sort alphabetically so different capabilities map to different platforms over time,
132
- // preventing a single platform (e.g. moltbook) from monopolising all connector traffic.
133
- if (registry) {
134
- const supported = platformIds
135
- .filter((pid) => validatePlatformCapability(pid, kind, registry))
136
- .sort();
137
- if (supported.length > 0) {
138
- return supported[0];
139
- }
140
- }
141
- // No registry: use fallback capability mapping
142
- const supportedFallback = platformIds
143
- .filter((pid) => fallbackPlatformSupportsCapability(pid, kind))
144
- .sort();
145
- if (supportedFallback.length > 0) {
146
- return supportedFallback[0];
147
- }
148
- return undefined;
149
- }
1
+ function kindToCapability(kind) {
2
+ if (kind === "exploration")
3
+ return "feed.read";
4
+ if (kind === "social")
5
+ return "comment.reply";
6
+ if (kind === "work")
7
+ return "work.discover";
8
+ if (kind === "outreach")
9
+ return "message.send";
10
+ return null;
11
+ }
12
+ function getPlatformIds(registry) {
13
+ if (registry) {
14
+ return registry.listRegisteredPlatformIds();
15
+ }
16
+ // Fallback: built-in platforms when registry is absent (backward compat)
17
+ return ["moltbook", "instreet", "evomap", "agent-world"];
18
+ }
19
+ const FALLBACK_PLATFORM_CAPABILITIES = {
20
+ "moltbook": ["feed.read", "post.publish", "comment.reply", "message.send"],
21
+ "instreet": ["notification.list", "message.send", "comment.reply", "agent.heartbeat"],
22
+ "evomap": ["agent.register", "agent.heartbeat", "work.discover", "task.claim"],
23
+ "agent-world": ["feed.read", "work.discover", "task.claim"],
24
+ };
25
+ function fallbackPlatformSupportsCapability(platformId, kind) {
26
+ const capability = kindToCapability(kind);
27
+ if (!capability)
28
+ return false;
29
+ return (FALLBACK_PLATFORM_CAPABILITIES[platformId] ?? []).includes(capability);
30
+ }
31
+ function extractPlatformIdsFromGoals(goals, kind, platformIds) {
32
+ const capability = kindToCapability(kind);
33
+ const results = new Set();
34
+ for (const goal of goals) {
35
+ const text = `${goal.description} ${goal.completionCriteria ?? ""}`.toLowerCase();
36
+ for (const pid of platformIds) {
37
+ if (text.includes(pid)) {
38
+ results.add(pid);
39
+ }
40
+ }
41
+ // Also match if goal text contains the capability name (e.g. "feed.read")
42
+ if (capability && text.includes(capability.toLowerCase())) {
43
+ // capability alone doesn't tell us platform; keep for later
44
+ }
45
+ }
46
+ return [...results];
47
+ }
48
+ function extractPlatformIdsFromEvidence(refs, platformIds) {
49
+ const results = new Set();
50
+ for (const ref of refs) {
51
+ if (ref.kind === "connector_result" && ref.id) {
52
+ for (const pid of platformIds) {
53
+ if (ref.id.includes(pid)) {
54
+ results.add(pid);
55
+ }
56
+ }
57
+ }
58
+ // Parse platform:// URIs (e.g. platform://moltbook/feed.read)
59
+ if (ref.uri && ref.uri.startsWith("platform://")) {
60
+ const afterScheme = ref.uri.slice("platform://".length);
61
+ const platformPart = afterScheme.split("/")[0];
62
+ if (platformPart && platformIds.includes(platformPart)) {
63
+ results.add(platformPart);
64
+ }
65
+ }
66
+ // L-02: Also support namespace format moltbook:feed.read (connector-system §5.3)
67
+ if (ref.uri && !ref.uri.includes("://") && ref.uri.includes(":")) {
68
+ const nsPart = ref.uri.split(":")[0];
69
+ if (nsPart && platformIds.includes(nsPart)) {
70
+ results.add(nsPart);
71
+ }
72
+ }
73
+ }
74
+ return [...results];
75
+ }
76
+ function validatePlatformCapability(platformId, kind, registry) {
77
+ const capability = kindToCapability(kind);
78
+ if (!capability)
79
+ return false;
80
+ try {
81
+ return registry.hasCapability(platformId, capability);
82
+ }
83
+ catch (err) {
84
+ // H-08: Log registry validation failures for observability.
85
+ console.warn(`[platform-capability-router] Registry validation failed for ${platformId}:${capability}`, err);
86
+ return false;
87
+ }
88
+ }
89
+ /**
90
+ * Resolve an explicit platformId for a candidate intent kind.
91
+ * Returns `undefined` when no unambiguous platform can be inferred.
92
+ */
93
+ export function resolvePlatformForIntent(kind, context, registry) {
94
+ const capability = kindToCapability(kind);
95
+ if (!capability) {
96
+ // Quiet, reflection, maintenance have no connector capability mapping.
97
+ return undefined;
98
+ }
99
+ const platformIds = getPlatformIds(registry);
100
+ const candidates = [];
101
+ if (context.acceptedGoals && context.acceptedGoals.length > 0) {
102
+ candidates.push(...extractPlatformIdsFromGoals(context.acceptedGoals, kind, platformIds));
103
+ }
104
+ if (context.evidenceRefs && context.evidenceRefs.length > 0) {
105
+ candidates.push(...extractPlatformIdsFromEvidence(context.evidenceRefs, platformIds));
106
+ }
107
+ // Deduplicate while preserving order
108
+ const ordered = [...new Set(candidates)];
109
+ if (ordered.length > 1) {
110
+ // Ambiguous: multiple platforms inferred → do not guess, return undefined.
111
+ // Guard layer will deny with "ambiguous_platform" reason.
112
+ return undefined;
113
+ }
114
+ if (ordered.length === 1) {
115
+ const single = ordered[0];
116
+ if (registry) {
117
+ if (validatePlatformCapability(single, kind, registry)) {
118
+ return single;
119
+ }
120
+ // Registry says unsupported → undefined (guard layer will deny)
121
+ return undefined;
122
+ }
123
+ // No registry: keep legacy platform-name fallback, but do not invent an
124
+ // unsupported platform/capability pair that later fails as protocol_mismatch.
125
+ if (!fallbackPlatformSupportsCapability(single, kind)) {
126
+ return undefined;
127
+ }
128
+ return single;
129
+ }
130
+ // No candidates inferred from goals/evidence → fallback to a supported platform.
131
+ // Sort alphabetically so different capabilities map to different platforms over time,
132
+ // preventing a single platform (e.g. moltbook) from monopolising all connector traffic.
133
+ if (registry) {
134
+ const supported = platformIds
135
+ .filter((pid) => validatePlatformCapability(pid, kind, registry))
136
+ .sort();
137
+ if (supported.length > 0) {
138
+ return supported[0];
139
+ }
140
+ }
141
+ // No registry: use fallback capability mapping
142
+ const supportedFallback = platformIds
143
+ .filter((pid) => fallbackPlatformSupportsCapability(pid, kind))
144
+ .sort();
145
+ if (supportedFallback.length > 0) {
146
+ return supportedFallback[0];
147
+ }
148
+ return undefined;
149
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * JudgmentEngine — Produce JudgmentVerdict records from PerceptionCard.
3
+ *
4
+ * Core logic: Read a PerceptionCard, apply rules-only decision tree, and
5
+ * write a source-backed JudgmentVerdict. No model assist; deterministic
6
+ * verdict based on relevance, risk flags, source refs, and confidence.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/perception-judgment-system.detail.md §3.3`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/perception-judgment-system.md §5`
11
+ *
12
+ * Dependencies:
13
+ * - `src/storage/v8-state-stores.js` (readPerceptionCardById, writeJudgmentVerdict)
14
+ * - `src/shared/types/v8-contracts.js` (PlatformNeutralActionKind, SourceRef,
15
+ * DegradedOperationResult, V8ReasonCode, ACTION_KIND_REGISTRY)
16
+ *
17
+ * Boundary:
18
+ * - Does not execute actions; produces verdict only.
19
+ * - Does not write long-term memory; only emits review intent.
20
+ * - Degrades gracefully on missing card or unreadable state.
21
+ *
22
+ * Test coverage: tests/unit/judgment/judgment-engine.test.ts
23
+ */
24
+ import type { StateDatabase } from "../../../storage/db/index.js";
25
+ import type { SourceRef, DegradedOperationResult, PlatformNeutralActionKind, V8ReasonCode } from "../../../shared/types/v8-contracts.js";
26
+ export interface JudgmentVerdictResult {
27
+ id: string;
28
+ cycleId: string;
29
+ perceptionCardId: string;
30
+ actionKind: PlatformNeutralActionKind;
31
+ confidence: number;
32
+ reason: V8ReasonCode;
33
+ riskPosture: "low" | "medium" | "high" | "blocked";
34
+ sourceRefs: SourceRef[];
35
+ createdAt: string;
36
+ }
37
+ export interface RunAgentJudgmentResult {
38
+ status: "completed" | "blocked" | "degraded";
39
+ verdicts: JudgmentVerdictResult[];
40
+ reason?: V8ReasonCode;
41
+ }
42
+ export interface RunAgentJudgmentOptions {
43
+ now?: string;
44
+ }
45
+ export declare function runAgentJudgment(db: StateDatabase, perceptionCardId: string, options?: RunAgentJudgmentOptions): Promise<RunAgentJudgmentResult | DegradedOperationResult>;
46
+ export interface BatchJudgmentResult {
47
+ succeeded: JudgmentVerdictResult[];
48
+ failed: {
49
+ perceptionCardId: string;
50
+ degraded: DegradedOperationResult;
51
+ }[];
52
+ }
53
+ export declare function runAgentJudgments(db: StateDatabase, perceptionCardIds: string[], options?: RunAgentJudgmentOptions): Promise<BatchJudgmentResult>;
@@ -0,0 +1,239 @@
1
+ /**
2
+ * JudgmentEngine — Produce JudgmentVerdict records from PerceptionCard.
3
+ *
4
+ * Core logic: Read a PerceptionCard, apply rules-only decision tree, and
5
+ * write a source-backed JudgmentVerdict. No model assist; deterministic
6
+ * verdict based on relevance, risk flags, source refs, and confidence.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/perception-judgment-system.detail.md §3.3`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/perception-judgment-system.md §5`
11
+ *
12
+ * Dependencies:
13
+ * - `src/storage/v8-state-stores.js` (readPerceptionCardById, writeJudgmentVerdict)
14
+ * - `src/shared/types/v8-contracts.js` (PlatformNeutralActionKind, SourceRef,
15
+ * DegradedOperationResult, V8ReasonCode, ACTION_KIND_REGISTRY)
16
+ *
17
+ * Boundary:
18
+ * - Does not execute actions; produces verdict only.
19
+ * - Does not write long-term memory; only emits review intent.
20
+ * - Degrades gracefully on missing card or unreadable state.
21
+ *
22
+ * Test coverage: tests/unit/judgment/judgment-engine.test.ts
23
+ */
24
+ import { readPerceptionCardById, writeJudgmentVerdict, } from "../../../storage/v8-state-stores.js";
25
+ import { ACTION_KIND_REGISTRY } from "../../../shared/types/v8-contracts.js";
26
+ // ───────────────────────────────────────────────────────────────
27
+ // Config
28
+ // ───────────────────────────────────────────────────────────────
29
+ const MIN_EXTERNAL_ACTION_CONFIDENCE = 0.70;
30
+ // ───────────────────────────────────────────────────────────────
31
+ // Helpers
32
+ // ───────────────────────────────────────────────────────────────
33
+ function inferRiskPosture(sensitivityClass, riskFlags) {
34
+ if (sensitivityClass === "sensitive" || riskFlags.includes("credential_shape_detected")) {
35
+ return "blocked";
36
+ }
37
+ if (sensitivityClass === "private_context" || riskFlags.includes("private_context")) {
38
+ return "high";
39
+ }
40
+ if (sensitivityClass === "public_technical") {
41
+ return "medium";
42
+ }
43
+ return "low";
44
+ }
45
+ function parseCardSourceRefs(json) {
46
+ if (!json)
47
+ return [];
48
+ try {
49
+ const parsed = JSON.parse(json);
50
+ return Array.isArray(parsed) ? parsed : [];
51
+ }
52
+ catch {
53
+ return [];
54
+ }
55
+ }
56
+ function selectVerdict(relevance, confidence, riskPosture, hasSourceRefs, possibleIntents) {
57
+ // Missing source refs → ignore/watch only
58
+ if (!hasSourceRefs) {
59
+ return {
60
+ actionKind: relevance > 0.5 ? "watch" : "ignore",
61
+ reason: "judgment_missing_source_refs",
62
+ finalConfidence: 0.5,
63
+ };
64
+ }
65
+ // Risk blocked → watch or notify_owner only, no external write
66
+ if (riskPosture === "blocked") {
67
+ return {
68
+ actionKind: relevance > 0.5 ? "notify_owner" : "watch",
69
+ reason: "proposal_risk_blocked",
70
+ finalConfidence: 0.6,
71
+ };
72
+ }
73
+ // Low confidence → no auto/draft external write
74
+ const canExternalWrite = confidence >= MIN_EXTERNAL_ACTION_CONFIDENCE;
75
+ // Low relevance → ignore/watch
76
+ if (relevance <= 0.3) {
77
+ return {
78
+ actionKind: relevance > 0.15 ? "watch" : "ignore",
79
+ reason: "judgment_low_confidence",
80
+ finalConfidence: 0.4,
81
+ };
82
+ }
83
+ // Medium/high relevance + low risk → actionable
84
+ const preferredIntent = possibleIntents.find((intent) => {
85
+ const meta = ACTION_KIND_REGISTRY[intent];
86
+ if (!meta)
87
+ return false;
88
+ if (meta.sideEffectClass === "external_write" && !canExternalWrite)
89
+ return false;
90
+ if (meta.sideEffectClass === "external_write" && riskPosture === "high")
91
+ return false;
92
+ return true;
93
+ });
94
+ if (preferredIntent) {
95
+ return {
96
+ actionKind: preferredIntent,
97
+ reason: "proposal_created",
98
+ finalConfidence: confidence,
99
+ };
100
+ }
101
+ // Fallback to watch if no actionable intent allowed
102
+ return {
103
+ actionKind: "watch",
104
+ reason: "judgment_low_confidence",
105
+ finalConfidence: 0.5,
106
+ };
107
+ }
108
+ // ───────────────────────────────────────────────────────────────
109
+ // Public API
110
+ // ───────────────────────────────────────────────────────────────
111
+ export async function runAgentJudgment(db, perceptionCardId, options) {
112
+ const now = options?.now ?? new Date().toISOString();
113
+ const readResult = await readPerceptionCardById(db, perceptionCardId);
114
+ if (readResult.degraded) {
115
+ return readResult.degraded;
116
+ }
117
+ const card = readResult.row;
118
+ if (!card) {
119
+ return {
120
+ status: "degraded",
121
+ reason: "state_unreadable",
122
+ ownerStage: "judgment",
123
+ sourceRefs: [],
124
+ operatorNextAction: `PerceptionCard ${perceptionCardId} not found`,
125
+ retryable: false,
126
+ };
127
+ }
128
+ const sourceRefs = parseCardSourceRefs(card.sourceRefsJson);
129
+ const hasSourceRefs = sourceRefs.length > 0;
130
+ // Parse sensitivity class from payload (stored there by perception-builder)
131
+ let sensitivityClass = "public_general";
132
+ if (card.payloadJson) {
133
+ try {
134
+ const payload = JSON.parse(card.payloadJson);
135
+ if (payload.sensitivityClass)
136
+ sensitivityClass = String(payload.sensitivityClass);
137
+ }
138
+ catch {
139
+ /* ignore */
140
+ }
141
+ }
142
+ const riskPosture = inferRiskPosture(sensitivityClass, card.riskFlagsJson ? JSON.parse(card.riskFlagsJson) : []);
143
+ // Parse possible intents from payload
144
+ let possibleIntents = ["watch"];
145
+ if (card.payloadJson) {
146
+ try {
147
+ const payload = JSON.parse(card.payloadJson);
148
+ if (payload.possibleIntents && Array.isArray(payload.possibleIntents)) {
149
+ possibleIntents = payload.possibleIntents;
150
+ }
151
+ }
152
+ catch {
153
+ /* ignore */
154
+ }
155
+ }
156
+ const { actionKind, reason, finalConfidence } = selectVerdict(card.relevance ?? 0.3, card.confidence ?? 0.6, riskPosture, hasSourceRefs, possibleIntents);
157
+ const verdict = {
158
+ id: `jud_${perceptionCardId}_${now.replace(/[:.]/g, "")}`,
159
+ cycleId: card.cycleId,
160
+ perceptionCardId,
161
+ actionKind,
162
+ confidence: finalConfidence,
163
+ reason,
164
+ riskPosture,
165
+ sourceRefs: hasSourceRefs
166
+ ? sourceRefs
167
+ : [
168
+ {
169
+ uri: `sn://judgment/missing_source_refs/${perceptionCardId}`,
170
+ family: "judgment",
171
+ id: perceptionCardId,
172
+ redactionClass: "none",
173
+ resolveStatus: "missing",
174
+ },
175
+ ],
176
+ createdAt: now,
177
+ };
178
+ const writeResult = await writeJudgmentVerdict(db, {
179
+ id: verdict.id,
180
+ createdAt: now,
181
+ cycleId: verdict.cycleId,
182
+ perceptionCardId: verdict.perceptionCardId,
183
+ actionKind: verdict.actionKind,
184
+ confidence: verdict.confidence,
185
+ reason: verdict.reason,
186
+ riskPosture: verdict.riskPosture,
187
+ sourceRefs: verdict.sourceRefs,
188
+ redactionClass: riskPosture === "blocked" ? "blocked" : "none",
189
+ lifecycleStatus: "pending",
190
+ payloadJson: JSON.stringify({
191
+ possibleIntents,
192
+ sensitivityClass,
193
+ }),
194
+ });
195
+ if ("reason" in writeResult) {
196
+ return {
197
+ status: "degraded",
198
+ verdicts: [],
199
+ reason: writeResult.reason,
200
+ };
201
+ }
202
+ return {
203
+ status: riskPosture === "blocked" ? "blocked" : "completed",
204
+ verdicts: [verdict],
205
+ reason,
206
+ };
207
+ }
208
+ export async function runAgentJudgments(db, perceptionCardIds, options) {
209
+ const succeeded = [];
210
+ const failed = [];
211
+ for (const perceptionCardId of perceptionCardIds) {
212
+ const result = await runAgentJudgment(db, perceptionCardId, options);
213
+ if ("verdicts" in result) {
214
+ if (result.status === "completed" || result.status === "blocked") {
215
+ succeeded.push(...result.verdicts);
216
+ }
217
+ else {
218
+ failed.push({
219
+ perceptionCardId,
220
+ degraded: {
221
+ status: "degraded",
222
+ reason: result.reason ?? "judgment_low_confidence",
223
+ ownerStage: "judgment",
224
+ sourceRefs: [],
225
+ operatorNextAction: `Judgment failed for ${perceptionCardId}`,
226
+ retryable: true,
227
+ },
228
+ });
229
+ }
230
+ }
231
+ else {
232
+ failed.push({
233
+ perceptionCardId,
234
+ degraded: result,
235
+ });
236
+ }
237
+ }
238
+ return { succeeded, failed };
239
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * PerceptionBuilder — Generate PerceptionCard records from EvidenceItem batches.
3
+ *
4
+ * Core logic: Read pending evidence, deduplicate by content hash, build
5
+ * PerceptionCard with topic, entities, novelty, relevance, summary, risk
6
+ * flags, confidence, and reviewPriority. Rules-only fallback when model
7
+ * assist is unavailable.
8
+ *
9
+ * Design authority:
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/perception-judgment-system.detail.md §3.1`
11
+ * - `.anws/v8/04_SYSTEM_DESIGN/perception-judgment-system.md §5`
12
+ *
13
+ * Dependencies:
14
+ * - `src/storage/v8-state-stores.js` (readEvidenceItemsByStatus, writePerceptionCard)
15
+ * - `src/shared/types/v8-contracts.js` (PerceptionCard fields)
16
+ *
17
+ * Boundary:
18
+ * - Does not judge actionability; that is judgment's job.
19
+ * - Does not fabricate perception on empty input.
20
+ * - Rules-only path is deterministic and source-backed.
21
+ *
22
+ * Test coverage: tests/unit/perception/perception-builder.test.ts
23
+ */
24
+ import type { StateDatabase } from "../../../storage/db/index.js";
25
+ import type { SourceRef, DegradedOperationResult, PlatformNeutralActionKind, V8ReasonCode } from "../../../shared/types/v8-contracts.js";
26
+ export interface EvidenceItemInput {
27
+ id: string;
28
+ platformId: string;
29
+ contentHash: string;
30
+ observedAt: string;
31
+ sensitivityHint?: string;
32
+ sourceRefsJson: string;
33
+ payloadJson?: string | null;
34
+ }
35
+ export interface PerceptionCardResult {
36
+ id: string;
37
+ cycleId: string;
38
+ topic: string;
39
+ entities: string[];
40
+ novelty: "new" | "recurring" | "update";
41
+ relevance: number;
42
+ summary: string;
43
+ possibleIntents: PlatformNeutralActionKind[];
44
+ reviewPriority: "low" | "medium" | "high";
45
+ sensitivityClass: string;
46
+ riskFlags: string[];
47
+ confidence: number;
48
+ evidenceRefs: SourceRef[];
49
+ createdAt: string;
50
+ }
51
+ export interface BuildPerceptionCardsResult {
52
+ status: "completed" | "rules_only" | "blocked" | "empty" | "degraded";
53
+ cards: PerceptionCardResult[];
54
+ reason?: V8ReasonCode;
55
+ truncated?: boolean;
56
+ }
57
+ export interface BuildPerceptionCardsOptions {
58
+ cycleId: string;
59
+ maxEvidence?: number;
60
+ now?: string;
61
+ }
62
+ export declare function buildPerceptionCards(db: StateDatabase, options: BuildPerceptionCardsOptions): Promise<BuildPerceptionCardsResult | DegradedOperationResult>;