@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 +201 -0
- package/README.md +41 -2
- package/package.json +30 -4
- package/src/godspeed.ts +150 -0
- package/src/index.ts +141 -0
- package/src/sxr-cli.ts +186 -0
- package/src/types.ts +52 -0
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
|
-
#
|
|
1
|
+
# @godspeedai/cognate-realitytrace
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
}
|
package/src/godspeed.ts
ADDED
|
@@ -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
|
+
}
|