@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,213 @@
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 { ACTION_KIND_REGISTRY } from "../../../shared/types/v8-contracts.js";
24
+ // ───────────────────────────────────────────────────────────────
25
+ // Config
26
+ // ───────────────────────────────────────────────────────────────
27
+ const AUTO_WRITE_MIN_RISK = "low";
28
+ // ───────────────────────────────────────────────────────────────
29
+ // Helpers
30
+ // ───────────────────────────────────────────────────────────────
31
+ function isWriteSideEffect(sideEffectClass) {
32
+ return sideEffectClass === "external_write" || sideEffectClass === "capability_declared";
33
+ }
34
+ function isOwnerAttentionAction(actionKind) {
35
+ return ["notify_owner", "draft_reply", "draft_publish"].includes(actionKind);
36
+ }
37
+ function canAutoRun(actionKind, riskPosture) {
38
+ const meta = ACTION_KIND_REGISTRY[actionKind];
39
+ if (!meta)
40
+ return false;
41
+ if (meta.sideEffectClass === "external_write" && riskPosture !== "low")
42
+ return false;
43
+ if (meta.sideEffectClass === "external_write" && riskPosture === "low")
44
+ return true;
45
+ if (meta.sideEffectClass === "capability_declared")
46
+ return riskPosture === "low";
47
+ if (meta.sideEffectClass === "owner_attention")
48
+ return riskPosture === "low" || riskPosture === "medium";
49
+ return meta.requiresPolicyDecision === false || meta.sideEffectClass === "none" || meta.sideEffectClass === "local_state";
50
+ }
51
+ function downgradeTarget(actionKind) {
52
+ const meta = ACTION_KIND_REGISTRY[actionKind];
53
+ if (!meta || meta.allowedDowngrades.length === 0)
54
+ return undefined;
55
+ return meta.allowedDowngrades[0];
56
+ }
57
+ // ───────────────────────────────────────────────────────────────
58
+ // Public API
59
+ // ───────────────────────────────────────────────────────────────
60
+ export function evaluateActionPolicy(proposal, context, options) {
61
+ const now = options?.now ?? new Date().toISOString();
62
+ const proposalId = proposal.id;
63
+ const decisionId = `dec_${proposalId}_${now.replace(/[:.]/g, "")}`;
64
+ const proofRefs = proposal.sourceRefs.length > 0
65
+ ? proposal.sourceRefs
66
+ : [
67
+ {
68
+ uri: `sn://policy/${proposalId}`,
69
+ family: "action_closure",
70
+ id: proposalId,
71
+ redactionClass: "none",
72
+ resolveStatus: "resolvable",
73
+ },
74
+ ];
75
+ // 1. Missing source refs for owner/write actions → deny
76
+ if (proposal.sourceRefs.length === 0 &&
77
+ (isWriteSideEffect(proposal.sideEffectClass) || isOwnerAttentionAction(proposal.actionKind))) {
78
+ return {
79
+ id: decisionId,
80
+ proposalId,
81
+ decision: "deny",
82
+ decisionReason: "policy_denied_missing_permission",
83
+ autonomyLevel: "none",
84
+ proofRefs,
85
+ decidedAt: now,
86
+ };
87
+ }
88
+ // 2. Risk blocked or high → deny or defer
89
+ if (proposal.riskPosture === "blocked") {
90
+ return {
91
+ id: decisionId,
92
+ proposalId,
93
+ decision: "deny",
94
+ decisionReason: "policy_denied_high_risk",
95
+ autonomyLevel: "none",
96
+ proofRefs,
97
+ decidedAt: now,
98
+ };
99
+ }
100
+ if (proposal.riskPosture === "high") {
101
+ return {
102
+ id: decisionId,
103
+ proposalId,
104
+ decision: "defer",
105
+ decisionReason: "policy_deferred_owner_confirmation",
106
+ autonomyLevel: "owner_confirm",
107
+ proofRefs,
108
+ decidedAt: now,
109
+ };
110
+ }
111
+ // 3. Circuit breaker open → deny
112
+ if (context.breakerStatus === "open") {
113
+ return {
114
+ id: decisionId,
115
+ proposalId,
116
+ decision: "deny",
117
+ decisionReason: "policy_denied_breaker_open",
118
+ autonomyLevel: "none",
119
+ proofRefs,
120
+ decidedAt: now,
121
+ };
122
+ }
123
+ // 4. Write-side action + no platform permission → downgrade or deny
124
+ if (isWriteSideEffect(proposal.sideEffectClass) &&
125
+ context.platformPermissionDeclared === false) {
126
+ const target = downgradeTarget(proposal.actionKind);
127
+ if (target) {
128
+ return {
129
+ id: decisionId,
130
+ proposalId,
131
+ decision: "downgrade",
132
+ decisionReason: "policy_downgraded_to_draft",
133
+ autonomyLevel: "draft_only",
134
+ downgradedActionKind: target,
135
+ proofRefs,
136
+ decidedAt: now,
137
+ };
138
+ }
139
+ return {
140
+ id: decisionId,
141
+ proposalId,
142
+ decision: "deny",
143
+ decisionReason: "policy_denied_missing_permission",
144
+ autonomyLevel: "none",
145
+ proofRefs,
146
+ decidedAt: now,
147
+ };
148
+ }
149
+ // 5. Owner preference blocks auto → downgrade
150
+ if (context.ownerPreferenceAllowAuto === false &&
151
+ isWriteSideEffect(proposal.sideEffectClass)) {
152
+ const target = downgradeTarget(proposal.actionKind);
153
+ if (target) {
154
+ return {
155
+ id: decisionId,
156
+ proposalId,
157
+ decision: "downgrade",
158
+ decisionReason: "policy_downgraded_to_draft",
159
+ autonomyLevel: "draft_only",
160
+ downgradedActionKind: target,
161
+ proofRefs,
162
+ decidedAt: now,
163
+ };
164
+ }
165
+ return {
166
+ id: decisionId,
167
+ proposalId,
168
+ decision: "deny",
169
+ decisionReason: "policy_denied_missing_permission",
170
+ autonomyLevel: "none",
171
+ proofRefs,
172
+ decidedAt: now,
173
+ };
174
+ }
175
+ // 6. Low risk + permission + healthy affordance → allow
176
+ if (proposal.riskPosture === AUTO_WRITE_MIN_RISK ||
177
+ proposal.riskPosture === "medium") {
178
+ if (canAutoRun(proposal.actionKind, proposal.riskPosture)) {
179
+ return {
180
+ id: decisionId,
181
+ proposalId,
182
+ decision: "allow",
183
+ decisionReason: "policy_allowed",
184
+ autonomyLevel: "auto_allowed",
185
+ proofRefs,
186
+ decidedAt: now,
187
+ };
188
+ }
189
+ }
190
+ // Default: downgrade to draft or deny
191
+ const target = downgradeTarget(proposal.actionKind);
192
+ if (target) {
193
+ return {
194
+ id: decisionId,
195
+ proposalId,
196
+ decision: "downgrade",
197
+ decisionReason: "policy_downgraded_to_draft",
198
+ autonomyLevel: "draft_only",
199
+ downgradedActionKind: target,
200
+ proofRefs,
201
+ decidedAt: now,
202
+ };
203
+ }
204
+ return {
205
+ id: decisionId,
206
+ proposalId,
207
+ decision: "deny",
208
+ decisionReason: "policy_denied_missing_permission",
209
+ autonomyLevel: "none",
210
+ proofRefs,
211
+ decidedAt: now,
212
+ };
213
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * PolicyBoundDispatch — Dispatch allowed actions and record closure-safe
3
+ * downgraded results.
4
+ *
5
+ * Core logic: Read ActionPolicyDecision, route to connector or guidance
6
+ * based on decision, and return dispatch result for closure recording.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.detail.md §3.3`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.md §4.2`
11
+ *
12
+ * Dependencies:
13
+ * - `src/core/second-nature/action/autonomy-policy-evaluator.js` (ActionPolicyDecision)
14
+ * - `src/core/second-nature/action/action-proposal-builder.js` (ActionProposal)
15
+ *
16
+ * Boundary:
17
+ * - Does not execute connector directly; returns dispatch envelope.
18
+ * - Does not generate guidance text; returns guidance request envelope.
19
+ * - Degrades gracefully on unavailable guidance.
20
+ *
21
+ * Test coverage: tests/unit/action/policy-bound-dispatch.test.ts
22
+ */
23
+ import type { DegradedOperationResult, PlatformNeutralActionKind, V8ReasonCode } from "../../../shared/types/v8-contracts.js";
24
+ import type { ActionProposal } from "./action-proposal-builder.js";
25
+ import type { ActionPolicyDecision } from "./autonomy-policy-evaluator.js";
26
+ export interface ConnectorDispatchRequest {
27
+ type: "connector";
28
+ platformId: string;
29
+ capabilityId: string;
30
+ idempotencyKey: string;
31
+ policyProof: {
32
+ decisionId: string;
33
+ decision: string;
34
+ };
35
+ sourceRefs: string;
36
+ }
37
+ export interface GuidanceDispatchRequest {
38
+ type: "guidance";
39
+ actionKind: PlatformNeutralActionKind;
40
+ draftType: "reply" | "publish" | "notify";
41
+ policyProof: {
42
+ decisionId: string;
43
+ decision: string;
44
+ };
45
+ sourceRefs: string;
46
+ }
47
+ export interface NoDispatchResult {
48
+ type: "none";
49
+ reason: V8ReasonCode;
50
+ }
51
+ export interface GuidanceUnavailableResult {
52
+ type: "guidance_unavailable";
53
+ downgradedActionKind: PlatformNeutralActionKind;
54
+ reason: "guidance_unavailable";
55
+ }
56
+ export type DispatchResult = {
57
+ type: "connector";
58
+ request: ConnectorDispatchRequest;
59
+ } | {
60
+ type: "guidance";
61
+ request: GuidanceDispatchRequest;
62
+ } | NoDispatchResult | GuidanceUnavailableResult | {
63
+ type: "degraded";
64
+ degraded: DegradedOperationResult;
65
+ };
66
+ export interface DispatchAllowedActionOptions {
67
+ guidanceAvailable?: boolean;
68
+ }
69
+ export declare function dispatchAllowedAction(proposal: ActionProposal, decision: ActionPolicyDecision, options?: DispatchAllowedActionOptions): DispatchResult;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * PolicyBoundDispatch — Dispatch allowed actions and record closure-safe
3
+ * downgraded results.
4
+ *
5
+ * Core logic: Read ActionPolicyDecision, route to connector or guidance
6
+ * based on decision, and return dispatch result for closure recording.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.detail.md §3.3`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.md §4.2`
11
+ *
12
+ * Dependencies:
13
+ * - `src/core/second-nature/action/autonomy-policy-evaluator.js` (ActionPolicyDecision)
14
+ * - `src/core/second-nature/action/action-proposal-builder.js` (ActionProposal)
15
+ *
16
+ * Boundary:
17
+ * - Does not execute connector directly; returns dispatch envelope.
18
+ * - Does not generate guidance text; returns guidance request envelope.
19
+ * - Degrades gracefully on unavailable guidance.
20
+ *
21
+ * Test coverage: tests/unit/action/policy-bound-dispatch.test.ts
22
+ */
23
+ // ───────────────────────────────────────────────────────────────
24
+ // Helpers
25
+ // ───────────────────────────────────────────────────────────────
26
+ function inferGuidanceDraftType(actionKind) {
27
+ if (actionKind === "draft_reply" || actionKind === "auto_reply")
28
+ return "reply";
29
+ if (actionKind === "draft_publish" || actionKind === "auto_publish")
30
+ return "publish";
31
+ return "notify";
32
+ }
33
+ // ───────────────────────────────────────────────────────────────
34
+ // Public API
35
+ // ───────────────────────────────────────────────────────────────
36
+ export function dispatchAllowedAction(proposal, decision, options) {
37
+ // deny / defer → no dispatch
38
+ if (decision.decision === "deny") {
39
+ return { type: "none", reason: decision.decisionReason };
40
+ }
41
+ if (decision.decision === "defer") {
42
+ return { type: "none", reason: decision.decisionReason };
43
+ }
44
+ // downgrade → guidance only (no external write)
45
+ if (decision.decision === "downgrade") {
46
+ const target = decision.downgradedActionKind ?? proposal.actionKind;
47
+ const draftType = inferGuidanceDraftType(target);
48
+ // If guidance is unavailable, return closure-safe downgrade result
49
+ if (options?.guidanceAvailable === false) {
50
+ return {
51
+ type: "guidance_unavailable",
52
+ downgradedActionKind: target,
53
+ reason: "guidance_unavailable",
54
+ };
55
+ }
56
+ return {
57
+ type: "guidance",
58
+ request: {
59
+ type: "guidance",
60
+ actionKind: target,
61
+ draftType,
62
+ policyProof: { decisionId: decision.id, decision: decision.decision },
63
+ sourceRefs: JSON.stringify(decision.proofRefs),
64
+ },
65
+ };
66
+ }
67
+ // allow → route by action kind
68
+ if (decision.decision === "allow") {
69
+ if (proposal.actionKind === "run_connector") {
70
+ return {
71
+ type: "connector",
72
+ request: {
73
+ type: "connector",
74
+ platformId: proposal.targetPlatformId ?? "connector",
75
+ capabilityId: proposal.targetCapabilityId ?? "run_connector",
76
+ idempotencyKey: proposal.idempotencyKey,
77
+ policyProof: { decisionId: decision.id, decision: decision.decision },
78
+ sourceRefs: JSON.stringify(proposal.sourceRefs),
79
+ },
80
+ };
81
+ }
82
+ if (proposal.actionKind === "auto_reply" ||
83
+ proposal.actionKind === "auto_publish" ||
84
+ proposal.actionKind === "draft_reply" ||
85
+ proposal.actionKind === "draft_publish") {
86
+ const draftType = inferGuidanceDraftType(proposal.actionKind);
87
+ return {
88
+ type: "guidance",
89
+ request: {
90
+ type: "guidance",
91
+ actionKind: proposal.actionKind,
92
+ draftType,
93
+ policyProof: { decisionId: decision.id, decision: decision.decision },
94
+ sourceRefs: JSON.stringify(proposal.sourceRefs),
95
+ },
96
+ };
97
+ }
98
+ // notify_owner / remember / watch / ignore — no external dispatch needed
99
+ return { type: "none", reason: decision.decisionReason };
100
+ }
101
+ return {
102
+ type: "degraded",
103
+ degraded: {
104
+ status: "degraded",
105
+ reason: "closure_failed",
106
+ ownerStage: "execution",
107
+ sourceRefs: decision.proofRefs,
108
+ operatorNextAction: "Unexpected policy decision shape",
109
+ retryable: false,
110
+ },
111
+ };
112
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * AffordanceSideEffect — v8 capability side-effect classifier.
3
+ *
4
+ * Core logic: Derive effective side-effect class for `run_connector` actions
5
+ * from connector capability metadata. Action policy uses this to decide
6
+ * allow/defer/downgrade/deny without knowing platform internals.
7
+ *
8
+ * Classification rules:
9
+ * - `feed.read`, `work.discover`, `notification.list` → external_read
10
+ * - `post.publish`, `comment.reply`, `message.send`, `task.claim` → external_write
11
+ * - `agent.register`, `agent.heartbeat` → local_state
12
+ * - Unknown / unlisted capability → unknown (policy must deny or downgrade)
13
+ *
14
+ * Design authority:
15
+ * - `.anws/v8/04_SYSTEM_DESIGN/body-tool-system.md`
16
+ * - `.anws/v8/04_SYSTEM_DESIGN/shared-v8-contracts.md §1.2`
17
+ *
18
+ * Dependencies:
19
+ * - `ConnectorCapabilitySideEffect` from `../../../../shared/types/v8-contracts.js`
20
+ *
21
+ * Boundary:
22
+ * - Pure classification function; no side effects, no DB access.
23
+ * - Does NOT perform capability probe — reads manifest metadata only.
24
+ * - Unknown capabilities return `unknown`; caller (policy) decides posture.
25
+ *
26
+ * Test coverage: tests/unit/body/affordance-side-effect.test.ts
27
+ */
28
+ import type { ConnectorCapabilitySideEffect } from "../../../../shared/types/v8-contracts.js";
29
+ export declare function deriveConnectorSideEffect(capabilityId: string): ConnectorCapabilitySideEffect;
30
+ export interface CapabilityAffordancePosture {
31
+ connectorId: string;
32
+ capabilityId: string;
33
+ sideEffectClass: ConnectorCapabilitySideEffect;
34
+ authStatus: "ready" | "needs_auth" | "revoked" | "unknown";
35
+ breakerStatus: "closed" | "open" | "half_open";
36
+ }
37
+ export declare function assembleCapabilityAffordancePosture(connectorId: string, capabilityId: string, authStatus: CapabilityAffordancePosture["authStatus"], breakerStatus: CapabilityAffordancePosture["breakerStatus"]): CapabilityAffordancePosture;
38
+ export interface SideEffectAwareAffordanceMap {
39
+ [connectorId: string]: {
40
+ [capabilityId: string]: CapabilityAffordancePosture;
41
+ };
42
+ }
43
+ export declare function buildSideEffectAwareAffordanceMap(postures: CapabilityAffordancePosture[]): SideEffectAwareAffordanceMap;
44
+ export declare function lookupSideEffectPosture(map: SideEffectAwareAffordanceMap, connectorId: string, capabilityId: string): CapabilityAffordancePosture | undefined;
45
+ export declare function isWriteSideEffect(sideEffect: ConnectorCapabilitySideEffect): boolean;
46
+ export declare function isReadSideEffect(sideEffect: ConnectorCapabilitySideEffect): boolean;
47
+ export declare function isLocalStateSideEffect(sideEffect: ConnectorCapabilitySideEffect): boolean;
48
+ export declare function isUnknownSideEffect(sideEffect: ConnectorCapabilitySideEffect): boolean;
49
+ export declare function effectiveActionSideEffectClass(sideEffect: ConnectorCapabilitySideEffect): "external_write" | "external_read" | "local_state" | "unknown";
@@ -0,0 +1,100 @@
1
+ /**
2
+ * AffordanceSideEffect — v8 capability side-effect classifier.
3
+ *
4
+ * Core logic: Derive effective side-effect class for `run_connector` actions
5
+ * from connector capability metadata. Action policy uses this to decide
6
+ * allow/defer/downgrade/deny without knowing platform internals.
7
+ *
8
+ * Classification rules:
9
+ * - `feed.read`, `work.discover`, `notification.list` → external_read
10
+ * - `post.publish`, `comment.reply`, `message.send`, `task.claim` → external_write
11
+ * - `agent.register`, `agent.heartbeat` → local_state
12
+ * - Unknown / unlisted capability → unknown (policy must deny or downgrade)
13
+ *
14
+ * Design authority:
15
+ * - `.anws/v8/04_SYSTEM_DESIGN/body-tool-system.md`
16
+ * - `.anws/v8/04_SYSTEM_DESIGN/shared-v8-contracts.md §1.2`
17
+ *
18
+ * Dependencies:
19
+ * - `ConnectorCapabilitySideEffect` from `../../../../shared/types/v8-contracts.js`
20
+ *
21
+ * Boundary:
22
+ * - Pure classification function; no side effects, no DB access.
23
+ * - Does NOT perform capability probe — reads manifest metadata only.
24
+ * - Unknown capabilities return `unknown`; caller (policy) decides posture.
25
+ *
26
+ * Test coverage: tests/unit/body/affordance-side-effect.test.ts
27
+ */
28
+ // ───────────────────────────────────────────────────────────────
29
+ // Capability → side-effect mapping
30
+ // ───────────────────────────────────────────────────────────────
31
+ const READ_CAPABILITIES = new Set([
32
+ "feed.read",
33
+ "work.discover",
34
+ "notification.list",
35
+ "profile.inspect",
36
+ "github:issue.search",
37
+ ]);
38
+ const WRITE_CAPABILITIES = new Set([
39
+ "post.publish",
40
+ "comment.reply",
41
+ "message.send",
42
+ "task.claim",
43
+ ]);
44
+ const LOCAL_STATE_CAPABILITIES = new Set([
45
+ "agent.register",
46
+ "agent.heartbeat",
47
+ "status.update",
48
+ ]);
49
+ // ───────────────────────────────────────────────────────────────
50
+ // Public API
51
+ // ───────────────────────────────────────────────────────────────
52
+ export function deriveConnectorSideEffect(capabilityId) {
53
+ if (READ_CAPABILITIES.has(capabilityId))
54
+ return "external_read";
55
+ if (WRITE_CAPABILITIES.has(capabilityId))
56
+ return "external_write";
57
+ if (LOCAL_STATE_CAPABILITIES.has(capabilityId))
58
+ return "local_state";
59
+ return "unknown";
60
+ }
61
+ export function assembleCapabilityAffordancePosture(connectorId, capabilityId, authStatus, breakerStatus) {
62
+ return {
63
+ connectorId,
64
+ capabilityId,
65
+ sideEffectClass: deriveConnectorSideEffect(capabilityId),
66
+ authStatus,
67
+ breakerStatus,
68
+ };
69
+ }
70
+ export function buildSideEffectAwareAffordanceMap(postures) {
71
+ const map = {};
72
+ for (const posture of postures) {
73
+ if (!map[posture.connectorId]) {
74
+ map[posture.connectorId] = {};
75
+ }
76
+ map[posture.connectorId][posture.capabilityId] = posture;
77
+ }
78
+ return map;
79
+ }
80
+ export function lookupSideEffectPosture(map, connectorId, capabilityId) {
81
+ return map[connectorId]?.[capabilityId];
82
+ }
83
+ // ───────────────────────────────────────────────────────────────
84
+ // Policy-facing helpers
85
+ // ───────────────────────────────────────────────────────────────
86
+ export function isWriteSideEffect(sideEffect) {
87
+ return sideEffect === "external_write";
88
+ }
89
+ export function isReadSideEffect(sideEffect) {
90
+ return sideEffect === "external_read";
91
+ }
92
+ export function isLocalStateSideEffect(sideEffect) {
93
+ return sideEffect === "local_state";
94
+ }
95
+ export function isUnknownSideEffect(sideEffect) {
96
+ return sideEffect === "unknown";
97
+ }
98
+ export function effectiveActionSideEffectClass(sideEffect) {
99
+ return sideEffect;
100
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * AcceptedProjectionLoader — Load accepted long-term memory into EmbodiedContext.
3
+ *
4
+ * Core logic: Read active/accepted projections from state, exclude candidates,
5
+ * and return bounded memory slice for heartbeat context assembly.
6
+ *
7
+ * Design authority:
8
+ * - `.anws/v8/04_SYSTEM_DESIGN/control-plane-system.md §5`
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.md §4.2`
10
+ *
11
+ * Dependencies:
12
+ * - `src/storage/v8-state-stores.js` (readMemoryProjectionsByStatus)
13
+ * - `src/shared/types/v8-contracts.js` (SourceRef, DegradedOperationResult)
14
+ *
15
+ * Boundary:
16
+ * - Only loads accepted/active projections; candidates are excluded.
17
+ * - Does not judge projection importance; loads all active.
18
+ * - Degrades gracefully on unreadable state.
19
+ *
20
+ * Test coverage: tests/unit/control-plane/accepted-projection-loader.test.ts
21
+ */
22
+ import type { StateDatabase } from "../../../storage/db/index.js";
23
+ import type { SourceRef, DegradedOperationResult } from "../../../shared/types/v8-contracts.js";
24
+ export interface MemoryProjectionSlice {
25
+ projections: AcceptedProjection[];
26
+ topicKeys: string[];
27
+ totalProjections: number;
28
+ }
29
+ export interface AcceptedProjection {
30
+ id: string;
31
+ topicKey: string;
32
+ memoryText: string;
33
+ sourceRefs: SourceRef[];
34
+ acceptedAt?: string;
35
+ }
36
+ export type LoadAcceptedProjectionsResult = {
37
+ ok: true;
38
+ slice: MemoryProjectionSlice;
39
+ } | {
40
+ ok: false;
41
+ degraded: DegradedOperationResult;
42
+ };
43
+ export declare function loadAcceptedProjections(db: StateDatabase, _options?: {
44
+ limit?: number;
45
+ }): Promise<LoadAcceptedProjectionsResult>;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * AcceptedProjectionLoader — Load accepted long-term memory into EmbodiedContext.
3
+ *
4
+ * Core logic: Read active/accepted projections from state, exclude candidates,
5
+ * and return bounded memory slice for heartbeat context assembly.
6
+ *
7
+ * Design authority:
8
+ * - `.anws/v8/04_SYSTEM_DESIGN/control-plane-system.md §5`
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.md §4.2`
10
+ *
11
+ * Dependencies:
12
+ * - `src/storage/v8-state-stores.js` (readMemoryProjectionsByStatus)
13
+ * - `src/shared/types/v8-contracts.js` (SourceRef, DegradedOperationResult)
14
+ *
15
+ * Boundary:
16
+ * - Only loads accepted/active projections; candidates are excluded.
17
+ * - Does not judge projection importance; loads all active.
18
+ * - Degrades gracefully on unreadable state.
19
+ *
20
+ * Test coverage: tests/unit/control-plane/accepted-projection-loader.test.ts
21
+ */
22
+ import { readMemoryProjectionsByStatus, } from "../../../storage/v8-state-stores.js";
23
+ // ───────────────────────────────────────────────────────────────
24
+ // Helpers
25
+ // ───────────────────────────────────────────────────────────────
26
+ function parsePayloadJson(json) {
27
+ if (!json)
28
+ return {};
29
+ try {
30
+ return JSON.parse(json);
31
+ }
32
+ catch {
33
+ return {};
34
+ }
35
+ }
36
+ function parseSourceRefs(json) {
37
+ if (!json)
38
+ return [];
39
+ try {
40
+ const parsed = JSON.parse(json);
41
+ return Array.isArray(parsed) ? parsed : [];
42
+ }
43
+ catch {
44
+ return [];
45
+ }
46
+ }
47
+ // ───────────────────────────────────────────────────────────────
48
+ // Public API
49
+ // ───────────────────────────────────────────────────────────────
50
+ export async function loadAcceptedProjections(db, _options) {
51
+ const activeResult = await readMemoryProjectionsByStatus(db, "active");
52
+ if (activeResult.degraded) {
53
+ return {
54
+ ok: false,
55
+ degraded: activeResult.degraded,
56
+ };
57
+ }
58
+ const acceptedResult = await readMemoryProjectionsByStatus(db, "accepted");
59
+ if (acceptedResult.degraded) {
60
+ return {
61
+ ok: false,
62
+ degraded: acceptedResult.degraded,
63
+ };
64
+ }
65
+ const allProjections = [...activeResult.rows, ...acceptedResult.rows];
66
+ const projections = allProjections.map((row) => {
67
+ const payload = parsePayloadJson(row.payloadJson);
68
+ return {
69
+ id: row.id,
70
+ topicKey: row.topicKey,
71
+ memoryText: String(payload.memoryText ?? ""),
72
+ sourceRefs: parseSourceRefs(row.sourceRefsJson),
73
+ acceptedAt: payload.acceptedAt ? String(payload.acceptedAt) : undefined,
74
+ };
75
+ });
76
+ const topicKeys = [...new Set(projections.map((p) => p.topicKey))];
77
+ return {
78
+ ok: true,
79
+ slice: {
80
+ projections,
81
+ topicKeys,
82
+ totalProjections: projections.length,
83
+ },
84
+ };
85
+ }