@haaaiawd/second-nature 0.1.27 → 0.1.29

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 (157) hide show
  1. package/SKILL.md +35 -33
  2. package/agent-inner-guide.md +144 -124
  3. package/index.js +76 -1
  4. package/openclaw.plugin.json +2 -2
  5. package/package.json +2 -1
  6. package/runtime/cli/commands/connector-behavior.d.ts +20 -0
  7. package/runtime/cli/commands/connector-behavior.js +160 -0
  8. package/runtime/cli/commands/index.js +8 -0
  9. package/runtime/cli/index.js +9 -2
  10. package/runtime/cli/ops/manual-run-dispatcher.d.ts +79 -0
  11. package/runtime/cli/ops/manual-run-dispatcher.js +110 -0
  12. package/runtime/cli/ops/ops-router.d.ts +45 -4
  13. package/runtime/cli/ops/ops-router.js +543 -2
  14. package/runtime/cli/read-models/index.js +35 -18
  15. package/runtime/cli/read-models/types.d.ts +1 -0
  16. package/runtime/connectors/agent-network/agent-world/adapter.d.ts +1 -0
  17. package/runtime/connectors/agent-network/agent-world/adapter.js +2 -2
  18. package/runtime/connectors/base/contract.d.ts +4 -1
  19. package/runtime/connectors/base/contract.js +5 -1
  20. package/runtime/connectors/base/effect-commit-ledger-sqlite.d.ts +31 -0
  21. package/runtime/connectors/base/effect-commit-ledger-sqlite.js +86 -0
  22. package/runtime/connectors/base/failure-taxonomy.js +5 -0
  23. package/runtime/connectors/base/manifest-v7.d.ts +151 -0
  24. package/runtime/connectors/base/manifest-v7.js +170 -0
  25. package/runtime/connectors/base/manifest.d.ts +67 -77
  26. package/runtime/connectors/base/manifest.js +7 -7
  27. package/runtime/connectors/base/route-planner.js +11 -8
  28. package/runtime/connectors/base/structured-unavailable-reason.d.ts +59 -0
  29. package/runtime/connectors/base/structured-unavailable-reason.js +113 -0
  30. package/runtime/connectors/base/wet-probe-runner.d.ts +40 -0
  31. package/runtime/connectors/base/wet-probe-runner.js +132 -0
  32. package/runtime/connectors/manifest/manifest-schema.d.ts +4 -0
  33. package/runtime/connectors/manifest/manifest-schema.js +2 -0
  34. package/runtime/connectors/services/connector-executor-adapter.d.ts +1 -0
  35. package/runtime/connectors/services/connector-executor-adapter.js +132 -26
  36. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.d.ts +45 -0
  37. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.js +132 -0
  38. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.d.ts +60 -0
  39. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.js +174 -0
  40. package/runtime/core/second-nature/body/probe-signal-adapter.d.ts +38 -0
  41. package/runtime/core/second-nature/body/probe-signal-adapter.js +60 -0
  42. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.d.ts +51 -0
  43. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.js +129 -0
  44. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.d.ts +30 -0
  45. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.js +92 -0
  46. package/runtime/core/second-nature/body/tool-experience/experience-writer.d.ts +34 -0
  47. package/runtime/core/second-nature/body/tool-experience/experience-writer.js +67 -0
  48. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.d.ts +37 -0
  49. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.js +62 -0
  50. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.d.ts +29 -0
  51. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.js +28 -0
  52. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.d.ts +54 -0
  53. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.js +164 -0
  54. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.d.ts +37 -0
  55. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.js +61 -0
  56. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.d.ts +37 -0
  57. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.js +60 -0
  58. package/runtime/core/second-nature/heartbeat/index.d.ts +4 -0
  59. package/runtime/core/second-nature/heartbeat/index.js +5 -0
  60. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.d.ts +63 -0
  61. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.js +118 -0
  62. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.d.ts +41 -0
  63. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.js +43 -0
  64. package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +2 -1
  65. package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +2 -0
  66. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.d.ts +31 -0
  67. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.js +102 -0
  68. package/runtime/core/second-nature/orchestrator/index.d.ts +5 -0
  69. package/runtime/core/second-nature/orchestrator/index.js +7 -0
  70. package/runtime/core/second-nature/quiet/claim-synthesizer.d.ts +53 -0
  71. package/runtime/core/second-nature/quiet/claim-synthesizer.js +153 -0
  72. package/runtime/core/second-nature/quiet/daily-diary-writer.d.ts +29 -0
  73. package/runtime/core/second-nature/quiet/daily-diary-writer.js +92 -0
  74. package/runtime/core/second-nature/quiet/index.d.ts +5 -0
  75. package/runtime/core/second-nature/quiet/index.js +5 -0
  76. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +19 -12
  77. package/runtime/core/second-nature/types.d.ts +2 -0
  78. package/runtime/guidance/channel-feedback-ingestion-service.d.ts +88 -0
  79. package/runtime/guidance/channel-feedback-ingestion-service.js +231 -0
  80. package/runtime/guidance/guidance-draft-service.d.ts +60 -0
  81. package/runtime/guidance/guidance-draft-service.js +80 -0
  82. package/runtime/guidance/index.d.ts +3 -0
  83. package/runtime/guidance/index.js +3 -0
  84. package/runtime/guidance/outreach-draft-schema.d.ts +8 -8
  85. package/runtime/guidance/outreach-strategy-selector.d.ts +77 -0
  86. package/runtime/guidance/outreach-strategy-selector.js +211 -0
  87. package/runtime/observability/audit/append-only-audit-store.d.ts +20 -2
  88. package/runtime/observability/audit/append-only-audit-store.js +32 -6
  89. package/runtime/observability/audit/audit-envelope.d.ts +2 -1
  90. package/runtime/observability/audit/audit-envelope.js +8 -7
  91. package/runtime/observability/audit/audit-family-registry.json +66 -0
  92. package/runtime/observability/audit/family-registry.d.ts +43 -0
  93. package/runtime/observability/audit/family-registry.js +70 -0
  94. package/runtime/observability/index.d.ts +6 -1
  95. package/runtime/observability/index.js +6 -1
  96. package/runtime/observability/redaction/policy.d.ts +24 -3
  97. package/runtime/observability/redaction/policy.js +74 -0
  98. package/runtime/observability/services/heartbeat-digest-assembler.d.ts +152 -0
  99. package/runtime/observability/services/heartbeat-digest-assembler.js +248 -0
  100. package/runtime/observability/services/lived-experience-audit.js +6 -6
  101. package/runtime/observability/services/narrative-timeline-query-service.d.ts +136 -0
  102. package/runtime/observability/services/narrative-timeline-query-service.js +169 -0
  103. package/runtime/observability/services/restore-audit-service.d.ts +74 -0
  104. package/runtime/observability/services/restore-audit-service.js +79 -0
  105. package/runtime/observability/services/runtime-secret-anchor-view.d.ts +77 -0
  106. package/runtime/observability/services/runtime-secret-anchor-view.js +168 -0
  107. package/runtime/observability/services/self-health-snapshot.d.ts +92 -0
  108. package/runtime/observability/services/self-health-snapshot.js +251 -0
  109. package/runtime/shared/types/goal.d.ts +62 -0
  110. package/runtime/shared/types/goal.js +20 -0
  111. package/runtime/shared/types/index.d.ts +3 -0
  112. package/runtime/shared/types/index.js +3 -0
  113. package/runtime/shared/types/source-ref.d.ts +14 -0
  114. package/runtime/shared/types/source-ref.js +1 -0
  115. package/runtime/shared/types/v7-entities.d.ts +206 -0
  116. package/runtime/shared/types/v7-entities.js +27 -0
  117. package/runtime/storage/db/index.js +3 -0
  118. package/runtime/storage/db/migration-runner.d.ts +30 -0
  119. package/runtime/storage/db/migration-runner.js +93 -0
  120. package/runtime/storage/db/migrations/index.d.ts +5 -0
  121. package/runtime/storage/db/migrations/index.js +13 -0
  122. package/runtime/storage/db/migrations/v7-001-foundation.d.ts +13 -0
  123. package/runtime/storage/db/migrations/v7-001-foundation.js +144 -0
  124. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.d.ts +8 -0
  125. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.js +27 -0
  126. package/runtime/storage/db/migrations/v7-003-circuit-breaker.d.ts +7 -0
  127. package/runtime/storage/db/migrations/v7-003-circuit-breaker.js +26 -0
  128. package/runtime/storage/db/migrations/v7-004-behavior-promotion.d.ts +7 -0
  129. package/runtime/storage/db/migrations/v7-004-behavior-promotion.js +26 -0
  130. package/runtime/storage/db/schema/agent-goal.d.ts +38 -0
  131. package/runtime/storage/db/schema/agent-goal.js +2 -0
  132. package/runtime/storage/db/transaction-utils.d.ts +14 -0
  133. package/runtime/storage/db/transaction-utils.js +29 -0
  134. package/runtime/storage/db/write-queue.d.ts +38 -0
  135. package/runtime/storage/db/write-queue.js +97 -0
  136. package/runtime/storage/quiet/persist-quiet-artifact.js +2 -1
  137. package/runtime/storage/services/diary-dream-store.d.ts +35 -0
  138. package/runtime/storage/services/diary-dream-store.js +165 -0
  139. package/runtime/storage/services/embodied-context-state-port.d.ts +77 -0
  140. package/runtime/storage/services/embodied-context-state-port.js +115 -0
  141. package/runtime/storage/services/goal-lifecycle-store.d.ts +42 -0
  142. package/runtime/storage/services/goal-lifecycle-store.js +181 -0
  143. package/runtime/storage/services/history-digest-store.d.ts +33 -0
  144. package/runtime/storage/services/history-digest-store.js +140 -0
  145. package/runtime/storage/services/identity-profile-store.d.ts +25 -0
  146. package/runtime/storage/services/identity-profile-store.js +81 -0
  147. package/runtime/storage/services/interaction-snapshot-projector.d.ts +15 -0
  148. package/runtime/storage/services/interaction-snapshot-projector.js +35 -0
  149. package/runtime/storage/services/restore-snapshot-store.d.ts +52 -0
  150. package/runtime/storage/services/restore-snapshot-store.js +193 -0
  151. package/runtime/storage/services/runtime-secret-anchor-store.d.ts +26 -0
  152. package/runtime/storage/services/runtime-secret-anchor-store.js +82 -0
  153. package/runtime/storage/services/tool-experience-store.d.ts +25 -0
  154. package/runtime/storage/services/tool-experience-store.js +116 -0
  155. package/runtime/storage/services/write-validation-gate.d.ts +46 -0
  156. package/runtime/storage/services/write-validation-gate.js +200 -0
  157. package/workspace-ops-bridge.js +16 -1
@@ -55,19 +55,31 @@ function buildCredentialNextStep(status) {
55
55
  return "verify_or_re_create_credential_then_re_import";
56
56
  return undefined;
57
57
  }
58
- /**
59
- * T1.2.4: count persisted Quiet artifact JSON files under `.second-nature/quiet/{day}/`
60
- * so `loadQuiet` / `loadDailyReport` can reflect Quiet artifacts in the read model.
61
- */
62
- function countQuietArtifactsForDay(workspaceRoot, day) {
58
+ function classifyQuietArtifactsForDay(workspaceRoot, day) {
63
59
  try {
64
60
  const dir = path.join(workspaceRoot, ".second-nature", "quiet", day);
65
61
  if (!fs.existsSync(dir))
66
- return 0;
67
- return fs.readdirSync(dir).filter((f) => f.endsWith(".json")).length;
62
+ return { reportArtifacts: 0, emptyStateArtifacts: 0, totalArtifacts: 0 };
63
+ let reportArtifacts = 0;
64
+ let emptyStateArtifacts = 0;
65
+ let totalArtifacts = 0;
66
+ for (const file of fs.readdirSync(dir).filter((f) => f.endsWith(".json"))) {
67
+ totalArtifacts++;
68
+ try {
69
+ const raw = JSON.parse(fs.readFileSync(path.join(dir, file), "utf-8"));
70
+ if (raw.write?.kind === "empty_state")
71
+ emptyStateArtifacts++;
72
+ else
73
+ reportArtifacts++;
74
+ }
75
+ catch {
76
+ reportArtifacts++;
77
+ }
78
+ }
79
+ return { reportArtifacts, emptyStateArtifacts, totalArtifacts };
68
80
  }
69
81
  catch {
70
- return 0;
82
+ return { reportArtifacts: 0, emptyStateArtifacts: 0, totalArtifacts: 0 };
71
83
  }
72
84
  }
73
85
  /**
@@ -78,22 +90,26 @@ function countRecentQuietArtifacts(workspaceRoot, windowDays = 2) {
78
90
  try {
79
91
  const quietRoot = path.join(workspaceRoot, ".second-nature", "quiet");
80
92
  if (!fs.existsSync(quietRoot))
81
- return { totalArtifacts: 0, recentDays: [] };
93
+ return { totalArtifacts: 0, reportArtifacts: 0, emptyStateArtifacts: 0, recentDays: [] };
82
94
  const now = Date.now();
83
95
  const recentDays = [];
84
96
  let total = 0;
97
+ let reports = 0;
98
+ let emptyStates = 0;
85
99
  for (let i = 0; i < windowDays; i++) {
86
100
  const d = new Date(now - i * 86400000).toISOString().slice(0, 10);
87
- const count = countQuietArtifactsForDay(workspaceRoot, d);
88
- if (count > 0) {
101
+ const count = classifyQuietArtifactsForDay(workspaceRoot, d);
102
+ if (count.totalArtifacts > 0) {
89
103
  recentDays.push(d);
90
- total += count;
104
+ total += count.totalArtifacts;
105
+ reports += count.reportArtifacts;
106
+ emptyStates += count.emptyStateArtifacts;
91
107
  }
92
108
  }
93
- return { totalArtifacts: total, recentDays };
109
+ return { totalArtifacts: total, reportArtifacts: reports, emptyStateArtifacts: emptyStates, recentDays };
94
110
  }
95
111
  catch {
96
- return { totalArtifacts: 0, recentDays: [] };
112
+ return { totalArtifacts: 0, reportArtifacts: 0, emptyStateArtifacts: 0, recentDays: [] };
97
113
  }
98
114
  }
99
115
  function mapRuntimeStatus(attempt) {
@@ -282,7 +298,7 @@ export function createCliReadModels(deps) {
282
298
  // into the daily report sourceRefs so the read model reflects artifacts written by
283
299
  // `persistQuietArtifactToWorkspace` (closes the canonical read/write gap for loadDailyReport).
284
300
  const fsArtifactCount = deps.workspaceRoot
285
- ? countQuietArtifactsForDay(deps.workspaceRoot, day)
301
+ ? classifyQuietArtifactsForDay(deps.workspaceRoot, day).reportArtifacts
286
302
  : 0;
287
303
  const report = bundle.dailyReports[0];
288
304
  const existingSources = report?.sources ?? [];
@@ -314,15 +330,16 @@ export function createCliReadModels(deps) {
314
330
  // journal path is empty.
315
331
  const quietArtifacts = deps.workspaceRoot
316
332
  ? countRecentQuietArtifacts(deps.workspaceRoot, 2)
317
- : { totalArtifacts: 0, recentDays: [] };
318
- const totalSourceCount = bundle.sourceCount + quietArtifacts.totalArtifacts;
319
- const totalReportCount = bundle.dailyReports.length + quietArtifacts.totalArtifacts;
333
+ : { totalArtifacts: 0, reportArtifacts: 0, emptyStateArtifacts: 0, recentDays: [] };
334
+ const totalSourceCount = bundle.sourceCount + quietArtifacts.reportArtifacts;
335
+ const totalReportCount = bundle.dailyReports.length + quietArtifacts.reportArtifacts;
320
336
  return {
321
337
  scope,
322
338
  mode: totalSourceCount > 0 ? "quiet" : "unknown",
323
339
  sourceCount: totalSourceCount,
324
340
  reportCount: totalReportCount,
325
341
  recentJournalCount: bundle.journalEntries.length,
342
+ emptyStateCount: quietArtifacts.emptyStateArtifacts,
326
343
  };
327
344
  },
328
345
  async loadSession(sessionId) {
@@ -119,6 +119,7 @@ export interface QuietReadModel {
119
119
  sourceCount: number;
120
120
  reportCount: number;
121
121
  recentJournalCount: number;
122
+ emptyStateCount?: number;
122
123
  }
123
124
  export interface SessionDetailReadModel {
124
125
  requestedSessionId: string;
@@ -6,6 +6,7 @@ export interface AgentWorldApiClient {
6
6
  }
7
7
  export declare function createAgentWorldRunner(input: {
8
8
  apiClient: AgentWorldApiClient;
9
+ apiKey?: string;
9
10
  }): {
10
11
  run(plan: ExecutionPlan, request: ConnectorRequest): Promise<RawAttempt>;
11
12
  };
@@ -1,10 +1,10 @@
1
1
  export function createAgentWorldRunner(input) {
2
- const { apiClient } = input;
2
+ const { apiClient, apiKey: configuredApiKey } = input;
3
3
  return {
4
4
  async run(plan, request) {
5
5
  const started = Date.now();
6
6
  try {
7
- const apiKey = request.payload?.apiKey ?? "";
7
+ const apiKey = configuredApiKey ?? request.payload.apiKey ?? "";
8
8
  if (!apiKey) {
9
9
  return {
10
10
  platformId: request.platformId,
@@ -3,7 +3,9 @@ import { type FailureClass } from "./failure-taxonomy.js";
3
3
  export declare const CHANNEL_TYPES: readonly ["api_rest", "api_rpc", "a2a", "mcp", "cli", "skill", "browser"];
4
4
  export type ChannelType = (typeof CHANNEL_TYPES)[number];
5
5
  export declare const CAPABILITY_INTENTS: readonly ["feed.read", "post.publish", "comment.reply", "notification.list", "message.send", "agent.register", "agent.heartbeat", "work.discover", "task.claim"];
6
- export type CapabilityIntent = (typeof CAPABILITY_INTENTS)[number];
6
+ export type BuiltInCapabilityIntent = (typeof CAPABILITY_INTENTS)[number];
7
+ export type CapabilityIntent = BuiltInCapabilityIntent | (string & {});
8
+ export declare function isKnownCapabilityIntent(intent: string): intent is BuiltInCapabilityIntent;
7
9
  export interface ConnectorRequest {
8
10
  platformId: string;
9
11
  intent: CapabilityIntent;
@@ -28,6 +30,7 @@ export interface ConnectorResult<T> {
28
30
  data?: T;
29
31
  failureClass?: FailureClass;
30
32
  retryAfterMs?: number;
33
+ executionId?: string;
31
34
  metadata: {
32
35
  platformId: string;
33
36
  channel: ChannelType;
@@ -20,9 +20,13 @@ export const CAPABILITY_INTENTS = [
20
20
  "work.discover",
21
21
  "task.claim",
22
22
  ];
23
+ export function isKnownCapabilityIntent(intent) {
24
+ return CAPABILITY_INTENTS.includes(intent);
25
+ }
26
+ const capabilityIntentSchema = z.string().min(1).regex(/^[a-zA-Z0-9_.:-]+$/);
23
27
  const connectorRequestSchema = z.object({
24
28
  platformId: z.string().min(1),
25
- intent: z.enum(CAPABILITY_INTENTS),
29
+ intent: capabilityIntentSchema,
26
30
  payload: z.record(z.string(), z.unknown()),
27
31
  preferredChannel: z.enum(CHANNEL_TYPES).optional(),
28
32
  timeoutMs: z.number().int().positive().optional(),
@@ -0,0 +1,31 @@
1
+ /**
2
+ * EffectCommitLedger SQLite implementation — T-CS.C.2
3
+ *
4
+ * Core logic: Persists effect-commit records to SQLite with idempotency-key
5
+ * uniqueness. On process restart, a previously committed key returns
6
+ * `{ existing: true, record: { ... } }`, enabling replay without side-effect.
7
+ *
8
+ * Implements `EffectCommitLedgerPort` from `./execution-policy.js`.
9
+ *
10
+ * Dependencies:
11
+ * - `StateDatabase` from `../../storage/db/index.js`
12
+ * - `EffectCommitLedgerPort` from `./execution-policy.js`
13
+ *
14
+ * Boundary:
15
+ * - idempotency_key is UNIQUE; duplicate insert attempts are caught and
16
+ * resolved to the existing record.
17
+ * - `markCommitted` is an extension beyond the port for use by execution
18
+ * adapters to transition state from `planned`/`dispatched` to `committed`.
19
+ *
20
+ * Test coverage: tests/unit/connectors/effect-commit-ledger-sqlite.test.ts
21
+ */
22
+ import type { StateDatabase } from "../../storage/db/index.js";
23
+ import type { EffectCommitLedgerPort } from "./execution-policy.js";
24
+ export interface EffectCommitLedgerSQLite extends EffectCommitLedgerPort {
25
+ /** Transition an existing record to committed with an outcome ref. */
26
+ markCommitted(idempotencyKey: string, outcomeRef: string): Promise<{
27
+ ok: boolean;
28
+ previousState?: string;
29
+ }>;
30
+ }
31
+ export declare function createEffectCommitLedgerSQLite(database: StateDatabase): EffectCommitLedgerSQLite;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * EffectCommitLedger SQLite implementation — T-CS.C.2
3
+ *
4
+ * Core logic: Persists effect-commit records to SQLite with idempotency-key
5
+ * uniqueness. On process restart, a previously committed key returns
6
+ * `{ existing: true, record: { ... } }`, enabling replay without side-effect.
7
+ *
8
+ * Implements `EffectCommitLedgerPort` from `./execution-policy.js`.
9
+ *
10
+ * Dependencies:
11
+ * - `StateDatabase` from `../../storage/db/index.js`
12
+ * - `EffectCommitLedgerPort` from `./execution-policy.js`
13
+ *
14
+ * Boundary:
15
+ * - idempotency_key is UNIQUE; duplicate insert attempts are caught and
16
+ * resolved to the existing record.
17
+ * - `markCommitted` is an extension beyond the port for use by execution
18
+ * adapters to transition state from `planned`/`dispatched` to `committed`.
19
+ *
20
+ * Test coverage: tests/unit/connectors/effect-commit-ledger-sqlite.test.ts
21
+ */
22
+ import * as crypto from "node:crypto";
23
+ export function createEffectCommitLedgerSQLite(database) {
24
+ const { sqlite } = database;
25
+ return {
26
+ async getOrCreateIntentCommitRecord(input) {
27
+ // Try insert first; if UNIQUE conflict, read existing
28
+ const commitId = crypto.randomUUID();
29
+ const createdAt = new Date().toISOString();
30
+ try {
31
+ sqlite.run(`INSERT INTO effect_commit_ledger
32
+ (commit_id, idempotency_key, decision_id, intent_id, effect_class, status, created_at)
33
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, [
34
+ commitId,
35
+ input.idempotencyKey,
36
+ input.decisionId,
37
+ input.intentId,
38
+ input.effectClass,
39
+ "planned",
40
+ createdAt,
41
+ ]);
42
+ return {
43
+ existing: false,
44
+ record: { id: commitId, state: "planned" },
45
+ };
46
+ }
47
+ catch (err) {
48
+ const msg = err instanceof Error ? err.message : String(err);
49
+ // SQLite UNIQUE constraint violation codes: 2067 (SQLITE_CONSTRAINT_UNIQUE)
50
+ if (msg.includes("UNIQUE constraint failed") || msg.includes("constraint")) {
51
+ const existingResult = sqlite.exec(`SELECT commit_id, status, outcome_ref
52
+ FROM effect_commit_ledger
53
+ WHERE idempotency_key = ?`, [input.idempotencyKey]);
54
+ if (existingResult.length > 0 &&
55
+ existingResult[0].values.length > 0) {
56
+ const cols = existingResult[0].columns;
57
+ const row = existingResult[0].values[0];
58
+ const get = (name) => row[cols.indexOf(name)];
59
+ return {
60
+ existing: true,
61
+ record: {
62
+ id: get("commit_id"),
63
+ state: get("status"),
64
+ outcomeRef: get("outcome_ref") ?? undefined,
65
+ },
66
+ };
67
+ }
68
+ }
69
+ throw err;
70
+ }
71
+ },
72
+ async markCommitted(idempotencyKey, outcomeRef) {
73
+ const currentResult = sqlite.exec(`SELECT status FROM effect_commit_ledger WHERE idempotency_key = ?`, [idempotencyKey]);
74
+ if (currentResult.length === 0 ||
75
+ currentResult[0].values.length === 0) {
76
+ return { ok: false };
77
+ }
78
+ const previousState = currentResult[0].values[0][0];
79
+ const committedAt = new Date().toISOString();
80
+ sqlite.run(`UPDATE effect_commit_ledger
81
+ SET status = ?, outcome_ref = ?, committed_at = ?
82
+ WHERE idempotency_key = ?`, ["committed", outcomeRef, committedAt, idempotencyKey]);
83
+ return { ok: true, previousState };
84
+ },
85
+ };
86
+ }
@@ -118,6 +118,11 @@ export function classifyFailure(error) {
118
118
  class: "permanent_input_error",
119
119
  retryable: RETRYABLE_BY_CLASS.permanent_input_error,
120
120
  };
121
+ if (code === "unknown_platform" || code === "unknown_platform_change")
122
+ return {
123
+ class: "unknown_platform_change",
124
+ retryable: RETRYABLE_BY_CLASS.unknown_platform_change,
125
+ };
121
126
  }
122
127
  const status = record.status;
123
128
  if (status === 429) {
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Manifest v7 Schema + CapabilityContractRegistry v7 Extension — T-CS.C.1
3
+ *
4
+ * Core logic: Extends v6 manifest with v7 fields:
5
+ * - `probeConfig` (safeEndpoint, idempotencyClass)
6
+ * - `endpointMappings` (profilePath, claimPath, heartbeatPath)
7
+ * - `capabilityId` per capability (DR-001 fix)
8
+ *
9
+ * Zod strict validation with detailed error messages on registration failure.
10
+ *
11
+ * Dependencies:
12
+ * - Zod for schema validation
13
+ * - v6 `CapabilityContractRegistry` from `./manifest.js` as baseline
14
+ *
15
+ * Boundary:
16
+ * - `ConnectorManifestV7` is a superset of v6 manifest fields.
17
+ * - `CapabilityContractRegistryV7` wraps/extends the base registry with
18
+ * v7-specific lookups (`resolveCapabilityV7`, `getProbeConfig`,
19
+ * `getEndpointMapping`).
20
+ * - Registration failures return `{ ok: false; errors: string[] }` instead of
21
+ * throwing.
22
+ *
23
+ * Test coverage: tests/unit/connectors/manifest-v7-schema.test.ts
24
+ */
25
+ import { z } from "zod";
26
+ export declare const IdempotencyClassSchema: z.ZodEnum<{
27
+ strict: "strict";
28
+ read_only: "read_only";
29
+ idempotent_write: "idempotent_write";
30
+ }>;
31
+ export type IdempotencyClass = z.infer<typeof IdempotencyClassSchema>;
32
+ export declare const ProbeConfigSchema: z.ZodObject<{
33
+ safeEndpoint: z.ZodString;
34
+ idempotencyClass: z.ZodEnum<{
35
+ strict: "strict";
36
+ read_only: "read_only";
37
+ idempotent_write: "idempotent_write";
38
+ }>;
39
+ }, z.core.$strip>;
40
+ export type ProbeConfig = z.infer<typeof ProbeConfigSchema>;
41
+ export declare const EndpointMappingsSchema: z.ZodObject<{
42
+ profilePath: z.ZodOptional<z.ZodString>;
43
+ claimPath: z.ZodOptional<z.ZodString>;
44
+ heartbeatPath: z.ZodOptional<z.ZodString>;
45
+ }, z.core.$strip>;
46
+ export type EndpointMappings = z.infer<typeof EndpointMappingsSchema>;
47
+ export declare const V7CapabilitySchema: z.ZodObject<{
48
+ capabilityId: z.ZodString;
49
+ intent: z.ZodString;
50
+ description: z.ZodOptional<z.ZodString>;
51
+ probeConfig: z.ZodOptional<z.ZodObject<{
52
+ safeEndpoint: z.ZodString;
53
+ idempotencyClass: z.ZodEnum<{
54
+ strict: "strict";
55
+ read_only: "read_only";
56
+ idempotent_write: "idempotent_write";
57
+ }>;
58
+ }, z.core.$strip>>;
59
+ endpointMappings: z.ZodOptional<z.ZodObject<{
60
+ profilePath: z.ZodOptional<z.ZodString>;
61
+ claimPath: z.ZodOptional<z.ZodString>;
62
+ heartbeatPath: z.ZodOptional<z.ZodString>;
63
+ }, z.core.$strip>>;
64
+ }, z.core.$strip>;
65
+ export type V7Capability = z.infer<typeof V7CapabilitySchema>;
66
+ export declare const ConnectorManifestV7Schema: z.ZodObject<{
67
+ platformId: z.ZodString;
68
+ capabilities: z.ZodArray<z.ZodObject<{
69
+ capabilityId: z.ZodString;
70
+ intent: z.ZodString;
71
+ description: z.ZodOptional<z.ZodString>;
72
+ probeConfig: z.ZodOptional<z.ZodObject<{
73
+ safeEndpoint: z.ZodString;
74
+ idempotencyClass: z.ZodEnum<{
75
+ strict: "strict";
76
+ read_only: "read_only";
77
+ idempotent_write: "idempotent_write";
78
+ }>;
79
+ }, z.core.$strip>>;
80
+ endpointMappings: z.ZodOptional<z.ZodObject<{
81
+ profilePath: z.ZodOptional<z.ZodString>;
82
+ claimPath: z.ZodOptional<z.ZodString>;
83
+ heartbeatPath: z.ZodOptional<z.ZodString>;
84
+ }, z.core.$strip>>;
85
+ }, z.core.$strip>>;
86
+ channelPriority: z.ZodArray<z.ZodString>;
87
+ credentialTypes: z.ZodArray<z.ZodString>;
88
+ probeConfig: z.ZodOptional<z.ZodObject<{
89
+ safeEndpoint: z.ZodString;
90
+ idempotencyClass: z.ZodEnum<{
91
+ strict: "strict";
92
+ read_only: "read_only";
93
+ idempotent_write: "idempotent_write";
94
+ }>;
95
+ }, z.core.$strip>>;
96
+ endpointMappings: z.ZodOptional<z.ZodObject<{
97
+ profilePath: z.ZodOptional<z.ZodString>;
98
+ claimPath: z.ZodOptional<z.ZodString>;
99
+ heartbeatPath: z.ZodOptional<z.ZodString>;
100
+ }, z.core.$strip>>;
101
+ degradedChannels: z.ZodOptional<z.ZodArray<z.ZodString>>;
102
+ sourceRefPolicy: z.ZodOptional<z.ZodObject<{
103
+ minSourceRefs: z.ZodOptional<z.ZodNumber>;
104
+ rejectInlineSensitivePayload: z.ZodOptional<z.ZodBoolean>;
105
+ }, z.core.$strip>>;
106
+ }, z.core.$strip>;
107
+ export type ConnectorManifestV7 = z.infer<typeof ConnectorManifestV7Schema>;
108
+ export interface ManifestValidationResult {
109
+ ok: boolean;
110
+ errors: string[];
111
+ manifest?: ConnectorManifestV7;
112
+ }
113
+ /**
114
+ * Parse and validate a v7 manifest object.
115
+ * Returns detailed errors rather than throwing.
116
+ */
117
+ export declare function validateManifestV7(input: unknown): ManifestValidationResult;
118
+ export interface ResolvedV7Capability {
119
+ platformId: string;
120
+ capabilityId: string;
121
+ intent: string;
122
+ probeConfig?: ProbeConfig;
123
+ endpointMappings?: EndpointMappings;
124
+ }
125
+ export declare class CapabilityContractRegistryV7 {
126
+ private readonly byPlatform;
127
+ /**
128
+ * Register a v7 manifest.
129
+ * Returns `{ ok: false, errors }` on validation failure;
130
+ * never throws for validation errors.
131
+ */
132
+ register(manifest: unknown): ManifestValidationResult;
133
+ loadManifest(platformId: string): ConnectorManifestV7 | undefined;
134
+ listRegisteredPlatformIds(): string[];
135
+ /**
136
+ * Resolve a capability by its `capabilityId` (namespace optional).
137
+ * Supports `platformId:capabilityId` qualified form.
138
+ */
139
+ resolveCapability(capabilityIdOrQualified: string): ResolvedV7Capability | undefined;
140
+ private _resolveOnPlatform;
141
+ getProbeConfig(platformId: string, capabilityId: string): ProbeConfig | undefined;
142
+ getEndpointMappings(platformId: string, capabilityId: string): EndpointMappings | undefined;
143
+ hasCapability(platformId: string, capabilityId: string): boolean;
144
+ listCapabilities(platformId: string): Array<{
145
+ capabilityId: string;
146
+ intent: string;
147
+ hasProbeConfig: boolean;
148
+ hasEndpointMappings: boolean;
149
+ }>;
150
+ }
151
+ export { ConnectorManifestV7Schema as ManifestV7Schema };
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Manifest v7 Schema + CapabilityContractRegistry v7 Extension — T-CS.C.1
3
+ *
4
+ * Core logic: Extends v6 manifest with v7 fields:
5
+ * - `probeConfig` (safeEndpoint, idempotencyClass)
6
+ * - `endpointMappings` (profilePath, claimPath, heartbeatPath)
7
+ * - `capabilityId` per capability (DR-001 fix)
8
+ *
9
+ * Zod strict validation with detailed error messages on registration failure.
10
+ *
11
+ * Dependencies:
12
+ * - Zod for schema validation
13
+ * - v6 `CapabilityContractRegistry` from `./manifest.js` as baseline
14
+ *
15
+ * Boundary:
16
+ * - `ConnectorManifestV7` is a superset of v6 manifest fields.
17
+ * - `CapabilityContractRegistryV7` wraps/extends the base registry with
18
+ * v7-specific lookups (`resolveCapabilityV7`, `getProbeConfig`,
19
+ * `getEndpointMapping`).
20
+ * - Registration failures return `{ ok: false; errors: string[] }` instead of
21
+ * throwing.
22
+ *
23
+ * Test coverage: tests/unit/connectors/manifest-v7-schema.test.ts
24
+ */
25
+ import { z } from "zod";
26
+ // ─── V7 Capability Schema ───────────────────────────────────────────────────
27
+ export const IdempotencyClassSchema = z.enum([
28
+ "read_only",
29
+ "idempotent_write",
30
+ "strict",
31
+ ]);
32
+ export const ProbeConfigSchema = z.object({
33
+ safeEndpoint: z.string().min(1),
34
+ idempotencyClass: IdempotencyClassSchema,
35
+ });
36
+ export const EndpointMappingsSchema = z.object({
37
+ profilePath: z.string().min(1).optional(),
38
+ claimPath: z.string().min(1).optional(),
39
+ heartbeatPath: z.string().min(1).optional(),
40
+ });
41
+ export const V7CapabilitySchema = z.object({
42
+ capabilityId: z.string().min(1),
43
+ intent: z.string().min(1).regex(/^[a-zA-Z0-9_.:-]+$/),
44
+ description: z.string().optional(),
45
+ probeConfig: ProbeConfigSchema.optional(),
46
+ endpointMappings: EndpointMappingsSchema.optional(),
47
+ });
48
+ // ─── V7 Manifest Schema ─────────────────────────────────────────────────────
49
+ export const ConnectorManifestV7Schema = z.object({
50
+ platformId: z.string().min(1),
51
+ capabilities: z.array(V7CapabilitySchema).min(1),
52
+ channelPriority: z.array(z.string().min(1)).min(1),
53
+ credentialTypes: z.array(z.string().min(1)).min(1),
54
+ probeConfig: ProbeConfigSchema.optional(),
55
+ endpointMappings: EndpointMappingsSchema.optional(),
56
+ degradedChannels: z.array(z.string().min(1)).optional(),
57
+ sourceRefPolicy: z
58
+ .object({
59
+ minSourceRefs: z.number().int().min(0).optional(),
60
+ rejectInlineSensitivePayload: z.boolean().optional(),
61
+ })
62
+ .optional(),
63
+ });
64
+ /**
65
+ * Parse and validate a v7 manifest object.
66
+ * Returns detailed errors rather than throwing.
67
+ */
68
+ export function validateManifestV7(input) {
69
+ const parsed = ConnectorManifestV7Schema.safeParse(input);
70
+ if (!parsed.success) {
71
+ const errors = parsed.error.issues.map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`);
72
+ return { ok: false, errors };
73
+ }
74
+ return { ok: true, errors: [], manifest: parsed.data };
75
+ }
76
+ export class CapabilityContractRegistryV7 {
77
+ byPlatform = new Map();
78
+ /**
79
+ * Register a v7 manifest.
80
+ * Returns `{ ok: false, errors }` on validation failure;
81
+ * never throws for validation errors.
82
+ */
83
+ register(manifest) {
84
+ const validation = validateManifestV7(manifest);
85
+ if (!validation.ok) {
86
+ return validation;
87
+ }
88
+ const parsed = validation.manifest;
89
+ // DR-001: capabilityId must be present on every capability
90
+ const missingIds = [];
91
+ for (const cap of parsed.capabilities) {
92
+ if (!cap.capabilityId || cap.capabilityId.trim().length === 0) {
93
+ missingIds.push(cap.intent);
94
+ }
95
+ }
96
+ if (missingIds.length > 0) {
97
+ return {
98
+ ok: false,
99
+ errors: missingIds.map((intent) => `capabilities: capabilityId missing for intent "${intent}"`),
100
+ };
101
+ }
102
+ this.byPlatform.set(parsed.platformId, parsed);
103
+ return { ok: true, errors: [] };
104
+ }
105
+ loadManifest(platformId) {
106
+ return this.byPlatform.get(platformId);
107
+ }
108
+ listRegisteredPlatformIds() {
109
+ return [...this.byPlatform.keys()];
110
+ }
111
+ /**
112
+ * Resolve a capability by its `capabilityId` (namespace optional).
113
+ * Supports `platformId:capabilityId` qualified form.
114
+ */
115
+ resolveCapability(capabilityIdOrQualified) {
116
+ const colonIndex = capabilityIdOrQualified.indexOf(":");
117
+ if (colonIndex >= 0) {
118
+ const platformId = capabilityIdOrQualified.slice(0, colonIndex);
119
+ const capabilityId = capabilityIdOrQualified.slice(colonIndex + 1);
120
+ return this._resolveOnPlatform(platformId, capabilityId);
121
+ }
122
+ // Search all platforms for first matching capabilityId
123
+ for (const platformId of this.byPlatform.keys()) {
124
+ const found = this._resolveOnPlatform(platformId, capabilityIdOrQualified);
125
+ if (found)
126
+ return found;
127
+ }
128
+ return undefined;
129
+ }
130
+ _resolveOnPlatform(platformId, capabilityIdOrIntent) {
131
+ const manifest = this.byPlatform.get(platformId);
132
+ if (!manifest)
133
+ return undefined;
134
+ // Match by capabilityId (exact) or intent (fallback)
135
+ const cap = manifest.capabilities.find((c) => c.capabilityId === capabilityIdOrIntent) ??
136
+ manifest.capabilities.find((c) => c.intent === capabilityIdOrIntent);
137
+ if (!cap)
138
+ return undefined;
139
+ return {
140
+ platformId,
141
+ capabilityId: cap.capabilityId,
142
+ intent: cap.intent,
143
+ probeConfig: cap.probeConfig ?? manifest.probeConfig,
144
+ endpointMappings: cap.endpointMappings ?? manifest.endpointMappings,
145
+ };
146
+ }
147
+ getProbeConfig(platformId, capabilityId) {
148
+ const resolved = this._resolveOnPlatform(platformId, capabilityId);
149
+ return resolved?.probeConfig;
150
+ }
151
+ getEndpointMappings(platformId, capabilityId) {
152
+ const resolved = this._resolveOnPlatform(platformId, capabilityId);
153
+ return resolved?.endpointMappings;
154
+ }
155
+ hasCapability(platformId, capabilityId) {
156
+ return this._resolveOnPlatform(platformId, capabilityId) !== undefined;
157
+ }
158
+ listCapabilities(platformId) {
159
+ const manifest = this.byPlatform.get(platformId);
160
+ if (!manifest)
161
+ return [];
162
+ return manifest.capabilities.map((cap) => ({
163
+ capabilityId: cap.capabilityId,
164
+ intent: cap.intent,
165
+ hasProbeConfig: !!cap.probeConfig || !!manifest.probeConfig,
166
+ hasEndpointMappings: !!cap.endpointMappings || !!manifest.endpointMappings,
167
+ }));
168
+ }
169
+ }
170
+ export { ConnectorManifestV7Schema as ManifestV7Schema };