From 13aa6b71da737f9bd61844e1582e11857dd56b10 Mon Sep 17 00:00:00 2001 From: Illia Pashkov Date: Mon, 20 Apr 2026 17:35:27 -0700 Subject: [PATCH] feat(core): add migrationAttestation structural precondition to ConstraintEnvelope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds ConstraintEnvelopeMigrationAttestation interface (SBR-002-compatible shape) and an optional migrationAttestation field on ConstraintEnvelope. When the field is present, validateConstraintEnvelope requires continuityVerified === true and all four shape fields (schema, attestationUri, agentWallet, issuedAt) to be non-empty strings. Otherwise the envelope is invalid and scope is not granted. This makes entity_continuity a structural precondition for actuator scope rather than an advisory signal — envelope validation is the activation gate. Aligns with the aeoess/agent-governance-vocabulary canonical two- implementation sequencing: PDR + continuity-analyzer provide the two independent implementations; SBR-002 remains the embodied-AI-specific reference schema underneath. Validation applies regardless of envelope version (legacy or CL-1.0) — the field is forward-compatible and its presence always demands the full shape with continuityVerified === true. Tests: 8 new cases covering valid shape, each invalid-shape branch, legacy-envelope compatibility, and optional-field behavior. All 61 core tests pass. Refs: aeoess/agent-governance-vocabulary#8 --- .../__tests__/constraint-language.test.ts | 139 ++++++++++++++++++ packages/core/src/constraint-language.ts | 23 +++ packages/core/src/types/protocol.ts | 22 +++ 3 files changed, 184 insertions(+) diff --git a/packages/core/__tests__/constraint-language.test.ts b/packages/core/__tests__/constraint-language.test.ts index 3ef4b462..89ca26bb 100644 --- a/packages/core/__tests__/constraint-language.test.ts +++ b/packages/core/__tests__/constraint-language.test.ts @@ -110,6 +110,145 @@ describe("validateConstraintEnvelope", () => { expect(result.mode).toBe("legacy"); }); + // ------------------------------------------------------------------------- + // migrationAttestation — entity_continuity structural precondition + // Refs: aeoess/agent-governance-vocabulary#8 + // ------------------------------------------------------------------------- + + it("migrationAttestation with continuityVerified:true and all fields is valid", () => { + const envelope: ConstraintEnvelope = { + version: "cl-1.0", + migrationAttestation: { + schema: "https://soulboundrobots.ai/schemas/sbr-002-v1.json", + attestationUri: "https://eas.example/0xabc", + agentWallet: "0xdead", + continuityVerified: true, + issuedAt: "2026-04-20T00:00:00Z", + }, + }; + const result = validateConstraintEnvelope(envelope); + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it("migrationAttestation with continuityVerified:false fails validation", () => { + const envelope: ConstraintEnvelope = { + version: "cl-1.0", + migrationAttestation: { + schema: "https://soulboundrobots.ai/schemas/sbr-002-v1.json", + attestationUri: "https://eas.example/0xabc", + agentWallet: "0xdead", + continuityVerified: false, + issuedAt: "2026-04-20T00:00:00Z", + }, + }; + const result = validateConstraintEnvelope(envelope); + expect(result.valid).toBe(false); + expect(result.errors).toContain( + "migrationAttestation.continuityVerified must be true for envelope to activate", + ); + }); + + it("migrationAttestation with empty schema fails validation", () => { + const envelope: ConstraintEnvelope = { + version: "cl-1.0", + migrationAttestation: { + schema: "", + attestationUri: "https://eas.example/0xabc", + agentWallet: "0xdead", + continuityVerified: true, + issuedAt: "2026-04-20T00:00:00Z", + }, + }; + const result = validateConstraintEnvelope(envelope); + expect(result.valid).toBe(false); + expect(result.errors).toContain( + "migrationAttestation.schema must be a non-empty string", + ); + }); + + it("migrationAttestation with empty attestationUri fails validation", () => { + const envelope: ConstraintEnvelope = { + version: "cl-1.0", + migrationAttestation: { + schema: "https://soulboundrobots.ai/schemas/sbr-002-v1.json", + attestationUri: "", + agentWallet: "0xdead", + continuityVerified: true, + issuedAt: "2026-04-20T00:00:00Z", + }, + }; + const result = validateConstraintEnvelope(envelope); + expect(result.valid).toBe(false); + expect(result.errors).toContain( + "migrationAttestation.attestationUri must be a non-empty string", + ); + }); + + it("migrationAttestation with empty agentWallet fails validation", () => { + const envelope: ConstraintEnvelope = { + version: "cl-1.0", + migrationAttestation: { + schema: "https://soulboundrobots.ai/schemas/sbr-002-v1.json", + attestationUri: "https://eas.example/0xabc", + agentWallet: "", + continuityVerified: true, + issuedAt: "2026-04-20T00:00:00Z", + }, + }; + const result = validateConstraintEnvelope(envelope); + expect(result.valid).toBe(false); + expect(result.errors).toContain( + "migrationAttestation.agentWallet must be a non-empty string", + ); + }); + + it("migrationAttestation with empty issuedAt fails validation", () => { + const envelope: ConstraintEnvelope = { + version: "cl-1.0", + migrationAttestation: { + schema: "https://soulboundrobots.ai/schemas/sbr-002-v1.json", + attestationUri: "https://eas.example/0xabc", + agentWallet: "0xdead", + continuityVerified: true, + issuedAt: "", + }, + }; + const result = validateConstraintEnvelope(envelope); + expect(result.valid).toBe(false); + expect(result.errors).toContain( + "migrationAttestation.issuedAt must be a non-empty ISO8601 string", + ); + }); + + it("migrationAttestation validates on legacy envelope too (forward-compatible)", () => { + const envelope: ConstraintEnvelope = { + migrationAttestation: { + schema: "https://soulboundrobots.ai/schemas/sbr-002-v1.json", + attestationUri: "https://eas.example/0xabc", + agentWallet: "0xdead", + continuityVerified: false, + issuedAt: "2026-04-20T00:00:00Z", + }, + }; + const result = validateConstraintEnvelope(envelope); + expect(result.valid).toBe(false); + expect(result.mode).toBe("legacy"); + expect(result.errors).toContain( + "migrationAttestation.continuityVerified must be true for envelope to activate", + ); + }); + + it("envelope without migrationAttestation is unaffected (field is optional)", () => { + const envelope: ConstraintEnvelope = { + version: "cl-1.0", + physical: { maxVelocityMps: 0.5 }, + }; + const result = validateConstraintEnvelope(envelope); + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + it("CL-1.0 with mode:corridor-preapproved is valid", () => { const envelope: ConstraintEnvelope = { version: "cl-1.0", diff --git a/packages/core/src/constraint-language.ts b/packages/core/src/constraint-language.ts index ce53ca3e..8254a39f 100644 --- a/packages/core/src/constraint-language.ts +++ b/packages/core/src/constraint-language.ts @@ -61,6 +61,29 @@ export function validateConstraintEnvelope(envelope: ConstraintEnvelope): Constr } } + // migrationAttestation validation applies regardless of version — the field + // is forward-compatible and its presence always requires the full attestation + // shape with continuityVerified === true as the structural precondition for + // scope grant. + if (envelope.migrationAttestation !== undefined) { + const ma = envelope.migrationAttestation; + if (ma.continuityVerified !== true) { + errors.push("migrationAttestation.continuityVerified must be true for envelope to activate"); + } + if (typeof ma.schema !== "string" || ma.schema.length === 0) { + errors.push("migrationAttestation.schema must be a non-empty string"); + } + if (typeof ma.attestationUri !== "string" || ma.attestationUri.length === 0) { + errors.push("migrationAttestation.attestationUri must be a non-empty string"); + } + if (typeof ma.agentWallet !== "string" || ma.agentWallet.length === 0) { + errors.push("migrationAttestation.agentWallet must be a non-empty string"); + } + if (typeof ma.issuedAt !== "string" || ma.issuedAt.length === 0) { + errors.push("migrationAttestation.issuedAt must be a non-empty ISO8601 string"); + } + } + return { valid: errors.length === 0, errors, mode }; } diff --git a/packages/core/src/types/protocol.ts b/packages/core/src/types/protocol.ts index 8855f59f..7ca9fce7 100644 --- a/packages/core/src/types/protocol.ts +++ b/packages/core/src/types/protocol.ts @@ -73,6 +73,27 @@ export interface ConstraintEnvelopeAttestation { readonly requireForTiers?: readonly ApprovalTier[]; } +/** + * CL-1.0 migration attestation — proves agent identity continuity across + * embodied-AI migrations (body swaps, wallet rotations, runtime handoffs). + * + * Shape aligns with the SBR-002 (Soulbound Robots) reference schema. When + * this field is present on an envelope, `continuityVerified` must be `true` + * for the envelope to validate. This makes entity_continuity a structural + * precondition for scope grant rather than an advisory signal. + * + * Refs: + * - aeoess/agent-governance-vocabulary#8 (entity_continuity signal type) + * - https://soulboundrobots.ai/schemas/sbr-002-v1.json (reference schema) + */ +export interface ConstraintEnvelopeMigrationAttestation { + readonly schema: string; + readonly attestationUri: string; + readonly agentWallet: string; + readonly continuityVerified: boolean; + readonly issuedAt: ISO8601; +} + /** * CL-1.0 constraint enforcement mode. */ @@ -92,6 +113,7 @@ export interface ConstraintEnvelope extends Partial { readonly behavioral?: ConstraintEnvelopeBehavioral; readonly model?: ConstraintEnvelopeModel; readonly attestation?: ConstraintEnvelopeAttestation; + readonly migrationAttestation?: ConstraintEnvelopeMigrationAttestation; readonly dynamic?: ConstraintEnvelopeDynamic; readonly execution?: ConstraintEnvelopeExecution; readonly extensions?: Readonly>;