Skip to content

Commit 5f567f7

Browse files
author
Chris Li
committed
docs: completion checklist for #662 + docs/changes record for state+auth extensions
1 parent bf42329 commit 5f567f7

2 files changed

Lines changed: 122 additions & 2 deletions

File tree

PLAN_WORKFLOW_STATE_GUARDIAN_MONITORING.md

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,5 +334,50 @@ scans (absence ≠ cleared, studio parity). A time-based `wfstate` TTL is a futu
334334

335335
---
336336

337-
*Planning only. No code changed by this document. Gateway-side response to the studio
338-
`PLAN_GUARDIAN_MONITORING_ARCHITECTURE.md` hand-off.*
337+
## 10. Completion status
338+
339+
The two gateway extensions shipped in **PR #662** (merged to `staging`, 2026-07-16). Shipped record:
340+
`docs/changes/20260717-workflow-state-and-rest-auth.md`.
341+
342+
**✅ Done — gateway (PR #662):**
343+
- [x] **Extension 1REST `options.auth` providers.** `restAuthProvider` reads `options.auth.provider`;
344+
`goplusAuthHeader` mints + caches a GoPlus signed token (sha1 sign → `/v1/token`), cache keyed by
345+
`app_key`, 15 s HTTP timeout, 2xx check, `Bearer ` normalization; Authorization injected server-side
346+
in `vm_runner_rest.go`. Keyless fallback when keys unset. (`vm_runner_rest.go`)
347+
- [x] **Extension 2`{{state.*}}` per-workflow state.** `wfstate:<taskId>:<stateKey>` namespace +
348+
`state.get/set/list` goja binding (VM-scoped scratch, `IsSimulation`-guarded, deterministic-sorted
349+
`list`); cascade-delete on task teardown. Additive storage (`make storage-check` clean).
350+
(`schema.go`, `vm_runner_customcode.go`, `vm.go`, `engine.go`)
351+
- [x] **`guardian_ruleset` config wired**`gateway.example.yaml` / `test.example.yaml`, the local
352+
gateway config, and avs-infra `gateway-railway.yaml` (`${GOPLUS_APP_KEY}`/`${GOPLUS_APP_SECRET}` env
353+
refs + inlined ruleset).
354+
- [x] **Tests** — state roundtrip / cross-run persistence / simulation no-op; `restAuthProvider`
355+
parsing; Authorization-injection (mockable seam). Live-verified: authed GoPlus + `state` against a
356+
local gateway.
357+
358+
**✅ Done — SDK (ava-sdk-js `staging`):**
359+
- [x] Guardian wallet-risk monitor test + builder/verdict fixture (offline verdict parity + builder
360+
shape + live deploy/trigger) — `tests/v4/templates/guardian-wallet-risk-monitor.test.ts`.
361+
362+
**⏭️ Deferred / optional (not blocking v1):**
363+
- [ ] `SetIfAbsent` storage primitive for **strict** exactly-once claim-once (mark-after-send is the v1
364+
default, §3.3).
365+
- [ ] `{{state.*}}` template auto-load for non-CustomCode nodes2.3).
366+
- [ ] GoPlus `code:4033` retry-keyless nuance (keyless-on-unset-keys **is** implemented).
367+
- [ ] Held-token (critical-token) scan — approvals-only for v1; held-token is a fast-follow.
368+
- [ ] Promote the `Guardian` builder from SDK test fixture to published `packages/sdk-js` surface.
369+
370+
**🟨 Client-side (studio) — separate product work, tracked in the studio guardian docs:**
371+
- [ ] Advisor tool `enable_guardian_monitoring` deploys the per-user workflow.
372+
- [ ] Enrollment via the one-time web-sign hand-off9-B).
373+
- [ ] Sync `guardian_ruleset` from `app/lib/goplus.ts`.
374+
375+
**🔧 Ops follow-up:**
376+
- [x] Railway `GOPLUS_APP_KEY` / `GOPLUS_APP_SECRET` set on the gateway service.
377+
- [ ] Republish `avs-dev:latest` dev docker so the SDK E2E guardian **live** test asserts (it soft-skips
378+
against the pre-merge image).
379+
380+
---
381+
382+
*Gateway-side response to the studio `PLAN_GUARDIAN_MONITORING_ARCHITECTURE.md` hand-off. The two
383+
extensions are now implemented (PR #662); this document is retained as the design record.*
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Per-Workflow State (`{{state.*}}`) + REST `options.auth` Providers
2+
3+
## Summary
4+
5+
Two generic, reusable gateway extensions (PR #662, merged to `staging` 2026-07-16). Design record:
6+
`PLAN_WORKFLOW_STATE_GUARDIAN_MONITORING.md`. The first consumer is the studio "guardian" wallet-risk
7+
monitor, but neither extension is guardian-specific.
8+
9+
1. **`{{state.*}}` — durable, cross-run, per-workflow state.** A `customCode` node can call
10+
`state.get/set/list` to persist arbitrary client-defined JSON that survives across scheduled runs,
11+
scoped to the workflow (`taskId`) — not the user. This is what lets a monitor alert on *new*
12+
findings only (diff current-vs-last-seen) without any studio callback in the hot path.
13+
2. **REST `options.auth` providers.** A `restApi` node can set
14+
`config.options.auth = { provider: "goplus" }`; the gateway mints/attaches the provider credential
15+
server-side so the secret never lives in the (client-visible) workflow JSON. GoPlus is provider #1.
16+
17+
## Extension 1 — `{{state.*}}`
18+
19+
- **Storage**: new `wfstate:<taskId>:<stateKey>` BadgerDB namespace (`core/taskengine/schema.go`
20+
`WorkflowStateKey` / `WorkflowStatePrefix`). Scoped by `taskId` only, so a user's multiple monitoring
21+
workflows never collide and a workflow's state can be wiped exactly on deletion. Additive — no
22+
migration (`make storage-check` clean).
23+
- **Binding**: `state.get/set/list` bound into the goja runtime in both `NewJSProcessor` and
24+
`NewJSProcessorWithIsolatedVars` (`core/taskengine/vm_runner_customcode.go` `installStateBinding`),
25+
over `vm.db`. Bound after the step vars so a node named `state` can't shadow it.
26+
- **Non-persistent mode**: in simulation, single-node runs (no `taskId`), or when no DB is wired,
27+
reads/writes use a VM-scoped scratch map (`vm.stateScratch`, mutex-guarded, `core/taskengine/vm.go`)
28+
so `nodes:run` / `workflows:simulate` previews never mutate live state, yet read-after-write and
29+
cross-node coherence still hold within one run.
30+
- **Deterministic `list`**: results are sorted in both the DB and scratch paths so simulation and real
31+
execution agree on ordering.
32+
- **Lifecycle**: cascade-deleted on task teardown (`DeleteWorkflowByUser`, `core/taskengine/engine.go`);
33+
a `ListKeys` failure is logged rather than silently orphaning state.
34+
- **Namespace convention**: `{{state.*}}` is the mutable, client-defined, read/write namespace parallel
35+
to `{{settings.*}}` (immutable config) and `{{context.*}}` (read-only runtime).
36+
37+
## Extension 2 — REST `options.auth` (GoPlus)
38+
39+
- **`restAuthProvider(node)`** reads `options.auth.provider` from the node's `options` bag (mirrors
40+
`shouldSummarize`). No proto/SDK type change — `options` is already an open `structpb.Value`.
41+
- **`goplusAuthHeader()`** mints a GoPlus session token from `macros.secrets`
42+
(`goplus_app_key`/`goplus_app_secret`): `sign = sha1_hex(app_key + time + app_secret)` → POST
43+
`/api/v1/token`. Token is module-cached (RWMutex), **keyed by `app_key`** so a rotated key never
44+
reuses a stale token, refreshed ~60 s early. Uses an `http.Client` with a 15 s timeout, checks for a
45+
2xx status, and normalizes the token to a `Bearer ` prefix defensively.
46+
- **Injection**: if the provider mints a token, it's set as the `Authorization` header in
47+
`processedHeaders` before the request; on `""` (keys unset or mint failed) no header is sent — GoPlus
48+
answers keyless (lower rate limits). A `goplusTokenProvider` seam makes the injection unit-testable.
49+
- **Config**: `goplus_app_key`/`goplus_app_secret` added to `macros.secrets` in the example configs;
50+
real values via Railway env on the deployed `gateway-railway.yaml` (`${GOPLUS_APP_KEY}` /
51+
`${GOPLUS_APP_SECRET}`). `guardian_ruleset` (the tunable verdict knobs) is inlined as JSON (not a
52+
secret).
53+
54+
## Files
55+
56+
`core/taskengine/schema.go`, `vm.go`, `vm_runner_customcode.go`, `vm_runner_rest.go`, `engine.go`,
57+
`vm_workflow_state_test.go`; `config/gateway.example.yaml`, `config/test.example.yaml`.
58+
59+
## Testing
60+
61+
- Go: state set/get/list roundtrip, cross-run persistence, simulation no-op guard; `restAuthProvider`
62+
parsing; Authorization-injection via `httptest` + the `goplusTokenProvider` seam.
63+
- Live: verified against a local gateway with real GoPlus keys — the guardian scan mints an authed
64+
token (`attached GoPlus session token (authed)`) and the `state`-backed verdict runs; the ava-sdk-js
65+
guardian template test passes 14/14 with the verdict step asserting.
66+
67+
## Deferred / follow-ups
68+
69+
- `SetIfAbsent` storage primitive for **strict** exactly-once claim-once (mark-after-send is the v1
70+
default); `{{state.*}}` template auto-load for non-CustomCode nodes; GoPlus `code:4033` retry-keyless
71+
nuance; held-token (critical-token) scan (approvals-only for v1).
72+
- Ops: republish `avs-dev:latest` so the SDK E2E guardian **live** test asserts against a gateway with
73+
these extensions.
74+
- Studio (separate): the `enable_guardian_monitoring` advisor tool, web-sign enrollment, and syncing
75+
`guardian_ruleset` from `app/lib/goplus.ts`.

0 commit comments

Comments
 (0)