Skip to content
Open
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
7 changes: 7 additions & 0 deletions packages/agent/src/dkg-agent-lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,13 @@ export class LifecycleSyncMethods extends DKGAgentBase {
`keeping handler active: ${err instanceof Error ? err.message : String(err)}`,
);
},
onDecline: (details) => {
this.log.warn(
attemptCtx,
`V10 StorageACK declined: code=${details.code} ` +
`cg=${details.contextGraphId} reason=${details.message}`,
);
},
// PR5 ACK-provenance — bind to the agent's host-mode
// bookkeeping so every signed ACK carries which of the
// four LU-6 Phase B discovery paths brought this CG's
Expand Down
11 changes: 10 additions & 1 deletion packages/agent/src/dkg-agent-publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import {
sharedMemoryReadBothFilter,
partitionCatalogQuads,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Nit: normalizeLargeRdfLiteralsForBlazegraph is imported from @origintrail-official/dkg-core in its own statement immediately after an existing import from the same package. Consolidating it into the existing import matches the local pattern and avoids needless import drift as more core symbols are added.

} from '@origintrail-official/dkg-core';
import { normalizeLargeRdfLiteralsForBlazegraph } from '@origintrail-official/dkg-core';
import { GraphManager, PrivateContentStore, createTripleStore, type TripleStore, type TripleStoreConfig, type Quad, type LargeLiteralStorageConfig } from '@origintrail-official/dkg-storage';
import { EVMChainAdapter, NoChainAdapter, enrichEvmError, buildKnowledgeAssetUal, type EVMAdapterConfig, type ChainAdapter, type CreateContextGraphParams, type CreateOnChainContextGraphParams, type CreateOnChainContextGraphResult, type TxResult, type V10PublishingConvictionAccountInfo } from '@origintrail-official/dkg-chain';
import {
Expand Down Expand Up @@ -1267,6 +1268,10 @@ export class PublishMethods extends DKGAgentBase {
): Promise<PublishResult> {
const ctx = opts?.operationCtx ?? createOperationContext('publish');
const onPhase = opts?.onPhase;
quads = normalizeLargeRdfLiteralsForBlazegraph(quads).quads as Quad[];
if (privateQuads) {
privateQuads = normalizeLargeRdfLiteralsForBlazegraph(privateQuads).quads as Quad[];
}
this.log.info(ctx, `Starting publish to context graph "${contextGraphId}" with ${quads.length} triples`);

const isSystem = contextGraphId === SYSTEM_CONTEXT_GRAPHS.AGENTS || contextGraphId === SYSTEM_CONTEXT_GRAPHS.ONTOLOGY;
Expand Down Expand Up @@ -2567,6 +2572,10 @@ export class PublishMethods extends DKGAgentBase {
privateQuads?: Quad[];
},
): Promise<PublishOptions['precomputedAttestation']> {
quads = normalizeLargeRdfLiteralsForBlazegraph(quads).quads as Quad[];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: This normalization feeds _buildPrecomputedAttestationForSelection, but the added coverage does not exercise an agent/on-chain publish where the precomputed attestation is built from oversized public or private quads. Add a regression test through the agent selection/precomputed-attestation path so a raw-vs-normalized seal mismatch is caught.

const normalizedPrivateQuads = opts?.privateQuads
? normalizeLargeRdfLiteralsForBlazegraph(opts.privateQuads).quads as Quad[]
: undefined;
if (
opts?.authorAgentAddress != null &&
opts?.preSignedAuthorAttestation != null
Expand Down Expand Up @@ -2595,7 +2604,7 @@ export class PublishMethods extends DKGAgentBase {
// KC merkle. The order MUST follow the publisher's manifest
// iteration over `kaMap`, which is the insertion order — same map
// we built two lines up.
const privateQuads = opts?.privateQuads ?? [];
const privateQuads = normalizedPrivateQuads ?? [];
const privateRoots: Uint8Array[] = [];
for (const rootEntity of kaMap.keys()) {
if (privateQuads.length === 0) break;
Expand Down
9 changes: 9 additions & 0 deletions packages/agent/test/storage-ack-protocol-extra.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ describe('A-9: storage-ack protocol id (libp2p) pin', () => {
expect(combined).toMatch(registerRE);
});

it('agent wires core-side StorageACK decline logging', () => {
const combined = walk(AGENT_SRC)
.map((f) => readFileSync(f, 'utf8'))
.join('\n');

expect(combined).toMatch(/onDecline:\s*\(details\)\s*=>/);
expect(combined).toContain('V10 StorageACK declined: code=');
});

it('agent source never publishes ACKs on GossipSub', () => {
// A false-positive here would be any call like
// `publish('/dkg/10.0.1/storage-ack', ...)` or
Expand Down
13 changes: 13 additions & 0 deletions packages/cli/src/daemon/http-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ export function payloadTooLargeResponseBody(err: unknown): Record<string, unknow
return body;
}

export function isRdfLiteralSizeError(err: unknown): boolean {
if (!err || typeof err !== 'object') return false;
const shaped = err as { name?: unknown; code?: unknown };
return shaped.name === 'RdfLiteralSizeError' || shaped.code === 'RDF_LITERAL_TOO_LARGE';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: String(err ?? ...) turns any non-Error object that matches isRdfLiteralSizeError into an unhelpful "[object Object]" response. Since the predicate explicitly accepts shaped objects by name or code, this helper should also read a string message field before falling back, so daemon responses stay actionable for cross-package/plain-object errors.

export function rdfLiteralSizeResponseBody(err: unknown): Record<string, unknown> {
return {
error: err instanceof Error ? err.message : String(err ?? 'RDF literal exceeds size limit'),
code: 'RDF_LITERAL_TOO_LARGE',
};
}

export async function resolveNameToPeerId(
agent: DKGAgent,
nameOrId: string,
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/daemon/routes/knowledge-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
import type { RequestContext } from "./context.js";
import {
isPayloadTooLargeError,
isRdfLiteralSizeError,
jsonResponse,
payloadTooLargeResponseBody,
rdfLiteralSizeResponseBody,
readBody,
safeParseJson,
validateEntities,
Expand Down Expand Up @@ -650,6 +652,9 @@ export async function handleKnowledgeAssetsRoutes(ctx: RequestContext): Promise<
if (isPayloadTooLargeError(e)) {
return jsonResponse(res, 413, payloadTooLargeResponseBody(e));
}
if (isRdfLiteralSizeError(e)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: The daemon now maps RdfLiteralSizeError to a public 400 response, but there is no route-level test that makes the underlying publish/share call throw this error and asserts the HTTP status/body. Please add coverage for this route and the shared-memory handlers that recur below, so a regression to the generic 500 path or missing RDF_LITERAL_TOO_LARGE code is caught.

return jsonResponse(res, 400, rdfLiteralSizeResponseBody(e));
}
return jsonResponse(res, 500, { error: e?.message ?? String(e) });
}
}
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/src/daemon/routes/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ import {
shortId,
sleep,
deriveBlockExplorerUrl,
isRdfLiteralSizeError,
rdfLiteralSizeResponseBody,
} from '../http-utils.js';
import {
normalizeRepo,
Expand Down Expand Up @@ -1695,6 +1697,9 @@ WHERE {
) {
return jsonResponse(res, 400, { error: err.message });
}
if (isRdfLiteralSizeError(err)) {
return jsonResponse(res, 400, rdfLiteralSizeResponseBody(err));
}
// Strict curator-ack gate (OT-RFC-49 curator-leader): the write was NOT
// persisted because the curator (the authoritative replica) did not
// confirm it. Surface a distinct, actionable status instead of a generic
Expand Down Expand Up @@ -2256,6 +2261,9 @@ WHERE {
});
} catch (err: any) {
tracker.fail(ctx, err);
if (isRdfLiteralSizeError(err)) {
return jsonResponse(res, 400, rdfLiteralSizeResponseBody(err));
}
if (
err.name === "StaleWriteError" ||
err.message?.includes("stale") ||
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { parseDotenvValue } from './dotenv.js';
export * from './memory-model.js';
export * from './trust.js';
export * from './publisher-extension.js';
export * from './rdf-literal-limits.js';
export * from './imported-artifact-bytes.js';
export * from './imported-artifact-metadata.js';
export * from './event-bus.js';
Expand Down
Loading