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
18 changes: 18 additions & 0 deletions packages/agent/src/dkg-agent-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,22 @@ export class DKGAgentBase {
*/
static readonly VM_RECONCILE_BATCH_SIZE =
Math.max(1, Number(process.env['DKG_VM_RECONCILE_BATCH_SIZE']) || 10);
/** Hard ceiling: RS heal is best-effort maintenance and must stay bounded. */
static readonly RS_HEAL_BATCH_MAX = 64;
/**
* Maximum stranded KCs one RS-heal pass may inspect before yielding. RS heal
* is periodic repair work, so bounding each pass keeps foreground publish,
* SWM and gossip operations from competing with an entire historical backlog.
*/
static readonly RS_HEAL_BATCH_SIZE = Math.min(
DKGAgentBase.RS_HEAL_BATCH_MAX,
readPositiveSafeIntegerEnv('DKG_RS_HEAL_BATCH_SIZE', 8),
);
/** Bounded per-CG keyset cursor retention for the independent RS-heal pager. */
static readonly RS_HEAL_CG_STATE_MAX_ENTRIES = Math.min(
10_000,
readPositiveSafeIntegerEnv('DKG_RS_HEAL_CG_STATE_MAX_ENTRIES', 1_000),
);
/**
* Parallel ordinal work per CG. Combined with the default two-CG dispatcher
* concurrency this caps chain/store pressure at ten in-flight ordinals.
Expand Down Expand Up @@ -1043,6 +1059,8 @@ export class DKGAgentBase {
protected vmReconcileLifecycleController = new AbortController();
/** Phase D/A4 — per-CG active-fetch cooldown so one sweep cannot fan out repeated fetches. */
protected readonly vmReconcileFetchCooldownAt = new Map<string, number>();
/** Last stranded UAL visited by the bounded RS-heal sweep for each CG. */
protected readonly rsHealCursorByCg = new Map<string, string>();
/** Phase D/A4 — round-robin cursor over the already ordered catch-up peer list. */
protected readonly vmReconcileCatchupPeerCursor = new Map<string, number>();
protected readonly vmReconcileCatchupPeerOrder = new Map<string, {
Expand Down
73 changes: 53 additions & 20 deletions packages/agent/src/dkg-agent-cg-resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -889,26 +889,49 @@ export class ContextGraphResolveMethods extends DKGAgentBase {
const persistedSubscriptionPromise = subscriptionStore?.load
? subscriptionStore.load(contextGraphId)
: Promise.resolve(null);
// One bounded point projection over the three trusted metadata sources.
// Facts are intentionally collected independently: the established
// ContextGraphMetaProjection model permits the declaration to live in one
// source and policy/curator/gates in another. Requiring every source to
// repeat rdf:type would both reject valid gate-only graphs and, worse,
// ignore a cross-source private-policy ratchet. Gate presence uses EXISTS
// so this path returns at most one gate row per source instead of expanding
// a potentially large allowlist.
const declarationPromise = this.store.query(`
SELECT ?access ?curator WHERE {
SELECT ?kind ?value WHERE {
VALUES (?sourceGraph ?sourceSubject) {
(<${ontologyGraph}> <${contextGraphUri}>)
(<${agentsGraph}> <${contextGraphUri}>)
(<${cgMetaGraph}> <${metaSubjectUri}>)
}
{
GRAPH <${ontologyGraph}> {
<${contextGraphUri}> <${DKG_ONTOLOGY.RDF_TYPE}> <${DKG_ONTOLOGY.DKG_CONTEXT_GRAPH}> .
OPTIONAL { <${contextGraphUri}> <${DKG_ONTOLOGY.DKG_ACCESS_POLICY}> ?access }
OPTIONAL { <${contextGraphUri}> <${DKG_ONTOLOGY.DKG_CURATOR}> ?curator }
GRAPH ?sourceGraph {
?sourceSubject <${DKG_ONTOLOGY.RDF_TYPE}> <${DKG_ONTOLOGY.DKG_CONTEXT_GRAPH}> .
}
BIND("declared" AS ?kind)
} UNION {
GRAPH <${agentsGraph}> {
<${contextGraphUri}> <${DKG_ONTOLOGY.RDF_TYPE}> <${DKG_ONTOLOGY.DKG_CONTEXT_GRAPH}> .
OPTIONAL { <${contextGraphUri}> <${DKG_ONTOLOGY.DKG_ACCESS_POLICY}> ?access }
OPTIONAL { <${contextGraphUri}> <${DKG_ONTOLOGY.DKG_CURATOR}> ?curator }
GRAPH ?sourceGraph {
?sourceSubject <${DKG_ONTOLOGY.DKG_ACCESS_POLICY}> ?value .
}
BIND("access" AS ?kind)
} UNION {
GRAPH <${cgMetaGraph}> {
<${metaSubjectUri}> <${DKG_ONTOLOGY.RDF_TYPE}> <${DKG_ONTOLOGY.DKG_CONTEXT_GRAPH}> .
OPTIONAL { <${metaSubjectUri}> <${DKG_ONTOLOGY.DKG_ACCESS_POLICY}> ?access }
OPTIONAL { <${metaSubjectUri}> <${DKG_ONTOLOGY.DKG_CURATOR}> ?curator }
GRAPH ?sourceGraph {
?sourceSubject <${DKG_ONTOLOGY.DKG_CURATOR}> ?value .
}
BIND("curator" AS ?kind)
} UNION {
FILTER EXISTS {
VALUES ?gatePredicate {
<${DKG_ONTOLOGY.DKG_ALLOWED_AGENT}>
<${DKG_ONTOLOGY.DKG_ALLOWED_PEER}>
<${DKG_ONTOLOGY.DKG_PARTICIPANT_AGENT}>
<${DKG_ONTOLOGY.DKG_PARTICIPANT_IDENTITY_ID}>
}
GRAPH ?sourceGraph {
?sourceSubject ?gatePredicate ?gateValue .
}
}
BIND("gate" AS ?kind)
}
}
`);
Expand Down Expand Up @@ -955,26 +978,36 @@ export class ContextGraphResolveMethods extends DKGAgentBase {
: noteStoreFailure(declarationRead.reason);

let accessPolicy: 'public' | 'private' | undefined;
// Tri-state: stays `undefined` (unknown) when the declaration read
// Tri-state: stays `undefined` (unknown) when the metadata projection read
// failed, so a store outage can never masquerade as "no declaration".
let declarationFound: boolean | undefined = declarationResult === undefined ? undefined : false;
const curators: string[] = [];
if (declarationResult && declarationResult.type === 'bindings') {
declarationFound = declarationResult.bindings.length > 0;
let sawPublic = false;
let sawPrivate = false;
let sawGate = false;
for (const row of declarationResult.bindings as Record<string, string>[]) {
const access = row['access'];
if (typeof access === 'string') {
const normalized = stripLiteral(access).trim().toLowerCase();
const kind = typeof row['kind'] === 'string'
? stripLiteral(row['kind']).trim().toLowerCase()
: '';
const value = row['value'];
if (kind === 'declared') declarationFound = true;
if (kind === 'access' && typeof value === 'string') {
const normalized = stripLiteral(value).trim().toLowerCase();
if (normalized === 'private') sawPrivate = true;
if (normalized === 'public') sawPublic = true;
}
const curator = row['curator'];
if (typeof curator === 'string' && curator.trim()) curators.push(curator);
if (kind === 'curator' && typeof value === 'string' && value.trim()) {
curators.push(value);
}
if (kind === 'gate') sawGate = true;
}
if (sawPrivate) accessPolicy = 'private';
else if (sawPublic) accessPolicy = 'public';
Comment thread
Bojan131 marked this conversation as resolved.
// Match list/projection semantics: an explicit public policy wins, but
// legacy/implicit allowlist gates make a graph private when no policy
// literal exists. This is a bounded point query for one canonical id.
else if (sawGate) accessPolicy = 'private';
}

let checksum: string | null = null;
Expand Down
Loading
Loading