From fd76d77599b045dac9793fac368b9e6b2a993ed5 Mon Sep 17 00:00:00 2001 From: RNA4219 Date: Fri, 3 Jul 2026 06:49:52 +0900 Subject: [PATCH 1/4] chore: add code-to-gate policy config --- .ctg/policy.yaml | 37 +++++++++++++++++++++++++++++++++++++ .ctg/suppressions.yaml | 8 ++++++++ 2 files changed, 45 insertions(+) create mode 100644 .ctg/policy.yaml create mode 100644 .ctg/suppressions.yaml diff --git a/.ctg/policy.yaml b/.ctg/policy.yaml new file mode 100644 index 0000000..55c925f --- /dev/null +++ b/.ctg/policy.yaml @@ -0,0 +1,37 @@ +version: ctg/v1 + +policyId: agent-protocols-policy +description: Quality policy for agent-protocols repository + +blocking: + severity: + critical: true + high: false + medium: false + low: false + category: + auth: false + payment: false + validation: false + data: false + config: false + maintainability: false + testing: false + compatibility: false + releaseRisk: false + security: false + rules: + count: + criticalMax: 0 + highMax: 100 + mediumMax: 100 + lowMax: 100 + +readiness: + criticalFindingStatus: needs_review + requireLlm: false + allowSuppressed: true + +suppression: + file: .ctg/suppressions.yaml + expiryWarningDays: 30 \ No newline at end of file diff --git a/.ctg/suppressions.yaml b/.ctg/suppressions.yaml new file mode 100644 index 0000000..b3d55d7 --- /dev/null +++ b/.ctg/suppressions.yaml @@ -0,0 +1,8 @@ +version: ctg/v1 + +suppressions: + # LARGE_MODULE suppression + - rule_id: LARGE_MODULE + path: src/validation/semantic-validator.ts + reason: "Semantic validator with 22 validation rules. Split into rule-specific validators when exceeds 30 rules." + expiry: 2027-05-02 \ No newline at end of file From 42a28e7d01bfd53945c2f2647292420916c194c2 Mon Sep 17 00:00:00 2001 From: RNA4219 Date: Sun, 12 Jul 2026 03:51:25 +0900 Subject: [PATCH 2/4] feat: implement agent-protocols v2 canonical package --- .github/workflows/ci.yml | 62 + README.md | 146 +- docs/README-en.md | 150 +-- docs/README-ja.md | 154 +-- docs/operations.md | 185 +-- docs/protocol.md | 244 +--- docs/requirements.md | 877 +----------- eslint.config.mjs | 12 + package-lock.json | 1797 +++++++++++++++++++++++-- package.json | 45 +- schemas/v2/Acceptance.schema.json | 33 + schemas/v2/CloudEvent.schema.json | 22 + schemas/v2/Evidence.schema.json | 108 ++ schemas/v2/IntentContract.schema.json | 25 + schemas/v2/PublishGate.schema.json | 42 + schemas/v2/TaskSeed.schema.json | 37 + schemas/v2/common.schema.json | 16 + scripts/generate-types.mjs | 31 + scripts/package-smoke.mjs | 31 + src/cli/index.ts | 33 + src/errors.ts | 20 + src/events.ts | 52 + src/gates.ts | 119 ++ src/generated/contracts.ts | 137 ++ src/generated/schema-manifest.ts | 100 ++ src/graph.ts | 131 ++ src/id.ts | 58 + src/index.ts | 9 + src/migration/index.ts | 217 +++ src/policy.ts | 84 ++ src/schema-registry.ts | 39 + src/validation-types.ts | 22 + src/validation-v2.ts | 143 ++ tests/v2/protocol-v2.test.ts | 141 ++ tsconfig.json | 6 +- 35 files changed, 3580 insertions(+), 1748 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 eslint.config.mjs create mode 100644 schemas/v2/Acceptance.schema.json create mode 100644 schemas/v2/CloudEvent.schema.json create mode 100644 schemas/v2/Evidence.schema.json create mode 100644 schemas/v2/IntentContract.schema.json create mode 100644 schemas/v2/PublishGate.schema.json create mode 100644 schemas/v2/TaskSeed.schema.json create mode 100644 schemas/v2/common.schema.json create mode 100644 scripts/generate-types.mjs create mode 100644 scripts/package-smoke.mjs create mode 100644 src/cli/index.ts create mode 100644 src/errors.ts create mode 100644 src/events.ts create mode 100644 src/gates.ts create mode 100644 src/generated/contracts.ts create mode 100644 src/generated/schema-manifest.ts create mode 100644 src/graph.ts create mode 100644 src/id.ts create mode 100644 src/index.ts create mode 100644 src/migration/index.ts create mode 100644 src/policy.ts create mode 100644 src/schema-registry.ts create mode 100644 src/validation-types.ts create mode 100644 src/validation-v2.ts create mode 100644 tests/v2/protocol-v2.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..34552fd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: agent-protocols-v2 + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + protocol: + runs-on: ubuntu-latest + strategy: + matrix: + node: [20, 22] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: npm + - run: npm ci + - run: npm run generate:check + - run: npm run typecheck + - run: npm run lint + - run: npm test + - run: npm run build + - run: npm run test:package + + migration: + runs-on: ubuntu-latest + needs: protocol + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm test -- --run tests/v2/protocol-v2.test.ts + + shipyard-conformance: + runs-on: ubuntu-latest + needs: protocol + steps: + - uses: actions/checkout@v4 + with: + path: agent-protocols + - uses: actions/checkout@v4 + with: + repository: RNA4219/shipyard-cp + path: shipyard-cp + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Build beta package + working-directory: agent-protocols + run: npm ci && npm run build && npm pack --pack-destination ../shipyard-cp + - name: Install and typecheck Shipyard + working-directory: shipyard-cp + run: npm install --no-audit --no-fund ./rna4219-agent-protocols-2.0.0-beta.1.tgz && npm run check diff --git a/README.md b/README.md index 542f037..9296744 100644 --- a/README.md +++ b/README.md @@ -1,126 +1,56 @@ -# agent-protocols +# @rna4219/agent-protocols -Contract-driven AI workflow protocol specifications. +AI workflow契約の唯一の正本です。v2は破壊的変更であり、Node.js 20以上、ESM、公開 npm scoped package +として配布します。 -## Purpose +- package: `@rna4219/agent-protocols@2.0.0-beta.1` +- 正本Schema: [schemas/v2](./schemas/v2) +- v1入力Schema: [schemas](./schemas)(移行専用) +- 正本仕様: [docs/requirements.md](./docs/requirements.md) +- 参照runtime: [Agent_tools/shipyard-cp](../Agent_tools/shipyard-cp) -Defines 5 contract types for AI agent orchestration: -- `IntentContract` - Intent request with capability requirements -- `TaskSeed` - Executable work unit -- `Acceptance` - Execution verification result -- `PublishGate` - Approval gate for publish decisions -- `Evidence` - Immutable execution record +## 契約フロー -## File Structure +`IntentContract -> TaskSeed -> Acceptance -> PublishGate -> Evidence` -``` -schemas/ # JSON Schema definitions -examples/ # Sample JSON files -src/validation/ # Semantic validator -tests/ # Test files -scripts/ # Utility scripts -docs/ # Documentation -``` - -## Contract Flow - -``` -IntentContract -> TaskSeed -> Acceptance -> PublishGate -> Evidence - IC-xxx -> TS-xxx -> AC-xxx -> PG-xxx -> EV-xxx -``` +共通メタデータは `schemaVersion: "2.0.0"`、種別付きULID(例: +`Acceptance_01J...`)、`revision`、RFC 3339 UTC時刻、`lifecycle`を使います。 +イベントはCloudEvents 1.0です。Evidenceはfinal/revision 1/不変です。 -## ID Prefixes +## Public API -| Kind | Prefix | Pattern | -|---|---|---| -| IntentContract | IC | `^IC-[0-9]{3,}$` | -| TaskSeed | TS | `^TS-[0-9]{3,}$` | -| Acceptance | AC | `^AC-[0-9]{3,}$` | -| PublishGate | PG | `^PG-[0-9]{3,}$` | -| Evidence | EV | `^EV-[0-9]{3,}$` | +`src`から次を公開します。 -## States +- `safeParseContract` / `parseContract` +- `safeParseEvent` / `parseEvent` +- `validateTransition` / `validateContractGraph` +- `deriveGenerationPolicy` / `assessPolicy` +- `createPublishGate` / `applyApproval` / `expireGate` +- `createContractId` / `createContractEvent` -`Draft -> Active -> Frozen -> Published -> Superseded -> Revoked -> Archived` +safe APIのエラーは `{ code, path, message, source }` です。未知のkind、capability、roleはfail-closedで拒否します。 -## Approval Rules - -| riskLevel | requiredApprovals | autoApproved | -|---|---|---| -| low | [] | true | -| medium | [] | true | -| high | [project_lead, security_reviewer] | false | -| critical | [project_lead, security_reviewer, release_manager] | false | - -## Capabilities - -``` -read_repo, write_repo, install_deps, network_access, read_secrets, publish_release -``` +## v1移行 -## Generation Policy Derivation +移行CLIは新規の絶対出力先だけを受け付け、既存出力を上書きしません。 +```powershell +agent-protocols migrate-v1 <絶対入力パス> --namespace <名前空間> --out <絶対出力ディレクトリ> ``` -IF capabilities IN [read_repo] OR [read_repo, write_repo]: - auto_activate = true - requiredActivationApprovals = [] -ELSE IF install_deps OR network_access OR read_secrets IN capabilities: - auto_activate = false - requiredActivationApprovals = [project_lead, security_reviewer] -ELSE IF publish_release IN capabilities: - auto_activate = false - requiredActivationApprovals = [project_lead, release_manager] -``` - -## Risk Level Derivation -``` -IF productionDataAccess OR externalSecretTransmission OR legalConcern OR rollbackImpossible: - riskLevel = critical -ELSE IF install_deps OR network_access OR read_secrets OR publish_release IN capabilities: - riskLevel = high -ELSE IF write_repo IN capabilities: - riskLevel = medium -ELSE: - riskLevel = low -``` +出力は `contracts.v2.jsonl`、`id-map.json`、`migration-report.json`です。 -## Commands +## 開発 -```bash -npm install # Install dependencies -npm test # Run all tests (83 tests) -npx tsx scripts/demo.ts # Run demo script +```powershell +npm install +npm test +npm run typecheck +npm run lint +npm run build +npm run generate:check +npm run test:package ``` -## Human Documentation - -- [README (Japanese)](docs/README-ja.md) -- [README (English)](docs/README-en.md) - -## Source of Truth - -[docs/requirements.md](docs/requirements.md) is the authoritative specification. - -## Integrations - -- [`workflow-cookbook`](C:/Users/ryo-n/Codex_dev/workflow-cookbook/README.md) - can emit `Evidence` records through its `StructuredLogger` plugin system. -- Reference plugin guide: - [`tools/protocols/README.md`](C:/Users/ryo-n/Codex_dev/workflow-cookbook/tools/protocols/README.md) -- Reference plugin config sample: - [`examples/inference_plugins.agent_protocol.sample.json`](C:/Users/ryo-n/Codex_dev/workflow-cookbook/examples/inference_plugins.agent_protocol.sample.json) -- Reference Evidence consumer sample: - [`examples/agent_protocol_evidence_consumer.sample.py`](C:/Users/ryo-n/Codex_dev/workflow-cookbook/examples/agent_protocol_evidence_consumer.sample.py) - -## Key Files - -| Path | Purpose | -|---|---| -| [schemas/](schemas/) | JSON Schema definitions | -| [src/validation/](src/validation/) | Semantic validation logic | -| [docs/requirements.md](docs/requirements.md) | Authoritative requirements | -| [docs/protocol.md](docs/protocol.md) | Protocol specification | -| [docs/operations.md](docs/operations.md) | Operations policy | -| [docs/RUNBOOK.md](docs/RUNBOOK.md) | Runbook | -| [docs/BLUEPRINT.md](docs/BLUEPRINT.md) | Blueprint | +契約Schema・型・検証・policy・生成規則は本repoが所有します。ShipyardはDB、イベント配送、retry/lock、scheduler、 +worker実行、runtime adapterだけを所有し、契約判定を重複実装しません。 diff --git a/docs/README-en.md b/docs/README-en.md index 55e45c9..f0d7c78 100644 --- a/docs/README-en.md +++ b/docs/README-en.md @@ -1,144 +1,14 @@ -# agent-protocols +# agent-protocols v2 -Contract-driven AI workflow protocol specifications. +This is the entry point for `@rna4219/agent-protocols` v2. -## Overview +The canonical repository is `C:\Users\ryo-n\Codex_dev\agent-protocols`. It owns schemas, generated +TypeScript types, strict AJV/schema validation, semantic/reference validation, policy decisions, +PublishGate construction, CloudEvents construction, and v1 migration rules. -`agent-protocols` defines specifications for managing AI agent tasks using contract-based orchestration. It specifies 5 contract types, state transitions, and approval workflows. +`C:\Users\ryo-n\Codex_dev\Agent_tools\shipyard-cp` is the reference runtime/control plane. It owns persistence, +event delivery, retry/idempotency control, locks/leases/heartbeats, deadline scheduling, worker execution, +and runtime-specific adapters. -## Why this exists - -This project exists because AI agent operations often become hard to reason about when the system does not clearly define what an agent is allowed to do, when human approval is required, and how execution evidence should be recorded. - -In practice, teams often run into problems like these: - -- prompts act as the only contract between request and execution -- low-risk and high-risk operations are not separated clearly -- manual approval and automatic execution are mixed together -- audit trails are incomplete or inconsistent -- the team wants to grow from single-agent runs to multi-agent or swarm workflows without a shared control model - -`agent-protocols` is meant to reduce that ambiguity by separating intent, executable work units, verification, publish decisions, and immutable evidence into explicit contracts. - -## When to use it - -This repository is a good fit when you want to: - -- design a control plane for coding-focused AI agents -- define schemas and validators before building implementation details -- introduce gated execution with approval boundaries -- preserve auditability and reproducibility -- support both single-agent execution and future multi-agent orchestration - -If you only need lightweight one-off automation and do not need approval, audit, state transitions, or reproducibility requirements, this specification may be heavier than necessary. - -## Benefits - -- separates intent, execution, verification, approval, and evidence into explicit responsibilities -- makes low / medium / high / critical handling predictable -- standardizes when execution can be automatic versus human-gated -- supports a clean split between JSON Schema validation and semantic validation -- makes the system easier to onboard to with Birdseye and supporting docs -- gives teams a reusable protocol that can survive runtime or agent changes - -## Target Audience - -- Product Owners: Refer to requirements layer -- Implementers: Refer to requirements + protocol specification layers -- Operators/Auditors: Refer to operations policy layer - -## Document Structure - -| Document | Role | -|---|---| -| [BLUEPRINT.md](BLUEPRINT.md) | Overall purpose, non-goals, design principles | -| [requirements.md](requirements.md) | Authoritative requirements | -| [protocol.md](protocol.md) | Protocol specification for implementers | -| [operations.md](operations.md) | Operations and audit policies | -| [implementation-prep.md](implementation-prep.md) | Implementation preparation guide | -| [RUNBOOK.md](RUNBOOK.md) | Runbook | -| [CHECKLISTS.md](CHECKLISTS.md) | Checklists | -| [INSPECTION.md](INSPECTION.md) | Inspection report | - -## Contract Flow - -``` -IntentContract → TaskSeed → Acceptance → PublishGate → Evidence - (intent) → (task) → (verify) → (approve) → (record) -``` - -1. **IntentContract**: Define user intent and required capabilities -2. **TaskSeed**: Decompose into executable work units -3. **Acceptance**: Verify execution results -4. **PublishGate**: Approve or reject publication -5. **Evidence**: Record execution audit trail - -## Approval Rules - -| Risk Level | Required Approvals | Auto-Approved | -|---|---|---| -| low | none | ✓ | -| medium | none | ✓ | -| high | project_lead, security_reviewer | ✗ | -| critical | project_lead, security_reviewer, release_manager | ✗ | - -## Capabilities - -| Capability | Description | -|---|---| -| `read_repo` | Repository read access | -| `write_repo` | Repository write access | -| `install_deps` | Install dependencies | -| `network_access` | Network access | -| `read_secrets` | Read secrets | -| `publish_release` | Publish releases | - -## State Transitions - -``` -Draft → Active → Frozen → Published → Superseded → Revoked → Archived -``` - -- **Draft**: Created, editable -- **Active**: Ready for execution -- **Frozen**: Paused, needs investigation -- **Published**: Finalized -- **Superseded**: Replaced by newer version -- **Revoked**: Invalidated -- **Archived**: Stored for records - -## Implementation Status - -| Milestone | Status | Content | -|---|---|---| -| M1: Contract Baseline | ✅ | 6 JSON Schemas, 6 sample files | -| M2: Validation Baseline | ✅ | Semantic validator | -| M3: Orchestration Baseline | ✅ | Orchestrator | -| M4: Approval Baseline | ✅ | Policy engine | - -## Quick Start - -```bash -# Install dependencies -npm install - -# Run tests -npm test - -# Run demo -npx tsx scripts/demo.ts -``` - -## Test Results - -- Tests: 83 -- Coverage: schemas, semantic validation - -## Related Links - -- [README (Japanese)](README-ja.md) -- [Root README (Agent-focused)](../README.md) - -## License - -See [LICENSE](../LICENSE). +v2 is a breaking runtime change. The official migration path is the `migrate-v1` CLI; no v1 runtime compatibility +layer is provided. See [requirements.md](requirements.md), [protocol.md](protocol.md), and [operations.md](operations.md). diff --git a/docs/README-ja.md b/docs/README-ja.md index 4be0358..eebf308 100644 --- a/docs/README-ja.md +++ b/docs/README-ja.md @@ -1,144 +1,24 @@ -# agent-protocols +# agent-protocols v2(日本語) -契約駆動型 AI ワークフローのプロトコル仕様リポジトリです。 +この文書は `@rna4219/agent-protocols` v2の入口です。 -## 概要 +## 正本と責務 -`agent-protocols` は、AI エージェントによる作業実行を契約ベースで管理するための仕様を定義します。5種類の契約オブジェクトと、それらの状態遷移・承認フローを規定します。 +契約のSchema、TypeScript型、strict AJV検証、semantic/reference validation、policy判定、 +PublishGate生成、CloudEvents構築、v1移行規則の唯一の正本は +[C:\Users\ryo-n\Codex_dev\agent-protocols](../)です。 -## 何のためにあるか +[C:\Users\ryo-n\Codex_dev\Agent_tools\shipyard-cp](../../Agent_tools/shipyard-cp)は参照runtime/control planeです。 +永続化、イベント配送、retry/冪等制御、lock/lease/heartbeat、期限scheduler、worker実行とruntime固有adapterを担当します。 -この仕様が必要になるのは、AI エージェントが「何をしてよいか」「どこで人間承認が必要か」「実行結果をどう監査するか」が曖昧なまま運用されやすいからです。 +## v2の要点 -特に次のような状況では、エージェント運用が属人的になりやすくなります。 +- 実行時v1互換は提供しない。公式移行経路は `migrate-v1` CLIだけ。 +- 契約IDは `_`。 +- 共通状態は `draft|active|frozen|final|superseded|revoked|archived`。 +- PublishGateは `acceptanceId`、`operation: publish`、`decision`を使う。 +- Evidenceはstage/taskSeedIdを必須とし、publish stageではacceptanceId/publishGateIdと承認snapshotを要求する。 +- Evidenceはfinal、revision 1、createdAt=updatedAtで不変。 +- CloudEventsの必須拡張は `correlationid`、`causationid`、`idempotencykey`、`contractrevision`。 -- 依頼文だけで実行しており、入力と出力の契約が明確でない -- low risk な変更と high risk な変更の境界が曖昧 -- 承認が必要な操作と自動で進めてよい操作が混ざる -- 実行後に「なぜこの判断になったか」を追跡しづらい -- 単体エージェント運用から複数エージェント運用へ広げたいが、共通ルールがない - -`agent-protocols` は、この曖昧さを減らすために、意図、作業単位、検証、公開判定、証跡を分離して扱うための共通仕様です。 - -## どんなときに使うか - -このリポジトリは、次のような目的で使うことを想定しています。 - -- AI エージェント作業の control plane を設計したい -- schema と validator の正本を先に固めたい -- 承認付きの自動実行フローを定義したい -- 監査可能な Evidence を残したい -- 単体実行から swarm 的な並列運用まで拡張したい - -逆に、単発のスクリプト実行だけで十分で、承認、監査、再現性、状態遷移を管理しない場合は、この仕様はやや重い可能性があります。 - -## 導入メリット - -- 意図、実行、検証、承認、証跡の責務を分離できる -- low / medium / high / critical の境界を明文化できる -- 自動承認と人間承認の条件を共通化できる -- JSON Schema と semantic validation を分離して実装しやすい -- Birdseye や補助文書と組み合わせて、初見の実装者でも入りやすくなる -- 将来、別のランタイムや別のエージェントにも同じ契約を適用しやすい - -## 対象読者 - -- プロダクトオーナー: 要件定義層を参照 -- 実装者: 要件定義層 + プロトコル仕様層を参照 -- 運用者/監査者: 運用ポリシー層を参照 - -## ドキュメント構成 - -| ドキュメント | 役割 | -|---|---| -| [BLUEPRINT.md](BLUEPRINT.md) | 全体目的、非ゴール、設計方針 | -| [requirements.md](requirements.md) | 要件正本(規範) | -| [protocol.md](protocol.md) | 実装者向けプロトコル仕様 | -| [operations.md](operations.md) | 運用・監査ルール | -| [implementation-prep.md](implementation-prep.md) | 実装準備ガイド | -| [RUNBOOK.md](RUNBOOK.md) | 運用手順書 | -| [CHECKLISTS.md](CHECKLISTS.md) | チェックリスト | -| [INSPECTION.md](INSPECTION.md) | 検収レポート | - -## 契約の流れ - -``` -IntentContract → TaskSeed → Acceptance → PublishGate → Evidence - (意図) → (タスク) → (検証) → (承認) → (証跡) -``` - -1. **IntentContract**: ユーザーの意図と必要な権限を定義 -2. **TaskSeed**: 実行可能な作業単位に分解 -3. **Acceptance**: 実行結果の検証 -4. **PublishGate**: 公開の可否判定と承認 -5. **Evidence**: 実行証跡の記録 - -## 承認ルール - -| リスクレベル | 必要な承認 | 自動承認 | -|---|---|---| -| low | なし | ○ | -| medium | なし | ○ | -| high | project_lead, security_reviewer | × | -| critical | project_lead, security_reviewer, release_manager | × | - -## 権限(Capabilities) - -| 権限 | 説明 | -|---|---| -| `read_repo` | リポジトリ読み取り | -| `write_repo` | リポジトリ書き込み | -| `install_deps` | 依存パッケージインストール | -| `network_access` | ネットワークアクセス | -| `read_secrets` | シークレット読み取り | -| `publish_release` | リリース公開 | - -## 状態遷移 - -``` -Draft → Active → Frozen → Published → Superseded → Revoked → Archived -``` - -- **Draft**: 作成直後、編集可能 -- **Active**: 実行対象 -- **Frozen**: 一時停止、要調査 -- **Published**: 公示済み -- **Superseded**: 後継あり -- **Revoked**: 無効化 -- **Archived**: 保管済み - -## 実装状況 - -| マイルストーン | 状態 | 内容 | -|---|---|---| -| M1: Contract Baseline | ✅ | JSON Schema 6ファイル、サンプル 6ファイル | -| M2: Validation Baseline | ✅ | セマンティックバリデータ | -| M3: Orchestration Baseline | ✅ | オーケストレーター | -| M4: Approval Baseline | ✅ | ポリシーエンジン | - -## クイックスタート - -```bash -# 依存関係インストール -npm install - -# テスト実行 -npm test - -# デモ実行 -npx tsx scripts/demo.ts -``` - -## テスト結果 - -- テスト数: 83件 -- カバレッジ: schemas, semantic validation - -## 関連リンク - -- [README (English)](README-en.md) -- [ルートREADME(Agent向け)](../README.md) - -## ライセンス - -[LICENSE](../LICENSE) を参照してください。 +詳細は [requirements.md](requirements.md)、[protocol.md](protocol.md)、[operations.md](operations.md)を参照してください。 diff --git a/docs/operations.md b/docs/operations.md index 592a5d5..1f52502 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -1,165 +1,48 @@ -# agent-protocols Operations Policy +# v2 Operations Runbook -## 1. 位置づけ -本書は [requirements.md](C:/Users/ryo-n/Codex_dev/agent-protocols/docs/requirements.md) を運用・監査観点に再編した文書です。実装手順ではなく、実行時の判断規則と証跡要求を定義します。 +## Release and package validation -## 2. ロール +公開前に次を実行する。 -| ロール | 主責務 | -|---|---| -| `requester` | Intent の起票 | -| `orchestrator` | 契約生成、状態遷移、イベント発行 | -| `policy_engine` | risk 判定、PublishGate 生成、自動承認 | -| `developer` | Build / Refactor の実作業 | -| `ci_agent` | CI 実行、依存導入、ネットワークを伴う実行 | -| `qa` | 手動または自動検証 | -| `project_lead` | activation / publish の承認責務 | -| `security_reviewer` | high/critical のセキュリティ承認 | -| `release_manager` | publish_release の承認と公開判断 | -| `admin` | 例外時の代行、緊急停止 | +```powershell +npm ci +npm run generate:check +npm run typecheck +npm run lint +npm test +npm run build +npm run test:package +``` -## 3. capability とアクセス制御 +beta検証中のShipyard依存は `@rna4219/agent-protocols@2.0.0-beta.1`を完全一致で使用する。 +stable移行後に `^2.0.0`へ更新する。 -| ロール | read_repo | write_repo | install_deps | network_access | read_secrets | publish_release | -|---|---|---|---|---|---|---| -| requester | ○ | - | - | - | - | - | -| orchestrator | - | - | - | - | - | - | -| policy_engine | - | - | - | - | - | - | -| developer | ○ | ○ | - | - | - | - | -| ci_agent | ○ | ○ | ○ | ○ | - | - | -| qa | ○ | ○ | - | - | - | - | -| project_lead | ○ | ○ | - | - | - | - | -| release_manager | ○ | ○ | - | - | - | ○ | -| security_reviewer | ○ | - | - | - | ○ | - | -| admin | ○ | ○ | ○ | ○ | ○ | ○ | +## Migration -運用規則: +移行は必ず新規の絶対出力ディレクトリへ行う。 -- capability 未付与の操作は Orchestrator が拒否する -- `project_lead` `security_reviewer` `release_manager` の承認は capability ではなく PublishGate ロール責務として扱う -- `orchestrator` と `policy_engine` は repo capability を持たない +```powershell +agent-protocols migrate-v1 C:\input\contracts.jsonl --namespace project-a --out C:\output\v2 +``` -## 4. PublishGate 運用 +入力はJSONまたはJSONL。全入力を検証し、参照関係を収集してから一括変換する。 +入力不正、参照切れ、ID衝突、出力先既存では非ゼロ終了し、元入力と部分出力を残さない。 +成功後はcontracts.v2.jsonl、id-map.json、migration-report.jsonを保存する。 -### 4.1 low / medium +## Shipyard保存・配送境界 -- `requiredApprovals = []` -- `approvalDeadline` 不要 -- `policy_engine` が通常 `approved` を設定 -- ポリシー違反検知時のみ `rejected` +保存前にShipyardは `safeParseContract` と `validateTransition`を呼ぶ。 +関連契約の保存前に `validateContractGraph`を呼ぶ。イベント配送前には +`createContractEvent`でCloudEventsを構築する。Shipyard側に契約policyや生成規則の別実装を置かない。 -### 4.2 high / critical +## Gate運用 -- `approvalDeadline` 必須 -- 初期 `finalDecision = pending` -- 承認完了前に `Published` へ遷移してはならない -- `critical` では `security_reviewer` と `release_manager` の両承認必須 +low/mediumはpolicy_engineの自動承認を保存する。high/criticalは期限schedulerが期限を監視し、 +期限到達後はexpireGateでfrozen/expiredへ更新する。承認者はrequiredApprovalsと完全一致し、 +期限後、決定後、同一role再決定を受け付けない。 -### 4.3 承認ログ -承認イベントごとに以下を保存します。 +## Evidence運用 -- `role` -- `actorId` -- `decision` -- `decidedAt` -- `reason` - -## 5. stale / lock / 例外 - -### 5.1 stale - -- `soft_stale`: 最終取得から 10 分超 -- `hard_stale`: 最終取得から 60 分超、または依存契約/参照コミット変化 - -運用規則: - -- `soft_stale` は再取得を試みつつ継続可 -- `hard_stale` は再取得成功まで停止 -- 必要に応じて `Frozen` へ遷移可 - -### 5.2 lock - -- ロック単位: `contract:` または `repo-path:` -- TTL: 300 秒 -- heartbeat: 60 秒ごと -- 2 回連続 heartbeat 失敗で失効可 -- 取得失敗時の再試行: 15 秒、30 秒、60 秒 -- 3 回失敗後は `Frozen` - -## 6. Evidence 運用 - -### 6.1 保存要件 - -- 実行ごとに 1 件以上の Evidence を作成 -- Evidence は `Published` 状態で固定 -- 手動承認が発生した場合のみ `approvalsSnapshot` を保存 - -### 6.2 監査観点 - -- 誰が実行したか: `actor` -- 何を根拠に承認されたか: `policyVerdict`, `approvalsSnapshot` -- 再現できるか: commit / hash / model / environment -- 競合や stale の影響があったか: `staleStatus`, `mergeResult` - -### 6.3 workflow-cookbook からの受け入れ - -- `workflow-cookbook` 連携では、`StructuredLogger` plugin が `Evidence` JSON Lines - を出力し、それを受け側の保存先へ取り込む運用を想定する。 -- 受け入れ前に次を確認する。 - - `taskSeedId` が既存 TaskSeed と結びつくこと - - `actor` が追跡対象の実行主体を示すこと - - `policyVerdict` と `approvalsSnapshot` が監査方針に合うこと - - `environment.containerImageDigest` が未使用環境では `uncontainerized` であること -- 参照実装と plugin config sample は次を利用する。 - - [`workflow-cookbook/examples/agent_protocol_evidence_consumer.sample.py`](C:/Users/ryo-n/Codex_dev/workflow-cookbook/examples/agent_protocol_evidence_consumer.sample.py) - - [`workflow-cookbook/tools/protocols/README.md`](C:/Users/ryo-n/Codex_dev/workflow-cookbook/tools/protocols/README.md) - - [`workflow-cookbook/examples/inference_plugins.agent_protocol.sample.json`](C:/Users/ryo-n/Codex_dev/workflow-cookbook/examples/inference_plugins.agent_protocol.sample.json) - -## 7. ログ保持 - -### 7.1 保存期間 - -- 全操作ログを最低 1 年保持 - -### 7.2 検索キー - -- `contractId` -- `taskSeedId` -- `actorId` -- `role` -- `action` -- `riskLevel` -- `finalDecision` -- `date` - -### 7.3 必須ログ項目 - -- `timestamp` -- `kind` -- `id` -- `version` -- `actorId` -- `role` -- `action` -- `success/failure` -- `error message` -- `approval decision` -- `environment summary` - -## 8. 運用上の停止条件 -以下のいずれかに該当する場合、継続実行より `Frozen` を優先します。 - -- 自動生成が 3 回失敗 -- lock 競合が解消しない -- `hard_stale` が解消しない -- 外部依存異常で結果の信頼性が保てない -- 手動確認が必要な policy 違反が検出された - -## 9. 受入前チェック -実装に着手する前に、少なくとも次を運用合意します。 - -- riskLevel の判定責務をどのサービスが持つか -- PublishGate と Evidence の保存先 -- 承認者の actorId 管理方法 -- stale 判定の参照時刻の取得元 -- 監査ログの保存場所と 1 年保持の実現方法 +Evidenceはfinal/revision 1で保存するimmutable recordである。publish stageでは +Gateのapprovalsをsnapshotとしてコピーし、保存前に完全一致を検証する。Evidence更新APIは提供せず、 +訂正は新しいEvidenceとして記録する。 diff --git a/docs/protocol.md b/docs/protocol.md index 1635b4d..e803486 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -1,227 +1,45 @@ -# agent-protocols Protocol Specification +# v2 Protocol Specification -## 1. 位置づけ -本書は [requirements.md](C:/Users/ryo-n/Codex_dev/agent-protocols/docs/requirements.md) を正本とし、その内容を実装者向けのプロトコル仕様へ再構成した補助仕様です。規範判断が衝突した場合は `requirements.md` を優先します。 +## Contract identifiers and metadata -対象スコープは以下です。 +契約IDは種別名とCrockford ULIDをアンダースコアで連結する。ULIDの時刻部はcreatedAtのUTCミリ秒と一致させ、 +移行時は `createdAt + namespace + kind + 旧ID`から決定的に生成する。 -- 契約オブジェクトの型と識別子 -- イベント駆動フロー -- 状態遷移 -- リスク判定と PublishGate -- Evidence の記録境界 +## Contract graph -## 2. 契約モデル +``` +IntentContract + -> TaskSeed + -> Acceptance + -> PublishGate + -> Evidence +``` -### 2.1 共通フィールド -全契約は以下を共通必須とします。 +TaskSeed.intentId、Acceptance.taskSeedId、PublishGate.acceptanceIdは型付きIDで参照する。 +publish stageのEvidenceはAcceptance、PublishGate、TaskSeedの参照鎖を解決しなければならない。 -- `schemaVersion` -- `id` -- `kind` -- `state` -- `version` -- `createdAt` -- `updatedAt` +## Lifecycle and revision -共通 schema は継承ベースであり、最終バリデーションは具象 schema 側の `allOf` と `unevaluatedProperties: false` で行います。 +通常の契約はdraftからactiveへ進み、障害時はfrozen、確定時はfinalになる。final後は後継契約による +superseded、監査によるrevoked、保持期限によるarchivedだけを許可する。更新はrevisionを厳密に1増やし、 +createdAtを変更してはならない。Evidenceは更新対象ではない。 -### 2.2 契約種別 -扱う契約は以下の 5 種です。 +PublishGateだけは承認の各更新でもrevisionを増やす。decisionとlifecycleの対応は +pending/active、approvedまたはrejected/final、expired/frozenで固定する。 -| kind | 役割 | ID 規約 | -|---|---|---| -| `IntentContract` | 依頼意図と capability 要求の正本 | `IC-` | -| `TaskSeed` | 実行可能な作業単位 | `TS-` | -| `Acceptance` | 実行結果に対する検証結果 | `AC-` | -| `PublishGate` | 公開可否と承認状態 | `PG-` | -| `Evidence` | 実行証跡と再現性記録 | `EV-` | +## Policy API -### 2.3 参照関係 +`deriveGenerationPolicy`はread_repo単独またはread_repo+write_repoだけをauto_activateとする。 +install_deps/network_access/read_secretsはproject_leadとsecurity_reviewer、publish_releaseは +project_leadとrelease_managerを追加要求する。`assessPolicy`はriskをlow/medium/high/criticalへ導出し、 +criticalではproduction data、secret transmission、legal concern、rollback impossibleを最優先する。 -- `TaskSeed.intentId -> IntentContract.id` -- `Acceptance.taskSeedId -> TaskSeed.id` -- `PublishGate.entityId -> Acceptance.id` -- `Evidence.taskSeedId -> TaskSeed.id` +## Public API -### 2.4 共通状態 -共通状態は以下に固定します。 +実装は `src/index.ts`から公開する。safeParseContract/safeParseEventは成功時にdata、失敗時にerrorsを返す。 +parseContract/parseEventは失敗時に例外を投げる。生成APIはClockとID generatorを注入でき、再現可能なテストを可能にする。 -- `Draft` -- `Active` -- `Frozen` -- `Published` -- `Superseded` -- `Revoked` -- `Archived` +## CloudEvents -## 3. イベントモデル - -### 3.1 規範イベント - -- `intent.created.v1` -- `taskseed.created.v1` -- `taskseed.execution.completed.v1` -- `acceptance.created.v1` -- `publishgate.created.v1` -- `publishgate.decision.recorded.v1` -- `evidence.created.v1` - -### 3.2 イベント責務 - -| イベント | 発火主体 | 発火条件 | -|---|---|---| -| `intent.created.v1` | Orchestrator | `IntentContract.state = Active` | -| `taskseed.created.v1` | Orchestrator | TaskSeed 永続化完了 | -| `taskseed.execution.completed.v1` | Executor | `TaskSeed.state = Active` かつ実行完了 | -| `acceptance.created.v1` | Validator | Acceptance 永続化完了 | -| `publishgate.created.v1` | Policy Engine | PublishGate 永続化完了 | -| `publishgate.decision.recorded.v1` | Policy Engine | 承認、却下、自動承認の記録 | -| `evidence.created.v1` | Executor | Evidence 永続化完了 | - -## 4. 自動生成フロー - -### 4.1 正常系 - -1. `IntentContract` が `Active` になる -2. Orchestrator が `intent.created.v1` を発行する -3. TaskSeed Generator が `TaskSeed` を生成する -4. Orchestrator が `taskseed.created.v1` を発行する -5. Executor が `TaskSeed` を実行する -6. Executor が `taskseed.execution.completed.v1` を発行する -7. Validator が `Acceptance` を生成する -8. `Acceptance.status = passed` の場合のみ Policy Engine が `PublishGate` を生成する -9. Executor が各実行終了時に `Evidence` を生成する - -### 4.2 再試行 - -- 自動生成処理は最大 3 回まで再試行 -- バックオフは 30 秒、60 秒、120 秒 -- 冪等キーは `sourceContractId + sourceVersion + targetKind` -- 3 回失敗時は、生成対象が永続化済みならその対象契約、未永続化なら生成元契約を `Frozen` に遷移 - -## 5. activation policy - -### 5.1 capability 一覧 - -- `read_repo` -- `write_repo` -- `install_deps` -- `network_access` -- `read_secrets` -- `publish_release` - -### 5.2 `generationPolicy` -`TaskSeed` と `Acceptance` は `generationPolicy` を持ちます。 - -| 条件 | `auto_activate` | `requiredActivationApprovals` | -|---|---|---| -| `read_repo` のみ | `true` | `[]` | -| `read_repo + write_repo` のみ | `true` | `[]` | -| `install_deps` または `network_access` を含む | `false` | `["project_lead", "security_reviewer"]` | -| `read_secrets` を含む | `false` | `["project_lead", "security_reviewer"]` | -| `publish_release` を含む | `false` | `["project_lead", "release_manager"]` | - -複数条件に該当する場合、`requiredActivationApprovals` は和集合とします。 - -## 6. 状態遷移 - -### 6.1 契約状態遷移 - -| 契約 | 初期状態 | 特記事項 | -|---|---|---| -| `IntentContract` | `Draft` | 明示承認後に `Active` | -| `TaskSeed` | `Draft` または `Active` | `generationPolicy.auto_activate = true` の場合のみ初期 `Active` | -| `Acceptance` | `Draft` または `Active` | `generationPolicy.auto_activate = true` の場合のみ初期 `Active` | -| `PublishGate` | `Active` または `Published` | low/medium 自動承認時のみ初期 `Published` | -| `Evidence` | `Published` | 不変記録のため `Draft` / `Active` を経由しない | - -### 6.2 PublishGate 遷移 - -- `requiredApprovals = []` かつ `finalDecision = approved|rejected` のみ即時確定 -- `requiredApprovals` が空でない場合は `approvalDeadline` 必須 -- `finalDecision = pending` は人間承認待ちに限る -- `approvalDeadline` 経過時に未完了なら `expired` - -## 7. リスク判定 - -### 7.1 riskLevel - -| riskLevel | 判定条件 | -|---|---| -| `low` | read-only。公開、外部通信、依存追加なし | -| `medium` | repo 書き込みあり。外部通信なし。本番影響なし | -| `high` | `install_deps` `network_access` `read_secrets` `publish_release` のいずれかを含む | -| `critical` | 本番データ変更、シークレット外部送信、法令/契約違反懸念、またはロールバック不能公開 | - -### 7.2 PublishGate 承認マトリクス - -| riskLevel | `requiredApprovals` | `finalDecision` 初期値 | -|---|---|---| -| `low` | `[]` | 通常 `approved`、違反検知時のみ `rejected` | -| `medium` | `[]` | 通常 `approved`、違反検知時のみ `rejected` | -| `high` | `["project_lead", "security_reviewer"]` | `pending` | -| `critical` | `["project_lead", "security_reviewer", "release_manager"]` | `pending` | - -## 8. 実行フェーズ - -| フェーズ | トリガー | 主体 | 代表成果物 | -|---|---|---|---| -| Plan | `intent.created.v1` | Orchestrator | `TaskSeed` | -| Build | `taskseed.created.v1` | developer / ci_agent | build log, Evidence | -| Stabilize | build 成功 | qa / ci_agent | test report, Evidence | -| Refactor | integration 成功 | developer / project_lead | review result, Evidence | -| Publish | `PublishGate.finalDecision = approved` | policy_engine / release_manager / admin | PublishGate, release note, Evidence | - -## 9. Evidence - -### 9.1 必須項目 - -- `taskSeedId` -- `baseCommit` -- `headCommit` -- `inputHash` -- `outputHash` -- `model` -- `tools` -- `environment` -- `staleStatus` -- `mergeResult` -- `startTime` -- `endTime` -- `actor` -- `policyVerdict` -- `diffHash` - -### 9.2 条件付き項目 - -- `approvalsSnapshot`: 手動承認が発生した場合のみ必須 - -### 9.3 実装注意 - -- `startTime <= endTime` はアプリケーション検証で担保 -- `baseCommit == headCommit` は許容 -- コンテナ未使用環境では `containerImageDigest = "uncontainerized"` - -### 9.4 workflow-cookbook 連携 - -- `workflow-cookbook` は `StructuredLogger` plugin を通じて `Evidence` を生成できる。 -- 連携時の入力コンテキストは少なくとも次を含む。 - - `evidence_id` - - `task_seed_id` - - `base_commit` - - `head_commit` - - `actor` -- `inputHash` / `outputHash` / `diffHash` / `model.parametersHash` は - `workflow-cookbook` 側で正規化入力から導出できる。 -- 参照実装と sample config は次を参照する。 - - [`workflow-cookbook/tools/protocols/README.md`](C:/Users/ryo-n/Codex_dev/workflow-cookbook/tools/protocols/README.md) - - [`workflow-cookbook/examples/inference_plugins.agent_protocol.sample.json`](C:/Users/ryo-n/Codex_dev/workflow-cookbook/examples/inference_plugins.agent_protocol.sample.json) - -## 10. 実装境界 -本書から実装へ落とすときの責務境界は以下です。 - -- JSON Schema: 型、必須、列挙、ネスト、追加プロパティ禁止 -- アプリケーション検証: 時系列整合、条件付き必須、正規化手順、参照整合 -- オーケストレータ: イベント順序、再試行、冪等性、`Frozen` 遷移 -- Policy Engine: risk 判定、PublishGate 作成、自動承認、承認記録 +`createContractEvent`はCloudEvents 1.0形式を構築する。dataは検証済み契約そのもの、 +subjectはdata.id、contractrevisionはdata.revisionと一致させる。ShipyardはこのAPIでイベントを作成してから配送する。 diff --git a/docs/requirements.md b/docs/requirements.md index e64885a..0f08196 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -1,857 +1,56 @@ -# 要件定義書の厳密仕様改訂報告書 +# agent-protocols v2 要件(正本) -## エグゼクティブサマリ -本書は、契約駆動型の AI ワークフローを実装可能なレベルまで厳密化した要件仕様です。従来の説明中心の記述を改め、文書構造、契約オブジェクト、状態遷移、承認フロー、権限、再現性、監査、並列実行を相互に矛盾しない形で定義します。 +## 1. 目的と責務境界 -本仕様の対象は、`IntentContract -> TaskSeed -> Acceptance -> PublishGate -> Evidence` を中核とするオーケストレーションです。各契約は共通状態を持ち、イベント駆動で生成・更新されます。高リスク操作は `PublishGate` の承認ルールに従い、証跡は `Evidence` に統一フォーマットで保存されます。 +`agent-protocols`は契約Schema、型、検証、policy判定、契約生成規則の唯一の正本である。 +`shipyard-cp`は参照runtime/control planeであり、永続化・イベント配送・retry/冪等制御・lock/lease/heartbeat・期限scheduler・ +worker実行・runtime固有adapterだけを所有する。両repoで契約型、risk導出、Gate生成、ID生成、CloudEvents構築を重複実装しない。 -この改訂では、以下を規範仕様として固定します。 +## 2. v2共通契約 -- 契約共通状態は `Draft -> Active -> Frozen -> Published -> Superseded -> Revoked -> Archived` とする -- 全契約に `schemaVersion` `kind` `state` `createdAt` `updatedAt` `version` を必須化する -- 共通 schema は継承用ベースとし、具象 schema は `allOf` で合成して厳密化する -- `Evidence` の必須項目を 1 つの正本定義に統一する -- `PublishGate` に複数承認者、判定理由、期限、最終判断を表現できるデータモデルを持たせる -- ロールと capability を一致させ、各フェーズの実行主体と権限表の矛盾を解消する -- 自動生成トリガー、リトライ、タイムアウト、stale 判定、ロック制御、再現性項目を規範値として明文化する +全契約は次を必須とする。 -## 1. 目的・非ゴール・MVP 範囲 +- `schemaVersion: "2.0.0"` +- `id: _` +- `kind: IntentContract|TaskSeed|Acceptance|PublishGate|Evidence` +- `lifecycle: draft|active|frozen|final|superseded|revoked|archived` +- `revision`(1以上の整数) +- `createdAt` / `updatedAt`(RFC 3339 UTC、末尾Z) -### 1.1 目的 -- 契約駆動型 AI ワークフローを実現する -- 各フェーズの入力・出力・判定条件を機械可読な契約として定義する -- 実行結果を再現可能な証跡として記録し、監査可能にする -- 高リスク操作に対して人間承認を強制できるようにする +v1のstate/version/旧IDはv2実行時APIに受け入れず、移行CLIで変換する。 -### 1.2 非ゴール -- UI/UX 設計 -- 特定ベンダー依存のデプロイ手順 -- ハードウェア構成やネットワークトポロジの最適化 -- モデル品質そのものの研究評価 +## 3. PublishGate -### 1.3 MVP 範囲 -MVP は以下の成功条件を満たすことです。 +PublishGateはAcceptanceのstatusがpassedで、Schema検証とsemantic検証に合格した場合だけ生成できる。 +フィールドは `acceptanceId`、`operation: "publish"`、`riskLevel`、`requiredApprovals`、 +`approvals`、`decision: pending|approved|rejected|expired`を持つ。 -1. `IntentContract` の作成イベントから `TaskSeed` が自動生成される -2. `TaskSeed` の実行結果として `Acceptance` が生成される -3. `Acceptance` が `passed` の場合に `PublishGate` 判定へ進める -4. 実行ごとに `Evidence` が生成され、必要な再現情報が保存される -5. 高リスク操作では `PublishGate.finalDecision = approved` がない限り公開されない +policyは完全一致である。 -## 2. 文書 3 層構成 -仕様書は以下の 3 層で管理します。 +| risk | requiredApprovals | 初期decision/lifecycle | +|---|---|---| +| low / medium | [] | approved / final(policy_engine記録) | +| high | project_lead, security_reviewer | pending / active | +| critical | high + release_manager | pending / active | -- 要件定義層: 目的、非ゴール、MVP、ユースケース、非機能要件 -- プロトコル仕様層: 契約オブジェクト、イベント、状態遷移、JSON Schema、API/CLI 入出力 -- 運用ポリシー層: リスク承認、監査、ログ保持、例外対応、権限運用 +pendingはactive、approved/rejectedはfinal、expiredはfrozenに固定する。 +重複role、未要求role、競合決定、同一roleの再決定、期限後の承認は拒否し、更新ごとにrevisionを1増やす。 -役割ごとの参照範囲は以下とします。 +## 4. Evidence -- プロダクトオーナー: 要件定義層 -- 実装者: 要件定義層 + プロトコル仕様層 -- 運用者/監査者: 運用ポリシー層 + 必要に応じてプロトコル仕様層 +Evidenceは `stage`、`taskSeedId`を必須とする。publish stageでは +`acceptanceId`、`publishGateId`、Gateの承認記録と完全一致する`approvalsSnapshot`を必須とする。 +commit/hashは `{ algorithm, value }`、toolsは `{ name, version?, digest? }[]`とする。 -## 3. システム前提とイベント駆動モデル +Evidenceは `lifecycle=final`、`revision=1`、`createdAt=updatedAt`であり、更新transitionを常に拒否する。 -### 3.1 構成要素 -- Contract Store: 契約オブジェクトの永続化層 -- Event Bus: 契約生成・更新イベントの配送 -- Orchestrator: 契約生成、状態遷移、再試行の制御 -- TaskSeed Generator: `IntentContract` から `TaskSeed` を導出する生成器 -- Validator: 実行結果から `Acceptance` を導出する検証器 -- Policy Engine: capability とリスク判定、PublishGate 生成 -- Executor: Build/Refactor/Validation を実行する主体 -- Evidence Store: 監査証跡の保存先 +## 5. イベント -### 3.2 規範イベント -イベント名は以下に固定します。 +イベントはCloudEvents 1.0の `specversion`、`id`、`source`、`type`、`subject`、`time`、`data`を持つ。 +さらに `correlationid`、`causationid`、`idempotencykey`、`contractrevision`を必須拡張とする。 +`data.kind`、`data.id`、`data.revision`とsubject/contractrevisionの一致を検証する。 -- `intent.created.v1` -- `taskseed.created.v1` -- `taskseed.execution.completed.v1` -- `acceptance.created.v1` -- `publishgate.created.v1` -- `publishgate.decision.recorded.v1` -- `evidence.created.v1` +## 6. Fail-closed -### 3.3 自動生成トリガー -- `IntentContract.state = Active` になった時点で Orchestrator は `intent.created.v1` を発行する -- `intent.created.v1` を受けた TaskSeed Generator は 30 秒以内に `TaskSeed` を作成し、`IntentContract.requestedCapabilities` から `generationPolicy` を導出する -- TaskSeed の永続化完了後、Orchestrator は `taskseed.created.v1` を発行する -- `TaskSeed.state = Active` かつ実行完了時に Executor は `taskseed.execution.completed.v1` を発行する -- 実行結果を受けた Validator は 60 秒以内に `Acceptance` を作成し、`TaskSeed` の実行リスクと検証対象に応じて `generationPolicy` を導出する -- Acceptance の永続化完了後、Validator は `acceptance.created.v1` を発行する -- `Acceptance.status = passed` の場合、Policy Engine は `PublishGate` を作成する -- `PublishGate.riskLevel` が `low` または `medium` の場合、Policy Engine は `requiredApprovals = []` とし、通常は生成時に `finalDecision = approved` を設定する。ポリシー違反を検知した場合のみ `rejected` とする -- PublishGate の永続化完了後、Policy Engine は `publishgate.created.v1` を発行する -- Executor は各実行の終了後 30 秒以内に不変の監査記録として `Evidence` を `Published` 状態で作成し、永続化完了後に `evidence.created.v1` を発行する -- `PublishGate` に対する各承認、却下、または自動承認の記録時、Policy Engine は `publishgate.decision.recorded.v1` を発行する - -`generationPolicy` の導出規則: - -- `requestedCapabilities` が `read_repo` のみ、または `read_repo + write_repo` のみなら `auto_activate = true` -- `install_deps` または `network_access` を含む場合は `auto_activate = false` とし、`requiredActivationApprovals` に `project_lead` と `security_reviewer` を設定する -- `read_secrets` を含む場合は `auto_activate = false` とし、`requiredActivationApprovals` に `project_lead` と `security_reviewer` を設定する -- `publish_release` を含む場合は `auto_activate = false` とし、`requiredActivationApprovals` に `project_lead` と `release_manager` を設定する -- 複数条件に該当する場合、`requiredActivationApprovals` は和集合とする -- `TaskSeed` は `IntentContract.requestedCapabilities` を `requestedCapabilitiesSnapshot` として保持する -- `Acceptance` は既定で `TaskSeed.generationPolicy.requiredActivationApprovals` を継承し、`TaskSeed.requestedCapabilitiesSnapshot` が `read_repo` のみ、または `read_repo + write_repo` のみの場合に限り `auto_activate = true` としてよい - -### 3.4 再試行と冪等性 -- 各自動生成処理は最大 3 回まで再試行する -- 再試行間隔は 30 秒、60 秒、120 秒の指数バックオフとする -- 契約生成の冪等キーは `sourceContractId + sourceVersion + targetKind` とする -- 3 回失敗した場合、生成対象が永続化済みならその対象契約を `Frozen` に遷移し、未永続化なら生成元契約を `Frozen` に遷移して障害イベントを記録する - -## 4. 契約共通ルール - -### 4.1 共通必須フィールド -全契約は以下を必須とします。以下の schema は継承用ベースであり、単体で最終バリデーションに使ってはなりません。具象 schema は `allOf: [{"$ref":"common.schema.json"}, {...具象定義...}]` で合成し、最終 schema 側で `unevaluatedProperties: false` を指定します。`allOf` で合成される具象側サブ schema では、共通 schema 由来プロパティを不当に拒否しないよう `additionalProperties` を指定してはなりません。 - -```json -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "required": [ - "schemaVersion", - "id", - "kind", - "state", - "version", - "createdAt", - "updatedAt" - ], - "properties": { - "schemaVersion": { - "type": "string", - "const": "1.0.0" - }, - "id": { - "type": "string", - "pattern": "^[A-Z]{2,4}-[0-9]{3,}$" - }, - "kind": { - "type": "string", - "enum": [ - "IntentContract", - "TaskSeed", - "Acceptance", - "PublishGate", - "Evidence" - ] - }, - "state": { - "type": "string", - "enum": [ - "Draft", - "Active", - "Frozen", - "Published", - "Superseded", - "Revoked", - "Archived" - ] - }, - "version": { - "type": "integer", - "minimum": 1 - }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "updatedAt": { - "type": "string", - "format": "date-time" - } - } -} -``` - -### 4.2 状態遷移ルール -共通状態の意味は以下のとおりです。 - -- `Draft`: 作成直後。編集可能、実行不可 -- `Active`: 実行対象。関連イベントを発行可能 -- `Frozen`: 一時停止。要調査。自動実行不可 -- `Published`: 公開判断または採用判断が完了した最終有効状態 -- `Superseded`: 後継バージョンに置換済み -- `Revoked`: 明示的に無効化 -- `Archived`: 保管専用。変更不可 - -状態遷移の制約は以下です。 - -- `Draft -> Active` は明示承認時のみ許可 -- システム自動生成された `TaskSeed` および `Acceptance` は、生成元契約が `Active` かつ生成ポリシーで `auto_activate = true` の場合に限り、初期状態を `Active` として作成してよい -- システム自動生成された `PublishGate` は、`finalDecision = pending` の場合のみ初期状態を `Active` とし、`riskLevel` が `low` または `medium` で `requiredApprovals = []` かつ `finalDecision = approved` の場合に限り初期状態を `Published` として作成してよい -- システム自動生成された `Evidence` は不変記録のため初期状態を `Published` として作成し、`Draft` または `Active` を経由してはならない -- システム自動生成された `TaskSeed` または `Acceptance` が `Draft` の場合、`generationPolicy.requiredActivationApprovals` に定義された全ロールの承認完了後にのみ `Active` へ遷移できる -- `IntentContract` `TaskSeed` `Acceptance` の `Active -> Published` は `PublishGate.finalDecision = approved` を満たす場合のみ許可 -- `PublishGate` の `Active -> Published` は `requiredApprovals` を全充足したうえで `finalDecision = approved` になった場合のみ許可 -- `Active -> Frozen` は障害、要手動確認、外部依存異常時に許可 -- `Published -> Superseded` は後継契約の `version` がより大きい場合のみ許可 -- `Draft|Active|Frozen|Published -> Revoked` は監査または手動停止で許可 -- `Superseded|Revoked|Published -> Archived` は保持期限または運用ルール到達時に許可 - -## 5. 契約型仕様 - -### 5.1 IntentContract -具象 schema は以下のように `common.schema.json` と合成する。 - -```json -{ - "$defs": { - "capability": { - "type": "string", - "enum": [ - "read_repo", - "write_repo", - "install_deps", - "network_access", - "read_secrets", - "publish_release" - ] - } - }, - "allOf": [ - { "$ref": "common.schema.json" }, - { - "title": "IntentContract", - "type": "object", - "required": [ - "intent", - "creator", - "priority", - "requestedCapabilities" - ], - "properties": { - "kind": { "const": "IntentContract" }, - "id": { "type": "string", "pattern": "^IC-[0-9]{3,}$" }, - "intent": { "type": "string", "minLength": 1 }, - "creator": { "type": "string", "minLength": 1 }, - "priority": { "type": "string", "enum": ["low", "medium", "high", "critical"] }, - "requestedCapabilities": { - "type": "array", - "items": { "$ref": "#/$defs/capability" }, - "minItems": 1, - "uniqueItems": true - } - } - } - ], - "unevaluatedProperties": false -} -``` - -### 5.2 TaskSeed -```json -{ - "allOf": [ - { "$ref": "common.schema.json" }, - { - "title": "TaskSeed", - "type": "object", - "required": [ - "intentId", - "description", - "ownerRole", - "executionPlan", - "requestedCapabilitiesSnapshot", - "generationPolicy" - ], - "properties": { - "kind": { "const": "TaskSeed" }, - "id": { "type": "string", "pattern": "^TS-[0-9]{3,}$" }, - "intentId": { "type": "string", "pattern": "^IC-[0-9]{3,}$" }, - "description": { "type": "string", "minLength": 1 }, - "ownerRole": { - "type": "string", - "enum": ["developer", "ci_agent", "qa", "project_lead", "release_manager", "admin"] - }, - "executionPlan": { - "type": "array", - "items": { "type": "string", "minLength": 1 }, - "minItems": 1 - }, - "requestedCapabilitiesSnapshot": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "read_repo", - "write_repo", - "install_deps", - "network_access", - "read_secrets", - "publish_release" - ] - }, - "minItems": 1, - "uniqueItems": true - }, - "generationPolicy": { - "type": "object", - "required": ["auto_activate", "requiredActivationApprovals"], - "properties": { - "auto_activate": { "type": "boolean" }, - "requiredActivationApprovals": { - "type": "array", - "items": { - "type": "string", - "enum": ["policy_engine", "project_lead", "security_reviewer", "release_manager", "admin"] - }, - "uniqueItems": true - } - }, - "allOf": [ - { - "if": { - "properties": { - "auto_activate": { "const": false } - } - }, - "then": { - "properties": { - "requiredActivationApprovals": { - "minItems": 1 - } - } - } - } - ], - "additionalProperties": false - } - } - } - ], - "unevaluatedProperties": false -} -``` - -### 5.3 Acceptance -```json -{ - "allOf": [ - { "$ref": "common.schema.json" }, - { - "title": "Acceptance", - "type": "object", - "required": [ - "taskSeedId", - "status", - "details", - "criteria", - "generationPolicy" - ], - "properties": { - "kind": { "const": "Acceptance" }, - "id": { "type": "string", "pattern": "^AC-[0-9]{3,}$" }, - "taskSeedId": { "type": "string", "pattern": "^TS-[0-9]{3,}$" }, - "status": { - "type": "string", - "enum": ["pending", "passed", "failed", "blocked"] - }, - "details": { "type": "string", "minLength": 1 }, - "criteria": { - "type": "array", - "items": { "type": "string", "minLength": 1 }, - "minItems": 1 - }, - "generationPolicy": { - "type": "object", - "required": ["auto_activate", "requiredActivationApprovals"], - "properties": { - "auto_activate": { "type": "boolean" }, - "requiredActivationApprovals": { - "type": "array", - "items": { - "type": "string", - "enum": ["project_lead", "security_reviewer", "release_manager", "admin"] - }, - "uniqueItems": true - } - }, - "allOf": [ - { - "if": { - "properties": { - "auto_activate": { "const": false } - } - }, - "then": { - "properties": { - "requiredActivationApprovals": { - "minItems": 1 - } - } - } - } - ], - "additionalProperties": false - } - } - } - ], - "unevaluatedProperties": false -} -``` - -### 5.4 PublishGate -```json -{ - "allOf": [ - { "$ref": "common.schema.json" }, - { - "title": "PublishGate", - "type": "object", - "required": [ - "entityId", - "action", - "riskLevel", - "requiredApprovals", - "approvals", - "finalDecision" - ], - "properties": { - "kind": { "const": "PublishGate" }, - "id": { "type": "string", "pattern": "^PG-[0-9]{3,}$" }, - "entityId": { "type": "string", "pattern": "^AC-[0-9]{3,}$" }, - "action": { "type": "string", "enum": ["publish", "reject", "hold"] }, - "riskLevel": { "type": "string", "enum": ["low", "medium", "high", "critical"] }, - "requiredApprovals": { - "type": "array", - "items": { - "type": "string", - "enum": ["project_lead", "security_reviewer", "release_manager", "admin"] - }, - "minItems": 0, - "uniqueItems": true - }, - "approvals": { - "type": "array", - "items": { - "type": "object", - "required": ["role", "actorId", "decision", "decidedAt"], - "properties": { - "role": { - "type": "string", - "enum": ["policy_engine", "project_lead", "security_reviewer", "release_manager", "admin"] - }, - "actorId": { "type": "string", "minLength": 1 }, - "decision": { "type": "string", "enum": ["approved", "rejected"] }, - "decidedAt": { "type": "string", "format": "date-time" }, - "reason": { "type": "string" } - }, - "additionalProperties": false - } - }, - "finalDecision": { - "type": "string", - "enum": ["pending", "approved", "rejected", "expired"] - }, - "approvalDeadline": { - "type": "string", - "format": "date-time" - } - }, - "allOf": [ - { - "if": { - "properties": { - "requiredApprovals": { - "minItems": 1 - } - } - }, - "then": { - "required": ["approvalDeadline"] - } - }, - { - "if": { - "properties": { - "requiredApprovals": { - "maxItems": 0 - } - } - }, - "then": { - "properties": { - "finalDecision": { - "enum": ["approved", "rejected"] - } - } - } - } - ] - } - ], - "unevaluatedProperties": false -} -``` - -### 5.5 Evidence -`Evidence` の最小必須セットは本節を正本とし、他節は本節に従います。 - -```json -{ - "allOf": [ - { "$ref": "common.schema.json" }, - { - "title": "Evidence", - "type": "object", - "required": [ - "taskSeedId", - "baseCommit", - "headCommit", - "inputHash", - "outputHash", - "model", - "tools", - "environment", - "staleStatus", - "mergeResult", - "startTime", - "endTime", - "actor", - "policyVerdict", - "diffHash" - ], - "properties": { - "kind": { "const": "Evidence" }, - "id": { "type": "string", "pattern": "^EV-[0-9]{3,}$" }, - "taskSeedId": { "type": "string", "pattern": "^TS-[0-9]{3,}$" }, - "baseCommit": { "type": "string", "minLength": 7 }, - "headCommit": { "type": "string", "minLength": 7 }, - "inputHash": { "type": "string", "minLength": 1 }, - "outputHash": { "type": "string", "minLength": 1 }, - "model": { - "type": "object", - "required": ["name", "version", "parametersHash"], - "properties": { - "name": { "type": "string", "minLength": 1 }, - "version": { "type": "string", "minLength": 1 }, - "parametersHash": { "type": "string", "minLength": 1 } - }, - "additionalProperties": false - }, - "tools": { - "type": "array", - "items": { "type": "string", "minLength": 1 }, - "minItems": 1 - }, - "environment": { - "type": "object", - "required": ["os", "runtime", "containerImageDigest", "lockfileHash"], - "properties": { - "os": { "type": "string", "minLength": 1 }, - "runtime": { "type": "string", "minLength": 1 }, - "containerImageDigest": { "type": "string", "minLength": 1 }, - "lockfileHash": { "type": "string", "minLength": 1 } - }, - "additionalProperties": false - }, - "staleStatus": { - "type": "object", - "required": ["classification", "evaluatedAt"], - "properties": { - "classification": { - "type": "string", - "enum": ["fresh", "soft_stale", "hard_stale"] - }, - "evaluatedAt": { "type": "string", "format": "date-time" }, - "reason": { "type": "string" } - }, - "additionalProperties": false - }, - "mergeResult": { - "type": "object", - "required": ["status"], - "properties": { - "status": { - "type": "string", - "enum": ["not_applicable", "not_attempted", "merged", "manual_resolution_required"] - }, - "mergedAt": { "type": "string", "format": "date-time" }, - "strategy": { "type": "string" }, - "reason": { "type": "string" } - }, - "additionalProperties": false - }, - "startTime": { "type": "string", "format": "date-time" }, - "endTime": { "type": "string", "format": "date-time" }, - "actor": { "type": "string", "minLength": 1 }, - "approvalsSnapshot": { - "type": "array", - "items": { - "type": "object", - "required": ["role", "actorId", "decision", "decidedAt"], - "properties": { - "role": { - "type": "string", - "enum": ["project_lead", "security_reviewer", "release_manager", "admin"] - }, - "actorId": { "type": "string", "minLength": 1 }, - "decision": { "type": "string", "enum": ["approved", "rejected"] }, - "decidedAt": { "type": "string", "format": "date-time" }, - "reason": { "type": "string" } - }, - "additionalProperties": false - }, - "minItems": 1 - }, - "policyVerdict": { - "type": "string", - "enum": ["approved", "rejected", "manual_review_required"] - }, - "diffHash": { "type": "string", "minLength": 1 } - } - } - ], - "unevaluatedProperties": false -} -``` - -## 6. 実行フェーズの状態遷移 -フェーズ遷移は以下に固定します。 - -```mermaid -stateDiagram-v2 - [*] --> Plan: intent active - Plan --> Build: taskseed generated - Build --> Stabilize: build and unit tests passed - Build --> Plan: build failed - Stabilize --> Refactor: integration tests passed - Stabilize --> Build: tests failed - Refactor --> Publish: review passed - Refactor --> Build: code changed - Publish --> [*]: publishgate approved -``` - -| 遷移元 | 遷移先 | トリガー | 実行主体 | 必須証拠 | ガード条件 | リトライ | タイムアウト | 手動介入 | -|---|---|---|---|---|---|---|---|---| -| Plan | Build | `taskseed.created.v1` | orchestrator | TaskSeed | IntentContract が `Active` | 不可 | 30 秒 | 不要 | -| Build | Stabilize | ビルド成功 | developer / ci_agent | build log, unit test result, Evidence | unit test 成功 | 3 回 | 20 分 | 任意 | -| Build | Plan | ビルド失敗 | ci_agent | error log, Evidence | retry 上限超過 | 3 回 | 20 分 | 必須 | -| Stabilize | Refactor | 統合テスト成功 | qa / ci_agent | integration report, Evidence | acceptance criteria 充足 | 2 回 | 30 分 | 任意 | -| Stabilize | Build | テスト失敗 | qa / ci_agent | failed test log, Evidence | acceptance 不充足 | 2 回 | 30 分 | 任意 | -| Refactor | Publish | レビュー成功 | developer / project_lead | review result, Evidence | blocking issue なし | 1 回 | 10 分 | 任意 | -| Publish | Published | `PublishGate.finalDecision = approved` | policy_engine / release_manager / admin | PublishGate, release note, Evidence | low/medium は `policy_engine` による自動承認可、high/critical は requiredApprovals を全充足 | 不可 | low/medium は即時、high/critical は approvalDeadline まで | high/critical のみ必須 | - -実行主体の選択規則: - -- Build/Refactor は `requestedCapabilities` に `install_deps` または `network_access` を含む場合 `ci_agent` を優先し、それ以外は `developer` を既定主体とする -- Stabilize は自動テストのみなら `ci_agent`、手動検証を含む場合は `qa` を主体とする -- Refactor -> Publish のレビュー承認は、差分が low/medium リスクなら `project_lead`、high/critical リスクなら `project_lead` に加えて `security_reviewer` のレビュー完了を必須とする -- Publish は low/medium の自動承認時は `policy_engine`、`publish_release` capability を含む場合は `release_manager`、緊急停止または管理者代行時のみ `admin` を主体とする -- 監査ログの `role` には、上記規則で実際に選ばれた単一主体のみを保存する - -## 7. リスク分類と PublishGate 判定 - -### 7.1 リスク分類 -- `low`: read-only 操作のみ。公開・外部通信・依存追加なし -- `medium`: repo 書き込みあり。外部通信なし。本番影響なし -- `high`: `install_deps` `network_access` `read_secrets` `publish_release` のいずれかを含む -- `critical`: 本番データ変更、シークレット外部送信、法令・契約違反の可能性、またはロールバック不能な公開 - -### 7.2 規範判定ルール -- requestedCapabilities に `read_secrets` が含まれる場合、最低 `high` -- requestedCapabilities に `publish_release` が含まれる場合、最低 `high` -- 本番環境または customer data への書き込みを伴う場合、`critical` -- `high` 以上では人間承認必須 -- `critical` では `security_reviewer` と `release_manager` の両承認を必須とする - -### 7.3 PublishGate 承認ルール -- `low`: `requiredApprovals = []` とし、Policy Engine は通常 `finalDecision = approved` を設定する。ポリシー違反を検知した場合のみ `rejected` とする -- `medium`: `requiredApprovals = []` とし、Policy Engine は通常 `finalDecision = approved` を設定する。ポリシー違反を検知した場合のみ `rejected` とする -- `high`: `requiredApprovals = ["project_lead", "security_reviewer"]` -- `critical`: `requiredApprovals = ["project_lead", "security_reviewer", "release_manager"]` -- `requiredApprovals` が空でない場合、`approvalDeadline` は必須とする -- `requiredApprovals = []` の場合、`finalDecision = pending` は禁止し、生成時に `approved` または `rejected` のどちらかへ確定させる -- `approvalDeadline` 経過時に未完了なら `finalDecision = expired` -- Refactor -> Publish で行うレビュー承認は品質レビューであり、PublishGate の人間承認要件とは別に扱う - -## 8. capability とアクセス制御マトリクス -capability は以下の 6 種に固定します。 - -- `read_repo` -- `write_repo` -- `install_deps` -- `network_access` -- `read_secrets` -- `publish_release` - -ロール定義は以下です。 - -- `requester` -- `orchestrator` -- `policy_engine` -- `developer` -- `ci_agent` -- `qa` -- `project_lead` -- `release_manager` -- `security_reviewer` -- `admin` - -| ロール | read_repo | write_repo | install_deps | network_access | read_secrets | publish_release | -|---|---|---|---|---|---|---| -| requester | ○ | - | - | - | - | - | -| orchestrator | - | - | - | - | - | - | -| policy_engine | - | - | - | - | - | - | -| developer | ○ | ○ | - | - | - | - | -| ci_agent | ○ | ○ | ○ | ○ | - | - | -| qa | ○ | ○ | - | - | - | - | -| project_lead | ○ | ○ | - | - | - | - | -| release_manager | ○ | ○ | - | - | - | ○ | -| security_reviewer | ○ | - | - | - | ○ | - | -| admin | ○ | ○ | ○ | ○ | ○ | ○ | - -運用ルールは以下です。 - -- フェーズ表に登場する主体は必ず本表のロールと一致させる -- capability 未付与の操作は Orchestrator が拒否する -- `developer` が Build/Refactor を行うため `write_repo` は必須とする -- `project_lead` と `security_reviewer` の承認行為は capability ではなく PublishGate 上のロール責務として扱う -- `orchestrator` は repo capability を持たず、契約生成・状態遷移・イベント発行のみを行うシステムロールとする -- `policy_engine` は repo capability を持たず、risk 判定・PublishGate 生成・自動承認のみを行うシステムロールとする - -## 9. Evidence と再現性要件 -再現性のため、以下を必須保存対象とします。 - -- 基準コミット `baseCommit` -- 実行後コミット `headCommit` -- 入力正規化後ハッシュ `inputHash` -- 出力ハッシュ `outputHash` -- モデル名、モデル版、推論パラメータハッシュ -- 使用ツール一覧 -- OS、ランタイム、コンテナイメージ digest、依存 lockfile hash -- stale 判定結果 -- 自動マージ結果 -- 開始・終了時刻 -- 実行者 ID -- ポリシー判定 -- 差分ハッシュ - -追加ルール: - -- 手動承認が発生した場合のみ `approvalsSnapshot` を必須とし、PublishGate.approvals と同一内容を保存する -- `startTime <= endTime` を満たさない Evidence は無効とする -- `baseCommit` と `headCommit` が同一でも許可するが、その場合は `diffHash` に空差分ハッシュを保存する -- `containerImageDigest` が存在しない実行環境では固定値 `uncontainerized` を保存する - -検証境界: - -- JSON Schema で担保する範囲: 必須項目、型、列挙値、ネスト構造、追加プロパティ禁止 -- アプリケーション検証で担保する範囲: `approvalsSnapshot` の条件必須、`startTime <= endTime`、`inputHash` と正規化手順の整合、`uncontainerized` の代替値適用、`TaskSeed.requestedCapabilitiesSnapshot` と生成元 `IntentContract.requestedCapabilities` の一致 -- CI では schema test と別に semantic validation test を設け、上記アプリケーション検証を必須とする - -## 10. stale 判定とコンテキスト更新 -stale 判定は例示ではなく以下を規範値とします。 - -- `soft_stale`: 最終取得から 10 分超 -- `hard_stale`: 最終取得から 60 分超、または依存契約/version が変化、または参照コミットが変化 - -運用ルール: - -- `soft_stale` の場合、再取得を試みつつ処理継続を許可する -- `hard_stale` の場合、再取得成功まで処理を停止し `Frozen` に遷移可能とする -- stale 判定結果は Evidence に付随ログとして保存する - -## 11. スウォーム並列実行と競合制御 -共有リソース競合を避けるため、以下を規範化します。 - -- ロック単位は `contract:` または `repo-path:` とする -- ロック TTL は 300 秒 -- ロック延長は 60 秒ごとに heartbeat を送る -- heartbeat が 2 回連続失敗した場合、ロックは失効可能とする -- ロック取得失敗時は 15 秒、30 秒、60 秒で最大 3 回リトライする -- 3 回失敗後は `Frozen` に遷移し、手動解消待ちとする - -マージ戦略: - -- 同一契約の競合更新は `version` が高い方を優先する -- 同一 version の競合は `updatedAt` の新しい方を優先せず、必ず手動解消とする -- 自動マージ結果は必ず Evidence に記録する - -## 12. 監査・ログ保持要件 -全操作ログは最低 1 年保存し、以下を検索キーにします。 - -- `contractId` -- `taskSeedId` -- `actorId` -- `role` -- `action` -- `riskLevel` -- `finalDecision` -- `date` - -必須ログ項目: - -- timestamp -- contract kind/id/version -- actorId -- role -- action -- success/failure -- error message -- approval decision -- environment summary - -## 13. テスト・検証基準 -Acceptance の完了条件は以下を最低限満たすことです。 - -1. IntentContract から TaskSeed が 1 回だけ自動生成される -2. TaskSeed 実行後に Evidence が必ず生成される -3. Acceptance.status が `passed` のときのみ PublishGate が生成される -4. `low` と `medium` では requiredApprovals が空となり自動承認、`high` 以上では requiredApprovals が正しく設定される -5. `high` 以上では `approvalDeadline` が設定され、PublishGate の requiredApprovals を満たさない限り `Published` へ遷移しない -6. 各契約 kind の `id` が規定 prefix に一致する -7. stale と lock 例外時に `Frozen` 遷移が発生する - -自動テスト区分: - -- Schema test: 各契約 JSON が厳密 schema に適合すること -- Orchestration test: イベントから契約生成までの遷移が正しいこと -- Policy test: capability と riskLevel の判定が正しいこと -- Reproducibility test: Evidence 必須項目が欠けると失敗すること -- Concurrency test: ロック競合時に期待どおり再試行/凍結すること - -## 14. 変更管理とバージョニング -- 契約 Schema と実装コードは同一リポジトリで管理する -- 破壊的変更は major、後方互換ありの追加は minor、文言修正や非互換なし変更は patch とする -- `schemaVersion` は schema の互換性境界で更新する -- 互換性を壊す変更では migration 手順とサンプル変換を必須とする -- CI は schema 変更時に schema test と orchestration test を必須実行する - -## 15. 再構成版ファイル構成案 - -| ファイルパス | 内容概要 | -|---|---| -| `docs/requirements.md` | 目的、非ゴール、MVP、非機能要件 | -| `docs/protocol.md` | 契約仕様、イベント、状態遷移、schema 解説 | -| `docs/operations.md` | 承認、監査、保持期間、障害運用 | -| `schemas/common.schema.json` | 共通契約 schema | -| `schemas/IntentContract.schema.json` | IntentContract schema | -| `schemas/TaskSeed.schema.json` | TaskSeed schema | -| `schemas/Acceptance.schema.json` | Acceptance schema | -| `schemas/PublishGate.schema.json` | PublishGate schema | -| `schemas/Evidence.schema.json` | Evidence schema | -| `diagrams/state_transitions.mmd` | Mermaid 状態図 | -| `tables/access_matrix.md` | ロールと capability の対応表 | -| `tables/risk_classification.md` | リスク分類と承認ルール | -| `tests/schema/` | schema 検証テスト | -| `tests/orchestration/` | イベント遷移テスト | -| `tests/policy/` | リスク/権限判定テスト | -| `tests/concurrency/` | ロックと stale テスト | -| `CHANGELOG.md` | 変更履歴 | - -## 16. 今回の改訂で解消した不整合 -- 共通状態に `Published` を追加し、状態定義と本文の表現を一致させた -- 全契約に `state` を明示的に持たせた -- 共通 schema を継承用ベースとし、具象 schema を `allOf` 合成で成立させた -- `Evidence` の必須項目を 1 つの正本定義へ統一した -- `PublishGate` に複数承認者、最終判断、期限を追加した -- フェーズ主体とアクセス制御ロールを一致させた -- stale、lock、retry を「例」ではなく規範値に変更した -- 再現性要件として環境・モデル・入力正規化情報を必須化した +未知のkind、capability、role、ID prefix、Schema不足、参照切れ、revision不整合は拒否する。 +safe APIのエラー形式は `{ code, path, message, source: schema|semantic|reference }`に統一する。 diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..8de4aea --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,12 @@ +import tseslint from '@typescript-eslint/eslint-plugin'; +import parser from '@typescript-eslint/parser'; + +export default [ + { ignores: ['dist/**', 'node_modules/**', 'src/validation/**'] }, + { + files: ['src/**/*.ts'], + languageOptions: { parser, parserOptions: { project: './tsconfig.json', sourceType: 'module' } }, + plugins: { '@typescript-eslint': tseslint }, + rules: { 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }] } + } +]; diff --git a/package-lock.json b/package-lock.json index f27bad6..f2f8c9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,30 @@ { - "name": "agent-protocols", - "version": "1.0.0", + "name": "@rna4219/agent-protocols", + "version": "2.0.0-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "agent-protocols", - "version": "1.0.0", + "name": "@rna4219/agent-protocols", + "version": "2.0.0-beta.1", "dependencies": { + "ajv": "^8.17.0", "ajv-formats": "^3.0.1" }, + "bin": { + "agent-protocols": "dist/cli/index.js" + }, "devDependencies": { - "ajv": "^8.17.0", + "@types/node": "^22.13.14", + "@typescript-eslint/eslint-plugin": "^8.57.1", + "@typescript-eslint/parser": "^8.57.1", + "eslint": "^9.22.0", "tsx": "^4.21.0", - "vitest": "^3.0.0" + "typescript": "^5.8.2", + "vitest": "^3.2.4" + }, + "engines": { + "node": ">=20" } }, "node_modules/@esbuild/aix-ppc64": { @@ -458,6 +469,299 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -840,139 +1144,412 @@ "dev": true, "license": "MIT" }, - "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", "dev": true, "license": "MIT", "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "undici-types": "~6.21.0" } }, - "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz", + "integrity": "sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.63.0", + "@typescript-eslint/type-utils": "8.63.0", + "@typescript-eslint/utils": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "@typescript-eslint/parser": "^8.63.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "node_modules/@typescript-eslint/parser": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.63.0.tgz", + "integrity": "sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^2.0.0" + "@typescript-eslint/scope-manager": "8.63.0", + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz", + "integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" + "@typescript-eslint/tsconfig-utils": "^8.63.0", + "@typescript-eslint/types": "^8.63.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz", + "integrity": "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz", + "integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz", + "integrity": "sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0", + "@typescript-eslint/utils": "8.63.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "node_modules/@typescript-eslint/types": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz", + "integrity": "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==", + "dev": true, "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz", + "integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.63.0", + "@typescript-eslint/tsconfig-utils": "8.63.0", + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz", + "integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.63.0", + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz", + "integrity": "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.63.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "license": "MIT", @@ -988,6 +1565,29 @@ } } }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -998,6 +1598,29 @@ "node": ">=12" } }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -1008,6 +1631,16 @@ "node": ">=8" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/chai": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", @@ -1025,6 +1658,23 @@ "node": ">=18" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/check-error": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", @@ -1035,6 +1685,48 @@ "node": ">= 16" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1063,6 +1755,13 @@ "node": ">=6" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -1112,107 +1811,617 @@ "@esbuild/win32-x64": "0.27.4" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, - "license": "Apache-2.0", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, "engines": { - "node": ">=12.0.0" + "node": ">=0.10.0" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, "license": "MIT" }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/fastify" + "url": "https://github.com/sponsors/puzrin" }, { - "type": "opencollective", - "url": "https://opencollective.com/fastify" + "type": "github", + "url": "https://github.com/sponsors/nodeca" } ], - "license": "BSD-3-Clause" + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "dependencies": { + "json-buffer": "3.0.1" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.8.0" } }, - "node_modules/get-tsconfig": { - "version": "4.13.7", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", - "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { - "resolve-pkg-maps": "^1.0.0" + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, "license": "MIT" }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, "node_modules/loupe": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", @@ -1230,6 +2439,22 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1256,6 +2481,96 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -1322,6 +2637,26 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -1331,6 +2666,16 @@ "node": ">=0.10.0" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -1386,6 +2731,42 @@ "fsevents": "~2.3.2" } }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -1417,6 +2798,19 @@ "dev": true, "license": "MIT" }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-literal": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", @@ -1430,6 +2824,19 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -1491,6 +2898,19 @@ "node": ">=14.0.0" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tsx": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", @@ -1511,6 +2931,50 @@ "fsevents": "~2.3.3" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", @@ -1682,6 +3146,22 @@ } } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -1698,6 +3178,29 @@ "engines": { "node": ">=8" } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index b734e98..2e68255 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,42 @@ { - "name": "agent-protocols", - "version": "1.0.0", - "description": "Contract-driven AI workflow protocols", + "name": "@rna4219/agent-protocols", + "version": "2.0.0-beta.1", + "description": "Canonical v2 contract schemas, types, validation, policy and migration for AI workflows", "type": "module", + "engines": { "node": ">=20" }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "bin": { "agent-protocols": "./dist/cli/index.js" }, + "exports": { + ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" }, + "./migration": { "types": "./dist/migration/index.d.ts", "import": "./dist/migration/index.js" }, + "./schemas/v2/*": "./schemas/v2/*", + "./schemas/v1/*": "./schemas/*", + "./package.json": "./package.json" + }, + "files": ["dist", "schemas", "docs", "README.md", "LICENSE"], "scripts": { + "build": "npm run generate && tsc -p tsconfig.json", + "typecheck": "tsc --noEmit -p tsconfig.json", + "lint": "eslint src scripts", "test": "vitest run", - "test:watch": "vitest", - "validate": "vitest run tests/schema" - }, - "devDependencies": { - "ajv": "^8.17.0", - "tsx": "^4.21.0", - "vitest": "^3.0.0" + "generate": "node scripts/generate-types.mjs", + "generate:check": "node scripts/generate-types.mjs --check", + "test:package": "node scripts/package-smoke.mjs", + "release:validate": "npm run generate:check && npm run typecheck && npm run lint && npm test && npm run build && npm run test:package", + "validate": "npm test" }, "dependencies": { + "ajv": "^8.17.0", "ajv-formats": "^3.0.1" + }, + "devDependencies": { + "@types/node": "^22.13.14", + "@typescript-eslint/eslint-plugin": "^8.57.1", + "@typescript-eslint/parser": "^8.57.1", + "eslint": "^9.22.0", + "tsx": "^4.21.0", + "typescript": "^5.8.2", + "vitest": "^3.2.4" } -} +} \ No newline at end of file diff --git a/schemas/v2/Acceptance.schema.json b/schemas/v2/Acceptance.schema.json new file mode 100644 index 0000000..2ce4ab9 --- /dev/null +++ b/schemas/v2/Acceptance.schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-protocols.rna4219.dev/schemas/v2/Acceptance.schema.json", + "title": "Acceptance v2", + "type": "object", + "allOf": [ + { "$ref": "https://agent-protocols.rna4219.dev/schemas/v2/common.schema.json" }, + { + "type": "object", + "required": ["taskSeedId", "status", "details", "criteria", "generationPolicy"], + "properties": { + "kind": { "const": "Acceptance" }, + "id": { "type": "string", "pattern": "^Acceptance_[0-9A-HJKMNP-TV-Z]{26}$" }, + "taskSeedId": { "type": "string", "pattern": "^TaskSeed_[0-9A-HJKMNP-TV-Z]{26}$" }, + "status": { "enum": ["pending", "passed", "failed", "blocked"] }, + "details": { "type": "string", "minLength": 1 }, + "criteria": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }, + "generationPolicy": { + "type": "object", "required": ["auto_activate", "requiredActivationApprovals"], + "properties": { + "auto_activate": { "type": "boolean" }, + "requiredActivationApprovals": { + "type": "array", "uniqueItems": true, + "items": { "enum": ["project_lead", "security_reviewer", "release_manager", "admin"] } + } + }, + "additionalProperties": false + } + } + } + ], + "unevaluatedProperties": false +} \ No newline at end of file diff --git a/schemas/v2/CloudEvent.schema.json b/schemas/v2/CloudEvent.schema.json new file mode 100644 index 0000000..d05a9b8 --- /dev/null +++ b/schemas/v2/CloudEvent.schema.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-protocols.rna4219.dev/schemas/v2/CloudEvent.schema.json", + "title": "agent-protocols CloudEvents 1.0", + "type": "object", + "required": ["specversion", "id", "source", "type", "subject", "time", "data", "correlationid", "causationid", "idempotencykey", "contractrevision"], + "properties": { + "specversion": { "const": "1.0" }, + "id": { "type": "string", "minLength": 1 }, + "source": { "type": "string", "minLength": 1 }, + "type": { "type": "string", "minLength": 1 }, + "subject": { "type": "string", "pattern": "^(IntentContract|TaskSeed|Acceptance|PublishGate|Evidence)_[0-9A-HJKMNP-TV-Z]{26}$" }, + "time": { "type": "string", "format": "date-time", "pattern": "Z$" }, + "datacontenttype": { "const": "application/json" }, + "data": { "type": "object" }, + "correlationid": { "type": "string", "minLength": 1 }, + "causationid": { "type": "string", "minLength": 1 }, + "idempotencykey": { "type": "string", "minLength": 1 }, + "contractrevision": { "type": "integer", "minimum": 1 } + }, + "additionalProperties": true +} \ No newline at end of file diff --git a/schemas/v2/Evidence.schema.json b/schemas/v2/Evidence.schema.json new file mode 100644 index 0000000..d5f16df --- /dev/null +++ b/schemas/v2/Evidence.schema.json @@ -0,0 +1,108 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-protocols.rna4219.dev/schemas/v2/Evidence.schema.json", + "title": "Evidence v2", + "type": "object", + "$defs": { + "hash": { + "type": "object", "required": ["algorithm", "value"], + "properties": { + "algorithm": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]*$" }, + "value": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + }, + "approval": { + "type": "object", "required": ["role", "actorId", "decision", "decidedAt"], + "properties": { + "role": { "enum": ["project_lead", "security_reviewer", "release_manager", "policy_engine"] }, + "actorId": { "type": "string", "minLength": 1 }, + "decision": { "enum": ["approved", "rejected"] }, + "decidedAt": { "type": "string", "format": "date-time", "pattern": "Z$" }, + "reason": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + } + }, + "allOf": [ + { "$ref": "https://agent-protocols.rna4219.dev/schemas/v2/common.schema.json" }, + { + "type": "object", + "required": [ + "stage", "taskSeedId", "baseCommit", "headCommit", "inputHash", "outputHash", "model", "tools", + "environment", "staleStatus", "mergeResult", "startTime", "endTime", "actor", "policyVerdict", "diffHash" + ], + "properties": { + "kind": { "const": "Evidence" }, + "id": { "type": "string", "pattern": "^Evidence_[0-9A-HJKMNP-TV-Z]{26}$" }, + "lifecycle": { "const": "final" }, + "revision": { "const": 1 }, + "stage": { "enum": ["plan", "execution", "acceptance", "publish", "integration"] }, + "taskSeedId": { "type": "string", "pattern": "^TaskSeed_[0-9A-HJKMNP-TV-Z]{26}$" }, + "acceptanceId": { "type": "string", "pattern": "^Acceptance_[0-9A-HJKMNP-TV-Z]{26}$" }, + "publishGateId": { "type": "string", "pattern": "^PublishGate_[0-9A-HJKMNP-TV-Z]{26}$" }, + "baseCommit": { "$ref": "#/$defs/hash" }, + "headCommit": { "$ref": "#/$defs/hash" }, + "inputHash": { "$ref": "#/$defs/hash" }, + "outputHash": { "$ref": "#/$defs/hash" }, + "model": { + "type": "object", "required": ["name", "version", "parametersHash"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "version": { "type": "string", "minLength": 1 }, + "parametersHash": { "$ref": "#/$defs/hash" } + }, + "additionalProperties": false + }, + "tools": { + "type": "array", + "items": { + "type": "object", "required": ["name"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "version": { "type": "string", "minLength": 1 }, + "digest": { "$ref": "#/$defs/hash" } + }, + "additionalProperties": false + } + }, + "environment": { + "type": "object", "required": ["os", "runtime", "containerImageDigest", "lockfileHash"], + "properties": { + "os": { "type": "string", "minLength": 1 }, + "runtime": { "type": "string", "minLength": 1 }, + "containerImageDigest": { "$ref": "#/$defs/hash" }, + "lockfileHash": { "$ref": "#/$defs/hash" } + }, + "additionalProperties": false + }, + "staleStatus": { + "type": "object", "required": ["classification", "evaluatedAt"], + "properties": { + "classification": { "enum": ["fresh", "soft_stale", "hard_stale"] }, + "evaluatedAt": { "type": "string", "format": "date-time", "pattern": "Z$" }, + "reason": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + }, + "mergeResult": { + "type": "object", "required": ["status"], + "properties": { + "status": { "enum": ["not_applicable", "not_attempted", "merged", "manual_resolution_required"] }, + "mergedAt": { "type": "string", "format": "date-time", "pattern": "Z$" }, + "strategy": { "type": "string", "minLength": 1 }, + "reason": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + }, + "startTime": { "type": "string", "format": "date-time", "pattern": "Z$" }, + "endTime": { "type": "string", "format": "date-time", "pattern": "Z$" }, + "actor": { "type": "string", "minLength": 1 }, + "approvalsSnapshot": { "type": "array", "items": { "$ref": "#/$defs/approval" } }, + "policyVerdict": { "enum": ["approved", "rejected", "manual_review_required"] }, + "diffHash": { "$ref": "#/$defs/hash" } + } + } + ], + "unevaluatedProperties": false +} \ No newline at end of file diff --git a/schemas/v2/IntentContract.schema.json b/schemas/v2/IntentContract.schema.json new file mode 100644 index 0000000..9bc4e5d --- /dev/null +++ b/schemas/v2/IntentContract.schema.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-protocols.rna4219.dev/schemas/v2/IntentContract.schema.json", + "title": "IntentContract v2", + "type": "object", + "allOf": [ + { "$ref": "https://agent-protocols.rna4219.dev/schemas/v2/common.schema.json" }, + { + "type": "object", + "required": ["intent", "creator", "priority", "requestedCapabilities"], + "properties": { + "kind": { "const": "IntentContract" }, + "id": { "type": "string", "pattern": "^IntentContract_[0-9A-HJKMNP-TV-Z]{26}$" }, + "intent": { "type": "string", "minLength": 1 }, + "creator": { "type": "string", "minLength": 1 }, + "priority": { "enum": ["low", "medium", "high", "critical"] }, + "requestedCapabilities": { + "type": "array", "minItems": 1, "uniqueItems": true, + "items": { "enum": ["read_repo", "write_repo", "install_deps", "network_access", "read_secrets", "publish_release"] } + } + } + } + ], + "unevaluatedProperties": false +} \ No newline at end of file diff --git a/schemas/v2/PublishGate.schema.json b/schemas/v2/PublishGate.schema.json new file mode 100644 index 0000000..bbfae29 --- /dev/null +++ b/schemas/v2/PublishGate.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-protocols.rna4219.dev/schemas/v2/PublishGate.schema.json", + "title": "PublishGate v2", + "type": "object", + "allOf": [ + { "$ref": "https://agent-protocols.rna4219.dev/schemas/v2/common.schema.json" }, + { + "type": "object", + "required": ["acceptanceId", "operation", "riskLevel", "requiredApprovals", "approvals", "decision"], + "properties": { + "kind": { "const": "PublishGate" }, + "id": { "type": "string", "pattern": "^PublishGate_[0-9A-HJKMNP-TV-Z]{26}$" }, + "acceptanceId": { "type": "string", "pattern": "^Acceptance_[0-9A-HJKMNP-TV-Z]{26}$" }, + "operation": { "const": "publish" }, + "riskLevel": { "enum": ["low", "medium", "high", "critical"] }, + "requiredApprovals": { + "type": "array", "uniqueItems": true, + "items": { "enum": ["project_lead", "security_reviewer", "release_manager"] } + }, + "approvals": { + "type": "array", + "items": { + "type": "object", + "required": ["role", "actorId", "decision", "decidedAt"], + "properties": { + "role": { "enum": ["project_lead", "security_reviewer", "release_manager", "policy_engine"] }, + "actorId": { "type": "string", "minLength": 1 }, + "decision": { "enum": ["approved", "rejected"] }, + "decidedAt": { "type": "string", "format": "date-time", "pattern": "Z$" }, + "reason": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + } + }, + "decision": { "enum": ["pending", "approved", "rejected", "expired"] }, + "approvalDeadline": { "type": "string", "format": "date-time", "pattern": "Z$" } + } + } + ], + "unevaluatedProperties": false +} \ No newline at end of file diff --git a/schemas/v2/TaskSeed.schema.json b/schemas/v2/TaskSeed.schema.json new file mode 100644 index 0000000..a4b3dea --- /dev/null +++ b/schemas/v2/TaskSeed.schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-protocols.rna4219.dev/schemas/v2/TaskSeed.schema.json", + "title": "TaskSeed v2", + "type": "object", + "allOf": [ + { "$ref": "https://agent-protocols.rna4219.dev/schemas/v2/common.schema.json" }, + { + "type": "object", + "required": ["intentId", "description", "ownerRole", "executionPlan", "requestedCapabilitiesSnapshot", "generationPolicy"], + "properties": { + "kind": { "const": "TaskSeed" }, + "id": { "type": "string", "pattern": "^TaskSeed_[0-9A-HJKMNP-TV-Z]{26}$" }, + "intentId": { "type": "string", "pattern": "^IntentContract_[0-9A-HJKMNP-TV-Z]{26}$" }, + "description": { "type": "string", "minLength": 1 }, + "ownerRole": { "enum": ["developer", "ci_agent", "qa", "project_lead", "release_manager", "admin"] }, + "executionPlan": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }, + "requestedCapabilitiesSnapshot": { + "type": "array", "minItems": 1, "uniqueItems": true, + "items": { "enum": ["read_repo", "write_repo", "install_deps", "network_access", "read_secrets", "publish_release"] } + }, + "generationPolicy": { + "type": "object", "required": ["auto_activate", "requiredActivationApprovals"], + "properties": { + "auto_activate": { "type": "boolean" }, + "requiredActivationApprovals": { + "type": "array", "uniqueItems": true, + "items": { "enum": ["project_lead", "security_reviewer", "release_manager", "admin"] } + } + }, + "additionalProperties": false + } + } + } + ], + "unevaluatedProperties": false +} \ No newline at end of file diff --git a/schemas/v2/common.schema.json b/schemas/v2/common.schema.json new file mode 100644 index 0000000..3cc047e --- /dev/null +++ b/schemas/v2/common.schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-protocols.rna4219.dev/schemas/v2/common.schema.json", + "title": "agent-protocols v2 common contract metadata", + "type": "object", + "required": ["schemaVersion", "id", "kind", "lifecycle", "revision", "createdAt", "updatedAt"], + "properties": { + "schemaVersion": { "const": "2.0.0" }, + "id": { "type": "string", "pattern": "^(IntentContract|TaskSeed|Acceptance|PublishGate|Evidence)_[0-9A-HJKMNP-TV-Z]{26}$" }, + "kind": { "type": "string", "enum": ["IntentContract", "TaskSeed", "Acceptance", "PublishGate", "Evidence"] }, + "lifecycle": { "type": "string", "enum": ["draft", "active", "frozen", "final", "superseded", "revoked", "archived"] }, + "revision": { "type": "integer", "minimum": 1 }, + "createdAt": { "type": "string", "format": "date-time", "pattern": "Z$" }, + "updatedAt": { "type": "string", "format": "date-time", "pattern": "Z$" } + } +} \ No newline at end of file diff --git a/scripts/generate-types.mjs b/scripts/generate-types.mjs new file mode 100644 index 0000000..bf25012 --- /dev/null +++ b/scripts/generate-types.mjs @@ -0,0 +1,31 @@ +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; + +const schemaDir = new URL('../schemas/v2/', import.meta.url); +const generatedTypes = new URL('../src/generated/contracts.ts', import.meta.url); +const generatedManifest = new URL('../src/generated/schema-manifest.ts', import.meta.url); +const kinds = ['IntentContract', 'TaskSeed', 'Acceptance', 'PublishGate', 'Evidence']; +const schemaNames = ['common', ...kinds, 'CloudEvent']; +const schemas = {}; +for (const name of schemaNames) { + const path = new URL(name + '.schema.json', schemaDir); + if (!existsSync(path)) throw new Error('Missing schema: ' + path.pathname); + schemas[name] = JSON.parse(readFileSync(path, 'utf8')); +} +const content = readFileSync(generatedTypes, 'utf8'); +for (const kind of kinds) { + if (!content.includes('interface ' + kind)) throw new Error('Generated type is missing: ' + kind); +} +const manifest = '/* Generated from schemas/v2. Do not edit manually. */\nexport const V2_SCHEMA_MANIFEST = ' + + JSON.stringify(Object.fromEntries(Object.entries(schemas).map(([name, schema]) => [name, { + id: schema.$id, + title: schema.title, + required: schema.required ?? schema.allOf?.flatMap((part) => part.required ?? []) ?? [], + }])), null, 2) + ' as const;\n'; +if (process.argv.includes('--check')) { + if (readFileSync(generatedManifest, 'utf8') !== manifest) throw new Error('Generated schema manifest is out of date'); + process.stdout.write('generated types and schema manifest are up to date\n'); +} else { + writeFileSync(generatedTypes, content, 'utf8'); + writeFileSync(generatedManifest, manifest, 'utf8'); + process.stdout.write('generated types and schema manifest verified from schemas/v2\n'); +} diff --git a/scripts/package-smoke.mjs b/scripts/package-smoke.mjs new file mode 100644 index 0000000..557146d --- /dev/null +++ b/scripts/package-smoke.mjs @@ -0,0 +1,31 @@ +import { execFileSync, spawnSync } from 'node:child_process'; +import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const packageRoot = fileURLToPath(new URL('..', import.meta.url)); +const temp = mkdtempSync(join(tmpdir(), 'agent-protocols-v2-')); +try { + const command = process.platform === 'win32' ? (process.env.ComSpec ?? 'cmd.exe') : 'npm'; + const npm = (args, cwd) => process.platform === 'win32' + ? execFileSync(command, ['/d', '/c', 'npm ' + args.join(' ')], { cwd, encoding: 'utf8' }) + : execFileSync(command, args, { cwd, encoding: 'utf8' }); + const packed = npm(['pack', '--pack-destination=' + temp], packageRoot).trim().split(/\r?\n/).pop(); + const tarball = join(temp, packed ?? ''); + if (!packed || !existsSync(tarball)) throw new Error('npm pack did not create a tarball'); + writeFileSync(join(temp, 'package.json'), JSON.stringify({ name: 'agent-protocols-v2-consumer', private: true, type: 'module' }, null, 2)); + writeFileSync(join(temp, 'consumer.ts'), "import { createContractId, type Contract } from '@rna4219/agent-protocols';\nconst id: string = createContractId('IntentContract');\nconst value: Contract | undefined = undefined;\nvoid id; void value;\n"); + writeFileSync(join(temp, 'tsconfig.json'), JSON.stringify({ compilerOptions: { target: 'ES2022', module: 'NodeNext', moduleResolution: 'NodeNext', strict: true, skipLibCheck: true, noEmit: true }, include: ['consumer.ts'] }, null, 2)); + npm(['install', '--no-audit', '--no-fund', '--ignore-scripts', tarball], temp); + execFileSync(process.execPath, ['--input-type=module', '-e', "import { createContractId } from '@rna4219/agent-protocols'; if (!createContractId('IntentContract')) throw new Error('import failed');"], { cwd: temp, stdio: 'inherit' }); + JSON.parse(readFileSync(join(temp, 'node_modules', '@rna4219', 'agent-protocols', 'schemas', 'v2', 'common.schema.json'), 'utf8')); + const tsc = join(packageRoot, 'node_modules', 'typescript', 'bin', 'tsc'); + execFileSync(process.execPath, [tsc, '--noEmit', '-p', join(temp, 'tsconfig.json')], { cwd: temp, stdio: 'inherit' }); + const cli = join(temp, 'node_modules', '@rna4219', 'agent-protocols', 'dist', 'cli', 'index.js'); + const cliResult = spawnSync(process.execPath, [cli], { cwd: temp, encoding: 'utf8' }); + if (cliResult.status !== 1 || !cliResult.stderr.includes('Usage: agent-protocols')) throw new Error('CLI smoke failed'); + process.stdout.write('package consumer smoke passed\n'); +} finally { + rmSync(temp, { recursive: true, force: true }); +} diff --git a/src/cli/index.ts b/src/cli/index.ts new file mode 100644 index 0000000..b801c7b --- /dev/null +++ b/src/cli/index.ts @@ -0,0 +1,33 @@ +#!/usr/bin/env node +import { migrateV1 } from '../migration/index.js'; +import { fileURLToPath } from 'node:url'; +import { resolve } from 'node:path'; + +export function parseArgs(args: string[]): { input: string; namespace: string; out: string } { + if (args[0] !== 'migrate-v1') throw new Error('Usage: agent-protocols migrate-v1 --namespace --out '); + const input = args[1]; + let namespace = ''; + let out = ''; + for (let index = 2; index < args.length; index += 1) { + if (args[index] === '--namespace') namespace = args[++index] ?? ''; + else if (args[index] === '--out') out = args[++index] ?? ''; + else throw new Error('Unknown argument: ' + args[index]); + } + if (!input || !namespace || !out) throw new Error('Usage: agent-protocols migrate-v1 --namespace --out '); + return { input, namespace, out }; +} + +export function main(args = process.argv.slice(2)): number { + try { + const options = parseArgs(args); + const report = migrateV1(options.input, options.namespace, options.out); + process.stdout.write(JSON.stringify(report) + '\n'); + return 0; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + process.stderr.write(message + '\n'); + return 1; + } +} + +if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) process.exitCode = main(); diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..9c38751 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,20 @@ +import type { ProtocolError } from './validation-types.js'; + +export class ProtocolException extends Error { + readonly errors: ProtocolError[]; + + constructor(message: string, errors: ProtocolError[] = []) { + super(message); + this.name = 'ProtocolException'; + this.errors = errors; + } +} + +export function protocolError( + code: string, + path: string, + message: string, + source: ProtocolError['source'], +): ProtocolError { + return { code, path, message, source }; +} diff --git a/src/events.ts b/src/events.ts new file mode 100644 index 0000000..8a6115f --- /dev/null +++ b/src/events.ts @@ -0,0 +1,52 @@ +import type { CloudEvent, Contract } from './generated/contracts.js'; +import { createContractId, formatUtc } from './id.js'; +import { safeParseContract, safeParseEvent } from './validation-v2.js'; +import { ProtocolException } from './errors.js'; +import type { Clock } from './policy.js'; + +export interface ContractEventOptions { + source?: string; + type?: string; + subject?: string; + correlationid?: string; + causationid?: string; + idempotencykey?: string; + eventId?: string; + clock?: Clock; +} + +function clockDate(clock?: Clock): Date { + const value = clock ? clock() : new Date(); + const date = value instanceof Date ? new Date(value.getTime()) : new Date(value); + if (!Number.isFinite(date.getTime())) throw new Error('Invalid event clock value'); + return date; +} + +function eventId(contract: Contract, at: Date): string { + const suffix = createContractId(contract.kind, { now: at }).split('_')[1]; + return 'event_' + suffix; +} + +export function createContractEvent(contractInput: Contract, options: ContractEventOptions = {}): CloudEvent { + const validation = safeParseContract(contractInput); + if (!validation.success) throw new ProtocolException('Contract event data is invalid', validation.errors); + const contract = validation.data; + const at = clockDate(options.clock); + const event: CloudEvent = { + specversion: '1.0', + id: options.eventId ?? eventId(contract, at), + source: options.source ?? 'urn:rna4219:agent-protocols', + type: options.type ?? 'com.rna4219.agent-protocols.' + contract.kind + '.v2', + subject: options.subject ?? contract.id, + time: formatUtc(at), + datacontenttype: 'application/json', + data: contract, + correlationid: options.correlationid ?? contract.id, + causationid: options.causationid ?? contract.id, + idempotencykey: options.idempotencykey ?? contract.id + ':' + contract.revision, + contractrevision: contract.revision, + }; + const parsed = safeParseEvent(event); + if (!parsed.success) throw new ProtocolException('Generated CloudEvent failed validation', parsed.errors); + return parsed.data; +} diff --git a/src/gates.ts b/src/gates.ts new file mode 100644 index 0000000..d3ab240 --- /dev/null +++ b/src/gates.ts @@ -0,0 +1,119 @@ +import type { Acceptance, ApprovalRecord, ContractKind, PublishGate } from './generated/contracts.js'; +import { ProtocolException, protocolError } from './errors.js'; +import { createContractId, formatUtc } from './id.js'; +import { assessPolicy, type Clock, type RiskFactors } from './policy.js'; +import { safeParseContract } from './validation-v2.js'; + +export interface GateOptions { + clock?: Clock; + idGenerator?: (kind: ContractKind, at: Date) => string; + riskFactors?: RiskFactors; +} + +function clockDate(clock?: Clock): Date { + const value = clock ? clock() : new Date(); + const date = value instanceof Date ? new Date(value.getTime()) : new Date(value); + if (!Number.isFinite(date.getTime())) throw new Error('Invalid clock value'); + return date; +} + +function reject(message: string, code: string, path = '/'): never { + throw new ProtocolException(message, [protocolError(code, path, message, 'semantic')]); +} + +function requireGate(input: unknown): PublishGate { + const result = safeParseContract(input); + if (!result.success || result.data.kind !== 'PublishGate') { + throw new ProtocolException('Invalid PublishGate', result.success ? [] : result.errors); + } + return result.data; +} + +export function createPublishGate( + acceptanceInput: unknown, + capabilities: readonly string[], + options: GateOptions = {}, +): PublishGate { + const acceptanceResult = safeParseContract(acceptanceInput); + if (!acceptanceResult.success || acceptanceResult.data.kind !== 'Acceptance') { + throw new ProtocolException('Acceptance schema or semantic validation failed', acceptanceResult.success ? [] : acceptanceResult.errors); + } + const acceptance = acceptanceResult.data as Acceptance; + if (acceptance.status !== 'passed') { + reject('Only a passed Acceptance can create a PublishGate', 'ACCEPTANCE_NOT_PASSED', '/status'); + } + const clock = clockDate(options.clock); + const timestamp = formatUtc(clock); + const assessment = assessPolicy(capabilities, options.riskFactors, { clock: () => clock }); + const id = options.idGenerator ? options.idGenerator('PublishGate', clock) : createContractId('PublishGate', { now: clock }); + const gate: PublishGate = { + schemaVersion: '2.0.0', + id, + kind: 'PublishGate', + lifecycle: assessment.autoApproved ? 'final' : 'active', + revision: 1, + createdAt: timestamp, + updatedAt: timestamp, + acceptanceId: acceptance.id, + operation: 'publish', + riskLevel: assessment.riskLevel, + requiredApprovals: assessment.requiredApprovals, + approvals: assessment.autoApproved + ? [{ role: 'policy_engine', actorId: 'policy-engine', decision: 'approved', decidedAt: timestamp }] + : [], + decision: assessment.autoApproved ? 'approved' : 'pending', + }; + if (assessment.approvalDeadline) gate.approvalDeadline = assessment.approvalDeadline; + const validation = safeParseContract(gate); + if (!validation.success) throw new ProtocolException('Generated PublishGate failed validation', validation.errors); + return validation.data as PublishGate; +} + +export interface ApprovalInput { + role: ApprovalRecord['role']; + actorId: string; + decision: ApprovalRecord['decision']; + decidedAt?: string; + reason?: string; +} + +export function applyApproval(gateInput: unknown, input: ApprovalInput, options: { clock?: Clock } = {}): PublishGate { + const gate = requireGate(gateInput); + if (gate.decision !== 'pending' || gate.lifecycle !== 'active') reject('Only a pending active gate accepts approvals', 'GATE_NOT_PENDING', '/decision'); + if (!gate.requiredApprovals.includes(input.role as never)) reject('Approval role was not requested', 'UNREQUESTED_APPROVAL_ROLE', '/role'); + if (gate.approvals.some((approval) => approval.role === input.role)) reject('A role cannot be decided twice', 'DUPLICATE_APPROVAL_ROLE', '/role'); + const now = clockDate(options.clock); + if (gate.approvalDeadline && now.getTime() > new Date(gate.approvalDeadline).getTime()) reject('Approval deadline has expired', 'APPROVAL_AFTER_DEADLINE', '/approvalDeadline'); + const decidedAt = input.decidedAt ?? formatUtc(now); + if (new Date(decidedAt).getTime() > now.getTime()) reject('Approval time cannot be in the future', 'APPROVAL_TIME_INVALID', '/decidedAt'); + const approval: ApprovalRecord = { role: input.role, actorId: input.actorId, decision: input.decision, decidedAt }; + if (input.reason !== undefined) approval.reason = input.reason; + const approvals = [...gate.approvals, approval]; + const rejected = approval.decision === 'rejected'; + const approvedRoles = new Set(approvals.filter((item) => item.decision === 'approved').map((item) => item.role)); + const complete = gate.requiredApprovals.every((role) => approvedRoles.has(role)); + const decision = rejected ? 'rejected' : complete ? 'approved' : 'pending'; + const next: PublishGate = { + ...gate, + approvals, + decision, + lifecycle: decision === 'pending' ? 'active' : 'final', + revision: gate.revision + 1, + updatedAt: formatUtc(now), + }; + const validation = safeParseContract(next); + if (!validation.success) throw new ProtocolException('Updated PublishGate failed validation', validation.errors); + return validation.data as PublishGate; +} + +export function expireGate(gateInput: unknown, options: { clock?: Clock } = {}): PublishGate { + const gate = requireGate(gateInput); + if (gate.decision !== 'pending' || gate.lifecycle !== 'active') reject('Only a pending active gate can expire', 'GATE_NOT_PENDING', '/decision'); + if (!gate.approvalDeadline) reject('Pending gate has no deadline', 'APPROVAL_DEADLINE_REQUIRED', '/approvalDeadline'); + const now = clockDate(options.clock); + if (now.getTime() <= new Date(gate.approvalDeadline).getTime()) reject('Approval deadline has not elapsed', 'DEADLINE_NOT_REACHED', '/approvalDeadline'); + const next: PublishGate = { ...gate, decision: 'expired', lifecycle: 'frozen', revision: gate.revision + 1, updatedAt: formatUtc(now) }; + const validation = safeParseContract(next); + if (!validation.success) throw new ProtocolException('Expired PublishGate failed validation', validation.errors); + return validation.data as PublishGate; +} diff --git a/src/generated/contracts.ts b/src/generated/contracts.ts new file mode 100644 index 0000000..e7dad26 --- /dev/null +++ b/src/generated/contracts.ts @@ -0,0 +1,137 @@ +/* Generated from schemas/v2 by scripts/generate-types.mjs. Do not edit manually. */ +export type ContractKind = 'IntentContract' | 'TaskSeed' | 'Acceptance' | 'PublishGate' | 'Evidence'; +export type Lifecycle = 'draft' | 'active' | 'frozen' | 'final' | 'superseded' | 'revoked' | 'archived'; +export type Capability = 'read_repo' | 'write_repo' | 'install_deps' | 'network_access' | 'read_secrets' | 'publish_release'; +export type Priority = 'low' | 'medium' | 'high' | 'critical'; +export type RiskLevel = Priority; +export type ApprovalRole = 'project_lead' | 'security_reviewer' | 'release_manager' | 'policy_engine' | 'admin'; +export type ExecutionRole = 'developer' | 'ci_agent' | 'qa' | 'project_lead' | 'release_manager' | 'admin'; +export type AcceptanceStatus = 'pending' | 'passed' | 'failed' | 'blocked'; +export type ApprovalDecision = 'approved' | 'rejected'; +export type GateDecision = 'pending' | 'approved' | 'rejected' | 'expired'; +export type EvidenceStage = 'plan' | 'execution' | 'acceptance' | 'publish' | 'integration'; +export type PolicyVerdict = 'approved' | 'rejected' | 'manual_review_required'; + +export interface BaseContract { + schemaVersion: '2.0.0'; + id: string; + kind: ContractKind; + lifecycle: Lifecycle; + revision: number; + createdAt: string; + updatedAt: string; +} + +export interface GenerationPolicy { + auto_activate: boolean; + requiredActivationApprovals: Exclude[]; +} + +export interface IntentContract extends BaseContract { + kind: 'IntentContract'; + intent: string; + creator: string; + priority: Priority; + requestedCapabilities: Capability[]; +} + +export interface TaskSeed extends BaseContract { + kind: 'TaskSeed'; + intentId: string; + description: string; + ownerRole: ExecutionRole; + executionPlan: string[]; + requestedCapabilitiesSnapshot: Capability[]; + generationPolicy: GenerationPolicy; +} + +export interface Acceptance extends BaseContract { + kind: 'Acceptance'; + taskSeedId: string; + status: AcceptanceStatus; + details: string; + criteria: string[]; + generationPolicy: GenerationPolicy; +} + +export interface ApprovalRecord { + role: ApprovalRole; + actorId: string; + decision: ApprovalDecision; + decidedAt: string; + reason?: string; +} + +export interface PublishGate extends BaseContract { + kind: 'PublishGate'; + acceptanceId: string; + operation: 'publish'; + riskLevel: RiskLevel; + requiredApprovals: Exclude[]; + approvals: ApprovalRecord[]; + decision: GateDecision; + approvalDeadline?: string; +} + +export interface HashReference { + algorithm: string; + value: string; +} + +export interface ToolReference { + name: string; + version?: string; + digest?: HashReference; +} + +export interface Evidence extends BaseContract { + kind: 'Evidence'; + lifecycle: 'final'; + revision: 1; + stage: EvidenceStage; + taskSeedId: string; + acceptanceId?: string; + publishGateId?: string; + baseCommit: HashReference; + headCommit: HashReference; + inputHash: HashReference; + outputHash: HashReference; + model: { name: string; version: string; parametersHash: HashReference }; + tools: ToolReference[]; + environment: { + os: string; + runtime: string; + containerImageDigest: HashReference; + lockfileHash: HashReference; + }; + staleStatus: { classification: 'fresh' | 'soft_stale' | 'hard_stale'; evaluatedAt: string; reason?: string }; + mergeResult: { + status: 'not_applicable' | 'not_attempted' | 'merged' | 'manual_resolution_required'; + mergedAt?: string; + strategy?: string; + reason?: string; + }; + startTime: string; + endTime: string; + actor: string; + approvalsSnapshot?: ApprovalRecord[]; + policyVerdict: PolicyVerdict; + diffHash: HashReference; +} + +export type Contract = IntentContract | TaskSeed | Acceptance | PublishGate | Evidence; + +export interface CloudEvent { + specversion: '1.0'; + id: string; + source: string; + type: string; + subject: string; + time: string; + datacontenttype?: 'application/json'; + data: T; + correlationid: string; + causationid: string; + idempotencykey: string; + contractrevision: number; +} diff --git a/src/generated/schema-manifest.ts b/src/generated/schema-manifest.ts new file mode 100644 index 0000000..4eb1089 --- /dev/null +++ b/src/generated/schema-manifest.ts @@ -0,0 +1,100 @@ +/* Generated from schemas/v2. Do not edit manually. */ +export const V2_SCHEMA_MANIFEST = { + "common": { + "id": "https://agent-protocols.rna4219.dev/schemas/v2/common.schema.json", + "title": "agent-protocols v2 common contract metadata", + "required": [ + "schemaVersion", + "id", + "kind", + "lifecycle", + "revision", + "createdAt", + "updatedAt" + ] + }, + "IntentContract": { + "id": "https://agent-protocols.rna4219.dev/schemas/v2/IntentContract.schema.json", + "title": "IntentContract v2", + "required": [ + "intent", + "creator", + "priority", + "requestedCapabilities" + ] + }, + "TaskSeed": { + "id": "https://agent-protocols.rna4219.dev/schemas/v2/TaskSeed.schema.json", + "title": "TaskSeed v2", + "required": [ + "intentId", + "description", + "ownerRole", + "executionPlan", + "requestedCapabilitiesSnapshot", + "generationPolicy" + ] + }, + "Acceptance": { + "id": "https://agent-protocols.rna4219.dev/schemas/v2/Acceptance.schema.json", + "title": "Acceptance v2", + "required": [ + "taskSeedId", + "status", + "details", + "criteria", + "generationPolicy" + ] + }, + "PublishGate": { + "id": "https://agent-protocols.rna4219.dev/schemas/v2/PublishGate.schema.json", + "title": "PublishGate v2", + "required": [ + "acceptanceId", + "operation", + "riskLevel", + "requiredApprovals", + "approvals", + "decision" + ] + }, + "Evidence": { + "id": "https://agent-protocols.rna4219.dev/schemas/v2/Evidence.schema.json", + "title": "Evidence v2", + "required": [ + "stage", + "taskSeedId", + "baseCommit", + "headCommit", + "inputHash", + "outputHash", + "model", + "tools", + "environment", + "staleStatus", + "mergeResult", + "startTime", + "endTime", + "actor", + "policyVerdict", + "diffHash" + ] + }, + "CloudEvent": { + "id": "https://agent-protocols.rna4219.dev/schemas/v2/CloudEvent.schema.json", + "title": "agent-protocols CloudEvents 1.0", + "required": [ + "specversion", + "id", + "source", + "type", + "subject", + "time", + "data", + "correlationid", + "causationid", + "idempotencykey", + "contractrevision" + ] + } +} as const; diff --git a/src/graph.ts b/src/graph.ts new file mode 100644 index 0000000..71b5254 --- /dev/null +++ b/src/graph.ts @@ -0,0 +1,131 @@ +import type { Contract, ContractKind, Evidence, PublishGate } from './generated/contracts.js'; +import { protocolError } from './errors.js'; +import { safeParseContract } from './validation-v2.js'; +import type { ProtocolError, ValidationSummary } from './validation-types.js'; + +function summary(errors: ProtocolError[]): ValidationSummary { + return { valid: errors.length === 0, success: errors.length === 0, errors }; +} + +const allowed: Record, Record> = { + IntentContract: { + draft: ['active', 'frozen', 'revoked', 'archived'], active: ['frozen', 'final', 'revoked'], + frozen: ['active', 'final', 'revoked', 'archived'], final: ['superseded', 'revoked', 'archived'], + superseded: ['archived'], revoked: ['archived'], archived: [], + }, + TaskSeed: { + draft: ['active', 'frozen', 'revoked', 'archived'], active: ['frozen', 'final', 'revoked'], + frozen: ['active', 'final', 'revoked', 'archived'], final: ['superseded', 'revoked', 'archived'], + superseded: ['archived'], revoked: ['archived'], archived: [], + }, + Acceptance: { + draft: ['active', 'frozen', 'revoked', 'archived'], active: ['frozen', 'final', 'revoked'], + frozen: ['active', 'final', 'revoked', 'archived'], final: ['superseded', 'revoked', 'archived'], + superseded: ['archived'], revoked: ['archived'], archived: [], + }, + PublishGate: { + active: ['final', 'frozen', 'revoked'], frozen: ['active', 'final', 'revoked', 'archived'], + final: ['archived', 'revoked'], revoked: ['archived'], archived: [], + }, +}; + +function refFields(contract: Contract): string[] { + switch (contract.kind) { + case 'TaskSeed': return ['intentId']; + case 'Acceptance': return ['taskSeedId']; + case 'PublishGate': return ['acceptanceId']; + case 'Evidence': return ['taskSeedId', 'acceptanceId', 'publishGateId']; + default: return []; + } +} + +export function validateTransition(previousInput: unknown, nextInput: unknown): ValidationSummary { + const errors: ProtocolError[] = []; + const previousResult = safeParseContract(previousInput); + const nextResult = safeParseContract(nextInput); + if (!previousResult.success) errors.push(...previousResult.errors); + if (!nextResult.success) errors.push(...nextResult.errors); + if (!previousResult.success || !nextResult.success) return summary(errors); + const previous = previousResult.data; + const next = nextResult.data; + if (previous.kind !== next.kind || previous.id !== next.id) { + errors.push(protocolError('TRANSITION_IDENTITY', '/', 'A transition must keep kind and id unchanged', 'semantic')); + return summary(errors); + } + if (previous.kind === 'Evidence') { + errors.push(protocolError('EVIDENCE_IMMUTABLE', '/', 'Evidence cannot be updated', 'semantic')); + return summary(errors); + } + if (next.revision !== previous.revision + 1) errors.push(protocolError('REVISION_MISMATCH', '/revision', 'revision must increase by exactly one', 'semantic')); + if (next.createdAt !== previous.createdAt) errors.push(protocolError('CREATED_AT_IMMUTABLE', '/createdAt', 'createdAt cannot change', 'semantic')); + if (new Date(next.updatedAt).getTime() < new Date(previous.updatedAt).getTime()) errors.push(protocolError('UPDATED_AT_ORDER', '/updatedAt', 'updatedAt cannot move backwards', 'semantic')); + if (previous.lifecycle !== next.lifecycle) { + const nextStates = allowed[previous.kind][previous.lifecycle] ?? []; + if (!nextStates.includes(next.lifecycle)) errors.push(protocolError('ILLEGAL_TRANSITION', '/lifecycle', 'Lifecycle transition is not allowed for this kind', 'semantic')); + } + if (next.kind === 'PublishGate') { + const gate = next as PublishGate; + if (gate.decision === 'pending' && gate.lifecycle !== 'active') errors.push(protocolError('GATE_LIFECYCLE_MISMATCH', '/lifecycle', 'Pending gates must be active', 'semantic')); + if ((gate.decision === 'approved' || gate.decision === 'rejected') && gate.lifecycle !== 'final') errors.push(protocolError('GATE_LIFECYCLE_MISMATCH', '/lifecycle', 'Decided gates must be final', 'semantic')); + if (gate.decision === 'expired' && gate.lifecycle !== 'frozen') errors.push(protocolError('GATE_LIFECYCLE_MISMATCH', '/lifecycle', 'Expired gates must be frozen', 'semantic')); + } + for (const field of refFields(previous)) { + if ((previous as unknown as Record)[field] !== (next as unknown as Record)[field]) { + errors.push(protocolError('REFERENCE_IMMUTABLE', '/' + field, 'Contract references cannot change during a transition', 'reference')); + } + } + return summary(errors); +} + +export function validateContractGraph(inputs: Iterable): ValidationSummary { + const errors: ProtocolError[] = []; + const contracts: Contract[] = []; + for (const input of inputs) { + const result = safeParseContract(input); + if (!result.success) errors.push(...result.errors); + else contracts.push(result.data); + } + const byId = new Map(contracts.map((contract) => [contract.id, contract])); + for (const contract of contracts) { + if (contract.kind === 'TaskSeed') { + const target = byId.get(contract.intentId); + if (!target) errors.push(protocolError('REFERENCE_NOT_FOUND', '/intentId', 'Referenced IntentContract was not found', 'reference')); + else if (target.kind !== 'IntentContract') errors.push(protocolError('REFERENCE_KIND_MISMATCH', '/intentId', 'intentId must reference IntentContract', 'reference')); + else if (JSON.stringify(target.requestedCapabilities) !== JSON.stringify(contract.requestedCapabilitiesSnapshot)) { + errors.push(protocolError('CAPABILITY_SNAPSHOT_MISMATCH', '/requestedCapabilitiesSnapshot', 'TaskSeed capability snapshot must match IntentContract', 'reference')); + } + } + if (contract.kind === 'Acceptance') { + const target = byId.get(contract.taskSeedId); + if (!target) errors.push(protocolError('REFERENCE_NOT_FOUND', '/taskSeedId', 'Referenced TaskSeed was not found', 'reference')); + else if (target.kind !== 'TaskSeed') errors.push(protocolError('REFERENCE_KIND_MISMATCH', '/taskSeedId', 'taskSeedId must reference TaskSeed', 'reference')); + } + if (contract.kind === 'PublishGate') { + const target = byId.get(contract.acceptanceId); + if (!target) errors.push(protocolError('REFERENCE_NOT_FOUND', '/acceptanceId', 'Referenced Acceptance was not found', 'reference')); + else if (target.kind !== 'Acceptance') errors.push(protocolError('REFERENCE_KIND_MISMATCH', '/acceptanceId', 'acceptanceId must reference Acceptance', 'reference')); + else if (target.status !== 'passed') errors.push(protocolError('ACCEPTANCE_NOT_PASSED', '/acceptanceId', 'PublishGate requires a passed Acceptance', 'reference')); + } + if (contract.kind === 'Evidence') { + const evidence = contract as Evidence; + const task = byId.get(evidence.taskSeedId); + if (!task) errors.push(protocolError('REFERENCE_NOT_FOUND', '/taskSeedId', 'Referenced TaskSeed was not found', 'reference')); + else if (task.kind !== 'TaskSeed') errors.push(protocolError('REFERENCE_KIND_MISMATCH', '/taskSeedId', 'taskSeedId must reference TaskSeed', 'reference')); + if (evidence.stage === 'publish') { + const acceptance = evidence.acceptanceId ? byId.get(evidence.acceptanceId) : undefined; + const gate = evidence.publishGateId ? byId.get(evidence.publishGateId) : undefined; + if (!acceptance || acceptance.kind !== 'Acceptance') errors.push(protocolError('REFERENCE_NOT_FOUND', '/acceptanceId', 'Publish Evidence must reference Acceptance', 'reference')); + if (!gate || gate.kind !== 'PublishGate') errors.push(protocolError('REFERENCE_NOT_FOUND', '/publishGateId', 'Publish Evidence must reference PublishGate', 'reference')); + if (acceptance && acceptance.kind === 'Acceptance' && acceptance.taskSeedId !== evidence.taskSeedId) errors.push(protocolError('REFERENCE_CHAIN_MISMATCH', '/acceptanceId', 'Acceptance must reference Evidence.taskSeedId', 'reference')); + if (gate && gate.kind === 'PublishGate' && gate.acceptanceId !== evidence.acceptanceId) errors.push(protocolError('REFERENCE_CHAIN_MISMATCH', '/publishGateId', 'PublishGate must reference Evidence.acceptanceId', 'reference')); + if (gate && gate.kind === 'PublishGate' && (gate.riskLevel === 'high' || gate.riskLevel === 'critical') && !evidence.approvalsSnapshot) { + errors.push(protocolError('APPROVAL_SNAPSHOT_REQUIRED', '/approvalsSnapshot', 'Manual approval PublishGate requires approvalsSnapshot', 'reference')); + } + if (gate && gate.kind === 'PublishGate' && evidence.approvalsSnapshot && JSON.stringify(gate.approvals) !== JSON.stringify(evidence.approvalsSnapshot)) { + errors.push(protocolError('APPROVAL_SNAPSHOT_MISMATCH', '/approvalsSnapshot', 'Evidence approvalsSnapshot must exactly match PublishGate approvals', 'reference')); + } + } + } + } + return summary(errors); +} diff --git a/src/id.ts b/src/id.ts new file mode 100644 index 0000000..20de00e --- /dev/null +++ b/src/id.ts @@ -0,0 +1,58 @@ +import { createHash, randomBytes } from 'node:crypto'; +import type { ContractKind } from './generated/contracts.js'; + +const ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; +const KIND_NAMES: ContractKind[] = ['IntentContract', 'TaskSeed', 'Acceptance', 'PublishGate', 'Evidence']; + +function timestampPart(timestampMs: number): string { + let value = BigInt(timestampMs); + let output = ''; + for (let index = 0; index < 10; index += 1) { + output = ALPHABET[Number(value & 31n)] + output; + value >>= 5n; + } + return output; +} + +function randomPart(bytes: Uint8Array): string { + let value = 0n; + for (const byte of bytes) value = (value << 8n) | BigInt(byte); + let output = ''; + for (let index = 0; index < 16; index += 1) { + output = ALPHABET[Number(value & 31n)] + output; + value >>= 5n; + } + return output; +} + +function asDate(value: Date | string | number | undefined): Date { + const date = value instanceof Date ? new Date(value.getTime()) : new Date(value ?? Date.now()); + if (!Number.isFinite(date.getTime())) throw new Error('Invalid ULID timestamp'); + return date; +} + +export function formatUtc(value: Date | string | number): string { + return asDate(value).toISOString(); +} + +export interface ContractIdOptions { + now?: Date | string | number; + random?: Uint8Array; +} + +export function createContractId(kind: ContractKind, options: ContractIdOptions = {}): string { + if (!KIND_NAMES.includes(kind)) throw new Error('Unknown contract kind: ' + String(kind)); + const date = asDate(options.now); + const bytes = options.random ?? randomBytes(10); + if (bytes.length !== 10) throw new Error('ULID random component must be 10 bytes'); + return kind + '_' + timestampPart(date.getTime()) + randomPart(bytes); +} + +export function createDeterministicContractId(kind: ContractKind, key: string, createdAt: string): string { + const digest = createHash('sha256').update(key, 'utf8').digest(); + return createContractId(kind, { now: createdAt, random: digest.subarray(0, 10) }); +} + +export function isContractIdForKind(value: unknown, kind: ContractKind): value is string { + return typeof value === 'string' && new RegExp('^' + kind + '_[0-9A-HJKMNP-TV-Z]{26}$').test(value); +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..0509c1e --- /dev/null +++ b/src/index.ts @@ -0,0 +1,9 @@ +export * from './generated/contracts.js'; +export * from './validation-types.js'; +export * from './validation-v2.js'; +export * from './policy.js'; +export * from './gates.js'; +export * from './events.js'; +export * from './graph.js'; +export * from './id.js'; +export { ProtocolException } from './errors.js'; diff --git a/src/migration/index.ts b/src/migration/index.ts new file mode 100644 index 0000000..ecb0f40 --- /dev/null +++ b/src/migration/index.ts @@ -0,0 +1,217 @@ +import { createHash } from 'node:crypto'; +import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'; +import { dirname, isAbsolute, join } from 'node:path'; +import type { Contract, ContractKind, ApprovalRecord } from '../generated/contracts.js'; +import { createDeterministicContractId } from '../id.js'; +import { safeParseContract } from '../validation-v2.js'; +import { validateContractGraph } from '../graph.js'; +import { ProtocolException, protocolError } from '../errors.js'; + +const KINDS: ContractKind[] = ['IntentContract', 'TaskSeed', 'Acceptance', 'PublishGate', 'Evidence']; +const CAPABILITIES = new Set(['read_repo', 'write_repo', 'install_deps', 'network_access', 'read_secrets', 'publish_release']); +const STATES = new Set(['Draft', 'Active', 'Frozen', 'Published', 'Superseded', 'Revoked', 'Archived']); +const STATE_MAP: Record = { + Draft: 'draft', Active: 'active', Frozen: 'frozen', Published: 'final', + Superseded: 'superseded', Revoked: 'revoked', Archived: 'archived', +}; +const PREFIX: Record = { + IntentContract: 'IC', TaskSeed: 'TS', Acceptance: 'AC', PublishGate: 'PG', Evidence: 'EV', +}; + +export interface MigrationReport { + success: true; + schemaVersion: '2.0.0'; + namespace: string; + inputPath: string; + contractCount: number; + migratedAt: string; +} + +function fail(code: string, path: string, message: string): never { + throw new ProtocolException(message, [protocolError(code, path, message, 'schema')]); +} + +function parseInput(inputPath: string): Record[] { + if (!isAbsolute(inputPath)) fail('INPUT_PATH_NOT_ABSOLUTE', '/', 'Input path must be absolute'); + if (!existsSync(inputPath)) fail('INPUT_NOT_FOUND', '/', 'Input file does not exist'); + const text = readFileSync(inputPath, 'utf8').replace(/^\uFEFF/, '').trim(); + if (!text) fail('INPUT_EMPTY', '/', 'Input file is empty'); + try { + const value = JSON.parse(text) as unknown; + const records = Array.isArray(value) ? value : [value]; + if (records.every((record) => Boolean(record) && typeof record === 'object' && !Array.isArray(record))) return records as Record[]; + } catch { + // JSONL is parsed below. + } + const records: Record[] = []; + for (const [lineNumber, line] of text.split(/\r?\n/).entries()) { + if (!line.trim()) continue; + try { + const value = JSON.parse(line) as unknown; + if (!value || typeof value !== 'object' || Array.isArray(value)) fail('INPUT_RECORD_INVALID', '/line/' + (lineNumber + 1), 'JSONL record must be an object'); + records.push(value as Record); + } catch (error) { + if (error instanceof ProtocolException) throw error; + fail('INPUT_JSON_INVALID', '/line/' + (lineNumber + 1), 'Invalid JSON or JSONL input'); + } + } + if (records.length === 0) fail('INPUT_EMPTY', '/', 'Input contains no records'); + return records; +} + +function checkApproval(value: unknown, path: string): void { + if (!value || typeof value !== 'object' || Array.isArray(value)) fail('V1_APPROVAL_INVALID', path, 'Approval record must be an object'); + const approval = value as Record; + if (!['project_lead', 'security_reviewer', 'release_manager', 'policy_engine', 'admin'].includes(String(approval.role))) fail('UNKNOWN_ROLE', path + '/role', 'Unknown approval role'); + if (!['approved', 'rejected'].includes(String(approval.decision))) fail('V1_APPROVAL_INVALID', path + '/decision', 'Invalid approval decision'); + if (typeof approval.actorId !== 'string' || typeof approval.decidedAt !== 'string') fail('V1_APPROVAL_INVALID', path, 'Approval actorId and decidedAt are required'); +} + +function validateV1(record: Record, index: number): void { + const path = '/contracts/' + index; + const kind = record.kind; + if (!KINDS.includes(kind as ContractKind)) fail('UNKNOWN_KIND', path + '/kind', 'Unknown v1 contract kind'); + const prefix = PREFIX[kind as ContractKind]; + if (record.schemaVersion !== '1.0.0') fail('V1_SCHEMA_VERSION', path + '/schemaVersion', 'Input must be a v1 contract'); + if (typeof record.id !== 'string' || !new RegExp('^' + prefix + '-[0-9]{3,}$').test(record.id)) fail('V1_ID_INVALID', path + '/id', 'Invalid v1 contract id'); + if (typeof record.state !== 'string' || !STATES.has(record.state)) fail('V1_STATE_INVALID', path + '/state', 'Invalid v1 state'); + if (!Number.isInteger(record.version) || Number(record.version) < 1) fail('V1_VERSION_INVALID', path + '/version', 'Invalid v1 version'); + if (typeof record.createdAt !== 'string' || Number.isNaN(Date.parse(record.createdAt))) fail('V1_TIMESTAMP_INVALID', path + '/createdAt', 'Invalid createdAt'); + if (typeof record.updatedAt !== 'string' || Number.isNaN(Date.parse(record.updatedAt))) fail('V1_TIMESTAMP_INVALID', path + '/updatedAt', 'Invalid updatedAt'); + if (kind === 'IntentContract') { + if (typeof record.intent !== 'string' || typeof record.creator !== 'string') fail('V1_FIELD_REQUIRED', path, 'IntentContract fields are incomplete'); + if (!Array.isArray(record.requestedCapabilities) || record.requestedCapabilities.length === 0 || record.requestedCapabilities.some((cap) => !CAPABILITIES.has(String(cap)))) fail('UNKNOWN_CAPABILITY', path + '/requestedCapabilities', 'Unknown capability'); + } + if (kind === 'TaskSeed') { + if (typeof record.intentId !== 'string' || typeof record.description !== 'string' || !Array.isArray(record.executionPlan)) fail('V1_FIELD_REQUIRED', path, 'TaskSeed fields are incomplete'); + if (!Array.isArray(record.requestedCapabilitiesSnapshot) || record.requestedCapabilitiesSnapshot.some((cap) => !CAPABILITIES.has(String(cap)))) fail('UNKNOWN_CAPABILITY', path + '/requestedCapabilitiesSnapshot', 'Unknown capability'); + } + if (kind === 'Acceptance') { + if (typeof record.taskSeedId !== 'string' || !['pending', 'passed', 'failed', 'blocked'].includes(String(record.status))) fail('V1_FIELD_REQUIRED', path, 'Acceptance fields are incomplete'); + } + if (kind === 'PublishGate') { + if (typeof record.entityId !== 'string' || record.action !== 'publish' || !['low', 'medium', 'high', 'critical'].includes(String(record.riskLevel))) fail('V1_FIELD_REQUIRED', path, 'PublishGate v1 fields are incomplete'); + if (!Array.isArray(record.approvals) || !Array.isArray(record.requiredApprovals)) fail('V1_FIELD_REQUIRED', path, 'PublishGate approvals are incomplete'); + record.approvals.forEach((approval, approvalIndex) => checkApproval(approval, path + '/approvals/' + approvalIndex)); + } + if (kind === 'Evidence') { + if (typeof record.taskSeedId !== 'string' || typeof record.actor !== 'string') fail('V1_FIELD_REQUIRED', path, 'Evidence fields are incomplete'); + if (Array.isArray(record.approvalsSnapshot)) record.approvalsSnapshot.forEach((approval, approvalIndex) => checkApproval(approval, path + '/approvalsSnapshot/' + approvalIndex)); + } +} + +function hash(value: unknown, algorithm: string): { algorithm: string; value: string } { + if (value && typeof value === 'object' && !Array.isArray(value)) { + const item = value as Record; + if (typeof item.algorithm === 'string' && typeof item.value === 'string') return { algorithm: item.algorithm, value: item.value }; + } + if (typeof value !== 'string' || value.length === 0) fail('V1_HASH_INVALID', '/', 'Commit/hash value is required'); + return { algorithm, value }; +} + +function approval(value: Record): ApprovalRecord { + const result: ApprovalRecord = { + role: String(value.role) as ApprovalRecord['role'], + actorId: String(value.actorId), + decision: String(value.decision) as ApprovalRecord['decision'], + decidedAt: String(value.decidedAt), + }; + if (value.reason !== undefined) result.reason = String(value.reason); + return result; +} + +function ref(id: unknown, map: Map, path: string): string | undefined { + if (id === undefined) return undefined; + if (typeof id !== 'string' || !map.has(id)) fail('REFERENCE_NOT_FOUND', path, 'Referenced v1 id was not found'); + return map.get(id); +} + +function transform(record: Record, map: Map): Record { + const kind = record.kind as ContractKind; + const output: Record = { ...record }; + output.schemaVersion = '2.0.0'; + output.id = map.get(String(record.id)); + output.lifecycle = STATE_MAP[String(record.state)]; + output.revision = kind === 'Evidence' ? 1 : record.version; + output.updatedAt = kind === 'Evidence' ? record.createdAt : record.updatedAt; + delete output.state; + delete output.version; + if (kind === 'TaskSeed') output.intentId = ref(record.intentId, map, '/intentId'); + if (kind === 'Acceptance') output.taskSeedId = ref(record.taskSeedId, map, '/taskSeedId'); + if (kind === 'PublishGate') { + output.acceptanceId = ref(record.entityId, map, '/entityId'); + output.operation = 'publish'; + output.decision = record.finalDecision; + delete output.entityId; + delete output.action; + delete output.finalDecision; + } + if (kind === 'Evidence') { + output.stage = typeof record.stage === 'string' ? record.stage : 'execution'; + output.taskSeedId = ref(record.taskSeedId, map, '/taskSeedId'); + output.acceptanceId = ref(record.acceptanceId, map, '/acceptanceId'); + output.publishGateId = ref(record.publishGateId, map, '/publishGateId'); + output.baseCommit = hash(record.baseCommit, 'git'); + output.headCommit = hash(record.headCommit, 'git'); + output.inputHash = hash(record.inputHash, 'sha256'); + output.outputHash = hash(record.outputHash, 'sha256'); + const model = (record.model ?? {}) as Record; + output.model = { name: String(model.name ?? 'unknown'), version: String(model.version ?? 'unknown'), parametersHash: hash(model.parametersHash ?? 'unknown', 'sha256') }; + output.tools = Array.isArray(record.tools) ? record.tools.map((tool) => typeof tool === 'string' ? { name: tool } : tool) : []; + const environment = (record.environment ?? {}) as Record; + output.environment = { + os: String(environment.os ?? 'unknown'), + runtime: String(environment.runtime ?? 'unknown'), + containerImageDigest: hash(environment.containerImageDigest ?? 'unknown', 'sha256'), + lockfileHash: hash(environment.lockfileHash ?? 'unknown', 'sha256'), + }; + output.approvalsSnapshot = Array.isArray(record.approvalsSnapshot) ? record.approvalsSnapshot.map((item) => approval(item as Record)) : undefined; + output.diffHash = hash(record.diffHash, 'sha256'); + } + if (Array.isArray(record.approvals)) output.approvals = record.approvals.map((item) => approval(item as Record)); + return output; +} + +export function migrateV1(inputPath: string, namespace: string, outputDirectory: string): MigrationReport { + if (!namespace || /[\r\n]/.test(namespace)) fail('NAMESPACE_INVALID', '/namespace', 'Namespace must be a non-empty single line'); + if (!isAbsolute(outputDirectory)) fail('OUTPUT_PATH_NOT_ABSOLUTE', '/', 'Output directory must be absolute'); + if (existsSync(outputDirectory)) fail('OUTPUT_EXISTS', '/', 'Output directory already exists and will not be overwritten'); + const records = parseInput(inputPath); + records.forEach((record, index) => validateV1(record, index)); + const idMap = new Map(); + const reverseMap = new Map(); + for (const record of records) { + const oldId = String(record.id); + const newId = createDeterministicContractId(record.kind as ContractKind, String(record.createdAt) + namespace + String(record.kind) + oldId, String(record.createdAt)); + if (idMap.has(oldId) || reverseMap.has(newId)) fail('ID_COLLISION', '/id', 'ID collision during migration'); + idMap.set(oldId, newId); + reverseMap.set(newId, oldId); + } + const contracts = records.map((record) => transform(record, idMap)); + const parsed: Contract[] = []; + for (const [index, contract] of contracts.entries()) { + const result = safeParseContract(contract); + if (!result.success) throw new ProtocolException('Migrated contract failed v2 validation', result.errors.map((error) => ({ ...error, path: '/contracts/' + index + error.path }))); + parsed.push(result.data); + } + const graph = validateContractGraph(parsed); + if (!graph.valid) throw new ProtocolException('Migrated contract graph has unresolved references', graph.errors); + const temporary = outputDirectory + '.tmp-' + createHash('sha256').update(inputPath + namespace).digest('hex').slice(0, 12); + if (existsSync(temporary)) rmSync(temporary, { recursive: true, force: true }); + mkdirSync(temporary, { recursive: true }); + try { + writeFileSync(join(temporary, 'contracts.v2.jsonl'), parsed.map((contract) => JSON.stringify(contract)).join('\n') + '\n', 'utf8'); + writeFileSync(join(temporary, 'id-map.json'), JSON.stringify(Object.fromEntries(idMap), null, 2) + '\n', 'utf8'); + const report: MigrationReport = { + success: true, schemaVersion: '2.0.0', namespace, inputPath, + contractCount: parsed.length, migratedAt: new Date().toISOString(), + }; + writeFileSync(join(temporary, 'migration-report.json'), JSON.stringify(report, null, 2) + '\n', 'utf8'); + mkdirSync(dirname(outputDirectory), { recursive: true }); + renameSync(temporary, outputDirectory); + return report; + } catch (error) { + rmSync(temporary, { recursive: true, force: true }); + throw error; + } +} diff --git a/src/policy.ts b/src/policy.ts new file mode 100644 index 0000000..2a9d3ae --- /dev/null +++ b/src/policy.ts @@ -0,0 +1,84 @@ +import type { Capability, Priority, RiskLevel, GenerationPolicy } from './generated/contracts.js'; +import { ProtocolException, protocolError } from './errors.js'; + +export interface RiskFactors { + productionDataAccess?: boolean; + externalSecretTransmission?: boolean; + legalConcern?: boolean; + rollbackImpossible?: boolean; +} + +export type Clock = () => Date | string; +export interface PolicyAssessment { + riskLevel: RiskLevel; + requiresApproval: boolean; + requiredApprovals: Exclude[]; + approvalDeadline?: string; + autoApproved: boolean; +} + +const CAPABILITIES: Capability[] = ['read_repo', 'write_repo', 'install_deps', 'network_access', 'read_secrets', 'publish_release']; +const REQUIRED: Record = { + low: [], medium: [], high: ['project_lead', 'security_reviewer'], + critical: ['project_lead', 'security_reviewer', 'release_manager'], +}; + +function assertCapabilities(capabilities: readonly string[]): asserts capabilities is readonly Capability[] { + const unknown = capabilities.filter((capability) => !CAPABILITIES.includes(capability as Capability)); + if (unknown.length > 0 || new Set(capabilities).size !== capabilities.length) { + throw new ProtocolException('Unknown or duplicate capability', [protocolError('UNKNOWN_CAPABILITY', '/capabilities', 'Capabilities must be known and unique', 'semantic')]); + } +} + +function nowUtc(clock: Clock | undefined): string { + const value = clock ? clock() : new Date(); + const date = value instanceof Date ? value : new Date(value); + if (!Number.isFinite(date.getTime())) throw new Error('Invalid policy clock value'); + return date.toISOString(); +} + +export function deriveGenerationPolicy(capabilities: readonly string[]): GenerationPolicy { + assertCapabilities(capabilities); + const safe = capabilities.length === 1 && capabilities[0] === 'read_repo' + || capabilities.length === 2 && capabilities.includes('read_repo') && capabilities.includes('write_repo'); + if (safe) return { auto_activate: true, requiredActivationApprovals: [] }; + const approvals = new Set(); + if (capabilities.some((capability) => ['install_deps', 'network_access', 'read_secrets'].includes(capability))) { + approvals.add('project_lead'); + approvals.add('security_reviewer'); + } + if (capabilities.includes('publish_release')) { + approvals.add('project_lead'); + approvals.add('release_manager'); + } + return { auto_activate: false, requiredActivationApprovals: [...approvals] }; +} + +export function deriveRiskLevel(capabilities: readonly string[], factors: RiskFactors = {}): RiskLevel { + assertCapabilities(capabilities); + if (factors.productionDataAccess || factors.externalSecretTransmission || factors.legalConcern || factors.rollbackImpossible) return 'critical'; + if (capabilities.some((capability) => ['install_deps', 'network_access', 'read_secrets', 'publish_release'].includes(capability))) return 'high'; + if (capabilities.includes('write_repo')) return 'medium'; + return 'low'; +} + +export function assessPolicy( + capabilities: readonly string[], + factors: RiskFactors = {}, + options: { clock?: Clock } = {}, +): PolicyAssessment { + const riskLevel = deriveRiskLevel(capabilities, factors); + const requiredApprovals = REQUIRED[riskLevel]; + const autoApproved = requiredApprovals.length === 0; + const result: PolicyAssessment = { + riskLevel, + requiresApproval: !autoApproved, + requiredApprovals: [...requiredApprovals], + autoApproved, + }; + if (!autoApproved) { + const hours = riskLevel === 'critical' ? 48 : 24; + result.approvalDeadline = new Date(new Date(nowUtc(options.clock)).getTime() + hours * 60 * 60 * 1000).toISOString(); + } + return result; +} diff --git a/src/schema-registry.ts b/src/schema-registry.ts new file mode 100644 index 0000000..4e9d9e4 --- /dev/null +++ b/src/schema-registry.ts @@ -0,0 +1,39 @@ +import Ajv2020Module from 'ajv/dist/2020.js'; +import addFormatsModule from 'ajv-formats'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import type { ContractKind } from './generated/contracts.js'; + +const SCHEMA_DIR = new URL('../schemas/v2/', import.meta.url); +const CONTRACT_KINDS: ContractKind[] = ['IntentContract', 'TaskSeed', 'Acceptance', 'PublishGate', 'Evidence']; + +type Validator = { (data: unknown): boolean; errors?: unknown[] | null }; +type AjvLike = { addSchema(schema: unknown): void; getSchema(id: string): Validator | undefined }; +type AjvConstructor = new (options: Record) => AjvLike; + +function readSchema(name: string): Record { + return JSON.parse(readFileSync(fileURLToPath(new URL(name, SCHEMA_DIR)), 'utf8')) as Record; +} + +let registry: { ajv: AjvLike; contracts: Map; event: Validator } | undefined; + +export function getSchemaRegistry() { + if (registry) return registry; + const Ajv2020 = Ajv2020Module as unknown as AjvConstructor; + const addFormats = addFormatsModule as unknown as (ajv: AjvLike) => void; + const ajv = new Ajv2020({ strict: true, allErrors: true, validateFormats: true }); + addFormats(ajv); + ajv.addSchema(readSchema('common.schema.json')); + for (const kind of CONTRACT_KINDS) ajv.addSchema(readSchema(kind + '.schema.json')); + ajv.addSchema(readSchema('CloudEvent.schema.json')); + const contracts = new Map(); + for (const kind of CONTRACT_KINDS) { + const validator = ajv.getSchema('https://agent-protocols.rna4219.dev/schemas/v2/' + kind + '.schema.json'); + if (!validator) throw new Error('Missing validator for ' + kind); + contracts.set(kind, validator); + } + const event = ajv.getSchema('https://agent-protocols.rna4219.dev/schemas/v2/CloudEvent.schema.json'); + if (!event) throw new Error('Missing CloudEvent validator'); + registry = { ajv, contracts, event }; + return registry; +} diff --git a/src/validation-types.ts b/src/validation-types.ts new file mode 100644 index 0000000..abe37e5 --- /dev/null +++ b/src/validation-types.ts @@ -0,0 +1,22 @@ +import type { Contract } from './generated/contracts.js'; + +export type ErrorSource = 'schema' | 'semantic' | 'reference'; + +export interface ProtocolError { + code: string; + path: string; + message: string; + source: ErrorSource; +} + +export type SafeParseSuccess = { success: true; data: T; errors?: never }; +export type SafeParseFailure = { success: false; data?: never; errors: ProtocolError[] }; +export type SafeResult = SafeParseSuccess | SafeParseFailure; + +export interface ValidationSummary { + valid: boolean; + success: boolean; + errors: ProtocolError[]; +} + +export type ContractLike = Contract; diff --git a/src/validation-v2.ts b/src/validation-v2.ts new file mode 100644 index 0000000..452ddbf --- /dev/null +++ b/src/validation-v2.ts @@ -0,0 +1,143 @@ +import type { Contract, ContractKind, Evidence, PublishGate } from './generated/contracts.js'; +import { getSchemaRegistry } from './schema-registry.js'; +import { protocolError } from './errors.js'; +import type { ProtocolError, SafeResult } from './validation-types.js'; + +const KINDS: ContractKind[] = ['IntentContract', 'TaskSeed', 'Acceptance', 'PublishGate', 'Evidence']; + +function isKind(value: unknown): value is ContractKind { + return typeof value === 'string' && KINDS.includes(value as ContractKind); +} + +type AjvError = { keyword: string; params?: unknown; instancePath?: string; message?: string }; + +function ajvErrors(errors: unknown[] | null | undefined): ProtocolError[] { + return (errors ?? []).map((raw) => { + const error = raw as AjvError; + const suffix = error.keyword === 'required' && typeof error.params === 'object' && error.params && 'missingProperty' in error.params + ? '/' + String((error.params as { missingProperty: string }).missingProperty) + : ''; + return protocolError('SCHEMA_' + error.keyword.toUpperCase(), (error.instancePath || '') + suffix || '/', error.message ?? 'Schema validation failed', 'schema'); + }); +} + +function semanticError(code: string, path: string, message: string): ProtocolError { + return protocolError(code, path, message, 'semantic'); +} + + +export function validateContractSemantics(contract: Contract): ProtocolError[] { + const errors: ProtocolError[] = []; + if (contract.kind === 'PublishGate') { + const gate = contract as PublishGate; + const requiredByRisk: Record = { + low: [], medium: [], high: ['project_lead', 'security_reviewer'], + critical: ['project_lead', 'security_reviewer', 'release_manager'], + }; + if (JSON.stringify(gate.requiredApprovals) !== JSON.stringify(requiredByRisk[gate.riskLevel])) { + errors.push(semanticError('POLICY_APPROVAL_MISMATCH', '/requiredApprovals', 'requiredApprovals must exactly match risk policy')); + } + const roles = new Set(); + for (const approval of gate.approvals) { + if (roles.has(approval.role)) errors.push(semanticError('DUPLICATE_APPROVAL_ROLE', '/approvals', 'A role may be decided only once')); + roles.add(approval.role); + if (approval.role !== 'policy_engine' && !gate.requiredApprovals.includes(approval.role as never)) { + errors.push(semanticError('UNREQUESTED_APPROVAL_ROLE', '/approvals', 'Approval role was not requested')); + } + } + if (gate.riskLevel !== 'low' && gate.riskLevel !== 'medium' && !gate.approvalDeadline) { + errors.push(semanticError('APPROVAL_DEADLINE_REQUIRED', '/approvalDeadline', 'High and critical gates require an approval deadline')); + } + if ((gate.riskLevel === 'low' || gate.riskLevel === 'medium') && gate.decision === 'pending') { + errors.push(semanticError('AUTO_APPROVAL_REQUIRED', '/decision', 'Low and medium gates must be immediately approved')); + } + if ((gate.riskLevel === 'low' || gate.riskLevel === 'medium') && gate.decision === 'approved' && + !gate.approvals.some((approval) => approval.role === 'policy_engine' && approval.decision === 'approved')) { + errors.push(semanticError('POLICY_ENGINE_APPROVAL_REQUIRED', '/approvals', 'Auto-approved gates require a policy_engine approval')); + } + if (gate.decision === 'pending' && gate.lifecycle !== 'active') { + errors.push(semanticError('GATE_LIFECYCLE_MISMATCH', '/lifecycle', 'Pending gates must be active')); + } + if ((gate.decision === 'approved' || gate.decision === 'rejected') && gate.lifecycle !== 'final') { + errors.push(semanticError('GATE_LIFECYCLE_MISMATCH', '/lifecycle', 'Approved and rejected gates must be final')); + } + if (gate.decision === 'expired' && gate.lifecycle !== 'frozen') { + errors.push(semanticError('GATE_LIFECYCLE_MISMATCH', '/lifecycle', 'Expired gates must be frozen')); + } + const approvedRoles = new Set(gate.approvals.filter((approval) => approval.decision === 'approved').map((approval) => approval.role)); + const rejectedRoles = gate.approvals.filter((approval) => approval.decision === 'rejected'); + if (gate.decision === 'approved' && gate.requiredApprovals.some((role) => !approvedRoles.has(role))) { + errors.push(semanticError('MISSING_APPROVAL', '/approvals', 'Approved gates must contain every required approval')); + } + if (gate.decision === 'approved' && rejectedRoles.length > 0) { + errors.push(semanticError('CONFLICTING_APPROVAL', '/approvals', 'An approved gate cannot contain a rejection')); + } + if (gate.decision === 'rejected' && rejectedRoles.length === 0) { + errors.push(semanticError('REJECTION_RECORD_REQUIRED', '/approvals', 'Rejected gates require a rejection record')); + } + if (gate.decision === 'pending' && gate.approvals.some((approval) => approval.decision === 'rejected')) { + errors.push(semanticError('GATE_DECISION_MISMATCH', '/decision', 'A rejected approval cannot remain pending')); + } + } + if (contract.kind === 'Evidence') { + const evidence = contract as Evidence; + if (new Date(evidence.startTime).getTime() > new Date(evidence.endTime).getTime()) { + errors.push(semanticError('EVIDENCE_TIME_ORDER', '/startTime', 'startTime must not be after endTime')); + } + if (evidence.createdAt !== evidence.updatedAt) { + errors.push(semanticError('EVIDENCE_IMMUTABLE_TIMESTAMP', '/updatedAt', 'Evidence createdAt and updatedAt must be identical')); + } + if (evidence.policyVerdict === 'manual_review_required' && !evidence.approvalsSnapshot) { + errors.push(semanticError('EVIDENCE_APPROVAL_SNAPSHOT_REQUIRED', '/approvalsSnapshot', 'Manual review Evidence requires approvalsSnapshot')); + } + if (evidence.stage === 'publish' && (!evidence.acceptanceId || !evidence.publishGateId)) { + errors.push(semanticError('EVIDENCE_PUBLISH_REFERENCES_REQUIRED', '/', 'Publish Evidence requires acceptanceId and publishGateId')); + } + } + return errors; +} + +export function safeParseContract(input: unknown): SafeResult { + if (!input || typeof input !== 'object' || Array.isArray(input)) { + return { success: false, errors: [protocolError('SCHEMA_TYPE', '/', 'Contract must be an object', 'schema')] }; + } + const kind = (input as { kind?: unknown }).kind; + if (!isKind(kind)) { + return { success: false, errors: [protocolError('UNKNOWN_KIND', '/kind', 'Unknown contract kind; validation is fail-closed', 'schema')] }; + } + const validator = getSchemaRegistry().contracts.get(kind)!; + if (!validator(input)) return { success: false, errors: ajvErrors(validator.errors) }; + const contract = input as Contract; + const errors = validateContractSemantics(contract); + return errors.length > 0 ? { success: false, errors } : { success: true, data: contract }; +} + +export function parseContract(input: unknown): Contract { + const result = safeParseContract(input); + if (!result.success) throw new Error(result.errors.map((error) => error.code + ' ' + error.path + ': ' + error.message).join('; ')); + return result.data; +} + +export function safeParseEvent(input: unknown): SafeResult { + if (!input || typeof input !== 'object' || Array.isArray(input)) { + return { success: false, errors: [protocolError('SCHEMA_TYPE', '/', 'CloudEvent must be an object', 'schema')] }; + } + const registry = getSchemaRegistry(); + if (!registry.event(input)) return { success: false, errors: ajvErrors(registry.event.errors) }; + const event = input as import('./generated/contracts.js').CloudEvent; + const errors: ProtocolError[] = []; + const contractResult = safeParseContract(event.data); + if (!contractResult.success) { + errors.push(...contractResult.errors.map((error) => ({ ...error, path: '/data' + (error.path === '/' ? '' : error.path) }))); + } else { + if (event.subject !== contractResult.data.id) errors.push(protocolError('EVENT_SUBJECT_MISMATCH', '/subject', 'subject must equal data.id', 'semantic')); + if (event.contractrevision !== contractResult.data.revision) errors.push(protocolError('EVENT_REVISION_MISMATCH', '/contractrevision', 'contractrevision must equal data.revision', 'semantic')); + } + return errors.length > 0 ? { success: false, errors } : { success: true, data: event }; +} + +export function parseEvent(input: unknown): import('./generated/contracts.js').CloudEvent { + const result = safeParseEvent(input); + if (!result.success) throw new Error(result.errors.map((error) => error.code + ' ' + error.path + ': ' + error.message).join('; ')); + return result.data; +} diff --git a/tests/v2/protocol-v2.test.ts b/tests/v2/protocol-v2.test.ts new file mode 100644 index 0000000..573c53f --- /dev/null +++ b/tests/v2/protocol-v2.test.ts @@ -0,0 +1,141 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { + applyApproval, + createContractEvent, + createContractId, + createPublishGate, + expireGate, + ProtocolException, + safeParseContract, + safeParseEvent, + validateContractGraph, + validateTransition, +} from '../../src/index.js'; +import { migrateV1 } from '../../src/migration/index.js'; + +const now = '2026-07-12T00:00:00.000Z'; +const id = (kind: 'IntentContract' | 'TaskSeed' | 'Acceptance' | 'PublishGate' | 'Evidence') => + createContractId(kind, { now, random: new Uint8Array(10).fill(kind.length) }); + +function contracts() { + const intent = { + schemaVersion: '2.0.0' as const, id: id('IntentContract'), kind: 'IntentContract' as const, + lifecycle: 'active' as const, revision: 1, createdAt: now, updatedAt: now, + intent: 'v2 test', creator: 'tester', priority: 'low' as const, requestedCapabilities: ['read_repo' as const], + }; + const taskSeed = { + schemaVersion: '2.0.0' as const, id: id('TaskSeed'), kind: 'TaskSeed' as const, + lifecycle: 'active' as const, revision: 1, createdAt: now, updatedAt: now, intentId: intent.id, + description: 'run tests', ownerRole: 'developer' as const, executionPlan: ['test'], + requestedCapabilitiesSnapshot: ['read_repo' as const], + generationPolicy: { auto_activate: true, requiredActivationApprovals: [] as never[] }, + }; + const acceptance = { + schemaVersion: '2.0.0' as const, id: id('Acceptance'), kind: 'Acceptance' as const, + lifecycle: 'active' as const, revision: 1, createdAt: now, updatedAt: now, taskSeedId: taskSeed.id, + status: 'passed' as const, details: 'passed', criteria: ['tests'], + generationPolicy: { auto_activate: true, requiredActivationApprovals: [] as never[] }, + }; + return { intent, taskSeed, acceptance }; +} + +describe('agent-protocols v2', () => { + const directories: string[] = []; + afterEach(() => directories.splice(0).forEach((directory) => rmSync(directory, { recursive: true, force: true }))); + + it('rejects failed or blocked Acceptance when creating a gate', () => { + const { acceptance } = contracts(); + for (const status of ['failed', 'blocked', 'pending'] as const) { + expect(() => createPublishGate({ ...acceptance, status }, ['read_repo'], { clock: () => now })).toThrow(ProtocolException); + } + }); + + it('enforces exact high-risk approvals and revisioned decisions', () => { + const { acceptance } = contracts(); + const gate = createPublishGate(acceptance, ['read_repo', 'install_deps'], { clock: () => now }); + expect(gate.requiredApprovals).toEqual(['project_lead', 'security_reviewer']); + expect(gate.decision).toBe('pending'); + const first = applyApproval(gate, { role: 'project_lead', actorId: 'lead', decision: 'approved' }, { clock: () => '2026-07-12T01:00:00.000Z' }); + expect(first.revision).toBe(2); + expect(first.decision).toBe('pending'); + const final = applyApproval(first, { role: 'security_reviewer', actorId: 'security', decision: 'approved' }, { clock: () => '2026-07-12T02:00:00.000Z' }); + expect(final.lifecycle).toBe('final'); + expect(final.decision).toBe('approved'); + expect(final.revision).toBe(3); + expect(() => applyApproval(first, { role: 'project_lead', actorId: 'other', decision: 'approved' })).toThrow(ProtocolException); + expect(() => applyApproval(gate, { role: 'admin', actorId: 'admin', decision: 'approved' })).toThrow(ProtocolException); + }); + + it('expires pending gates as frozen and rejects late approval', () => { + const { acceptance } = contracts(); + const gate = createPublishGate(acceptance, ['read_repo', 'network_access'], { clock: () => now }); + const expired = expireGate(gate, { clock: () => '2026-07-14T01:00:00.000Z' }); + expect(expired.lifecycle).toBe('frozen'); + expect(expired.decision).toBe('expired'); + expect(expired.revision).toBe(2); + expect(() => applyApproval(gate, { role: 'project_lead', actorId: 'lead', decision: 'approved' }, { clock: () => '2026-07-14T01:00:00.000Z' })).toThrow(ProtocolException); + }); + + it('fails closed for unknown capability and invalid typed id', () => { + const { intent } = contracts(); + const unknown = safeParseContract({ ...intent, requestedCapabilities: ['unknown'] }); + expect(unknown.success).toBe(false); + expect(unknown.success ? undefined : unknown.errors[0].source).toBe('schema'); + const badId = safeParseContract({ ...intent, id: 'TaskSeed_01ARZ3NDEKTSV4RRFFQ69G5FAV' }); + expect(badId.success).toBe(false); + }); + + it('validates immutable Evidence and publish references', () => { + const { intent, taskSeed, acceptance } = contracts(); + const gate = createPublishGate(acceptance, ['read_repo'], { clock: () => now }); + const evidence = { + schemaVersion: '2.0.0' as const, id: id('Evidence'), kind: 'Evidence' as const, lifecycle: 'final' as const, + revision: 1 as const, createdAt: now, updatedAt: now, stage: 'publish' as const, taskSeedId: taskSeed.id, + acceptanceId: acceptance.id, publishGateId: gate.id, + baseCommit: { algorithm: 'git', value: 'base' }, headCommit: { algorithm: 'git', value: 'head' }, + inputHash: { algorithm: 'sha256', value: 'input' }, outputHash: { algorithm: 'sha256', value: 'output' }, + model: { name: 'model', version: '1', parametersHash: { algorithm: 'sha256', value: 'params' } }, + tools: [{ name: 'vitest', version: '3' }], + environment: { os: 'windows', runtime: 'node20', containerImageDigest: { algorithm: 'sha256', value: 'image' }, lockfileHash: { algorithm: 'sha256', value: 'lock' } }, + staleStatus: { classification: 'fresh' as const, evaluatedAt: now }, + mergeResult: { status: 'not_applicable' as const }, startTime: now, endTime: now, actor: 'tester', + approvalsSnapshot: gate.approvals, policyVerdict: 'approved' as const, diffHash: { algorithm: 'sha256', value: 'diff' }, + }; + expect(safeParseContract(evidence).success).toBe(true); + expect(safeParseContract({ ...evidence, updatedAt: '2026-07-12T00:00:01.000Z' }).success).toBe(false); + expect(validateContractGraph([intent, taskSeed, acceptance, gate, evidence]).valid).toBe(true); + const changed = { ...evidence, revision: 2 as const }; + expect(validateTransition(evidence, changed).valid).toBe(false); + }); + + it('creates and parses CloudEvents with required correlation fields', () => { + const { acceptance } = contracts(); + const gate = createPublishGate(acceptance, ['read_repo'], { clock: () => now }); + const event = createContractEvent(gate, { clock: () => now, correlationid: 'corr', causationid: 'cause', idempotencykey: 'idem' }); + expect(event.specversion).toBe('1.0'); + expect(safeParseEvent(event).success).toBe(true); + expect(safeParseEvent({ ...event, correlationid: undefined }).success).toBe(false); + }); + + it('migrates v1 JSONL deterministically without overwriting output', () => { + const directory = mkdtempSync(join(tmpdir(), 'agent-protocols-v1-')); + directories.push(directory); + const input = join(directory, 'input.jsonl'); + const outputA = join(directory, 'out-a'); + const outputB = join(directory, 'out-b'); + const records = [ + { schemaVersion: '1.0.0', id: 'IC-001', kind: 'IntentContract', state: 'Active', version: 1, createdAt: now, updatedAt: now, intent: 'migrate', creator: 'tester', priority: 'low', requestedCapabilities: ['read_repo'] }, + { schemaVersion: '1.0.0', id: 'TS-001', kind: 'TaskSeed', state: 'Active', version: 1, createdAt: now, updatedAt: now, intentId: 'IC-001', description: 'test', ownerRole: 'developer', executionPlan: ['test'], requestedCapabilitiesSnapshot: ['read_repo'], generationPolicy: { auto_activate: true, requiredActivationApprovals: [] } }, + { schemaVersion: '1.0.0', id: 'AC-001', kind: 'Acceptance', state: 'Active', version: 1, createdAt: now, updatedAt: now, taskSeedId: 'TS-001', status: 'passed', details: 'ok', criteria: ['ok'], generationPolicy: { auto_activate: true, requiredActivationApprovals: [] } }, + { schemaVersion: '1.0.0', id: 'PG-001', kind: 'PublishGate', state: 'Published', version: 1, createdAt: now, updatedAt: now, entityId: 'AC-001', action: 'publish', riskLevel: 'low', requiredApprovals: [], approvals: [{ role: 'policy_engine', actorId: 'policy-engine', decision: 'approved', decidedAt: now }], finalDecision: 'approved' }, + ]; + writeFileSync(input, records.map((record) => JSON.stringify(record)).join('\n') + '\n', 'utf8'); + migrateV1(input, 'test', outputA); + migrateV1(input, 'test', outputB); + expect(readFileSync(join(outputA, 'id-map.json'), 'utf8')).toBe(readFileSync(join(outputB, 'id-map.json'), 'utf8')); + expect(() => migrateV1(input, 'test', outputA)).toThrow(ProtocolException); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 972b9de..5342cd6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,8 +12,10 @@ "rootDir": "./src", "declaration": true, "declarationMap": true, - "sourceMap": true + "sourceMap": true, + "resolveJsonModule": false, + "noEmitOnError": true }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] + "exclude": ["node_modules", "dist", "src/validation/**/*"] } \ No newline at end of file From ca4f39887b556d8333503217cee6c148f6bb4533 Mon Sep 17 00:00:00 2001 From: RNA4219 Date: Sun, 12 Jul 2026 10:08:56 +0900 Subject: [PATCH 3/4] ci: use pnpm for shipyard conformance --- .github/workflows/ci.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34552fd..5d3f2e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,9 +54,17 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 20 + - uses: pnpm/action-setup@v4 + with: + version: 9.15.9 + run_install: false - name: Build beta package working-directory: agent-protocols run: npm ci && npm run build && npm pack --pack-destination ../shipyard-cp - name: Install and typecheck Shipyard working-directory: shipyard-cp - run: npm install --no-audit --no-fund ./rna4219-agent-protocols-2.0.0-beta.1.tgz && npm run check + run: | + node -e "const fs = require('node:fs'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); pkg.dependencies['@rna4219/agent-protocols'] = 'file:./rna4219-agent-protocols-2.0.0-beta.1.tgz'; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');" + pnpm install --no-frozen-lockfile + pnpm run build:packages + pnpm run check From 93cd9fc3ef6cd3ee7fec58152deaa6dad1d208c5 Mon Sep 17 00:00:00 2001 From: RNA4219 Date: Sun, 12 Jul 2026 10:19:28 +0900 Subject: [PATCH 4/4] ci: require Node.js 24 --- .github/workflows/ci.yml | 22 ++++++++++----------- README.md | 2 +- package-lock.json | 2 +- package.json | 2 +- tests/validation/semantic-validator.test.ts | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d3f2e7..155cff7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,10 +13,10 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [20, 22] + node: [24] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node }} cache: npm @@ -32,10 +32,10 @@ jobs: runs-on: ubuntu-latest needs: protocol steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 24 cache: npm - run: npm ci - run: npm test -- --run tests/v2/protocol-v2.test.ts @@ -44,17 +44,17 @@ jobs: runs-on: ubuntu-latest needs: protocol steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: path: agent-protocols - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: repository: RNA4219/shipyard-cp path: shipyard-cp - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: 20 - - uses: pnpm/action-setup@v4 + node-version: 24 + - uses: pnpm/action-setup@v6 with: version: 9.15.9 run_install: false diff --git a/README.md b/README.md index 9296744..d63cac9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # @rna4219/agent-protocols -AI workflow契約の唯一の正本です。v2は破壊的変更であり、Node.js 20以上、ESM、公開 npm scoped package +AI workflow契約の唯一の正本です。v2は破壊的変更であり、Node.js 24以上、ESM、公開 npm scoped package として配布します。 - package: `@rna4219/agent-protocols@2.0.0-beta.1` diff --git a/package-lock.json b/package-lock.json index f2f8c9e..861ada8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "vitest": "^3.2.4" }, "engines": { - "node": ">=20" + "node": ">=24" } }, "node_modules/@esbuild/aix-ppc64": { diff --git a/package.json b/package.json index 2e68255..6cb9b33 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "2.0.0-beta.1", "description": "Canonical v2 contract schemas, types, validation, policy and migration for AI workflows", "type": "module", - "engines": { "node": ">=20" }, + "engines": { "node": ">=24" }, "main": "./dist/index.js", "types": "./dist/index.d.ts", "bin": { "agent-protocols": "./dist/cli/index.js" }, diff --git a/tests/validation/semantic-validator.test.ts b/tests/validation/semantic-validator.test.ts index e923472..0d2f65a 100644 --- a/tests/validation/semantic-validator.test.ts +++ b/tests/validation/semantic-validator.test.ts @@ -41,7 +41,7 @@ describe('SemanticValidator', () => { tools: ['Read', 'Edit'], environment: { os: 'Linux', - runtime: 'Node.js 20', + runtime: 'Node.js 24', containerImageDigest: 'sha256:container', lockfileHash: 'sha256:lock', },