@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
@@ -0,0 +1,70 @@
1
+ /**
2
+ * ActionClosureRecorder — Record heartbeat cycle closure outcomes.
3
+ *
4
+ * Core logic: Write ActionClosureRecord for no-action, completed, denied,
5
+ * deferred, downgraded, and failed outcomes. Handles idempotent retry
6
+ * and remember-for-review memory candidates.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.detail.md §3.4`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.md §4.2`
11
+ *
12
+ * Dependencies:
13
+ * - `src/storage/v8-state-stores.js` (writeActionClosureRecord, readActionClosuresByCycle)
14
+ * - `src/shared/types/v8-contracts.js` (SourceRef, DegradedOperationResult, V8ReasonCode)
15
+ *
16
+ * Boundary:
17
+ * - Does not execute actions; only records outcomes.
18
+ * - Does not form long-term memory; only emits review intent.
19
+ * - Degrades gracefully on DB failure.
20
+ *
21
+ * Test coverage: tests/unit/action/action-closure-recorder.test.ts
22
+ */
23
+ import type { StateDatabase } from "../../../storage/db/index.js";
24
+ import type { SourceRef, DegradedOperationResult, V8ReasonCode, MemoryReviewCandidateClosure } from "../../../shared/types/v8-contracts.js";
25
+ export type ClosureStatus = "completed" | "no_action" | "denied" | "deferred" | "downgraded" | "failed";
26
+ export interface ActionClosureRecord {
27
+ id: string;
28
+ cycleId: string;
29
+ proposalId?: string;
30
+ decisionId?: string;
31
+ idempotencyKey?: string;
32
+ retryOfClosureId?: string;
33
+ dispatchAttempt: number;
34
+ closureStatus: ClosureStatus;
35
+ inputSummary: string;
36
+ outputSummary?: string;
37
+ postProcessing: string[];
38
+ nextState: string;
39
+ reason: V8ReasonCode;
40
+ sourceRefs: SourceRef[];
41
+ memoryReviewCandidate?: MemoryReviewCandidateClosure;
42
+ closedAt: string;
43
+ }
44
+ export interface RecordClosureOptions {
45
+ now?: string;
46
+ }
47
+ export type RecordClosureResult = {
48
+ status: "recorded";
49
+ closureId: string;
50
+ } | {
51
+ status: "idempotent";
52
+ closureId: string;
53
+ } | DegradedOperationResult;
54
+ export declare function recordNoActionClosure(db: StateDatabase, cycleId: string, noActionReason: V8ReasonCode, options?: RecordClosureOptions): Promise<RecordClosureResult>;
55
+ export declare function recordRememberClosure(db: StateDatabase, cycleId: string, memoryReviewCandidate: MemoryReviewCandidateClosure, options?: RecordClosureOptions): Promise<RecordClosureResult>;
56
+ export declare function recordPolicyOutcomeClosure(db: StateDatabase, cycleId: string, closureStatus: ClosureStatus, reason: V8ReasonCode, params: {
57
+ proposalId?: string;
58
+ decisionId?: string;
59
+ downgradedActionKind?: string;
60
+ postProcessing?: string[];
61
+ nextState?: string;
62
+ }, options?: RecordClosureOptions): Promise<RecordClosureResult>;
63
+ export declare function recordExecutionClosure(db: StateDatabase, cycleId: string, closureStatus: "completed" | "failed", reason: V8ReasonCode, params: {
64
+ proposalId: string;
65
+ decisionId: string;
66
+ executionResultRef?: string;
67
+ outputSummary?: string;
68
+ nextState?: string;
69
+ retryable?: boolean;
70
+ }, options?: RecordClosureOptions): Promise<RecordClosureResult>;
@@ -0,0 +1,184 @@
1
+ /**
2
+ * ActionClosureRecorder — Record heartbeat cycle closure outcomes.
3
+ *
4
+ * Core logic: Write ActionClosureRecord for no-action, completed, denied,
5
+ * deferred, downgraded, and failed outcomes. Handles idempotent retry
6
+ * and remember-for-review memory candidates.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.detail.md §3.4`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.md §4.2`
11
+ *
12
+ * Dependencies:
13
+ * - `src/storage/v8-state-stores.js` (writeActionClosureRecord, readActionClosuresByCycle)
14
+ * - `src/shared/types/v8-contracts.js` (SourceRef, DegradedOperationResult, V8ReasonCode)
15
+ *
16
+ * Boundary:
17
+ * - Does not execute actions; only records outcomes.
18
+ * - Does not form long-term memory; only emits review intent.
19
+ * - Degrades gracefully on DB failure.
20
+ *
21
+ * Test coverage: tests/unit/action/action-closure-recorder.test.ts
22
+ */
23
+ import { writeActionClosureRecord, readActionClosuresByCycle, } from "../../../storage/v8-state-stores.js";
24
+ // ───────────────────────────────────────────────────────────────
25
+ // Helpers
26
+ // ───────────────────────────────────────────────────────────────
27
+ function buildInputSummary(proposalId, decisionId) {
28
+ const parts = [];
29
+ if (proposalId)
30
+ parts.push(`proposal=${proposalId}`);
31
+ if (decisionId)
32
+ parts.push(`decision=${decisionId}`);
33
+ return parts.join(" ") || "no-action";
34
+ }
35
+ // ───────────────────────────────────────────────────────────────
36
+ // Public API — No action
37
+ // ───────────────────────────────────────────────────────────────
38
+ export async function recordNoActionClosure(db, cycleId, noActionReason, options) {
39
+ const now = options?.now ?? new Date().toISOString();
40
+ const closureId = `cls_no_${cycleId}`;
41
+ const existing = await readActionClosuresByCycle(db, cycleId);
42
+ if (!existing.degraded && existing.rows.some((r) => r.status === "no_action")) {
43
+ return { status: "idempotent", closureId };
44
+ }
45
+ const result = await writeActionClosureRecord(db, {
46
+ id: closureId,
47
+ createdAt: now,
48
+ cycleId,
49
+ status: "no_action",
50
+ reason: noActionReason,
51
+ nextState: "await_next_cycle",
52
+ sourceRefs: [
53
+ {
54
+ uri: `sn://closure/no_action/${cycleId}`,
55
+ family: "action_closure",
56
+ id: cycleId,
57
+ redactionClass: "none",
58
+ resolveStatus: "resolvable",
59
+ },
60
+ ],
61
+ redactionClass: "none",
62
+ lifecycleStatus: "closed",
63
+ payloadJson: JSON.stringify({ dispatchAttempt: 0, inputSummary: "no-action" }),
64
+ });
65
+ if ("reason" in result) {
66
+ return result;
67
+ }
68
+ return { status: "recorded", closureId };
69
+ }
70
+ // ───────────────────────────────────────────────────────────────
71
+ // Public API — Remember for review
72
+ // ───────────────────────────────────────────────────────────────
73
+ export async function recordRememberClosure(db, cycleId, memoryReviewCandidate, options) {
74
+ const now = options?.now ?? new Date().toISOString();
75
+ const closureId = `cls_remember_${cycleId}_${now.replace(/[:.]/g, "")}`;
76
+ const result = await writeActionClosureRecord(db, {
77
+ id: closureId,
78
+ createdAt: now,
79
+ cycleId,
80
+ status: "completed",
81
+ reason: "remember_for_review",
82
+ nextState: "pending_daily_review",
83
+ sourceRefs: memoryReviewCandidate.sourceRefs,
84
+ redactionClass: "none",
85
+ lifecycleStatus: "closed",
86
+ payloadJson: JSON.stringify({
87
+ memoryReviewCandidate,
88
+ dispatchAttempt: 1,
89
+ inputSummary: `remember_for_review topic=${memoryReviewCandidate.topicKey}`,
90
+ }),
91
+ });
92
+ if ("reason" in result) {
93
+ return result;
94
+ }
95
+ return { status: "recorded", closureId };
96
+ }
97
+ // ───────────────────────────────────────────────────────────────
98
+ // Public API — Policy decision outcome
99
+ // ───────────────────────────────────────────────────────────────
100
+ export async function recordPolicyOutcomeClosure(db, cycleId, closureStatus, reason, params, options) {
101
+ const now = options?.now ?? new Date().toISOString();
102
+ const closureId = `cls_${closureStatus}_${cycleId}_${now.replace(/[:.]/g, "")}`;
103
+ const sourceRefs = [
104
+ {
105
+ uri: `sn://closure/${closureStatus}/${cycleId}`,
106
+ family: "action_closure",
107
+ id: cycleId,
108
+ redactionClass: "none",
109
+ resolveStatus: "resolvable",
110
+ },
111
+ ];
112
+ if (params.decisionId) {
113
+ sourceRefs.push({
114
+ uri: `sn://decision/${params.decisionId}`,
115
+ family: "action_closure",
116
+ id: params.decisionId,
117
+ redactionClass: "none",
118
+ resolveStatus: "resolvable",
119
+ });
120
+ }
121
+ const result = await writeActionClosureRecord(db, {
122
+ id: closureId,
123
+ createdAt: now,
124
+ cycleId,
125
+ proposalId: params.proposalId,
126
+ decisionId: params.decisionId,
127
+ status: closureStatus,
128
+ reason,
129
+ nextState: params.nextState ?? "await_next_cycle",
130
+ sourceRefs,
131
+ redactionClass: "none",
132
+ lifecycleStatus: "closed",
133
+ payloadJson: JSON.stringify({
134
+ dispatchAttempt: 1,
135
+ inputSummary: buildInputSummary(params.proposalId, params.decisionId),
136
+ postProcessing: params.postProcessing ?? [],
137
+ downgradedActionKind: params.downgradedActionKind,
138
+ }),
139
+ });
140
+ if ("reason" in result) {
141
+ return result;
142
+ }
143
+ return { status: "recorded", closureId };
144
+ }
145
+ // ───────────────────────────────────────────────────────────────
146
+ // Public API — Execution outcome
147
+ // ───────────────────────────────────────────────────────────────
148
+ export async function recordExecutionClosure(db, cycleId, closureStatus, reason, params, options) {
149
+ const now = options?.now ?? new Date().toISOString();
150
+ const closureId = `cls_exec_${closureStatus}_${cycleId}_${now.replace(/[:.]/g, "")}`;
151
+ const sourceRefs = [
152
+ {
153
+ uri: `sn://closure/${closureStatus}/${cycleId}`,
154
+ family: "action_closure",
155
+ id: cycleId,
156
+ redactionClass: "none",
157
+ resolveStatus: "resolvable",
158
+ },
159
+ ];
160
+ const result = await writeActionClosureRecord(db, {
161
+ id: closureId,
162
+ createdAt: now,
163
+ cycleId,
164
+ proposalId: params.proposalId,
165
+ decisionId: params.decisionId,
166
+ status: closureStatus,
167
+ reason,
168
+ nextState: params.nextState ?? (closureStatus === "completed" ? "await_next_cycle" : "retryable"),
169
+ sourceRefs,
170
+ redactionClass: "none",
171
+ lifecycleStatus: "closed",
172
+ payloadJson: JSON.stringify({
173
+ dispatchAttempt: 1,
174
+ executionResultRef: params.executionResultRef,
175
+ outputSummary: params.outputSummary,
176
+ inputSummary: buildInputSummary(params.proposalId, params.decisionId),
177
+ retryable: params.retryable ?? closureStatus === "failed",
178
+ }),
179
+ });
180
+ if ("reason" in result) {
181
+ return result;
182
+ }
183
+ return { status: "recorded", closureId };
184
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * ActionProposalBuilder — Convert JudgmentVerdict into ActionProposal.
3
+ *
4
+ * Core logic: Read a verdict, map action kind to side-effect class and
5
+ * expected output, and write an ActionProposal row. For `remember` verdicts,
6
+ * emit a MemoryReviewCandidateClosure instead of an executable proposal.
7
+ * For `ignore` / `watch`, return a no-action result.
8
+ *
9
+ * Design authority:
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.detail.md §3.1`
11
+ * - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.md §4.2`
12
+ *
13
+ * Dependencies:
14
+ * - `src/storage/v8-state-stores.js` (readJudgmentVerdictById, writeActionClosureRecord)
15
+ * - `src/shared/types/v8-contracts.js` (PlatformNeutralActionKind, SourceRef,
16
+ * DegradedOperationResult, V8ReasonCode, ACTION_KIND_REGISTRY)
17
+ *
18
+ * Boundary:
19
+ * - Does not evaluate policy; only builds proposal payload.
20
+ * - Does not write long-term memory on `remember`; emits review intent only.
21
+ * - Degrades gracefully on missing verdict or unreadable state.
22
+ *
23
+ * Test coverage: tests/unit/action/action-proposal-builder.test.ts
24
+ */
25
+ import type { StateDatabase } from "../../../storage/db/index.js";
26
+ import type { SourceRef, DegradedOperationResult, PlatformNeutralActionKind, V8ReasonCode, MemoryReviewCandidateClosure } from "../../../shared/types/v8-contracts.js";
27
+ export interface ActionProposal {
28
+ id: string;
29
+ cycleId: string;
30
+ judgmentVerdictId: string;
31
+ actionKind: PlatformNeutralActionKind;
32
+ targetPlatformId?: string;
33
+ targetCapabilityId?: string;
34
+ sourceRefs: SourceRef[];
35
+ reason: V8ReasonCode;
36
+ riskPosture: "low" | "medium" | "high" | "blocked";
37
+ expectedOutput: string;
38
+ sideEffectClass: string;
39
+ idempotencyKey: string;
40
+ createdAt: string;
41
+ }
42
+ export interface NoActionResult {
43
+ status: "no_action";
44
+ reason: V8ReasonCode;
45
+ cycleId: string;
46
+ judgmentVerdictId: string;
47
+ }
48
+ export interface RememberForReviewResult {
49
+ status: "remember_for_review";
50
+ memoryReviewCandidate: MemoryReviewCandidateClosure;
51
+ closureId: string;
52
+ }
53
+ export type BuildActionProposalResult = {
54
+ status: "proposal";
55
+ proposal: ActionProposal;
56
+ } | NoActionResult | RememberForReviewResult;
57
+ export interface BuildActionProposalOptions {
58
+ now?: string;
59
+ }
60
+ export declare function buildActionProposal(db: StateDatabase, judgmentVerdictId: string, options?: BuildActionProposalOptions): Promise<BuildActionProposalResult | DegradedOperationResult>;
61
+ export interface BatchBuildProposalResult {
62
+ proposals: ActionProposal[];
63
+ noActions: NoActionResult[];
64
+ rememberForReviews: RememberForReviewResult[];
65
+ failed: {
66
+ judgmentVerdictId: string;
67
+ degraded: DegradedOperationResult;
68
+ }[];
69
+ }
70
+ export declare function buildActionProposals(db: StateDatabase, judgmentVerdictIds: string[], options?: BuildActionProposalOptions): Promise<BatchBuildProposalResult>;
@@ -0,0 +1,217 @@
1
+ /**
2
+ * ActionProposalBuilder — Convert JudgmentVerdict into ActionProposal.
3
+ *
4
+ * Core logic: Read a verdict, map action kind to side-effect class and
5
+ * expected output, and write an ActionProposal row. For `remember` verdicts,
6
+ * emit a MemoryReviewCandidateClosure instead of an executable proposal.
7
+ * For `ignore` / `watch`, return a no-action result.
8
+ *
9
+ * Design authority:
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.detail.md §3.1`
11
+ * - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.md §4.2`
12
+ *
13
+ * Dependencies:
14
+ * - `src/storage/v8-state-stores.js` (readJudgmentVerdictById, writeActionClosureRecord)
15
+ * - `src/shared/types/v8-contracts.js` (PlatformNeutralActionKind, SourceRef,
16
+ * DegradedOperationResult, V8ReasonCode, ACTION_KIND_REGISTRY)
17
+ *
18
+ * Boundary:
19
+ * - Does not evaluate policy; only builds proposal payload.
20
+ * - Does not write long-term memory on `remember`; emits review intent only.
21
+ * - Degrades gracefully on missing verdict or unreadable state.
22
+ *
23
+ * Test coverage: tests/unit/action/action-proposal-builder.test.ts
24
+ */
25
+ import { readJudgmentVerdictById, writeActionClosureRecord, } from "../../../storage/v8-state-stores.js";
26
+ import { ACTION_KIND_REGISTRY } from "../../../shared/types/v8-contracts.js";
27
+ // ───────────────────────────────────────────────────────────────
28
+ // Helpers
29
+ // ───────────────────────────────────────────────────────────────
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
+ function buildExpectedOutput(actionKind) {
42
+ switch (actionKind) {
43
+ case "ignore":
44
+ return "No action required";
45
+ case "watch":
46
+ return "Monitor for changes";
47
+ case "remember":
48
+ return "Queue for daily review";
49
+ case "notify_owner":
50
+ return "Send owner notification";
51
+ case "draft_reply":
52
+ return "Generate reply draft";
53
+ case "auto_reply":
54
+ return "Send automated reply";
55
+ case "draft_publish":
56
+ return "Generate publish draft";
57
+ case "auto_publish":
58
+ return "Publish to platform";
59
+ case "run_connector":
60
+ return "Execute connector capability";
61
+ default:
62
+ return "Unknown action";
63
+ }
64
+ }
65
+ function inferTargetPlatform(actionKind) {
66
+ if (actionKind === "run_connector") {
67
+ return { platformId: "connector", capabilityId: "run_connector" };
68
+ }
69
+ return {};
70
+ }
71
+ // ───────────────────────────────────────────────────────────────
72
+ // Public API
73
+ // ───────────────────────────────────────────────────────────────
74
+ export async function buildActionProposal(db, judgmentVerdictId, options) {
75
+ const now = options?.now ?? new Date().toISOString();
76
+ const readResult = await readJudgmentVerdictById(db, judgmentVerdictId);
77
+ if (readResult.degraded) {
78
+ return readResult.degraded;
79
+ }
80
+ const verdict = readResult.row;
81
+ if (!verdict) {
82
+ return {
83
+ status: "degraded",
84
+ reason: "state_unreadable",
85
+ ownerStage: "policy",
86
+ sourceRefs: [],
87
+ operatorNextAction: `JudgmentVerdict ${judgmentVerdictId} not found`,
88
+ retryable: false,
89
+ };
90
+ }
91
+ const actionKind = verdict.actionKind;
92
+ const cycleId = verdict.cycleId;
93
+ // ignore / watch → no-action
94
+ if (actionKind === "ignore" || actionKind === "watch") {
95
+ return {
96
+ status: "no_action",
97
+ reason: "proposal_no_action",
98
+ cycleId,
99
+ judgmentVerdictId,
100
+ };
101
+ }
102
+ const sourceRefs = parseVerdictSourceRefs(verdict.sourceRefsJson);
103
+ // remember → memory review candidate closure (no direct projection)
104
+ if (actionKind === "remember") {
105
+ const candidate = {
106
+ closureSubtype: "remember_for_review",
107
+ perceptionRef: {
108
+ uri: `sn://perception/${verdict.perceptionCardId}`,
109
+ family: "perception",
110
+ id: verdict.perceptionCardId,
111
+ redactionClass: "none",
112
+ resolveStatus: "resolvable",
113
+ },
114
+ judgmentVerdictRef: {
115
+ uri: `sn://judgment/${judgmentVerdictId}`,
116
+ family: "judgment",
117
+ id: judgmentVerdictId,
118
+ redactionClass: "none",
119
+ resolveStatus: "resolvable",
120
+ },
121
+ topicKey: verdict.perceptionCardId,
122
+ memoryIntentReason: verdict.reason ?? "remember",
123
+ reviewPriority: "medium",
124
+ sourceRefs: (sourceRefs.length > 0 ? sourceRefs : [
125
+ {
126
+ uri: `sn://proposal/remember/${judgmentVerdictId}`,
127
+ family: "action_closure",
128
+ id: judgmentVerdictId,
129
+ redactionClass: "none",
130
+ resolveStatus: "resolvable",
131
+ },
132
+ ]),
133
+ };
134
+ const closureId = `cls_remember_${judgmentVerdictId}_${now.replace(/[:.]/g, "")}`;
135
+ const writeResult = await writeActionClosureRecord(db, {
136
+ id: closureId,
137
+ createdAt: now,
138
+ cycleId,
139
+ status: "completed",
140
+ reason: "remember_for_review",
141
+ nextState: "pending_daily_review",
142
+ sourceRefs: candidate.sourceRefs,
143
+ redactionClass: "none",
144
+ lifecycleStatus: "closed",
145
+ payloadJson: JSON.stringify({ memoryReviewCandidate: candidate }),
146
+ });
147
+ if ("reason" in writeResult) {
148
+ return writeResult;
149
+ }
150
+ return {
151
+ status: "remember_for_review",
152
+ memoryReviewCandidate: candidate,
153
+ closureId,
154
+ };
155
+ }
156
+ // Actionable verdict → build proposal
157
+ const meta = ACTION_KIND_REGISTRY[actionKind];
158
+ const { platformId, capabilityId } = inferTargetPlatform(actionKind);
159
+ const proposal = {
160
+ id: `prop_${judgmentVerdictId}_${now.replace(/[:.]/g, "")}`,
161
+ cycleId,
162
+ judgmentVerdictId,
163
+ actionKind,
164
+ targetPlatformId: platformId,
165
+ targetCapabilityId: capabilityId,
166
+ sourceRefs: sourceRefs.length > 0 ? sourceRefs : [
167
+ {
168
+ uri: `sn://proposal/${judgmentVerdictId}`,
169
+ family: "action_closure",
170
+ id: judgmentVerdictId,
171
+ redactionClass: "none",
172
+ resolveStatus: "resolvable",
173
+ },
174
+ ],
175
+ reason: verdict.reason ?? "proposal_created",
176
+ riskPosture: verdict.riskPosture ?? "low",
177
+ expectedOutput: buildExpectedOutput(actionKind),
178
+ sideEffectClass: meta?.sideEffectClass ?? "none",
179
+ idempotencyKey: `idem_${cycleId}_${judgmentVerdictId}`,
180
+ createdAt: now,
181
+ };
182
+ return {
183
+ status: "proposal",
184
+ proposal,
185
+ };
186
+ }
187
+ export async function buildActionProposals(db, judgmentVerdictIds, options) {
188
+ const proposals = [];
189
+ const noActions = [];
190
+ const rememberForReviews = [];
191
+ const failed = [];
192
+ for (const judgmentVerdictId of judgmentVerdictIds) {
193
+ const result = await buildActionProposal(db, judgmentVerdictId, options);
194
+ if ("status" in result && result.status === "proposal") {
195
+ proposals.push(result.proposal);
196
+ }
197
+ else if ("status" in result && result.status === "no_action") {
198
+ noActions.push(result);
199
+ }
200
+ else if ("status" in result && result.status === "remember_for_review") {
201
+ rememberForReviews.push(result);
202
+ }
203
+ else if ("status" in result && result.status === "degraded") {
204
+ failed.push({
205
+ judgmentVerdictId,
206
+ degraded: result,
207
+ });
208
+ }
209
+ else {
210
+ failed.push({
211
+ judgmentVerdictId,
212
+ degraded: result,
213
+ });
214
+ }
215
+ }
216
+ return { proposals, noActions, rememberForReviews, failed };
217
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * AutonomyPolicyEvaluator — Evaluate ActionProposal against platform policy,
3
+ * affordance posture, and risk flags.
4
+ *
5
+ * Core logic: Table-driven allow/defer/downgrade/deny decisions based on
6
+ * side-effect class, source refs, risk posture, and breaker status.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.detail.md §3.2, §4.1`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.md §4.2`
11
+ *
12
+ * Dependencies:
13
+ * - `src/shared/types/v8-contracts.js` (PlatformNeutralActionKind,
14
+ * SourceRef, DegradedOperationResult, V8ReasonCode, ACTION_KIND_REGISTRY)
15
+ *
16
+ * Boundary:
17
+ * - Does not execute actions; only evaluates policy.
18
+ * - Does not read external platform state; relies on affordance input.
19
+ * - Pure function for testability; DB write is caller responsibility.
20
+ *
21
+ * Test coverage: tests/unit/action/autonomy-policy-evaluator.test.ts
22
+ */
23
+ import type { SourceRef, PlatformNeutralActionKind, V8ReasonCode } from "../../../shared/types/v8-contracts.js";
24
+ import type { ActionProposal } from "./action-proposal-builder.js";
25
+ export interface ActionPolicyDecision {
26
+ id: string;
27
+ proposalId: string;
28
+ decision: "allow" | "defer" | "downgrade" | "deny";
29
+ decisionReason: V8ReasonCode;
30
+ autonomyLevel: "none" | "draft_only" | "owner_confirm" | "auto_allowed";
31
+ downgradedActionKind?: PlatformNeutralActionKind;
32
+ proofRefs: SourceRef[];
33
+ decidedAt: string;
34
+ }
35
+ export interface PolicyEvaluationContext {
36
+ breakerStatus?: "closed" | "open" | "half_open";
37
+ platformPermissionDeclared?: boolean;
38
+ ownerPreferenceAllowAuto?: boolean;
39
+ }
40
+ export interface EvaluateActionPolicyOptions {
41
+ now?: string;
42
+ }
43
+ export declare function evaluateActionPolicy(proposal: ActionProposal, context: PolicyEvaluationContext, options?: EvaluateActionPolicyOptions): ActionPolicyDecision;