Skip to content

feat(runtime): proposal_created carries the proposal body (content)#292

Merged
drewstone merged 1 commit into
mainfrom
feat/proposal-body-inband
Jun 14, 2026
Merged

feat(runtime): proposal_created carries the proposal body (content)#292
drewstone merged 1 commit into
mainfrom
feat/proposal-body-inband

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Why

The proposal_created RuntimeStreamEvent carried only {proposalId, title, status} — the proposal body was dropped at the event boundary. The artifact variant already carries content; proposals didn't. Consequence: every produced-state consumer that grades a proposal's content had to re-fetch the body out-of-band from its own database, because the in-band event never had it. That's the thin-contract root cause behind a class of consumer band-aids.

What

  • types.tsproposal_created gains content?: string (same role + vocabulary as the artifact variant's content). A title-only filing omits it.
  • backends.tsmapCommonBackendEvent populates content from data.content / data.body / record.content, so SSE-emitting backends (sandbox agents) thread the body through.
  • sanitize.ts — surfaces content under includeControlPayloads, mirroring the artifact variant's redaction gating (safe by default).

Additive + backward-compatible — no field removed, no caller breaks. Unblocks every produced-state eval consumer to read the proposal body in-band instead of reaching into the product DB.

899 tests green.

The proposal_created RuntimeStreamEvent carried only {proposalId, title,
status} — the proposal BODY was dropped at the event boundary, so every
produced-state consumer had to re-fetch it out-of-band from its own database to
grade the deliverable. The artifact event already carries content; proposals
now match it.

- types.ts: proposal_created gains content?: string (same role/vocabulary as
  the artifact variant's content).
- backends.ts: mapCommonBackendEvent populates content from data.content /
  data.body / record.content so SSE-emitting backends thread the body.
- sanitize.ts: surface content under includeControlPayloads, mirroring the
  artifact variant's gating.

Additive + backward-compatible: a title-only filing omits content.

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved PR — 086ca079

Blanket team auto-approval is enabled for this reviewer service.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: blanket_auto_approve · 2026-06-14T11:18:47Z

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Value Audit — sound

Verdict sound
Concerns 0 (none)
Heuristic 0.0s
Duplication 0.0s
Interrogation 113.4s (2 bridge agents)
Total 113.4s

💰 Value — sound

Adds optional content to proposal_created stream events, mirroring the existing artifact pattern so proposal bodies flow in-band; clean, additive, and consistent with the codebase.

  • What it does: Adds an optional content?: string field to the proposal_created variant of RuntimeStreamEvent (src/types.ts:371), populates it in mapCommonBackendEvent from data.content / data.body / record.content (src/backends.ts:498), and gates it behind includeControlPayloads in sanitization (src/sanitize.ts:272) — exactly mirroring how artifact carries and redacts its content.
  • Goals it achieves: Eliminates the thin-contract gap where produced-state consumers grading a proposal's body had to re-fetch it out-of-band from their own database; the event stream can now carry the assessable proposal body in-band, like it already does for artifact deliverables.
  • Assessment: Good change. It is additive and backward-compatible (no fields removed, optional field, callers that don't use it are unaffected). It follows the established grain of the codebase by reusing the same vocabulary, mapping fallbacks, and redaction gating already used for artifact content.
  • Better / existing approach: none — this is the right approach. The codebase already models produced deliverable bodies as content?: string on artifact events and gates them under includeControlPayloads. Extending the same design to proposal_created is the minimal, idiomatic fix; no existing equivalent or better architecture was found after searching src/ for proposal_created, includeControlPayloads, and the `st

🎯 Usefulness — sound

A thin, additive fix that mirrors the existing artifact content contract and closes a real event-boundary gap for downstream eval consumers.

  • Integration: Wires correctly. mapCommonBackendEvent in src/backends.ts:498 now threads content through the default backend-event mapper, which is invoked by createIterableBackend (src/backends.ts:67) and the OpenAI-compatible backend tool-delta path (src/backends.ts:785). sanitizeRuntimeStreamEvent in src/sanitize.ts:272 gates it under includeControlPayloads. The proposal_created type is ex
  • Fit with existing patterns: Fits the established artifact pattern precisely: the same field name (content), the same doc-comment vocabulary (same role as content on the artifact variant), the same redaction gate (includeControlPayloads), and the same fallback chain (data.content ?? data.body ?? record.content) already used for artifacts at src/backends.ts:477. No competing pattern exists.
  • Real-world viability: Holds up. The mapping is stateless and uses the existing stringValue helper (src/backends.ts:912), which rejects non-strings and empty strings, so bogus payloads fall back to undefined safely. The redaction path defaults to hiding content and title unless includeControlPayloads is set, matching every other payload-bearing event. No concurrency or error-path complications are introduced

No concerns — sound change, no better or existing approach found. ✅


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260614T112703Z

@tangletools

Copy link
Copy Markdown
Contributor

✅ No Blockers — 086ca079

Readiness 86/100 · Confidence 75/100 · 5 findings (5 low)

deepseek glm aggregate
Readiness 86 86 86
Confidence 75 75 75
Correctness 86 86 86
Security 86 86 86
Testing 86 86 86
Architecture 86 86 86

Full multi-shot audit completed 3/3 planned shots over 3 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 3/3 planned shots over 3 changed files. Global verifier still owns final merge decision.

🟡 LOW No test coverage for proposal content extraction — src/backends.ts

runtime.test.ts:619 sends {"type":"proposal_created","proposalId":"prop-9","title":"File amendment","status":"pending"} with no content/body field, and the assertion at line 640-645 does not check content. The new line at backends.ts:498 (content: stringValue(data.content) ?? stringValue(data.body) ?? stringValue(record.content)) is therefore untested. The artifact sibling at runtime.test.ts:618-637 includes "content":"# Brief body" in wire data and asserts it. Fix: add "content":"Amendment body text" to the proposal wire data at line 619

🟡 LOW proposal_created content field not exercise in tests — src/sanitize.ts

Neither the redaction test (line 806 in tests/runtime.test.ts) nor the opt-in test (line 886) sends content on proposal_created events. The redaction test only validates title (line 846), and the opt-in test only checks title inclusion (line 912) plus loose toMatchObject ([line 916](https://github.com/tangle-network/

🟡 LOW proposal_created.content sanitize path is not exercised by any test fixture — src/sanitize.ts

Both relevant test fixtures (tests/runtime.test.ts:806-814 redaction case and :885-893 opt-in case) construct proposal_created events WITHOUT a content field. grep confirms content: appears only on artifact events (lines 637, 802, 881), never on proposal_created. Consequence: (a) the redaction test at :846 (expect(serialized).not.toContain('confidential-proposal-title')) would still pass if the includeControlPayloads gating on content were removed, because there's no content payload to leak; (b) the opt-in test at :911-912 would pass even if the new line at src/sanitize.ts:272 were deleted entirely. Impact is low because the line is a mechanical mirror of the artifact

🟡 LOW Comment verbosity for a single optional field — src/types.ts

Lines 371-373 add a 3-line comment for one optional content?: string field. The comment is accurate and useful (cross-references the artifact variant and produced-state grading), but 3 lines of prose for one field is heavier than the surrounding style — e.g., the artifact variant's content?: string at line 360 carries no comment, and the detailed doc-comments in this file are reserved for non-obvious invariants (see the error?: BackendErrorDetail blocks at [lines 384-394](https://github.com/tangle-network/agent-runtime/blob/086ca0794692b056429715643e2

🟡 LOW No test coverage for new content field on proposal_created — src/types.ts

The new content?: string field on the proposal_created event is not exercised in any test. The wire-mapping test (tests/runtime.test.ts:619) sends a proposal_created SSE payload without content, and the redaction/opt-in tests (tests/runtime.test.ts:~806, ~885) also omit the field. The artifact.content path was added together with its tests — proposal_created.content should get the same treatment. Add an assertion that content maps correctly from data.content/data.body and is redacted when includeControlPayloads is false.


tangletools · 2026-06-14T11:37:33Z · trace

@drewstone drewstone merged commit 0376f2c into main Jun 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants