Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
"./dist/rfc64/inventory-v1/statements.js": "./dist/rfc64/inventory-v1/statements.js",
"./dist/rfc64/public-catalog-activation-config-v1.js": "./dist/rfc64/public-catalog-activation-config-v1.js",
"./dist/rfc64/swm-author-inventory-producer-v1.js": "./dist/rfc64/swm-author-inventory-producer-v1.js",
"./dist/system-records/agent-profile-producer-v1.js": "./dist/system-records/agent-profile-producer-v1.js",
"./dist/system-records/artifact-v1.js": "./dist/system-records/artifact-v1.js",
"./dist/system-records/in-memory-agent-profile-publication-store-v1.js": "./dist/system-records/in-memory-agent-profile-publication-store-v1.js",
"./dist/system-records/provider-v1.js": "./dist/system-records/provider-v1.js",
"./dist/system-records/transport-v1.js": "./dist/system-records/transport-v1.js",
"./dist/system-records/*": null,
"./dist/rfc64/control-envelope-signer-v1.js": null,
"./dist/rfc64/swm-inventory-shadow-runtime-v1.js": null,
"./dist/rfc64/control-object-store-v1-internal.js": null,
Expand Down
60 changes: 60 additions & 0 deletions packages/agent/scripts/test-package-root.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const legacyAgent = await import('@origintrail-official/dkg-agent/dist/dkg-agent
const publicCatalogActivation = await import(
'@origintrail-official/dkg-agent/rfc64/public-catalog-activation-config-v1'
);
const agentProfileProducer = await import(
'@origintrail-official/dkg-agent/dist/system-records/agent-profile-producer-v1.js'
);
const require = createRequire(import.meta.url);
const packageManifest = require('@origintrail-official/dkg-agent/package.json');
const expectedRfc64PolicyCells = [
Expand All @@ -16,6 +19,63 @@ const expectedRfc64PolicyCells = [
'private-open',
'private-curated',
];
const internalAgentProfilePhaseSpecifiers = [
'@origintrail-official/dkg-agent/dist/system-records/agent-profile-producer-api-v1.js',
'@origintrail-official/dkg-agent/dist/system-records/agent-profile-producer-artifacts-v1-internal.js',
'@origintrail-official/dkg-agent/dist/system-records/agent-profile-producer-preparation-v1.js',
'@origintrail-official/dkg-agent/dist/system-records/agent-profile-producer-signing-v1.js',
'@origintrail-official/dkg-agent/dist/system-records/agent-profile-producer-inventory-v1.js',
'@origintrail-official/dkg-agent/dist/system-records/agent-profile-producer-commit-v1.js',
];
const publicSystemRecordSpecifiers = [
'@origintrail-official/dkg-agent/dist/system-records/agent-profile-producer-v1.js',
'@origintrail-official/dkg-agent/dist/system-records/artifact-v1.js',
'@origintrail-official/dkg-agent/dist/system-records/in-memory-agent-profile-publication-store-v1.js',
'@origintrail-official/dkg-agent/dist/system-records/provider-v1.js',
'@origintrail-official/dkg-agent/dist/system-records/transport-v1.js',
];

for (const specifier of internalAgentProfilePhaseSpecifiers) {
let resolved = false;
try {
await import(specifier);
resolved = true;
} catch (error) {
if (error?.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw error;
}
if (resolved) throw new Error(`published package exposed internal phase ${specifier}`);
}

for (const specifier of publicSystemRecordSpecifiers) {
const subpath = `.${specifier.slice('@origintrail-official/dkg-agent'.length)}`;
if (packageManifest.exports[subpath] !== subpath) {
throw new Error(`historical System Record module is not explicitly public: ${specifier}`);
}
await import(specifier);
}
if (packageManifest.exports['./dist/system-records/*'] !== null) {
throw new Error('unclassified System Record deep imports are not blocked by default');
}

const profileArtifact = (objectKind, byte) => Object.freeze({
objectKind,
objectDigest: `0x${byte.toString(16).padStart(64, '0')}`,
canonicalBytes: Uint8Array.of(byte),
});
const flattenedProfileArtifacts = agentProfileProducer
.flattenAgentProfileProducerPublicationArtifactsV1({
head: profileArtifact('agent-profile-head', 1),
bundle: profileArtifact('profile-bundle', 2),
ownedSubjectTable: profileArtifact('owned-subject-table', 3),
inventoryObjects: Object.freeze([
profileArtifact('inventory-internal', 4),
profileArtifact('inventory-leaf', 5),
]),
});
if (flattenedProfileArtifacts.map((artifact) => artifact.objectKind).join(',')
!== 'agent-profile-head,profile-bundle,owned-subject-table,inventory-internal,inventory-leaf') {
throw new Error('historical producer artifact-flattening export changed behavior');
}

if (
typeof root.DKGAgent !== 'function'
Expand Down
86 changes: 41 additions & 45 deletions packages/agent/src/profile.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { Quad } from '@origintrail-official/dkg-storage';
import {
DKG_ONTOLOGY,
SYSTEM_CONTEXT_GRAPHS,
isPublicLikeAddress,
} from '@origintrail-official/dkg-core';
import {
AGENT_PROFILE_SCHEMA_TERMS_V1,
agentProfileIdentityFactsV1,
deriveAgentProfileOwnedSubjectV1,
type AgentProfileIdentityFactsV1,
type AgentProfileProjectionQuadV1,
} from '@origintrail-official/dkg-core/system-record-v1';
Expand Down Expand Up @@ -77,12 +78,7 @@ export function collectPublishableMultiaddrs(
return out;
}

const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
const SCHEMA = 'https://schema.org/';
const DKG = 'https://dkg.network/ontology#';
const ERC8004 = 'https://eips.ethereum.org/erc-8004#';
const PROV = 'http://www.w3.org/ns/prov#';
const SKILL = 'https://dkg.origintrail.io/skill#';
const T = AGENT_PROFILE_SCHEMA_TERMS_V1;

export const AGENT_REGISTRY_CONTEXT_GRAPH = SYSTEM_CONTEXT_GRAPHS.AGENTS;
export const AGENT_REGISTRY_GRAPH = `did:dkg:context-graph:${AGENT_REGISTRY_CONTEXT_GRAPH}`;
Expand Down Expand Up @@ -267,24 +263,24 @@ function buildAgentProfileModelV1(config: AgentProfileConfig): AgentProfileModel
facts.push({ subject: s, predicate: p, object: o });

// Type: dkg:Agent + role-specific subclass
q(entity, RDF_TYPE, `${DKG}Agent`);
q(entity, RDF_TYPE, role === 'core' ? `${DKG}CoreNode` : `${DKG}EdgeNode`);
q(entity, T.rdfType, T.dkgAgent);
q(entity, T.rdfType, role === 'core' ? T.dkgCoreNode : T.dkgEdgeNode);

// schema.org metadata
q(entity, `${SCHEMA}name`, `"${config.name}"`);
q(entity, T.schemaName, `"${config.name}"`);
if (config.description) {
q(entity, `${SCHEMA}description`, `"${config.description}"`);
q(entity, T.schemaDescription, `"${config.description}"`);
}

// DKG P2P properties
q(entity, identity.peerId.predicate, identity.peerId.object);
q(entity, `${DKG}nodeRole`, `"${role}"`);
q(entity, T.dkgNodeRole, `"${role}"`);

if (identity.publicKey !== undefined) {
q(entity, identity.publicKey.predicate, identity.publicKey.object);
}
if (config.relayAddress) {
q(entity, `${DKG}relayAddress`, `"${config.relayAddress}"`);
q(entity, T.dkgRelayAddress, `"${config.relayAddress}"`);
}
if (identity.agentAddress !== undefined) {
q(entity, identity.agentAddress.predicate, identity.agentAddress.object);
Expand All @@ -304,76 +300,76 @@ function buildAgentProfileModelV1(config: AgentProfileConfig): AgentProfileModel
// contain quote characters; this guard is purely against
// malformed callers.
if (!ma || ma.includes('"')) continue;
q(entity, `${DKG}multiaddr`, `"${ma}"`);
q(entity, T.dkgMultiaddr, `"${ma}"`);
}
}
q(entity, `${DKG}lastSeen`, `"${profileTimestamp}"`);
q(entity, T.dkgLastSeen, `"${profileTimestamp}"`);
// Encryption keys: prefer the multi-key array; fall back to the deprecated
// singular fields only when the array isn't supplied (legacy callers /
// test fixtures). Retired keys still get published so peers learn their
// wallet-signed revocations and the resolver can prune them.
if (config.encryptionKeys && config.encryptionKeys.length > 0) {
for (const key of config.encryptionKeys) {
q(entity, `${DKG}publicEncryptionKey`, `"${key.publicEncryptionKey}"`);
q(entity, `${DKG}encryptionKeyAlgorithm`, `"${key.encryptionKeyAlgorithm}"`);
q(entity, `${DKG}encryptionKeyProof`, `"${key.encryptionKeyProof}"`);
q(entity, T.dkgPublicEncryptionKey, `"${key.publicEncryptionKey}"`);
q(entity, T.dkgEncryptionKeyAlgorithm, `"${key.encryptionKeyAlgorithm}"`);
q(entity, T.dkgEncryptionKeyProof, `"${key.encryptionKeyProof}"`);
if (key.revokedAt && key.revocationProof) {
q(key.encryptionKeyId, `${DKG}revokedAt`, `"${key.revokedAt}"`);
q(key.encryptionKeyId, `${DKG}revokedBy`, entity);
q(key.encryptionKeyId, `${DKG}encryptionKeyRevocationProof`, `"${key.revocationProof}"`);
q(key.encryptionKeyId, T.dkgRevokedAt, `"${key.revokedAt}"`);
q(key.encryptionKeyId, T.dkgRevokedBy, entity);
q(key.encryptionKeyId, T.dkgEncryptionKeyRevocationProof, `"${key.revocationProof}"`);
}
}
} else if (config.publicEncryptionKey && config.encryptionKeyAlgorithm && config.encryptionKeyProof) {
q(entity, `${DKG}publicEncryptionKey`, `"${config.publicEncryptionKey}"`);
q(entity, `${DKG}encryptionKeyAlgorithm`, `"${config.encryptionKeyAlgorithm}"`);
q(entity, `${DKG}encryptionKeyProof`, `"${config.encryptionKeyProof}"`);
q(entity, T.dkgPublicEncryptionKey, `"${config.publicEncryptionKey}"`);
q(entity, T.dkgEncryptionKeyAlgorithm, `"${config.encryptionKeyAlgorithm}"`);
q(entity, T.dkgEncryptionKeyProof, `"${config.encryptionKeyProof}"`);
}
if (config.framework) {
q(entity, `${SKILL}framework`, `"${config.framework}"`);
q(entity, T.skillFramework, `"${config.framework}"`);
}

// ERC-8004 capabilities (skills as capabilities)
for (let i = 0; i < config.skills.length; i++) {
const skill = config.skills[i];
const capUri = `${entity}/.well-known/genid/cap${i + 1}`;
const capUri = deriveAgentProfileOwnedSubjectV1(entity, 'capability', i + 1);

q(entity, `${ERC8004}capabilities`, capUri);
q(capUri, RDF_TYPE, `${ERC8004}Capability`);
q(capUri, `${SCHEMA}name`, `"${skill.skillType}"`);
q(entity, T.erc8004Capabilities, capUri);
q(capUri, T.rdfType, T.erc8004Capability);
q(capUri, T.schemaName, `"${skill.skillType}"`);

// Keep backward-compatible skill offering triples
const offeringUri = `${entity}/.well-known/genid/offering${i + 1}`;
q(entity, `${SKILL}offersSkill`, offeringUri);
q(offeringUri, RDF_TYPE, `${SKILL}SkillOffering`);
q(offeringUri, `${SKILL}skill`, `${SKILL}${skill.skillType}`);
const offeringUri = deriveAgentProfileOwnedSubjectV1(entity, 'offering', i + 1);
q(entity, T.skillOffersSkill, offeringUri);
q(offeringUri, T.rdfType, T.skillSkillOffering);
q(offeringUri, T.skillSkill, `${T.skillNamespace}${skill.skillType}`);

if (skill.pricePerCall !== undefined) {
q(offeringUri, `${SKILL}pricePerCall`, `"${skill.pricePerCall}"`);
q(offeringUri, T.skillPricePerCall, `"${skill.pricePerCall}"`);
}
if (skill.currency) {
q(offeringUri, `${SKILL}currency`, `"${skill.currency}"`);
q(offeringUri, T.skillCurrency, `"${skill.currency}"`);
}
if (skill.successRate !== undefined) {
q(offeringUri, `${SKILL}successRate`, `"${skill.successRate}"`);
q(offeringUri, T.skillSuccessRate, `"${skill.successRate}"`);
}
if (skill.pricingModel) {
q(offeringUri, `${SKILL}pricing`, `${SKILL}${skill.pricingModel}`);
q(offeringUri, T.skillPricing, `${T.skillNamespace}${skill.pricingModel}`);
}
}

// PROV provenance
const activityUri = `${entity}/.well-known/genid/registration`;
q(entity, `${PROV}wasGeneratedBy`, activityUri);
q(activityUri, RDF_TYPE, `${PROV}Activity`);
q(activityUri, `${PROV}atTime`, `"${profileTimestamp}"`);
const activityUri = deriveAgentProfileOwnedSubjectV1(entity, 'registration');
q(entity, T.provWasGeneratedBy, activityUri);
q(activityUri, T.rdfType, T.provActivity);
q(activityUri, T.provAtTime, `"${profileTimestamp}"`);

const served = config.contextGraphsServed;
if (served?.length) {
const hostingUri = `${entity}/.well-known/genid/hosting`;
q(entity, `${SKILL}hostingProfile`, hostingUri);
q(hostingUri, RDF_TYPE, `${SKILL}HostingProfile`);
const hostingUri = deriveAgentProfileOwnedSubjectV1(entity, 'hosting');
q(entity, T.skillHostingProfile, hostingUri);
q(hostingUri, T.rdfType, T.skillHostingProfileType);
for (const cg of served) {
q(hostingUri, `${SKILL}contextGraphsServed`, `"${cg}"`);
q(hostingUri, T.skillContextGraphsServed, `"${cg}"`);
}
}

Expand Down
133 changes: 133 additions & 0 deletions packages/agent/src/system-records/agent-profile-producer-api-v1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import {
type AssertionCoordinateV1,
type CanonicalGraphScopedAuthorSealV1,
type CatalogSealDeploymentProfileV1,
} from '@origintrail-official/dkg-core';
import {
type AgentProfileActiveHeadObjectV1,
type AgentProfileVerifiedAuthoritySummaryV1,
type Digest32V1,
type NetworkIdV1,
type OwnedSubjectTableObjectV1,
type SignedAgentProfileHeadEnvelopeV1,
type SignedSystemRecordRootDescriptorEnvelopeV1,
type SystemRecordInventoryTreeSnapshotV1,
type SystemRecordObjectKindV1,
type SystemRecordPeerPublicKeyV1,
} from '@origintrail-official/dkg-core/system-record-v1';
import type { Quad } from '@origintrail-official/dkg-storage';

import type { EvmPersonalMessageSignerV1 } from '../evm-message-signer-v1.js';
import type { PreparedAgentProfileV1 } from '../profile.js';
import type { SystemRecordArtifactV1 } from './artifact-v1.js';

/** Stable producer boundary; implementation-phase capabilities stay in their owning modules. */
export interface SystemRecordPeerSignerV1 {
readonly peerId: string;
readonly publicKey: SystemRecordPeerPublicKeyV1;
sign(message: Uint8Array): Promise<Uint8Array>;
}

export type AgentProfilePublicationStatusV1 = 'tentative' | 'confirmed' | 'failed';

/** Untrusted legacy-publication result accepted at the producer boundary. */
export interface AgentProfilePublicationBindingV1 {
readonly publicationStatus: AgentProfilePublicationStatusV1;
readonly assertionCoordinate: AssertionCoordinateV1;
readonly seal: Readonly<CanonicalGraphScopedAuthorSealV1>;
readonly issuedAt: string;
readonly validUntil: string;
readonly projectionSchemaDigest: Digest32V1;
}

export interface AgentProfileProducerInstallInputV1 {
readonly head: AgentProfileActiveHeadObjectV1;
readonly envelope: SignedAgentProfileHeadEnvelopeV1;
readonly canonicalProjectionBytes: Uint8Array;
readonly projectionQuads: readonly Readonly<Quad>[];
readonly ownedSubjectTable: OwnedSubjectTableObjectV1;
readonly verifiedAuthoritySummary: AgentProfileVerifiedAuthoritySummaryV1;
readonly signal: AbortSignal;
}

export interface AgentProfileProducerPublicationV1 {
readonly headDigest: Digest32V1;
readonly rootDescriptorDigest: Digest32V1;
readonly version: string;
readonly authoritySequence: string;
readonly inventoryWrites: number;
readonly inventoryWriteBytes: number;
}

export interface AgentProfileProducerPublicationCommitV1 {
/** Snapshot preconditions reserved before materialization begins. */
readonly expectedHeadDigest: Digest32V1 | null;
readonly expectedRootDescriptorDigest: Digest32V1 | null;
readonly publicationArtifacts: AgentProfileProducerPublicationArtifactsV1;
readonly inventory: SystemRecordInventoryTreeSnapshotV1;
readonly rootEnvelope: SignedSystemRecordRootDescriptorEnvelopeV1;
}

export type AgentProfileProducerArtifactV1<Kind extends SystemRecordObjectKindV1> = Readonly<
Omit<SystemRecordArtifactV1, 'objectKind'> & { objectKind: Kind }
>;

export interface AgentProfileProducerPublicationArtifactsV1 {
readonly head: AgentProfileProducerArtifactV1<'agent-profile-head'>;
readonly bundle: AgentProfileProducerArtifactV1<'profile-bundle'>;
readonly ownedSubjectTable: AgentProfileProducerArtifactV1<'owned-subject-table'>;
readonly inventoryObjects: readonly AgentProfileProducerArtifactV1<
'inventory-internal' | 'inventory-leaf'
>[];
}

export interface AgentProfileProducerPublicationCommitLeaseV1 {
commit(): void | Promise<void>;
abort(): void;
}

export interface AgentProfileProducerPublicationStoreV1 {
snapshot(): Readonly<{
inventory: SystemRecordInventoryTreeSnapshotV1 | null;
currentHead: SignedAgentProfileHeadEnvelopeV1 | null;
}>;
/** Resolve retained authority history by content address, without wire semantics. */
resolveArtifact(
reference: Pick<SystemRecordArtifactV1, 'objectKind' | 'objectDigest'>,
): SystemRecordArtifactV1 | null | Promise<SystemRecordArtifactV1 | null>;
/** Atomically verify and reserve the expected snapshot until commit or abort. */
prepareCommit(
input: AgentProfileProducerPublicationCommitV1,
): AgentProfileProducerPublicationCommitLeaseV1 | Promise<AgentProfileProducerPublicationCommitLeaseV1>;
}

export interface CreateAgentProfileProducerOptionsV1 {
readonly networkId: NetworkIdV1;
/** Locally pinned VM publication lane; never derived from an untrusted seal. */
readonly publicationDeployment: Readonly<CatalogSealDeploymentProfileV1>;
readonly peerSigner: SystemRecordPeerSignerV1;
readonly evmSigner: EvmPersonalMessageSignerV1;
readonly store: AgentProfileProducerPublicationStoreV1;
/** Independent verifier clock; publication timestamps are untrusted input. */
readonly nowMs?: () => number;
/** Storage-runtime bridge: fence before publish; successful install commits advertisement. */
readonly fence: (
prepared: PreparedAgentProfileV1,
signal: AbortSignal,
) => void | Promise<void>;
readonly install: (input: AgentProfileProducerInstallInputV1) => void | Promise<void>;
}

export interface AgentProfileProducerLeaseV1 {
complete(
publication: AgentProfilePublicationBindingV1,
): Promise<AgentProfileProducerPublicationV1>;
abort(reason?: unknown): void;
}

export interface AgentProfileProducerV1 {
/** Fence one immutable profile before the legacy publication begins. */
prepare(
prepared: PreparedAgentProfileV1,
): Promise<AgentProfileProducerLeaseV1>;
}
Loading
Loading