Phase 5 implementation complete — WS1 (Rust SDK), WS2 (A2A Authorization), WS5 (Developer Experience). All QA gates passed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
17 KiB
17 KiB
1. WS1: Rust SDK — Crate Setup
- 1.1 Create
sdk-rust/directory andCargo.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-exportAgentIdPClient,TokenManager,AgentIdPError, and all model types from submodules; add crate-level//!doc comment describing the SDK - 1.3 Create
sdk-rust/src/error.rs— defineAgentIdPErrorenum 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); derivethiserror::ErrorandDebug; implementstd::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 deriveserde::Serialize,serde::Deserialize,Debug,Clone
2. WS1: Rust SDK — Token Manager
- 2.1 Create
sdk-rust/src/token_manager.rs— defineTokenCachestruct withaccess_token: Option<String>andexpires_at: Option<std::time::Instant>; defineTokenManagerstruct with fieldsapi_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, checksexpires_atagainstInstant::now() + 60s, returns cached token if valid, else callsPOST /oauth2/tokenviareqwest, updates cache, releases lock - 2.4 Write unit test
token_manager_returns_cached_token— mockPOST /oauth2/tokenusingmockito, callget_token()twice, verify mock is hit only once - 2.5 Write unit test
token_manager_refreshes_expired_token— setexpires_atto past, verifyget_token()triggers a newPOST /oauth2/tokencall - 2.6 Write concurrent safety test
token_manager_concurrent_calls_no_race— spawn 50tokio::spawntasks all callingget_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— defineAgentIdPClientstruct with fieldsbase_url,client_id,client_secret,http: reqwest::Client,token_manager: Arc<Mutex<TokenManager>>; implementnew(base_url, client_id, client_secret) -> Selfandfrom_env() -> Result<Self, AgentIdPError>(readsAGENTIDP_API_URL,AGENTIDP_CLIENT_ID,AGENTIDP_CLIENT_SECRET) - 3.2 Create
sdk-rust/src/agents.rs— implement all agent methods onAgentIdPClient:register_agent,get_agent,list_agents,update_agent,delete_agent— each acquires a bearer token viatoken_manager.get_token(), makes the correct HTTP call, deserializes response, maps non-2xx responses toAgentIdPError::ApiError - 3.3 Create
sdk-rust/src/oauth2.rs— implementissue_token(&self, agent_id: &str, scopes: &[&str]) -> Result<TokenResponse, AgentIdPError>— sendsPOST /oauth2/tokenwithgrant_type=client_credentials - 3.4 Create
sdk-rust/src/credentials.rs— implementgenerate_credentials,rotate_credentials,revoke_credentials— map 404 response toAgentIdPError::NotFound, map 401 toAgentIdPError::AuthError - 3.5 Create
sdk-rust/src/audit.rs— implementlist_audit_logs(filters: AuditLogFilters)— serialize filters as query parameters; handle empty result set (return emptyVec, not error) - 3.6 Create
sdk-rust/src/marketplace.rs— implementlist_public_agents(filters)andget_public_agent(agent_id)— no auth header required for these endpoints - 3.7 Create
sdk-rust/src/delegation.rs— implementdelegate(req: DelegateRequest)andverify_delegation(token: &str) - 3.8 Implement 429 handling across all client methods — parse
Retry-Afterheader, returnAgentIdPError::RateLimited { retry_after_secs }; verify zerounwrap()calls in allsrc/files (rungrep -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: createAgentIdPClient::from_env(), callregister_agent, callissue_token, print token; example must compile withcargo build --example quickstart - 4.2 Create
sdk-rust/tests/integration_test.rs— integration tests requiringAGENTIDP_API_URL,AGENTIDP_CLIENT_ID,AGENTIDP_CLIENT_SECRETenv vars; test: register agent, issue token, get agent, update agent, rotate credentials, delete agent; each test is#[tokio::test]with#[ignore]attribute (run explicitly withcargo test -- --ignored) - 4.3 Write
sdk-rust/README.md— installation viaCargo.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; runcargo 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— createdelegation_chainstable 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 60–86400),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— implementsignDelegationPayload(payload: DelegationTokenPayload, secret: string): stringusing HMAC-SHA256 (Node.jscrypto.createHmac('sha256', secret)); implementverifyDelegationSignature(payload: DelegationTokenPayload, signature: string, secret: string): boolean; implementgenerateDelegationToken(): string(UUID v4); export only these three functions — no other exports - 6.2 Create
src/services/DelegationService.ts— implementIDelegationServiceinterface;createDelegation: validate delegateeAgentId exists in same tenant, validate scopes ⊆ delegator's scopes, reject self-delegation, sign payload, insertdelegation_chainsrow, write audit log entry (delegation.created), returnDelegationChain - 6.3 Implement
DelegationService.verifyDelegation(delegationToken)— fetch chain row bydelegation_token, if not found throwNotFoundError, verify HMAC signature, checkexpires_at > NOW()andrevoked_at IS NULL, returnDelegationVerificationResultwithvalid: true/false(never throw on expired/revoked — returnvalid: false); write audit log entry (delegation.verified) - 6.4 Implement
DelegationService.revokeDelegation(chainId, requestingAgentId)— fetch chain by ID, verifydelegator_agent_id === requestingAgentId(else throwForbiddenError), check not already revoked (else throwConflictError), updaterevoked_at = NOW(), write audit log entry (delegation.revoked)
7. WS2: A2A Authorization — Controller, Routes, Tests
- 7.1 Create
src/controllers/DelegationController.ts— implementcreateDelegationhandler (POST /oauth2/token/delegate): extract authenticated agent ID from request context, callDelegationService.createDelegation, return HTTP 201; implementverifyDelegationhandler (POST /oauth2/token/verify-delegation): callDelegationService.verifyDelegation, return HTTP 200; implementrevokeDelegationhandler (DELETE /oauth2/token/delegate/:chainId): callDelegationService.revokeDelegation, return HTTP 204 - 7.2 Create
src/routes/delegation.ts— Express router registeringPOST /oauth2/token/delegate,POST /oauth2/token/verify-delegation,DELETE /oauth2/token/delegate/:chainIdwith authentication middleware on all three routes - 7.3 Register delegation router in
src/routes/index.tsbehindA2A_ENABLEDfeature flag — return HTTP 404 on all delegation routes whenA2A_ENABLED=false - 7.4 Add delegation Prometheus metrics:
agentidp_delegations_created_total,agentidp_delegations_verified_total(labels: result),agentidp_delegations_revoked_total— increment inDelegationControllerhandlers - 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. WS5: Developer Experience — Scaffold Service
- 8.1 Install
archiverand@types/archiverin APIpackage.json - 8.2 Create
src/types/scaffold.ts— defineScaffoldLanguageunion ('typescript' | 'python' | 'go' | 'java' | 'rust'),ScaffoldOptionsinterface,ScaffoldTemplateinterface - 8.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.tmplMUST includeAGENTIDP_CLIENT_SECRET=<your-client-secret>placeholder (never inject real secret) - 8.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 - 8.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 - 8.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 - 8.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 - 8.8 Create
src/services/ScaffoldService.ts— implementIScaffoldService;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 usingarchiver; return{ stream: NodeJS.ReadableStream, filename: string }; emitagentidp_scaffold_generated_totalcounter andagentidp_scaffold_generation_duration_mshistogram
9. WS5: Developer Experience — Scaffold Controller & Route
- 9.1 Create
src/controllers/ScaffoldController.ts— implementgetScaffoldhandler forGET /sdk/scaffold/:agentId: validatelanguagequery param againstScaffoldLanguageunion (HTTP 400 on invalid); fetch agent, verify agent belongs to authenticated tenant (HTTP 403 if not); callScaffoldService.generateScaffold; setContent-Type: application/zip,Content-Disposition: attachment; filename="...", pipe stream to response; write audit log entry (scaffold.generated, metadata:{ language }) - 9.2 Create
src/routes/scaffold.ts— Express router forGET /sdk/scaffold/:agentIdwith authentication middleware; apply scaffold-specific rate limiter (10 req/min per tenant, separate from global rate limiter) - 9.3 Register
scaffoldrouter insrc/routes/index.ts - 9.4 Add
GET /sdk/scaffold/:agentIdtodocs/openapi.yaml— document binary response type, query parameters, all error responses - 9.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 throwsValidationError - 9.6 Write integration tests for scaffold endpoint — test: TypeScript scaffold returns ZIP with correct
Content-TypeandContent-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
10. WS5: Developer Experience — Portal & CLI
- 10.1 Install
@stoplight/elementsinportal/package.json— removeswagger-ui-react - 10.2 Rewrite
portal/app/api-explorer/page.tsx— replaceSwaggerUIcomponent with@stoplight/elements<API>component; setapiDescriptionUrl,router="hash",layout="sidebar",hideSchemas={false},tryItCredentialsPolicy="same-origin"; import Elements CSS; remove all Swagger UI imports and CSS - 10.3 Run
npm run buildinportal/— verify zero TypeScript errors and zero ESLint errors after Elements integration - 10.4 Install
unzipperand@types/unzipperincli/package.json - 10.5 Create
cli/src/commands/scaffold.ts— implementsentryagent scaffoldcommand with Commander options:--agent-id <id>(required),--language <lang>(default: typescript),--out <directory>(default:.); load config, issue Bearer token, callGET /sdk/scaffold/{agentId}?language={language}, pipe response throughunzipper.Extract({ path: outDir }), print success message and next steps; handle errors (404, 403, 400) with human-readable messages - 10.6 Register
scaffoldcommand incli/src/index.ts— add.addCommand(scaffoldCommand)to Commander program - 10.7 Run
npm run buildincli/— zero TypeScript errors; runnode dist/index.js scaffold --help— outputs correct usage
11. QA & Release
- 11.1 Run
cargo buildandcargo clippy -- -D warningsinsdk-rust/— zero warnings; runcargo test— all unit tests pass - 11.2 Run
tsc --noEmitacross API, portal, and CLI — zero TypeScript errors - 11.3 Run full Jest suite (
npm test) — all unit tests pass, coverage >= 80% across all new services:DelegationService,ScaffoldService - 11.4 Run
npm run buildinportal/with Elements integration — zero errors; verify/api-explorerpage renders Elements<API>component - 11.5 Run
npm run buildincli/— zero errors; runnode dist/index.js scaffold --help— shows correct options; runnode dist/index.js --help— showsscaffoldcommand listed - 11.6 Apply database migration
008_add_delegation_chains.sqlagainst a test database — verify migration runs without errors and table is created with correct schema - 11.7 Run integration tests for all Phase 5 endpoints — delegation (create, verify, revoke), scaffold (all 5 languages)
- 11.8 Verify feature flag:
A2A_ENABLED=false→ delegation routes return 404 - 11.9 Verify scaffold security:
GET /sdk/scaffold/:agentIdresponse ZIP never contains a realclient_secretvalue —.env.exampleplaceholder only - 11.10 Commit all Phase 5 work on
main— one conventional commit per workstream:feat(phase-5): WS1 — Rust SDK,feat(phase-5): WS2 — A2A Authorization,feat(phase-5): WS5 — Developer Experience