@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.
- package/openclaw.plugin.json +29 -29
- package/package.json +55 -55
- package/runtime/cli/commands/index.js +326 -325
- package/runtime/cli/ops/heartbeat-surface.d.ts +84 -84
- package/runtime/cli/ops/heartbeat-surface.js +100 -100
- package/runtime/cli/ops/ops-router.js +1555 -1482
- package/runtime/cli/ops/workspace-heartbeat-runner.d.ts +85 -85
- package/runtime/cli/ops/workspace-heartbeat-runner.js +242 -242
- package/runtime/connectors/base/contract.d.ts +111 -111
- package/runtime/connectors/base/failure-taxonomy.d.ts +13 -13
- package/runtime/connectors/base/failure-taxonomy.js +186 -186
- package/runtime/connectors/base/map-life-evidence.js +137 -137
- package/runtime/connectors/base/policy-layer.js +202 -202
- package/runtime/connectors/evidence-normalizer.d.ts +45 -0
- package/runtime/connectors/evidence-normalizer.js +115 -0
- package/runtime/connectors/manifest/manifest-schema.d.ts +152 -152
- package/runtime/connectors/manifest/manifest-schema.js +54 -54
- package/runtime/connectors/services/connector-executor-adapter.d.ts +20 -20
- package/runtime/connectors/services/connector-executor-adapter.js +645 -645
- package/runtime/core/second-nature/action/action-closure-recorder.d.ts +70 -0
- package/runtime/core/second-nature/action/action-closure-recorder.js +184 -0
- package/runtime/core/second-nature/action/action-proposal-builder.d.ts +70 -0
- package/runtime/core/second-nature/action/action-proposal-builder.js +217 -0
- package/runtime/core/second-nature/action/autonomy-policy-evaluator.d.ts +43 -0
- package/runtime/core/second-nature/action/autonomy-policy-evaluator.js +213 -0
- package/runtime/core/second-nature/action/policy-bound-dispatch.d.ts +69 -0
- package/runtime/core/second-nature/action/policy-bound-dispatch.js +112 -0
- package/runtime/core/second-nature/body/tool-affordance/affordance-side-effect.d.ts +49 -0
- package/runtime/core/second-nature/body/tool-affordance/affordance-side-effect.js +100 -0
- package/runtime/core/second-nature/control-plane/accepted-projection-loader.d.ts +45 -0
- package/runtime/core/second-nature/control-plane/accepted-projection-loader.js +85 -0
- package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.d.ts +38 -0
- package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.js +165 -0
- package/runtime/core/second-nature/guidance/guidance-proposal-consumer.d.ts +51 -0
- package/runtime/core/second-nature/guidance/guidance-proposal-consumer.js +113 -0
- package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.d.ts +24 -24
- package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.js +61 -61
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +97 -97
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +397 -397
- package/runtime/core/second-nature/orchestrator/platform-capability-router.js +149 -149
- package/runtime/core/second-nature/perception/judgment-engine.d.ts +53 -0
- package/runtime/core/second-nature/perception/judgment-engine.js +239 -0
- package/runtime/core/second-nature/perception/perception-builder.d.ts +62 -0
- package/runtime/core/second-nature/perception/perception-builder.js +208 -0
- package/runtime/core/second-nature/perception/sensitivity-classifier.d.ts +37 -0
- package/runtime/core/second-nature/perception/sensitivity-classifier.js +87 -0
- package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.d.ts +44 -0
- package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.js +180 -0
- package/runtime/core/second-nature/quiet-dream/dream-scheduler.d.ts +36 -0
- package/runtime/core/second-nature/quiet-dream/dream-scheduler.js +105 -0
- package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.d.ts +36 -0
- package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.js +151 -0
- package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.d.ts +46 -0
- package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.js +123 -0
- package/runtime/observability/causal-loop-health.d.ts +44 -0
- package/runtime/observability/causal-loop-health.js +118 -0
- package/runtime/observability/diagnostic-redaction.d.ts +43 -0
- package/runtime/observability/diagnostic-redaction.js +114 -0
- package/runtime/observability/loop-stage-event-sink.d.ts +43 -0
- package/runtime/observability/loop-stage-event-sink.js +148 -0
- package/runtime/observability/loop-status.d.ts +46 -0
- package/runtime/observability/loop-status.js +85 -0
- package/runtime/shared/types/index.js +3 -0
- package/runtime/shared/types/v8-contracts.d.ts +86 -0
- package/runtime/shared/types/v8-contracts.js +84 -0
- package/runtime/storage/db/schema/index.d.ts +1 -0
- package/runtime/storage/db/schema/index.js +1 -0
- package/runtime/storage/db/schema/v8-entities.d.ts +1973 -0
- package/runtime/storage/db/schema/v8-entities.js +160 -0
- package/runtime/storage/v8-state-stores.d.ts +147 -0
- 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
|
+
}
|