feat(openspec): propose phase-5-scale-ecosystem change

6 workstreams, 119 tasks — Scale & Ecosystem:
- WS1: Rust SDK
- WS2: Agent-to-Agent (A2A) Authorization
- WS3: Advanced Analytics Dashboard
- WS4: Public API Gateway & Rate Limiting SaaS
- WS5: Developer Experience (DX) improvements
- WS6: AGNTCY Compliance Certification Package

Awaiting CEO approval to begin implementation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SentryAgent.ai Developer
2026-04-02 15:33:08 +00:00
parent 831e91c467
commit 389a764e8d
10 changed files with 2230 additions and 0 deletions

View File

@@ -0,0 +1,175 @@
## 1. WS1: Rust SDK — Crate Setup
- [ ] 1.1 Create `sdk-rust/` directory and `Cargo.toml` — name: `sentryagent-idp`, version: `1.0.0`, edition: `2021`; add dependencies: `tokio` (features: full), `reqwest` (features: json, rustls-tls), `serde` (features: derive), `serde_json`, `uuid` (features: v4), `thiserror`, `async-trait`; add dev-dependencies: `tokio-test`, `mockito`
- [ ] 1.2 Create `sdk-rust/src/lib.rs` — crate root with `#![deny(warnings)]`; re-export `AgentIdPClient`, `TokenManager`, `AgentIdPError`, and all model types from submodules; add crate-level `//!` doc comment describing the SDK
- [ ] 1.3 Create `sdk-rust/src/error.rs` — define `AgentIdPError` enum with variants: `HttpError(reqwest::Error)`, `ApiError { status: u16, message: String, code: Option<String> }`, `AuthError(String)`, `NotFound(String)`, `RateLimited { retry_after_secs: u64 }`, `ConfigError(String)`, `SerdeError(serde_json::Error)`, `DelegationError(String)`; derive `thiserror::Error` and `Debug`; implement `std::error::Error`
- [ ] 1.4 Create `sdk-rust/src/models.rs` — define all request structs (`RegisterAgentRequest`, `UpdateAgentRequest`, `AuditLogFilters`, `MarketplaceFilters`, `DelegateRequest`) and all response structs (`Agent`, `AgentList`, `TokenResponse`, `Credentials`, `AuditLogEntry`, `AuditLogList`, `MarketplaceAgent`, `MarketplaceAgentList`, `DelegationToken`, `DelegationVerification`); all structs derive `serde::Serialize`, `serde::Deserialize`, `Debug`, `Clone`
## 2. WS1: Rust SDK — Token Manager
- [ ] 2.1 Create `sdk-rust/src/token_manager.rs` — define `TokenCache` struct with `access_token: Option<String>` and `expires_at: Option<std::time::Instant>`; define `TokenManager` struct with fields `api_url`, `client_id`, `client_secret`, `cache: Arc<Mutex<TokenCache>>`
- [ ] 2.2 Implement `TokenManager::new(api_url: &str, client_id: &str, client_secret: &str) -> Self` — initializes with empty cache
- [ ] 2.3 Implement `TokenManager::get_token(&self) -> Result<String, AgentIdPError>` — acquires lock, checks `expires_at` against `Instant::now() + 60s`, returns cached token if valid, else calls `POST /oauth2/token` via `reqwest`, updates cache, releases lock
- [ ] 2.4 Write unit test `token_manager_returns_cached_token` — mock `POST /oauth2/token` using `mockito`, call `get_token()` twice, verify mock is hit only once
- [ ] 2.5 Write unit test `token_manager_refreshes_expired_token` — set `expires_at` to past, verify `get_token()` triggers a new `POST /oauth2/token` call
- [ ] 2.6 Write concurrent safety test `token_manager_concurrent_calls_no_race` — spawn 50 `tokio::spawn` tasks all calling `get_token()` simultaneously, verify mock is hit at most once (no thundering herd), verify all 50 tasks receive valid tokens
## 3. WS1: Rust SDK — Client Methods
- [ ] 3.1 Create `sdk-rust/src/client.rs` — define `AgentIdPClient` struct with fields `base_url`, `client_id`, `client_secret`, `http: reqwest::Client`, `token_manager: Arc<Mutex<TokenManager>>`; implement `new(base_url, client_id, client_secret) -> Self` and `from_env() -> Result<Self, AgentIdPError>` (reads `AGENTIDP_API_URL`, `AGENTIDP_CLIENT_ID`, `AGENTIDP_CLIENT_SECRET`)
- [ ] 3.2 Create `sdk-rust/src/agents.rs` — implement all agent methods on `AgentIdPClient`: `register_agent`, `get_agent`, `list_agents`, `update_agent`, `delete_agent` — each acquires a bearer token via `token_manager.get_token()`, makes the correct HTTP call, deserializes response, maps non-2xx responses to `AgentIdPError::ApiError`
- [ ] 3.3 Create `sdk-rust/src/oauth2.rs` — implement `issue_token(&self, agent_id: &str, scopes: &[&str]) -> Result<TokenResponse, AgentIdPError>` — sends `POST /oauth2/token` with `grant_type=client_credentials`
- [ ] 3.4 Create `sdk-rust/src/credentials.rs` — implement `generate_credentials`, `rotate_credentials`, `revoke_credentials` — map 404 response to `AgentIdPError::NotFound`, map 401 to `AgentIdPError::AuthError`
- [ ] 3.5 Create `sdk-rust/src/audit.rs` — implement `list_audit_logs(filters: AuditLogFilters)` — serialize filters as query parameters; handle empty result set (return empty `Vec`, not error)
- [ ] 3.6 Create `sdk-rust/src/marketplace.rs` — implement `list_public_agents(filters)` and `get_public_agent(agent_id)` — no auth header required for these endpoints
- [ ] 3.7 Create `sdk-rust/src/delegation.rs` — implement `delegate(req: DelegateRequest)` and `verify_delegation(token: &str)`
- [ ] 3.8 Implement 429 handling across all client methods — parse `Retry-After` header, return `AgentIdPError::RateLimited { retry_after_secs }`; verify zero `unwrap()` calls in all `src/` files (run `grep -r 'unwrap()' sdk-rust/src/` — must return empty)
## 4. WS1: Rust SDK — Tests, Examples, Documentation
- [ ] 4.1 Create `sdk-rust/examples/quickstart.rs` — working example: create `AgentIdPClient::from_env()`, call `register_agent`, call `issue_token`, print token; example must compile with `cargo build --example quickstart`
- [ ] 4.2 Create `sdk-rust/tests/integration_test.rs` — integration tests requiring `AGENTIDP_API_URL`, `AGENTIDP_CLIENT_ID`, `AGENTIDP_CLIENT_SECRET` env vars; test: register agent, issue token, get agent, update agent, rotate credentials, delete agent; each test is `#[tokio::test]` with `#[ignore]` attribute (run explicitly with `cargo test -- --ignored`)
- [ ] 4.3 Write `sdk-rust/README.md` — installation via `Cargo.toml`, environment variable configuration, quickstart code example, full method reference table with signatures, error handling guide, link to crates.io
- [ ] 4.4 Run `cargo doc --no-deps` — verify docs generate without errors or warnings; verify all public items have `///` doc comments
- [ ] 4.5 Run `cargo clippy -- -D warnings` — zero warnings; run `cargo test` (unit tests only, no `--ignored`) — all pass
## 5. WS2: A2A Authorization — Database & Types
- [ ] 5.1 Create `src/infrastructure/migrations/008_add_delegation_chains.sql` — create `delegation_chains` table with columns: `id` (UUID PK), `tenant_id` (UUID FK), `delegator_agent_id` (UUID FK), `delegatee_agent_id` (UUID FK), `scopes` (TEXT[]), `delegation_token` (TEXT UNIQUE), `signature` (TEXT), `ttl_seconds` (INTEGER CHECK 6086400), `issued_at` (TIMESTAMPTZ), `expires_at` (TIMESTAMPTZ), `revoked_at` (TIMESTAMPTZ nullable), `created_at` (TIMESTAMPTZ DEFAULT NOW); create all four indexes as specified in spec
- [ ] 5.2 Create `src/types/delegation.ts` — define interfaces: `DelegationChain`, `CreateDelegationRequest` (delegateeAgentId, scopes, ttlSeconds), `DelegationVerificationResult` (valid, chainId, delegatorAgentId, delegateeAgentId, scopes, issuedAt, expiresAt, revokedAt), `DelegationTokenPayload`
## 6. WS2: A2A Authorization — Crypto & Service
- [ ] 6.1 Create `src/utils/delegationCrypto.ts` — implement `signDelegationPayload(payload: DelegationTokenPayload, secret: string): string` using HMAC-SHA256 (Node.js `crypto.createHmac('sha256', secret)`); implement `verifyDelegationSignature(payload: DelegationTokenPayload, signature: string, secret: string): boolean`; implement `generateDelegationToken(): string` (UUID v4); export only these three functions — no other exports
- [ ] 6.2 Create `src/services/DelegationService.ts` — implement `IDelegationService` interface; `createDelegation`: validate delegateeAgentId exists in same tenant, validate scopes ⊆ delegator's scopes, reject self-delegation, sign payload, insert `delegation_chains` row, write audit log entry (`delegation.created`), return `DelegationChain`
- [ ] 6.3 Implement `DelegationService.verifyDelegation(delegationToken)` — fetch chain row by `delegation_token`, if not found throw `NotFoundError`, verify HMAC signature, check `expires_at > NOW()` and `revoked_at IS NULL`, return `DelegationVerificationResult` with `valid: true/false` (never throw on expired/revoked — return `valid: false`); write audit log entry (`delegation.verified`)
- [ ] 6.4 Implement `DelegationService.revokeDelegation(chainId, requestingAgentId)` — fetch chain by ID, verify `delegator_agent_id === requestingAgentId` (else throw `ForbiddenError`), check not already revoked (else throw `ConflictError`), update `revoked_at = NOW()`, write audit log entry (`delegation.revoked`)
## 7. WS2: A2A Authorization — Controller, Routes, Tests
- [ ] 7.1 Create `src/controllers/DelegationController.ts` — implement `createDelegation` handler (POST /oauth2/token/delegate): extract authenticated agent ID from request context, call `DelegationService.createDelegation`, return HTTP 201; implement `verifyDelegation` handler (POST /oauth2/token/verify-delegation): call `DelegationService.verifyDelegation`, return HTTP 200; implement `revokeDelegation` handler (DELETE /oauth2/token/delegate/:chainId): call `DelegationService.revokeDelegation`, return HTTP 204
- [ ] 7.2 Create `src/routes/delegation.ts` — Express router registering `POST /oauth2/token/delegate`, `POST /oauth2/token/verify-delegation`, `DELETE /oauth2/token/delegate/:chainId` with authentication middleware on all three routes
- [ ] 7.3 Register delegation router in `src/routes/index.ts` behind `A2A_ENABLED` feature flag — return HTTP 404 on all delegation routes when `A2A_ENABLED=false`
- [ ] 7.4 Add delegation Prometheus metrics: `agentidp_delegations_created_total`, `agentidp_delegations_verified_total` (labels: result), `agentidp_delegations_revoked_total` — increment in `DelegationController` handlers
- [ ] 7.5 Add delegation endpoints to `docs/openapi.yaml` — include all request/response schemas, error responses, and authentication requirements as defined in spec
- [ ] 7.6 Write unit tests for `delegationCrypto.ts` — test sign/verify round-trip, test tampered payload fails verification, test different secrets produce different signatures
- [ ] 7.7 Write unit tests for `DelegationService` — mock DB and audit service; test: create delegation (valid), create delegation (scope escalation rejected), create delegation (self-delegation rejected), create delegation (delegatee in different tenant rejected), verify delegation (valid), verify delegation (expired — returns valid: false not throw), verify delegation (revoked — returns valid: false), revoke delegation (by delegator — succeeds), revoke delegation (by non-delegator — throws ForbiddenError), revoke delegation (already revoked — throws ConflictError)
- [ ] 7.8 Write integration tests for delegation endpoints — test all happy paths and all error cases defined in spec; verify audit log entries are created for each delegation operation
## 8. WS3: Analytics — Database, Aggregation Job
- [ ] 8.1 Create `src/infrastructure/migrations/009_add_analytics_aggregates.sql` — create `analytics_daily_aggregates` table with columns: `id` (UUID PK), `tenant_id` (UUID FK), `agent_id` (UUID nullable FK), `date` (DATE), `metric_type` (VARCHAR 64), `count` (BIGINT), `created_at`, `updated_at`; add unique constraint on `(tenant_id, agent_id, date, metric_type)`; create indexes on `(tenant_id, date)` and `(agent_id, date) WHERE agent_id IS NOT NULL`
- [ ] 8.2 Install `node-cron` npm package — add to `package.json`
- [ ] 8.3 Create `src/jobs/analyticsAggregation.ts` — implement `runAnalyticsAggregation(targetDate: Date): Promise<void>`: execute upsert query aggregating previous day's `usage_events` rows into `analytics_daily_aggregates`; query is idempotent (upsert on unique constraint); update `agentidp_analytics_aggregation_job_duration_ms` gauge and `agentidp_analytics_aggregation_job_last_run` gauge on completion
- [ ] 8.4 Register cron job in `src/app.ts` — schedule `runAnalyticsAggregation` at `00:05 UTC` daily using `node-cron`; log job start, completion, and any errors; do not crash the process on job failure — log error and continue
## 9. WS3: Analytics — Service, Controller, Routes
- [ ] 9.1 Create `src/types/analytics.ts` — define interfaces: `UsageSummary`, `AgentActivityResponse`, `TokenTrendsResponse`, `DailyAggregate`, `AnalyticsDateRange`
- [ ] 9.2 Create `src/services/AnalyticsService.ts` — implement `IAnalyticsService`; `getUsageSummary`: validate date range (from <= to, max 365 days), check Redis cache (`analytics:{tenantId}:summary:{hash}`, TTL 60s), on miss query `analytics_daily_aggregates`, compute totals, write to cache, return `UsageSummary`
- [ ] 9.3 Implement `AnalyticsService.getAgentActivity(tenantId, from, to, agentId?)` — validate date range (max 90 days), check Redis cache (TTL 5 min), on miss query `analytics_daily_aggregates` grouped by `agent_id` and `date`, join agent names from `agents` table, write to cache, return `AgentActivityResponse`
- [ ] 9.4 Implement `AnalyticsService.getTokenTrends(tenantId, from, to, granularity)` — support `day` and `week` granularity (weekly: `date_trunc('week', date)`), check Redis cache (TTL 5 min), return `TokenTrendsResponse` with `successfulIssuances`, `failedIssuances`, `uniqueAgents` per period
- [ ] 9.5 Create `src/controllers/AnalyticsController.ts` — handlers for `getUsageSummary`, `getAgentActivity`, `getTokenTrends`; parse and validate query parameters; return HTTP 403 for free-tier tenants (check `ANALYTICS_FREE_TIER` env and tenant subscription); emit `agentidp_analytics_query_duration_ms` histogram and cache hit/miss counters
- [ ] 9.6 Create `src/routes/analytics.ts` — Express router for `/analytics/usage-summary`, `/analytics/agent-activity`, `/analytics/token-trends`; all routes require authentication middleware
- [ ] 9.7 Register analytics router in `src/routes/index.ts` behind `ANALYTICS_ENABLED` feature flag
- [ ] 9.8 Add analytics endpoints to `docs/openapi.yaml` — all query parameters, response schemas, and error codes as defined in spec
- [ ] 9.9 Write unit tests for `AnalyticsService` — test: usage-summary (cache hit), usage-summary (cache miss → DB query), agent-activity (with agentId filter), agent-activity (no filter — all agents), token-trends (daily), token-trends (weekly), date range validation (from > to rejected), date range validation (> max days rejected), free-tier rejection
- [ ] 9.10 Write integration tests for analytics endpoints — test all three endpoints with valid date ranges, verify free-tier rejection, verify invalid date range errors
## 10. WS3: Analytics — Dashboard UI
- [ ] 10.1 Install `recharts` and `date-fns` in `dashboard/package.json`
- [ ] 10.2 Create `dashboard/src/api/analyticsApi.ts` — typed fetch functions for all three analytics endpoints: `fetchUsageSummary(token, from, to)`, `fetchAgentActivity(token, from, to, agentId?)`, `fetchTokenTrends(token, from, to, granularity)`; all functions return typed response objects; handle 403 response with a typed `AnalyticsNotAvailableError`
- [ ] 10.3 Create `dashboard/src/components/charts/AgentHeatmap.tsx` — renders a grid heatmap (agents × dates) using `recharts` or a custom CSS grid; color intensity represents `apiCalls` count; hover tooltip shows agent name, date, apiCalls, tokenIssuances, credentialRotations; accepts `agents` prop from `AgentActivityResponse`
- [ ] 10.4 Create `dashboard/src/components/charts/TokenTrendsChart.tsx` — renders a `recharts` `ComposedChart` with a `Line` for `successfulIssuances` and a `Bar` for `failedIssuances`; X-axis is dates; tooltip shows all three metrics per period; accepts `dataPoints` prop from `TokenTrendsResponse`
- [ ] 10.5 Create `dashboard/src/components/charts/RotationFrequencyTable.tsx` — renders a sortable table of credential rotation counts per agent; columns: Agent Name, Rotations (period), Last Rotation Date; sortable by any column; accepts `agents` prop derived from `AgentActivityResponse` filtering `credentialRotations`
- [ ] 10.6 Create `dashboard/src/pages/Analytics.tsx` — analytics tab page; renders date range picker (from/to), calls all three analytics APIs, renders `AgentHeatmap`, `TokenTrendsChart`, `RotationFrequencyTable`; shows a `UpgradeRequired` component when API returns 403
- [ ] 10.7 Add Analytics route to `dashboard/src/App.tsx` — add `/analytics` route; add "Analytics" link to dashboard navigation
- [ ] 10.8 Run `npm run build` in `dashboard/` — zero TypeScript errors, zero ESLint errors
## 11. WS4: API Gateway Tiers — Configuration & Middleware
- [ ] 11.1 Create `src/types/tiers.ts` — define interfaces: `TierName` (union: `'free' | 'pro' | 'enterprise'`), `TierLimits`, `TierFeatures`, `TierDefinition` (includes `id`, `limits`, `features`, `stripeProductId`)
- [ ] 11.2 Create `src/config/tiers.ts` — define `TIER_CONFIG: Record<TierName, TierDefinition>` with complete limit and feature definitions for `free`, `pro`, and `enterprise` tiers as specified in spec; export `getTierConfig(tier: TierName): TierDefinition` helper
- [ ] 11.3 Create `src/middleware/tierRateLimiter.ts` — implement `TierRateLimiter` middleware: extract `tenantId` from authenticated request context; check Redis key `tier:{tenantId}` (TTL 60s) for cached tier; on miss query `tenant_subscriptions` for tenant's current tier, cache for 60s; look up rate limit config from `TIER_CONFIG`; apply `RateLimiterRedis` with key `rl:{tier}:{tenantId}`; on rejection return HTTP 429 with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `Retry-After` headers; increment `agentidp_rate_limit_hits_total` counter with `tier` and `tenant_id` labels
- [ ] 11.4 Replace `RateLimiterRedis` middleware on all authenticated routes in `src/routes/index.ts` with `TierRateLimiter`; keep the flat IP-based `RateLimiterRedis` on unauthenticated routes unchanged; wrap replacement in `TIER_RATE_LIMITING_ENABLED` feature flag (fall back to old middleware when `false`)
## 12. WS4: API Gateway Tiers — Endpoints
- [ ] 12.1 Create `src/routes/tiers.ts` — Express router for `GET /tiers`; handler reads `TIER_CONFIG`, formats response as specified in spec, sets `Cache-Control: public, max-age=3600` header; no database query; no authentication required
- [ ] 12.2 Register `tiers` router in `src/routes/index.ts`
- [ ] 12.3 Implement `BillingService.upgradeTier(tenantId: string, targetTier: 'pro' | 'enterprise'): Promise<{ checkoutUrl: string; sessionId: string; expiresAt: string }>` — fetch current tier from `tenant_subscriptions`, validate no self-upgrade or downgrade, create Stripe Checkout session with `STRIPE_PRICE_ID_PRO` or `STRIPE_PRICE_ID_ENTERPRISE`, return checkout URL
- [ ] 12.4 Add `upgradeTier` handler to `src/controllers/BillingController.ts` — validate `targetTier` enum, call `BillingService.upgradeTier`, return HTTP 200 with `checkoutUrl`, `sessionId`, `targetTier`, `expiresAt`
- [ ] 12.5 Register `POST /billing/upgrade` route in `src/routes/billing.ts` with authentication middleware
- [ ] 12.6 Add `STRIPE_PRICE_ID_PRO`, `STRIPE_PRICE_ID_ENTERPRISE`, `TIER_RATE_LIMITING_ENABLED` to `.env.example` with documentation comments
- [ ] 12.7 Add `GET /tiers` and `POST /billing/upgrade` to `docs/openapi.yaml`
- [ ] 12.8 Write unit tests for `TierRateLimiter` — test: free tier limit enforced (60 req/min), pro tier limit enforced (600 req/min), tier looked up from Redis cache (DB not called), tier fetched from DB on cache miss, rollback path (`TIER_RATE_LIMITING_ENABLED=false` uses old flat limiter)
- [ ] 12.9 Write unit tests for `BillingService.upgradeTier` — test: upgrade free → pro (creates Stripe session), upgrade free → enterprise (creates Stripe session), already on pro (returns ALREADY_ON_TIER error), downgrade attempt (returns DOWNGRADE_NOT_SUPPORTED error)
- [ ] 12.10 Write integration tests for `GET /tiers` — verify response structure, verify `Cache-Control` header, verify no auth required; write integration tests for `POST /billing/upgrade` — mock Stripe, verify checkout URL returned
## 13. WS5: Developer Experience — Scaffold Service
- [ ] 13.1 Install `archiver` and `@types/archiver` in API `package.json`
- [ ] 13.2 Create `src/types/scaffold.ts` — define `ScaffoldLanguage` union (`'typescript' | 'python' | 'go' | 'java' | 'rust'`), `ScaffoldOptions` interface, `ScaffoldTemplate` interface
- [ ] 13.3 Create scaffold template files for TypeScript in `src/templates/scaffold/typescript/`: `package.json.tmpl`, `tsconfig.json.tmpl`, `src/index.ts.tmpl`, `.env.example.tmpl`, `.gitignore.tmpl`, `README.md.tmpl` — each file uses `{{AGENT_ID}}`, `{{AGENT_NAME}}`, `{{CLIENT_ID}}`, `{{API_URL}}` as template variables; `.env.example.tmpl` MUST include `AGENTIDP_CLIENT_SECRET=<your-client-secret>` placeholder (never inject real secret)
- [ ] 13.4 Create scaffold template files for Python in `src/templates/scaffold/python/`: `requirements.txt.tmpl`, `main.py.tmpl`, `.env.example.tmpl`, `.gitignore.tmpl`, `README.md.tmpl` — same template variable convention
- [ ] 13.5 Create scaffold template files for Go in `src/templates/scaffold/go/`: `go.mod.tmpl`, `main.go.tmpl`, `.env.example.tmpl`, `.gitignore.tmpl`, `README.md.tmpl`
- [ ] 13.6 Create scaffold template files for Java in `src/templates/scaffold/java/`: `pom.xml.tmpl`, `src/main/java/Main.java.tmpl`, `.env.example.tmpl`, `.gitignore.tmpl`, `README.md.tmpl`
- [ ] 13.7 Create scaffold template files for Rust in `src/templates/scaffold/rust/`: `Cargo.toml.tmpl`, `src/main.rs.tmpl`, `.env.example.tmpl`, `.gitignore.tmpl`, `README.md.tmpl`
- [ ] 13.8 Create `src/services/ScaffoldService.ts` — implement `IScaffoldService`; `generateScaffold(agentId, language, apiUrl)`: load template files for language, inject template variables (replace `{{AGENT_ID}}`, `{{AGENT_NAME}}`, `{{CLIENT_ID}}`, `{{API_URL}}`), build in-memory ZIP using `archiver`; return `{ stream: NodeJS.ReadableStream, filename: string }`; emit `agentidp_scaffold_generated_total` counter and `agentidp_scaffold_generation_duration_ms` histogram
## 14. WS5: Developer Experience — Scaffold Controller & Route
- [ ] 14.1 Create `src/controllers/ScaffoldController.ts` — implement `getScaffold` handler for `GET /sdk/scaffold/:agentId`: validate `language` query param against `ScaffoldLanguage` union (HTTP 400 on invalid); fetch agent, verify agent belongs to authenticated tenant (HTTP 403 if not); call `ScaffoldService.generateScaffold`; set `Content-Type: application/zip`, `Content-Disposition: attachment; filename="..."`, pipe stream to response; write audit log entry (`scaffold.generated`, metadata: `{ language }`)
- [ ] 14.2 Create `src/routes/scaffold.ts` — Express router for `GET /sdk/scaffold/:agentId` with authentication middleware; apply scaffold-specific rate limiter (10 req/min per tenant, separate from `TierRateLimiter`)
- [ ] 14.3 Register `scaffold` router in `src/routes/index.ts`
- [ ] 14.4 Add `GET /sdk/scaffold/:agentId` to `docs/openapi.yaml` — document binary response type, query parameters, all error responses
- [ ] 14.5 Write unit tests for `ScaffoldService` — test: generate TypeScript scaffold (verify ZIP contains all 6 files), generate Python scaffold (verify all 5 files), verify `{{CLIENT_ID}}` is replaced in `.env.example`, verify `{{AGENTIDP_CLIENT_SECRET}}` is placeholder not real secret, verify invalid language throws `ValidationError`
- [ ] 14.6 Write integration tests for scaffold endpoint — test: TypeScript scaffold returns ZIP with correct `Content-Type` and `Content-Disposition`; Python scaffold returns ZIP; HTTP 400 on invalid language; HTTP 403 when agent belongs to different tenant; HTTP 404 when agent does not exist
## 15. WS5: Developer Experience — Portal & CLI
- [ ] 15.1 Install `@stoplight/elements` in `portal/package.json` — remove `swagger-ui-react`
- [ ] 15.2 Rewrite `portal/app/api-explorer/page.tsx` — replace `SwaggerUI` component with `@stoplight/elements` `<API>` component; set `apiDescriptionUrl`, `router="hash"`, `layout="sidebar"`, `hideSchemas={false}`, `tryItCredentialsPolicy="same-origin"`; import Elements CSS; remove all Swagger UI imports and CSS
- [ ] 15.3 Run `npm run build` in `portal/` — verify zero TypeScript errors and zero ESLint errors after Elements integration
- [ ] 15.4 Install `unzipper` and `@types/unzipper` in `cli/package.json`
- [ ] 15.5 Create `cli/src/commands/scaffold.ts` — implement `sentryagent scaffold` command with Commander options: `--agent-id <id>` (required), `--language <lang>` (default: typescript), `--out <directory>` (default: `.`); load config, issue Bearer token, call `GET /sdk/scaffold/{agentId}?language={language}`, pipe response through `unzipper.Extract({ path: outDir })`, print success message and next steps; handle errors (404, 403, 400) with human-readable messages
- [ ] 15.6 Register `scaffold` command in `cli/src/index.ts` — add `.addCommand(scaffoldCommand)` to Commander program
- [ ] 15.7 Run `npm run build` in `cli/` — zero TypeScript errors; run `node dist/index.js scaffold --help` — outputs correct usage
## 16. WS6: AGNTCY Compliance — Compliance Service
- [ ] 16.1 Create `src/types/compliance.ts` — define interfaces: `ComplianceRequirement` (id, description, status, evidence, verifiedAt), `ComplianceSection` (id, name, status, requirements), `ComplianceReport` (reportId, generatedAt, agntcySpecVersion, tenantId, overallStatus, sections, summary), `AgentCard` (agntcyVersion, type, agent, issuedAt, expiresAt)
- [ ] 16.2 Create `src/config/agntcyRequirements.ts` — define the complete array of AGNTCY requirement objects (minimum 24 requirements), each with: `id` (e.g., `AI-001`), `description` (from AGNTCY spec), `section` (e.g., `agent-identity`), and `evaluate(tenantId: string, db: Pool): Promise<RequirementEvaluation>` function — each evaluator queries the live system and returns `{ status, evidence }`
- [ ] 16.3 Create `src/services/ComplianceService.ts` — implement `IComplianceService`; `generateComplianceReport(tenantId)`: run all requirement evaluators from `agntcyRequirements.ts` in parallel, group results by section, compute overall status (`compliant` if all pass, `partial` if any non-compliant, `non-compliant` if >20% fail), build `ComplianceReport`, write audit log entry (`compliance.report_generated`), emit `agentidp_compliance_reports_generated_total` counter and `agentidp_compliance_report_duration_ms` histogram
- [ ] 16.4 Implement `ComplianceService.generateAgentCard(agentId)` — fetch agent from DB, build `AgentCard` per AGNTCY spec format, set `expiresAt = issuedAt + 24 hours`, set `complianceStandards` from system config, emit `agentidp_agent_cards_served_total` counter with `visibility` label
## 17. WS6: AGNTCY Compliance — Controller, Routes
- [ ] 17.1 Create `src/controllers/ComplianceController.ts` — implement `getComplianceReport` handler: check tenant tier is pro or enterprise (HTTP 403 `TIER_REQUIRED` for free tier), call `ComplianceService.generateComplianceReport`, return HTTP 200; implement `getAgentCard` handler: check agent visibility (HTTP 401 if private and unauthenticated, HTTP 403 if private and wrong tenant), call `ComplianceService.generateAgentCard`, return HTTP 200
- [ ] 17.2 Create `src/routes/agntcy.ts` — Express router for `GET /agntcy/compliance-report` (requires auth) and `GET /agents/:id/agent-card` (auth optional); register behind `AGNTCY_ENABLED` feature flag
- [ ] 17.3 Register `agntcy` router in `src/routes/index.ts`
- [ ] 17.4 Add `GET /agntcy/compliance-report` and `GET /agents/:id/agent-card` to `docs/openapi.yaml`
- [ ] 17.5 Write unit tests for `ComplianceService` — test: `generateComplianceReport` (all 24 requirements pass → `compliant`), `generateComplianceReport` (one evaluator fails → `partial`), `generateAgentCard` (public agent), `generateAgentCard` (private agent — verify agent data is included), `generateAgentCard` (non-existent agent → throws NotFoundError)
- [ ] 17.6 Write integration tests for compliance endpoints — test: compliance report for pro tenant (HTTP 200, overallStatus), compliance report for free tenant (HTTP 403), agent card for public agent (no auth required), agent card for private agent (auth required, correct tenant succeeds, wrong tenant HTTP 403)
## 18. WS6: AGNTCY Compliance — Interoperability Tests & Docs
- [ ] 18.1 Create `tests/agntcy/interoperability.test.ts` — implement all 25+ AGNTCY interoperability test cases as defined in spec: AI-001 (agent UUID uniqueness), AI-002 (W3C DID document), AUTH-001 (OAuth 2.0 token issuance), AUTH-002 (OIDC discovery), AUTHZ-001 (scope enforcement), DEL-001 through DEL-004 (delegation chain), AUDIT-001 through AUDIT-002 (immutable audit log), GOV-001 through GOV-002 (lifecycle governance), INTER-001 (agent card), COMP-001 (compliance report)
- [ ] 18.2 Add `"test:agntcy": "jest --testPathPattern=tests/agntcy --forceExit"` script to `package.json`
- [ ] 18.3 Write `docs/agntcy/certification-guide.md` — complete document with all 6 sections: Overview, Requirement Mapping table, Running the Compliance Report (step-by-step), Agent Card Usage, Self-Certification Checklist, Submitting for Official AGNTCY Certification; no placeholders, no TODOs
## 19. QA & Release
- [ ] 19.1 Run `cargo build` and `cargo clippy -- -D warnings` in `sdk-rust/` — zero warnings; run `cargo test` — all unit tests pass
- [ ] 19.2 Run `tsc --noEmit` across API, dashboard, portal, and CLI — zero TypeScript errors
- [ ] 19.3 Run full Jest suite (`npm test`) — all unit tests pass, coverage >= 80% across all new services: `DelegationService`, `AnalyticsService`, `ScaffoldService`, `ComplianceService`, `TierRateLimiter`
- [ ] 19.4 Run `npm run build` in `portal/` with Elements integration — zero errors; verify `/api-explorer` page renders Elements `<API>` component
- [ ] 19.5 Run `npm run build` in `cli/` — zero errors; run `node dist/index.js scaffold --help` — shows correct options; run `node dist/index.js --help` — shows `scaffold` command listed
- [ ] 19.6 Apply database migrations `008_add_delegation_chains.sql` and `009_add_analytics_aggregates.sql` against a test database — verify migrations run without errors and tables are created with correct schemas
- [ ] 19.7 Run integration tests for all Phase 5 endpoints — delegation (create, verify, revoke), analytics (usage-summary, agent-activity, token-trends), tiers (GET /tiers, POST /billing/upgrade), scaffold (all 5 languages), AGNTCY (compliance-report, agent-card)
- [ ] 19.8 Run `npm run test:agntcy` — all 25+ interoperability test cases pass
- [ ] 19.9 Verify feature flags: `A2A_ENABLED=false` → delegation routes return 404; `ANALYTICS_ENABLED=false` → analytics routes return 404; `TIER_RATE_LIMITING_ENABLED=false` → flat rate limiter used; `AGNTCY_ENABLED=false` → AGNTCY routes return 404
- [ ] 19.10 Verify tier rate limiting: free tenant receives 429 at 61st request/minute; pro tenant allows 600 requests/minute; tier cache refresh within 60s after Stripe webhook updates subscription
- [ ] 19.11 Verify scaffold security: `GET /sdk/scaffold/:agentId` response ZIP never contains a real `client_secret` value — `.env.example` placeholder only
- [ ] 19.12 Commit all Phase 5 work on `main` — one conventional commit per workstream (e.g., `feat(phase-5): WS1 — Rust SDK`, `feat(phase-5): WS2 — A2A Authorization`, etc.)