Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
2 changes: 2 additions & 0 deletions packages/agent/src/dkg-agent-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ import {
type DKGAgentConfig,
type ReplicationEvent,
type SyncReconcilerBackoff,
type ImportedSourceBlobStore,
} from './dkg-agent-types.js';
import {
normalizePublishContextGraphId,
Expand Down Expand Up @@ -434,6 +435,7 @@ export class DKGAgentBase {
/** Shared write locks so gossip writes serialize against local CAS writes. */
protected readonly writeLocks: Map<string, Promise<void>>;
protected readonly publicSnapshotStore?: WorkspacePublicSnapshotStore;
protected importedSourceBlobStore?: ImportedSourceBlobStore;
protected sharedMemoryHandler?: InstanceType<typeof SharedMemoryHandler>;
protected gossipPublishHandler?: GossipPublishHandler;
protected finalizationHandler?: FinalizationHandler;
Expand Down
35 changes: 22 additions & 13 deletions packages/agent/src/dkg-agent-cg-resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ export class ContextGraphResolveMethods extends DKGAgentBase {
return {
contextGraphId: parsed.contextGraphId,
offset: parsed.offset ?? 0,
limit: Math.min(parsed.limit ?? SYNC_PAGE_SIZE, SYNC_PAGE_SIZE),
limit: parsed.limit ?? SYNC_PAGE_SIZE,
includeSharedMemory: parsed.includeSharedMemory ?? false,
phase: normalizeSyncPhase(parsed.phase),
snapshotRef: typeof parsed.snapshotRef === 'string' ? parsed.snapshotRef : undefined,
Expand All @@ -489,6 +489,8 @@ export class ContextGraphResolveMethods extends DKGAgentBase {
requesterAgentAddress: parsed.requesterAgentAddress,
requesterSignatureR: parsed.requesterSignatureR,
requesterSignatureVS: parsed.requesterSignatureVS,
authPurpose: typeof parsed.authPurpose === 'string' ? parsed.authPurpose : undefined,
authSelector: typeof parsed.authSelector === 'string' ? parsed.authSelector : undefined,
// Phase C: unsigned delta hint. Validated/normalised in the responder.
sinceBatchId: typeof parsed.sinceBatchId === 'string' ? parsed.sinceBatchId : undefined,
};
Expand Down Expand Up @@ -644,27 +646,34 @@ export class ContextGraphResolveMethods extends DKGAgentBase {
requestId: string | undefined,
issuedAtMs: number | undefined,
requesterAgentAddress: string | undefined,
authPurpose?: string,
authSelector?: string,
): Uint8Array {
// `requesterAgentAddress` participates in the digest so the
// "on behalf of" claim is signed, not free-form envelope data.
// Without it, the responder's delegation lookup can be steered by
// tampering with `requesterAgentAddress` after the signature was
// produced — which would be a way to bypass the per-agent
// delegation binding in `request-authorize`.
const baseTypes = ['string', 'uint256', 'uint256', 'bool', 'string', 'string', 'string', 'uint256', 'string'];
const baseValues = [
contextGraphId,
BigInt(offset),
BigInt(limit),
includeSharedMemory,
targetPeerId,
requesterPeerId ?? '',
requestId ?? '',
BigInt(issuedAtMs ?? 0),
(requesterAgentAddress ?? '').toLowerCase(),
];
if (!authPurpose && !authSelector) {
return ethers.getBytes(ethers.solidityPackedKeccak256(baseTypes, baseValues));
}
return ethers.getBytes(
ethers.solidityPackedKeccak256(
['string', 'uint256', 'uint256', 'bool', 'string', 'string', 'string', 'uint256', 'string'],
[
contextGraphId,
BigInt(offset),
BigInt(limit),
includeSharedMemory,
targetPeerId,
requesterPeerId ?? '',
requestId ?? '',
BigInt(issuedAtMs ?? 0),
(requesterAgentAddress ?? '').toLowerCase(),
],
[...baseTypes, 'string', 'string'],
[...baseValues, authPurpose ?? '', authSelector ?? ''],
),
);
}
Expand Down
10 changes: 9 additions & 1 deletion packages/agent/src/dkg-agent-lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { createHash, randomUUID } from 'node:crypto';
import {
DKGNode, ProtocolRouter, GossipSubManager, TypedEventBus, DKGEvent,
LibP2PNetwork, PeerResolver, StubNetworkStateRegistry,
PROTOCOL_ACCESS, PROTOCOL_PUBLISH, PROTOCOL_SYNC, PROTOCOL_QUERY_REMOTE, PROTOCOL_STORAGE_ACK, PROTOCOL_STORAGE_ACK_V2, PROTOCOL_STORAGE_UPDATE_ACK, PROTOCOL_GET_CIPHERTEXT_CHUNK, PROTOCOL_VERIFY_PROPOSAL, PROTOCOL_JOIN_REQUEST,
PROTOCOL_ACCESS, PROTOCOL_PUBLISH, PROTOCOL_SYNC, PROTOCOL_QUERY_REMOTE, PROTOCOL_STORAGE_ACK, PROTOCOL_STORAGE_ACK_V2, PROTOCOL_STORAGE_UPDATE_ACK, PROTOCOL_GET_CIPHERTEXT_CHUNK, PROTOCOL_GET_IMPORTED_SOURCE_BLOB, PROTOCOL_VERIFY_PROPOSAL, PROTOCOL_JOIN_REQUEST,
PROTOCOL_SWM_SENDER_KEY, PROTOCOL_SWM_UPDATE, PROTOCOL_SWM_SHARE_ACK, PROTOCOL_SWM_HOST_CATCHUP, PROTOCOL_MESSAGE,
contextGraphPublishTopic, contextGraphWorkspaceTopic, contextGraphAppTopic, contextGraphUpdateTopic, contextGraphFinalizationTopic,
contextGraphDataGraphUri, contextGraphMetaGraphUri, contextGraphWorkspaceGraphUri, contextGraphWorkspaceMetaGraphUri,
Expand Down Expand Up @@ -1553,6 +1553,14 @@ export class LifecycleSyncMethods extends DKGAgentBase {
logDebug: (ctx, message) => this.log.debug(ctx, message),
});

// Issue #872: imported Markdown source blobs can be larger than the
// Universal Messenger response cache, so serve them on the raw router
// like sync pages. The handler performs its own signed selector check.
this.router.register(
PROTOCOL_GET_IMPORTED_SOURCE_BLOB,
(data, peerIdObj) => this.handleGetImportedSourceBlob(data, peerIdObj.toString()),
);

// Join-request protocol: receives signed join requests forwarded by peers.
// Stores them locally if this node is the curator; ACKs with "ok" or "error".
// rc.9 PR-10: migrated onto the Universal Messenger substrate
Expand Down
3 changes: 2 additions & 1 deletion packages/agent/src/dkg-agent-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -646,9 +646,10 @@ export class QueryMethods extends DKGAgentBase {
opts: {
callerAgentAddress?: string;
allowSubscriptionFallback?: boolean;
forcePrivatePolicy?: boolean;
} = {},
): Promise<boolean> {
if (!(await this.isPrivateContextGraph(contextGraphId))) {
if (!opts.forcePrivatePolicy && !(await this.isPrivateContextGraph(contextGraphId))) {
return true;
}

Expand Down
Loading
Loading