@vauban-org/agent-sdk 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRACT.md +6401 -813
- package/dist/adapters/llm/anthropic-direct.d.ts +1 -0
- package/dist/adapters/llm/anthropic-direct.d.ts.map +1 -1
- package/dist/adapters/llm/anthropic-direct.js +43 -0
- package/dist/adapters/llm/anthropic-direct.js.map +1 -1
- package/dist/adapters/llm/cascade.d.ts.map +1 -1
- package/dist/adapters/llm/cascade.js +57 -14
- package/dist/adapters/llm/cascade.js.map +1 -1
- package/dist/adapters/llm/litellm.d.ts +2 -0
- package/dist/adapters/llm/litellm.d.ts.map +1 -1
- package/dist/adapters/llm/litellm.js +44 -0
- package/dist/adapters/llm/litellm.js.map +1 -1
- package/dist/compute/difficulty-estimator.d.ts +53 -0
- package/dist/compute/difficulty-estimator.d.ts.map +1 -0
- package/dist/compute/difficulty-estimator.js +82 -0
- package/dist/compute/difficulty-estimator.js.map +1 -0
- package/dist/compute/strategies/mixture-of-agents.d.ts +40 -0
- package/dist/compute/strategies/mixture-of-agents.d.ts.map +1 -0
- package/dist/compute/strategies/mixture-of-agents.js +110 -0
- package/dist/compute/strategies/mixture-of-agents.js.map +1 -0
- package/dist/compute/strategies/tree-of-thoughts.d.ts +48 -0
- package/dist/compute/strategies/tree-of-thoughts.d.ts.map +1 -0
- package/dist/compute/strategies/tree-of-thoughts.js +242 -0
- package/dist/compute/strategies/tree-of-thoughts.js.map +1 -0
- package/dist/compute/strategies/two-phase-orient.d.ts +72 -0
- package/dist/compute/strategies/two-phase-orient.d.ts.map +1 -0
- package/dist/compute/strategies/two-phase-orient.js +85 -0
- package/dist/compute/strategies/two-phase-orient.js.map +1 -0
- package/dist/constitution/types.d.ts +10 -10
- package/dist/container/protocol.d.ts +134 -0
- package/dist/container/protocol.d.ts.map +1 -0
- package/dist/container/protocol.js +157 -0
- package/dist/container/protocol.js.map +1 -0
- package/dist/container/runtime.d.ts +140 -0
- package/dist/container/runtime.d.ts.map +1 -0
- package/dist/container/runtime.js +256 -0
- package/dist/container/runtime.js.map +1 -0
- package/dist/events/catalogue.d.ts +46 -46
- package/dist/events/schemas/agent.completed.v1.d.ts +4 -4
- package/dist/events/schemas/agent.failed.v1.d.ts +2 -2
- package/dist/events/schemas/agent.hitl_resolved.v1.d.ts +2 -2
- package/dist/events/schemas/agent.started.v1.d.ts +2 -2
- package/dist/events/schemas/brain.skill.extracted.v1.d.ts +4 -4
- package/dist/events/schemas/cc.cost.anomaly_detected.v1.d.ts +2 -2
- package/dist/events/schemas/cc.cost.recorded.v1.d.ts +4 -4
- package/dist/events/schemas/citadel.sprint.analyzed.v1.d.ts +6 -6
- package/dist/events/schemas/citadel.sprint.closed.v1.d.ts +2 -2
- package/dist/events/schemas/forge.inbox.reply_classified.v1.d.ts +6 -6
- package/dist/events/schemas/forge.lead.qualified.v1.d.ts +2 -2
- package/dist/events/schemas/forge.outreach.sent.v1.d.ts +4 -4
- package/dist/events/schemas/incident.detected.v1.d.ts +2 -2
- package/dist/events/schemas/vauban.goal.checked.v1.d.ts +2 -2
- package/dist/events/schemas/vauban.rebalancing.checked.v1.d.ts +2 -2
- package/dist/events/schemas/vauban.tax.checked.v1.d.ts +2 -2
- package/dist/events/schemas/vauban.vault.analyzed.v1.d.ts +6 -6
- package/dist/identity/agent-persona.d.ts +73 -0
- package/dist/identity/agent-persona.d.ts.map +1 -0
- package/dist/identity/agent-persona.js +165 -0
- package/dist/identity/agent-persona.js.map +1 -0
- package/dist/identity/persona-prompt.d.ts +25 -0
- package/dist/identity/persona-prompt.d.ts.map +1 -0
- package/dist/identity/persona-prompt.js +71 -0
- package/dist/identity/persona-prompt.js.map +1 -0
- package/dist/identity/persona-schema.d.ts +120 -0
- package/dist/identity/persona-schema.d.ts.map +1 -0
- package/dist/identity/persona-schema.js +103 -0
- package/dist/identity/persona-schema.js.map +1 -0
- package/dist/index.d.ts +37 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -1
- package/dist/index.js.map +1 -1
- package/dist/loop/minimal-loop.js +293 -287
- package/dist/memory/episodic-rrf.d.ts +114 -0
- package/dist/memory/episodic-rrf.d.ts.map +1 -0
- package/dist/memory/episodic-rrf.js +148 -0
- package/dist/memory/episodic-rrf.js.map +1 -0
- package/dist/mesh/attenuation.d.ts +78 -0
- package/dist/mesh/attenuation.d.ts.map +1 -0
- package/dist/mesh/attenuation.js +141 -0
- package/dist/mesh/attenuation.js.map +1 -0
- package/dist/mesh/delegate.d.ts +96 -0
- package/dist/mesh/delegate.d.ts.map +1 -0
- package/dist/mesh/delegate.js +172 -0
- package/dist/mesh/delegate.js.map +1 -0
- package/dist/mesh/dispatcher.d.ts +119 -0
- package/dist/mesh/dispatcher.d.ts.map +1 -0
- package/dist/mesh/dispatcher.js +207 -0
- package/dist/mesh/dispatcher.js.map +1 -0
- package/dist/mesh/index.d.ts +12 -0
- package/dist/mesh/index.d.ts.map +1 -0
- package/dist/mesh/index.js +11 -0
- package/dist/mesh/index.js.map +1 -0
- package/dist/mesh/types.d.ts +30 -0
- package/dist/mesh/types.d.ts.map +1 -0
- package/dist/mesh/types.js +11 -0
- package/dist/mesh/types.js.map +1 -0
- package/dist/orchestration/ooda/skills.d.ts +104 -0
- package/dist/orchestration/ooda/skills.d.ts.map +1 -1
- package/dist/orchestration/ooda/skills.js +106 -0
- package/dist/orchestration/ooda/skills.js.map +1 -1
- package/dist/ports/bastion-action.contract.test.d.ts +11 -0
- package/dist/ports/bastion-action.contract.test.d.ts.map +1 -0
- package/dist/ports/bastion-action.contract.test.js +238 -0
- package/dist/ports/bastion-action.contract.test.js.map +1 -0
- package/dist/ports/bastion-action.d.ts +133 -0
- package/dist/ports/bastion-action.d.ts.map +1 -0
- package/dist/ports/bastion-action.js +73 -0
- package/dist/ports/bastion-action.js.map +1 -0
- package/dist/ports/brain.d.ts +31 -0
- package/dist/ports/brain.d.ts.map +1 -1
- package/dist/ports/brain.js +115 -1
- package/dist/ports/brain.js.map +1 -1
- package/dist/ports/citadel-action.contract.test.d.ts +11 -0
- package/dist/ports/citadel-action.contract.test.d.ts.map +1 -0
- package/dist/ports/citadel-action.contract.test.js +317 -0
- package/dist/ports/citadel-action.contract.test.js.map +1 -0
- package/dist/ports/citadel-action.d.ts +111 -0
- package/dist/ports/citadel-action.d.ts.map +1 -0
- package/dist/ports/citadel-action.js +62 -0
- package/dist/ports/citadel-action.js.map +1 -0
- package/dist/ports/compliance-contract.d.ts +123 -0
- package/dist/ports/compliance-contract.d.ts.map +1 -0
- package/dist/ports/compliance-contract.js +35 -0
- package/dist/ports/compliance-contract.js.map +1 -0
- package/dist/ports/db.d.ts +38 -0
- package/dist/ports/db.d.ts.map +1 -1
- package/dist/ports/db.js +88 -1
- package/dist/ports/db.js.map +1 -1
- package/dist/ports/delegation.contract.test.d.ts +9 -0
- package/dist/ports/delegation.contract.test.d.ts.map +1 -0
- package/dist/ports/delegation.contract.test.js +337 -0
- package/dist/ports/delegation.contract.test.js.map +1 -0
- package/dist/ports/delegation.d.ts +134 -0
- package/dist/ports/delegation.d.ts.map +1 -0
- package/dist/ports/delegation.js +105 -0
- package/dist/ports/delegation.js.map +1 -0
- package/dist/ports/event-bus.d.ts +29 -0
- package/dist/ports/event-bus.d.ts.map +1 -1
- package/dist/ports/event-bus.js +106 -1
- package/dist/ports/event-bus.js.map +1 -1
- package/dist/ports/federation.contract.test.d.ts +9 -0
- package/dist/ports/federation.contract.test.d.ts.map +1 -0
- package/dist/ports/federation.contract.test.js +279 -0
- package/dist/ports/federation.contract.test.js.map +1 -0
- package/dist/ports/federation.d.ts +140 -0
- package/dist/ports/federation.d.ts.map +1 -0
- package/dist/ports/federation.js +57 -0
- package/dist/ports/federation.js.map +1 -0
- package/dist/ports/index.d.ts +28 -2
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +17 -2
- package/dist/ports/index.js.map +1 -1
- package/dist/ports/llm-provider.d.ts +37 -0
- package/dist/ports/llm-provider.d.ts.map +1 -1
- package/dist/ports/llm-provider.js +99 -1
- package/dist/ports/llm-provider.js.map +1 -1
- package/dist/ports/logger.d.ts +27 -0
- package/dist/ports/logger.d.ts.map +1 -1
- package/dist/ports/logger.js +87 -0
- package/dist/ports/logger.js.map +1 -1
- package/dist/ports/manifest-registry.contract.test.d.ts +9 -0
- package/dist/ports/manifest-registry.contract.test.d.ts.map +1 -0
- package/dist/ports/manifest-registry.contract.test.js +246 -0
- package/dist/ports/manifest-registry.contract.test.js.map +1 -0
- package/dist/ports/manifest-registry.d.ts +116 -0
- package/dist/ports/manifest-registry.d.ts.map +1 -0
- package/dist/ports/manifest-registry.js +79 -0
- package/dist/ports/manifest-registry.js.map +1 -0
- package/dist/ports/observability.contract.test.d.ts +12 -0
- package/dist/ports/observability.contract.test.d.ts.map +1 -0
- package/dist/ports/observability.contract.test.js +260 -0
- package/dist/ports/observability.contract.test.js.map +1 -0
- package/dist/ports/observability.d.ts +98 -0
- package/dist/ports/observability.d.ts.map +1 -0
- package/dist/ports/observability.js +59 -0
- package/dist/ports/observability.js.map +1 -0
- package/dist/ports/outcome.d.ts +26 -0
- package/dist/ports/outcome.d.ts.map +1 -1
- package/dist/ports/outcome.js +62 -1
- package/dist/ports/outcome.js.map +1 -1
- package/dist/ports/privacy.contract.test.d.ts +12 -0
- package/dist/ports/privacy.contract.test.d.ts.map +1 -0
- package/dist/ports/privacy.contract.test.js +325 -0
- package/dist/ports/privacy.contract.test.js.map +1 -0
- package/dist/ports/privacy.d.ts +132 -0
- package/dist/ports/privacy.d.ts.map +1 -0
- package/dist/ports/privacy.js +83 -0
- package/dist/ports/privacy.js.map +1 -0
- package/dist/ports/tenant-context.contract.test.d.ts +14 -0
- package/dist/ports/tenant-context.contract.test.d.ts.map +1 -0
- package/dist/ports/tenant-context.contract.test.js +352 -0
- package/dist/ports/tenant-context.contract.test.js.map +1 -0
- package/dist/ports/tenant-context.d.ts +103 -0
- package/dist/ports/tenant-context.d.ts.map +1 -0
- package/dist/ports/tenant-context.js +48 -0
- package/dist/ports/tenant-context.js.map +1 -0
- package/dist/ports/vauban-finance-action.contract.test.d.ts +11 -0
- package/dist/ports/vauban-finance-action.contract.test.d.ts.map +1 -0
- package/dist/ports/vauban-finance-action.contract.test.js +260 -0
- package/dist/ports/vauban-finance-action.contract.test.js.map +1 -0
- package/dist/ports/vauban-finance-action.d.ts +106 -0
- package/dist/ports/vauban-finance-action.d.ts.map +1 -0
- package/dist/ports/vauban-finance-action.js +60 -0
- package/dist/ports/vauban-finance-action.js.map +1 -0
- package/dist/ports/workflow-runtime.d.ts +204 -0
- package/dist/ports/workflow-runtime.d.ts.map +1 -0
- package/dist/ports/workflow-runtime.js +72 -0
- package/dist/ports/workflow-runtime.js.map +1 -0
- package/dist/proof/cert-verify.d.ts +80 -0
- package/dist/proof/cert-verify.d.ts.map +1 -0
- package/dist/proof/cert-verify.js +178 -0
- package/dist/proof/cert-verify.js.map +1 -0
- package/dist/replay/replay.d.ts.map +1 -1
- package/dist/replay/replay.js +5 -1
- package/dist/replay/replay.js.map +1 -1
- package/dist/retry/index.d.ts +129 -0
- package/dist/retry/index.d.ts.map +1 -0
- package/dist/retry/index.js +156 -0
- package/dist/retry/index.js.map +1 -0
- package/dist/retry/presets.d.ts +39 -0
- package/dist/retry/presets.d.ts.map +1 -0
- package/dist/retry/presets.js +69 -0
- package/dist/retry/presets.js.map +1 -0
- package/dist/skill-loop/ab-runner.d.ts +67 -0
- package/dist/skill-loop/ab-runner.d.ts.map +1 -0
- package/dist/skill-loop/ab-runner.js +160 -0
- package/dist/skill-loop/ab-runner.js.map +1 -0
- package/dist/skill-loop/adoption.d.ts +67 -0
- package/dist/skill-loop/adoption.d.ts.map +1 -0
- package/dist/skill-loop/adoption.js +126 -0
- package/dist/skill-loop/adoption.js.map +1 -0
- package/dist/skill-loop/candidate.d.ts +45 -0
- package/dist/skill-loop/candidate.d.ts.map +1 -0
- package/dist/skill-loop/candidate.js +43 -0
- package/dist/skill-loop/candidate.js.map +1 -0
- package/dist/skill-loop/evaluator.d.ts +42 -0
- package/dist/skill-loop/evaluator.d.ts.map +1 -0
- package/dist/skill-loop/evaluator.js +184 -0
- package/dist/skill-loop/evaluator.js.map +1 -0
- package/dist/skill-loop/index.d.ts +27 -0
- package/dist/skill-loop/index.d.ts.map +1 -0
- package/dist/skill-loop/index.js +27 -0
- package/dist/skill-loop/index.js.map +1 -0
- package/dist/skill-loop/reflexion-replay.d.ts +87 -0
- package/dist/skill-loop/reflexion-replay.d.ts.map +1 -0
- package/dist/skill-loop/reflexion-replay.js +110 -0
- package/dist/skill-loop/reflexion-replay.js.map +1 -0
- package/dist/skill-loop/sign-off.d.ts +88 -0
- package/dist/skill-loop/sign-off.d.ts.map +1 -0
- package/dist/skill-loop/sign-off.js +146 -0
- package/dist/skill-loop/sign-off.js.map +1 -0
- package/dist/skill-loop/value-metric.d.ts +55 -0
- package/dist/skill-loop/value-metric.d.ts.map +1 -0
- package/dist/skill-loop/value-metric.js +69 -0
- package/dist/skill-loop/value-metric.js.map +1 -0
- package/dist/skill-loop/versioning.d.ts +36 -0
- package/dist/skill-loop/versioning.d.ts.map +1 -0
- package/dist/skill-loop/versioning.js +47 -0
- package/dist/skill-loop/versioning.js.map +1 -0
- package/dist/skill-manifest/anchor.d.ts +91 -0
- package/dist/skill-manifest/anchor.d.ts.map +1 -0
- package/dist/skill-manifest/anchor.js +331 -0
- package/dist/skill-manifest/anchor.js.map +1 -0
- package/dist/skill-manifest/builder.d.ts +47 -0
- package/dist/skill-manifest/builder.d.ts.map +1 -0
- package/dist/skill-manifest/builder.js +93 -0
- package/dist/skill-manifest/builder.js.map +1 -0
- package/dist/skill-manifest/index.d.ts +13 -0
- package/dist/skill-manifest/index.d.ts.map +1 -0
- package/dist/skill-manifest/index.js +9 -0
- package/dist/skill-manifest/index.js.map +1 -0
- package/dist/skill-manifest/types.d.ts +67 -0
- package/dist/skill-manifest/types.d.ts.map +1 -0
- package/dist/skill-manifest/types.js +16 -0
- package/dist/skill-manifest/types.js.map +1 -0
- package/dist/skill-manifest/verifier.d.ts +42 -0
- package/dist/skill-manifest/verifier.d.ts.map +1 -0
- package/dist/skill-manifest/verifier.js +136 -0
- package/dist/skill-manifest/verifier.js.map +1 -0
- package/dist/skills/brain-query.d.ts +4 -4
- package/dist/skills/brain-store.d.ts +6 -6
- package/dist/skills/errors.d.ts +15 -0
- package/dist/skills/errors.d.ts.map +1 -1
- package/dist/skills/errors.js +21 -0
- package/dist/skills/errors.js.map +1 -1
- package/dist/skills/hitl-request.d.ts +2 -2
- package/dist/skills/index.d.ts +3 -1
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +4 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/markdown/loader.d.ts +52 -0
- package/dist/skills/markdown/loader.d.ts.map +1 -0
- package/dist/skills/markdown/loader.js +93 -0
- package/dist/skills/markdown/loader.js.map +1 -0
- package/dist/skills/markdown/schema.d.ts +432 -0
- package/dist/skills/markdown/schema.d.ts.map +1 -0
- package/dist/skills/markdown/schema.js +121 -0
- package/dist/skills/markdown/schema.js.map +1 -0
- package/dist/skills/poc-md-loader/markdown-loader.d.ts +77 -0
- package/dist/skills/poc-md-loader/markdown-loader.d.ts.map +1 -0
- package/dist/skills/poc-md-loader/markdown-loader.js +125 -0
- package/dist/skills/poc-md-loader/markdown-loader.js.map +1 -0
- package/dist/skills/poc-md-loader/runner.d.ts +24 -0
- package/dist/skills/poc-md-loader/runner.d.ts.map +1 -0
- package/dist/skills/poc-md-loader/runner.js +57 -0
- package/dist/skills/poc-md-loader/runner.js.map +1 -0
- package/dist/skills/poc-md-loader/vitest.poc.config.d.ts +3 -0
- package/dist/skills/poc-md-loader/vitest.poc.config.d.ts.map +1 -0
- package/dist/skills/poc-md-loader/vitest.poc.config.js +13 -0
- package/dist/skills/poc-md-loader/vitest.poc.config.js.map +1 -0
- package/dist/skills/poc-md-loader/web-search/script.d.ts +33 -0
- package/dist/skills/poc-md-loader/web-search/script.d.ts.map +1 -0
- package/dist/skills/poc-md-loader/web-search/script.js +75 -0
- package/dist/skills/poc-md-loader/web-search/script.js.map +1 -0
- package/dist/skills/record-outcome.d.ts +4 -4
- package/dist/skills/send-email.d.ts.map +1 -1
- package/dist/skills/send-email.js +15 -3
- package/dist/skills/send-email.js.map +1 -1
- package/dist/skills/slack-notify.d.ts +4 -4
- package/dist/skills/starknet-balance.d.ts +1 -1
- package/dist/skills/telegram-notify.d.ts +4 -4
- package/dist/skills/web-search.d.ts +1 -1
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/test-brain-port.d.ts +4 -0
- package/dist/testing/test-brain-port.d.ts.map +1 -1
- package/dist/testing/test-brain-port.js +75 -20
- package/dist/testing/test-brain-port.js.map +1 -1
- package/dist/testing/test-event-bus.d.ts.map +1 -1
- package/dist/testing/test-event-bus.js +89 -36
- package/dist/testing/test-event-bus.js.map +1 -1
- package/dist/trace/schema.d.ts +1 -1
- package/dist/trace/schema.d.ts.map +1 -1
- package/dist/trace/schema.js +1 -1
- package/dist/trace/schema.js.map +1 -1
- package/dist/verify/formal/index.d.ts +44 -0
- package/dist/verify/formal/index.d.ts.map +1 -0
- package/dist/verify/formal/index.js +98 -0
- package/dist/verify/formal/index.js.map +1 -0
- package/dist/verify/formal/policy.d.ts +105 -0
- package/dist/verify/formal/policy.d.ts.map +1 -0
- package/dist/verify/formal/policy.js +159 -0
- package/dist/verify/formal/policy.js.map +1 -0
- package/dist/verify/formal/result.d.ts +50 -0
- package/dist/verify/formal/result.d.ts.map +1 -0
- package/dist/verify/formal/result.js +21 -0
- package/dist/verify/formal/result.js.map +1 -0
- package/dist/verify/formal/solver.d.ts +67 -0
- package/dist/verify/formal/solver.d.ts.map +1 -0
- package/dist/verify/formal/solver.js +184 -0
- package/dist/verify/formal/solver.js.map +1 -0
- package/dist/verify/formal/spec-language.d.ts +80 -0
- package/dist/verify/formal/spec-language.d.ts.map +1 -0
- package/dist/verify/formal/spec-language.js +219 -0
- package/dist/verify/formal/spec-language.js.map +1 -0
- package/docs/attestation.md +199 -0
- package/docs/identity.md +193 -0
- package/package.json +34 -17
- package/src/adapters/llm/anthropic-direct.ts +51 -0
- package/src/adapters/llm/cascade.ts +64 -19
- package/src/adapters/llm/litellm.ts +49 -0
- package/src/compute/difficulty-estimator.ts +111 -0
- package/src/compute/strategies/mixture-of-agents.ts +150 -0
- package/src/compute/strategies/tree-of-thoughts.ts +293 -0
- package/src/compute/strategies/two-phase-orient.ts +147 -0
- package/src/container/protocol.ts +243 -0
- package/src/container/runtime.ts +424 -0
- package/src/db/migrations/026_formal_verify_results.sql +30 -0
- package/src/identity/agent-persona.ts +203 -0
- package/src/identity/persona-prompt.ts +84 -0
- package/src/identity/persona-schema.ts +127 -0
- package/src/index.ts +338 -1
- package/src/memory/episodic-rrf.ts +224 -0
- package/src/mesh/attenuation.ts +190 -0
- package/src/mesh/delegate.ts +254 -0
- package/src/mesh/dispatcher.ts +301 -0
- package/src/mesh/index.ts +39 -0
- package/src/mesh/types.ts +31 -0
- package/src/orchestration/ooda/skills.ts +177 -0
- package/src/ports/bastion-action.contract.test.ts +355 -0
- package/src/ports/bastion-action.ts +198 -0
- package/src/ports/brain.ts +177 -15
- package/src/ports/citadel-action.contract.test.ts +430 -0
- package/src/ports/citadel-action.ts +174 -0
- package/src/ports/compliance-contract.ts +191 -0
- package/src/ports/db.ts +98 -0
- package/src/ports/delegation.contract.test.ts +428 -0
- package/src/ports/delegation.ts +211 -0
- package/src/ports/event-bus.ts +133 -0
- package/src/ports/federation.contract.test.ts +355 -0
- package/src/ports/federation.ts +190 -0
- package/src/ports/index.ts +186 -1
- package/src/ports/llm-provider.ts +123 -0
- package/src/ports/logger.ts +104 -0
- package/src/ports/manifest-registry.contract.test.ts +324 -0
- package/src/ports/manifest-registry.ts +188 -0
- package/src/ports/observability.contract.test.ts +315 -0
- package/src/ports/observability.ts +150 -0
- package/src/ports/outcome.ts +69 -0
- package/src/ports/privacy.contract.test.ts +413 -0
- package/src/ports/privacy.ts +207 -0
- package/src/ports/tenant-context.contract.test.ts +454 -0
- package/src/ports/tenant-context.ts +150 -0
- package/src/ports/vauban-finance-action.contract.test.ts +335 -0
- package/src/ports/vauban-finance-action.ts +166 -0
- package/src/ports/workflow-runtime.ts +327 -0
- package/src/proof/cert-verify.ts +249 -0
- package/src/replay/replay.ts +11 -8
- package/src/retry/index.ts +227 -0
- package/src/retry/presets.ts +75 -0
- package/src/skill-loop/ab-runner.ts +196 -0
- package/src/skill-loop/adoption.ts +188 -0
- package/src/skill-loop/candidate.ts +75 -0
- package/src/skill-loop/evaluator.ts +238 -0
- package/src/skill-loop/index.ts +51 -0
- package/src/skill-loop/reflexion-replay.ts +173 -0
- package/src/skill-loop/sign-off.ts +247 -0
- package/src/skill-loop/value-metric.ts +120 -0
- package/src/skill-loop/versioning.ts +75 -0
- package/src/skill-manifest/anchor.ts +401 -0
- package/src/skill-manifest/builder.ts +129 -0
- package/src/skill-manifest/index.ts +18 -0
- package/src/skill-manifest/types.ts +72 -0
- package/src/skill-manifest/verifier.ts +198 -0
- package/src/skills/errors.ts +30 -2
- package/src/skills/index.ts +19 -0
- package/src/skills/markdown/loader.ts +129 -0
- package/src/skills/markdown/schema.ts +144 -0
- package/src/skills/poc-md-loader/e2e-parity.test.ts +237 -0
- package/src/skills/poc-md-loader/markdown-loader.ts +161 -0
- package/src/skills/poc-md-loader/runner.ts +82 -0
- package/src/skills/poc-md-loader/vitest.poc.config.ts +13 -0
- package/src/skills/poc-md-loader/web-search/SKILL.md +42 -0
- package/src/skills/poc-md-loader/web-search/script.ts +109 -0
- package/src/skills/send-email.ts +15 -3
- package/src/testing/test-brain-port.ts +98 -24
- package/src/testing/test-event-bus.ts +104 -43
- package/src/trace/schema.ts +1 -1
- package/src/verify/formal/index.ts +154 -0
- package/src/verify/formal/policy.ts +253 -0
- package/src/verify/formal/result.ts +52 -0
- package/src/verify/formal/solver.ts +235 -0
- package/src/verify/formal/spec-language.ts +274 -0
package/src/ports/event-bus.ts
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
* ADR-ECO-017: Cross-product events MUST carry a DomainEvent signature.
|
|
20
20
|
* Use DomainEvent for inter-product events and CloudEvent for intra-product.
|
|
21
21
|
*
|
|
22
|
+
* OTel instrumentation: import { createTracedEventBusPort } to wrap any
|
|
23
|
+
* EventBusPort implementation with OpenTelemetry spans per publish/subscribe call.
|
|
24
|
+
* Gracefully degrades to noop spans when no OTel SDK is installed.
|
|
25
|
+
*
|
|
22
26
|
* @public
|
|
23
27
|
*/
|
|
24
28
|
|
|
@@ -179,3 +183,132 @@ export interface EventBusPort {
|
|
|
179
183
|
*/
|
|
180
184
|
pendingCount(stream: string, consumerGroup: string): Promise<number>;
|
|
181
185
|
}
|
|
186
|
+
|
|
187
|
+
// ─── Typed errors ─────────────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Thrown when an event could not be published to Redis (connection lost,
|
|
191
|
+
* stream key conflict, or OOM on XADD). The original event is available
|
|
192
|
+
* for retry or DLQ routing.
|
|
193
|
+
*/
|
|
194
|
+
export class EventPublishError extends Error {
|
|
195
|
+
/** The stream key the event was targeting. */
|
|
196
|
+
readonly stream: string;
|
|
197
|
+
/** The event id (UUIDv7) that failed to publish. */
|
|
198
|
+
readonly eventId: string;
|
|
199
|
+
|
|
200
|
+
constructor(
|
|
201
|
+
message: string,
|
|
202
|
+
stream: string,
|
|
203
|
+
eventId: string,
|
|
204
|
+
public readonly cause?: unknown
|
|
205
|
+
) {
|
|
206
|
+
super(message);
|
|
207
|
+
this.name = "EventPublishError";
|
|
208
|
+
this.stream = stream;
|
|
209
|
+
this.eventId = eventId;
|
|
210
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── OTel-traced wrapper ──────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
import type { Span } from "@opentelemetry/api";
|
|
217
|
+
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
218
|
+
|
|
219
|
+
const PORT_TRACER = trace.getTracer("vauban-agent-sdk.ports", "0.1.0");
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Wrap any EventBusPort implementation with OTel spans.
|
|
223
|
+
* Publishes and subscribeDomain calls each get a span with the event type
|
|
224
|
+
* and stream as attributes. Gracefully degrades to noop spans when no OTel
|
|
225
|
+
* SDK is installed.
|
|
226
|
+
*
|
|
227
|
+
* Usage:
|
|
228
|
+
* const raw: EventBusPort = buildRedisEventBus(...);
|
|
229
|
+
* const traced = createTracedEventBusPort(raw);
|
|
230
|
+
* await traced.publish(event, "vault.events") // emits "event-bus.publish" span
|
|
231
|
+
*/
|
|
232
|
+
export function createTracedEventBusPort(impl: EventBusPort): EventBusPort {
|
|
233
|
+
function spanName(op: string): string {
|
|
234
|
+
return `event-bus.${op}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
async publish(event, stream) {
|
|
239
|
+
return PORT_TRACER.startActiveSpan(
|
|
240
|
+
spanName("publish"),
|
|
241
|
+
{
|
|
242
|
+
attributes: {
|
|
243
|
+
"event.type": event.type,
|
|
244
|
+
"event.stream": stream,
|
|
245
|
+
"event.id": event.id,
|
|
246
|
+
"vauban.port.name": "event-bus",
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
async (span: Span) => {
|
|
250
|
+
try {
|
|
251
|
+
await impl.publish(event, stream);
|
|
252
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
253
|
+
} catch (err) {
|
|
254
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
255
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
256
|
+
if (err instanceof Error) span.recordException(err);
|
|
257
|
+
throw err;
|
|
258
|
+
} finally {
|
|
259
|
+
span.end();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
async publishWithIdempotency<T>(
|
|
266
|
+
event: Omit<DomainEvent<T>, "signature">,
|
|
267
|
+
key: string
|
|
268
|
+
) {
|
|
269
|
+
return PORT_TRACER.startActiveSpan(
|
|
270
|
+
spanName("publishWithIdempotency"),
|
|
271
|
+
{
|
|
272
|
+
attributes: {
|
|
273
|
+
"event.type": event.type,
|
|
274
|
+
"event.idempotency_key": key,
|
|
275
|
+
"vauban.port.name": "event-bus",
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
async (span: Span) => {
|
|
279
|
+
try {
|
|
280
|
+
await impl.publishWithIdempotency<T>(event, key);
|
|
281
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
282
|
+
} catch (err) {
|
|
283
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
284
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
285
|
+
if (err instanceof Error) span.recordException(err);
|
|
286
|
+
throw err;
|
|
287
|
+
} finally {
|
|
288
|
+
span.end();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
subscribeDomain<T>(
|
|
295
|
+
eventType: string,
|
|
296
|
+
handler: (event: DomainEvent<T>) => Promise<void>,
|
|
297
|
+
opts?: { groupId?: string; dlq?: string }
|
|
298
|
+
) {
|
|
299
|
+
return impl.subscribeDomain<T>(eventType, handler, opts);
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
replayFrom(streamKey, fromId) {
|
|
303
|
+
return impl.replayFrom(streamKey, fromId);
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
dlq() {
|
|
307
|
+
return impl.dlq();
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
pendingCount(stream, consumerGroup) {
|
|
311
|
+
return impl.pendingCount(stream, consumerGroup);
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FederationPort contract tests (S4 spec).
|
|
3
|
+
*
|
|
4
|
+
* Applied to any FederationPort implementation.
|
|
5
|
+
* Verifies: send→receive roundtrip, signature verification, delegation chain preservation,
|
|
6
|
+
* consistent hash routing determinism, replay protection via nonce.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, test } from "vitest";
|
|
10
|
+
import type {
|
|
11
|
+
FederationMessage,
|
|
12
|
+
FederationMessageHeader,
|
|
13
|
+
ContentClaim,
|
|
14
|
+
TransportMeta,
|
|
15
|
+
} from "./federation.js";
|
|
16
|
+
import {
|
|
17
|
+
FederationSignatureInvalidError,
|
|
18
|
+
FederationDelegationChainError,
|
|
19
|
+
FederationMessageExpiredError,
|
|
20
|
+
} from "./federation.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Factory function to create a minimal valid FederationMessage for testing.
|
|
24
|
+
*/
|
|
25
|
+
function makeFederationMessage(
|
|
26
|
+
overrides: Partial<FederationMessage> = {}
|
|
27
|
+
): FederationMessage {
|
|
28
|
+
const now = new Date().toISOString();
|
|
29
|
+
const expiresAt = new Date(Date.now() + 86400000).toISOString(); // +24h
|
|
30
|
+
|
|
31
|
+
const header: FederationMessageHeader = {
|
|
32
|
+
message_id: crypto.randomUUID(),
|
|
33
|
+
from_agent: {
|
|
34
|
+
agent_id: "vauban.cc.test@v1.0",
|
|
35
|
+
tenant_id: "test-tenant-1",
|
|
36
|
+
agent_card_url: "https://example.com/cards/test-agent",
|
|
37
|
+
},
|
|
38
|
+
to_agent: {
|
|
39
|
+
agent_id: "vauban.cc.receiver@v1.0",
|
|
40
|
+
tenant_id: "test-tenant-2",
|
|
41
|
+
},
|
|
42
|
+
correlation_id: crypto.randomUUID(),
|
|
43
|
+
timestamp: now,
|
|
44
|
+
nonce: crypto.getRandomValues(new Uint8Array(32)).toString(),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const contentClaim: ContentClaim = {
|
|
48
|
+
subject: `digest:${header.message_id}`,
|
|
49
|
+
predicate: {
|
|
50
|
+
domain: "vauban.federation.message.v1",
|
|
51
|
+
body: {
|
|
52
|
+
action: "test_message",
|
|
53
|
+
payload: { test_data: "hello world" },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
evidence: {
|
|
57
|
+
proof: "ed25519_" + crypto.randomUUID().replace(/-/g, "").slice(0, 32),
|
|
58
|
+
public_inputs: {
|
|
59
|
+
from_agent: header.from_agent.agent_id,
|
|
60
|
+
to_agent: header.to_agent.agent_id,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
temporal_frame: {
|
|
64
|
+
not_before: now,
|
|
65
|
+
not_after: expiresAt,
|
|
66
|
+
revoked_at: null,
|
|
67
|
+
},
|
|
68
|
+
revelation_mask: {
|
|
69
|
+
disclosed: ["/subject", "/predicate", "/temporal_frame"],
|
|
70
|
+
committed: [],
|
|
71
|
+
},
|
|
72
|
+
anchor: [
|
|
73
|
+
{
|
|
74
|
+
chain: "StarknetMainnet",
|
|
75
|
+
tx_hash: "0x" + crypto.randomUUID().replace(/-/g, "").slice(0, 32),
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const transport: TransportMeta = {
|
|
81
|
+
protocol: "mcp",
|
|
82
|
+
version: "1.0.0",
|
|
83
|
+
jws_signature:
|
|
84
|
+
"eyJ" +
|
|
85
|
+
crypto.randomUUID().replace(/-/g, "").slice(0, 48) +
|
|
86
|
+
".payload.signature",
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
id: header.message_id,
|
|
91
|
+
header,
|
|
92
|
+
content_claim: contentClaim,
|
|
93
|
+
transport,
|
|
94
|
+
...overrides,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const federationPortContract = (factory: () => any) => {
|
|
99
|
+
describe("FederationPort contract", () => {
|
|
100
|
+
test("send→receive roundtrip delivers message to destination tenant", async () => {
|
|
101
|
+
const port = factory();
|
|
102
|
+
const msg = makeFederationMessage();
|
|
103
|
+
|
|
104
|
+
// Send message
|
|
105
|
+
const msgId = await port.send(msg);
|
|
106
|
+
expect(msgId).toBe(msg.id);
|
|
107
|
+
|
|
108
|
+
// Receive should deliver it to the destination tenant
|
|
109
|
+
const received = await port.receive({
|
|
110
|
+
destinationTenantId: msg.header.to_agent.tenant_id,
|
|
111
|
+
timeout: 2000,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(received.length).toBeGreaterThan(0);
|
|
115
|
+
const deliveredMsg = received.find(
|
|
116
|
+
(m: FederationMessage) => m.id === msg.id
|
|
117
|
+
);
|
|
118
|
+
expect(deliveredMsg).toBeDefined();
|
|
119
|
+
expect(deliveredMsg?.header.to_agent.tenant_id).toBe(
|
|
120
|
+
msg.header.to_agent.tenant_id
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("verifyMessage rejects tampered signature", async () => {
|
|
125
|
+
const port = factory();
|
|
126
|
+
const msg = makeFederationMessage();
|
|
127
|
+
|
|
128
|
+
// Tamper with the signature
|
|
129
|
+
const tamperedMsg: FederationMessage = {
|
|
130
|
+
...msg,
|
|
131
|
+
transport: {
|
|
132
|
+
...msg.transport,
|
|
133
|
+
jws_signature: "invalid_signature_header.payload.signature",
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const result = await port.verifyMessage(tamperedMsg);
|
|
138
|
+
expect(result.valid).toBe(false);
|
|
139
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("verifyMessage accepts valid message signature and structure", async () => {
|
|
143
|
+
const port = factory();
|
|
144
|
+
const msg = makeFederationMessage();
|
|
145
|
+
|
|
146
|
+
// For this test, we mock a proper signature check
|
|
147
|
+
// In real implementation, this would verify Ed25519
|
|
148
|
+
const result = await port.verifyMessage(msg);
|
|
149
|
+
|
|
150
|
+
// Result should indicate validation (or require real crypto)
|
|
151
|
+
// This test is structural — real crypto is adapter responsibility
|
|
152
|
+
expect(result.signer_agent_id).toBeDefined();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("delegation chain narrowing is preserved (R-1 invariant)", async () => {
|
|
156
|
+
const port = factory();
|
|
157
|
+
|
|
158
|
+
// Message with delegation chain showing authority narrowing
|
|
159
|
+
const msg = makeFederationMessage({
|
|
160
|
+
delegation_chain: [
|
|
161
|
+
{
|
|
162
|
+
id: crypto.randomUUID(),
|
|
163
|
+
parent_id: undefined,
|
|
164
|
+
scope: {
|
|
165
|
+
capabilities: ["read", "write"],
|
|
166
|
+
constraints: [],
|
|
167
|
+
jurisdictions: ["EU.v1"],
|
|
168
|
+
expires_at: new Date(Date.now() + 86400000).toISOString(),
|
|
169
|
+
},
|
|
170
|
+
ed25519_sig: "sig1",
|
|
171
|
+
ttl: 86400,
|
|
172
|
+
issuer: "root-agent",
|
|
173
|
+
holder: "intermediate-agent",
|
|
174
|
+
} as any,
|
|
175
|
+
{
|
|
176
|
+
id: crypto.randomUUID(),
|
|
177
|
+
parent_id: "parent1",
|
|
178
|
+
scope: {
|
|
179
|
+
capabilities: ["read"], // narrowed from ["read", "write"]
|
|
180
|
+
constraints: [],
|
|
181
|
+
jurisdictions: ["EU.v1"],
|
|
182
|
+
expires_at: new Date(Date.now() + 86400000).toISOString(),
|
|
183
|
+
},
|
|
184
|
+
ed25519_sig: "sig2",
|
|
185
|
+
ttl: 86400,
|
|
186
|
+
issuer: "intermediate-agent",
|
|
187
|
+
holder: "delegated-agent",
|
|
188
|
+
} as any,
|
|
189
|
+
],
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Send and verify
|
|
193
|
+
const msgId = await port.send(msg);
|
|
194
|
+
expect(msgId).toBe(msg.id);
|
|
195
|
+
|
|
196
|
+
const result = await port.verifyMessage(msg);
|
|
197
|
+
// Delegation chain should be preserved
|
|
198
|
+
expect(result.chain_depth).toBe(2);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("routing by consistent hash is deterministic", async () => {
|
|
202
|
+
const port = factory();
|
|
203
|
+
|
|
204
|
+
// Two messages from same tenant should hash to same node
|
|
205
|
+
const msg1 = makeFederationMessage({
|
|
206
|
+
header: {
|
|
207
|
+
...makeFederationMessage().header,
|
|
208
|
+
message_id: "msg-1",
|
|
209
|
+
} as any,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const msg2 = makeFederationMessage({
|
|
213
|
+
header: {
|
|
214
|
+
...makeFederationMessage().header,
|
|
215
|
+
message_id: "msg-2",
|
|
216
|
+
to_agent: { ...msg1.header.to_agent }, // same dest tenant
|
|
217
|
+
} as any,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Both sent to same tenant
|
|
221
|
+
await port.send(msg1);
|
|
222
|
+
await port.send(msg2);
|
|
223
|
+
|
|
224
|
+
// Receive from that tenant — both should arrive
|
|
225
|
+
const received = await port.receive({
|
|
226
|
+
destinationTenantId: msg1.header.to_agent.tenant_id,
|
|
227
|
+
maxMessages: 10,
|
|
228
|
+
timeout: 1000,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const ids = received.map((m: FederationMessage) => m.id);
|
|
232
|
+
expect(ids).toContain("msg-1");
|
|
233
|
+
expect(ids).toContain("msg-2");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("expired message is rejected by verifyMessage", async () => {
|
|
237
|
+
const port = factory();
|
|
238
|
+
const now = new Date();
|
|
239
|
+
const expired = new Date(now.getTime() - 3600000); // 1h ago
|
|
240
|
+
|
|
241
|
+
const msg = makeFederationMessage({
|
|
242
|
+
content_claim: {
|
|
243
|
+
...makeFederationMessage().content_claim,
|
|
244
|
+
temporal_frame: {
|
|
245
|
+
not_before: new Date(now.getTime() - 7200000).toISOString(),
|
|
246
|
+
not_after: expired.toISOString(),
|
|
247
|
+
revoked_at: null,
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const result = await port.verifyMessage(msg);
|
|
253
|
+
expect(result.valid).toBe(false);
|
|
254
|
+
expect(result.errors.some((e: string) => e.includes("expired"))).toBe(
|
|
255
|
+
true
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("ack marks message as consumed and prevents redelivery", async () => {
|
|
260
|
+
const port = factory();
|
|
261
|
+
const msg = makeFederationMessage();
|
|
262
|
+
|
|
263
|
+
await port.send(msg);
|
|
264
|
+
|
|
265
|
+
// First receive
|
|
266
|
+
let received = await port.receive({
|
|
267
|
+
destinationTenantId: msg.header.to_agent.tenant_id,
|
|
268
|
+
timeout: 1000,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
expect(received.length).toBeGreaterThan(0);
|
|
272
|
+
const msgId = received[0]!.id;
|
|
273
|
+
|
|
274
|
+
// Ack it
|
|
275
|
+
await port.ack(msgId);
|
|
276
|
+
|
|
277
|
+
// Second receive should NOT return the same message
|
|
278
|
+
received = await port.receive({
|
|
279
|
+
destinationTenantId: msg.header.to_agent.tenant_id,
|
|
280
|
+
timeout: 500,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const acked = received.find((m: FederationMessage) => m.id === msgId);
|
|
284
|
+
expect(acked).toBeUndefined();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("nack returns message to queue (max 3 attempts)", async () => {
|
|
288
|
+
const port = factory();
|
|
289
|
+
const msg = makeFederationMessage();
|
|
290
|
+
|
|
291
|
+
await port.send(msg);
|
|
292
|
+
|
|
293
|
+
const received = await port.receive({
|
|
294
|
+
destinationTenantId: msg.header.to_agent.tenant_id,
|
|
295
|
+
timeout: 1000,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
expect(received.length).toBeGreaterThan(0);
|
|
299
|
+
const msgId = received[0]!.id;
|
|
300
|
+
|
|
301
|
+
// Nack it (1st time)
|
|
302
|
+
await port.nack(msgId, "retry");
|
|
303
|
+
|
|
304
|
+
// Should reappear in next receive
|
|
305
|
+
const redelivered = await port.receive({
|
|
306
|
+
destinationTenantId: msg.header.to_agent.tenant_id,
|
|
307
|
+
timeout: 1000,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const redeliveredMsg = redelivered.find(
|
|
311
|
+
(m: FederationMessage) => m.id === msgId
|
|
312
|
+
);
|
|
313
|
+
expect(redeliveredMsg).toBeDefined();
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// ─── Apply contract to test implementations ────────────────────────────────
|
|
319
|
+
|
|
320
|
+
// Placeholder: real implementations will hook this up
|
|
321
|
+
// federationPortContract(() => new MyFederationPortImpl());
|
|
322
|
+
|
|
323
|
+
// ─── Error type tests ─────────────────────────────────────────────────────
|
|
324
|
+
|
|
325
|
+
describe("FederationPort — error types", () => {
|
|
326
|
+
test("FederationSignatureInvalidError captures messageId and reason", () => {
|
|
327
|
+
const err = new FederationSignatureInvalidError(
|
|
328
|
+
"msg-123",
|
|
329
|
+
"Ed25519 verification failed"
|
|
330
|
+
);
|
|
331
|
+
expect(err.messageId).toBe("msg-123");
|
|
332
|
+
expect(err.reason).toContain("Ed25519");
|
|
333
|
+
expect(err.name).toBe("FederationSignatureInvalidError");
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("FederationDelegationChainError captures depth and reason", () => {
|
|
337
|
+
const err = new FederationDelegationChainError(
|
|
338
|
+
"msg-456",
|
|
339
|
+
3,
|
|
340
|
+
"Narrowing invariant violated"
|
|
341
|
+
);
|
|
342
|
+
expect(err.messageId).toBe("msg-456");
|
|
343
|
+
expect(err.chainDepth).toBe(3);
|
|
344
|
+
expect(err.reason).toContain("Narrowing");
|
|
345
|
+
expect(err.name).toBe("FederationDelegationChainError");
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test("FederationMessageExpiredError captures temporal bounds", () => {
|
|
349
|
+
const notAfter = "2025-01-01T00:00:00Z";
|
|
350
|
+
const err = new FederationMessageExpiredError("msg-789", notAfter);
|
|
351
|
+
expect(err.messageId).toBe("msg-789");
|
|
352
|
+
expect(err.notAfter).toBe(notAfter);
|
|
353
|
+
expect(err.name).toBe("FederationMessageExpiredError");
|
|
354
|
+
});
|
|
355
|
+
});
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FederationPort — agent-to-agent messaging with cryptographic provenance (S4 spec).
|
|
3
|
+
*
|
|
4
|
+
* Implements A2A v1.0 transport (JWS RFC 7515) superset with Vauban Claim sextuplet
|
|
5
|
+
* payload. Messages routed by consistent hashing on tenant_id. Delegation chains preserved.
|
|
6
|
+
* Ed25519 signatures over CBOR canonical encoding.
|
|
7
|
+
*
|
|
8
|
+
* Spec: docs/plans/PreCarre/canonical/specs/S4.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ─── Message types (S4 §3) ────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export type Protocol = "mcp" | "a2a-jsonrpc" | "a2a-grpc" | "http-gateway";
|
|
14
|
+
|
|
15
|
+
export interface AgentRef {
|
|
16
|
+
readonly agent_id: string; // e.g. "vauban.cc.synthesizer@v2.1"
|
|
17
|
+
readonly tenant_id: string; // crosslink S2
|
|
18
|
+
readonly agent_card_url?: string; // A2A discovery URL
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface FederationMessageHeader {
|
|
22
|
+
readonly message_id: string; // UUIDv7, timestamp-ordered
|
|
23
|
+
readonly from_agent: AgentRef;
|
|
24
|
+
readonly to_agent: AgentRef;
|
|
25
|
+
readonly correlation_id: string; // conversation tracking
|
|
26
|
+
readonly causation_id?: string; // parent message in chain
|
|
27
|
+
readonly timestamp: string; // ISO 8601
|
|
28
|
+
readonly nonce: string; // 32-byte hex, replay protection
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TransportMeta {
|
|
32
|
+
readonly protocol: Protocol;
|
|
33
|
+
readonly version: string; // SemVer
|
|
34
|
+
readonly jws_signature: string; // RFC 7515 JWS (outer, transport-level)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Content claim payload — Vauban Claim sextuplet.
|
|
39
|
+
* Domain normalized to "vauban.federation.message.v1" for all S4 messages.
|
|
40
|
+
*/
|
|
41
|
+
export interface ContentClaim {
|
|
42
|
+
readonly subject: string; // ArtefactDigest(message_id)
|
|
43
|
+
readonly predicate: {
|
|
44
|
+
readonly domain: "vauban.federation.message.v1";
|
|
45
|
+
readonly body: Record<string, unknown>; // action + payload
|
|
46
|
+
};
|
|
47
|
+
readonly evidence: {
|
|
48
|
+
readonly proof: string; // Ed25519 hex signature
|
|
49
|
+
readonly public_inputs: Record<string, unknown>;
|
|
50
|
+
};
|
|
51
|
+
readonly temporal_frame: {
|
|
52
|
+
readonly not_before: string; // ISO 8601
|
|
53
|
+
readonly not_after: string; // ISO 8601
|
|
54
|
+
readonly revoked_at?: string | null;
|
|
55
|
+
};
|
|
56
|
+
readonly revelation_mask: {
|
|
57
|
+
readonly disclosed: string[]; // JSON pointers
|
|
58
|
+
readonly committed: string[];
|
|
59
|
+
};
|
|
60
|
+
readonly anchor: ReadonlyArray<{
|
|
61
|
+
readonly chain: "StarknetMainnet" | "StarknetL3Glacis";
|
|
62
|
+
readonly tx_hash?: string;
|
|
63
|
+
}>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface FederationMessage {
|
|
67
|
+
readonly id: string;
|
|
68
|
+
readonly header: FederationMessageHeader;
|
|
69
|
+
readonly content_claim: ContentClaim;
|
|
70
|
+
readonly transport: TransportMeta;
|
|
71
|
+
readonly delegation_chain?: ReadonlyArray<{
|
|
72
|
+
readonly id: string;
|
|
73
|
+
readonly parent_id?: string;
|
|
74
|
+
readonly scope: {
|
|
75
|
+
readonly capabilities: ReadonlyArray<string>;
|
|
76
|
+
readonly constraints: ReadonlyArray<unknown>;
|
|
77
|
+
readonly jurisdictions: ReadonlyArray<string>;
|
|
78
|
+
readonly expires_at: string;
|
|
79
|
+
};
|
|
80
|
+
readonly ed25519_sig: string;
|
|
81
|
+
}>; // optional narrow chain
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type MessageId = string;
|
|
85
|
+
|
|
86
|
+
// ─── Verification result ──────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
export interface VerifyResult {
|
|
89
|
+
readonly valid: boolean;
|
|
90
|
+
readonly errors: string[]; // empty if valid
|
|
91
|
+
readonly signer_agent_id?: string; // extracted from signature
|
|
92
|
+
readonly chain_depth?: number; // delegation chain length if present
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── Receive options ─────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
export interface ReceiveOpts {
|
|
98
|
+
readonly destinationTenantId?: string; // filter by dest
|
|
99
|
+
readonly timeout?: number; // ms, default 5000
|
|
100
|
+
readonly maxMessages?: number; // batch size, default 10
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─── FederationPort interface ────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
export interface FederationPort {
|
|
106
|
+
/**
|
|
107
|
+
* Send a message to another agent.
|
|
108
|
+
* Message is JWS-signed and routed by consistent hashing on destination tenant_id.
|
|
109
|
+
* Returns message_id on success.
|
|
110
|
+
*/
|
|
111
|
+
send(
|
|
112
|
+
message: FederationMessage,
|
|
113
|
+
opts?: { timeoutMs?: number }
|
|
114
|
+
): Promise<MessageId>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Receive pending messages (subscriber pattern).
|
|
118
|
+
* Messages are routed to this agent's tenant by consistent hash.
|
|
119
|
+
* Returns empty array if no messages within timeout window.
|
|
120
|
+
*/
|
|
121
|
+
receive(opts?: ReceiveOpts): Promise<FederationMessage[]>;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Verify JWS signature and Claim sextuplet integrity.
|
|
125
|
+
* Checks Ed25519 signature, CBOR canonicalization, temporal bounds, delegation chain narrowing.
|
|
126
|
+
* Does NOT verify on-chain anchor (separate concern).
|
|
127
|
+
*/
|
|
128
|
+
verifyMessage(msg: FederationMessage): Promise<VerifyResult>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Acknowledge a received message to mark it as consumed.
|
|
132
|
+
* Prevents redelivery.
|
|
133
|
+
*/
|
|
134
|
+
ack(messageId: MessageId): Promise<void>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Nack a message to return it to the queue (poison pill protection).
|
|
138
|
+
* Max 3 nacks per message, then dead-letter.
|
|
139
|
+
*/
|
|
140
|
+
nack(messageId: MessageId, reason?: string): Promise<void>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── Typed errors ─────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
export class FederationSignatureInvalidError extends Error {
|
|
146
|
+
constructor(
|
|
147
|
+
public readonly messageId: string,
|
|
148
|
+
public readonly reason: string
|
|
149
|
+
) {
|
|
150
|
+
super(`Federation signature invalid (${messageId}): ${reason}`);
|
|
151
|
+
this.name = "FederationSignatureInvalidError";
|
|
152
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export class FederationRoutingError extends Error {
|
|
157
|
+
constructor(
|
|
158
|
+
public readonly tenantId: string,
|
|
159
|
+
public readonly reason: string
|
|
160
|
+
) {
|
|
161
|
+
super(`Federation routing failed for tenant ${tenantId}: ${reason}`);
|
|
162
|
+
this.name = "FederationRoutingError";
|
|
163
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export class FederationDelegationChainError extends Error {
|
|
168
|
+
constructor(
|
|
169
|
+
public readonly messageId: string,
|
|
170
|
+
public readonly chainDepth: number,
|
|
171
|
+
public readonly reason: string
|
|
172
|
+
) {
|
|
173
|
+
super(
|
|
174
|
+
`Federation delegation chain invalid (${messageId}, depth=${chainDepth}): ${reason}`
|
|
175
|
+
);
|
|
176
|
+
this.name = "FederationDelegationChainError";
|
|
177
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export class FederationMessageExpiredError extends Error {
|
|
182
|
+
constructor(
|
|
183
|
+
public readonly messageId: string,
|
|
184
|
+
public readonly notAfter: string
|
|
185
|
+
) {
|
|
186
|
+
super(`Federation message expired (${messageId}): not_after=${notAfter}`);
|
|
187
|
+
this.name = "FederationMessageExpiredError";
|
|
188
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
189
|
+
}
|
|
190
|
+
}
|