@godspeedai/cognate-realitytrace 0.0.0-stage → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md CHANGED
@@ -1,3 +1,42 @@
1
- # Temporary Holding Version
1
+ # @godspeedai/cognate-realitytrace
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Optional observation adapter (spec §20): committed-outbox delivery of
4
+ Framework observations to RealityTrace (SXR), plus the SXR CLI port.
5
+
6
+ ## Normal vs strict mode
7
+
8
+ **Normal mode** (default): `observationIntent` is committed in the same
9
+ decision as the event; `ObservationDelivery` drains the outbox at-least-once
10
+ in the background. A slow or unreachable SXR never blocks or rolls back
11
+ committed history — failures retry with backoff, then poison for operator
12
+ reconciliation (`unreconciled()`).
13
+
14
+ **Strict mode**: callers gate a specific operation on `isRecorded(eventId)`,
15
+ which is `true` only after SXR has `accepted` or already had (`duplicate`)
16
+ the observation. `queued`/`retrying` never satisfies the gate.
17
+
18
+ ## What a receipt means — and does not mean
19
+
20
+ A `DeliveryReceipt` (`accepted`/`duplicate`) means SXR's ledger recorded (or
21
+ had already recorded) the observation bytes. It is **not** evidence and
22
+ **not** settlement — those are separate, later, explicit SXR commands
23
+ (`evidence create`, `settlement register`) outside this adapter's scope.
24
+
25
+ ## Per-observation verifier design
26
+
27
+ `sxr ingest` dedupes by `(run_id, verifier)`, not by any caller-supplied id,
28
+ and mints a fresh `observation_id` on every call — even duplicates. This
29
+ port uses a unique verifier per observation (`cognate.<observationId>`) so
30
+ SXR's own idempotency key becomes, in effect, per-observation: retries land
31
+ on the same `ledger_position` and report `already_committed`.
32
+
33
+ ## Structured correlation (RealityTrace ≥ 0.3.0)
34
+
35
+ `record()` passes Cognate's correlation fields to `ingest --extensions`, so
36
+ the committed record carries them as CEP `extensions.cognate`.
37
+ `readCorrelation(targetRef)` reads them back from that record via
38
+ `ledger export`, returning `null` when no record sits at that position. A
39
+ duplicate delivery keeps the first record's ids. Cognate still keeps the
40
+ authoritative event → observation → receipt mapping in its `observations`
41
+ store, keyed by `eventId`, and never treats a duplicate response's newly
42
+ minted SXR `observation_id` as the committed id.
package/package.json CHANGED
@@ -1,6 +1,32 @@
1
1
  {
2
2
  "name": "@godspeedai/cognate-realitytrace",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Observation port, committed-outbox delivery, receipts, strict gating, and the SXR CLI adapter (spec §20). RealityTrace stays unchanged.",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/GodSpeedAI/Cognate.git",
10
+ "directory": "packages/realitytrace"
11
+ },
12
+ "engines": {
13
+ "bun": ">=1.4.0"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "files": [
19
+ "src"
20
+ ],
21
+ "exports": {
22
+ ".": "./src/index.ts"
23
+ },
24
+ "dependencies": {
25
+ "@godspeedai/cognate-events": "^0.1.1",
26
+ "@godspeedai/cognate-kernel-api": "^0.1.1"
27
+ },
28
+ "devDependencies": {
29
+ "@godspeedai/cognate-store-memory": "workspace:*",
30
+ "@godspeedai/cognate-store-sqlite": "workspace:*"
31
+ }
32
+ }
@@ -0,0 +1,150 @@
1
+ // GodSpeed CEP profile envelopes for the RealityTrace boundary (migration Stage 8).
2
+ //
3
+ // Roles are fixed: Cognate operates and reports completion (execution_trace); RealityTrace observes and derives
4
+ // its own evidence; SEA-Forge settles. Nothing here upgrades one into the other: a trace says an operation
5
+ // completed, evidence keeps exactly the direction and reliability RealityTrace recorded, and settlement is not
6
+ // built here at all.
7
+ import { createHash } from "node:crypto";
8
+
9
+ type Obj = Record<string, unknown>;
10
+
11
+ const PROFILE_VERSION = "1.0.0";
12
+ const BASE = { cep_version: "1.0.0", envelope_version: "1.0", completeness_status: "partial", omission_status: "marked", validation_status: "valid" } as const;
13
+
14
+ const isoSeconds = (date: Date): string => date.toISOString().replace(/\.\d{3}Z$/, "Z");
15
+
16
+ function boundary(scope: string, included: string[], omissions: string[], limitations: string[] = []): Obj {
17
+ return { scope, included_sections: included, excluded_sections: [], known_omissions: omissions, unknowns: [], redactions: [], compression_notes: [], out_of_scope_entities: [], classification: "internal", limitations };
18
+ }
19
+
20
+ function provenance(id: string, system: string, producer: string, method: string, at: string): Obj {
21
+ return { provenance_id: id, source_system_refs: [system], producer_refs: [producer], production_method: method, created_at: at };
22
+ }
23
+
24
+ export const executionTraceId = (operationId: string): string => `env-execution_trace-${operationId}`;
25
+ export const transformationId = (operationId: string): string => `tr-${operationId}`;
26
+
27
+ export interface TraceInput {
28
+ readonly worldRef: string;
29
+ /** The authority decision that allowed the operation (from the recorded decision metadata). */
30
+ readonly authority: { readonly operationId: string; readonly decisionEnvelopeId: string };
31
+ readonly correlationId: string;
32
+ readonly capability: string;
33
+ readonly ok: boolean;
34
+ readonly failureKind?: string;
35
+ readonly observationId: string;
36
+ readonly occurredAt: string;
37
+ readonly createdAt?: Date;
38
+ }
39
+
40
+ /** A GodSpeed `execution_trace`: what was done under an authority decision. Completion only, never settlement. */
41
+ export function executionTraceEnvelope(input: TraceInput): Obj {
42
+ const op = input.authority.operationId;
43
+ const at = isoSeconds(input.createdAt ?? new Date());
44
+ const occurred = isoSeconds(new Date(input.occurredAt));
45
+ return {
46
+ envelope_id: executionTraceId(op),
47
+ ...BASE,
48
+ envelope_kind: "execution_trace",
49
+ created_at: at,
50
+ created_by: "cognate",
51
+ scope: { evaluation_context: "godspeed-execution_trace", world_ref: input.worldRef },
52
+ boundary_record: boundary("one governed Cognate capability invocation", ["observations", "transformations"], ["outputs are not carried"], ["records that the operation completed or failed; it is not evidence and not settlement"]),
53
+ provenance_refs: [`prov-${executionTraceId(op)}`],
54
+ lineage_refs: [input.authority.decisionEnvelopeId],
55
+ provenance: [provenance(`prov-${executionTraceId(op)}`, "cognate", "cognate", "governed_invocation", at)],
56
+ transformations: [{ transformation_id: transformationId(op), transformation_type: "capability_invocation" }],
57
+ observations: [{ observation_id: input.observationId, observed_entity_ref: input.capability, observer_ref: "cognate", method: "governed_invocation", timestamp: occurred, limitations: ["completion only"] }],
58
+ extensions: {
59
+ "cep.profile": { profile_id: "godspeed.execution_trace", profile_version: PROFILE_VERSION },
60
+ "godspeed.execution_trace": { operation_id: op, authority_decision_ref: input.authority.decisionEnvelopeId, correlation_id: input.correlationId, outcome: input.ok ? "completed" : "failed", ...(input.failureKind ? { failure_kind: input.failureKind } : {}) },
61
+ },
62
+ };
63
+ }
64
+
65
+ /** What RealityTrace recorded for one evidence item, with where it sits in RealityTrace's ledger. */
66
+ export interface SxrEvidenceRow {
67
+ readonly evidence: Obj;
68
+ readonly position: { readonly ledger_id: string; readonly append_ordinal: number };
69
+ }
70
+
71
+ export interface EvidencePacketInput {
72
+ readonly worldRef: string;
73
+ readonly authority: { readonly operationId: string; readonly decisionEnvelopeId: string };
74
+ /** RealityTrace's recorded question (`questions[0]` of its question.declared record). */
75
+ readonly question: Obj;
76
+ readonly rows: readonly SxrEvidenceRow[];
77
+ readonly createdAt?: Date;
78
+ }
79
+
80
+ const ITEM_KEYS = ["evidence_id", "evidence_type", "question_ref", "target_claim_ref", "target_entity_ref", "representation_ref", "relevance", "reliability", "direction", "confidence_impact", "limitations"] as const;
81
+ const QUESTION_KEYS = ["question_id", "question_form", "question_type", "scope", "target_entities", "target_states", "target_transformations", "expected_answer_shape", "evaluation_context", "assumptions", "unknowns"] as const;
82
+
83
+ function pick(source: Obj, keys: readonly string[]): Obj {
84
+ const out: Obj = {};
85
+ for (const key of keys) if (source[key] !== undefined) out[key] = source[key];
86
+ return out;
87
+ }
88
+
89
+ /**
90
+ * A GodSpeed `evidence_packet` from RealityTrace's recorded evidence. Each item is copied as recorded: direction
91
+ * and reliability are never upgraded, and an item without a direction stays `unknown`.
92
+ */
93
+ export function evidencePacketEnvelope(input: EvidencePacketInput): Obj {
94
+ if (input.rows.length === 0) throw new Error("an evidence packet needs at least one recorded evidence item");
95
+ const op = input.authority.operationId;
96
+ const at = isoSeconds(input.createdAt ?? new Date());
97
+ const ids = input.rows.map((r) => String(r.evidence.evidence_id)).sort();
98
+ const envelopeId = `env-evidence_packet-${op}-${createHash("sha256").update(ids.join("|")).digest("hex").slice(0, 12)}`;
99
+ const items = input.rows.map((row) => ({
100
+ ...pick(row.evidence, ITEM_KEYS),
101
+ direction: typeof row.evidence.direction === "string" ? row.evidence.direction : "unknown",
102
+ source: "realitytrace",
103
+ provenance_refs: [`sxr:${row.position.ledger_id}:${row.position.append_ordinal}`],
104
+ }));
105
+ return {
106
+ envelope_id: envelopeId,
107
+ ...BASE,
108
+ envelope_kind: "evidence_packet",
109
+ created_at: at,
110
+ created_by: "cognate-realitytrace-adapter",
111
+ scope: { evaluation_context: "godspeed-evidence_packet", world_ref: input.worldRef },
112
+ boundary_record: boundary("evidence RealityTrace recorded for one operation", ["evidence", "questions"], ["only evidence recorded in RealityTrace's ledger is carried"], ["evidence is relative to the question; it is not settlement"]),
113
+ provenance_refs: [`prov-${envelopeId}`],
114
+ lineage_refs: [executionTraceId(op)],
115
+ provenance: [provenance(`prov-${envelopeId}`, "realitytrace", "cognate-realitytrace-adapter", "evidence_packaging", at)],
116
+ questions: [pick(input.question, QUESTION_KEYS)],
117
+ evidence: items,
118
+ extensions: {
119
+ "cep.profile": { profile_id: "godspeed.evidence_packet", profile_version: PROFILE_VERSION },
120
+ "godspeed.evidence_packet": { operation_id: op, authority_decision_ref: input.authority.decisionEnvelopeId, execution_trace_ref: executionTraceId(op) },
121
+ },
122
+ };
123
+ }
124
+
125
+ /**
126
+ * The execution_trace for a committed `run.invocation` event, or undefined when the invocation was not governed
127
+ * by a SEA-Forge decision (no authority reference was recorded, so there is nothing for the trace to hang from).
128
+ * Reads only what the runtime recorded; it never asks SEA-Forge anything.
129
+ */
130
+ export function governedTraceFor(event: {
131
+ readonly eventId: string;
132
+ readonly correlationId: string;
133
+ readonly occurredAt: string;
134
+ readonly metadata?: { readonly [key: string]: unknown };
135
+ }): Obj | undefined {
136
+ const metadata = event.metadata ?? {};
137
+ const decision = metadata.decision as { authority?: { operationId?: string; decisionEnvelopeId?: string; worldRef?: string } } | null | undefined;
138
+ const authority = decision?.authority;
139
+ if (!authority?.operationId || !authority.decisionEnvelopeId || !authority.worldRef) return undefined;
140
+ return executionTraceEnvelope({
141
+ worldRef: authority.worldRef,
142
+ authority: { operationId: authority.operationId, decisionEnvelopeId: authority.decisionEnvelopeId },
143
+ correlationId: event.correlationId,
144
+ capability: typeof metadata.capability === "string" ? metadata.capability : "unknown",
145
+ ok: metadata.ok === true,
146
+ ...(typeof metadata.failureKind === "string" ? { failureKind: metadata.failureKind } : {}),
147
+ observationId: `obs-${event.eventId}`,
148
+ occurredAt: event.occurredAt,
149
+ });
150
+ }
package/src/index.ts ADDED
@@ -0,0 +1,141 @@
1
+ export type * from "./types.ts";
2
+ export * from "./godspeed.ts";
3
+ import type { DurableStore, Json, OutboxIntent, StoredEvent } from "@godspeedai/cognate-events";
4
+ import { OutboxDispatcher } from "@godspeedai/cognate-events";
5
+ import type { Observation, ObservationPort, ObservationStatus } from "./types.ts";
6
+
7
+ export const OBSERVATION_TOPIC = "observation.deliver";
8
+
9
+ const OBSERVATIONS_COLLECTION = "observations";
10
+
11
+ /** Build the observation for a committed event and the outbox intent to include in the SAME decision (spec §13.1). */
12
+ export function observationIntent(
13
+ event: Pick<StoredEvent, "eventId" | "tenant" | "correlationId" | "causationId" | "runId" | "scopeId" | "componentId" | "fiberId" | "semanticRef" | "recordedAt">,
14
+ extra: Pick<Observation, "classification"> & Partial<Pick<Observation, "capabilityId" | "continuationId" | "inputRef" | "outputRef" | "trace">>,
15
+ ): OutboxIntent {
16
+ const observation: Observation = {
17
+ observationId: `obs-${event.eventId}`,
18
+ eventId: event.eventId,
19
+ tenant: event.tenant,
20
+ correlationId: event.correlationId,
21
+ causationId: event.causationId,
22
+ runId: event.runId,
23
+ scopeId: event.scopeId,
24
+ ...(event.componentId !== undefined ? { componentId: event.componentId } : {}),
25
+ ...(event.fiberId !== undefined ? { fiberId: event.fiberId } : {}),
26
+ ...(extra.capabilityId !== undefined ? { capabilityId: extra.capabilityId } : {}),
27
+ ...(extra.continuationId !== undefined ? { continuationId: extra.continuationId } : {}),
28
+ ...(event.semanticRef !== undefined ? { semanticRef: event.semanticRef } : {}),
29
+ classification: extra.classification,
30
+ ...(extra.inputRef !== undefined ? { inputRef: extra.inputRef } : {}),
31
+ ...(extra.outputRef !== undefined ? { outputRef: extra.outputRef } : {}),
32
+ ...(extra.trace !== undefined ? { trace: extra.trace } : {}),
33
+ timestamp: event.recordedAt,
34
+ };
35
+ return {
36
+ id: `observation:${event.eventId}`,
37
+ topic: OBSERVATION_TOPIC,
38
+ payload: observation as unknown as Json,
39
+ };
40
+ }
41
+
42
+ function eventIdFromOutboxId(id: string): string {
43
+ return id.startsWith("observation:") ? id.slice("observation:".length) : id;
44
+ }
45
+
46
+ export interface DeliveryOptions {
47
+ readonly port: ObservationPort;
48
+ readonly backoff: { readonly baseMs: number; readonly maxMs: number; readonly maxAttempts: number };
49
+ readonly now?: () => number;
50
+ }
51
+
52
+ /** Normal mode: at-least-once delivery from the committed outbox with persisted receipts and reconciliation status. */
53
+ export class ObservationDelivery {
54
+ private readonly dispatcher: OutboxDispatcher;
55
+
56
+ constructor(
57
+ private readonly store: DurableStore,
58
+ private readonly options: DeliveryOptions,
59
+ ) {
60
+ this.dispatcher = new OutboxDispatcher(store, {
61
+ topic: OBSERVATION_TOPIC,
62
+ backoff: options.backoff,
63
+ now: options.now,
64
+ deliver: async (item) => {
65
+ const eventId = eventIdFromOutboxId(item.id);
66
+ const observation = item.payload as unknown as Observation;
67
+ try {
68
+ const receipt = await options.port.record(observation);
69
+ await this.writeStatus({
70
+ eventId,
71
+ observationId: observation.observationId,
72
+ state: receipt.status,
73
+ attempts: item.attempts,
74
+ targetRef: receipt.targetRef,
75
+ lastError: null,
76
+ });
77
+ return receipt.detail;
78
+ } catch (error) {
79
+ const message = error instanceof Error ? error.message : String(error);
80
+ const poisoning = item.attempts >= options.backoff.maxAttempts;
81
+ await this.writeStatus({
82
+ eventId,
83
+ observationId: observation.observationId,
84
+ state: poisoning ? "failed" : "retrying",
85
+ attempts: item.attempts,
86
+ targetRef: null,
87
+ lastError: message,
88
+ });
89
+ throw error;
90
+ }
91
+ },
92
+ });
93
+ }
94
+
95
+ private async writeStatus(value: ObservationStatus): Promise<void> {
96
+ const existing = await this.store.getRecord(OBSERVATIONS_COLLECTION, value.eventId);
97
+ await this.store.commit({
98
+ appends: [],
99
+ records: [
100
+ {
101
+ collection: OBSERVATIONS_COLLECTION,
102
+ id: value.eventId,
103
+ expectedVersion: existing?.version ?? 0,
104
+ value: value as unknown as { readonly [key: string]: Json },
105
+ },
106
+ ],
107
+ });
108
+ }
109
+
110
+ /** Deliver available observations once; failures never touch committed framework events. */
111
+ async drain(): Promise<{ readonly delivered: readonly string[]; readonly retried: readonly string[]; readonly failed: readonly string[] }> {
112
+ const report = await this.dispatcher.drain();
113
+ return {
114
+ delivered: report.delivered.map(eventIdFromOutboxId),
115
+ retried: report.retried.map((r) => eventIdFromOutboxId(r.id)),
116
+ failed: report.poisoned.map(eventIdFromOutboxId),
117
+ };
118
+ }
119
+
120
+ async status(eventId: string): Promise<ObservationStatus | undefined> {
121
+ const record = await this.store.getRecord(OBSERVATIONS_COLLECTION, eventId);
122
+ return record ? (record.value as unknown as ObservationStatus) : undefined;
123
+ }
124
+
125
+ /** Observations needing operator attention (poisoned deliveries). */
126
+ async unreconciled(): Promise<ObservationStatus[]> {
127
+ const records = await this.store.listRecords(OBSERVATIONS_COLLECTION, { state: "failed" });
128
+ return records.map((r) => r.value as unknown as ObservationStatus);
129
+ }
130
+
131
+ /**
132
+ * Strict-mode gate for a specific operation (spec §20): true only if the target accepted (or already had) the
133
+ * observation. Queued or retrying is not enough, and acceptance is still not evidence.
134
+ */
135
+ async isRecorded(eventId: string): Promise<boolean> {
136
+ const status = await this.status(eventId);
137
+ return status?.state === "accepted" || status?.state === "duplicate";
138
+ }
139
+ }
140
+
141
+ export { createSxrCliPort, type SxrCliOptions, type SxrWorkflow } from "./sxr-cli.ts";
package/src/sxr-cli.ts ADDED
@@ -0,0 +1,186 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import type { Json } from "@godspeedai/cognate-events";
5
+ import type { SxrEvidenceRow } from "./godspeed.ts";
6
+ import type { DeliveryReceipt, Observation, ObservationPort } from "./types.ts";
7
+
8
+ export interface SxrCliOptions {
9
+ /** Path to the pinned `sxr` binary (DEBT-010). */
10
+ readonly bin: string;
11
+ /** Working directory holding the enrolled repository and SXR state. */
12
+ readonly repoDir: string;
13
+ /** Extra environment (e.g. SXR home) for isolation. */
14
+ readonly env?: Record<string, string>;
15
+ }
16
+
17
+ /** Identifiers produced by the SXR prerequisite workflow (enroll → question → threshold → run). */
18
+ export interface SxrWorkflow {
19
+ readonly repoId: string;
20
+ readonly questionId: string;
21
+ readonly thresholdId: string;
22
+ readonly runId: string;
23
+ readonly target: string;
24
+ }
25
+
26
+ /** The `--target` used for the declared question, threshold's plans, and every per-observation `plan create`. */
27
+ const TARGET = "concept:runtime_execution";
28
+
29
+ interface SxrResult {
30
+ readonly exitCode: number;
31
+ readonly stdout: string;
32
+ readonly stderr: string;
33
+ readonly json: any;
34
+ }
35
+
36
+ async function runSxr(bin: string, env: Record<string, string | undefined>, cwd: string, args: string[], stdin?: string): Promise<SxrResult> {
37
+ const proc = Bun.spawn([bin, ...args], {
38
+ cwd,
39
+ env,
40
+ stdin: stdin === undefined ? undefined : "pipe",
41
+ stdout: "pipe",
42
+ stderr: "pipe",
43
+ });
44
+ if (stdin !== undefined && proc.stdin && typeof proc.stdin !== "number") {
45
+ proc.stdin.write(stdin);
46
+ proc.stdin.end();
47
+ }
48
+ const [stdout, stderr, exitCode] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]);
49
+ let json: any = undefined;
50
+ try {
51
+ json = JSON.parse(stdout);
52
+ } catch {
53
+ // Not every command emits parseable JSON on failure; callers check stderr too.
54
+ }
55
+ return { exitCode, stdout, stderr, json };
56
+ }
57
+
58
+ function requireOk(result: SxrResult, description: string): any {
59
+ if (result.exitCode !== 0) {
60
+ throw new Error(`realitytrace: sxr ${description} failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
61
+ }
62
+ return result.json;
63
+ }
64
+
65
+ /**
66
+ * ObservationPort over the documented SXR CLI. `prepare()` completes the prerequisite workflow; `record()` creates a
67
+ * plan with verifier `cognate.<observationId>` and ingests the observation JSON on stdin. SXR deduplicates by
68
+ * (run, verifier), so retries of one observation reconcile as `duplicate` with the same ledger position. Cognate's
69
+ * correlation ids travel as the record's CEP `extensions.cognate` (`ingest --extensions`, RealityTrace ≥ 0.3.0) and
70
+ * are read back from the committed record with `readCorrelation(targetRef)`. SXR keeps the first committed record, so a
71
+ * duplicate delivery never rewrites them.
72
+ */
73
+ export function createSxrCliPort(
74
+ options: SxrCliOptions,
75
+ ): ObservationPort & {
76
+ prepare(question: { readonly title: string }): Promise<SxrWorkflow>;
77
+ readCorrelation(targetRef: string): Promise<Json | null>;
78
+ /** Ask RealityTrace to derive evidence from one recorded observation (its own derivation; may be `unknown`). */
79
+ deriveEvidence(sxrObservationId: string, evidenceType?: string): Promise<{ readonly evidenceId: string; readonly targetRef: string }>;
80
+ /** RealityTrace's recorded question and evidence for the prepared run, as recorded. */
81
+ readEvidence(): Promise<{ readonly question: Record<string, unknown>; readonly rows: readonly SxrEvidenceRow[] }>;
82
+ } {
83
+ const env: Record<string, string | undefined> = { ...process.env, ...options.env };
84
+ let workflow: SxrWorkflow | undefined;
85
+
86
+ const sxr = (args: string[], stdin?: string) => runSxr(options.bin, env, options.repoDir, args, stdin);
87
+
88
+ return {
89
+ id: "sxr-cli",
90
+
91
+ async prepare(question) {
92
+ mkdirSync(options.repoDir, { recursive: true });
93
+ Bun.spawnSync(["git", "init", "-q"], { cwd: options.repoDir, env });
94
+ Bun.spawnSync(["git", "-c", "user.email=realitytrace@cognate.local", "-c", "user.name=realitytrace", "commit", "--allow-empty", "-qm", "realitytrace enrollment"], { cwd: options.repoDir, env });
95
+
96
+ const enroll = requireOk(await sxr(["repo", "enroll", options.repoDir, "--json"]), "repo enroll");
97
+ const repoId: string = enroll.repo_id;
98
+
99
+ const scratch = mkdtempSync(join(tmpdir(), "cognate-sxr-prereq-"));
100
+ try {
101
+ const questionFile = join(scratch, "question.json");
102
+ writeFileSync(questionFile, JSON.stringify({ question_form: "is_true", question_type: "declared_semantics", target_entities: TARGET, title: question.title }));
103
+ const declaredQuestion = requireOk(await sxr(["question", "declare", "--repo", repoId, "--file", questionFile, "--json"]), "question declare");
104
+ const questionId: string = declaredQuestion.question_id;
105
+
106
+ const thresholdFile = join(scratch, "threshold.json");
107
+ writeFileSync(thresholdFile, JSON.stringify({ threshold_id: "cognate-threshold", kind: "AllEvidenceSupports", minimum_reliability: "medium" }));
108
+ const declaredThreshold = requireOk(await sxr(["threshold", "declare", "--repo", repoId, "--question", questionId, "--file", thresholdFile, "--json"]), "threshold declare");
109
+ const thresholdId: string = declaredThreshold.threshold_id;
110
+
111
+ const run = requireOk(await sxr(["run", "begin", "--repo", repoId, "--snapshot", "snapshot:genesis", "--question", questionId, "--threshold", thresholdId, "--json"]), "run begin");
112
+ const runId: string = run.run_id;
113
+
114
+ workflow = { repoId, questionId, thresholdId, runId, target: TARGET };
115
+ return workflow;
116
+ } finally {
117
+ rmSync(scratch, { recursive: true, force: true });
118
+ }
119
+ },
120
+
121
+ async record(observation: Observation): Promise<DeliveryReceipt> {
122
+ if (!workflow) throw new Error("realitytrace: sxr-cli port used before prepare()");
123
+ const verifier = `cognate.${observation.observationId}`;
124
+
125
+ const plan = await sxr(["plan", "create", "--run", workflow.runId, "--verifier", verifier, "--target", workflow.target, "--json"]);
126
+ if (plan.exitCode !== 0 && !/already exists/i.test(plan.stderr)) {
127
+ throw new Error(`realitytrace: sxr plan create failed (exit ${plan.exitCode}): ${plan.stderr || plan.stdout}`);
128
+ }
129
+
130
+ const extensions = JSON.stringify({ cognate: correlationOf(observation) });
131
+ // A governed operation hands RealityTrace its CEP execution_trace to record; otherwise the bare observation.
132
+ const content = observation.trace ?? stripTrace(observation);
133
+ const ingest = await sxr(["ingest", "--run", workflow.runId, "--verifier", verifier, "--stdin-format", "json", "--extensions", extensions, "--json"], JSON.stringify(content));
134
+ if (ingest.exitCode !== 0) {
135
+ throw new Error(`realitytrace: sxr ingest failed (exit ${ingest.exitCode}): ${ingest.stderr || ingest.stdout}`);
136
+ }
137
+
138
+ const receipt = ingest.json;
139
+ const targetRef = `${receipt.ledger_position.ledger_id}:${receipt.ledger_position.append_ordinal}`;
140
+ const status: "accepted" | "duplicate" = receipt.already_committed ? "duplicate" : "accepted";
141
+ return { status, observationId: observation.observationId, targetRef, detail: receipt as Json };
142
+ },
143
+
144
+ async deriveEvidence(sxrObservationId, evidenceType) {
145
+ if (!workflow) throw new Error("realitytrace: sxr-cli port used before prepare()");
146
+ const args = ["evidence", "create", "--run", workflow.runId, "--observation", sxrObservationId, "--derive", ...(evidenceType ? ["--evidence-type", evidenceType] : []), "--json"];
147
+ const created = requireOk(await sxr(args), "evidence create");
148
+ return { evidenceId: String(created.evidence_id), targetRef: `${created.ledger_position.ledger_id}:${created.ledger_position.append_ordinal}` };
149
+ },
150
+
151
+ async readEvidence() {
152
+ if (!workflow) throw new Error("realitytrace: sxr-cli port used before prepare()");
153
+ const rows = requireOk(await sxr(["ledger", "export", "--repo", workflow.repoId]), "ledger export") as { ledger_id: string; append_ordinal: number; record_kind: string; payload: any }[];
154
+ const questionRow = rows.find((r) => r.record_kind === "question.declared" && r.payload.questions?.[0]?.question_id === workflow!.questionId);
155
+ if (!questionRow) throw new Error(`realitytrace: question ${workflow.questionId} is not in the ledger`);
156
+ const evidence: SxrEvidenceRow[] = [];
157
+ for (const row of rows) {
158
+ if (row.record_kind !== "evidence.created") continue;
159
+ for (const item of row.payload.evidence ?? []) evidence.push({ evidence: item, position: { ledger_id: row.ledger_id, append_ordinal: row.append_ordinal } });
160
+ }
161
+ return { question: questionRow.payload.questions[0], rows: evidence };
162
+ },
163
+
164
+ async readCorrelation(targetRef) {
165
+ if (!workflow) throw new Error("realitytrace: sxr-cli port used before prepare()");
166
+ const split = targetRef.lastIndexOf(":");
167
+ const ledgerId = targetRef.slice(0, split);
168
+ const ordinal = Number(targetRef.slice(split + 1));
169
+ const rows = requireOk(await sxr(["ledger", "export", "--repo", workflow.repoId]), "ledger export") as { ledger_id: string; append_ordinal: number; payload: { extensions?: { cognate?: Json } } }[];
170
+ const row = rows.find((r) => r.ledger_id === ledgerId && r.append_ordinal === ordinal);
171
+ return row?.payload.extensions?.cognate ?? null;
172
+ },
173
+ };
174
+ }
175
+
176
+ /** The observation without its trace (the trace is delivered as the content instead, never alongside it). */
177
+ function stripTrace(observation: Observation): Observation {
178
+ const { trace: _trace, ...rest } = observation;
179
+ return rest;
180
+ }
181
+
182
+ /** Cognate's correlation fields for `extensions.cognate`; undefined optional fields are omitted. */
183
+ function correlationOf(observation: Observation): Json {
184
+ const { observationId, eventId, tenant, correlationId, causationId, runId, scopeId, componentId, fiberId, capabilityId, continuationId, semanticRef } = observation;
185
+ return JSON.parse(JSON.stringify({ observationId, eventId, tenant, correlationId, causationId, runId, scopeId, componentId, fiberId, capabilityId, continuationId, semanticRef })) as Json;
186
+ }
package/src/types.ts ADDED
@@ -0,0 +1,52 @@
1
+ import type { Json } from "@godspeedai/cognate-events";
2
+ import type { SemanticRef } from "@godspeedai/cognate-kernel-api";
3
+
4
+ /** A framework execution observation (spec §20). Correlation fields are Cognate's; SXR decides what becomes evidence. */
5
+ export interface Observation {
6
+ /** Stable id: derived from the source event, identical on every retry. */
7
+ readonly observationId: string;
8
+ readonly eventId: string;
9
+ readonly tenant: string;
10
+ readonly correlationId: string;
11
+ readonly causationId: string | null;
12
+ readonly runId: string | null;
13
+ readonly scopeId: string;
14
+ readonly componentId?: string;
15
+ readonly fiberId?: string;
16
+ readonly capabilityId?: string;
17
+ readonly continuationId?: string;
18
+ readonly semanticRef?: SemanticRef;
19
+ readonly classification: "reversible_effect" | "durable_fact" | "external_commitment";
20
+ readonly inputRef?: string;
21
+ readonly outputRef?: string;
22
+ readonly timestamp: string;
23
+ /**
24
+ * The CEP `execution_trace` envelope for this observation, when the operation was governed. If present it is
25
+ * what RealityTrace is handed to record (content-addressed), instead of the bare observation.
26
+ */
27
+ readonly trace?: Json;
28
+ }
29
+
30
+ /**
31
+ * Result of one delivery attempt. `accepted`/`duplicate` mean the target recorded (or had already recorded) the
32
+ * observation. None of these statuses is evidence or settlement (spec §20).
33
+ */
34
+ export type DeliveryReceipt =
35
+ | { readonly status: "accepted"; readonly observationId: string; readonly targetRef: string; readonly detail: Json }
36
+ | { readonly status: "duplicate"; readonly observationId: string; readonly targetRef: string; readonly detail: Json };
37
+
38
+ /** Implementation-independent observation port. Throws on delivery failure; the outbox retries. */
39
+ export interface ObservationPort {
40
+ readonly id: string;
41
+ record(observation: Observation): Promise<DeliveryReceipt>;
42
+ }
43
+
44
+ /** Persisted delivery status per source event (collection `observations`). */
45
+ export interface ObservationStatus {
46
+ readonly eventId: string;
47
+ readonly observationId: string;
48
+ readonly state: "queued" | "accepted" | "duplicate" | "retrying" | "failed";
49
+ readonly attempts: number;
50
+ readonly targetRef: string | null;
51
+ readonly lastError: string | null;
52
+ }