@jinn-network/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/canonical-json.d.ts +1 -0
  2. package/dist/canonical-json.js +67 -0
  3. package/dist/captured-task.d.ts +140 -0
  4. package/dist/captured-task.js +122 -0
  5. package/dist/contribution-store.d.ts +97 -0
  6. package/dist/contribution-store.js +770 -0
  7. package/dist/corpus-read/acquire.d.ts +39 -0
  8. package/dist/corpus-read/acquire.js +212 -0
  9. package/dist/corpus-read/cache.d.ts +14 -0
  10. package/dist/corpus-read/cache.js +17 -0
  11. package/dist/corpus-read/capture-meta.d.ts +16 -0
  12. package/dist/corpus-read/capture-meta.js +18 -0
  13. package/dist/corpus-read/create-corpus.d.ts +2 -0
  14. package/dist/corpus-read/create-corpus.js +97 -0
  15. package/dist/corpus-read/fetch-artifact.d.ts +21 -0
  16. package/dist/corpus-read/fetch-artifact.js +32 -0
  17. package/dist/corpus-read/fetch.d.ts +12 -0
  18. package/dist/corpus-read/fetch.js +24 -0
  19. package/dist/corpus-read/http-discovery.d.ts +9 -0
  20. package/dist/corpus-read/http-discovery.js +128 -0
  21. package/dist/corpus-read/index.d.ts +10 -0
  22. package/dist/corpus-read/index.js +10 -0
  23. package/dist/corpus-read/ipfs.d.ts +5 -0
  24. package/dist/corpus-read/ipfs.js +72 -0
  25. package/dist/corpus-read/route-resolver.d.ts +16 -0
  26. package/dist/corpus-read/route-resolver.js +19 -0
  27. package/dist/corpus-read/types.d.ts +177 -0
  28. package/dist/corpus-read/types.js +42 -0
  29. package/dist/envelope.d.ts +134 -0
  30. package/dist/envelope.js +162 -0
  31. package/dist/evidence-adapter.d.ts +26 -0
  32. package/dist/evidence-adapter.js +321 -0
  33. package/dist/evidence-filesystem.d.ts +40 -0
  34. package/dist/evidence-filesystem.js +267 -0
  35. package/dist/evidence-index.d.ts +117 -0
  36. package/dist/evidence-index.js +1083 -0
  37. package/dist/evidence-store-lock.d.ts +2 -0
  38. package/dist/evidence-store-lock.js +208 -0
  39. package/dist/execution-envelope.d.ts +3142 -0
  40. package/dist/execution-envelope.js +203 -0
  41. package/dist/index.d.ts +12 -0
  42. package/dist/index.js +12 -0
  43. package/dist/manifest.d.ts +75 -0
  44. package/dist/manifest.js +184 -0
  45. package/dist/paired.d.ts +68 -0
  46. package/dist/paired.js +92 -0
  47. package/dist/scrub/build.d.ts +52 -0
  48. package/dist/scrub/build.js +84 -0
  49. package/dist/scrub/emit-scrub.d.ts +41 -0
  50. package/dist/scrub/emit-scrub.js +87 -0
  51. package/dist/scrub/index.d.ts +12 -0
  52. package/dist/scrub/index.js +12 -0
  53. package/dist/scrub/key-policy.d.ts +20 -0
  54. package/dist/scrub/key-policy.js +42 -0
  55. package/dist/scrub/layer2.d.ts +23 -0
  56. package/dist/scrub/layer2.js +32 -0
  57. package/dist/scrub/ml-pii-stage.d.ts +24 -0
  58. package/dist/scrub/ml-pii-stage.js +42 -0
  59. package/dist/scrub/openredaction-stage.d.ts +196 -0
  60. package/dist/scrub/openredaction-stage.js +270 -0
  61. package/dist/scrub/pii-build.d.ts +38 -0
  62. package/dist/scrub/pii-build.js +67 -0
  63. package/dist/scrub/pipeline.d.ts +38 -0
  64. package/dist/scrub/pipeline.js +83 -0
  65. package/dist/scrub/plain-patterns-stage.d.ts +31 -0
  66. package/dist/scrub/plain-patterns-stage.js +84 -0
  67. package/dist/scrub/secretlint-stage.d.ts +36 -0
  68. package/dist/scrub/secretlint-stage.js +249 -0
  69. package/dist/scrub/transformers-detector.d.ts +35 -0
  70. package/dist/scrub/transformers-detector.js +67 -0
  71. package/dist/scrub/types.d.ts +25 -0
  72. package/dist/scrub/types.js +1 -0
  73. package/dist/session-provenance.d.ts +75 -0
  74. package/dist/session-provenance.js +20 -0
  75. package/dist/skill-artifact.d.ts +442 -0
  76. package/dist/skill-artifact.js +136 -0
  77. package/dist/trajectory/hash-chain.d.ts +18 -0
  78. package/dist/trajectory/hash-chain.js +47 -0
  79. package/dist/trajectory/index.d.ts +4 -0
  80. package/dist/trajectory/index.js +4 -0
  81. package/dist/trajectory/schema.d.ts +606 -0
  82. package/dist/trajectory/schema.js +110 -0
  83. package/dist/trajectory/transcript-parsers/aider-history.d.ts +42 -0
  84. package/dist/trajectory/transcript-parsers/aider-history.js +181 -0
  85. package/dist/trajectory/transcript-parsers/claude-code-jsonl.d.ts +37 -0
  86. package/dist/trajectory/transcript-parsers/claude-code-jsonl.js +142 -0
  87. package/dist/trajectory/transcript-parsers/codex-session.d.ts +42 -0
  88. package/dist/trajectory/transcript-parsers/codex-session.js +235 -0
  89. package/dist/trajectory/transcript-parsers/continue-devdata.d.ts +41 -0
  90. package/dist/trajectory/transcript-parsers/continue-devdata.js +190 -0
  91. package/dist/trajectory/transcript-parsers/cursor-sqlite.d.ts +38 -0
  92. package/dist/trajectory/transcript-parsers/cursor-sqlite.js +118 -0
  93. package/dist/trajectory/transcript-parsers/gemini-session.d.ts +35 -0
  94. package/dist/trajectory/transcript-parsers/gemini-session.js +137 -0
  95. package/dist/trajectory/transcript-parsers/index.d.ts +7 -0
  96. package/dist/trajectory/transcript-parsers/index.js +7 -0
  97. package/dist/trajectory/transcript-parsers/types.d.ts +101 -0
  98. package/dist/trajectory/transcript-parsers/types.js +32 -0
  99. package/dist/trajectory/transcript-to-spans/attrs.d.ts +4 -0
  100. package/dist/trajectory/transcript-to-spans/attrs.js +30 -0
  101. package/dist/trajectory/transcript-to-spans/claude-code-stream-json.d.ts +8 -0
  102. package/dist/trajectory/transcript-to-spans/claude-code-stream-json.js +162 -0
  103. package/dist/trajectory/transcript-to-spans/codex-exec-json.d.ts +8 -0
  104. package/dist/trajectory/transcript-to-spans/codex-exec-json.js +253 -0
  105. package/dist/trajectory/transcript-to-spans/index.d.ts +4 -0
  106. package/dist/trajectory/transcript-to-spans/index.js +4 -0
  107. package/dist/trajectory/transcript-to-spans/types.d.ts +18 -0
  108. package/dist/trajectory/transcript-to-spans/types.js +1 -0
  109. package/dist/window.d.ts +12 -0
  110. package/dist/window.js +5 -0
  111. package/package.json +67 -0
@@ -0,0 +1,5 @@
1
+ export declare function normalizeIpfsGatewayBase(gatewayUrl: string): string;
2
+ export declare function buildIpfsHexCidCandidatesFromPartialHex(hex: string): string[];
3
+ export declare function buildIpfsFetchCidPathCandidates(cidOrPath: string): string[];
4
+ /** Read-only multi-codec, primary-plus-fallback IPFS JSON fetch. */
5
+ export declare function fetchFromIpfs(gatewayUrl: string, cid: string): Promise<unknown>;
@@ -0,0 +1,72 @@
1
+ const IPFS_FETCH_TIMEOUT_MS = 15_000;
2
+ const FALLBACK_IPFS_GATEWAY_BASE = 'https://ipfs.io/ipfs/';
3
+ export function normalizeIpfsGatewayBase(gatewayUrl) {
4
+ let normalized = gatewayUrl.trim();
5
+ if (normalized === '')
6
+ normalized = 'https://gateway.autonolas.tech';
7
+ normalized = normalized.replace(/\/+$/, '');
8
+ if (!normalized.toLowerCase().endsWith('/ipfs'))
9
+ normalized = `${normalized}/ipfs`;
10
+ return `${normalized}/`;
11
+ }
12
+ export function buildIpfsHexCidCandidatesFromPartialHex(hex) {
13
+ const normalized = (hex.startsWith('0x') ? hex.slice(2) : hex).toLowerCase();
14
+ if (!/^[0-9a-f]{64}$/.test(normalized))
15
+ return [hex];
16
+ return [`f01551220${normalized}`, `f01701220${normalized}`];
17
+ }
18
+ export function buildIpfsFetchCidPathCandidates(cidOrPath) {
19
+ const value = cidOrPath.trim();
20
+ const lower = value.toLowerCase();
21
+ if (lower.startsWith('f01551220') && lower.length > 9) {
22
+ return buildIpfsHexCidCandidatesFromPartialHex(lower.slice(9));
23
+ }
24
+ if (lower.startsWith('f01701220') && lower.length > 9) {
25
+ return buildIpfsHexCidCandidatesFromPartialHex(lower.slice(9));
26
+ }
27
+ if (/^[0-9a-f]{64}$/i.test(value)) {
28
+ return buildIpfsHexCidCandidatesFromPartialHex(value);
29
+ }
30
+ return [value];
31
+ }
32
+ async function fetchJson(url, signal) {
33
+ const response = await fetch(url, { method: 'GET', signal });
34
+ if (!response.ok) {
35
+ throw new Error(`IPFS fetch failed: ${response.status} ${response.statusText} (${url.slice(0, 80)}…)`);
36
+ }
37
+ const contentType = response.headers.get('content-type') ?? '';
38
+ if (contentType.includes('application/json'))
39
+ return response.json();
40
+ const text = await response.text();
41
+ try {
42
+ return JSON.parse(text);
43
+ }
44
+ catch {
45
+ throw new Error(`IPFS response is not JSON (content-type: ${contentType || 'none'})`);
46
+ }
47
+ }
48
+ /** Read-only multi-codec, primary-plus-fallback IPFS JSON fetch. */
49
+ export async function fetchFromIpfs(gatewayUrl, cid) {
50
+ const primary = normalizeIpfsGatewayBase(gatewayUrl);
51
+ const errors = [];
52
+ for (const cidPath of buildIpfsFetchCidPathCandidates(cid)) {
53
+ for (const [name, baseUrl] of [
54
+ ['primary', primary],
55
+ ['fallback', FALLBACK_IPFS_GATEWAY_BASE],
56
+ ]) {
57
+ const url = `${baseUrl}${cidPath}`;
58
+ const controller = new AbortController();
59
+ const timer = setTimeout(() => controller.abort(), IPFS_FETCH_TIMEOUT_MS);
60
+ try {
61
+ return await fetchJson(url, controller.signal);
62
+ }
63
+ catch (error) {
64
+ errors.push(`${name}:${url.slice(0, 100)}: ${error instanceof Error ? error.message : String(error)}`);
65
+ }
66
+ finally {
67
+ clearTimeout(timer);
68
+ }
69
+ }
70
+ }
71
+ throw new Error(`IPFS JSON fetch failed after all candidates: ${errors.join(' | ')}`);
72
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Default RouteResolver — uses the artifact's own access.endpoint directly.
3
+ *
4
+ * Phase A.1 doesn't ship a centralized resolver: every artifact descriptor in
5
+ * the manifest envelope already carries its own `access.endpoint`. The default
6
+ * resolver simply returns null so the acquire chain falls through to the free
7
+ * origin fetch using that endpoint.
8
+ *
9
+ * Future phases may introduce a real resolver (e.g. multi-mirror selection,
10
+ * DHT lookup, peer routing) by implementing the `RouteResolver` interface from
11
+ * `./types.js`.
12
+ *
13
+ * Spec: spec/2026-04-30-phase-a-umbrella.md §2.5.
14
+ */
15
+ import type { RouteResolver } from './types.js';
16
+ export declare const noopRouteResolver: RouteResolver;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Default RouteResolver — uses the artifact's own access.endpoint directly.
3
+ *
4
+ * Phase A.1 doesn't ship a centralized resolver: every artifact descriptor in
5
+ * the manifest envelope already carries its own `access.endpoint`. The default
6
+ * resolver simply returns null so the acquire chain falls through to the free
7
+ * origin fetch using that endpoint.
8
+ *
9
+ * Future phases may introduce a real resolver (e.g. multi-mirror selection,
10
+ * DHT lookup, peer routing) by implementing the `RouteResolver` interface from
11
+ * `./types.js`.
12
+ *
13
+ * Spec: spec/2026-04-30-phase-a-umbrella.md §2.5.
14
+ */
15
+ export const noopRouteResolver = {
16
+ async resolve() {
17
+ return null;
18
+ },
19
+ };
@@ -0,0 +1,177 @@
1
+ import type { AcquireResult } from './fetch-artifact.js';
2
+ export interface EnvelopeRef {
3
+ manifestCid: string;
4
+ manifestHash: string;
5
+ operator: {
6
+ agentId: string;
7
+ safeAddress: string;
8
+ };
9
+ evidenceTier: 'self-signed' | 'committed' | 'attested' | 'unknown';
10
+ publishedAt: number;
11
+ }
12
+ export interface CorpusQuery {
13
+ solverType?: string;
14
+ artifactType?: string;
15
+ taskCid?: string;
16
+ participant?: {
17
+ safeAddress?: string;
18
+ };
19
+ evidenceTier?: 'self-signed' | 'committed' | 'attested';
20
+ generatedAfter?: number;
21
+ generatedBefore?: number;
22
+ limit?: number;
23
+ manifestHash?: string;
24
+ }
25
+ export interface ArtifactSource {
26
+ kind: 'ipfs';
27
+ cid: string;
28
+ sha256: string;
29
+ encoding: 'jinn.artifact.donation.v1';
30
+ }
31
+ export interface CorpusManifestArtifact {
32
+ artifactType: string;
33
+ sha256: string;
34
+ access: {
35
+ endpoint: string;
36
+ priceUsdc: string;
37
+ };
38
+ sources?: ArtifactSource[];
39
+ }
40
+ /**
41
+ * The read-side projection core needs from a signed execution envelope.
42
+ * Client-owned validation supplies this shape through `parseEnvelope`; core
43
+ * deliberately has no dependency on daemon schemas.
44
+ */
45
+ export interface CorpusManifest {
46
+ solverType: string;
47
+ role: string;
48
+ generatedAt: number;
49
+ task?: {
50
+ cid: string;
51
+ requestId: string;
52
+ };
53
+ participant: {
54
+ safeAddress: string;
55
+ };
56
+ artifacts: CorpusManifestArtifact[];
57
+ }
58
+ export interface ManifestPreview<TEnvelope extends CorpusManifest = CorpusManifest> {
59
+ ref: EnvelopeRef;
60
+ envelope: TEnvelope;
61
+ }
62
+ export interface ArtifactContent {
63
+ sha256: string;
64
+ bytes: Buffer;
65
+ artifactType: string;
66
+ source: 'cache' | 'self-store' | 'origin' | 'route-resolver' | 'ipfs';
67
+ paidAmountUsdc: string;
68
+ fetchedAt: string;
69
+ sourceOperator?: string;
70
+ }
71
+ export interface CorpusEnvelope<TEnvelope extends CorpusManifest = CorpusManifest> extends ManifestPreview<TEnvelope> {
72
+ artifactContents: Map<string, ArtifactContent>;
73
+ }
74
+ export interface RouteResolver {
75
+ resolve(req: {
76
+ sha256: string;
77
+ access: {
78
+ endpoint: string;
79
+ priceUsdc: string;
80
+ };
81
+ requesterSafe: string;
82
+ }): Promise<{
83
+ bytes: Buffer;
84
+ sourceOperator?: string;
85
+ pricePaidUsdc: string;
86
+ } | null>;
87
+ }
88
+ export interface NetworkArtifactRow {
89
+ sha256: string;
90
+ artifactType: string;
91
+ content: Buffer;
92
+ fetchedAt: string;
93
+ sourceOperator?: string | null;
94
+ }
95
+ export interface ServedArtifactRow {
96
+ artifactType: string;
97
+ envelopeCid: string | null;
98
+ content: Buffer;
99
+ }
100
+ export interface SaveNetworkArtifactInput {
101
+ sha256: string;
102
+ artifactType: string;
103
+ envelopeCid: string | null;
104
+ content: Buffer;
105
+ source: 'origin' | 'route-resolver' | 'self-store-mirror';
106
+ sourceOperator?: string | null;
107
+ sourceEndpoint?: string | null;
108
+ paidAmountUsdc: string;
109
+ fetchedAt: string;
110
+ }
111
+ /** SQLite ownership remains client-side until C4; core consumes this port. */
112
+ export interface CorpusStorePort {
113
+ getNetworkArtifact(sha256: string): NetworkArtifactRow | null;
114
+ touchNetworkArtifactUsage(sha256: string, usedAt: string): void;
115
+ saveNetworkArtifact(input: SaveNetworkArtifactInput): void;
116
+ getServedArtifact(sha256: string): ServedArtifactRow | null;
117
+ }
118
+ /** Chain/indexer selection remains client-side; core only needs this read port. */
119
+ export interface CorpusDiscoveryPort {
120
+ queryEnvelopes(query: CorpusQuery): Promise<EnvelopeRef[]>;
121
+ }
122
+ export interface CorpusOptions<TEnvelope extends CorpusManifest = CorpusManifest> {
123
+ ipfsGatewayUrl: string;
124
+ store: CorpusStorePort;
125
+ signer: {
126
+ privateKey: string;
127
+ };
128
+ selfSafeAddress: string;
129
+ routeResolver?: RouteResolver;
130
+ discovery?: CorpusDiscoveryPort;
131
+ legacyQuery?: (query: CorpusQuery) => Promise<EnvelopeRef[]>;
132
+ parseEnvelope(input: unknown): TEnvelope;
133
+ }
134
+ export interface CorpusDeps {
135
+ fetchFromIpfs?: (gatewayUrl: string, cid: string) => Promise<unknown>;
136
+ acquireFn?: (endpoint: string, sha256: string, privateKey?: string) => Promise<Buffer | null | AcquireResult>;
137
+ }
138
+ export interface ReadArgs<TEnvelope extends CorpusManifest = CorpusManifest> {
139
+ query: CorpusQuery;
140
+ select?: (manifests: ManifestPreview<TEnvelope>[]) => ManifestPreview<TEnvelope>[];
141
+ }
142
+ export interface Corpus<TEnvelope extends CorpusManifest = CorpusManifest> {
143
+ read(args: ReadArgs<TEnvelope>): Promise<CorpusEnvelope<TEnvelope>[]>;
144
+ query(query: CorpusQuery): Promise<EnvelopeRef[]>;
145
+ fetchManifest(ref: EnvelopeRef): Promise<ManifestPreview<TEnvelope>>;
146
+ acquire(manifest: ManifestPreview<TEnvelope>): Promise<CorpusEnvelope<TEnvelope>>;
147
+ acquireBySha256(sha256: string, access: {
148
+ endpoint: string;
149
+ priceUsdc: string;
150
+ }, hint?: {
151
+ artifactType?: string;
152
+ envelopeCid?: string;
153
+ sources?: ArtifactSource[];
154
+ ownerSafe?: string;
155
+ }): Promise<ArtifactContent>;
156
+ }
157
+ export declare class ManifestFetchError extends Error {
158
+ readonly manifestCid: string;
159
+ readonly cause?: unknown | undefined;
160
+ constructor(manifestCid: string, message: string, cause?: unknown | undefined);
161
+ }
162
+ export declare class AcquireError extends Error {
163
+ readonly sha256: string;
164
+ readonly cause?: unknown | undefined;
165
+ constructor(sha256: string, message: string, cause?: unknown | undefined);
166
+ }
167
+ export declare class HashMismatchError extends Error {
168
+ readonly sha256Expected: string;
169
+ readonly sha256Actual: string;
170
+ readonly source: string;
171
+ readonly sourceOperator?: string | undefined;
172
+ constructor(sha256Expected: string, sha256Actual: string, source: string, sourceOperator?: string | undefined);
173
+ }
174
+ export declare class DiscoveryUnavailableError extends Error {
175
+ readonly cause?: unknown | undefined;
176
+ constructor(message: string, cause?: unknown | undefined);
177
+ }
@@ -0,0 +1,42 @@
1
+ export class ManifestFetchError extends Error {
2
+ manifestCid;
3
+ cause;
4
+ constructor(manifestCid, message, cause) {
5
+ super(`manifest ${manifestCid}: ${message}`);
6
+ this.manifestCid = manifestCid;
7
+ this.cause = cause;
8
+ this.name = 'ManifestFetchError';
9
+ }
10
+ }
11
+ export class AcquireError extends Error {
12
+ sha256;
13
+ cause;
14
+ constructor(sha256, message, cause) {
15
+ super(`acquire ${sha256}: ${message}`);
16
+ this.sha256 = sha256;
17
+ this.cause = cause;
18
+ this.name = 'AcquireError';
19
+ }
20
+ }
21
+ export class HashMismatchError extends Error {
22
+ sha256Expected;
23
+ sha256Actual;
24
+ source;
25
+ sourceOperator;
26
+ constructor(sha256Expected, sha256Actual, source, sourceOperator) {
27
+ super(`hash mismatch: expected ${sha256Expected}, got ${sha256Actual} from ${source}`);
28
+ this.sha256Expected = sha256Expected;
29
+ this.sha256Actual = sha256Actual;
30
+ this.source = source;
31
+ this.sourceOperator = sourceOperator;
32
+ this.name = 'HashMismatchError';
33
+ }
34
+ }
35
+ export class DiscoveryUnavailableError extends Error {
36
+ cause;
37
+ constructor(message, cause) {
38
+ super(message);
39
+ this.cause = cause;
40
+ this.name = 'DiscoveryUnavailableError';
41
+ }
42
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Layer-1 trace envelope — `jinn.trace-envelope.v0`.
3
+ *
4
+ * The scrubbed evidence envelope a harness publishes to the corpus when a
5
+ * user completes (or fails) a task with contribution consent on. FROZEN at
6
+ * v0 operator sign-off (spec Q1) — changes after that are a spec amendment
7
+ * to `spec/2026-07-02-jinn-harness-network.md`.
8
+ *
9
+ * The schema is CLOSED (strict objects at every level): unknown fields are
10
+ * rejected so nothing can be smuggled into published traces, or silently
11
+ * depended on by consumers, without a spec amendment.
12
+ *
13
+ * Field-by-field rationale, reader map, and three validated examples:
14
+ * `packages/layer/docs/envelope-v0.md` (the examples in that
15
+ * doc are parsed against this schema in `test/envelope.test.ts`).
16
+ *
17
+ * Step shape mirrors the capture-span shape (`SpanRow`,
18
+ * `client/src/store/captures.ts`) minus session/trace IDs, which are hoisted
19
+ * to `session`.
20
+ *
21
+ * Plan: docs/superpowers/plans/2026-07-02-jinn-harness-network-v0-plan.md
22
+ * Task 2 (issue #1309).
23
+ */
24
+ import { z } from 'zod';
25
+ export declare const TRACE_ENVELOPE_SCHEMA_VERSION: "jinn.trace-envelope.v0";
26
+ export declare const EVIDENCE_PROVENANCES: readonly ["contributed", "imported", "derived-from-history"];
27
+ export declare const EvidenceProvenanceSchema: z.ZodEnum<{
28
+ contributed: "contributed";
29
+ imported: "imported";
30
+ "derived-from-history": "derived-from-history";
31
+ }>;
32
+ export type EvidenceProvenance = z.infer<typeof EvidenceProvenanceSchema>;
33
+ /** Max steps per envelope — keeps envelopes anchorable and cheap to fetch. */
34
+ export declare const MAX_STEPS = 512;
35
+ /** Max serialised size of a single step's `attributes`, in bytes. */
36
+ export declare const MAX_STEP_ATTRIBUTES_BYTES: number;
37
+ /** Max freeform distribution tags per envelope. */
38
+ export declare const MAX_DISTRIBUTION_TAGS = 16;
39
+ /**
40
+ * Verifiability tiers, weakest → strongest (spec §5). The
41
+ * ContributionActivityChecker counts contributions against this ladder;
42
+ * layer-2 promotion reads it as the quality gate.
43
+ */
44
+ export declare const VERIFIABILITY_TIERS: readonly ["user-accepted", "tests-passed", "evaluator-verified"];
45
+ export declare const VerifiabilityTierSchema: z.ZodEnum<{
46
+ "user-accepted": "user-accepted";
47
+ "tests-passed": "tests-passed";
48
+ "evaluator-verified": "evaluator-verified";
49
+ }>;
50
+ export type VerifiabilityTier = z.infer<typeof VerifiabilityTierSchema>;
51
+ export declare const OutcomeStatusSchema: z.ZodEnum<{
52
+ completed: "completed";
53
+ failed: "failed";
54
+ abandoned: "abandoned";
55
+ }>;
56
+ export type OutcomeStatus = z.infer<typeof OutcomeStatusSchema>;
57
+ /**
58
+ * One compressed trace step. Mirrors `SpanRow`
59
+ * (client/src/store/captures.ts): scrubbed `attributes` plus the
60
+ * `redactedKeys` receipt of what the scrub pipeline removed.
61
+ */
62
+ export declare const TraceStepSchema: z.ZodObject<{
63
+ spanId: z.ZodString;
64
+ parentSpanId: z.ZodNullable<z.ZodString>;
65
+ name: z.ZodString;
66
+ startTimeUnixNano: z.ZodString;
67
+ endTimeUnixNano: z.ZodString;
68
+ attributes: z.ZodRecord<z.ZodString, z.ZodUnknown>;
69
+ redactedKeys: z.ZodArray<z.ZodString>;
70
+ truncatedKeys: z.ZodOptional<z.ZodArray<z.ZodString>>;
71
+ }, z.core.$strict>;
72
+ export type TraceStep = z.infer<typeof TraceStepSchema>;
73
+ export declare const TraceEnvelopeV0Schema: z.ZodObject<{
74
+ schemaVersion: z.ZodLiteral<"jinn.trace-envelope.v0">;
75
+ session: z.ZodObject<{
76
+ sessionId: z.ZodString;
77
+ capturedAt: z.ZodISODateTime;
78
+ }, z.core.$strict>;
79
+ task: z.ZodObject<{
80
+ summary: z.ZodString;
81
+ distributionTags: z.ZodArray<z.ZodString>;
82
+ }, z.core.$strict>;
83
+ environment: z.ZodObject<{
84
+ harness: z.ZodObject<{
85
+ name: z.ZodString;
86
+ version: z.ZodString;
87
+ }, z.core.$strict>;
88
+ model: z.ZodString;
89
+ tools: z.ZodArray<z.ZodString>;
90
+ }, z.core.$strict>;
91
+ steps: z.ZodArray<z.ZodObject<{
92
+ spanId: z.ZodString;
93
+ parentSpanId: z.ZodNullable<z.ZodString>;
94
+ name: z.ZodString;
95
+ startTimeUnixNano: z.ZodString;
96
+ endTimeUnixNano: z.ZodString;
97
+ attributes: z.ZodRecord<z.ZodString, z.ZodUnknown>;
98
+ redactedKeys: z.ZodArray<z.ZodString>;
99
+ truncatedKeys: z.ZodOptional<z.ZodArray<z.ZodString>>;
100
+ }, z.core.$strict>>;
101
+ outcome: z.ZodObject<{
102
+ status: z.ZodEnum<{
103
+ completed: "completed";
104
+ failed: "failed";
105
+ abandoned: "abandoned";
106
+ }>;
107
+ verifiabilityTier: z.ZodEnum<{
108
+ "user-accepted": "user-accepted";
109
+ "tests-passed": "tests-passed";
110
+ "evaluator-verified": "evaluator-verified";
111
+ }>;
112
+ summary: z.ZodOptional<z.ZodString>;
113
+ }, z.core.$strict>;
114
+ cost: z.ZodObject<{
115
+ durationMs: z.ZodNumber;
116
+ tokens: z.ZodOptional<z.ZodObject<{
117
+ input: z.ZodNumber;
118
+ output: z.ZodNumber;
119
+ }, z.core.$strict>>;
120
+ usdEstimate: z.ZodOptional<z.ZodString>;
121
+ }, z.core.$strict>;
122
+ consent: z.ZodObject<{
123
+ contributionConsent: z.ZodLiteral<true>;
124
+ scrubCompleted: z.ZodLiteral<true>;
125
+ }, z.core.$strict>;
126
+ provenance: z.ZodEnum<{
127
+ contributed: "contributed";
128
+ imported: "imported";
129
+ "derived-from-history": "derived-from-history";
130
+ }>;
131
+ }, z.core.$strict>;
132
+ export type TraceEnvelopeV0 = z.infer<typeof TraceEnvelopeV0Schema>;
133
+ /** Parse an untrusted value as a TraceEnvelopeV0; throws ZodError on any deviation. */
134
+ export declare function parseTraceEnvelopeV0(input: unknown): TraceEnvelopeV0;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Layer-1 trace envelope — `jinn.trace-envelope.v0`.
3
+ *
4
+ * The scrubbed evidence envelope a harness publishes to the corpus when a
5
+ * user completes (or fails) a task with contribution consent on. FROZEN at
6
+ * v0 operator sign-off (spec Q1) — changes after that are a spec amendment
7
+ * to `spec/2026-07-02-jinn-harness-network.md`.
8
+ *
9
+ * The schema is CLOSED (strict objects at every level): unknown fields are
10
+ * rejected so nothing can be smuggled into published traces, or silently
11
+ * depended on by consumers, without a spec amendment.
12
+ *
13
+ * Field-by-field rationale, reader map, and three validated examples:
14
+ * `packages/layer/docs/envelope-v0.md` (the examples in that
15
+ * doc are parsed against this schema in `test/envelope.test.ts`).
16
+ *
17
+ * Step shape mirrors the capture-span shape (`SpanRow`,
18
+ * `client/src/store/captures.ts`) minus session/trace IDs, which are hoisted
19
+ * to `session`.
20
+ *
21
+ * Plan: docs/superpowers/plans/2026-07-02-jinn-harness-network-v0-plan.md
22
+ * Task 2 (issue #1309).
23
+ */
24
+ import { z } from 'zod';
25
+ export const TRACE_ENVELOPE_SCHEMA_VERSION = 'jinn.trace-envelope.v0';
26
+ export const EVIDENCE_PROVENANCES = [
27
+ 'contributed',
28
+ 'imported',
29
+ 'derived-from-history',
30
+ ];
31
+ export const EvidenceProvenanceSchema = z.enum(EVIDENCE_PROVENANCES);
32
+ /** Max steps per envelope — keeps envelopes anchorable and cheap to fetch. */
33
+ export const MAX_STEPS = 512;
34
+ /** Max serialised size of a single step's `attributes`, in bytes. */
35
+ export const MAX_STEP_ATTRIBUTES_BYTES = 16 * 1024;
36
+ /** Max freeform distribution tags per envelope. */
37
+ export const MAX_DISTRIBUTION_TAGS = 16;
38
+ /**
39
+ * Verifiability tiers, weakest → strongest (spec §5). The
40
+ * ContributionActivityChecker counts contributions against this ladder;
41
+ * layer-2 promotion reads it as the quality gate.
42
+ */
43
+ export const VERIFIABILITY_TIERS = [
44
+ 'user-accepted',
45
+ 'tests-passed',
46
+ 'evaluator-verified',
47
+ ];
48
+ export const VerifiabilityTierSchema = z.enum(VERIFIABILITY_TIERS);
49
+ export const OutcomeStatusSchema = z.enum(['completed', 'failed', 'abandoned']);
50
+ /** Unix-nanosecond timestamp string (OTel span convention). */
51
+ const UnixNanoSchema = z.string().regex(/^\d+$/, 'unix-nanosecond digit string');
52
+ /** Non-empty, no surrounding whitespace. */
53
+ const TrimmedString = (max) => z.string().min(1).max(max).regex(/^\S(.*\S)?$/s, 'no surrounding whitespace');
54
+ /**
55
+ * One compressed trace step. Mirrors `SpanRow`
56
+ * (client/src/store/captures.ts): scrubbed `attributes` plus the
57
+ * `redactedKeys` receipt of what the scrub pipeline removed.
58
+ */
59
+ export const TraceStepSchema = z.strictObject({
60
+ spanId: z.string().min(1),
61
+ parentSpanId: z.string().min(1).nullable(),
62
+ name: z.string().min(1),
63
+ startTimeUnixNano: UnixNanoSchema,
64
+ endTimeUnixNano: UnixNanoSchema,
65
+ attributes: z.record(z.string(), z.unknown()).superRefine((attrs, ctx) => {
66
+ let serialised;
67
+ try {
68
+ serialised = JSON.stringify(attrs);
69
+ }
70
+ catch {
71
+ ctx.addIssue({ code: 'custom', message: 'attributes must be JSON-serialisable' });
72
+ return;
73
+ }
74
+ const bytes = Buffer.byteLength(serialised, 'utf8');
75
+ if (bytes > MAX_STEP_ATTRIBUTES_BYTES) {
76
+ ctx.addIssue({
77
+ code: 'custom',
78
+ message: `attributes serialise to ${bytes} bytes (max ${MAX_STEP_ATTRIBUTES_BYTES})`,
79
+ });
80
+ }
81
+ }),
82
+ redactedKeys: z.array(z.string().min(1)),
83
+ /**
84
+ * Which attribute keys the capture→envelope pipeline truncated to fit
85
+ * `MAX_STEP_ATTRIBUTES_BYTES` — the visible truncation receipt, sibling
86
+ * to `redactedKeys` (2026-07-02 schema review, Q4).
87
+ */
88
+ truncatedKeys: z.array(z.string().min(1)).optional(),
89
+ });
90
+ export const TraceEnvelopeV0Schema = z.strictObject({
91
+ /** Migration gate — v1 readers reject/route on this. */
92
+ schemaVersion: z.literal(TRACE_ENVELOPE_SCHEMA_VERSION),
93
+ /** Capture-session identity: ledger correlation + dedup. */
94
+ session: z.strictObject({
95
+ sessionId: z.string().min(1).max(128),
96
+ capturedAt: z.iso.datetime(),
97
+ }),
98
+ /** What the user was doing — the demand signal (spec §5). */
99
+ task: z.strictObject({
100
+ /** Scrubbed one-line descriptor of the task. */
101
+ summary: TrimmedString(500),
102
+ /**
103
+ * Freeform — no fixed taxonomy; clustering is inferred server-side.
104
+ * At least one tag: an untagged envelope is invisible to the signal.
105
+ */
106
+ distributionTags: z.array(TrimmedString(64)).min(1).max(MAX_DISTRIBUTION_TAGS),
107
+ }),
108
+ /** Config-diversity fingerprint (spec §5): harness, model, tools. */
109
+ environment: z.strictObject({
110
+ harness: z.strictObject({
111
+ name: z.string().min(1),
112
+ version: z.string().min(1),
113
+ }),
114
+ model: z.string().min(1),
115
+ /** Tool names only — never tool configs or arguments. */
116
+ tools: z.array(z.string().min(1).max(128)).max(64),
117
+ }),
118
+ /** The compressed trace body — the evidence itself. */
119
+ steps: z.array(TraceStepSchema).min(1).max(MAX_STEPS),
120
+ /** What happened, and how the status was established. */
121
+ outcome: z.strictObject({
122
+ status: OutcomeStatusSchema,
123
+ /**
124
+ * How the recorded status was established — the anti-farming line the
125
+ * activity checker counts against. Qualifies the status, so failed
126
+ * tasks carry a tier too.
127
+ */
128
+ verifiabilityTier: VerifiabilityTierSchema,
129
+ summary: TrimmedString(500).optional(),
130
+ }),
131
+ /** What the task cost to run — the §8 capability-gate metric. */
132
+ cost: z.strictObject({
133
+ durationMs: z.number().int().nonnegative(),
134
+ tokens: z.strictObject({
135
+ input: z.number().int().nonnegative(),
136
+ output: z.number().int().nonnegative(),
137
+ }).optional(),
138
+ /** Decimal string — money is never IEEE-754. An estimate, named as such. */
139
+ usdEstimate: z.string().regex(/^\d+(\.\d+)?$/).optional(),
140
+ }),
141
+ /**
142
+ * The publish gate (spec §5), fail-closed at the type level: both flags
143
+ * are literal `true`, so an envelope representing an unconsented or
144
+ * unscrubbed trace is not constructible.
145
+ */
146
+ consent: z.strictObject({
147
+ /** First-run consent given AND no per-task veto on this trace. */
148
+ contributionConsent: z.literal(true),
149
+ /** The mandatory scrub pipeline completed on this trace. */
150
+ scrubCompleted: z.literal(true),
151
+ }),
152
+ /**
153
+ * `contributed` = real user trace; `imported` = seed (spec §7);
154
+ * `derived-from-history` = a same-schema projection of immutable ledger
155
+ * history. The distribution signal excludes `imported`.
156
+ */
157
+ provenance: EvidenceProvenanceSchema,
158
+ });
159
+ /** Parse an untrusted value as a TraceEnvelopeV0; throws ZodError on any deviation. */
160
+ export function parseTraceEnvelopeV0(input) {
161
+ return TraceEnvelopeV0Schema.parse(input);
162
+ }
@@ -0,0 +1,26 @@
1
+ import type { EvidencePort, EvidenceRetentionPolicy } from '@jinn-network/plugin';
2
+ import { type EpisodeV1 } from '@jinn-network/plugin';
3
+ import { type CapturedTask } from './captured-task.js';
4
+ export declare function episodeFileName(id: string): string;
5
+ export interface EvidenceAdapterDeps {
6
+ /** The one dir this store reads legacy captures from AND writes episodes to. */
7
+ capturesDir: string;
8
+ /** Retention policy surfaced by `retention()` and used as the up-map default. */
9
+ retention?: EvidenceRetentionPolicy;
10
+ /**
11
+ * Best-effort notification after a successful write and retention pass.
12
+ * Derived views may refresh here, but their failure never invalidates the
13
+ * canonical episode write.
14
+ */
15
+ onStoreChanged?: () => void | Promise<void>;
16
+ }
17
+ /**
18
+ * Up-map a legacy `CapturedTask` to a strict `EpisodeV1`. Fail-closed:
19
+ * `EpisodeV1Schema.parse` throws if the up-map is wrong.
20
+ *
21
+ * Field gaps `CapturedTask` leaves that this fills: each step's discriminated
22
+ * `kind`, `environment.skillsLoadout` (default `[]`), `retention.policy`
23
+ * (from the adapter's configured retention), and `episodeId` (the sessionId).
24
+ */
25
+ export declare function capturedTaskToEpisode(task: CapturedTask, retention: EvidenceRetentionPolicy): EpisodeV1;
26
+ export declare function createEvidenceAdapter(deps: EvidenceAdapterDeps): EvidencePort;