@jinn-network/jinn-layer 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.
- package/dist/adapters/contribution-adapter.d.ts +18 -0
- package/dist/adapters/contribution-adapter.js +134 -0
- package/dist/adapters/corpus-adapter.d.ts +12 -0
- package/dist/adapters/corpus-adapter.js +212 -0
- package/dist/adapters/index.d.ts +17 -0
- package/dist/adapters/index.js +17 -0
- package/dist/adapters/json-map-store.d.ts +4 -0
- package/dist/adapters/json-map-store.js +31 -0
- package/dist/adapters/local-learning-adapter.d.ts +25 -0
- package/dist/adapters/local-learning-adapter.js +49 -0
- package/dist/adapters/skills-adapter.d.ts +8 -0
- package/dist/adapters/skills-adapter.js +55 -0
- package/dist/admission-policy.d.ts +38 -0
- package/dist/admission-policy.js +17 -0
- package/dist/bin/jinn-distill-mcp.d.ts +2 -0
- package/dist/bin/jinn-distill-mcp.js +10 -0
- package/dist/bin/jinn-layer.d.ts +7 -0
- package/dist/bin/jinn-layer.js +15 -0
- package/dist/bridge-fetch-evidence.d.ts +102 -0
- package/dist/bridge-fetch-evidence.js +677 -0
- package/dist/bridge-verdict-source.d.ts +47 -0
- package/dist/bridge-verdict-source.js +216 -0
- package/dist/bridge.d.ts +247 -0
- package/dist/bridge.js +439 -0
- package/dist/capture.d.ts +98 -0
- package/dist/capture.js +617 -0
- package/dist/cli.d.ts +148 -0
- package/dist/cli.js +2221 -0
- package/dist/cluster-selection.d.ts +34 -0
- package/dist/cluster-selection.js +135 -0
- package/dist/cluster.d.ts +89 -0
- package/dist/cluster.js +142 -0
- package/dist/consume.d.ts +139 -0
- package/dist/consume.js +280 -0
- package/dist/corpus-probes.d.ts +61 -0
- package/dist/corpus-probes.js +120 -0
- package/dist/corpus-store.d.ts +16 -0
- package/dist/corpus-store.js +101 -0
- package/dist/distill-captures.d.ts +56 -0
- package/dist/distill-captures.js +283 -0
- package/dist/distill-feedback.d.ts +20 -0
- package/dist/distill-feedback.js +30 -0
- package/dist/distill-llm.d.ts +138 -0
- package/dist/distill-llm.js +438 -0
- package/dist/distill-mcp-server.d.ts +30 -0
- package/dist/distill-mcp-server.js +281 -0
- package/dist/distill-mode.d.ts +62 -0
- package/dist/distill-mode.js +107 -0
- package/dist/distill-progress.d.ts +78 -0
- package/dist/distill-progress.js +69 -0
- package/dist/distill-prompt.d.ts +34 -0
- package/dist/distill-prompt.js +85 -0
- package/dist/distill-render.d.ts +123 -0
- package/dist/distill-render.js +303 -0
- package/dist/distill-runs.d.ts +28 -0
- package/dist/distill-runs.js +39 -0
- package/dist/distill-traces.d.ts +67 -0
- package/dist/distill-traces.js +211 -0
- package/dist/distill.d.ts +180 -0
- package/dist/distill.js +337 -0
- package/dist/distiller.d.ts +118 -0
- package/dist/distiller.js +142 -0
- package/dist/envelope.d.ts +9 -0
- package/dist/envelope.js +9 -0
- package/dist/eval-prep.d.ts +129 -0
- package/dist/eval-prep.js +523 -0
- package/dist/execution-publish.d.ts +62 -0
- package/dist/execution-publish.js +72 -0
- package/dist/gate.d.ts +39 -0
- package/dist/gate.js +106 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +50 -0
- package/dist/ipfs-cid.d.ts +18 -0
- package/dist/ipfs-cid.js +149 -0
- package/dist/layer-default.d.ts +3 -0
- package/dist/layer-default.js +10 -0
- package/dist/ledger.d.ts +62 -0
- package/dist/ledger.js +104 -0
- package/dist/measurement.d.ts +90 -0
- package/dist/measurement.js +134 -0
- package/dist/path-safety.d.ts +19 -0
- package/dist/path-safety.js +303 -0
- package/dist/pipeline.d.ts +94 -0
- package/dist/pipeline.js +124 -0
- package/dist/plugin-wiring.d.ts +4 -0
- package/dist/plugin-wiring.js +54 -0
- package/dist/preview.d.ts +31 -0
- package/dist/preview.js +28 -0
- package/dist/process-contract.d.ts +303 -0
- package/dist/process-contract.js +118 -0
- package/dist/publish-skill.d.ts +86 -0
- package/dist/publish-skill.js +116 -0
- package/dist/publish.d.ts +294 -0
- package/dist/publish.js +1090 -0
- package/dist/scripts/scan-skills-registry.d.ts +24 -0
- package/dist/scripts/scan-skills-registry.js +207 -0
- package/dist/seed-import/curated-batch.d.ts +36 -0
- package/dist/seed-import/curated-batch.js +103 -0
- package/dist/seed-import/episode-execute.d.ts +50 -0
- package/dist/seed-import/episode-execute.js +235 -0
- package/dist/seed-import/episode-fetch.d.ts +119 -0
- package/dist/seed-import/episode-fetch.js +137 -0
- package/dist/seed-import/episode-plan.d.ts +11 -0
- package/dist/seed-import/episode-plan.js +27 -0
- package/dist/seed-import/episode-report.d.ts +39 -0
- package/dist/seed-import/episode-report.js +36 -0
- package/dist/seed-import/execute.d.ts +61 -0
- package/dist/seed-import/execute.js +245 -0
- package/dist/seed-import/fetch.d.ts +62 -0
- package/dist/seed-import/fetch.js +82 -0
- package/dist/seed-import/licence.d.ts +17 -0
- package/dist/seed-import/licence.js +36 -0
- package/dist/seed-import/plan.d.ts +10 -0
- package/dist/seed-import/plan.js +21 -0
- package/dist/seed-import/report.d.ts +34 -0
- package/dist/seed-import/report.js +31 -0
- package/dist/seed-import/state.d.ts +72 -0
- package/dist/seed-import/state.js +120 -0
- package/dist/signal.d.ts +36 -0
- package/dist/signal.js +48 -0
- package/dist/signing.d.ts +8 -0
- package/dist/signing.js +21 -0
- package/dist/skill-package.d.ts +70 -0
- package/dist/skill-package.js +141 -0
- package/dist/skill.d.ts +21 -0
- package/dist/skill.js +77 -0
- package/dist/snapshot-transcript.d.ts +54 -0
- package/dist/snapshot-transcript.js +117 -0
- package/dist/three-arm-run.d.ts +27 -0
- package/dist/three-arm-run.js +41 -0
- package/package.json +60 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ContributionCandidateV1Schema, EpisodeV1WriteSchema, JINN_PLUGIN_CONTRACT_VERSION, PickupConfigSchema, SessionActivityFactsWriteSchema, } from '@jinn-network/plugin';
|
|
3
|
+
export const PROCESS_CONTRACT_VERSION = JINN_PLUGIN_CONTRACT_VERSION;
|
|
4
|
+
export const PROCESS_STATUSES = ['ok', 'degraded', 'unavailable'];
|
|
5
|
+
const SessionMetaSchema = z.strictObject({
|
|
6
|
+
sessionId: z.string().min(1).max(128),
|
|
7
|
+
taskSummary: z.string().min(1),
|
|
8
|
+
distributionTags: z.array(z.string().min(1)).optional(),
|
|
9
|
+
harness: z.strictObject({ name: z.string().min(1), version: z.string().min(1) }),
|
|
10
|
+
model: z.string().min(1),
|
|
11
|
+
tools: z.array(z.string().min(1)),
|
|
12
|
+
skillsLoadout: z.array(z.string().min(1)).optional(),
|
|
13
|
+
pickup: PickupConfigSchema.optional(),
|
|
14
|
+
/** Fed into `deriveSearchTerms`'s repo-slug bonus (rescope §3.3). Additive — absent is fine. */
|
|
15
|
+
repositorySlug: z.string().min(1).optional(),
|
|
16
|
+
kind: z.enum(['user', 'host-internal']).optional(),
|
|
17
|
+
parentSessionId: z.string().min(1).max(128).optional(),
|
|
18
|
+
});
|
|
19
|
+
export const SessionPickupRequestV1Schema = z.strictObject({
|
|
20
|
+
contractVersion: z.literal(PROCESS_CONTRACT_VERSION),
|
|
21
|
+
meta: SessionMetaSchema,
|
|
22
|
+
firstMessage: z.string(),
|
|
23
|
+
});
|
|
24
|
+
export const SessionEndRequestV1Schema = z.strictObject({
|
|
25
|
+
contractVersion: z.literal(PROCESS_CONTRACT_VERSION),
|
|
26
|
+
episode: EpisodeV1WriteSchema,
|
|
27
|
+
activity: SessionActivityFactsWriteSchema,
|
|
28
|
+
eligibilityInputs: z.strictObject({
|
|
29
|
+
publicRepo: z.boolean().optional(),
|
|
30
|
+
acceptedDiff: z.boolean().optional(),
|
|
31
|
+
}),
|
|
32
|
+
contributionCandidate: ContributionCandidateV1Schema.optional(),
|
|
33
|
+
contributionVetoed: z.boolean().optional(),
|
|
34
|
+
});
|
|
35
|
+
export function envelope(status, value, reason) {
|
|
36
|
+
return {
|
|
37
|
+
contractVersion: PROCESS_CONTRACT_VERSION,
|
|
38
|
+
status,
|
|
39
|
+
...(value !== undefined ? { value } : {}),
|
|
40
|
+
...(reason !== undefined ? { reason } : {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function sessionEndEnvelope(result) {
|
|
44
|
+
if (result.persistence.status === 'unavailable') {
|
|
45
|
+
return envelope('unavailable', result, result.persistence.reason);
|
|
46
|
+
}
|
|
47
|
+
const reasons = [];
|
|
48
|
+
if (result.persistence.status === 'degraded')
|
|
49
|
+
reasons.push(result.persistence.reason);
|
|
50
|
+
if (result.contribution?.status === 'degraded' || result.contribution?.status === 'unavailable') {
|
|
51
|
+
reasons.push(result.contribution.reason);
|
|
52
|
+
}
|
|
53
|
+
return envelope(reasons.length > 0 ? 'degraded' : 'ok', result, reasons[0]);
|
|
54
|
+
}
|
|
55
|
+
/** Track pickup port health without repeating requests merely to classify output. */
|
|
56
|
+
export function trackingCorpus(deps) {
|
|
57
|
+
let status = 'ok';
|
|
58
|
+
let reason;
|
|
59
|
+
const observe = (result) => {
|
|
60
|
+
if (result.status === 'unavailable' || (result.status === 'degraded' && status === 'ok')) {
|
|
61
|
+
status = result.status;
|
|
62
|
+
reason = result.reason;
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
deps: {
|
|
68
|
+
...deps,
|
|
69
|
+
corpus: {
|
|
70
|
+
async search(query) { return observe(await deps.corpus.search(query)); },
|
|
71
|
+
async get(ref) { return observe(await deps.corpus.get(ref)); },
|
|
72
|
+
},
|
|
73
|
+
skills: {
|
|
74
|
+
async install(ref) { return observe(await deps.skills.install(ref)); },
|
|
75
|
+
async list() { return observe(await deps.skills.list()); },
|
|
76
|
+
async uninstall(ref) { return observe(await deps.skills.uninstall(ref)); },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
status: () => status,
|
|
80
|
+
reason: () => reason,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Preserve useful pickup evidence when one candidate-local corpus read is
|
|
85
|
+
* unavailable. A wholly unavailable corpus with no packets remains
|
|
86
|
+
* `unavailable`; partial success is `degraded` so hosts still consume the
|
|
87
|
+
* returned packets. Core-caught promise rejections also surface through the
|
|
88
|
+
* result's degraded reason even when the tracking wrapper observed no
|
|
89
|
+
* PortResult.
|
|
90
|
+
*/
|
|
91
|
+
export function sessionPickupEnvelope(result, observedStatus, observedReason) {
|
|
92
|
+
const reason = result.degraded ?? observedReason;
|
|
93
|
+
if (observedStatus === 'unavailable' && result.packets.length === 0) {
|
|
94
|
+
return envelope('unavailable', result, reason);
|
|
95
|
+
}
|
|
96
|
+
const status = observedStatus === 'ok' && reason === undefined ? 'ok' : 'degraded';
|
|
97
|
+
return envelope(status, result, reason);
|
|
98
|
+
}
|
|
99
|
+
function displayTimestamp(value) {
|
|
100
|
+
if (value === undefined)
|
|
101
|
+
return null;
|
|
102
|
+
const match = /^\d{4}-(\d{2})-(\d{2})T(\d{2}):(\d{2})/u.exec(value);
|
|
103
|
+
return match ? `${match[1]}-${match[2]} ${match[3]}:${match[4]}` : value;
|
|
104
|
+
}
|
|
105
|
+
/** Persistence-safe terminal projection. Local IDs and candidate evidence do not cross it. */
|
|
106
|
+
export function contributionLedgerRow(entry) {
|
|
107
|
+
const published = entry.status === 'published';
|
|
108
|
+
const repository = entry.repositorySlug ?? 'repository unavailable';
|
|
109
|
+
const commit = entry.baseCommit ? entry.baseCommit.slice(0, 8) : 'commit unavailable';
|
|
110
|
+
return {
|
|
111
|
+
time: displayTimestamp(entry.createdAt),
|
|
112
|
+
task: `${repository} @ ${commit}`,
|
|
113
|
+
env: published ? entry.mintRef ?? null : null,
|
|
114
|
+
anchor: published ? entry.publicationRef ?? null : null,
|
|
115
|
+
tier: entry.verifiabilityTier ?? null,
|
|
116
|
+
state: entry.status,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publish a layer-2 `jinn.skill.v1` package as a corpus record
|
|
3
|
+
* (spec/2026-07-06-distillation-v1.md §5, reconciled 2026-07-07 with the
|
|
4
|
+
* shipped #1394 substrate).
|
|
5
|
+
*
|
|
6
|
+
* This is the **distilled-skill producer** #1394 anticipated but did not ship:
|
|
7
|
+
* the artifact payload is the canonical `SkillArtifactV1`
|
|
8
|
+
* (client/src/types/skill-artifact.ts) with `provenance.kind: 'distilled'` and
|
|
9
|
+
* the DR-2026-07-06 distill fields (`skillKind`, `distillPromptSha256`,
|
|
10
|
+
* `distribution`, `verifiabilityTier`). The stored `skill.skillMd` is rendered
|
|
11
|
+
* WITHOUT the `metadata.jinn` frontmatter block — the structured provenance
|
|
12
|
+
* object is canonical; embedding a second copy would drift (use
|
|
13
|
+
* `buildSkillMarkdown(pkg)` in EXPORT mode to write a standalone SKILL.md).
|
|
14
|
+
*
|
|
15
|
+
* A distilled skill has no trace of its own (its evidence is the referenced
|
|
16
|
+
* layer-1 envelopes in `provenance.sourceEnvelopeCids`), so this publishes a
|
|
17
|
+
* **skill-only wrapper** — unlike the seed path, which attaches the skill
|
|
18
|
+
* alongside the seeded trace via `publish(pending, deps, { skill })`.
|
|
19
|
+
*
|
|
20
|
+
* The wrapper envelope is discriminated as `solverType: 'distilled-skill'`.
|
|
21
|
+
* `role` stays `'capture'` — it is a closed enum (`CanonicalRoleSchema`), so a
|
|
22
|
+
* `'knowledge'` role is unavailable without amending the execution-envelope
|
|
23
|
+
* schema (out of scope). The anchor uses a distinct `skill:<cid>` metadata key
|
|
24
|
+
* so a skill payload is not routed into `captureEnvelopeMeta` enrichment
|
|
25
|
+
* (which requires a trace-envelope artifact).
|
|
26
|
+
*
|
|
27
|
+
* NOTE (producer wiring deferred to Plan A5 — issue #1439): the `skill:<cid>`
|
|
28
|
+
* key requested below is honored by the injected `anchorEnvelope` dep and by
|
|
29
|
+
* the indexer's `parseEnvelopeKey` (which registers `skill` as a fourth kind),
|
|
30
|
+
* but the shipped production anchor wiring (`publishContentV2` → `buildMetadataKey`,
|
|
31
|
+
* whose `ContentKind` union is closed to `envelope|evaluation|capture`) does
|
|
32
|
+
* NOT yet emit it — so a live publish currently anchors under
|
|
33
|
+
* `capture:<cid>`. Extending `ContentKind` to `skill` is the A5 round-trip
|
|
34
|
+
* prerequisite (#1439). Until then the distinct-key / anti-enrichment guarantee
|
|
35
|
+
* holds in tests and in the indexer, not on the live anchor path.
|
|
36
|
+
*/
|
|
37
|
+
import type { CaptureEnvelopeAnchorInput, CaptureEnvelopeAnchorResult, CapturePublishedBlob } from './execution-publish.js';
|
|
38
|
+
import type { SignedEnvelope, UnsignedEnvelope } from '@jinn-network/core';
|
|
39
|
+
import { type SkillArtifactV1 } from '@jinn-network/core';
|
|
40
|
+
import { type SkillPackage } from './skill-package.js';
|
|
41
|
+
export declare const SKILL_SOLVER_TYPE: "distilled-skill";
|
|
42
|
+
export interface SkillPublishDeps {
|
|
43
|
+
participant: {
|
|
44
|
+
safeAddress: `0x${string}`;
|
|
45
|
+
agentEoa: `0x${string}`;
|
|
46
|
+
};
|
|
47
|
+
signer: {
|
|
48
|
+
address: `0x${string}`;
|
|
49
|
+
privateKey?: `0x${string}`;
|
|
50
|
+
};
|
|
51
|
+
clientGitSha: string;
|
|
52
|
+
/** Upload the skill artifact payload (a SkillArtifactV1) as a blob. */
|
|
53
|
+
publishArtifact: (input: {
|
|
54
|
+
artifactType: string;
|
|
55
|
+
payload: unknown;
|
|
56
|
+
}) => Promise<CapturePublishedBlob>;
|
|
57
|
+
/** Upload the signed wrapper envelope; its CID is the corpus ref. */
|
|
58
|
+
publishEnvelope: (envelope: SignedEnvelope) => Promise<CapturePublishedBlob>;
|
|
59
|
+
/** Anchor the envelope on-chain (ERC-8004). */
|
|
60
|
+
anchorEnvelope: (input: CaptureEnvelopeAnchorInput) => Promise<CaptureEnvelopeAnchorResult>;
|
|
61
|
+
/** Custom signer (e.g. remote key); default signs with signer.privateKey. */
|
|
62
|
+
signEnvelope?: (unsigned: UnsignedEnvelope) => Promise<SignedEnvelope['signature']>;
|
|
63
|
+
/** Access endpoint recorded on the artifact when the blob has none. */
|
|
64
|
+
defaultArtifactEndpoint?: string;
|
|
65
|
+
now?: () => Date;
|
|
66
|
+
}
|
|
67
|
+
export interface PublishSkillResult {
|
|
68
|
+
envelopeRef: string;
|
|
69
|
+
anchorTx: `0x${string}` | null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Supersede lineage a publish may stamp on a skill record (issue #1462).
|
|
73
|
+
* Lifecycle is *content* — not an injected collaborator — so it rides an
|
|
74
|
+
* optional param, preserving the deps/content seam and keeping every existing
|
|
75
|
+
* 2-arg call site working. Honored at read time only when the successor's
|
|
76
|
+
* operator matches the superseded record's operator (client/.../consume.ts).
|
|
77
|
+
*/
|
|
78
|
+
export interface SkillLifecycle {
|
|
79
|
+
/** Envelope/manifest CID of the prior record this one replaces. */
|
|
80
|
+
supersedes?: string;
|
|
81
|
+
/** Pure retirement marker: retires `supersedes` with no replacement. */
|
|
82
|
+
deprecates?: boolean;
|
|
83
|
+
}
|
|
84
|
+
/** Map the distiller-side SkillPackage onto the canonical SkillArtifactV1. */
|
|
85
|
+
export declare function toSkillArtifactV1(pkg: SkillPackage, operatorSafeAddress: `0x${string}`, lifecycle?: SkillLifecycle): SkillArtifactV1;
|
|
86
|
+
export declare function publishSkill(pkg: SkillPackage, deps: SkillPublishDeps, lifecycle?: SkillLifecycle): Promise<PublishSkillResult>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { buildUnsignedCaptureEnvelope, sha256Hex, } from './execution-publish.js';
|
|
2
|
+
import { SignedEnvelopeSchema } from '@jinn-network/core';
|
|
3
|
+
import { SKILL_ARTIFACT_TYPE, SkillArtifactV1Schema, } from '@jinn-network/core';
|
|
4
|
+
import { signCanonical } from './signing.js';
|
|
5
|
+
import { canonicalJson } from '@jinn-network/core';
|
|
6
|
+
import { EMPTY_BUNDLE_SHA256 } from '@jinn-network/core/trajectory';
|
|
7
|
+
import { buildSkillMarkdown } from './skill-package.js';
|
|
8
|
+
const DONATION_ARTIFACT_ENCODING = 'jinn.artifact.donation.v1';
|
|
9
|
+
const DEFAULT_PRICE_USDC = '0';
|
|
10
|
+
export const SKILL_SOLVER_TYPE = 'distilled-skill';
|
|
11
|
+
/** Map the distiller-side SkillPackage onto the canonical SkillArtifactV1. */
|
|
12
|
+
export function toSkillArtifactV1(pkg, operatorSafeAddress, lifecycle) {
|
|
13
|
+
return SkillArtifactV1Schema.parse({
|
|
14
|
+
schemaVersion: SKILL_ARTIFACT_TYPE,
|
|
15
|
+
skill: {
|
|
16
|
+
name: pkg.name,
|
|
17
|
+
description: pkg.description,
|
|
18
|
+
// Canonical stored form: provenance lives ONLY in the structured object
|
|
19
|
+
// below — render the SKILL.md without the metadata.jinn block.
|
|
20
|
+
skillMd: buildSkillMarkdown(pkg, { embedProvenance: false }),
|
|
21
|
+
},
|
|
22
|
+
files: [],
|
|
23
|
+
provenance: {
|
|
24
|
+
kind: 'distilled',
|
|
25
|
+
sourceEnvelopeCids: pkg.jinn.provenance,
|
|
26
|
+
operator: { safeAddress: operatorSafeAddress },
|
|
27
|
+
solverType: SKILL_SOLVER_TYPE,
|
|
28
|
+
...(pkg.jinn.skillKind !== undefined ? { skillKind: pkg.jinn.skillKind } : {}),
|
|
29
|
+
...(pkg.jinn.distillPromptSha256 !== undefined
|
|
30
|
+
? { distillPromptSha256: pkg.jinn.distillPromptSha256 }
|
|
31
|
+
: {}),
|
|
32
|
+
distribution: pkg.jinn.distribution,
|
|
33
|
+
verifiabilityTier: pkg.jinn.verifiabilityTier,
|
|
34
|
+
// Auditability (§5, v0.5): the distilling model + compression-ratio estimates.
|
|
35
|
+
...(pkg.jinn.distillModel !== undefined ? { distillModel: pkg.jinn.distillModel } : {}),
|
|
36
|
+
...(pkg.jinn.evidenceTokens !== undefined ? { evidenceTokens: pkg.jinn.evidenceTokens } : {}),
|
|
37
|
+
...(pkg.jinn.skillTokens !== undefined ? { skillTokens: pkg.jinn.skillTokens } : {}),
|
|
38
|
+
// Supersede lineage (#1462).
|
|
39
|
+
...(lifecycle?.supersedes !== undefined ? { supersedes: lifecycle.supersedes } : {}),
|
|
40
|
+
...(lifecycle?.deprecates !== undefined ? { deprecates: lifecycle.deprecates } : {}),
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export async function publishSkill(pkg, deps, lifecycle) {
|
|
45
|
+
const now = deps.now?.() ?? new Date();
|
|
46
|
+
const skillArtifact = toSkillArtifactV1(pkg, deps.participant.safeAddress, lifecycle);
|
|
47
|
+
const blob = await deps.publishArtifact({
|
|
48
|
+
artifactType: SKILL_ARTIFACT_TYPE,
|
|
49
|
+
payload: skillArtifact,
|
|
50
|
+
});
|
|
51
|
+
// canonicalJson matches the live uploader's serialization (publish-live.ts)
|
|
52
|
+
// and the sibling publish() fallback — JSON.stringify would hash different
|
|
53
|
+
// bytes than were uploaded (key order / whitespace) and break verification.
|
|
54
|
+
const sha256 = blob.sha256 ?? sha256Hex(canonicalJson(skillArtifact));
|
|
55
|
+
const endpoint = blob.endpoint ?? deps.defaultArtifactEndpoint;
|
|
56
|
+
if (!endpoint) {
|
|
57
|
+
throw new Error(`published skill artifact ${blob.cid} has no access endpoint (set defaultArtifactEndpoint)`);
|
|
58
|
+
}
|
|
59
|
+
const artifact = {
|
|
60
|
+
artifactType: SKILL_ARTIFACT_TYPE,
|
|
61
|
+
sha256,
|
|
62
|
+
metadata: { description: `Distilled skill: ${pkg.name}` },
|
|
63
|
+
access: { endpoint, priceUsdc: blob.priceUsdc ?? DEFAULT_PRICE_USDC },
|
|
64
|
+
sources: [{ kind: 'ipfs', cid: blob.cid, sha256, encoding: DONATION_ARTIFACT_ENCODING }],
|
|
65
|
+
};
|
|
66
|
+
// A skill carries no capture spans; synthesise the minimal PendingCaptureRow
|
|
67
|
+
// the envelope builder reads for provenance. The resulting wrapper carries a
|
|
68
|
+
// cosmetic `payload.capture` block (sessionId `skill:<name>`, spanCount 0) —
|
|
69
|
+
// an artifact of reusing the capture builder; consumers discriminate on
|
|
70
|
+
// solverType/artifactType, never on that block.
|
|
71
|
+
//
|
|
72
|
+
// Ledger note: unlike publish(), no contribution-ledger entry is written
|
|
73
|
+
// here — a distilled skill derives from ALREADY-consented, already-ledgered
|
|
74
|
+
// evidence (its sourceEnvelopeCids); there is no fresh consent event to
|
|
75
|
+
// record. Revisit if skills ever gain their own consent surface.
|
|
76
|
+
const captureRow = {
|
|
77
|
+
sessionId: `skill:${pkg.name}`,
|
|
78
|
+
capturedAt: now.toISOString(),
|
|
79
|
+
originatingTool: { name: 'jinn-distiller', version: '0.1.0' },
|
|
80
|
+
capturePath: 'A',
|
|
81
|
+
spanCount: 0,
|
|
82
|
+
redactedSpanCount: 0,
|
|
83
|
+
durationMs: 0,
|
|
84
|
+
};
|
|
85
|
+
const unsigned = buildUnsignedCaptureEnvelope({
|
|
86
|
+
capture: captureRow,
|
|
87
|
+
now,
|
|
88
|
+
participant: deps.participant,
|
|
89
|
+
signerAddress: deps.signer.address,
|
|
90
|
+
clientGitSha: deps.clientGitSha,
|
|
91
|
+
artifacts: [artifact],
|
|
92
|
+
harnessBundleSha: EMPTY_BUNDLE_SHA256,
|
|
93
|
+
solverType: SKILL_SOLVER_TYPE,
|
|
94
|
+
role: 'capture',
|
|
95
|
+
});
|
|
96
|
+
const signature = deps.signEnvelope
|
|
97
|
+
? await deps.signEnvelope(unsigned)
|
|
98
|
+
: await signWithPrivateKey(unsigned, deps);
|
|
99
|
+
const envelope = SignedEnvelopeSchema.parse({ ...unsigned, signature });
|
|
100
|
+
const envelopeBlob = await deps.publishEnvelope(envelope);
|
|
101
|
+
const envelopeRef = envelopeBlob.cid;
|
|
102
|
+
const anchor = await deps.anchorEnvelope({
|
|
103
|
+
metadataKey: `skill:${envelopeRef}`,
|
|
104
|
+
envelopeCid: envelopeRef,
|
|
105
|
+
envelopeHash: envelope.signature.hash,
|
|
106
|
+
envelope,
|
|
107
|
+
});
|
|
108
|
+
return { envelopeRef, anchorTx: anchor.txHash ?? null };
|
|
109
|
+
}
|
|
110
|
+
async function signWithPrivateKey(unsigned, deps) {
|
|
111
|
+
if (!deps.signer.privateKey) {
|
|
112
|
+
throw new Error('publishSkill() requires signer.privateKey or a signEnvelope dependency');
|
|
113
|
+
}
|
|
114
|
+
const signed = await signCanonical(unsigned, deps.signer.privateKey, deps.signer.address);
|
|
115
|
+
return { algo: 'secp256k1', signer: signed.signer, hash: signed.hash, sig: signed.sig };
|
|
116
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness-layer publish path (plan Task 4, issue #1311).
|
|
3
|
+
*
|
|
4
|
+
* `publish(pending)` performs the consent conversion (calling publish after
|
|
5
|
+
* preview IS the consent action), projects the scrubbed pending capture into
|
|
6
|
+
* a canonical EpisodeV1 artifact, and wraps it in the same signed
|
|
7
|
+
* `jinn.execution.v1` envelope the daemon capture path publishes (reusing
|
|
8
|
+
* `buildUnsignedCaptureEnvelope`), and anchors it via ERC-8004. Publishing
|
|
9
|
+
* through that wrapper is what makes the returned ref resolvable with
|
|
10
|
+
* `jinn-layer corpus get <ref>` — the corpus reads signed envelopes, so the
|
|
11
|
+
* contribute → anchored → discoverable round trip closes.
|
|
12
|
+
*
|
|
13
|
+
* No-bypass: only a `PendingEnvelope` produced by `capture()` (which ran the
|
|
14
|
+
* mandatory scrub, fail-closed) can be published — enforced by the parameter
|
|
15
|
+
* type and a runtime kind gate. There is no code path from raw spans to
|
|
16
|
+
* `publish()`.
|
|
17
|
+
*
|
|
18
|
+
* Per-task veto: `opts.veto` publishes nothing and records a ledger entry
|
|
19
|
+
* marked `vetoed (local only)` — the operator's receipt that the trace
|
|
20
|
+
* existed and was withheld.
|
|
21
|
+
*
|
|
22
|
+
* v0 anchors directly per publish (testnet); the batch relayer is v0.5 work
|
|
23
|
+
* alongside earning.
|
|
24
|
+
*/
|
|
25
|
+
import type { CaptureEnvelopeAnchorInput, CaptureEnvelopeAnchorResult, CapturePublishedBlob } from './execution-publish.js';
|
|
26
|
+
import type { SignedEnvelope, UnsignedEnvelope } from '@jinn-network/core';
|
|
27
|
+
import { type SkillArtifactV1 } from '@jinn-network/core';
|
|
28
|
+
import { type EpisodeV1Write } from '@jinn-network/plugin';
|
|
29
|
+
import { type ManifestV0, type ManifestPayload } from '@jinn-network/core';
|
|
30
|
+
import { type PendingEnvelope } from './capture.js';
|
|
31
|
+
import { type TraceEnvelopeV0 } from './envelope.js';
|
|
32
|
+
import type { LedgerStore } from './ledger.js';
|
|
33
|
+
/** Read-compat only: no new capture publication emits this payload. */
|
|
34
|
+
export declare const TRACE_ENVELOPE_ARTIFACT_TYPE: "jinn.trace-envelope.v0";
|
|
35
|
+
/** Canonical local/public evidence payload emitted by new publications. */
|
|
36
|
+
export declare const EPISODE_ARTIFACT_TYPE: "jinn.episode.v1";
|
|
37
|
+
export interface HarnessPublishDeps {
|
|
38
|
+
participant: {
|
|
39
|
+
safeAddress: `0x${string}`;
|
|
40
|
+
agentEoa: `0x${string}`;
|
|
41
|
+
};
|
|
42
|
+
signer: {
|
|
43
|
+
address: `0x${string}`;
|
|
44
|
+
privateKey?: `0x${string}`;
|
|
45
|
+
};
|
|
46
|
+
clientGitSha: string;
|
|
47
|
+
/** Upload an evidence or companion payload as an artifact blob. */
|
|
48
|
+
publishArtifact: (input: {
|
|
49
|
+
artifactType: string;
|
|
50
|
+
payload: unknown;
|
|
51
|
+
}) => Promise<CapturePublishedBlob>;
|
|
52
|
+
/** Upload the signed wrapper envelope; its CID is the corpus ref. */
|
|
53
|
+
publishEnvelope: (envelope: SignedEnvelope) => Promise<CapturePublishedBlob>;
|
|
54
|
+
/** Anchor the envelope on-chain (ERC-8004). */
|
|
55
|
+
anchorEnvelope: (input: CaptureEnvelopeAnchorInput) => Promise<CaptureEnvelopeAnchorResult>;
|
|
56
|
+
/** Custom signer (e.g. remote key); default signs with signer.privateKey. */
|
|
57
|
+
signEnvelope?: (unsigned: UnsignedEnvelope) => Promise<SignedEnvelope['signature']>;
|
|
58
|
+
/** The contribution ledger every publish/veto is recorded in. */
|
|
59
|
+
ledger: LedgerStore;
|
|
60
|
+
/** Access endpoint recorded on the artifact when the blob has none. */
|
|
61
|
+
defaultArtifactEndpoint?: string;
|
|
62
|
+
now?: () => Date;
|
|
63
|
+
}
|
|
64
|
+
export interface ManifestAnchorResult {
|
|
65
|
+
txHash: `0x${string}`;
|
|
66
|
+
blockNumber: number | null;
|
|
67
|
+
gasUsed: bigint | null;
|
|
68
|
+
feeWei: bigint | null;
|
|
69
|
+
}
|
|
70
|
+
export type ManifestAnchorReconciliation = ({
|
|
71
|
+
status: 'confirmed';
|
|
72
|
+
} & ManifestAnchorResult) | {
|
|
73
|
+
status: 'reverted';
|
|
74
|
+
txHash: `0x${string}`;
|
|
75
|
+
} | {
|
|
76
|
+
status: 'pending';
|
|
77
|
+
txHash: `0x${string}`;
|
|
78
|
+
};
|
|
79
|
+
export interface ManifestBatchJournalStore {
|
|
80
|
+
loadManifestBatchJournal(batchKey: string): string | null;
|
|
81
|
+
saveManifestBatchJournal(batchKey: string, stateJson: string): void;
|
|
82
|
+
compareAndSwapManifestBatchJournal(batchKey: string, expectedStateJson: string | null, nextStateJson: string): boolean;
|
|
83
|
+
}
|
|
84
|
+
export interface ManifestPublicationScope {
|
|
85
|
+
chainId: number;
|
|
86
|
+
identityRegistryAddress: `0x${string}`;
|
|
87
|
+
agentId: string;
|
|
88
|
+
}
|
|
89
|
+
export interface ManifestAnchorRecord {
|
|
90
|
+
manifestCid: string;
|
|
91
|
+
contentKind: 'manifest';
|
|
92
|
+
metadataKey: string;
|
|
93
|
+
payloadHex: `0x${string}`;
|
|
94
|
+
txHash: `0x${string}`;
|
|
95
|
+
blockNumber: number | null;
|
|
96
|
+
gasUsed: bigint | null;
|
|
97
|
+
feeWei: bigint | null;
|
|
98
|
+
anchoredAt: number;
|
|
99
|
+
}
|
|
100
|
+
export interface PerRecordControlAnchorRecord {
|
|
101
|
+
envelopeRef: string;
|
|
102
|
+
contentKind: 'capture';
|
|
103
|
+
metadataKey: string;
|
|
104
|
+
payloadHex: `0x${string}`;
|
|
105
|
+
txHash: `0x${string}`;
|
|
106
|
+
blockNumber: number | null;
|
|
107
|
+
gasUsed: bigint;
|
|
108
|
+
feeWei: bigint;
|
|
109
|
+
anchoredAt: number;
|
|
110
|
+
}
|
|
111
|
+
export interface ManifestBatchPublishDeps extends HarnessPublishDeps {
|
|
112
|
+
publishManifestBody(body: ManifestV0): Promise<CapturePublishedBlob>;
|
|
113
|
+
/**
|
|
114
|
+
* Read-only recovery check for a durably journaled upload intent. `absent`
|
|
115
|
+
* is safe only when the backing store can authoritatively prove absence;
|
|
116
|
+
* gateway/read failures must return `unknown`.
|
|
117
|
+
*/
|
|
118
|
+
reconcileManifestBody?(expectedCid: string, bodyBytes: Uint8Array): Promise<{
|
|
119
|
+
status: 'present';
|
|
120
|
+
} | {
|
|
121
|
+
status: 'absent';
|
|
122
|
+
} | {
|
|
123
|
+
status: 'unknown';
|
|
124
|
+
reason?: string;
|
|
125
|
+
}>;
|
|
126
|
+
anchorManifest(input: {
|
|
127
|
+
manifestCid: string;
|
|
128
|
+
payload: ManifestPayload;
|
|
129
|
+
onBroadcast?: (txHash: `0x${string}`) => void;
|
|
130
|
+
}): Promise<ManifestAnchorResult>;
|
|
131
|
+
/** Durable live-only recovery journal. */
|
|
132
|
+
manifestJournal?: ManifestBatchJournalStore;
|
|
133
|
+
/** Exact on-chain identity targeted by a durable recovery journal. */
|
|
134
|
+
manifestPublicationScope?: ManifestPublicationScope;
|
|
135
|
+
/** Read-only reconciliation for a previously journaled transaction. */
|
|
136
|
+
reconcileAnchor?(txHash: `0x${string}`): Promise<ManifestAnchorReconciliation>;
|
|
137
|
+
recordManifestAnchor(input: ManifestAnchorRecord): void;
|
|
138
|
+
/** Required only by the explicit paired gas-measurement control. */
|
|
139
|
+
recordControlAnchor?(input: PerRecordControlAnchorRecord): void;
|
|
140
|
+
log?: (line: string) => void;
|
|
141
|
+
}
|
|
142
|
+
export interface ManifestBatchMemberInput {
|
|
143
|
+
pending: PendingEnvelope;
|
|
144
|
+
polarity?: 'pass' | 'fail';
|
|
145
|
+
instanceId?: string;
|
|
146
|
+
/** Immutable upstream identity used to key durable recovery. */
|
|
147
|
+
sourceId?: string;
|
|
148
|
+
}
|
|
149
|
+
export interface ManifestBatchOptions {
|
|
150
|
+
batchKind: string;
|
|
151
|
+
/** Add one receipt-bound per-record control tx for the first uploaded member. */
|
|
152
|
+
measurePerRecordControl?: boolean;
|
|
153
|
+
}
|
|
154
|
+
export interface PublishedMemberEnvelope {
|
|
155
|
+
envelopeRef: string;
|
|
156
|
+
sha256: string;
|
|
157
|
+
envelope: SignedEnvelope;
|
|
158
|
+
/** Frozen read-compatible projection used by the in-process distillation pipeline. */
|
|
159
|
+
trace: TraceEnvelopeV0;
|
|
160
|
+
/** Canonical payload actually uploaded and signed for new publications. */
|
|
161
|
+
episode: EpisodeV1Write;
|
|
162
|
+
}
|
|
163
|
+
export interface ManifestBatchResult {
|
|
164
|
+
/** Durable recovery identity, present when a journal is active. */
|
|
165
|
+
batchKey?: string;
|
|
166
|
+
manifestCid: string;
|
|
167
|
+
anchorTx: `0x${string}`;
|
|
168
|
+
memberRefs: string[];
|
|
169
|
+
root: `0x${string}`;
|
|
170
|
+
gasUsed: bigint | null;
|
|
171
|
+
feeWei: bigint | null;
|
|
172
|
+
control?: {
|
|
173
|
+
memberRef: string;
|
|
174
|
+
anchorTx: `0x${string}`;
|
|
175
|
+
blockNumber: number | null;
|
|
176
|
+
gasUsed: bigint | null;
|
|
177
|
+
feeWei: bigint | null;
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
export interface ManifestBatchSetResult {
|
|
181
|
+
/** One entry per raw-block-sized manifest, in member order. */
|
|
182
|
+
batches: ManifestBatchResult[];
|
|
183
|
+
/** Every uploaded member ref, including refs in a later failed partition. */
|
|
184
|
+
memberRefs: string[];
|
|
185
|
+
/** Exact frozen member envelopes, including journal-resumed publications. */
|
|
186
|
+
publishedMembers: PublishedMemberEnvelope[];
|
|
187
|
+
}
|
|
188
|
+
export interface PublishOptions {
|
|
189
|
+
/** Per-task veto: record locally, publish nothing. */
|
|
190
|
+
veto?: boolean;
|
|
191
|
+
/**
|
|
192
|
+
* First-class skill artifact (#1394): validated against
|
|
193
|
+
* SkillArtifactV1Schema and published as a second artifact on the SAME
|
|
194
|
+
* wrapper envelope, alongside (never instead of) the canonical episode.
|
|
195
|
+
*/
|
|
196
|
+
skill?: SkillArtifactV1;
|
|
197
|
+
}
|
|
198
|
+
export interface PublishedResult {
|
|
199
|
+
vetoed: false;
|
|
200
|
+
envelopeRef: string;
|
|
201
|
+
anchorTx: `0x${string}` | null;
|
|
202
|
+
}
|
|
203
|
+
export type PublishResult = PublishedResult | {
|
|
204
|
+
vetoed: true;
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* The irreversible publish/anchor succeeded, but the local contribution
|
|
208
|
+
* ledger receipt could not be appended. Ordinary callers receive a failure;
|
|
209
|
+
* recovery-aware seed callers may persist `result` before stopping.
|
|
210
|
+
*/
|
|
211
|
+
export declare class PublishLedgerError extends Error {
|
|
212
|
+
readonly result: PublishedResult;
|
|
213
|
+
readonly ledgerError: unknown;
|
|
214
|
+
readonly name = "PublishLedgerError";
|
|
215
|
+
constructor(result: PublishedResult, ledgerError: unknown);
|
|
216
|
+
}
|
|
217
|
+
export declare class ManifestBatchRecordingError extends Error {
|
|
218
|
+
readonly result: ManifestBatchResult;
|
|
219
|
+
readonly recordingError: unknown;
|
|
220
|
+
readonly batchKey: string | null;
|
|
221
|
+
readonly name = "ManifestBatchRecordingError";
|
|
222
|
+
readonly txHash: `0x${string}` | null;
|
|
223
|
+
constructor(result: ManifestBatchResult, recordingError: unknown, batchKey: string | null);
|
|
224
|
+
readonly memberRefs: string[];
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* A partitioned manifest publish failed. Retain both any completed irreversible
|
|
228
|
+
* anchors and every uploaded member ref so the caller can reconcile without
|
|
229
|
+
* retrying successful work.
|
|
230
|
+
*/
|
|
231
|
+
export declare class ManifestBatchSetError extends Error {
|
|
232
|
+
readonly completed: ManifestBatchResult[];
|
|
233
|
+
readonly failed: unknown;
|
|
234
|
+
readonly memberRefs: string[];
|
|
235
|
+
readonly batchKey: string | null;
|
|
236
|
+
readonly name = "ManifestBatchSetError";
|
|
237
|
+
constructor(completed: ManifestBatchResult[], failed: unknown, memberRefs: string[], batchKey: string | null);
|
|
238
|
+
}
|
|
239
|
+
export declare class ManifestBatchPreparationError extends Error {
|
|
240
|
+
readonly stage: 'member-upload' | 'partition' | 'manifest-upload' | 'manifest-validation' | 'manifest-preparation';
|
|
241
|
+
readonly memberRefs: string[];
|
|
242
|
+
readonly preparationError: unknown;
|
|
243
|
+
readonly batchKey: string | null;
|
|
244
|
+
readonly name = "ManifestBatchPreparationError";
|
|
245
|
+
constructor(stage: 'member-upload' | 'partition' | 'manifest-upload' | 'manifest-validation' | 'manifest-preparation', memberRefs: string[], preparationError: unknown, batchKey: string | null);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* The manifest body is already content-addressed, but the durable anchor
|
|
249
|
+
* state could not advance safely. A callback hash is retained when known;
|
|
250
|
+
* a hashless write-ahead intent deliberately remains closed to retry.
|
|
251
|
+
*/
|
|
252
|
+
export declare class ManifestBatchAnchorError extends Error {
|
|
253
|
+
readonly manifestCid: string;
|
|
254
|
+
readonly memberRefs: string[];
|
|
255
|
+
readonly root: `0x${string}`;
|
|
256
|
+
readonly anchorError: unknown;
|
|
257
|
+
readonly batchKey: string | null;
|
|
258
|
+
readonly name = "ManifestBatchAnchorError";
|
|
259
|
+
readonly txHash: `0x${string}` | null;
|
|
260
|
+
constructor(manifestCid: string, memberRefs: string[], root: `0x${string}`, anchorError: unknown, batchKey: string | null);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Consent conversion: the pre-consent draft becomes a full TraceEnvelopeV0
|
|
264
|
+
* with both consent flags literal `true`. Valid only for capture()-produced
|
|
265
|
+
* pending envelopes (runtime kind gate) and re-validated against the frozen
|
|
266
|
+
* schema on the way out.
|
|
267
|
+
*/
|
|
268
|
+
export declare function toTraceEnvelope(pending: PendingEnvelope): TraceEnvelopeV0;
|
|
269
|
+
/**
|
|
270
|
+
* Project the scrubbed pending capture into the same canonical contract the
|
|
271
|
+
* local evidence store uses. Consent remains the publish action and is not a
|
|
272
|
+
* second payload field; trace-envelope.v0 remains available only to legacy
|
|
273
|
+
* readers.
|
|
274
|
+
*/
|
|
275
|
+
export declare function toPublishedEpisode(pending: PendingEnvelope): EpisodeV1Write;
|
|
276
|
+
/**
|
|
277
|
+
* Consent, upload, and sign one pending member without anchoring or writing the
|
|
278
|
+
* contribution ledger. Both the per-record and manifest paths share this exact
|
|
279
|
+
* byte-producing step so batch mode cannot drift into a second envelope format.
|
|
280
|
+
*/
|
|
281
|
+
export declare function publishMemberEnvelope(pending: PendingEnvelope, deps: HarnessPublishDeps, opts?: Pick<PublishOptions, 'skill'> & {
|
|
282
|
+
now?: Date;
|
|
283
|
+
}): Promise<PublishedMemberEnvelope>;
|
|
284
|
+
/**
|
|
285
|
+
* Publish a pending envelope: consent conversion → artifact upload → signed
|
|
286
|
+
* wrapper envelope → ERC-8004 anchor → ledger entry. Returns the corpus ref
|
|
287
|
+
* (the wrapper envelope's CID) and the anchor tx.
|
|
288
|
+
*/
|
|
289
|
+
export declare function publish(pending: PendingEnvelope, deps: HarnessPublishDeps, opts?: PublishOptions): Promise<PublishResult>;
|
|
290
|
+
/**
|
|
291
|
+
* Upload substrate members once, partition their manifest bodies at Kubo's
|
|
292
|
+
* raw-block boundary, and anchor exactly once per resulting manifest.
|
|
293
|
+
*/
|
|
294
|
+
export declare function publishManifestBatch(members: ManifestBatchMemberInput[], deps: ManifestBatchPublishDeps, opts: ManifestBatchOptions): Promise<ManifestBatchSetResult>;
|