docs: spec workspace primitives + custom digest functions#147
docs: spec workspace primitives + custom digest functions#147khaliqgant wants to merge 3 commits into
Conversation
Two implementation specs for the workspace primitives the "Just Give the Agent Files" blog post and the four @agent-relay/* skills (AgentWorkforce/skills#39) assume: 1. workspace-primitives-spec.md — the seven gaps between what the skills document and what code actually ships: digest generator, adapter digest() SDK contract, per-provider .layout.md files, <resource>/.schema.json sibling files, `writeback list` CLI subcommand, dead-letter .error.json sidecars, and the wb-<timestamp>.json doc convention. With acceptance criteria and sequencing. 2. custom-digest-functions-spec.md — the follow-on primitive that lets customer engineers write TypeScript digest logic, compile to WASM at deploy time, and run sandboxed via wazero in the mount daemon alongside the first-party adapter digests. Depends on work items 1 + 2 of the workspace-primitives spec. The "Just Give the Agent Files" blog teases the custom-functions work; this PR makes the spec real so an implementer can pick it up. Companion: closes #146 (the .claude/skills bundle was the wrong home for these skills — they live in AgentWorkforce/skills#39 instead). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (5)
📝 Walkthrough<review_stack_artifact> </review_stack_artifact> WalkthroughAdds workspace-primitives and custom-digest function specifications, implements a wazero-backed wasmrun engine (envelope, compile/cache, host ABI, determinism), a file-backed WASM cache, digest generator/render + types/tests, CLI and SDK surfaces for digest functions, and extensive tests/testdata. ChangesCustom digest + workspace primitives (single cohort)
Sequence Diagram(s)sequenceDiagram
participant CLI as CLI/SDK
participant ControlPlane as Relayfile Cloud API
participant Engine as wasmrun Engine
participant Cache as FileCache
participant Events as ChangeEventsProvider
CLI->>ControlPlane: deploy/list/show/disable/logs requests
ControlPlane->>Cache: store/retrieve SignedModule (ContentHash)
CLI->>Engine: test/invoke signed module locally
Engine->>Cache: Get compiled WASM by ContentHash
Engine->>Events: scoped ChangeEvents(filter)
Events-->>Engine: change event JSON
Engine-->>CLI: DigestSection + warnings / logs
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/custom-digest-functions-spec.md`:
- Around line 78-112: The fenced ASCII diagram block is unlabeled and triggers
markdownlint MD040; update the opening triple-backtick to include a language
identifier like text or plaintext (e.g., change ``` to ```text) so the diagram
(containing elements such as "relayfile digest function", "digest scheduler",
and "wasmrun (wazero sandbox)") is treated as plain text by markdown linters and
renderers.
In `@docs/workspace-primitives-spec.md`:
- Line 17: The audit/gap table currently lists only `digests/yesterday.md` but
Goal/Work item 1 requires both `digests/yesterday.md` and `digests/today.md`;
update the table row(s) to include both files (`digests/today.md` and
`digests/yesterday.md`) and ensure their "hand-authored" and "generator" status
columns reflect the intended scope (e.g., mark both as ✅ hand-authored or
indicate generator presence consistently). Also scan surrounding text
referencing the gap table to ensure any planning/goal language mentions both
`today` and `yesterday` digests so the spec and audit stay in sync.
- Around line 195-200: The unlabeled fenced code block in
docs/workspace-primitives-spec.md (the block showing the <mount>/linear/...
paths) needs a language identifier to satisfy markdownlint MD040; change the
opening fence from ``` to ```text so the block starts with ```text and leave the
closing fence as ```; update the single code block that contains the lines with
/comments/, /.schema.json, /state-transitions/ accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 80261e90-7314-4833-a2a4-a99b08ce343e
📒 Files selected for processing (2)
docs/custom-digest-functions-spec.mddocs/workspace-primitives-spec.md
| ``` | ||
| ┌────────────────────────────────┐ | ||
| │ relayfile digest function │ | ||
| │ deploy ./eng-roadmap.ts │ | ||
| └────────────────────────────────┘ | ||
| │ | ||
| ▼ | ||
| ┌────────────────────────────────┐ | ||
| │ control plane (cloud) │ | ||
| │ • compile TS → JS → WASM │ | ||
| │ • content-hash, sign │ | ||
| │ • store per workspace_id │ | ||
| └────────────────────────────────┘ | ||
| │ pull via long-poll / WS | ||
| ▼ | ||
| ┌──────────────────────────────────────────────┐ | ||
| │ mount daemon │ | ||
| │ │ | ||
| │ digest scheduler │ | ||
| │ │ │ | ||
| │ ├──► first-party adapter digest() │ | ||
| │ │ (Go, in-process) │ | ||
| │ │ │ | ||
| │ └──► wasmrun (wazero sandbox) │ | ||
| │ customer digest() WASM modules │ | ||
| │ • timeout: 5s │ | ||
| │ • memory: 64MB │ | ||
| │ • deterministic clock │ | ||
| │ • no host syscalls except │ | ||
| │ ctx.changeEvents() bridge │ | ||
| │ │ | ||
| │ renderer: merge all DigestSections │ | ||
| │ → digests/yesterday.md │ | ||
| └──────────────────────────────────────────────┘ | ||
| ``` |
There was a problem hiding this comment.
Add a language identifier to the architecture code fence.
This block is currently an unlabeled fenced block and will trip markdownlint MD040. Use text (or plaintext) for the ASCII diagram fence.
Suggested patch
-```
+```text
┌────────────────────────────────┐
│ relayfile digest function │
│ deploy ./eng-roadmap.ts │
@@
-```
+```📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ``` | |
| ┌────────────────────────────────┐ | |
| │ relayfile digest function │ | |
| │ deploy ./eng-roadmap.ts │ | |
| └────────────────────────────────┘ | |
| │ | |
| ▼ | |
| ┌────────────────────────────────┐ | |
| │ control plane (cloud) │ | |
| │ • compile TS → JS → WASM │ | |
| │ • content-hash, sign │ | |
| │ • store per workspace_id │ | |
| └────────────────────────────────┘ | |
| │ pull via long-poll / WS | |
| ▼ | |
| ┌──────────────────────────────────────────────┐ | |
| │ mount daemon │ | |
| │ │ | |
| │ digest scheduler │ | |
| │ │ │ | |
| │ ├──► first-party adapter digest() │ | |
| │ │ (Go, in-process) │ | |
| │ │ │ | |
| │ └──► wasmrun (wazero sandbox) │ | |
| │ customer digest() WASM modules │ | |
| │ • timeout: 5s │ | |
| │ • memory: 64MB │ | |
| │ • deterministic clock │ | |
| │ • no host syscalls except │ | |
| │ ctx.changeEvents() bridge │ | |
| │ │ | |
| │ renderer: merge all DigestSections │ | |
| │ → digests/yesterday.md │ | |
| └──────────────────────────────────────────────┘ | |
| ``` |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 78-78: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/custom-digest-functions-spec.md` around lines 78 - 112, The fenced ASCII
diagram block is unlabeled and triggers markdownlint MD040; update the opening
triple-backtick to include a language identifier like text or plaintext (e.g.,
change ``` to ```text) so the diagram (containing elements such as "relayfile
digest function", "digest scheduler", and "wasmrun (wazero sandbox)") is treated
as plain text by markdown linters and renderers.
| | Feature | mount-verify | Code | | ||
| |---|---|---| | ||
| | `<identifier>__<uuid>` filenames, `by-*` indexes, `_index.json`, `LAYOUT.md`, `.relay/dead-letter/`, `writeback retry --opId`, `writeback.{succeeded,failed}` events | ✅ ✅ | ✅ | | ||
| | `digests/yesterday.md` | ✅ (hand-authored) | ❌ no generator | |
There was a problem hiding this comment.
Align the audit table with the two-file digest scope (today + yesterday).
The gap table calls out only digests/yesterday.md, but the Goal/Work item 1 require both digests/yesterday.md and digests/today.md. This can cause implementation under-scope during planning.
Suggested patch
-| `digests/yesterday.md` | ✅ (hand-authored) | ❌ no generator |
+| `digests/yesterday.md` + `digests/today.md` | ✅ (`yesterday.md` hand-authored, no `today.md`) | ❌ no generator |🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/workspace-primitives-spec.md` at line 17, The audit/gap table currently
lists only `digests/yesterday.md` but Goal/Work item 1 requires both
`digests/yesterday.md` and `digests/today.md`; update the table row(s) to
include both files (`digests/today.md` and `digests/yesterday.md`) and ensure
their "hand-authored" and "generator" status columns reflect the intended scope
(e.g., mark both as ✅ hand-authored or indicate generator presence
consistently). Also scan surrounding text referencing the gap table to ensure
any planning/goal language mentions both `today` and `yesterday` digests so the
spec and audit stay in sync.
| ``` | ||
| <mount>/linear/issues/AGE-16__.../comments/ | ||
| <mount>/linear/issues/AGE-16__.../comments/.schema.json ← virtual | ||
| <mount>/linear/issues/AGE-16__.../state-transitions/ | ||
| <mount>/linear/issues/AGE-16__.../state-transitions/.schema.json | ||
| ``` |
There was a problem hiding this comment.
Add a language identifier to the layout code fence.
This fenced block is unlabeled and will fail markdownlint MD040. Use text for the path layout example.
Suggested patch
-```
+```text
<mount>/linear/issues/AGE-16__.../comments/
<mount>/linear/issues/AGE-16__.../comments/.schema.json ← virtual
<mount>/linear/issues/AGE-16__.../state-transitions/
<mount>/linear/issues/AGE-16__.../state-transitions/.schema.json
-```
+```🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 195-195: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/workspace-primitives-spec.md` around lines 195 - 200, The unlabeled
fenced code block in docs/workspace-primitives-spec.md (the block showing the
<mount>/linear/... paths) needs a language identifier to satisfy markdownlint
MD040; change the opening fence from ``` to ```text so the block starts with
```text and leave the closing fence as ```; update the single code block that
contains the lines with /comments/, /.schema.json, /state-transitions/
accordingly.
…teback list CLI, dead-letter sidecars (#148) * feat(workspace-primitives): digest generator, schema.json discovery, writeback list CLI, dead-letter sidecars Implements the seven work items in docs/workspace-primitives-spec.md (PR #147), turning the four `@agent-relay/*` workspace-primitive skills from documentation into working code paths. Work items in this PR: 1. Digest generator (WI 1) - internal/digest/{digest,render,types}.go: deterministic Markdown generator with YAML frontmatter that produces <mount>/digests/yesterday.md and today.md from the change-event log. - testdata/digest/{yesterday,empty}-{events.json,fixture.md}: golden fixtures used by digest_test.go for the byte-identity guarantee. - cmd/relayfile-cli/digest.go + digest_test.go: `relayfile digest rebuild --window yesterday` CLI entry point. - Wire-up in internal/mountsync/syncer.go for the close-of-day and 30s rolling triggers. 4. Per-resource .schema.json virtual files (WI 4) - internal/mountfuse/schema.go + schema_test.go: serves <resource>/.schema.json paths from schemas/relay/ at mount time; read-only (EACCES on write). - schemas/relay/dead-letter-error.schema.json: dead-letter error contract referenced by WI 6. 5. `relayfile writeback list` CLI subcommand (WI 5) - cmd/relayfile-cli/writeback_list.go + writeback_list_test.go: adds the `list --state pending|dead|succeeded|failed [--json]` subcommand wired through main.go's writeback switch. - internal/httpapi/server.go + server_test.go: new GET /v1/workspaces/{workspaceId}/writeback?state=… endpoint that backs the CLI; OpenAPI updated separately in spec PR #147. - packages/core/src/writeback.list.test.ts: contract tests against the WritebackItem shape exported from the TS SDK. 6. Dead-letter .error.json sidecars (WI 6) - internal/writeback/dead-letter.go + dead-letter_test.go: writes the sibling <filename>.error.json with code/message/providerStatus/ providerResponse/attempts/firstAttemptAt/lastAttemptAt/opId when a writeback exhausts retries. - packages/core/src/writeback.deadletter.test.ts: SDK-side consumption tests. - packages/core/src/writeback.schemaref.test.ts: validates the sidecar against schemas/relay/dead-letter-error.schema.json. SDK / Python / TypeScript: - packages/sdk/typescript/src/{index,types,client.test,types.test, writeback-consumer.test}.ts: export DigestSection/DigestBullet types and WritebackItem variants for the new states. - packages/sdk/python/src/relayfile/*.py + tests/test_*.py: mirror the TS surface; list_writebacks(state="pending") works against the real endpoint and degrades to NotImplementedError for non-pending states until the server endpoint extends per the spec. - packages/core/src/{storage,writeback}.ts: route the new endpoint and dead-letter writes through the existing core abstractions. Tests + infra: - packages/{core,file-observer,local-mount}/vitest.config.ts: vitest configs required by the new per-package test suites. - packages/local-mount/pnpm-lock.yaml: lockfile bump for the new test deps. Pairs with AgentWorkforce/relayfile-adapters PR for WI 2 (`digest()` and `layoutManifest()` adapter contracts + per-provider implementations). WI 3 (per-provider `.layout.md`) is partially landed: layout.go was extended to render per-provider layouts but the adapter-side layoutManifest() contract ships in the relayfile-adapters PR. WI 7 (`wb-<timestamp>.json` convention) is documentation-only and lives on the spec branch in PR #147. Generated by the workspace-primitives implementation workflow run 382654597c96e23b0b1dfa39; fix-loop reports under .workflow-artifacts/generated/workspace-primitives-*/*/fix-loop-report.md in the source repo confirm typecheck + tests green at the per-slice level. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(workspace-primitives): address PR review feedback CodeRabbit + Devin review feedback on #148. 1. internal/digest/digest.go - Include event providers missing from Window.Providers so their bullets render and `Meta.Events` stays consistent with visible bullets (CodeRabbit, digest.go:46). The Providers field is the "expected to be connected" hint, not an allow-list; once an event reaches Run() the renderer must surface it. Filtering by which adapters export `digest()` is the upstream concern. - Add deterministic tie-breakers (Identifier > Verb > CanonicalPath) for events with equal timestamps so SliceStable cannot leak source order into the rendered output (CodeRabbit, digest.go:53). The existing golden fixture is unchanged because the seeded events already have distinct timestamps; this guards against future fixtures or production traffic with timestamp collisions. - Rename and flip TestSkipsProviderWithoutDigest to TestEventFromUnlistedProviderStillRenders so it asserts the new contract instead of the old buggy behavior. 2. cmd/relayfile-cli/main.go - classifyDeadLetterSidecarCode: drop the `0 < status < 400` case that returned CodeProvider4xx. The doc comment says non-error statuses map to timeout-style; the explicit branch contradicted that and would mislabel 1xx/3xx responses as `provider_4xx` in the sidecar (Devin BUG-0001, CodeRabbit main.go:5605). - writeDeadLetterPayloadWithSidecar: marshal the sidecar before touching disk so a serialization failure can't leave an orphan payload, and if the sidecar write fails after the payload lands, remove the payload so the dead-letter directory never holds a half-committed record (CodeRabbit main.go:5588). 3. internal/relayfile/store.go - paginateTreeEntries now returns ErrInvalidInput when a non-empty cursor doesn't match any entry, instead of silently restarting pagination at page 1 and causing duplicate-page loops on stale/typo cursors (CodeRabbit store.go:4263). Signature changed to (entries, cursor, error); listTreeFromFiles and ListForkTree propagate the error to callers. 4. internal/mountfuse/fuse_test.go - Use the map-lookup idiom for the $schema assertion so a missing key fails the test instead of silently passing as `nil == ""` (CodeRabbit fuse_test.go:1201). 5. evals/suites/integrations/cases.md - Replace the hardcoded `/Users/khaliqgant/...` mount path with the fixture-relative `./mount-verify` (CodeRabbit cases.md:131). - Add a GitHub `jqFilter` op so the case actually exercises GH discovery instead of passing on Linear+Notion alone (CodeRabbit cases.md:181). 6. packages/sdk/python/src/relayfile/types.py - Add WritebackListState that includes "dead" alongside "dead_lettered" so the `writeback list` CLI's --state alias round-trips through the typed surface (CodeRabbit types.py:42). - WritebackItem.from_dict now accepts `attempts` and `enqueuedAt` in addition to the existing camel/snake variants, so rows emitted by the new CLI populate attempt_count/created_at instead of dropping the metadata. - Rename DigestContext.change_events parameter from `filter` to `event_filter` to silence Ruff A002 (shadows the built-in) (CodeRabbit types.py:313). Verification: - `go test ./internal/... ./cmd/...` — green - `npm run typecheck` — green - `npx vitest run packages/core/src` — 27/27 - `pytest packages/sdk/python/tests` — 76/76 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(digest): check error from deferred f.Close in loadEventsJSONL CodeRabbit errcheck finding on #148. The bare `defer f.Close()` dropped the close error; wrap in a deferred closure that reports via t.Errorf so a future read-side close failure surfaces instead of being silently swallowed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Proactive Runtime Bot <agent@agent-relay.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: clarify custom digest repo routing * feat: add custom digest function sdk and cli * fix: address digest function review feedback * test: make relayfile CI digest checks hermetic --------- Co-authored-by: Proactive Runtime Bot <agent@agent-relay.com>
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (5)
internal/wasmrun/host.go (1)
187-215: 💤 Low valueIn-place slice filtering is safe but subtle.
The in-place filter
filtered := events[:0]reuses the backing array from the provider's result. This is safe because:
EventsProvider.ChangeEventsreturns a fresh slice per call- Line 214 creates a new slice copy before returning
The pattern is efficient but worth a brief comment for future readers.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/wasmrun/host.go` around lines 187 - 215, The in-place filtering in Engine.scopedChangeEvents reuses the backing array from EventsProvider.ChangeEvents which is safe here but subtle; add a short clarifying comment. Edit the scopedChangeEvents function to insert a brief comment above `filtered := events[:0]` explaining that EventsProvider.ChangeEvents returns a fresh slice and that the final `return append([]ChangeEvent(nil), filtered...)` makes a copy before returning, so reusing the backing array is intentional and safe; reference the calls to EventsProvider.ChangeEvents, normalizePatterns and matchesAny so reviewers can locate the logic quickly.internal/wasmrun/wasmrun.go (2)
301-305: 💤 Low valueRedundant nil check for
Logger.
e.opts.Loggeris guaranteed non-nil afterNewEngineinitialization (lines 137-139), so the nil check on line 302 is unnecessary.Proposed simplification
func (e *Engine) log(functionID, level, message string) { - if e.opts.Logger != nil { - e.opts.Logger(level, functionID+": "+message) - } + e.opts.Logger(level, functionID+": "+message) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/wasmrun/wasmrun.go` around lines 301 - 305, The nil check in Engine.log is redundant because e.opts.Logger is always set by NewEngine; remove the if block and call e.opts.Logger directly in the log method (function Engine.log) so it simply invokes e.opts.Logger(level, functionID+": "+message); confirm NewEngine still guarantees a non-nil logger (references: Engine.log, e.opts.Logger, NewEngine).
44-54: 💤 Low value
CacheDirandMaxCacheEntriesoptions are declared but not used.These fields in
Optionssuggest file-backed caching, butNewEnginedoesn't utilize them. If file-backed caching is planned for a future iteration, consider adding a TODO comment or removing unused fields to avoid confusion.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/wasmrun/wasmrun.go` around lines 44 - 54, The Options struct declares CacheDir and MaxCacheEntries but neither is used by NewEngine; either remove these fields from Options or make their intent explicit by adding a TODO comment in the Options definition noting planned file-backed caching and that NewEngine currently ignores them, and if you choose to implement them later, wire them into NewEngine (reference Options.CacheDir, Options.MaxCacheEntries and the NewEngine function) so reviewers won't be confused by unused configuration.cmd/relayfile-cli/digest_functions.go (1)
874-877: 💤 Low valueRemove unused function
digestFunctionContentHash.Static analysis indicates this function is defined but never called. Consider removing it to avoid dead code.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/relayfile-cli/digest_functions.go` around lines 874 - 877, Remove the dead helper function digestFunctionContentHash by deleting its definition and also clean up any now-unused imports (sha256, hex) from the file; search for digestFunctionContentHash to confirm there are no callers before removal and run `go vet`/`golangci-lint` to ensure imports and symbols are tidy after deletion.internal/wasmrun/envelope.go (1)
91-120: 💤 Low valueContent hash validation only supports SHA-256 digest length.
The validation accepts different algorithm names (lines 104-109) but enforces SHA-256 digest length (64 hex chars) regardless of algorithm. If only SHA-256 is intentionally supported, consider rejecting non-sha256 algorithm names explicitly for clearer error messages.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/wasmrun/envelope.go` around lines 91 - 120, validateContentHash currently accepts any alphanumeric algorithm name but always enforces a SHA-256 digest length; either explicitly restrict algorithms to "sha256" or validate digest length based on the algorithm. Update validateContentHash to check the parsed algo (variable algo) and if algo is non-empty and not equal to "sha256" return ErrInvalidEnvelope with a clear message like "unsupported content hash algorithm"; if you intend to support other algorithms, map algo to the expected hex length and validate digest length and hex decode accordingly (use ErrInvalidEnvelope for failures), keeping the existing lowercase-hex check on digest.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cmd/relayfile-cli/digest_functions.go`:
- Around line 565-569: Before calling json.Unmarshal on stdout, check the
trimmed stdout bytes (bytes.TrimSpace(stdout.Bytes())) for zero length and
handle it explicitly to avoid the confusing unmarshal error: if empty, return
nil, nil (or a clear error) instead of proceeding to json.Unmarshal; update the
logic around the variables section, stdout and the json.Unmarshal call in
digest_functions.go so the function returns a clear result when the digest
function produces no output.
In `@internal/wasmrun/abi_test.go`:
- Around line 78-91: The test assertion is checking for "echo: hello" but the
fixture sends "hello" through digestFunctionBody → hostLog → recordingLogger
without any "echo: " prefix; update the expectation in the test (the loop
condition comparing entry.message) to look for "hello" instead of "echo: hello"
and adjust the t.Fatalf error message to reflect the expected "info/hello"
(retain the same found logic using entries := logSink.snapshot(), the loop over
entries, and the found boolean).
In `@internal/wasmrun/cache/cache_test.go`:
- Line 116: Tests currently ignore the error returned by rc.Close() (calls like
rc.Close()), which fails errcheck; update each location where rc.Close() is
deferred or called in cache_test.go to check its error and fail the test on
error — for example capture the return (err := rc.Close()) and call t.Fatalf or
use require.NoError(t, err) to report the failure; apply this change to the
three rc.Close() occurrences found in the file.
In `@internal/wasmrun/cache/cache.go`:
- Line 263: The defer os.Remove(tmpPath) and unchecked Close/Remove calls in the
cache write+verify paths must propagate or record I/O errors instead of being
ignored; locate the tmpPath usage and file handles referenced by defer
os.Remove(tmpPath) and the Close calls and change them to check and handle
returned errors (e.g., capture Close/Remove results into variables, and if they
return non-nil either wrap/append them to the function error return or log
them), for example replace blind defers with a deferred function that merges
Close/Remove errors into the named return error (if any) or returns a combined
error using the same error variable used by the surrounding function so failures
in tmp file cleanup and Close are not dropped.
In `@packages/sdk/typescript/src/digest.ts`:
- Around line 113-123: The normalized path can become empty (e.g. input.path ===
"/") and produce invalid files[0].path/entrypoint; in
digestFunctionSourceFromText validate after computing const path =
input.path.replace(/^\/+/, "") that path is non-empty (or normalize it to a safe
value like "."), and ensure entrypoint falls back to that validated value; if
you choose to reject empty paths, throw a clear error mentioning input.path,
otherwise set path to a safe non-empty string before building the returned
object so files[0].path and entrypoint are never empty.
In `@packages/sdk/typescript/src/setup.ts`:
- Around line 538-600: Each digest-function method (deployDigestFunction,
listDigestFunctions, getDigestFunction, disableDigestFunction,
getDigestFunctionLogs) is calling this._setup.requestJson(...) which bypasses
the WorkspaceHandle's token provider; replace those calls with the
WorkspaceHandle-bound request helper (use WorkspaceHandle.requestJson via
this.requestJson(...)) so the workspace token/provider is used for auth, keeping
the same operation, method, path and body/query arguments.
---
Nitpick comments:
In `@cmd/relayfile-cli/digest_functions.go`:
- Around line 874-877: Remove the dead helper function digestFunctionContentHash
by deleting its definition and also clean up any now-unused imports (sha256,
hex) from the file; search for digestFunctionContentHash to confirm there are no
callers before removal and run `go vet`/`golangci-lint` to ensure imports and
symbols are tidy after deletion.
In `@internal/wasmrun/envelope.go`:
- Around line 91-120: validateContentHash currently accepts any alphanumeric
algorithm name but always enforces a SHA-256 digest length; either explicitly
restrict algorithms to "sha256" or validate digest length based on the
algorithm. Update validateContentHash to check the parsed algo (variable algo)
and if algo is non-empty and not equal to "sha256" return ErrInvalidEnvelope
with a clear message like "unsupported content hash algorithm"; if you intend to
support other algorithms, map algo to the expected hex length and validate
digest length and hex decode accordingly (use ErrInvalidEnvelope for failures),
keeping the existing lowercase-hex check on digest.
In `@internal/wasmrun/host.go`:
- Around line 187-215: The in-place filtering in Engine.scopedChangeEvents
reuses the backing array from EventsProvider.ChangeEvents which is safe here but
subtle; add a short clarifying comment. Edit the scopedChangeEvents function to
insert a brief comment above `filtered := events[:0]` explaining that
EventsProvider.ChangeEvents returns a fresh slice and that the final `return
append([]ChangeEvent(nil), filtered...)` makes a copy before returning, so
reusing the backing array is intentional and safe; reference the calls to
EventsProvider.ChangeEvents, normalizePatterns and matchesAny so reviewers can
locate the logic quickly.
In `@internal/wasmrun/wasmrun.go`:
- Around line 301-305: The nil check in Engine.log is redundant because
e.opts.Logger is always set by NewEngine; remove the if block and call
e.opts.Logger directly in the log method (function Engine.log) so it simply
invokes e.opts.Logger(level, functionID+": "+message); confirm NewEngine still
guarantees a non-nil logger (references: Engine.log, e.opts.Logger, NewEngine).
- Around line 44-54: The Options struct declares CacheDir and MaxCacheEntries
but neither is used by NewEngine; either remove these fields from Options or
make their intent explicit by adding a TODO comment in the Options definition
noting planned file-backed caching and that NewEngine currently ignores them,
and if you choose to implement them later, wire them into NewEngine (reference
Options.CacheDir, Options.MaxCacheEntries and the NewEngine function) so
reviewers won't be confused by unused configuration.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 402b8e2e-8389-4052-b032-7ea285dd29fc
⛔ Files ignored due to path filters (2)
go.sumis excluded by!**/*.suminternal/wasmrun/quickjs.wasmis excluded by!**/*.wasm
📒 Files selected for processing (37)
cmd/relayfile-cli/digest_functions.gocmd/relayfile-cli/digest_functions_test.gocmd/relayfile-cli/main.gocmd/relayfile-cli/main_test.gocmd/relayfile-cli/testdata/digest/eng-roadmap.jscmd/relayfile-cli/testdata/digest/eng-roadmap.tscmd/relayfile-cli/testdata/digest/events.jsondocs/custom-digest-functions-spec.mddocs/workspace-primitives-spec.mdgo.modinternal/digest/digest.gointernal/digest/digest_test.gointernal/digest/render.gointernal/digest/testdata/customer/customer-failure-fixture.mdinternal/digest/testdata/customer/customer-merged-events.jsonlinternal/digest/testdata/customer/customer-merged-fixture.mdinternal/digest/testdata/customer/customer-oom-fixture.mdinternal/digest/testdata/customer/customer-timeout-fixture.mdinternal/digest/types.gointernal/mountsync/watcher_test.gointernal/wasmrun/abi_test.gointernal/wasmrun/cache/cache.gointernal/wasmrun/cache/cache_test.gointernal/wasmrun/determinism.gointernal/wasmrun/determinism_test.gointernal/wasmrun/engine.gointernal/wasmrun/envelope.gointernal/wasmrun/envelope_test.gointernal/wasmrun/host.gointernal/wasmrun/quickjs_embed.gointernal/wasmrun/wasm_fixture_test.gointernal/wasmrun/wasmrun.gointernal/wasmrun/wasmrun_test.gopackages/sdk/typescript/src/digest.tspackages/sdk/typescript/src/index.tspackages/sdk/typescript/src/setup.test.tspackages/sdk/typescript/src/setup.ts
✅ Files skipped from review due to trivial changes (4)
- cmd/relayfile-cli/testdata/digest/eng-roadmap.js
- internal/digest/testdata/customer/customer-merged-events.jsonl
- cmd/relayfile-cli/testdata/digest/events.json
- internal/digest/render.go
| var section *wasmrun.DigestSection | ||
| if err := json.Unmarshal(bytes.TrimSpace(stdout.Bytes()), §ion); err != nil { | ||
| return nil, fmt.Errorf("parse digest function output: %w", err) | ||
| } | ||
| return section, nil |
There was a problem hiding this comment.
Handle empty stdout before JSON unmarshaling.
If the digest function returns nothing or the process succeeds with empty stdout, json.Unmarshal on empty/whitespace bytes will fail with a confusing error. Consider checking for empty output first.
Proposed fix
+ output := bytes.TrimSpace(stdout.Bytes())
+ if len(output) == 0 {
+ return nil, nil
+ }
var section *wasmrun.DigestSection
- if err := json.Unmarshal(bytes.TrimSpace(stdout.Bytes()), §ion); err != nil {
+ if err := json.Unmarshal(output, §ion); err != nil {
return nil, fmt.Errorf("parse digest function output: %w", err)
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cmd/relayfile-cli/digest_functions.go` around lines 565 - 569, Before calling
json.Unmarshal on stdout, check the trimmed stdout bytes
(bytes.TrimSpace(stdout.Bytes())) for zero length and handle it explicitly to
avoid the confusing unmarshal error: if empty, return nil, nil (or a clear
error) instead of proceeding to json.Unmarshal; update the logic around the
variables section, stdout and the json.Unmarshal call in digest_functions.go so
the function returns a clear result when the digest function produces no output.
| entries := logSink.snapshot() | ||
| if len(entries) == 0 { | ||
| t.Fatal("host_log did not deliver any message to Logger") | ||
| } | ||
| found := false | ||
| for _, entry := range entries { | ||
| if entry.level == "info" && entry.message == "echo: hello" { | ||
| found = true | ||
| break | ||
| } | ||
| } | ||
| if !found { | ||
| t.Fatalf("expected info/echo: hello from host_log, got %#v", entries) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check what message the WASM fixture sends
rg -n 'message.*:=.*\[\]byte' internal/wasmrun/wasm_fixture_test.go
# Check what the test expects
rg -n 'echo: hello' internal/wasmrun/abi_test.goRepository: AgentWorkforce/relayfile
Length of output: 240
🏁 Script executed:
# Get broader context around the message in the fixture
sed -n '20,40p' internal/wasmrun/wasm_fixture_test.go
# Get broader context around the test assertion
sed -n '70,91p' internal/wasmrun/abi_test.go
# Check if there's any message preprocessing/transformation
rg -n 'echo' internal/wasmrun/wasm_fixture_test.goRepository: AgentWorkforce/relayfile
Length of output: 1436
🏁 Script executed:
# Look at the digestFunctionBody implementation
rg -n 'digestFunctionBody' internal/wasmrun/wasm_fixture_test.go -A 20
# Check the provider implementation - look for where "echo:" might be added
rg -n '"echo' internal/wasmrun/ --type go
# Check the actual logging implementation
rg -n 'host_log' internal/wasmrun/ --type go -B 2 -A 5Repository: AgentWorkforce/relayfile
Length of output: 5983
🏁 Script executed:
# Find the host_log implementation
rg -n 'func.*hostLog|host_log' internal/wasmrun/host.go -A 15
# Check if there's any message transformation in the logging
rg -n 'FunctionID' internal/wasmrun/host.go -B 5 -A 5Repository: AgentWorkforce/relayfile
Length of output: 1131
🏁 Script executed:
# Get more of the hostLog implementation
sed -n '70,110p' internal/wasmrun/host.goRepository: AgentWorkforce/relayfile
Length of output: 1103
🏁 Script executed:
# Check the logger implementation - look for recordingLogger
rg -n 'type recordingLogger|func.*recordingLogger' internal/wasmrun/abi_test.go -A 10Repository: AgentWorkforce/relayfile
Length of output: 579
🏁 Script executed:
# Check the full test from setup through the assertion
sed -n '20,92p' internal/wasmrun/abi_test.goRepository: AgentWorkforce/relayfile
Length of output: 2035
🏁 Script executed:
# Look at the buildEchoModule function to see if it modifies the message
rg -n 'func buildEchoModule' internal/wasmrun/wasm_fixture_test.go -A 5Repository: AgentWorkforce/relayfile
Length of output: 345
🏁 Script executed:
# Search for any code that might add "echo: " prefix to messages
rg -n 'echo:' internal/wasmrun/ --type goRepository: AgentWorkforce/relayfile
Length of output: 267
Test assertion expects message that fixture doesn't send.
The test expects message == "echo: hello" at line 84, but wasm_fixture_test.go at line 29 defines message := []byte("hello"). This message flows directly through digestFunctionBody → hostLog (which reads it from memory unchanged) → recordingLogger (which stores it as-is). There is no code that adds the "echo: " prefix.
Update the test assertion to expect "hello" instead of "echo: hello":
Proposed fix
- if entry.level == "info" && entry.message == "echo: hello" {
+ if entry.level == "info" && entry.message == "hello" {Also update the error message on line 90:
- t.Fatalf("expected info/echo: hello from host_log, got %#v", entries)
+ t.Fatalf("expected info/hello from host_log, got %#v", entries)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| entries := logSink.snapshot() | |
| if len(entries) == 0 { | |
| t.Fatal("host_log did not deliver any message to Logger") | |
| } | |
| found := false | |
| for _, entry := range entries { | |
| if entry.level == "info" && entry.message == "echo: hello" { | |
| found = true | |
| break | |
| } | |
| } | |
| if !found { | |
| t.Fatalf("expected info/echo: hello from host_log, got %#v", entries) | |
| } | |
| entries := logSink.snapshot() | |
| if len(entries) == 0 { | |
| t.Fatal("host_log did not deliver any message to Logger") | |
| } | |
| found := false | |
| for _, entry := range entries { | |
| if entry.level == "info" && entry.message == "hello" { | |
| found = true | |
| break | |
| } | |
| } | |
| if !found { | |
| t.Fatalf("expected info/hello from host_log, got %#v", entries) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/wasmrun/abi_test.go` around lines 78 - 91, The test assertion is
checking for "echo: hello" but the fixture sends "hello" through
digestFunctionBody → hostLog → recordingLogger without any "echo: " prefix;
update the expectation in the test (the loop condition comparing entry.message)
to look for "hello" instead of "echo: hello" and adjust the t.Fatalf error
message to reflect the expected "info/hello" (retain the same found logic using
entries := logSink.snapshot(), the loop over entries, and the found boolean).
| if err != nil { | ||
| t.Fatalf("Get(legacy) error = %v", err) | ||
| } | ||
| defer rc.Close() |
There was a problem hiding this comment.
Check rc.Close() errors in tests to satisfy errcheck.
Line 116, Line 185, and Line 213 ignore Close() errors, which is flagged by golangci-lint in this PR.
Suggested patch
- defer rc.Close()
+ defer func() { _ = rc.Close() }()
@@
rc, err := c.Get(h)
if rc != nil {
- rc.Close()
+ _ = rc.Close()
}
@@
- defer rc.Close()
+ defer func() { _ = rc.Close() }()Also applies to: 185-185, 213-213
🧰 Tools
🪛 golangci-lint (2.12.2)
[error] 116-116: Error return value of rc.Close is not checked
(errcheck)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/wasmrun/cache/cache_test.go` at line 116, Tests currently ignore the
error returned by rc.Close() (calls like rc.Close()), which fails errcheck;
update each location where rc.Close() is deferred or called in cache_test.go to
check its error and fail the test on error — for example capture the return (err
:= rc.Close()) and call t.Fatalf or use require.NoError(t, err) to report the
failure; apply this change to the three rc.Close() occurrences found in the
file.
| return err | ||
| } | ||
| tmpPath := tmp.Name() | ||
| defer os.Remove(tmpPath) |
There was a problem hiding this comment.
Handle unchecked Close/Remove returns in cache write+verify paths.
Line 263, Line 267, Line 271, Line 284, Line 286, Line 290, and Line 455 currently ignore returned errors. This trips errcheck and can mask I/O failures.
Suggested patch
- defer os.Remove(tmpPath)
+ defer func() { _ = os.Remove(tmpPath) }()
hasher := sha256.New()
if _, err := io.Copy(tmp, io.TeeReader(src, hasher)); err != nil {
- tmp.Close()
+ _ = tmp.Close()
return err
}
if err := tmp.Sync(); err != nil {
- tmp.Close()
+ _ = tmp.Close()
return err
}
@@
- defer os.Remove(tmpSumPath)
+ defer func() { _ = os.Remove(tmpSumPath) }()
if _, err := io.WriteString(tmpSum, bytesSum); err != nil {
- tmpSum.Close()
+ _ = tmpSum.Close()
return err
}
if err := tmpSum.Sync(); err != nil {
- tmpSum.Close()
+ _ = tmpSum.Close()
return err
}
@@
- defer f.Close()
+ defer func() { _ = f.Close() }()Also applies to: 267-267, 271-271, 284-284, 286-286, 290-290, 455-455
🧰 Tools
🪛 golangci-lint (2.12.2)
[error] 263-263: Error return value of os.Remove is not checked
(errcheck)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/wasmrun/cache/cache.go` at line 263, The defer os.Remove(tmpPath)
and unchecked Close/Remove calls in the cache write+verify paths must propagate
or record I/O errors instead of being ignored; locate the tmpPath usage and file
handles referenced by defer os.Remove(tmpPath) and the Close calls and change
them to check and handle returned errors (e.g., capture Close/Remove results
into variables, and if they return non-nil either wrap/append them to the
function error return or log them), for example replace blind defers with a
deferred function that merges Close/Remove errors into the named return error
(if any) or returns a combined error using the same error variable used by the
surrounding function so failures in tmp file cleanup and Close are not dropped.
| export function digestFunctionSourceFromText(input: { | ||
| readonly path: string; | ||
| readonly contents: string; | ||
| readonly runtime?: DigestRuntime; | ||
| readonly entrypoint?: string; | ||
| }): DigestFunctionSource { | ||
| const path = input.path.replace(/^\/+/, ""); | ||
| return { | ||
| runtime: input.runtime ?? "node20", | ||
| entrypoint: input.entrypoint ?? path, | ||
| files: [ |
There was a problem hiding this comment.
Validate normalized source path/entrypoint before building payload.
input.path can normalize to an empty string (e.g. "/"), which creates invalid files[0].path/entrypoint values and defers failure to server-side handling.
Suggested fix
export function digestFunctionSourceFromText(input: {
readonly path: string;
readonly contents: string;
readonly runtime?: DigestRuntime;
readonly entrypoint?: string;
}): DigestFunctionSource {
- const path = input.path.replace(/^\/+/, "");
+ const path = input.path.replace(/^\/+/, "").trim();
+ if (!path) {
+ throw new Error("path must contain at least one non-slash character");
+ }
+ const entrypoint = (input.entrypoint ?? path).trim();
+ if (!entrypoint) {
+ throw new Error("entrypoint must be a non-empty string");
+ }
return {
runtime: input.runtime ?? "node20",
- entrypoint: input.entrypoint ?? path,
+ entrypoint,
files: [
{
path,
contents: input.contents
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/sdk/typescript/src/digest.ts` around lines 113 - 123, The normalized
path can become empty (e.g. input.path === "/") and produce invalid
files[0].path/entrypoint; in digestFunctionSourceFromText validate after
computing const path = input.path.replace(/^\/+/, "") that path is non-empty (or
normalize it to a safe value like "."), and ensure entrypoint falls back to that
validated value; if you choose to reject empty paths, throw a clear error
mentioning input.path, otherwise set path to a safe non-empty string before
building the returned object so files[0].path and entrypoint are never empty.
| async deployDigestFunction( | ||
| input: DeployDigestFunctionInput | ||
| ): Promise<DigestFunctionDeployResponse> { | ||
| return this._setup.requestJson({ | ||
| operation: "deployDigestFunction", | ||
| method: "POST", | ||
| path: `api/v1/workspaces/${encodeURIComponent(this.workspaceId)}/digest-functions`, | ||
| body: { | ||
| slug: input.slug, | ||
| displayName: input.displayName ?? undefined, | ||
| source: input.source | ||
| } | ||
| }) as Promise<DigestFunctionDeployResponse> | ||
| } | ||
|
|
||
| async listDigestFunctions( | ||
| options: ListDigestFunctionsOptions = {} | ||
| ): Promise<DigestFunctionListResponse> { | ||
| const query = buildQueryString({ | ||
| cursor: options.cursor, | ||
| limit: options.limit | ||
| }) | ||
| return this._setup.requestJson({ | ||
| operation: "listDigestFunctions", | ||
| method: "GET", | ||
| path: `api/v1/workspaces/${encodeURIComponent(this.workspaceId)}/digest-functions${query}` | ||
| }) as Promise<DigestFunctionListResponse> | ||
| } | ||
|
|
||
| async getDigestFunction( | ||
| digestFunctionId: string | ||
| ): Promise<DigestFunctionDetail> { | ||
| return this._setup.requestJson({ | ||
| operation: "getDigestFunction", | ||
| method: "GET", | ||
| path: `api/v1/workspaces/${encodeURIComponent(this.workspaceId)}/digest-functions/${encodeURIComponent(digestFunctionId)}` | ||
| }) as Promise<DigestFunctionDetail> | ||
| } | ||
|
|
||
| async disableDigestFunction( | ||
| digestFunctionId: string | ||
| ): Promise<DigestFunctionDisableResponse> { | ||
| return this._setup.requestJson({ | ||
| operation: "disableDigestFunction", | ||
| method: "POST", | ||
| path: `api/v1/workspaces/${encodeURIComponent(this.workspaceId)}/digest-functions/${encodeURIComponent(digestFunctionId)}/disable` | ||
| }) as Promise<DigestFunctionDisableResponse> | ||
| } | ||
|
|
||
| async getDigestFunctionLogs( | ||
| digestFunctionId: string, | ||
| options: GetDigestFunctionLogsOptions = {} | ||
| ): Promise<DigestFunctionLogsResponse> { | ||
| const query = buildQueryString({ | ||
| since: options.since instanceof Date ? options.since.toISOString() : options.since, | ||
| limit: options.limit | ||
| }) | ||
| return this._setup.requestJson({ | ||
| operation: "getDigestFunctionLogs", | ||
| method: "GET", | ||
| path: `api/v1/workspaces/${encodeURIComponent(this.workspaceId)}/digest-functions/${encodeURIComponent(digestFunctionId)}/logs${query}` | ||
| }) as Promise<DigestFunctionLogsResponse> | ||
| } |
There was a problem hiding this comment.
Use workspace-authenticated request path for digest-function endpoints.
These methods bypass WorkspaceHandle.requestJson(...) and therefore skip the workspace token provider. That can break workspace-scoped auth flows (especially invite/token-refresh paths).
Suggested fix
- return this._setup.requestJson({
+ return this.requestJson({
operation: "deployDigestFunction",
method: "POST",
path: `api/v1/workspaces/${encodeURIComponent(this.workspaceId)}/digest-functions`,
body: {
slug: input.slug,
displayName: input.displayName ?? undefined,
source: input.source
}
}) as Promise<DigestFunctionDeployResponse>
- return this._setup.requestJson({
+ return this.requestJson({
operation: "listDigestFunctions",
method: "GET",
path: `api/v1/workspaces/${encodeURIComponent(this.workspaceId)}/digest-functions${query}`
}) as Promise<DigestFunctionListResponse>
- return this._setup.requestJson({
+ return this.requestJson({
operation: "getDigestFunction",
method: "GET",
path: `api/v1/workspaces/${encodeURIComponent(this.workspaceId)}/digest-functions/${encodeURIComponent(digestFunctionId)}`
}) as Promise<DigestFunctionDetail>
- return this._setup.requestJson({
+ return this.requestJson({
operation: "disableDigestFunction",
method: "POST",
path: `api/v1/workspaces/${encodeURIComponent(this.workspaceId)}/digest-functions/${encodeURIComponent(digestFunctionId)}/disable`
}) as Promise<DigestFunctionDisableResponse>
- return this._setup.requestJson({
+ return this.requestJson({
operation: "getDigestFunctionLogs",
method: "GET",
path: `api/v1/workspaces/${encodeURIComponent(this.workspaceId)}/digest-functions/${encodeURIComponent(digestFunctionId)}/logs${query}`
}) as Promise<DigestFunctionLogsResponse>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/sdk/typescript/src/setup.ts` around lines 538 - 600, Each
digest-function method (deployDigestFunction, listDigestFunctions,
getDigestFunction, disableDigestFunction, getDigestFunctionLogs) is calling
this._setup.requestJson(...) which bypasses the WorkspaceHandle's token
provider; replace those calls with the WorkspaceHandle-bound request helper (use
WorkspaceHandle.requestJson via this.requestJson(...)) so the workspace
token/provider is used for auth, keeping the same operation, method, path and
body/query arguments.
Co-authored-by: Proactive Runtime Bot <agent@agent-relay.com>
Summary
Two implementation specs for the workspace primitives the Just Give the Agent Files blog post and the four
@agent-relay/*skills (AgentWorkforce/skills#39) assume:1.
docs/workspace-primitives-spec.mdThe seven gaps between what the skills document and what code actually ships:
<mount>/digests/yesterday.mdandtoday.md(file exists inmount-verify/but no code regenerates it)digest()/DigestContext/DigestSectionSDK contract inrelayfile-adapters/packages/core/.layout.mdmaterialization (referenced ininternal/mountfuse/layout.go:56but no writer)<resource>/.schema.jsonsibling files for writeback payload discoveryrelayfile writeback list --state pending|deadCLI subcommand (onlystatusandretryexist).error.jsonsidecars in.relay/dead-letter/for structured failure contextwb-<timestamp>.jsondoc convention promoted from skill-doc toLAYOUT.mdEach work item has acceptance criteria, file paths, and dependencies. Suggested shipping order in the spec.
2.
docs/custom-digest-functions-spec.mdFollow-on primitive: customer engineers write TypeScript digest functions that compile to WASM at deploy time and run sandboxed via wazero inside the mount daemon, alongside the first-party adapter digests. Depends on work items 1 + 2 of the base spec.
Covers: TS → QuickJS-in-WASM compilation pipeline, wazero runtime with 5s / 64MB / no-syscall sandbox, host-function bridge for
ctx.changeEvents(), deterministic clock + PRNG,relayfile digest function deploy/list/test/disableCLI, control-plane storage, trust model and quotas.Why now
The blog post ships claims the four skills document; the audit found that four of the seven primitives don't exist in code. Rather than rewriting the skills to remove the references (the skills are accurate design intent), we're shipping the implementation specs so the features catch up to the docs.
Closes
.claude/skillsbundle PR. Skills live in AgentWorkforce/skills#39 instead.Test plan
mount-verify/or existing code🤖 Generated with Claude Code