Skip to content

Commit 5b419fe

Browse files
nficanoNick Ficanocursoragentclaude
authored
Fix audit findings (non-docs): wire format, spec-conformance, bugs, security (#120)
* fix(core): pin spec wire format for union payloads (#94) Add custom JSON converters for JobStatus, LogLevel, ChunkEncoding, LeaseGrant (bare namespace map), CredentialConstraints (dotted keys), AgentInventory (plain array) and JobEventPayload (flat kind-specific body). Reorder Json.fs after Messages.fs so converters can reference the payload types. Add golden wire-format tests pinning the spec JSON shapes (§6.2, §7.3, §8.2, §8.4). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(runtime): spec-conformance blockers — close ack, cancel, idempotency, leases, credentials - session.close/session.closed wire types replace session.bye (#73) - idempotency replay compares param fingerprint, emits DUPLICATE_KEY on conflict (#76) - cancellation acks job.cancelled then job.error(CANCELLED) (#77, #100); job.cancel returns JOB_NOT_FOUND/PERMISSION_DENIED (#78) - EmitDelegateAsync validates lease subset, raises LEASE_SUBSET_VIOLATION (#81) - credential_rotated value redacted from subscribers (#82) - streamed job.result carries result_id; mixing inline+chunks errors (#83) - anonymous sessions never receive credentials; startup guard (#88) - EventLog.EvictExpired honors lastAckedSeq; periodic pruning timer + PruneEmpty (#75, #105) - terminal job records dispose CTS/watchdog, clear credentials, evicted after retention (#113) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(runtime): implement session.resume flow + replay (#74, #80) Add session.resume message type, move resume out of session.hello, retain disconnected sessions as resumable within the window, replay buffered events on reattach, rotate resume token, and return RESUME_WINDOW_EXPIRED for unknown/expired sessions. Add integration tests. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(runtime): enforce timeout, terminal guard, replay payload, telemetry, anonymous isolation - max_runtime_sec watchdog emits job.error(TIMEOUT)/timed_out (#101) - idempotent replay returns the original job.accepted verbatim (#102) - single terminal message per job via terminal gate (#103) - cost.budget.* telemetry no longer decrements budget (#104) - unique per-session anonymous principal id (#110) - malformed/unknown inbound -> session.error INVALID_REQUEST, session survives (#99) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(runtime/client): pagination, subscribe history, ordering, rotation, client races - list_jobs stable ordering + cursor pagination + next_cursor; bare-name agent filter; limit applied via take+1 (#109, #91, #108) - job.subscribe history:true replays buffered events; replayed reflects reality (#115) - per-session gate serializes event_seq assignment with send (#112) - credential rotation keeps id outstanding for terminal revocation (#107) - client buffers job-addressed envelopes until handle registered (#95) - SubscribeAsync surfaces denials instead of a dead handle (#96) - receive-loop exit faults pending waiters and completes handles (#97) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(runtime): agent_versions gating, chunk/progress/metric validation, revocation surfacing - reject name@version without agent_versions; strip suffix from non-negotiated sessions (#79) - enforce chunk_seq monotonicity and single-stream completion (#84) - progress current>=0 (reject) and current<=total (clamp) (#85) - negative cost metrics rejected; negative non-cost metrics still emit (#86) - permanent credential revocation failures logged + exposed via RevocationFailures (#87) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(bugs): crypto trace ids, error mapping, caps, lifecycle, transports - crypto RNG for trace/span ids (#41) - handler resolved before job.accepted; shared unwind (#47) - lease/runtime watchdog callbacks observe exceptions (#48) - 3-state RevocationOutcome; permanent failures keep credential outstanding (#49) - auto-ack/receive-loop/dispose tasks observed; Completion exposed (#60,#61,#62) - JobErrorMapper parses details fields (#63) - AutoAck docs corrected to event-driven (#64) - ChunkAssembler + WebSocket message size/chunk caps (#65,#66) - Giraffe short-circuits ws/400 branches (#40) - CLI awaits enumerator dispose and honors --stdio (#38,#39) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(minors): clean-code, perf, safety, feature gating - watchdog skips terminal jobs (#89); wire retryable + Unknown arm (#90) - client observes cancellation tokens (#98); reject re-hello (#106) - session-scoped LastEventSeq via Interlocked; subscribed_from in subscriber space (#111) - enforce negotiated features on submit (#114); duplicate-currency budget subset sum (#116) - readEnvelope rejects null/empty envelopes (#117); ArcpResult avoids shadowing FSharp.Core Result (#118) - StaticBearerVerifier constant-time compare (#119); DevMode rejects whitespace (#54) - glob regex cache (#44); literalPrefix/isSubset cleanup (#43,#45); array retryDelays (#55) - single idempotency lookup (#52); credential revoke failure observable (#53) - dead-code removal (#51,#67); volatile stdio closed (#68); typed pending error (#69) - Connected member (#70); optional jobId mapping (#71); lazy Otel source from Version.Sdk (#42) Co-authored-by: Cursor <cursoragent@cursor.com> * style: apply fantomas formatting Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Nick Ficano <nficano@teachmehipaa.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 2c6009e commit 5b419fe

57 files changed

Lines changed: 2318 additions & 462 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

recipes/_common/RecipeHarness.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ let connectWithOptions
4444
Features = features
4545
BearerVerifier = DevModeBearerVerifier() :> IBearerVerifier }
4646
|> configureOptions
47-
let server = ArcpServer(options)
47+
let server = new ArcpServer(options)
4848
configureServer server
4949
let clientTransport, serverTransport = MemoryTransport.CreatePair()
5050
let serverTask = server.HandleSessionAsync(serverTransport, cts.Token)

samples/AspNetCore/Program.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let main argv =
1515
let builder = WebApplication.CreateBuilder(argv)
1616

1717
let server =
18-
ArcpServer(
18+
new ArcpServer(
1919
{ ArcpServerOptions.defaults with
2020
Features = Features.All
2121
}

samples/CustomAuth/Program.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ let main _argv =
2929
let cts = new System.Threading.CancellationTokenSource()
3030

3131
let server =
32-
ArcpServer(
32+
new ArcpServer(
3333
{ ArcpServerOptions.defaults with
3434
BearerVerifier = FixedBearerVerifier "secret" :> IBearerVerifier
3535
Features = Features.All

samples/Giraffe/Program.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let main argv =
1515
let builder = WebApplication.CreateBuilder(argv)
1616

1717
let server =
18-
ArcpServer(
18+
new ArcpServer(
1919
{ ArcpServerOptions.defaults with
2020
Features = Features.All
2121
}

samples/LiteLLM/Program.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ type LiteLLMProvisioner(baseUrl: Uri, adminKey: string, http: HttpClient) =
8989
task {
9090
let body = {| key = credentialId |} :> obj
9191
let! _ = postJsonAsync "/key/delete" body ct
92-
return true
92+
return RevocationOutcome.Revoked
9393
}
9494

9595
[<EntryPoint>]

samples/ProvisionedCredentials/Program.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type StaticProvisioner(revoked: HashSet<string>) =
3131

3232
member _.RevokeAsync(credentialId, _ct) =
3333
lock revoked (fun () -> revoked.Add credentialId |> ignore)
34-
Task.FromResult true
34+
Task.FromResult RevocationOutcome.Revoked
3535

3636
[<EntryPoint>]
3737
let main _argv =

samples/Stdio/Program.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ open ARCP.Runtime
1212
[<EntryPoint>]
1313
let main _argv =
1414
let server =
15-
ArcpServer(
15+
new ArcpServer(
1616
{ ArcpServerOptions.defaults with
1717
Features = Features.All
1818
AllowAnonymousAuth = true

samples/_common/SampleHarness.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ let private makeServerWithOptions
4242
}
4343
|> configureOptions
4444

45-
let server = ArcpServer(options)
45+
let server = new ArcpServer(options)
4646
configure server
4747
server
4848

src/Arcp.Cli/Program.fs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ let private serveStdio (token: string option) : Task<int> =
6565
AllowAnonymousAuth = Option.isNone token
6666
}
6767

68-
let server = ArcpServer(options)
68+
let server = new ArcpServer(options)
6969
server.RegisterAgent("echo", fun ctx -> task { return Json.serializeToElement<string> "echo" })
7070
let transport = StdioTransport.fromConsole ()
7171
errorLine "serve --stdio: ready"
@@ -88,7 +88,11 @@ let private streamEventsAsync (handle: JobHandle) : Task =
8888
else
8989
writeLine (sprintf "event: %s" (JobEventBody.kind enumerator.Current))
9090
finally
91-
ignore (enumerator.DisposeAsync().AsTask())
91+
()
92+
93+
// §38: await disposal so teardown errors surface and complete
94+
// before this function returns.
95+
do! enumerator.DisposeAsync()
9296
}
9397
:> Task
9498

@@ -162,7 +166,12 @@ let main argv =
162166
let env = Environment.GetEnvironmentVariable "ARCP_TOKEN"
163167
if String.IsNullOrEmpty env then None else Some env)
164168

165-
(serveStdio token).GetAwaiter().GetResult()
169+
// §39: honor the --stdio flag instead of ignoring it.
170+
if sub.Contains ServeArgs.Stdio then
171+
(serveStdio token).GetAwaiter().GetResult()
172+
else
173+
errorLine "serve requires a transport flag; only --stdio is currently supported (pass --stdio)"
174+
2
166175
| Send sub :: _ ->
167176
let url = sub.GetResult SendArgs.Url
168177

0 commit comments

Comments
 (0)