@haaaiawd/second-nature 0.2.12 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +96 -6
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/runtime/cli/commands/index.js +85 -11
- package/runtime/cli/host-capability/host-discovery-port.d.ts +85 -0
- package/runtime/cli/host-capability/host-discovery-port.js +137 -0
- package/runtime/cli/ops/heartbeat-surface.d.ts +3 -3
- package/runtime/cli/ops/heartbeat-surface.js +6 -5
- package/runtime/cli/ops/ops-router.d.ts +6 -2
- package/runtime/cli/ops/ops-router.js +1273 -1145
- package/runtime/connectors/base/normalized-evidence-content.d.ts +4 -0
- package/runtime/connectors/base/normalized-evidence-content.js +21 -2
- package/runtime/connectors/evidence-normalizer.js +32 -1
- package/runtime/core/second-nature/action/action-closure-recorder.d.ts +2 -0
- package/runtime/core/second-nature/action/action-closure-recorder.js +49 -34
- package/runtime/core/second-nature/action/action-proposal-builder.js +3 -2
- package/runtime/core/second-nature/action/policy-bound-dispatch.d.ts +2 -0
- package/runtime/core/second-nature/action/policy-bound-dispatch.js +7 -3
- package/runtime/core/second-nature/control-plane/cycle-finalizer.d.ts +82 -0
- package/runtime/core/second-nature/control-plane/cycle-finalizer.js +187 -0
- package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.js +13 -9
- package/runtime/core/second-nature/control-plane/real-runtime-spine.js +1 -1
- package/runtime/core/second-nature/guidance/guidance-proposal-consumer.d.ts +2 -1
- package/runtime/core/second-nature/guidance/guidance-proposal-consumer.js +4 -2
- package/runtime/core/second-nature/perception/judgment-engine.js +8 -4
- package/runtime/core/second-nature/perception/perception-builder.js +14 -2
- package/runtime/core/second-nature/quiet-dream/daily-rhythm-scheduler.js +30 -3
- package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.d.ts +5 -1
- package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.js +68 -29
- package/runtime/core/second-nature/quiet-dream/dream-scheduler.js +2 -1
- package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.js +2 -1
- package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.d.ts +1 -0
- package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.js +33 -0
- package/runtime/observability/causal-loop-health.d.ts +2 -1
- package/runtime/observability/causal-loop-health.js +7 -0
- package/runtime/observability/loop-stage-event-sink.js +6 -1
- package/runtime/observability/loop-status.d.ts +2 -0
- package/runtime/observability/loop-status.js +14 -1
- package/runtime/observability/services/heartbeat-digest-assembler.d.ts +3 -0
- package/runtime/observability/services/heartbeat-digest-assembler.js +9 -0
- package/runtime/shared/degraded-status-classifier.d.ts +16 -0
- package/runtime/shared/degraded-status-classifier.js +68 -0
- package/runtime/shared/evidence-level-classifier.d.ts +61 -0
- package/runtime/shared/evidence-level-classifier.js +116 -0
- package/runtime/shared/provenance-tier.d.ts +37 -0
- package/runtime/shared/provenance-tier.js +97 -0
- package/runtime/shared/setup-ack.d.ts +54 -0
- package/runtime/shared/setup-ack.js +108 -0
- package/runtime/shared/source-ref-compat.js +5 -2
- package/runtime/shared/types/v8-contracts.d.ts +13 -2
- package/runtime/storage/db/index.js +71 -28
- package/runtime/storage/db/migrations/v8-005-single-status-schema.js +2 -2
- package/runtime/storage/db/migrations/v8-006-loop-stage-event-proof-trace-columns.d.ts +9 -0
- package/runtime/storage/db/migrations/v8-006-loop-stage-event-proof-trace-columns.js +15 -0
- package/runtime/storage/db/schema/v8-entities.d.ts +76 -0
- package/runtime/storage/db/schema/v8-entities.js +4 -0
- package/runtime/storage/services/write-validation-gate.js +1 -1
- package/runtime/storage/v8-state-stores.d.ts +7 -2
- package/runtime/storage/v8-state-stores.js +37 -19
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* Test coverage: tests/unit/observability/loop-stage-event-sink.test.ts
|
|
22
22
|
*/
|
|
23
23
|
import { writeLoopStageEvent } from "../storage/v8-state-stores.js";
|
|
24
|
+
import { classifyDegradedStatus } from "../shared/degraded-status-classifier.js";
|
|
24
25
|
function validateEvent(event) {
|
|
25
26
|
if (!event.cycleId || event.cycleId.trim().length === 0) {
|
|
26
27
|
return { ok: false, reason: "cycle_id_required", field: "cycleId" };
|
|
@@ -100,7 +101,7 @@ export async function recordLoopStageEvent(db, event, options) {
|
|
|
100
101
|
const validation = validateEvent(event);
|
|
101
102
|
if (!validation.ok) {
|
|
102
103
|
const degraded = {
|
|
103
|
-
status: "
|
|
104
|
+
status: classifyDegradedStatus("stage_event_missing"),
|
|
104
105
|
reason: "stage_event_missing",
|
|
105
106
|
ownerStage: event.stage || "ingestion",
|
|
106
107
|
sourceRefs: event.sourceRefs || [],
|
|
@@ -111,6 +112,8 @@ export async function recordLoopStageEvent(db, event, options) {
|
|
|
111
112
|
}
|
|
112
113
|
const now = options?.now ?? new Date().toISOString();
|
|
113
114
|
const sourceRefs = event.sourceRefs ?? [];
|
|
115
|
+
const proofRefs = event.proofRefs ?? [];
|
|
116
|
+
const traceRefs = event.traceRefs ?? [];
|
|
114
117
|
const { redacted: redactedRefs, redactionClass } = redactSourceRefs(sourceRefs);
|
|
115
118
|
const record = {
|
|
116
119
|
id: event.id ?? `evt_${now.replace(/[:.]/g, "")}_${event.cycleId}_${event.stage}`,
|
|
@@ -120,6 +123,8 @@ export async function recordLoopStageEvent(db, event, options) {
|
|
|
120
123
|
status: event.status,
|
|
121
124
|
reason: event.reason,
|
|
122
125
|
sourceRefs: redactedRefs,
|
|
126
|
+
proofRefs,
|
|
127
|
+
traceRefs,
|
|
123
128
|
redactionClass,
|
|
124
129
|
occurredAt: event.occurredAt,
|
|
125
130
|
expectedDownstreamByCycle: event.expectedDownstreamByCycle,
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import type { StateDatabase } from "../storage/db/index.js";
|
|
22
22
|
import type { DegradedOperationResult } from "../shared/types/v8-contracts.js";
|
|
23
|
+
import type { EvidenceLevel } from "../shared/types/v8-contracts.js";
|
|
23
24
|
export interface RealRunHealthProjection {
|
|
24
25
|
gatePassed: boolean;
|
|
25
26
|
contractSmokeOnly: boolean;
|
|
@@ -47,6 +48,7 @@ export interface LoopStatusReadModel {
|
|
|
47
48
|
connectorTerminalCount: number;
|
|
48
49
|
nextAction: string;
|
|
49
50
|
realRunHealth: RealRunHealthProjection;
|
|
51
|
+
evidenceLevel: EvidenceLevel;
|
|
50
52
|
}
|
|
51
53
|
export interface StageSummary {
|
|
52
54
|
stage: string;
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { assembleLoopStatus } from "./causal-loop-health.js";
|
|
22
22
|
import { checkRealRunHealth } from "./living-loop-health-gate.js";
|
|
23
23
|
import { readActionClosuresByDay, readConnectorCooldownState, } from "../storage/v8-state-stores.js";
|
|
24
|
+
import { classifyEvidenceLevel } from "../shared/evidence-level-classifier.js";
|
|
24
25
|
// ───────────────────────────────────────────────────────────────
|
|
25
26
|
// Helpers
|
|
26
27
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -163,7 +164,7 @@ export async function attributeDenials(db, options) {
|
|
|
163
164
|
// ───────────────────────────────────────────────────────────────
|
|
164
165
|
export async function readLoopStatus(db) {
|
|
165
166
|
const health = await assembleLoopStatus(db, { limit: 50 });
|
|
166
|
-
if ("
|
|
167
|
+
if ("ownerStage" in health) {
|
|
167
168
|
return {
|
|
168
169
|
ok: false,
|
|
169
170
|
degraded: health,
|
|
@@ -214,6 +215,17 @@ export async function readLoopStatus(db) {
|
|
|
214
215
|
overallStatus = "healthy";
|
|
215
216
|
stalledAt = undefined;
|
|
216
217
|
}
|
|
218
|
+
// T-OBS.R.7: derive evidenceLevel from observed proof
|
|
219
|
+
const evidenceLevel = classifyEvidenceLevel({
|
|
220
|
+
hasCarrierEnvelope: true, // loop_status command envelope itself
|
|
221
|
+
hasContractSmoke: snapshot.lastCycleSequence > 0 && !realRunHealth.hasRealClosure,
|
|
222
|
+
hasStatePresent: snapshot.lastCycleSequence === 0 &&
|
|
223
|
+
(realRunHealth.hasQuietArtifact ||
|
|
224
|
+
realRunHealth.hasDreamArtifact ||
|
|
225
|
+
realRunHealth.hasProjectionFeedback),
|
|
226
|
+
hasCycleExecution: snapshot.lastCycleSequence > 0 && realRunHealth.hasRealClosure,
|
|
227
|
+
hasReadbackVerification: realRunHealth.gatePassed,
|
|
228
|
+
});
|
|
217
229
|
const stageSummaries = snapshot.stages.map((s) => ({
|
|
218
230
|
stage: s.stage,
|
|
219
231
|
eventCount: s.eventCount,
|
|
@@ -240,6 +252,7 @@ export async function readLoopStatus(db) {
|
|
|
240
252
|
connectorTerminalCount: attribution.connectorTerminalCount,
|
|
241
253
|
nextAction,
|
|
242
254
|
realRunHealth,
|
|
255
|
+
evidenceLevel,
|
|
243
256
|
},
|
|
244
257
|
};
|
|
245
258
|
}
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
*/
|
|
33
33
|
import type { AppendOnlyAuditStore } from "../audit/append-only-audit-store.js";
|
|
34
34
|
import type { StateDatabase } from "../../storage/db/index.js";
|
|
35
|
+
import type { EvidenceLevel } from "../../shared/types/v8-contracts.js";
|
|
35
36
|
export interface ConnectorDaySummary {
|
|
36
37
|
platformId: string;
|
|
37
38
|
capability: string;
|
|
@@ -91,6 +92,8 @@ export interface HeartbeatDigest {
|
|
|
91
92
|
healthSummary: HealthDaySummary;
|
|
92
93
|
/** Real-run health gate result (T-OBS.R.3) */
|
|
93
94
|
realRunHealth: RealRunHealthDigestProjection;
|
|
95
|
+
/** T-OBS.R.7: evidence level for this digest */
|
|
96
|
+
evidenceLevel: EvidenceLevel;
|
|
94
97
|
/** Set when delivery succeeded */
|
|
95
98
|
deliveredAt?: string;
|
|
96
99
|
/** Proof of successful delivery (channel + message hash, no raw content) */
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* tests/integration/observability/digest-delivery.test.ts (T-OBS.C.4)
|
|
32
32
|
*/
|
|
33
33
|
import { checkRealRunHealth } from "../living-loop-health-gate.js";
|
|
34
|
+
import { classifyEvidenceLevel } from "../../shared/evidence-level-classifier.js";
|
|
34
35
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
35
36
|
function isSameDayUtc(isoTimestamp, dateStr) {
|
|
36
37
|
// dateStr: "YYYY-MM-DD"
|
|
@@ -262,6 +263,13 @@ export async function generateHeartbeatDigest(date, deps) {
|
|
|
262
263
|
};
|
|
263
264
|
}
|
|
264
265
|
}
|
|
266
|
+
const evidenceLevel = classifyEvidenceLevel({
|
|
267
|
+
hasCarrierEnvelope: true,
|
|
268
|
+
hasContractSmoke: !deps.db,
|
|
269
|
+
hasStatePresent: Boolean(deps.db) && !realRunHealth.gatePassed && !realRunHealth.hasRealClosure,
|
|
270
|
+
hasCycleExecution: realRunHealth.hasRealClosure,
|
|
271
|
+
hasReadbackVerification: realRunHealth.gatePassed,
|
|
272
|
+
});
|
|
265
273
|
const digest = {
|
|
266
274
|
date,
|
|
267
275
|
generatedAt,
|
|
@@ -271,6 +279,7 @@ export async function generateHeartbeatDigest(date, deps) {
|
|
|
271
279
|
quietDreamSummary,
|
|
272
280
|
healthSummary,
|
|
273
281
|
realRunHealth,
|
|
282
|
+
evidenceLevel,
|
|
274
283
|
};
|
|
275
284
|
// T-OBS.C.4: delivery hook — attempt delivery if adapter is provided
|
|
276
285
|
if (deliveryAdapter) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Degraded Status Classifier (T-OBS.R.8)
|
|
3
|
+
*
|
|
4
|
+
* Core logic: map canonical V8ReasonCode values to precise operational states
|
|
5
|
+
* so that stage-level diagnostics never use the aggregate "degraded" string.
|
|
6
|
+
*
|
|
7
|
+
* Design authority:
|
|
8
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/shared-v8-contracts.md §4.1`
|
|
9
|
+
*
|
|
10
|
+
* Dependencies: `src/shared/types/v8-contracts.js`
|
|
11
|
+
* Boundary: pure function; no I/O.
|
|
12
|
+
* Test coverage: tests/unit/shared/degraded-status-classifier.test.ts
|
|
13
|
+
*/
|
|
14
|
+
import type { DegradedOperationResult, V8ReasonCode } from "./types/v8-contracts.js";
|
|
15
|
+
export type PreciseDegradedStatus = DegradedOperationResult["status"];
|
|
16
|
+
export declare function classifyDegradedStatus(reason: V8ReasonCode): PreciseDegradedStatus;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Degraded Status Classifier (T-OBS.R.8)
|
|
3
|
+
*
|
|
4
|
+
* Core logic: map canonical V8ReasonCode values to precise operational states
|
|
5
|
+
* so that stage-level diagnostics never use the aggregate "degraded" string.
|
|
6
|
+
*
|
|
7
|
+
* Design authority:
|
|
8
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/shared-v8-contracts.md §4.1`
|
|
9
|
+
*
|
|
10
|
+
* Dependencies: `src/shared/types/v8-contracts.js`
|
|
11
|
+
* Boundary: pure function; no I/O.
|
|
12
|
+
* Test coverage: tests/unit/shared/degraded-status-classifier.test.ts
|
|
13
|
+
*/
|
|
14
|
+
const EMPTY_REASONS = new Set([
|
|
15
|
+
"evidence_batch_empty",
|
|
16
|
+
"evidence_content_missing",
|
|
17
|
+
"ingestion_empty",
|
|
18
|
+
"ingestion_no_data",
|
|
19
|
+
"quiet_empty_input",
|
|
20
|
+
]);
|
|
21
|
+
const BLOCKED_REASONS = new Set([
|
|
22
|
+
"source_refs_unresolved",
|
|
23
|
+
"proposal_missing_source_refs",
|
|
24
|
+
"judgment_missing_source_refs",
|
|
25
|
+
"quiet_redaction_blocked",
|
|
26
|
+
"dream_blocked_redaction",
|
|
27
|
+
"dream_blocked_no_content",
|
|
28
|
+
"dream_blocked_private_redacted",
|
|
29
|
+
"dream_blocked_credential",
|
|
30
|
+
"dream_blocked_validation_failed",
|
|
31
|
+
"dream_interval_active",
|
|
32
|
+
]);
|
|
33
|
+
const UNSAFE_REASONS = new Set([
|
|
34
|
+
"policy_denied_high_risk",
|
|
35
|
+
"policy_denied_breaker_open",
|
|
36
|
+
"closure_idempotency_conflict",
|
|
37
|
+
]);
|
|
38
|
+
const PARTIAL_REASONS = new Set([
|
|
39
|
+
"evidence_batch_truncated",
|
|
40
|
+
"perception_rules_only",
|
|
41
|
+
"dream_rules_only",
|
|
42
|
+
"dream_model_timeout",
|
|
43
|
+
"quiet_validation_failed",
|
|
44
|
+
"closure_downgraded_without_draft",
|
|
45
|
+
]);
|
|
46
|
+
const UNAVAILABLE_REASONS = new Set([
|
|
47
|
+
"state_unreadable",
|
|
48
|
+
"quiet_state_unreadable",
|
|
49
|
+
"ingestion_state_unreadable",
|
|
50
|
+
"stage_event_missing",
|
|
51
|
+
"execution_unavailable",
|
|
52
|
+
"guidance_unavailable",
|
|
53
|
+
"dream_scheduler_unavailable",
|
|
54
|
+
"policy_denied_missing_permission",
|
|
55
|
+
"closure_failed",
|
|
56
|
+
"closure_unavailable",
|
|
57
|
+
]);
|
|
58
|
+
export function classifyDegradedStatus(reason) {
|
|
59
|
+
if (EMPTY_REASONS.has(reason))
|
|
60
|
+
return "empty";
|
|
61
|
+
if (BLOCKED_REASONS.has(reason))
|
|
62
|
+
return "blocked";
|
|
63
|
+
if (UNSAFE_REASONS.has(reason))
|
|
64
|
+
return "unsafe";
|
|
65
|
+
if (PARTIAL_REASONS.has(reason))
|
|
66
|
+
return "partial";
|
|
67
|
+
return "unavailable";
|
|
68
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EvidenceLevelClassifier — Monotonic evidence-level taxonomy for operator-facing surfaces.
|
|
3
|
+
*
|
|
4
|
+
* Core logic: classify how strongly a command/response is backed by runtime proof.
|
|
5
|
+
* Levels are ordered from weakest (carrier_ack) to strongest (durable_verified).
|
|
6
|
+
* A level may only stay the same or increase within one command; synthetic/carrier
|
|
7
|
+
* proofs can never be promoted to real_runtime or durable_verified.
|
|
8
|
+
*
|
|
9
|
+
* Design authority:
|
|
10
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/shared-v8-contracts.md §4.2, §4.3`
|
|
11
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/runtime-ops-system.md §2, §3.3`
|
|
12
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/observability-health-system.md §6.1`
|
|
13
|
+
*
|
|
14
|
+
* Dependencies: `src/shared/types/v8-contracts.js` (EvidenceLevel)
|
|
15
|
+
* Boundary: Pure classification functions; no storage or side effects.
|
|
16
|
+
* Test coverage: tests/unit/shared/evidence-level-classifier.test.ts
|
|
17
|
+
*/
|
|
18
|
+
import type { EvidenceLevel } from "./types/v8-contracts.js";
|
|
19
|
+
export type { EvidenceLevel };
|
|
20
|
+
export declare const EVIDENCE_LEVEL_ORDER: Record<EvidenceLevel, number>;
|
|
21
|
+
export interface EvidenceLevelProofInput {
|
|
22
|
+
/** Host/plugin/CLI produced an envelope but no Second Nature contract path ran. */
|
|
23
|
+
hasCarrierEnvelope?: boolean;
|
|
24
|
+
/** Static/fixture contract path ran without proving live state mutation. */
|
|
25
|
+
hasContractSmoke?: boolean;
|
|
26
|
+
/** Durable state was read or existing rows were observed. */
|
|
27
|
+
hasStatePresent?: boolean;
|
|
28
|
+
/** Current v8 living-loop command executed and produced stage + closure proof. */
|
|
29
|
+
hasCycleExecution?: boolean;
|
|
30
|
+
/** real_runtime proof was persisted and read back through normal read model. */
|
|
31
|
+
hasReadbackVerification?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Classify evidence level from observed proof flags.
|
|
35
|
+
* Returns the strongest level whose required proof is present.
|
|
36
|
+
*/
|
|
37
|
+
export declare function classifyEvidenceLevel(input: EvidenceLevelProofInput): EvidenceLevel;
|
|
38
|
+
/**
|
|
39
|
+
* Cap a candidate level by a maximum allowed level.
|
|
40
|
+
* Used to prevent carrier/smoke proofs from masquerading as real runtime health.
|
|
41
|
+
*/
|
|
42
|
+
export declare function capEvidenceLevel(candidate: EvidenceLevel, cap: EvidenceLevel): EvidenceLevel;
|
|
43
|
+
/**
|
|
44
|
+
* Promote a current level to a target level only if target is strictly stronger
|
|
45
|
+
* and promotion is supported by supplied proof.
|
|
46
|
+
*/
|
|
47
|
+
export declare function promoteEvidenceLevel(current: EvidenceLevel, target: EvidenceLevel, proof: EvidenceLevelProofInput): EvidenceLevel;
|
|
48
|
+
/**
|
|
49
|
+
* Aggregate multiple stage evidence levels into the minimum (weakest) level.
|
|
50
|
+
* A chain is only as strong as its weakest proven stage.
|
|
51
|
+
*/
|
|
52
|
+
export declare function minEvidenceLevel(levels: EvidenceLevel[]): EvidenceLevel;
|
|
53
|
+
/**
|
|
54
|
+
* Convenience: the strongest level achievable from the input proof set,
|
|
55
|
+
* but never exceeding a hard cap.
|
|
56
|
+
*/
|
|
57
|
+
export declare function classifyAndCapEvidenceLevel(input: EvidenceLevelProofInput, cap: EvidenceLevel): EvidenceLevel;
|
|
58
|
+
/**
|
|
59
|
+
* Map an evidence level to a human-readable operator note.
|
|
60
|
+
*/
|
|
61
|
+
export declare function evidenceLevelDescription(level: EvidenceLevel): string;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EvidenceLevelClassifier — Monotonic evidence-level taxonomy for operator-facing surfaces.
|
|
3
|
+
*
|
|
4
|
+
* Core logic: classify how strongly a command/response is backed by runtime proof.
|
|
5
|
+
* Levels are ordered from weakest (carrier_ack) to strongest (durable_verified).
|
|
6
|
+
* A level may only stay the same or increase within one command; synthetic/carrier
|
|
7
|
+
* proofs can never be promoted to real_runtime or durable_verified.
|
|
8
|
+
*
|
|
9
|
+
* Design authority:
|
|
10
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/shared-v8-contracts.md §4.2, §4.3`
|
|
11
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/runtime-ops-system.md §2, §3.3`
|
|
12
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/observability-health-system.md §6.1`
|
|
13
|
+
*
|
|
14
|
+
* Dependencies: `src/shared/types/v8-contracts.js` (EvidenceLevel)
|
|
15
|
+
* Boundary: Pure classification functions; no storage or side effects.
|
|
16
|
+
* Test coverage: tests/unit/shared/evidence-level-classifier.test.ts
|
|
17
|
+
*/
|
|
18
|
+
export const EVIDENCE_LEVEL_ORDER = {
|
|
19
|
+
carrier_ack: 0,
|
|
20
|
+
contract_smoke: 1,
|
|
21
|
+
state_present: 2,
|
|
22
|
+
real_runtime: 3,
|
|
23
|
+
durable_verified: 4,
|
|
24
|
+
};
|
|
25
|
+
const LEVELS_BY_ORDER = [
|
|
26
|
+
"carrier_ack",
|
|
27
|
+
"contract_smoke",
|
|
28
|
+
"state_present",
|
|
29
|
+
"real_runtime",
|
|
30
|
+
"durable_verified",
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Classify evidence level from observed proof flags.
|
|
34
|
+
* Returns the strongest level whose required proof is present.
|
|
35
|
+
*/
|
|
36
|
+
export function classifyEvidenceLevel(input) {
|
|
37
|
+
if (input.hasReadbackVerification && input.hasCycleExecution) {
|
|
38
|
+
return "durable_verified";
|
|
39
|
+
}
|
|
40
|
+
if (input.hasCycleExecution) {
|
|
41
|
+
return "real_runtime";
|
|
42
|
+
}
|
|
43
|
+
if (input.hasStatePresent) {
|
|
44
|
+
return "state_present";
|
|
45
|
+
}
|
|
46
|
+
if (input.hasContractSmoke) {
|
|
47
|
+
return "contract_smoke";
|
|
48
|
+
}
|
|
49
|
+
if (input.hasCarrierEnvelope) {
|
|
50
|
+
return "carrier_ack";
|
|
51
|
+
}
|
|
52
|
+
// Default: carrier_ack when an envelope exists but no stronger proof was supplied.
|
|
53
|
+
return "carrier_ack";
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Cap a candidate level by a maximum allowed level.
|
|
57
|
+
* Used to prevent carrier/smoke proofs from masquerading as real runtime health.
|
|
58
|
+
*/
|
|
59
|
+
export function capEvidenceLevel(candidate, cap) {
|
|
60
|
+
if (EVIDENCE_LEVEL_ORDER[candidate] <= EVIDENCE_LEVEL_ORDER[cap]) {
|
|
61
|
+
return candidate;
|
|
62
|
+
}
|
|
63
|
+
return cap;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Promote a current level to a target level only if target is strictly stronger
|
|
67
|
+
* and promotion is supported by supplied proof.
|
|
68
|
+
*/
|
|
69
|
+
export function promoteEvidenceLevel(current, target, proof) {
|
|
70
|
+
const classified = classifyEvidenceLevel(proof);
|
|
71
|
+
if (EVIDENCE_LEVEL_ORDER[classified] >= EVIDENCE_LEVEL_ORDER[target]) {
|
|
72
|
+
return capEvidenceLevel(target, classified);
|
|
73
|
+
}
|
|
74
|
+
return current;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Aggregate multiple stage evidence levels into the minimum (weakest) level.
|
|
78
|
+
* A chain is only as strong as its weakest proven stage.
|
|
79
|
+
*/
|
|
80
|
+
export function minEvidenceLevel(levels) {
|
|
81
|
+
if (levels.length === 0)
|
|
82
|
+
return "carrier_ack";
|
|
83
|
+
let min = levels[0];
|
|
84
|
+
for (const level of levels) {
|
|
85
|
+
if (EVIDENCE_LEVEL_ORDER[level] < EVIDENCE_LEVEL_ORDER[min]) {
|
|
86
|
+
min = level;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return min;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Convenience: the strongest level achievable from the input proof set,
|
|
93
|
+
* but never exceeding a hard cap.
|
|
94
|
+
*/
|
|
95
|
+
export function classifyAndCapEvidenceLevel(input, cap) {
|
|
96
|
+
return capEvidenceLevel(classifyEvidenceLevel(input), cap);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Map an evidence level to a human-readable operator note.
|
|
100
|
+
*/
|
|
101
|
+
export function evidenceLevelDescription(level) {
|
|
102
|
+
switch (level) {
|
|
103
|
+
case "carrier_ack":
|
|
104
|
+
return "Host/plugin returned an envelope but no Second Nature contract path ran.";
|
|
105
|
+
case "contract_smoke":
|
|
106
|
+
return "Static or fixture contract path passed without live state mutation.";
|
|
107
|
+
case "state_present":
|
|
108
|
+
return "Durable state exists or was read, but no current cycle executed.";
|
|
109
|
+
case "real_runtime":
|
|
110
|
+
return "Current v8 living-loop command executed and produced stage/closure proof.";
|
|
111
|
+
case "durable_verified":
|
|
112
|
+
return "Real runtime proof persisted and read back through the normal read model.";
|
|
113
|
+
default:
|
|
114
|
+
return "Unknown evidence level.";
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provenance Tier Validator (T-SH.R.6)
|
|
3
|
+
*
|
|
4
|
+
* Core logic: enforce that real domain evidence lives in `sourceRefs`,
|
|
5
|
+
* runtime/policy/setup/host/packaging proofs live in `proofRefs`, and
|
|
6
|
+
* observability/audit/stage-event traces live in `traceRefs`.
|
|
7
|
+
*
|
|
8
|
+
* Design authority:
|
|
9
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/shared-v8-contracts.md §2.2`
|
|
10
|
+
*
|
|
11
|
+
* Dependencies: `src/shared/types/v8-contracts.js`
|
|
12
|
+
* Boundary: pure functions; no I/O.
|
|
13
|
+
* Test coverage: tests/unit/shared/provenance-tier.test.ts
|
|
14
|
+
*/
|
|
15
|
+
import type { SourceRef, ProvenanceBundle } from "./types/v8-contracts.js";
|
|
16
|
+
export interface ProvenanceValidationError {
|
|
17
|
+
ok: false;
|
|
18
|
+
field: "sourceRefs" | "proofRefs" | "traceRefs";
|
|
19
|
+
ref: SourceRef;
|
|
20
|
+
reason: string;
|
|
21
|
+
}
|
|
22
|
+
export type ProvenanceValidationResult = {
|
|
23
|
+
ok: true;
|
|
24
|
+
bundle: ProvenanceBundle;
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
errors: ProvenanceValidationError[];
|
|
28
|
+
};
|
|
29
|
+
export declare function validateProvenanceTiers(bundle: ProvenanceBundle): ProvenanceValidationResult;
|
|
30
|
+
export declare function buildClosureProvenance(input: {
|
|
31
|
+
sourceRefs?: SourceRef[];
|
|
32
|
+
proofRefs?: SourceRef[];
|
|
33
|
+
traceRefs?: SourceRef[];
|
|
34
|
+
}): ProvenanceBundle;
|
|
35
|
+
export declare function cycleTraceRef(cycleId: string): SourceRef;
|
|
36
|
+
export declare function closureTraceRef(closureId: string): SourceRef;
|
|
37
|
+
export declare function decisionProofRef(decisionId: string): SourceRef;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provenance Tier Validator (T-SH.R.6)
|
|
3
|
+
*
|
|
4
|
+
* Core logic: enforce that real domain evidence lives in `sourceRefs`,
|
|
5
|
+
* runtime/policy/setup/host/packaging proofs live in `proofRefs`, and
|
|
6
|
+
* observability/audit/stage-event traces live in `traceRefs`.
|
|
7
|
+
*
|
|
8
|
+
* Design authority:
|
|
9
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/shared-v8-contracts.md §2.2`
|
|
10
|
+
*
|
|
11
|
+
* Dependencies: `src/shared/types/v8-contracts.js`
|
|
12
|
+
* Boundary: pure functions; no I/O.
|
|
13
|
+
* Test coverage: tests/unit/shared/provenance-tier.test.ts
|
|
14
|
+
*/
|
|
15
|
+
const PROOF_FAMILIES = new Set([
|
|
16
|
+
"action_closure",
|
|
17
|
+
"audit",
|
|
18
|
+
"connector_result",
|
|
19
|
+
]);
|
|
20
|
+
const TRACE_FAMILIES = new Set([
|
|
21
|
+
"audit",
|
|
22
|
+
]);
|
|
23
|
+
function isSyntheticProof(ref) {
|
|
24
|
+
return (ref.uri.startsWith("sn://closure/") ||
|
|
25
|
+
ref.uri.startsWith("sn://decision/") ||
|
|
26
|
+
ref.uri.startsWith("sn://policy/") ||
|
|
27
|
+
ref.uri.startsWith("sn://execution/") ||
|
|
28
|
+
ref.uri.startsWith("sn://cycle/") ||
|
|
29
|
+
ref.uri.startsWith("sn://host/") ||
|
|
30
|
+
ref.uri.startsWith("sn://setup/") ||
|
|
31
|
+
PROOF_FAMILIES.has(ref.family));
|
|
32
|
+
}
|
|
33
|
+
function isTraceRef(ref) {
|
|
34
|
+
return (ref.uri.startsWith("sn://cycle/") ||
|
|
35
|
+
ref.uri.startsWith("sn://event/") ||
|
|
36
|
+
TRACE_FAMILIES.has(ref.family));
|
|
37
|
+
}
|
|
38
|
+
export function validateProvenanceTiers(bundle) {
|
|
39
|
+
const errors = [];
|
|
40
|
+
for (const ref of bundle.sourceRefs) {
|
|
41
|
+
if (isSyntheticProof(ref)) {
|
|
42
|
+
errors.push({
|
|
43
|
+
ok: false,
|
|
44
|
+
field: "sourceRefs",
|
|
45
|
+
ref,
|
|
46
|
+
reason: "Synthetic proof/trace ref must not live in sourceRefs",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const ref of bundle.proofRefs) {
|
|
51
|
+
if (isTraceRef(ref)) {
|
|
52
|
+
errors.push({
|
|
53
|
+
ok: false,
|
|
54
|
+
field: "proofRefs",
|
|
55
|
+
ref,
|
|
56
|
+
reason: "Trace ref must live in traceRefs, not proofRefs",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return errors.length === 0
|
|
61
|
+
? { ok: true, bundle }
|
|
62
|
+
: { ok: false, errors };
|
|
63
|
+
}
|
|
64
|
+
export function buildClosureProvenance(input) {
|
|
65
|
+
return {
|
|
66
|
+
sourceRefs: input.sourceRefs ?? [],
|
|
67
|
+
proofRefs: input.proofRefs ?? [],
|
|
68
|
+
traceRefs: input.traceRefs ?? [],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function cycleTraceRef(cycleId) {
|
|
72
|
+
return {
|
|
73
|
+
uri: `sn://cycle/${cycleId}`,
|
|
74
|
+
family: "audit",
|
|
75
|
+
id: cycleId,
|
|
76
|
+
redactionClass: "none",
|
|
77
|
+
resolveStatus: "resolvable",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function closureTraceRef(closureId) {
|
|
81
|
+
return {
|
|
82
|
+
uri: `sn://closure/${closureId}`,
|
|
83
|
+
family: "action_closure",
|
|
84
|
+
id: closureId,
|
|
85
|
+
redactionClass: "none",
|
|
86
|
+
resolveStatus: "resolvable",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function decisionProofRef(decisionId) {
|
|
90
|
+
return {
|
|
91
|
+
uri: `sn://decision/${decisionId}`,
|
|
92
|
+
family: "action_closure",
|
|
93
|
+
id: decisionId,
|
|
94
|
+
redactionClass: "none",
|
|
95
|
+
resolveStatus: "resolvable",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup Ack Truth Contract (T-ROS.R.8)
|
|
3
|
+
*
|
|
4
|
+
* Core logic: define the canonical SetupAck schema and a validator that rejects
|
|
5
|
+
* `placedIn: "unspecified"`, missing fields, unknown writers, and hand-written
|
|
6
|
+
* files that do not satisfy the schema.
|
|
7
|
+
*
|
|
8
|
+
* Design authority:
|
|
9
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/runtime-ops-system.md §3.2`
|
|
10
|
+
*
|
|
11
|
+
* Dependencies: none (plain validation to keep plugin load lightweight)
|
|
12
|
+
* Boundary: Pure validation; no I/O.
|
|
13
|
+
* Test coverage: tests/unit/shared/setup-ack-validator.test.ts
|
|
14
|
+
*/
|
|
15
|
+
export declare const SETUP_ACK_SCHEMA_VERSION = 1;
|
|
16
|
+
export type SetupAckPlacement = "workspace_guide" | "host_skill_registry" | "agent_profile" | "manual_operator_instruction";
|
|
17
|
+
export type SetupAckWriter = "setup_ack_command" | "host_setup_bridge";
|
|
18
|
+
export interface SetupAck {
|
|
19
|
+
schemaVersion: typeof SETUP_ACK_SCHEMA_VERSION;
|
|
20
|
+
acknowledgedAt: string;
|
|
21
|
+
placedIn: SetupAckPlacement;
|
|
22
|
+
placementProofRef: string;
|
|
23
|
+
writer: SetupAckWriter;
|
|
24
|
+
hostName?: string;
|
|
25
|
+
hostVersion?: string;
|
|
26
|
+
acceptedBy?: string;
|
|
27
|
+
note?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface SetupAckValidationResult {
|
|
30
|
+
ok: true;
|
|
31
|
+
ack: SetupAck;
|
|
32
|
+
}
|
|
33
|
+
export interface SetupAckValidationError {
|
|
34
|
+
ok: false;
|
|
35
|
+
field: string;
|
|
36
|
+
reason: string;
|
|
37
|
+
repairAction: string;
|
|
38
|
+
}
|
|
39
|
+
export type ValidateSetupAckResult = SetupAckValidationResult | {
|
|
40
|
+
ok: false;
|
|
41
|
+
errors: SetupAckValidationError[];
|
|
42
|
+
};
|
|
43
|
+
export declare function validateSetupAck(raw: Record<string, unknown>): ValidateSetupAckResult;
|
|
44
|
+
/**
|
|
45
|
+
* Check whether a raw marker object can be considered a complete ack.
|
|
46
|
+
* Hand-written files are treated as incomplete until verified.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isSetupAckComplete(raw: Record<string, unknown>): {
|
|
49
|
+
complete: true;
|
|
50
|
+
ack: SetupAck;
|
|
51
|
+
} | {
|
|
52
|
+
complete: false;
|
|
53
|
+
errors: SetupAckValidationError[];
|
|
54
|
+
};
|