Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .changeset/agent-map-revision-contracts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sapiom/harness": minor
---

Add public contracts for immutable Agent Map revisions, branded architecture digests, architecture approval evidence, trusted human-message receipts, and confirmations. Establish the harness's internal V1 domain-separated SHA-256 canonicalization for later confirmation and build-planning slices.
2 changes: 1 addition & 1 deletion packages/harness/src/core/agent-map-proposal-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import {
type StudioProjectId,
} from "../shared/agent-map.js";
import { parseProposalActor } from "../shared/agent-map-codec.js";
import { canonicalizeAgentMapGraph } from "../shared/agent-map-canonical.js";
import { parseProposalBatchRequest } from "./agent-map-proposal-schema.js";
import {
canonicalizeAgentMapGraph,
derivePersistedMapOperationTouchSet,
materializeValidatedMapBatch,
proposalTouchSetsOverlap,
Expand Down
93 changes: 24 additions & 69 deletions packages/harness/src/core/agent-map-proposal-validator.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,26 @@
import type {
AgentMapGraph,
DraftRef,
MapOperation,
MapOperationInput,
PlanNode,
PlanNodeId,
PlanNodeKind,
PlanRelationship,
PlanRelationshipId,
ProposalBatchRequest,
ProposalValidationIssue,
ProposalValidationResult,
RelationshipKind,
import {
AGENT_MAP_RELATIONSHIP_ENDPOINT_MATRIX,
type AgentMapGraph,
type DraftRef,
type MapOperation,
type MapOperationInput,
type PlanNode,
type PlanNodeId,
type PlanRelationship,
type PlanRelationshipId,
type ProposalBatchRequest,
type ProposalValidationIssue,
type ProposalValidationResult,
} from "../shared/agent-map.js";
import {
canonicalizeAgentMapGraph,
canonicalizeAgentMapStrings,
compareAgentMapStrings,
} from "../shared/agent-map-canonical.js";

const ACTOR_KINDS = new Set<PlanNodeKind>(["agent", "subagent"]);
const ALL_NODE_KINDS = new Set<PlanNodeKind>([
"agent",
"subagent",
"resource",
"connector",
"artifact",
]);

export const RELATIONSHIP_ENDPOINT_MATRIX: Readonly<
Record<
RelationshipKind,
{ from: ReadonlySet<PlanNodeKind>; to: ReadonlySet<PlanNodeKind> }
>
> = {
invokes: { from: ACTOR_KINDS, to: ACTOR_KINDS },
feeds: { from: ALL_NODE_KINDS, to: ACTOR_KINDS },
reads: {
from: ACTOR_KINDS,
to: new Set<PlanNodeKind>(["resource", "artifact"]),
},
writes: {
from: ACTOR_KINDS,
to: new Set<PlanNodeKind>(["resource", "artifact"]),
},
uses: {
from: ACTOR_KINDS,
to: new Set<PlanNodeKind>(["resource", "connector"]),
},
triggers: { from: ALL_NODE_KINDS, to: ACTOR_KINDS },
};
/** @deprecated Import the shared policy for new code. */
export const RELATIONSHIP_ENDPOINT_MATRIX =
AGENT_MAP_RELATIONSHIP_ENDPOINT_MATRIX;

export interface ProposalTouchSet {
entityKeys: string[];
Expand Down Expand Up @@ -97,11 +73,8 @@ const nodeDraftKey = (draftRef: DraftRef): string => `draft-node:${draftRef}`;
const relationshipDraftKey = (draftRef: DraftRef): string =>
`draft-relationship:${draftRef}`;

const compareStrings = (left: string, right: string): number =>
left < right ? -1 : left > right ? 1 : 0;

const canonicalStrings = (values: readonly string[]): string[] =>
[...values].sort(compareStrings);
const compareStrings = compareAgentMapStrings;
const canonicalStrings = canonicalizeAgentMapStrings;

const stripUndefinedProperties = <T extends Record<string, unknown>>(
value: T,
Expand All @@ -110,25 +83,7 @@ const stripUndefinedProperties = <T extends Record<string, unknown>>(
Object.entries(value).filter(([, fieldValue]) => fieldValue !== undefined),
) as T;

const canonicalNode = (node: PlanNode): PlanNode => ({
...node,
contractRefs: canonicalStrings(node.contractRefs),
});

const canonicalRelationship = (
relationship: PlanRelationship,
): PlanRelationship => ({ ...relationship });

export function canonicalizeAgentMapGraph(graph: AgentMapGraph): AgentMapGraph {
return {
nodes: graph.nodes
.map(canonicalNode)
.sort((left, right) => compareStrings(left.id, right.id)),
relationships: graph.relationships
.map(canonicalRelationship)
.sort((left, right) => compareStrings(left.id, right.id)),
};
}
export { canonicalizeAgentMapGraph };

export function semanticRelationshipKey(
relationship: Pick<
Expand Down
Loading
Loading